From 15cb9bec4002c0f41511cefae893c618df2de6f6 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 29 Aug 2020 21:55:59 +0200 Subject: [PATCH 1/2] Updates, new classes and fixes **AI_FORMATION** - Performance improvents. This also affects the **RESCUEHELO** class. **EVENT** - Added events when object is of category BASE. **FSM** - Removed a few tracing calls. - Updated docs. **POINT** - Added Update Vec functions to COORDINATE. - Added *overwrite* option to COORDINATE:Translate() function. - Removed second COORDINATE:Translate() function. - Optimized COORDINATE:GetClosestAirbase() function. - Added *Offset* parameter to COORDINATE:IsLOS() function. **RADIO** - Updated BEACON type and system enums. **RADIOQUEUE** - Use Vec3 instead of COORDINATE. Performance improvement. **SET** - Added some functions. **TIMER** - Added new class. Little sister of SCHEDULER class. **DCS** - Minor changes regarding docs. **ATIS** - Added "Miles.ogg", "StatuteMiles.ogg", "Zulu.ogg". **ENUMS** - Added ENUMS.Formation.Vehicle - Added ENUMS.AlarmState ** PROFILER** - Added new lua profiler. **UTILS** - Minor changes and additions. **AIRBASE** - Improved Registration. - Improved Parking spot handling. - Aded :IsAirdrome(), IsHelipad(), IsShip() functions. - Improved :GetRunwayData() for Syria airports. **CONTROLLABLE** - Fixed bug in :CommandSetFrequency() fuction (Hz vs. MHz). - Updated/fixed :TaskFAC_AttackGroup() function. **GROUP** - Added :GetThreatLevel() function. - Added :IsInZone() function to check if any unit is in the zone. **MARKER** - Added new class handling F10 markers using FSM. --- Moose Development/Moose/AI/AI_Formation.lua | 500 ++-- Moose Development/Moose/Core/Base.lua | 5 +- Moose Development/Moose/Core/Database.lua | 358 +-- Moose Development/Moose/Core/Event.lua | 10 + Moose Development/Moose/Core/Fsm.lua | 245 +- Moose Development/Moose/Core/Point.lua | 213 +- Moose Development/Moose/Core/Radio.lua | 93 +- Moose Development/Moose/Core/RadioQueue.lua | 30 +- Moose Development/Moose/Core/Set.lua | 2559 +++++++++-------- Moose Development/Moose/Core/Timer.lua | 250 ++ Moose Development/Moose/DCS.lua | 31 +- Moose Development/Moose/Ops/ATIS.lua | 19 +- .../Moose/Ops/RecoveryTanker.lua | 2 - Moose Development/Moose/Ops/RescueHelo.lua | 4 +- Moose Development/Moose/Utilities/Enums.lua | 20 + .../Moose/Utilities/Profiler.lua | 539 ++++ Moose Development/Moose/Utilities/Utils.lua | 38 +- Moose Development/Moose/Wrapper/Airbase.lua | 857 +++--- .../Moose/Wrapper/Controllable.lua | 31 +- Moose Development/Moose/Wrapper/Group.lua | 99 +- Moose Development/Moose/Wrapper/Marker.lua | 776 +++++ .../Moose/Wrapper/Positionable.lua | 157 +- 22 files changed, 4576 insertions(+), 2260 deletions(-) create mode 100644 Moose Development/Moose/Core/Timer.lua create mode 100644 Moose Development/Moose/Utilities/Profiler.lua create mode 100644 Moose Development/Moose/Wrapper/Marker.lua diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 2ecca0958..c1bfe5757 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -164,7 +164,7 @@ AI_FORMATION.__Enum.ReportType = { --- MENUPARAM type -- @type MENUPARAM -- @field #AI_FORMATION ParamSelf --- @field #Distance ParamDistance +-- @field #number ParamDistance -- @field #function ParamFunction -- @field #string ParamMessage @@ -207,9 +207,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -222,9 +222,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLine Trigger for AI_FORMATION @@ -232,9 +232,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLine Asynchronous Trigger for AI_FORMATION @@ -243,9 +243,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationTrail", "*" ) @@ -257,7 +257,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @return #boolean --- FormationTrail Handler OnAfter for AI_FORMATION @@ -268,14 +268,14 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. --- FormationTrail Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationTrail -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. --- FormationTrail Asynchronous Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] __FormationTrail @@ -283,7 +283,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. self:AddTransition( "*", "FormationStack", "*" ) --- FormationStack Handler OnBefore for AI_FORMATION @@ -294,7 +294,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. -- @return #boolean @@ -306,7 +306,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- FormationStack Trigger for AI_FORMATION @@ -314,7 +314,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- FormationStack Asynchronous Trigger for AI_FORMATION @@ -323,7 +323,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. self:AddTransition( "*", "FormationLeftLine", "*" ) @@ -335,8 +335,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -348,16 +348,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftLine Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationLeftLine -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftLine Asynchronous Trigger for AI_FORMATION @@ -365,8 +365,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationRightLine", "*" ) @@ -378,8 +378,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -391,16 +391,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightLine Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationRightLine -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightLine Asynchronous Trigger for AI_FORMATION @@ -408,8 +408,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationLeftWing", "*" ) @@ -422,8 +422,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -436,8 +436,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftWing Trigger for AI_FORMATION @@ -445,8 +445,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftWing Asynchronous Trigger for AI_FORMATION @@ -455,8 +455,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationRightWing", "*" ) @@ -469,8 +469,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -483,8 +483,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightWing Trigger for AI_FORMATION @@ -492,8 +492,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightWing Asynchronous Trigger for AI_FORMATION @@ -502,8 +502,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationCenterWing", "*" ) @@ -516,9 +516,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -531,9 +531,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationCenterWing Trigger for AI_FORMATION @@ -541,9 +541,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationCenterWing Asynchronous Trigger for AI_FORMATION @@ -552,9 +552,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationVic", "*" ) @@ -566,9 +566,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -580,9 +580,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationVic Trigger for AI_FORMATION @@ -590,9 +590,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationVic Asynchronous Trigger for AI_FORMATION @@ -601,9 +601,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationBox", "*" ) @@ -615,9 +615,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. -- @return #boolean @@ -630,9 +630,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -641,9 +641,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -653,9 +653,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -704,9 +704,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1 @@ -751,7 +751,7 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1 @@ -769,7 +769,7 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1 @@ -789,8 +789,8 @@ end -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 @@ -808,8 +808,8 @@ end -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 @@ -828,8 +828,8 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 @@ -848,8 +848,8 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 @@ -867,9 +867,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 @@ -905,9 +905,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 @@ -924,9 +924,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. -- @return #AI_FORMATION @@ -1065,7 +1065,7 @@ end -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. -- @param #string From From state. -- @param #string Event Event. --- @pram #string To The to state. +-- @param #string To The to state. function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1 self:E("Stopping formation.") end @@ -1075,7 +1075,7 @@ end -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. -- @param #string From From state. -- @param #string Event Event. --- @pram #string To The to state. +-- @param #string To The to state. function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 if From=="Stopped" then return false -- Deny transition. @@ -1083,7 +1083,12 @@ function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 return true end ---- @param #AI_FORMATION self +--- Enter following state. +-- @param #AI_FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To The to state. function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 if self.FollowUnit:IsAlive() then @@ -1093,153 +1098,184 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 local CT1, CT2, CV1, CV2 CT1 = ClientUnit:GetState( self, "CT1" ) + local CuVec3=ClientUnit:GetVec3() + if CT1 == nil or CT1 == 0 then - ClientUnit:SetState( self, "CV1", ClientUnit:GetPointVec3() ) + ClientUnit:SetState( self, "CV1", CuVec3) ClientUnit:SetState( self, "CT1", timer.getTime() ) else CT1 = ClientUnit:GetState( self, "CT1" ) CT2 = timer.getTime() CV1 = ClientUnit:GetState( self, "CV1" ) - CV2 = ClientUnit:GetPointVec3() + CV2 = CuVec3 ClientUnit:SetState( self, "CT1", CT2 ) ClientUnit:SetState( self, "CV1", CV2 ) end - FollowGroupSet:ForEachGroupAlive( - --- @param Wrapper.Group#GROUP FollowGroup - -- @param Wrapper.Unit#UNIT ClientUnit - function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 ) - - if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then - - self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) - - FollowGroup:OptionROTEvadeFire() - FollowGroup:OptionROEReturnFire() - - local GroupUnit = FollowGroup:GetUnit( 1 ) - local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) - if FollowFormation then - local FollowDistance = FollowFormation.x - - local GT1 = GroupUnit:GetState( self, "GT1" ) - - if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then - GroupUnit:SetState( self, "GV1", GroupUnit:GetPointVec3() ) - GroupUnit:SetState( self, "GT1", timer.getTime() ) - else - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 - - local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } - local Ca = math.atan2( CDv.x, CDv.z ) - - local GT1 = GroupUnit:GetState( self, "GT1" ) - local GT2 = timer.getTime() - local GV1 = GroupUnit:GetState( self, "GV1" ) - local GV2 = GroupUnit:GetPointVec3() - GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GroupUnit:SetState( self, "GT1", GT2 ) - GroupUnit:SetState( self, "GV1", GV2 ) - - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - - -- Calculate the distance - local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } - local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) - local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T - local Position = math.cos( Alpha_R ) - local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 - local Distance = GD * Position + - CS * 0.5 - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.x, GV.z ) - - local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) - local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) - - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local Inclination = ( Distance + FollowFormation.x ) / 10 - if Inclination < -30 then - Inclination = - 30 - end - local CVI = { x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y + Inclination, -- + FollowFormation.y, - y = GH2.y, - z = CV2.z + CS * 10 * math.cos(Ca), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } - - local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) - local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) - - local GDV_Formation = { - x = GDV.x - GVx, - y = GDV.y, - z = GDV.z - GVz - } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Green ) - trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) - end - - - - local Time = 120 - - local Speed = - ( Distance + FollowFormation.x ) / Time - - if Distance > -10000 then - Speed = - ( Distance + FollowFormation.x ) / 60 - end - - if Distance > -2500 then - Speed = - ( Distance + FollowFormation.x ) / 20 - end - - local GS = Speed + CS - - self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) - - - -- Now route the escort to the desired point with the desired speed. - FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) - end - end - end - end, - self, ClientUnit, CT1, CV1, CT2, CV2 - ) + --FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2) + + for _,_group in pairs(FollowGroupSet:GetSet()) do + local group=_group --Wrapper.Group#GROUP + if group and group:IsAlive() then + self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2) + end + end self:__Follow( -self.dtFollow ) end end + +--- Follow me. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup Follow group. +-- @param Wrapper.Unit#UNIT ClientUnit Client Unit. +-- @param DCS#Time CT1 Time +-- @param DCS#Vec3 CV1 Vec3 +-- @param DCS#Time CT2 Time +-- @param DCS#Vec3 CV2 Vec3 +function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) + + if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then + + self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + + FollowGroup:OptionROTEvadeFire() + FollowGroup:OptionROEReturnFire() + + local GroupUnit = FollowGroup:GetUnit( 1 ) + + local GuVec3=GroupUnit:GetVec3() + + local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) + + if FollowFormation then + local FollowDistance = FollowFormation.x + + local GT1 = GroupUnit:GetState( self, "GT1" ) + + if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then + GroupUnit:SetState( self, "GV1", GuVec3) + GroupUnit:SetState( self, "GT1", timer.getTime() ) + else + local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 + local CT = CT2 - CT1 + + local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 + + local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } + local Ca = math.atan2( CDv.x, CDv.z ) + + local GT1 = GroupUnit:GetState( self, "GT1" ) + local GT2 = timer.getTime() + + local GV1 = GroupUnit:GetState( self, "GV1" ) + local GV2 = GuVec3 + + --[[ + GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + ]] + + GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + + + GroupUnit:SetState( self, "GT1", GT2 ) + GroupUnit:SetState( self, "GV1", GV2 ) + + + local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 + local GT = GT2 - GT1 + + + -- Calculate the distance + local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } + local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) + local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T + local Position = math.cos( Alpha_R ) + local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 + local Distance = GD * Position + - CS * 0.5 + + -- Calculate the group direction vector + local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } + + -- Calculate GH2, GH2 with the same height as CV2. + local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } + + -- Calculate the angle of GV to the orthonormal plane + local alpha = math.atan2( GV.x, GV.z ) + + local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) + local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) + + + -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. + -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) + local Inclination = ( Distance + FollowFormation.x ) / 10 + if Inclination < -30 then + Inclination = - 30 + end + + local CVI = { + x = CV2.x + CS * 10 * math.sin(Ca), + y = GH2.y + Inclination, -- + FollowFormation.y, + y = GH2.y, + z = CV2.z + CS * 10 * math.cos(Ca), + } + + -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. + local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } + + -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. + -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. + -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... + local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } + + -- Now we can calculate the group destination vector GDV. + local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } + + local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) + local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) + + local GDV_Formation = { + x = GDV.x - GVx, + y = GDV.y, + z = GDV.z - GVz + } + + -- Debug smoke. + if self.SmokeDirectionVector == true then + trigger.action.smoke( GDV, trigger.smokeColor.Green ) + trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) + end + + + + local Time = 120 + + local Speed = - ( Distance + FollowFormation.x ) / Time + + if Distance > -10000 then + Speed = - ( Distance + FollowFormation.x ) / 60 + end + + if Distance > -2500 then + Speed = - ( Distance + FollowFormation.x ) / 20 + end + + local GS = Speed + CS + + --self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) + + -- Now route the escort to the desired point with the desired speed. + FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) + + end + end + end +end diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 313482880..bdedec8a1 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -26,8 +26,6 @@ -- @module Core.Base -- @image Core_Base.JPG - - local _TraceOnOff = true local _TraceLevel = 1 local _TraceAll = false @@ -256,6 +254,8 @@ end -- @param #BASE Parent is the Parent class that the Child inherits from. -- @return #BASE Child function BASE:Inherit( Child, Parent ) + + -- Create child. local Child = routines.utils.deepCopy( Child ) if Child ~= nil then @@ -271,6 +271,7 @@ function BASE:Inherit( Child, Parent ) --Child:_SetDestructor() end + return Child end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index f691fb143..6bd87bdd8 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1,9 +1,9 @@ ---- **Core** - Manages several databases containing templates, mission objects, and mission information. --- +--- **Core** - Manages several databases containing templates, mission objects, and mission information. +-- -- === --- +-- -- ## Features: --- +-- -- * During mission startup, scan the mission environment, and create / instantiate intelligently the different objects as defined within the mission. -- * Manage database of DCS Group templates (as modelled using the mission editor). -- - Group templates. @@ -20,14 +20,14 @@ -- * Manage database of hits to units and statics. -- * Manage database of destroys of units and statics. -- * Manage database of @{Core.Zone#ZONE_BASE} objects. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Database -- @image Core_Database.JPG @@ -36,9 +36,9 @@ -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- +-- -- Mission designers can use the DATABASE class to refer to: --- +-- -- * STATICS -- * UNITS -- * GROUPS @@ -47,12 +47,12 @@ -- * PLAYERSJOINED -- * PLAYERS -- * CARGOS --- +-- -- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- +-- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- +-- -- @field #DATABASE DATABASE = { ClassName = "DATABASE", @@ -82,6 +82,9 @@ DATABASE = { DESTROYS = {}, ZONES = {}, ZONES_GOAL = {}, + WAREHOUSES = {}, + FLIGHTGROUPS = {}, + FLIGHTCONTROLS = {}, } local _DATABASECoalition = @@ -113,7 +116,7 @@ function DATABASE:New() local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE self:SetEventPriority( 1 ) - + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) @@ -123,11 +126,11 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - + -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - + self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() @@ -136,16 +139,16 @@ function DATABASE:New() self:_RegisterAirbases() self.UNITS_Position = 0 - + --- @param #DATABASE self local function CheckPlayers( self ) - + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )} for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --self:E( { "CoalitionData:", CoalitionData } ) for UnitId, UnitData in pairs( CoalitionData ) do if UnitData and UnitData:isExist() then - + local UnitName = UnitData:getName() local PlayerName = UnitData:getPlayerName() local PlayerUnit = UNIT:Find( UnitData ) @@ -164,10 +167,10 @@ function DATABASE:New() end end end - + --self:E( "Scheduling" ) --PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 ) - + return self end @@ -190,10 +193,10 @@ function DATABASE:AddUnit( DCSUnitName ) self:T( { "Add UNIT:", DCSUnitName } ) local UnitRegister = UNIT:Register( DCSUnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - + table.insert( self.UNITS_Index, DCSUnitName ) end - + return self.UNITS[DCSUnitName] end @@ -202,7 +205,7 @@ end -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) - self.UNITS[DCSUnitName] = nil + self.UNITS[DCSUnitName] = nil end --- Adds a Static based on the Static Name in the DATABASE. @@ -213,7 +216,7 @@ function DATABASE:AddStatic( DCSStaticName ) self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) return self.STATICS[DCSStaticName] end - + return nil end @@ -222,7 +225,7 @@ end -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) - --self.STATICS[DCSStaticName] = nil + --self.STATICS[DCSStaticName] = nil end --- Finds a STATIC based on the StaticName. @@ -254,7 +257,7 @@ function DATABASE:AddAirbase( AirbaseName ) if not self.AIRBASES[AirbaseName] then self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName ) end - + return self.AIRBASES[AirbaseName] end @@ -264,7 +267,7 @@ end -- @param #string AirbaseName The name of the airbase function DATABASE:DeleteAirbase( AirbaseName ) - self.AIRBASES[AirbaseName] = nil + self.AIRBASES[AirbaseName] = nil end --- Finds an AIRBASE based on the AirbaseName. @@ -285,29 +288,29 @@ do -- Zones -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZone( ZoneName ) - + local ZoneFound = self.ZONES[ZoneName] return ZoneFound end - + --- Adds a @{Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZone( ZoneName, Zone ) - + if not self.ZONES[ZoneName] then self.ZONES[ZoneName] = Zone end end - - + + --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZone( ZoneName ) - - self.ZONES[ZoneName] = nil + + self.ZONES[ZoneName] = nil end @@ -324,20 +327,20 @@ do -- Zones self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone ) end - + for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) - + self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone_Polygon ) end end - + end @@ -350,29 +353,29 @@ do -- Zone_Goal -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZoneGoal( ZoneName ) - + local ZoneFound = self.ZONES_GOAL[ZoneName] return ZoneFound end - + --- Adds a @{Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZoneGoal( ZoneName, Zone ) - + if not self.ZONES_GOAL[ZoneName] then self.ZONES_GOAL[ZoneName] = Zone end end - - + + --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZoneGoal( ZoneName ) - - self.ZONES_GOAL[ZoneName] = nil + + self.ZONES_GOAL[ZoneName] = nil end end -- Zone_Goal @@ -382,31 +385,31 @@ do -- cargo -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:AddCargo( Cargo ) - + if not self.CARGOS[Cargo.Name] then self.CARGOS[Cargo.Name] = Cargo end end - - + + --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:DeleteCargo( CargoName ) - - self.CARGOS[CargoName] = nil + + self.CARGOS[CargoName] = nil end - + --- Finds an CARGO based on the CargoName. -- @param #DATABASE self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found CARGO. function DATABASE:FindCargo( CargoName ) - + local CargoFound = self.CARGOS[CargoName] return CargoFound end - + --- Checks if the Template name has a #CARGO tag. -- If yes, the group is a cargo. -- @param #DATABASE self @@ -415,10 +418,10 @@ do -- cargo function DATABASE:IsCargo( TemplateName ) TemplateName = env.getValueDictByKey( TemplateName ) - + local Cargo = TemplateName:match( "#(CARGO)" ) - return Cargo and Cargo == "CARGO" + return Cargo and Cargo == "CARGO" end --- Private method that registers new Static Templates within the DATABASE Object. @@ -427,7 +430,7 @@ do -- cargo function DATABASE:_RegisterCargos() local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! - + for CargoGroupName, CargoGroup in pairs( Groups ) do self:I( { Cargo = CargoGroupName } ) if self:IsCargo( CargoGroupName ) then @@ -440,12 +443,12 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end - + for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then local CargoInfo = CargoStaticName:match("#CARGO(.*)") @@ -456,7 +459,7 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + if Category == "SLING" then self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) @@ -468,7 +471,7 @@ do -- cargo end end end - + end end -- cargo @@ -514,9 +517,9 @@ function DATABASE:AddGroup( GroupName ) if not self.GROUPS[GroupName] then self:T( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] + end + + return self.GROUPS[GroupName] end --- Adds a player based on the Player Name in the DATABASE. @@ -618,7 +621,7 @@ function DATABASE:Spawn( SpawnTemplate ) for UnitID, UnitData in pairs( SpawnTemplate.units ) do self:AddUnit( UnitData.name ) end - + return SpawnGroup end @@ -650,21 +653,21 @@ end function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name ) - + if not self.Templates.Groups[GroupTemplateName] then self.Templates.Groups[GroupTemplateName] = {} self.Templates.Groups[GroupTemplateName].Status = nil end - + -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then + if GroupTemplate.route and GroupTemplate.route.spans then GroupTemplate.route.spans = nil end - + GroupTemplate.CategoryID = CategoryID GroupTemplate.CoalitionID = CoalitionSide GroupTemplate.CountryID = CountryID - + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName self.Templates.Groups[GroupTemplateName].Template = GroupTemplate self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId @@ -679,7 +682,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) - + self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name self.Templates.Units[UnitTemplate.name].Template = UnitTemplate @@ -697,8 +700,8 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - - UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName + + UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, @@ -727,13 +730,13 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) - + self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} - + StaticTemplate.CategoryID = CategoryID StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CountryID = CountryID - + self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] @@ -744,12 +747,12 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, - Country = self.Templates.Statics[StaticTemplateName].CountryID + Country = self.Templates.Statics[StaticTemplateName].CountryID } ) - + self:AddStatic( StaticTemplateName ) - + end @@ -817,7 +820,7 @@ function DATABASE:_RegisterPlayers() end end end - + return self end @@ -833,12 +836,12 @@ function DATABASE:_RegisterGroupsAndUnits() if DCSGroup:isExist() then local DCSGroupName = DCSGroup:getName() - + self:I( { "Register Group:", DCSGroupName } ) self:AddGroup( DCSGroupName ) for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - + local DCSUnitName = DCSUnit:getName() self:I( { "Register Unit:", DCSUnitName } ) self:AddUnit( DCSUnitName ) @@ -846,10 +849,10 @@ function DATABASE:_RegisterGroupsAndUnits() else self:E( { "Group does not exist: ", DCSGroup } ) end - + end end - + self:T("Groups:") for GroupName, Group in pairs( self.GROUPS ) do self:T( { "Group:", GroupName } ) @@ -867,7 +870,7 @@ function DATABASE:_RegisterClients() self:T( { "Register Client:", ClientName } ) self:AddClient( ClientName ) end - + return self end @@ -881,7 +884,7 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - + self:T( { "Register Static:", DCSStaticName } ) self:AddStatic( DCSStaticName ) else @@ -893,32 +896,26 @@ function DATABASE:_RegisterStatics() return self end ---- @param #DATABASE self +--- Register all world airbases. +-- @param #DATABASE self +-- @return #DATABASE self function DATABASE:_RegisterAirbases() - --[[ - local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do - - local DCSAirbaseName = DCSAirbase:getName() - - self:T( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } ) - self:AddAirbase( DCSAirbaseName ) - end - end - ]] - for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do - local DCSAirbaseName = DCSAirbase:getName() - - -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! - local airbaseID=DCSAirbase:getID() - - local airbase=self:AddAirbase( DCSAirbaseName ) - - self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) - end + + -- Get the airbase name. + local DCSAirbaseName = DCSAirbase:getName() + + -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. + local airbaseID=DCSAirbase:getID() + + -- Add and register airbase. + local airbase=self:AddAirbase( DCSAirbaseName ) + + -- Debug output. + self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) + + end return self end @@ -934,7 +931,7 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then if Event.IniObjectCategory == 3 then - self:AddStatic( Event.IniDCSUnitName ) + self:AddStatic( Event.IniDCSUnitName ) else if Event.IniObjectCategory == 1 then self:AddUnit( Event.IniDCSUnitName ) @@ -976,7 +973,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniObjectCategory == 3 then if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) - end + end else if Event.IniObjectCategory == 1 then if self.UNITS[Event.IniDCSUnitName] then @@ -985,7 +982,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end end end - + self:AccountDestroys( Event ) end @@ -1039,7 +1036,7 @@ end -- @return #DATABASE self function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) self:F2( arg ) - + local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do @@ -1048,20 +1045,20 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) Count = Count + 1 -- if Count % 100 == 0 then -- coroutine.yield( false ) --- end +-- end end return true end - + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine - + local function Schedule() - + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) - + if status == false then error( res ) end @@ -1076,7 +1073,7 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) Schedule() - + return self end @@ -1087,7 +1084,7 @@ end -- @return #DATABASE self function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) return self @@ -1100,7 +1097,7 @@ end -- @return #DATABASE self function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) return self @@ -1113,7 +1110,7 @@ end -- @return #DATABASE self function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.GROUPS ) return self @@ -1126,9 +1123,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERS ) - + return self end @@ -1139,9 +1136,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerJoined( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERSJOINED ) - + return self end @@ -1151,9 +1148,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERUNITS ) - + return self end @@ -1164,7 +1161,7 @@ end -- @return #DATABASE self function DATABASE:ForEachClient( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CLIENTS ) return self @@ -1176,7 +1173,7 @@ end -- @return #DATABASE self function DATABASE:ForEachCargo( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CARGOS ) return self @@ -1252,8 +1249,44 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings ) self.PLAYERSETTINGS[PlayerName] = Settings end +--- Add a flight group to the data base. +-- @param #DATABASE self +-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup +function DATABASE:AddFlightGroup(flightgroup) + self:I({NewFlightGroup=flightgroup.groupname}) + self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup +end +--- Get a flight group from the data base. +-- @param #DATABASE self +-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object. +-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object. +function DATABASE:GetFlightGroup(groupname) + -- Get group and group name. + if type(groupname)=="string" then + else + groupname=groupname:GetName() + end + + return self.FLIGHTGROUPS[groupname] +end + +--- Add a flight control to the data base. +-- @param #DATABASE self +-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol +function DATABASE:AddFlightControl(flightcontrol) + self:F2( { flightcontrol } ) + self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol +end + +--- Get a flight control object from the data base. +-- @param #DATABASE self +-- @param #string airbasename Name of the associated airbase. +-- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.s +function DATABASE:GetFlightControl(airbasename) + return self.FLIGHTCONTROLS[airbasename] +end --- @param #DATABASE self function DATABASE:_RegisterTemplates() @@ -1267,7 +1300,7 @@ function DATABASE:_RegisterTemplates() if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then --self.Units[coa_name] = {} - + local CoalitionSide = coalition.side[string.upper(CoalitionName)] if CoalitionName=="red" then CoalitionSide=coalition.side.RED @@ -1300,10 +1333,10 @@ function DATABASE:_RegisterTemplates() local CountryName = string.upper(cntry_data.name) local CountryID = cntry_data.id - + self.COUNTRY_ID[CountryName] = CountryID self.COUNTRY_NAME[CountryID] = CountryName - + --self.Units[coa_name][countryName] = {} --self.Units[coa_name][countryName]["countryId"] = cntry_data.id @@ -1322,18 +1355,18 @@ function DATABASE:_RegisterTemplates() for group_num, Template in pairs(obj_type_data.group) do if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterGroupTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) else - self:_RegisterStaticTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterStaticTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) end --if GroupTemplate and GroupTemplate.units then end --for group_num, GroupTemplate in pairs(obj_type_data.group) do @@ -1354,35 +1387,35 @@ end -- @param Core.Event#EVENTDATA Event function DATABASE:AccountHits( Event ) self:F( { Event } ) - + if Event.IniPlayerName ~= nil then -- It is a player that is hitting something self:T( "Hitting Something" ) - + -- What is he hitting? if Event.TgtCategory then - + -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.IniPlayerName] = true end end - + -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then + if Event.WeaponPlayerName ~= nil then self:T( "Hitting Scenery" ) - + -- What is he hitting? if Event.TgtCategory then - + if Event.WeaponCoalition then -- A coalition object was hit, probably a static. -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.WeaponPlayerName] = true else -- A scenery object was hit. @@ -1390,13 +1423,13 @@ end end end end - + --- Account the destroys. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:AccountDestroys( Event ) self:F( { Event } ) - + local TargetUnit = nil local TargetGroup = nil local TargetUnitName = "" @@ -1408,26 +1441,26 @@ end local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil - + if Event.IniDCSUnit then - + TargetUnit = Event.IniUnit TargetUnitName = Event.IniDCSUnitName TargetGroup = Event.IniDCSGroup TargetGroupName = Event.IniDCSGroupName TargetPlayerName = Event.IniPlayerName - + TargetCoalition = Event.IniCoalition --TargetCategory = TargetUnit:getCategory() --TargetCategory = TargetUnit:getDesc().category -- Workaround TargetCategory = Event.IniCategory TargetType = Event.IniTypeName - + TargetUnitType = TargetType - + self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end - + local Destroyed = false -- What is the player destroying? @@ -1436,8 +1469,3 @@ end self.DESTROYS[Event.IniUnitName] = true end end - - - - - diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 156e26153..c19e41d4c 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1001,6 +1001,16 @@ function EVENT:onEvent( Event ) Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1! end + + if Event.IniObjectCategory == Object.Category.BASE then + Event.IniDCSUnit = Event.initiator + Event.IniDCSUnitName = Event.IniDCSUnit:getName() + Event.IniUnitName = Event.IniDCSUnitName + Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName) + Event.IniCoalition = Event.IniDCSUnit:getCoalition() + Event.IniCategory = Event.IniDCSUnit:getDesc().category + Event.IniTypeName = Event.IniDCSUnit:getTypeName() + end end if Event.target then diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 09d447f65..80dfcb0c5 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -71,7 +71,7 @@ -- -- -- ### Author: **FlightControl** --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -81,6 +81,12 @@ do -- FSM --- @type FSM + -- @field #string ClassName Name of the class. + -- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler. + -- @field #table options Options. + -- @field #table subs Subs. + -- @field #table Scores Scores. + -- @field #string current Current state name. -- @extends Core.Base#BASE @@ -369,8 +375,7 @@ do -- FSM self._EventSchedules = {} self.CallScheduler = SCHEDULER:New( self ) - - + return self end @@ -379,7 +384,6 @@ do -- FSM -- @param #FSM self -- @param #string State A string defining the start state. function FSM:SetStartState( State ) - self._StartState = State self.current = State end @@ -389,7 +393,6 @@ do -- FSM -- @param #FSM self -- @return #string A string containing the start state. function FSM:GetStartState() - return self._StartState or {} end @@ -406,6 +409,7 @@ do -- FSM Transition.Event = Event Transition.To = To + -- Debug message. self:T2( Transition ) self._Transitions[Transition] = Transition @@ -414,9 +418,9 @@ do -- FSM --- Returns a table of the transition rules defined within the FSM. - -- @return #table - function FSM:GetTransitions() - + -- @param #FSM self + -- @return #table Transitions. + function FSM:GetTransitions() return self._Transitions or {} end @@ -448,7 +452,8 @@ do -- FSM --- Returns a table of the SubFSM rules defined within the FSM. - -- @return #table + -- @param #FSM self + -- @return #table Sub processes. function FSM:GetProcesses() self:F( { Processes = self._Processes } ) @@ -480,15 +485,17 @@ do -- FSM end --- Adds an End state. - function FSM:AddEndState( State ) - + -- @param #FSM self + -- @param #string State The FSM state. + function FSM:AddEndState( State ) self._EndStates[State] = State self.endstates[State] = State end --- Returns the End states. - function FSM:GetEndStates() - + -- @param #FSM self + -- @return #table End states. + function FSM:GetEndStates() return self._EndStates or {} end @@ -532,18 +539,22 @@ do -- FSM end --- Returns a table with the scores defined. - function FSM:GetScores() - + -- @param #FSM self + -- @param #table Scores. + function FSM:GetScores() return self._Scores or {} end --- Returns a table with the Subs defined. - function FSM:GetSubs() - + -- @param #FSM self + -- @return #table Sub processes. + function FSM:GetSubs() return self.options.subs end - + --- Load call backs. + -- @param #FSM self + -- @param #table CallBackTable Table of call backs. function FSM:LoadCallBacks( CallBackTable ) for name, callback in pairs( CallBackTable or {} ) do @@ -551,21 +562,34 @@ do -- FSM end end - + + --- 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 ) end - + + --- Sub maps. + -- @param #FSM self + -- @param #table subs Subs. + -- @param #table sub Sub. + -- @param #string name Name. function FSM:_submap( subs, sub, name ) - --self:F( { sub = sub, name = name } ) + subs[sub.From] = subs[sub.From] or {} subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} @@ -578,22 +602,24 @@ do -- FSM 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". + -- @param #string trigger Trigger. + -- @param #table params Parameters. + -- @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)) 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 + + --[[ if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) elseif step == "onbefore" or step == "OnBefore" then @@ -604,14 +630,33 @@ do -- FSM self:T( ":::>" .. step .. params[1] .. " : " .. params[1] .. ">" .. step .. params[1] .. "()" .. " >> " .. params[2] .. " >> " .. params[3] ) else self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) - end + end + ]] + self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value + + -- Error handler. + 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 + + --return self[handler](self, unpack( params )) + + -- Protected call. + local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + return Value end + end - --- @param #FSM self + --- Handler. + -- @param #FSM self + -- @param #string EventName Event name. + -- @param ... Arguments. function FSM._handler( self, EventName, ... ) local Can, To = self:can( EventName ) @@ -621,7 +666,11 @@ do -- FSM end if Can then + + -- From state. local From = self.current + + -- Parameters. local Params = { From, EventName, To, ... } @@ -632,8 +681,8 @@ do -- FSM self["onafter".. EventName] or self["OnAfter".. EventName] or self["onenter".. To] or - self["OnEnter".. To] - then + 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 @@ -653,8 +702,11 @@ do -- FSM end end end + else + local ClassName = self:GetClassName() + if ClassName == "FSM" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To ) end @@ -672,46 +724,56 @@ do -- FSM 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 -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) --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 ) + fsmparent[Event]( fsmparent ) + execute = false end if execute then - self:_call_handler("onafter", EventName, Params, EventName ) - self:_call_handler("OnAfter", EventName, Params, EventName ) - -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! - --if from ~= to then - self:_call_handler("onenter", To, Params, EventName ) - self:_call_handler("OnEnter", To, Params, EventName ) - --end + 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 .. " --> ? " ) @@ -719,37 +781,68 @@ do -- FSM return nil end - + + --- Delayed transition. + -- @param #FSM self + -- @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 + + 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))) 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)) -- 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))) 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. function FSM:_create_transition( EventName ) return function( self, ... ) return self._handler( self, EventName , ... ) end end - + + --- Go sub. + -- @param #FSM self + -- @param #string ParentFrom Parent from state. + -- @param #string ParentEvent Parent event name. + -- @return #table Subs. function FSM:_gosub( ParentFrom, ParentEvent ) local fsmtable = {} if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then @@ -759,9 +852,15 @@ do -- FSM return {} end end - + + --- Is end state. + -- @param #FSM self + -- @param #string Current Current state name. + -- @return #table FSM parent. + -- @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] } ) FSMParent.current = Current @@ -778,9 +877,14 @@ do -- FSM return nil end - + + --- Add to map. + -- @param #FSM self + -- @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 else @@ -788,33 +892,60 @@ do -- FSM Map[From] = Event.To end end + self:T3( { Map, Event } ) end - + + --- Get current state. + -- @param #FSM self + -- @return #string Current FSM state. function FSM:GetState() return self.current end - + + --- Get current state. + -- @param #FSM self + -- @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. + -- @param #boolean If true, FSM is in this state. function FSM:Is( State ) return self.current == State end - + + --- 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) return self.current == state end - + + --- Check if can do an event. + -- @param #FSM self + -- @param #string e Event name. + -- @return #boolean If true, FSM can do the event. + -- @return #string To state. 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 - + + --- Check if cannot do an event. + -- @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) end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0559ed660..97f0fb280 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -175,10 +175,6 @@ do -- COORDINATE -- In order to use the most optimal road system to transport vehicles, the method @{#COORDINATE.GetPathOnRoad}() will calculate -- the most optimal path following the road between two coordinates. -- - -- - -- - -- - -- -- ## 8) Metric or imperial system -- -- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles. @@ -204,23 +200,23 @@ do -- COORDINATE --- @field COORDINATE.WaypointAction COORDINATE.WaypointAction = { - TurningPoint = "Turning Point", - FlyoverPoint = "Fly Over Point", - FromParkingArea = "From Parking Area", + TurningPoint = "Turning Point", + FlyoverPoint = "Fly Over Point", + FromParkingArea = "From Parking Area", FromParkingAreaHot = "From Parking Area Hot", - FromRunway = "From Runway", - Landing = "Landing", - LandingReFuAr = "LandingReFuAr", + FromRunway = "From Runway", + Landing = "Landing", + LandingReFuAr = "LandingReFuAr", } --- @field COORDINATE.WaypointType COORDINATE.WaypointType = { - TakeOffParking = "TakeOffParking", + TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", - TakeOff = "TakeOffParkingHot", - TurningPoint = "Turning Point", - Land = "Land", - LandingReFuAr = "LandingReFuAr", + TakeOff = "TakeOffParkingHot", + TurningPoint = "Turning Point", + Land = "Land", + LandingReFuAr = "LandingReFuAr", } @@ -232,6 +228,7 @@ do -- COORDINATE -- @return #COORDINATE function COORDINATE:New( x, y, z ) + --env.info("FF COORDINATE New") local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE self.x = x self.y = y @@ -303,6 +300,45 @@ do -- COORDINATE return { x = self.x, y = self.z } end + --- Update x,y,z coordinates from a given 3D vector. + -- @param #COORDINATE self + -- @param DCS#Vec3 Vec3 The 3D vector with x,y,z components. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromVec3(Vec3) + + self.x=Vec3.x + self.y=Vec3.y + self.z=Vec3.z + + return self + end + + --- Update x,y,z coordinates from another given COORDINATE. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate The coordinate with the new x,y,z positions. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromCoordinate(Coordinate) + + self.x=Coordinate.x + self.y=Coordinate.y + self.z=Coordinate.z + + return self + end + + --- Update x and z coordinates from a given 2D vector. + -- @param #COORDINATE self + -- @param DCS#Vec2 Vec2 The 2D vector with x,y components. x is overwriting COORDINATE.x while y is overwriting COORDINATE.z. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromVec2(Vec2) + + self.x=Vec2.x + self.z=Vec2.y + + return self + end + + --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self -- @param #number latitude Latitude in decimal degrees. @@ -506,19 +542,28 @@ do -- COORDINATE -- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil). -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height. - -- @return Core.Point#COORDINATE The new calculated COORDINATE. - function COORDINATE:Translate( Distance, Angle, Keepalt ) - local SX = self.x - local SY = self.z - local Radians = (Angle or 0) / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TY = Distance * math.sin( Radians ) + SY - - if Keepalt then - return COORDINATE:NewFromVec3( { x = TX, y=self.y, z = TY } ) + -- @param #boolean Overwrite If true, overwrite the original COORDINATE with the translated one. Otherwise, create a new COODINATE. + -- @return #COORDINATE The new calculated COORDINATE. + function COORDINATE:Translate( Distance, Angle, Keepalt, Overwrite ) + + -- Angle in rad. + local alpha = math.rad((Angle or 0)) + + local x = Distance * math.cos(alpha) + self.x -- New x + local z = Distance * math.sin(alpha) + self.z -- New z + + local y=Keepalt and self.y or land.getHeight({x=x, y=z}) + + if Overwrite then + self.x=x + self.y=y + self.z=z + return self else - return COORDINATE:NewFromVec2( { x = TX, y = TY } ) + --env.info("FF translate with NEW coordinate T="..timer.getTime()) + return COORDINATE:New(x, y, z) end + end --- Rotate coordinate in 2D (x,z) space. @@ -721,12 +766,18 @@ do -- COORDINATE --- Return the 2D distance in meters between the target COORDINATE and the COORDINATE. -- @param #COORDINATE self - -- @param #COORDINATE TargetCoordinate The target COORDINATE. + -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3. -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance( TargetCoordinate ) - local TargetVec3 = TargetCoordinate:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + + local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} + + return UTILS.VecNorm(a) + + --local TargetVec3 = TargetCoordinate:GetVec3() + --local SourceVec3 = self:GetVec3() + + --return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 end --- Returns the temperature in Degrees Celsius. @@ -1086,23 +1137,6 @@ do -- COORDINATE return self end - --- Add a Distance in meters from the COORDINATE horizontal plane, with the given angle, and calculate the new COORDINATE. - -- @param #COORDINATE self - -- @param DCS#Distance Distance The Distance to be added in meters. - -- @param DCS#Angle Angle The Angle in degrees. - -- @return #COORDINATE The new calculated COORDINATE. - function COORDINATE:Translate( Distance, Angle ) - local SX = self.x - local SZ = self.z - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TZ = Distance * math.sin( Radians ) + SZ - - return COORDINATE:New( TX, self.y, TZ ) - end - - - --- Build an air type route point. -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1290,8 +1324,10 @@ do -- COORDINATE RoutePoint.x = self.x RoutePoint.y = self.z - RoutePoint.alt = self:GetLandHeight()+1 -- self.y + RoutePoint.alt = self:GetLandHeight()+1 RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO + + RoutePoint.type = "Turning Point" RoutePoint.action = Formation or "Off Road" RoutePoint.formation_template="" @@ -1351,7 +1387,7 @@ do -- COORDINATE -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase(Category, Coalition) + function COORDINATE:GetClosestAirbase2(Category, Coalition) -- Get all airbases of the map. local airbases=AIRBASE.GetAllAirbases(Coalition) @@ -1384,6 +1420,36 @@ do -- COORDINATE return closest,distmin end + + --- Gets the nearest airbase with respect to the current coordinates. + -- @param #COORDINATE self + -- @param #number Category (Optional) Category of the airbase. Enumerator of @{Wrapper.Airbase#AIRBASE.Category}. + -- @param #number Coalition (Optional) Coalition of the airbase. + -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. + -- @return #number Distance to the closest airbase in meters. + function COORDINATE:GetClosestAirbase(Category, Coalition) + + local a=self:GetVec3() + + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + --env.info(string.format("Airbase %s dist=%d category=%d", DCSairbase:getName(), dist, DCSairbase:getCategory())) + + if dist=2 then for i=1,#Path-1 do @@ -1564,7 +1628,7 @@ do -- COORDINATE end else -- There are cases where no path on road can be found. - return nil,nil + return nil,nil,false end return Path, Way, GotPath @@ -1921,15 +1985,18 @@ do -- COORDINATE --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self -- @param #COORDINATE ToCoordinate + -- @param #number Offset Height offset in meters. Default 2 m. -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. - function COORDINATE:IsLOS( ToCoordinate ) + function COORDINATE:IsLOS( ToCoordinate, Offset ) + + Offset=Offset or 2 -- Measurement of visibility should not be from the ground, so Adding a hypotethical 2 meters to each Coordinate. local FromVec3 = self:GetVec3() - FromVec3.y = FromVec3.y + 2 + FromVec3.y = FromVec3.y + Offset local ToVec3 = ToCoordinate:GetVec3() - ToVec3.y = ToVec3.y + 2 + ToVec3.y = ToVec3.y + Offset local IsLOS = land.isVisible( FromVec3, ToVec3 ) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index 575b45044..175bc2106 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -429,7 +429,7 @@ BEACON = { -- @field #number VOR -- @field #number DME -- @field #number VOR_DME --- @field #number TACAN +-- @field #number TACAN TACtical Air Navigation system. -- @field #number VORTAC -- @field #number RSBN -- @field #number BROADCAST_STATION @@ -440,45 +440,74 @@ BEACON = { -- @field #number ILS_NEAR_HOMER -- @field #number ILS_LOCALIZER -- @field #number ILS_GLIDESLOPE +-- @field #number PRMG_LOCALIZER +-- @field #number PRMG_GLIDESLOPE +-- @field #number ICLS Same as ICLS glideslope. +-- @field #number ICLS_LOCALIZER +-- @field #number ICLS_GLIDESLOPE -- @field #number NAUTICAL_HOMER --- @field #number ICLS BEACON.Type={ - NULL = 0, - VOR = 1, - DME = 2, - VOR_DME = 3, - TACAN = 4, - VORTAC = 5, - RSBN = 32, - BROADCAST_STATION = 1024, - HOMER = 8, - AIRPORT_HOMER = 4104, + NULL = 0, + VOR = 1, + DME = 2, + VOR_DME = 3, + TACAN = 4, + VORTAC = 5, + RSBN = 128, + BROADCAST_STATION = 1024, + HOMER = 8, + AIRPORT_HOMER = 4104, AIRPORT_HOMER_WITH_MARKER = 4136, - ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16456, - ILS_LOCALIZER = 16640, - ILS_GLIDESLOPE = 16896, - NAUTICAL_HOMER = 32776, - ICLS = 131584, + ILS_FAR_HOMER = 16408, + ILS_NEAR_HOMER = 16424, + ILS_LOCALIZER = 16640, + ILS_GLIDESLOPE = 16896, + PRMG_LOCALIZER = 33024, + PRMG_GLIDESLOPE = 33280, + ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE + ICLS_LOCALIZER = 131328, + ICLS_GLIDESLOPE = 131584, + NAUTICAL_HOMER = 65536, + } --- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon -- @type BEACON.System --- @field #number PAR_10 --- @field #number RSBN_5 --- @field #number TACAN --- @field #number TACAN_TANKER --- @field #number ILS_LOCALIZER (This is the one to be used for AA TACAN Tanker!) --- @field #number ILS_GLIDESLOPE --- @field #number BROADCAST_STATION +-- @field #number PAR_10 ? +-- @field #number RSBN_5 Russian VOR/DME system. +-- @field #number TACAN TACtical Air Navigation system on ground. +-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. +-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. +-- @field #number VOR Very High Frequency Omni-Directional Range +-- @field #number ILS_LOCALIZER ILS localizer +-- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number PRGM_LOCALIZER PRGM localizer. +-- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number BROADCAST_STATION Broadcast station. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. +-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. +-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). +-- @field #number ICLS_LOCALIZER Carrier landing system. +-- @field #number ICLS_GLIDESLOPE Carrier landing system. BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, - TACAN_TANKER = 4, - ILS_LOCALIZER = 5, - ILS_GLIDESLOPE = 6, - BROADCAST_STATION = 7, + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, + TACAN_TANKER_X = 4, + TACAN_TANKER_Y = 5, + VOR = 6, + ILS_LOCALIZER = 7, + ILS_GLIDESLOPE = 8, + PRMG_LOCALIZER = 9, + PRMG_GLIDESLOPE = 10, + BROADCAST_STATION = 11, + VORTAC = 12, + TACAN_AA_MODE_X = 13, + TACAN_AA_MODE_Y = 14, + VORDME = 15, + ICLS_LOCALIZER = 16, + ICLS_GLIDESLOPE = 17, } --- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index 1ff08c747..6a217c815 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -17,7 +17,7 @@ -- -- @type RADIOQUEUE -- @field #string ClassName Name of the class "RADIOQUEUE". --- @field #boolean Debug Debug mode. More info. +-- @field #boolean Debugmode Debug mode. More info. -- @field #string lid ID for dcs.log. -- @field #number frequency The radio frequency in Hz. -- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM. @@ -38,7 +38,7 @@ -- @extends Core.Base#BASE RADIOQUEUE = { ClassName = "RADIOQUEUE", - Debug = nil, + Debugmode = nil, lid = nil, frequency = nil, modulation = nil, @@ -55,7 +55,7 @@ RADIOQUEUE = { power = nil, numbers = {}, checking = nil, - schedonce = nil, + schedonce = false, } --- Radio queue transmission data. @@ -375,8 +375,10 @@ function RADIOQUEUE:Broadcast(transmission) sender:SetCommand(commandTransmit) -- Debug message. - local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) + if self.Debugmode then + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll() + end else @@ -388,10 +390,7 @@ function RADIOQUEUE:Broadcast(transmission) -- Try to get positon from sender unit/static. if self.sendername then - local coord=self:_GetRadioSenderCoord() - if coord then - vec3=coord:GetVec3() - end + vec3=self:_GetRadioSenderCoord() end -- Try to get fixed positon. @@ -408,8 +407,10 @@ function RADIOQUEUE:Broadcast(transmission) trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power) -- Debug message. - local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) + if self.Debugmode then + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll() + end end end @@ -532,6 +533,7 @@ function RADIOQUEUE:_GetRadioSender() -- Try the general default. if self.sendername then + -- First try to find a unit sender=UNIT:FindByName(self.sendername) @@ -547,7 +549,7 @@ end --- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. -- @param #RADIOQUEUE self --- @return Core.Point#COORDINATE Coordinate of the sender unit. +-- @return DCS#Vec3 Vector 3D. function RADIOQUEUE:_GetRadioSenderCoord() local vec3=nil @@ -560,7 +562,7 @@ function RADIOQUEUE:_GetRadioSenderCoord() -- Check that sender is alive and an aircraft. if sender and sender:IsAlive() then - return sender:GetCoordinate() + return sender:GetVec3() end -- Now try a static. @@ -568,7 +570,7 @@ function RADIOQUEUE:_GetRadioSenderCoord() -- Check that sender is alive and an aircraft. if sender then - return sender:GetCoordinate() + return sender:GetVec3() end end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f502578bf..df5c431df 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1,24 +1,24 @@ --- **Core** - Define collections of objects to perform bulk actions and logically group objects. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Dynamically maintain collections of objects. -- * Manually modify the collection, by adding or removing objects. -- * Collections of different types. -- * Validate the presence of objects in the collection. -- * Perform bulk actions on collection. --- +-- -- === --- +-- -- Group objects or data of the same type into a collection, which is either: --- +-- -- * Manually managed using the **:Add...()** or **:Remove...()** methods. The initial SET can be filtered with the **@{#SET_BASE.FilterOnce}()** method. -- * Dynamically updated when new objects are created or objects are destroyed using the **@{#SET_BASE.FilterStart}()** method. --- +-- -- Various types of SET_ classes are available: --- +-- -- * @{#SET_GROUP}: Defines a collection of @{Wrapper.Group}s filtered by filter criteria. -- * @{#SET_UNIT}: Defines a colleciton of @{Wrapper.Unit}s filtered by filter criteria. -- * @{#SET_STATIC}: Defines a collection of @{Wrapper.Static}s filtered by filter criteria. @@ -26,21 +26,21 @@ -- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria. -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. --- +-- -- These classes are derived from @{#SET_BASE}, which contains the main methods to manage the collections. --- +-- -- A multitude of other methods are available in the individual set classes that allow to: --- +-- -- * Validate the presence of objects in the SET. -- * Trigger events when objects in the SET change a zone presence. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: **funkyfranky** +-- -- === --- +-- -- @module Core.Set -- @image Core_Sets.JPG @@ -48,38 +48,43 @@ do -- SET_BASE --- @type SET_BASE - -- @field #table Filter - -- @field #table Set - -- @field #table List + -- @field #table Filter Table of filters. + -- @field #table Set Table of objects. + -- @field #table Index Table of indicies. + -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE - - + + --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. -- The default **"time interval"** is after 0.001 seconds. - -- + -- -- ## Add or remove objects from the SET - -- + -- -- Some key core functions are @{Core.Set#SET_BASE.Add} and @{Core.Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. - -- + -- -- ## Define the SET iterator **"yield interval"** and the **"time interval"** - -- + -- -- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). - -- - -- @field #SET_BASE SET_BASE + -- + -- @field #SET_BASE SET_BASE SET_BASE = { ClassName = "SET_BASE", Filter = {}, Set = {}, List = {}, Index = {}, + Database = nil, + CallScheduler=nil, + TimeInterval=nil, + YieldInterval=nil, } - - + + --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -87,14 +92,14 @@ do -- SET_BASE -- -- Define a new SET_BASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. -- DBObject = SET_BASE:New() function SET_BASE:New( Database ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- Core.Set#SET_BASE - + self.Database = Database - + self:SetStartState( "Started" ) - + --- Added Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterAdded -- @param #SET_BASE self @@ -103,10 +108,10 @@ do -- SET_BASE -- @param #string To -- @param #string ObjectName The name of the object. -- @param Object The object. - - + + self:AddTransition( "*", "Added", "*" ) - + --- Removed Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterRemoved -- @param #SET_BASE self @@ -115,19 +120,19 @@ do -- SET_BASE -- @param #string To -- @param #string ObjectName The name of the object. -- @param Object The object. - + self:AddTransition( "*", "Removed", "*" ) - + self.YieldInterval = 10 self.TimeInterval = 0.001 - + self.Set = {} self.Index = {} - + self.CallScheduler = SCHEDULER:New( self ) self:SetEventPriority( 2 ) - + return self end @@ -135,78 +140,78 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:Clear() - + for Name, Object in pairs( self.Set ) do self:Remove( Name ) end - + return self end - - - + + + --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName -- @return Core.Base#BASE The Object found. function SET_BASE:_Find( ObjectName ) - + local ObjectFound = self.Set[ObjectName] return ObjectFound end - - + + --- Gets the Set. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSet() self:F2() - + return self.Set or {} end - + --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSetNames() -- R2.3 self:F2() - + local Names = {} - + for Name, Object in pairs( self.Set ) do table.insert( Names, Name ) end - + return Names end - - + + --- Gets a list of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSetObjects() -- R2.3 self:F2() - + local Objects = {} - + for Name, Object in pairs( self.Set ) do table.insert( Objects, Object ) end - + return Objects end - - + + --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) - + local Object = self.Set[ObjectName] - - if Object then + + if Object then for Index, Key in ipairs( self.Index ) do if Key == ObjectName then table.remove( self.Index, Index ) @@ -220,224 +225,304 @@ do -- SET_BASE end end end - - + + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self - -- @param #string ObjectName - -- @param Core.Base#BASE Object + -- @param #string ObjectName The name of the object. + -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) self:F2( { ObjectName = ObjectName, Object = Object } ) - + -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then self:Remove( ObjectName, true ) end + + -- Add object to set. self.Set[ObjectName] = Object + + -- Add Object name to Index. table.insert( self.Index, ObjectName ) - + + -- Trigger Added event. self:Added( ObjectName, Object ) end - + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. -- @param #SET_BASE self -- @param Wrapper.Object#OBJECT Object -- @return Core.Base#BASE The added BASE Object. function SET_BASE:AddObject( Object ) self:F2( Object.ObjectName ) - + self:T( Object.UnitName ) self:T( Object.ObjectName ) self:Add( Object.ObjectName, Object ) - + end - - - - + + + --- Get the *union* of two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set *B*. + -- @return Core.Set#SET_BASE The union set, i.e. contains objects that are in set *A* **or** in set *B*. + function SET_BASE:GetSetUnion(SetB) + + local union=SET_BASE:New() + + for _,ObjectA in pairs(self.Set) do + union:AddObject(ObjectA) + end + + for _,ObjectB in pairs(SetB.Set) do + union:AddObject(ObjectB) + end + + return union + end + + --- Get the *intersection* of this set, called *A*, and another set. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set other set, called *B*. + -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. + function SET_BASE:GetSetIntersection(SetB) + + local intersection=SET_BASE:New() + + local union=self:GetSetUnion(SetB) + + for _,Object in pairs(union.Set) do + if self:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then + intersection:AddObject(intersection) + end + end + + return intersection + end + + --- Get the *complement* of two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set other set, called *B*. + -- @return Core.Set#SET_BASE The set of objects that are in set *B* but **not** in this set *A*. + function SET_BASE:GetSetComplement(SetB) + + local complement=SET_BASE:New() + + local union=self:GetSetUnion(SetA, SetB) + + for _,Object in pairs(union.Set) do + if SetA:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then + intersection:Add(intersection) + end + end + + return intersection + end + + + --- Compare two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetA First set. + -- @param Core.Set#SET_BASE SetB Set to be merged into first set. + -- @return Core.Set#SET_BASE The set of objects that are included in SetA and SetB. + function SET_BASE:CompareSets(SetA, SetB) + + for _,ObjectB in pairs(SetB.Set) do + if SetA:IsIncludeObject(ObjectB) then + SetA:Add(ObjectB) + end + end + + return SetA + end + + + + --- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName -- @return Core.Base#BASE function SET_BASE:Get( ObjectName ) self:F( ObjectName ) - + local Object = self.Set[ObjectName] - + self:T3( { ObjectName, Object } ) return Object end - + --- Gets the first object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetFirst() - + local ObjectName = self.Index[1] local FirstObject = self.Set[ObjectName] self:T3( { FirstObject } ) - return FirstObject + return FirstObject end - + --- Gets the last object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetLast() - + local ObjectName = self.Index[#self.Index] local LastObject = self.Set[ObjectName] self:T3( { LastObject } ) - return LastObject + return LastObject end - + --- Gets a random object from the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetRandom() - + local RandomItem = self.Set[self.Index[math.random(#self.Index)]] self:T3( { RandomItem } ) return RandomItem end - - + + --- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() - + return self.Index and #self.Index or 0 end - - + + --- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). -- @param #SET_BASE self -- @param #SET_BASE BaseSet -- @return #SET_BASE function SET_BASE:SetDatabase( BaseSet ) - + -- Copy the filter criteria of the BaseSet local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) self.Filter = OtherFilter - + -- Now base the new Set on the BaseSet self.Database = BaseSet:GetSet() return self end - - - + + + --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. -- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. -- @return #SET_BASE self function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - + self.YieldInterval = YieldInterval self.TimeInterval = TimeInterval - + return self end - + --- Define the SET iterator **"limit"**. -- @param #SET_BASE self -- @param #number Limit Defines how many objects are evaluated of the set as part of the Some iterators. The default is 1. -- @return #SET_BASE self function SET_BASE:SetSomeIteratorLimit( Limit ) - + self.SomeIteratorLimit = Limit or 1 - + return self end - + --- Get the SET iterator **"limit"**. -- @param #SET_BASE self -- @return #number Defines how many objects are evaluated of the set as part of the Some iterators. function SET_BASE:GetSomeIteratorLimit() - + return self.SomeIteratorLimit or self:Count() end - - + + --- Filters for the defined collection. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:FilterOnce() - + for ObjectName, Object in pairs( self.Database ) do - + if self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) end end - + return self end - + --- Starts the filtering for the defined collection. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:_FilterStart() - + for ObjectName, Object in pairs( self.Database ) do - + if self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) end end - + -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) --self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - - + + return self end - + --- Starts the filtering of the Dead events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection. - + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - + return self end - + --- Starts the filtering of the Crash events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection. - + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - + return self end - + --- Stops the filtering for the defined collection. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:FilterStop() - + self:UnHandleEvent( EVENTS.Birth ) self:UnHandleEvent( EVENTS.Dead ) self:UnHandleEvent( EVENTS.Crash ) - + return self end - + --- Iterate the SET_BASE while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_BASE self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. -- @return Core.Base#BASE The closest object. function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - + local NearestObject = nil local ClosestDistance = nil - + for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData @@ -450,12 +535,12 @@ do -- SET_BASE end end end - + return NearestObject end - - - + + + ----- Private method that registers all alive players in the mission. ---- @param #SET_BASE self ---- @return #SET_BASE self @@ -474,18 +559,18 @@ do -- SET_BASE -- end -- end -- end - -- + -- -- return self --end - + --- Events - + --- Handles the OnBirth event for the Set. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnBirth( Event ) self:F3( { Event } ) - + if Event.IniDCSUnit then local ObjectName, Object = self:AddInDatabase( Event ) self:T3( ObjectName, Object ) @@ -495,13 +580,13 @@ do -- SET_BASE end end end - + --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) self:F( { Event } ) - + if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then @@ -509,7 +594,7 @@ do -- SET_BASE end end end - + --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event @@ -525,7 +610,7 @@ do -- SET_BASE -- end -- end --end - + --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event @@ -551,9 +636,9 @@ do -- SET_BASE -- end -- end --end - + -- Iterators - + --- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. -- @param #SET_BASE self -- @param #function IteratorFunction The function that will be called. @@ -564,10 +649,10 @@ do -- SET_BASE -- @return #SET_BASE self function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) - + Set = Set or self:GetSet() arg = arg or {} - + local function CoRoutine() local Count = 0 for ObjectID, ObjectData in pairs( Set ) do @@ -583,33 +668,33 @@ do -- SET_BASE Count = Count + 1 -- if Count % self.YieldInterval == 0 then -- coroutine.yield( false ) - -- end + -- end end return true end - + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine - + local function Schedule() - + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) - + if status == false then error( res ) end if res == false then return true -- resume next time the loop end - + return false end - + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() - + return self end @@ -619,12 +704,12 @@ do -- SET_BASE -- @return #SET_BASE self function SET_BASE:ForSome( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) - + Set = Set or self:GetSet() arg = arg or {} - + local Limit = self:GetSomeIteratorLimit() - + local function CoRoutine() local Count = 0 for ObjectID, ObjectData in pairs( Set ) do @@ -643,44 +728,44 @@ do -- SET_BASE end -- if Count % self.YieldInterval == 0 then -- coroutine.yield( false ) - -- end + -- end end return true end - + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine - + local function Schedule() - + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) - + if status == false then error( res ) end if res == false then return true -- resume next time the loop end - + return false end - + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() - + return self end - - + + ----- Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self --function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) -- self:F3( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- -- return self @@ -692,9 +777,9 @@ do -- SET_BASE ---- @return #SET_BASE self --function SET_BASE:ForEachPlayer( IteratorFunction, ... ) -- self:F3( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) - -- + -- -- return self --end -- @@ -705,50 +790,60 @@ do -- SET_BASE ---- @return #SET_BASE self --function SET_BASE:ForEachClient( IteratorFunction, ... ) -- self:F3( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self --end - - - --- Decides whether to include the Object + + + --- Decides whether to include the Object. -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self function SET_BASE:IsIncludeObject( Object ) self:F3( Object ) - + return true end - + + --- Decides whether to include the Object. + -- @param #SET_BASE self + -- @param #table Object + -- @return #SET_BASE self + function SET_BASE:IsInSet(ObjectName) + self:F3( Object ) + + return true + end + --- Gets a string with all the object names. -- @param #SET_BASE self -- @return #string A string with the names of the objects. function SET_BASE:GetObjectNames() self:F3() - + local ObjectNames = "" for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - + return ObjectNames end - + --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self -- @param Core.Base#BASE MasterObject (optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) self:F3() - + local ObjectNames = "" for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end self:F( { MasterObject = MasterObject and MasterObject:GetClassNameAndID(), "Objects in Set:", ObjectNames } ) - + return ObjectNames end @@ -759,60 +854,60 @@ do -- SET_GROUP --- @type SET_GROUP -- @extends Core.Set#SET_BASE - + --- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: - -- + -- -- * Coalitions -- * Categories -- * Countries -- * Starting with certain prefix strings. - -- + -- -- ## SET_GROUP constructor - -- + -- -- Create a new SET_GROUP object with the @{#SET_GROUP.New} method: - -- + -- -- * @{#SET_GROUP.New}: Creates a new SET_GROUP object. - -- + -- -- ## Add or Remove GROUP(s) from SET_GROUP - -- - -- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. + -- + -- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. - -- + -- -- ## SET_GROUP filter criteria - -- + -- -- You can set filter criteria to define the set of groups within the SET_GROUP. -- Filter criteria are defined by: - -- + -- -- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). -- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). -- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). -- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). -- * @{#SET_GROUP.FilterActive}: Builds the SET_GROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! - -- + -- -- For the Category Filter, extra methods have been added: - -- + -- -- * @{#SET_GROUP.FilterCategoryAirplane}: Builds the SET_GROUP from airplanes. -- * @{#SET_GROUP.FilterCategoryHelicopter}: Builds the SET_GROUP from helicopters. -- * @{#SET_GROUP.FilterCategoryGround}: Builds the SET_GROUP from ground vehicles or infantry. -- * @{#SET_GROUP.FilterCategoryShip}: Builds the SET_GROUP from ships. -- * @{#SET_GROUP.FilterCategoryStructure}: Builds the SET_GROUP from structures. - -- - -- + -- + -- -- Once the filter criteria have been set for the SET_GROUP, you can start filtering using: - -- + -- -- * @{#SET_GROUP.FilterStart}: Starts the filtering of the groups within the SET_GROUP and add or remove GROUP objects **dynamically**. -- * @{#SET_GROUP.FilterOnce}: Filters of the groups **once**. - -- + -- -- Planned filter criteria within development are (so these are not yet available): - -- + -- -- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. - -- + -- -- ## SET_GROUP iterators - -- + -- -- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. -- The iterator methods will walk the SET_GROUP set, and call for each element within the set a function that you provide. -- The following iterator methods are currently available within the SET_GROUP: - -- + -- -- * @{#SET_GROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_GROUP. -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- * @{#SET_GROUP.ForEachGroupPartlyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. @@ -820,39 +915,39 @@ do -- SET_GROUP -- -- -- ## SET_GROUP trigger events on the GROUP objects. - -- + -- -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_GROUP. - -- + -- -- ### When a GROUP object crashes or is dead, the SET_GROUP will trigger a **Dead** event. - -- - -- You can handle the event using the OnBefore and OnAfter event handlers. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. -- The event handlers need to have the paramters From, Event, To, GroupObject. -- The GroupObject is the GROUP object that is dead and within the SET_GROUP, and is passed as a parameter to the event handler. -- See the following example: - -- + -- -- -- Create the SetCarrier SET_GROUP collection. -- -- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() - -- + -- -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. -- -- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject ) -- self:F( { GroupObject = GroupObject:GetName() } ) -- end - -- + -- -- While this is a good example, there is a catch. -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: - -- + -- -- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. -- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. -- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. - -- + -- -- function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones ) - -- + -- -- local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER - -- + -- -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. -- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. -- @@ -861,11 +956,11 @@ do -- SET_GROUP -- self.PickupCargo[GroupObject] = nil -- So here I clear the PickupCargo table entry of the self object AI_CARGO_DISPATCHER. -- self.CarrierHome[GroupObject] = nil -- end - -- + -- -- end - -- + -- -- === - -- @field #SET_GROUP SET_GROUP + -- @field #SET_GROUP SET_GROUP SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -889,8 +984,8 @@ do -- SET_GROUP }, }, } - - + + --- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_GROUP self -- @return #SET_GROUP @@ -898,23 +993,23 @@ do -- SET_GROUP -- -- Define a new SET_GROUP Object. This DBObject will contain a reference to all alive GROUPS. -- DBObject = SET_GROUP:New() function SET_GROUP:New() - + -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.GROUPS ) ) -- #SET_GROUP - + self:FilterActive( false ) - + return self end - + --- Gets the Set. -- @param #SET_GROUP self -- @return #SET_GROUP self function SET_GROUP:GetAliveSet() self:F2() - + local AliveSet = SET_GROUP:New() - + -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do local GroupObject=GroupObject --Wrapper.Group#GROUP @@ -924,7 +1019,7 @@ do -- SET_GROUP end end end - + return AliveSet.Set or {} end @@ -933,18 +1028,18 @@ do -- SET_GROUP -- @return Core.Report#REPORT A report of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. function SET_GROUP:GetUnitTypeNames() self:F2() - + local MT = {} -- Message Text local UnitTypes = {} - + local ReportUnitTypes = REPORT:New() - + for GroupID, GroupData in pairs( self:GetSet() ) do local Units = GroupData:GetUnits() for UnitID, UnitData in pairs( Units ) do if UnitData:IsAlive() then local UnitType = UnitData:GetTypeName() - + if not UnitTypes[UnitType] then UnitTypes[UnitType] = 1 else @@ -953,11 +1048,11 @@ do -- SET_GROUP end end end - + for UnitTypeID, UnitType in pairs( UnitTypes ) do ReportUnitTypes:Add( UnitType .. " of " .. UnitTypeID ) end - + return ReportUnitTypes end @@ -965,75 +1060,75 @@ do -- SET_GROUP -- Note that for each unit in the group that is set, a default cargo bay limit is initialized. -- @param Core.Set#SET_GROUP self -- @param Wrapper.Group#GROUP group The group which should be added to the set. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroup( group ) - + self:Add( group:GetName(), group ) - + -- I set the default cargo bay weight limit each time a new group is added to the set. for UnitID, UnitData in pairs( group:GetUnits() ) do UnitData:SetCargoBayWeightLimit() end - + return self end - + --- Add GROUP(s) to SET_GROUP. -- @param Core.Set#SET_GROUP self -- @param #string AddGroupNames A single name or an array of GROUP names. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) - + local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } - + for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) end - + return self end - + --- Remove GROUP(s) from SET_GROUP. -- @param Core.Set#SET_GROUP self -- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - + local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } - + for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) end - + return self end - - - - + + + + --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName -- @return Wrapper.Group#GROUP The found Group. function SET_GROUP:FindGroup( GroupName ) - + local GroupFound = self.Set[GroupName] return GroupFound end - + --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_GROUP self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. -- @return Wrapper.Group#GROUP The closest group. function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - + local NearestGroup = nil --Wrapper.Group#GROUP local ClosestDistance = nil - + for ObjectID, ObjectData in pairs( self.Set ) do if NearestGroup == nil then - NearestGroup = ObjectData + NearestGroup = ObjectData ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) @@ -1043,11 +1138,11 @@ do -- SET_GROUP end end end - + return NearestGroup end - - + + --- Builds a set of groups of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_GROUP self @@ -1065,8 +1160,8 @@ do -- SET_GROUP end return self end - - + + --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self @@ -1084,7 +1179,7 @@ do -- SET_GROUP end return self end - + --- Builds a set of groups out of ground category. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1092,7 +1187,7 @@ do -- SET_GROUP self:FilterCategories( "ground" ) return self end - + --- Builds a set of groups out of airplane category. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1100,7 +1195,7 @@ do -- SET_GROUP self:FilterCategories( "plane" ) return self end - + --- Builds a set of groups out of helicopter category. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1108,7 +1203,7 @@ do -- SET_GROUP self:FilterCategories( "helicopter" ) return self end - + --- Builds a set of groups out of ship category. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1116,7 +1211,7 @@ do -- SET_GROUP self:FilterCategories( "ship" ) return self end - + --- Builds a set of groups out of structure category. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1124,9 +1219,9 @@ do -- SET_GROUP self:FilterCategories( "structure" ) return self end - - - + + + --- Builds a set of groups of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_GROUP self @@ -1144,8 +1239,8 @@ do -- SET_GROUP end return self end - - + + --- Builds a set of groups of defined GROUP prefixes. -- All the groups starting with the given prefixes will be included within the set. -- @param #SET_GROUP self @@ -1163,7 +1258,7 @@ do -- SET_GROUP end return self end - + --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self @@ -1171,31 +1266,31 @@ do -- SET_GROUP -- Include inactive groups if you provide false. -- @return #SET_GROUP self -- @usage - -- + -- -- -- Include only active groups to the set. -- GroupSet = SET_GROUP:New():FilterActive():FilterStart() - -- + -- -- -- Include only active groups to the set of the blue coalition, and filter one time. -- GroupSet = SET_GROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() - -- + -- -- -- Include only active groups to the set of the blue coalition, and filter one time. -- -- Later, reset to include back inactive groups to the set. -- GroupSet = SET_GROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() -- ... logic ... -- GroupSet = SET_GROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() - -- + -- function SET_GROUP:FilterActive( Active ) Active = Active or not ( Active == false ) self.Filter.Active = Active return self end - - + + --- Starts the filtering. -- @param #SET_GROUP self -- @return #SET_GROUP self function SET_GROUP:FilterStart() - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) @@ -1203,19 +1298,19 @@ do -- SET_GROUP self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end - - - + + + return self end - + --- Handles the OnDead or OnCrash event for alive groups set. -- Note: The GROUP object in the SET_GROUP collection will only be removed if the last unit is destroyed of the GROUP. -- @param #SET_GROUP self -- @param Core.Event#EVENTDATA Event function SET_GROUP:_EventOnDeadOrCrash( Event ) self:F( { Event } ) - + if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then @@ -1225,7 +1320,7 @@ do -- SET_GROUP end end end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_GROUP self @@ -1234,17 +1329,17 @@ do -- SET_GROUP -- @return #table The GROUP function SET_GROUP:AddInDatabase( Event ) self:F3( { Event } ) - + if Event.IniObjectCategory == 1 then if not self.Database[Event.IniDCSGroupName] then self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) self:T3( self.Database[Event.IniDCSGroupName] ) end end - + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_GROUP self @@ -1253,58 +1348,58 @@ do -- SET_GROUP -- @return #table The GROUP function SET_GROUP:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] end - + --- Iterate the SET_GROUP and call an iterator function for each GROUP object, providing the GROUP and optional parameters. -- @param #SET_GROUP self -- @param #function IteratorFunction The function that will be called for all GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroup( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for some GROUP objects, providing the GROUP and optional parameters. -- @param #SET_GROUP self -- @param #function IteratorFunction The function that will be called for some GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForSomeGroup( IteratorFunction, ... ) self:F2( arg ) - + self:ForSome( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP object, providing the GROUP and optional parameters. -- @param #SET_GROUP self -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupAlive( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetAliveSet() ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for some **alive** GROUP objects, providing the GROUP and optional parameters. -- @param #SET_GROUP self -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForSomeGroupAlive( IteratorFunction, ... ) self:F2( arg ) - + self:ForSome( IteratorFunction, arg, self:GetAliveSet() ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1312,7 +1407,7 @@ do -- SET_GROUP -- @return #SET_GROUP self function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Group#GROUP GroupObject @@ -1323,10 +1418,10 @@ do -- SET_GROUP return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1334,7 +1429,7 @@ do -- SET_GROUP -- @return #SET_GROUP self function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Group#GROUP GroupObject @@ -1345,10 +1440,10 @@ do -- SET_GROUP return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1356,7 +1451,7 @@ do -- SET_GROUP -- @return #SET_GROUP self function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Group#GROUP GroupObject @@ -1367,10 +1462,10 @@ do -- SET_GROUP return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1389,7 +1484,7 @@ do -- SET_GROUP self:F2(Zone) local Set = self:GetSet() for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsCompletelyInZone(Zone) then + if not GroupData:IsCompletelyInZone(Zone) then return false end end @@ -1403,7 +1498,7 @@ do -- SET_GROUP -- @return #SET_GROUP self function SET_GROUP:ForEachGroupAnyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Group#GROUP GroupObject @@ -1414,11 +1509,11 @@ do -- SET_GROUP return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1437,13 +1532,13 @@ do -- SET_GROUP self:F2(Zone) local Set = self:GetSet() for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + if GroupData:IsCompletelyInZone(Zone) then return true end end return false end - + --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{GROUP} of the @{SET_GROUP} is in @{ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1462,13 +1557,13 @@ do -- SET_GROUP self:F2(Zone) local Set = self:GetSet() for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then + if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then return true end end return false end - + --- Iterate the SET_GROUP and return true if at least one @{GROUP} of the @{SET_GROUP} is partly in @{ZONE}. -- Will return false if a @{GROUP} is fully in the @{ZONE} -- @param #SET_GROUP self @@ -1491,20 +1586,20 @@ do -- SET_GROUP for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP if GroupData:IsCompletelyInZone(Zone) then return false - elseif GroupData:IsPartlyInZone(Zone) then + elseif GroupData:IsPartlyInZone(Zone) then IsPartlyInZone = true -- at least one GROUP is partly in zone end end - + if IsPartlyInZone then return true else return false end end - + --- Iterate the SET_GROUP and return true if no @{GROUP} of the @{SET_GROUP} is in @{ZONE} - -- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the + -- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the -- mission designer to add a dedicated method -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1529,7 +1624,7 @@ do -- SET_GROUP end return true end - + --- Iterate the SET_GROUP and count how many GROUPs are completely in the Zone -- That could easily be done with SET_GROUP:ForEachGroupCompletelyInZone(), but this function -- provides an easy to use shortcut... @@ -1547,13 +1642,13 @@ do -- SET_GROUP local Count = 0 local Set = self:GetSet() for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + if GroupData:IsCompletelyInZone(Zone) then Count = Count + 1 end end return Count end - + --- Iterate the SET_GROUP and count how many UNITs are completely in the Zone -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1573,7 +1668,7 @@ do -- SET_GROUP end return Count end - + --- Iterate the SET_GROUP and count how many GROUPs and UNITs are alive. -- @param #SET_GROUP self -- @return #number The number of GROUPs alive. @@ -1581,14 +1676,14 @@ do -- SET_GROUP function SET_GROUP:CountAlive() local CountG = 0 local CountU = 0 - + local Set = self:GetSet() - + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP if GroupData and GroupData:IsAlive() then - + CountG = CountG + 1 - + --Count Units. for _,_unit in pairs(GroupData:GetUnits()) do local unit=_unit --Wrapper.Unit#UNIT @@ -1597,21 +1692,21 @@ do -- SET_GROUP end end end - + end - + return CountG,CountU end - + ----- Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self --function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) - -- + -- -- return self --end -- @@ -1622,13 +1717,13 @@ do -- SET_GROUP ---- @return #SET_GROUP self --function SET_GROUP:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self --end - - + + --- -- @param #SET_GROUP self -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. @@ -1636,7 +1731,7 @@ do -- SET_GROUP function SET_GROUP:IsIncludeObject( MGroup ) self:F2( MGroup ) local MGroupInclude = true - + if self.Filter.Active ~= nil then local MGroupActive = false self:F( { Active = self.Filter.Active } ) @@ -1645,7 +1740,7 @@ do -- SET_GROUP end MGroupInclude = MGroupInclude and MGroupActive end - + if self.Filter.Coalitions then local MGroupCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -1656,7 +1751,7 @@ do -- SET_GROUP end MGroupInclude = MGroupInclude and MGroupCoalition end - + if self.Filter.Categories then local MGroupCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -1667,7 +1762,7 @@ do -- SET_GROUP end MGroupInclude = MGroupInclude and MGroupCategory end - + if self.Filter.Countries then local MGroupCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do @@ -1678,7 +1773,7 @@ do -- SET_GROUP end MGroupInclude = MGroupInclude and MGroupCountry end - + if self.Filter.GroupPrefixes then local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do @@ -1689,12 +1784,12 @@ do -- SET_GROUP end MGroupInclude = MGroupInclude and MGroupPrefix end - + self:T2( MGroupInclude ) return MGroupInclude end - - + + --- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit. -- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level. -- @param #SET_GROUP self @@ -1719,103 +1814,103 @@ do -- SET_UNIT --- @type SET_UNIT -- @extends Core.Set#SET_BASE - + --- Mission designers can use the SET_UNIT class to build sets of units belonging to certain: - -- + -- -- * Coalitions -- * Categories -- * Countries -- * Unit types -- * Starting with certain prefix strings. - -- + -- -- ## 1) SET_UNIT constructor -- -- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: - -- + -- -- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. - -- + -- -- ## 2) Add or Remove UNIT(s) from SET_UNIT -- - -- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. + -- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. - -- + -- -- ## 3) SET_UNIT filter criteria - -- + -- -- You can set filter criteria to define the set of units within the SET_UNIT. -- Filter criteria are defined by: - -- + -- -- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). -- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). -- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). -- * @{#SET_UNIT.FilterActive}: Builds the SET_UNIT with the units that are only active. Units that are inactive (late activation) won't be included in the set! - -- + -- -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: - -- + -- -- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units **dynamically**. -- * @{#SET_UNIT.FilterOnce}: Filters of the units **once**. - -- + -- -- Planned filter criteria within development are (so these are not yet available): - -- + -- -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. - -- + -- -- ## 4) SET_UNIT iterators - -- + -- -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. -- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. -- The following iterator methods are currently available within the SET_UNIT: - -- + -- -- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. -- * @{#SET_UNIT.ForEachUnitInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence completely in a @{Zone}, providing the UNIT object and optional parameters to the called function. -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence not in a @{Zone}, providing the UNIT object and optional parameters to the called function. - -- + -- -- Planned iterators methods in development are (so these are not yet available): - -- + -- -- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. -- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. - -- + -- -- ## 5) SET_UNIT atomic methods - -- + -- -- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT: - -- + -- -- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by a comma. - -- + -- -- ## 6) SET_UNIT trigger events on the UNIT objects. - -- + -- -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the UNIT objects in the SET_UNIT. - -- + -- -- ### 6.1) When a UNIT object crashes or is dead, the SET_UNIT will trigger a **Dead** event. - -- - -- You can handle the event using the OnBefore and OnAfter event handlers. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. -- The event handlers need to have the paramters From, Event, To, GroupObject. -- The GroupObject is the UNIT object that is dead and within the SET_UNIT, and is passed as a parameter to the event handler. -- See the following example: - -- + -- -- -- Create the SetCarrier SET_UNIT collection. -- -- local SetHelicopter = SET_UNIT:New():FilterPrefixes( "Helicopter" ):FilterStart() - -- + -- -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier unit is destroyed, that all internal parameters are reset. -- -- function SetHelicopter:OnAfterDead( From, Event, To, UnitObject ) -- self:F( { UnitObject = UnitObject:GetName() } ) -- end - -- + -- -- While this is a good example, there is a catch. -- Imageine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. -- See the modified example: - -- + -- -- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. -- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. -- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. - -- + -- -- function ACLASS:New( SetCarrier, SetCargo, SetDeployZones ) - -- + -- -- local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER - -- + -- -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. -- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. -- @@ -1823,7 +1918,7 @@ do -- SET_UNIT -- SetHelicopter:F( { UnitObject = UnitObject:GetName() } ) -- self.array[UnitObject] = nil -- So here I clear the array table entry of the self object ACLASS. -- end - -- + -- -- end -- === -- @field #SET_UNIT SET_UNIT @@ -1852,13 +1947,13 @@ do -- SET_UNIT }, }, } - - + + --- Get the first unit from the set. -- @function [parent=#SET_UNIT] GetFirst -- @param #SET_UNIT self -- @return Wrapper.Unit#UNIT The UNIT object. - + --- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_UNIT self -- @return #SET_UNIT @@ -1866,75 +1961,75 @@ do -- SET_UNIT -- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. -- DBObject = SET_UNIT:New() function SET_UNIT:New() - + -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) -- #SET_UNIT - + self:FilterActive( false ) - + return self end - + --- Add UNIT(s) to SET_UNIT. -- @param #SET_UNIT self -- @param Wrapper.Unit#UNIT Unit A single UNIT. -- @return #SET_UNIT self function SET_UNIT:AddUnit( Unit ) self:F2( Unit:GetName() ) - + self:Add( Unit:GetName(), Unit ) - + -- Set the default cargo bay limit each time a new unit is added to the set. Unit:SetCargoBayWeightLimit() - + return self end - - + + --- Add UNIT(s) to SET_UNIT. -- @param #SET_UNIT self -- @param #string AddUnitNames A single name or an array of UNIT names. -- @return #SET_UNIT self function SET_UNIT:AddUnitsByName( AddUnitNames ) - + local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } - + self:T( AddUnitNamesArray ) for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) end - + return self end - + --- Remove UNIT(s) from SET_UNIT. -- @param Core.Set#SET_UNIT self - -- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. - -- @return self + -- @param #table RemoveUnitNames A single name or an array of UNIT names. + -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - + local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } - + for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do self:Remove( RemoveUnitName ) end - + return self end - - + + --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName -- @return Wrapper.Unit#UNIT The found Unit. function SET_UNIT:FindUnit( UnitName ) - + local UnitFound = self.Set[UnitName] return UnitFound end - - - + + + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self @@ -1951,8 +2046,8 @@ do -- SET_UNIT end return self end - - + + --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_UNIT self @@ -1970,8 +2065,8 @@ do -- SET_UNIT end return self end - - + + --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_UNIT self @@ -1989,8 +2084,8 @@ do -- SET_UNIT end return self end - - + + --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_UNIT self @@ -2008,8 +2103,8 @@ do -- SET_UNIT end return self end - - + + --- Builds a set of units of defined unit prefixes. -- All the units starting with the given prefixes will be included within the set. -- @param #SET_UNIT self @@ -2027,7 +2122,7 @@ do -- SET_UNIT end return self end - + --- Builds a set of units that are only active. -- Only the units that are active will be included within the set. -- @param #SET_UNIT self @@ -2035,32 +2130,32 @@ do -- SET_UNIT -- Include inactive units if you provide false. -- @return #SET_UNIT self -- @usage - -- + -- -- -- Include only active units to the set. -- UnitSet = SET_UNIT:New():FilterActive():FilterStart() - -- + -- -- -- Include only active units to the set of the blue coalition, and filter one time. -- UnitSet = SET_UNIT:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() - -- + -- -- -- Include only active units to the set of the blue coalition, and filter one time. -- -- Later, reset to include back inactive units to the set. -- UnitSet = SET_UNIT:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() -- ... logic ... -- UnitSet = SET_UNIT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() - -- + -- function SET_UNIT:FilterActive( Active ) Active = Active or not ( Active == false ) self.Filter.Active = Active return self end - + --- Builds a set of units having a radar of give types. -- All the units having a radar of a given type will be included within the set. -- @param #SET_UNIT self -- @param #table RadarTypes The radar types. -- @return #SET_UNIT self function SET_UNIT:FilterHasRadar( RadarTypes ) - + self.Filter.RadarTypes = self.Filter.RadarTypes or {} if type( RadarTypes ) ~= "table" then RadarTypes = { RadarTypes } @@ -2070,23 +2165,39 @@ do -- SET_UNIT end return self end - + --- Builds a set of SEADable units. -- @param #SET_UNIT self -- @return #SET_UNIT self function SET_UNIT:FilterHasSEAD() - + self.Filter.SEAD = true return self end - - - + + --- Iterate the SET_UNIT and count how many UNITs are alive. + -- @param #SET_UNIT self + -- @return #number The number of UNITs alive. + function SET_UNIT:CountAlive() + + local Set = self:GetSet() + + local CountU = 0 + for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + if UnitData and UnitData:IsAlive() then + CountU = CountU + 1 + end + + end + + return CountU + end + --- Starts the filtering. -- @param #SET_UNIT self -- @return #SET_UNIT self function SET_UNIT:FilterStart() - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) @@ -2094,12 +2205,12 @@ do -- SET_UNIT self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end - + return self end - - + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self @@ -2108,17 +2219,17 @@ do -- SET_UNIT -- @return #table The UNIT function SET_UNIT:AddInDatabase( Event ) self:F3( { Event } ) - + if Event.IniObjectCategory == 1 then if not self.Database[Event.IniDCSUnitName] then self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) self:T3( self.Database[Event.IniDCSUnitName] ) end end - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_UNIT self @@ -2127,24 +2238,24 @@ do -- SET_UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - + + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - - + + do -- Is Zone methods - + --- Check if minimal one element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneTest The Zone to be tested for. -- @return #boolean function SET_UNIT:IsPartiallyInZone( ZoneTest ) - + local IsPartiallyInZone = false - + local function EvaluateZone( ZoneUnit ) - + local ZoneUnitName = ZoneUnit:GetName() self:F( { ZoneUnitName = ZoneUnitName } ) if self:FindUnit( ZoneUnitName ) then @@ -2152,87 +2263,87 @@ do -- SET_UNIT self:F( { Found = true } ) return false end - + return true end ZoneTest:SearchZone( EvaluateZone ) - + return IsPartiallyInZone end - - + + --- Check if no element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @return #boolean function SET_UNIT:IsNotInZone( Zone ) - + local IsNotInZone = true - + local function EvaluateZone( ZoneUnit ) - + local ZoneUnitName = ZoneUnit:GetName() if self:FindUnit( ZoneUnitName ) then IsNotInZone = false return false end - + return true end - + Zone:SearchZone( EvaluateZone ) - + return IsNotInZone end - + end - - + + --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #SET_UNIT self -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnit( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - - + + --- Get the SET of the SET_UNIT **sorted per Threat Level**. - -- + -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). -- @return #SET_UNIT self -- @usage - -- - -- + -- + -- function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) self:F2( arg ) - + local ThreatLevelSet = {} - + if self:Count() ~= 0 then for UnitName, UnitObject in pairs( self.Set ) do local Unit = UnitObject -- Wrapper.Unit#UNIT - + local ThreatLevel = Unit:GetThreatLevel() ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {} ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {} ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end - - + + local OrderedPerThreatLevelSet = {} - + local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 - + for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2245,44 +2356,44 @@ do -- SET_UNIT return OrderedPerThreatLevelSet end - + end - - + + --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. - -- + -- -- @param #SET_UNIT self -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10). -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10). -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self -- @usage - -- + -- -- UnitSet:ForEachUnitPerThreatLevel( 10, 0, -- -- @param Wrapper.Unit#UNIT UnitObject The UNIT object in the UnitSet, that will be passed to the local function for evaluation. -- function( UnitObject ) -- .. logic .. -- end -- ) - -- + -- function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation self:F2( arg ) - + local ThreatLevelSet = {} - + if self:Count() ~= 0 then for UnitName, UnitObject in pairs( self.Set ) do local Unit = UnitObject -- Wrapper.Unit#UNIT - + local ThreatLevel = Unit:GetThreatLevel() ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {} ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {} ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end - + local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 - + for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2291,12 +2402,12 @@ do -- SET_UNIT end end end - + return self end - - - + + + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2304,7 +2415,7 @@ do -- SET_UNIT -- @return #SET_UNIT self function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Unit#UNIT UnitObject @@ -2315,10 +2426,10 @@ do -- SET_UNIT return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -2326,7 +2437,7 @@ do -- SET_UNIT -- @return #SET_UNIT self function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Unit#UNIT UnitObject @@ -2337,24 +2448,24 @@ do -- SET_UNIT return false end end, { ZoneObject } ) - + return self end - + --- Returns map of unit types. -- @param #SET_UNIT self -- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. function SET_UNIT:GetUnitTypes() self:F2() - + local MT = {} -- Message Text local UnitTypes = {} - + for UnitID, UnitData in pairs( self:GetSet() ) do local TextUnit = UnitData -- Wrapper.Unit#UNIT if TextUnit:IsAlive() then local UnitType = TextUnit:GetTypeName() - + if not UnitTypes[UnitType] then UnitTypes[UnitType] = 1 else @@ -2362,60 +2473,60 @@ do -- SET_UNIT end end end - + for UnitTypeID, UnitType in pairs( UnitTypes ) do MT[#MT+1] = UnitType .. " of " .. UnitTypeID end - + return UnitTypes end - - + + --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_UNIT self -- @return #string The unit types string function SET_UNIT:GetUnitTypesText() self:F2() - + local MT = {} -- Message Text local UnitTypes = self:GetUnitTypes() - + for UnitTypeID, UnitType in pairs( UnitTypes ) do MT[#MT+1] = UnitType .. " of " .. UnitTypeID end - + return table.concat( MT, ", " ) end - + --- Returns map of unit threat levels. -- @param #SET_UNIT self -- @return #table. function SET_UNIT:GetUnitThreatLevels() self:F2() - + local UnitThreatLevels = {} - + for UnitID, UnitData in pairs( self:GetSet() ) do local ThreatUnit = UnitData -- Wrapper.Unit#UNIT if ThreatUnit:IsAlive() then local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() local ThreatUnitName = ThreatUnit:GetName() - + UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit end end - + return UnitThreatLevels end - + --- Calculate the maxium A2G threat level of the SET_UNIT. -- @param #SET_UNIT self -- @return #number The maximum threatlevel function SET_UNIT:CalculateThreatLevelA2G() - + local MaxThreatLevelA2G = 0 local MaxThreatText = "" for UnitName, UnitData in pairs( self:GetSet() ) do @@ -2426,19 +2537,19 @@ do -- SET_UNIT MaxThreatText = ThreatText end end - + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) return MaxThreatLevelA2G, MaxThreatText - + end - + --- Get the center coordinate of the SET_UNIT. -- @param #SET_UNIT self -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units. function SET_UNIT:GetCoordinate() - + local Coordinate = self:GetFirst():GetCoordinate() - + local x1 = Coordinate.x local x2 = Coordinate.x local y1 = Coordinate.y @@ -2448,19 +2559,19 @@ do -- SET_UNIT local MaxVelocity = 0 local AvgHeading = nil local MovingCount = 0 - + for UnitName, UnitData in pairs( self:GetSet() ) do - + local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 - + local Velocity = Coordinate:GetVelocity() if Velocity ~= 0 then MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity @@ -2469,58 +2580,58 @@ do -- SET_UNIT MovingCount = MovingCount + 1 end end - + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) - + Coordinate.x = ( x2 - x1 ) / 2 + x1 Coordinate.y = ( y2 - y1 ) / 2 + y1 Coordinate.z = ( z2 - z1 ) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) - + self:F( { Coordinate = Coordinate } ) return Coordinate - + end - + --- Get the maximum velocity of the SET_UNIT. -- @param #SET_UNIT self -- @return #number The speed in mps in case of moving units. function SET_UNIT:GetVelocity() - + local Coordinate = self:GetFirst():GetCoordinate() - + local MaxVelocity = 0 - + for UnitName, UnitData in pairs( self:GetSet() ) do - + local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - + local Velocity = Coordinate:GetVelocity() if Velocity ~= 0 then MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity end end - + self:F( { MaxVelocity = MaxVelocity } ) return MaxVelocity - + end - + --- Get the average heading of the SET_UNIT. -- @param #SET_UNIT self -- @return #number Heading Heading in degrees and speed in mps in case of moving units. function SET_UNIT:GetHeading() - + local HeadingSet = nil local MovingCount = 0 - + for UnitName, UnitData in pairs( self:GetSet() ) do - + local Unit = UnitData -- Wrapper.Unit#UNIT local Coordinate = Unit:GetCoordinate() - + local Velocity = Coordinate:GetVelocity() if Velocity ~= 0 then local Heading = Coordinate:GetHeading() @@ -2533,23 +2644,23 @@ do -- SET_UNIT HeadingSet = nil break end - end + end end end - + return HeadingSet - + end - - - + + + --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self -- @param DCS#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) self:F2( RadarType ) - + local RadarCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT @@ -2564,40 +2675,40 @@ do -- SET_UNIT RadarCount = RadarCount + 1 end end - + return RadarCount end - + --- Returns if the @{Set} has targets that can be SEADed. -- @param #SET_UNIT self -- @return #number The amount of SEADable units in the Set function SET_UNIT:HasSEAD() self:F2() - + local SEADCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes - + local HasSEAD = UnitSEAD:HasSEAD() - + self:T3(HasSEAD) if HasSEAD then SEADCount = SEADCount + 1 end end end - + return SEADCount end - + --- Returns if the @{Set} has ground targets. -- @param #SET_UNIT self -- @return #number The amount of ground targets in the Set. function SET_UNIT:HasGroundUnits() self:F2() - + local GroundUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do local UnitTest = UnitData -- Wrapper.Unit#UNIT @@ -2605,16 +2716,16 @@ do -- SET_UNIT GroundUnitCount = GroundUnitCount + 1 end end - + return GroundUnitCount end - + --- Returns if the @{Set} has air targets. -- @param #SET_UNIT self -- @return #number The amount of air targets in the Set. function SET_UNIT:HasAirUnits() self:F2() - + local AirUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT @@ -2622,16 +2733,16 @@ do -- SET_UNIT AirUnitCount = AirUnitCount + 1 end end - + return AirUnitCount end - + --- Returns if the @{Set} has friendly ground units. -- @param #SET_UNIT self -- @return #number The amount of ground targets in the Set. function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) self:F2() - + local FriendlyUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do local UnitTest = UnitData -- Wrapper.Unit#UNIT @@ -2639,21 +2750,21 @@ do -- SET_UNIT FriendlyUnitCount = FriendlyUnitCount + 1 end end - + return FriendlyUnitCount end - - - + + + ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_UNIT self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self --function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) - -- + -- -- return self --end -- @@ -2664,13 +2775,13 @@ do -- SET_UNIT ---- @return #SET_UNIT self --function SET_UNIT:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) - -- + -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self --end - - + + --- -- @param #SET_UNIT self -- @param Wrapper.Unit#UNIT MUnit @@ -2681,9 +2792,9 @@ do -- SET_UNIT local MUnitInclude = false if MUnit:IsAlive() ~= nil then - + MUnitInclude = true - + if self.Filter.Active ~= nil then local MUnitActive = false if self.Filter.Active == false or ( self.Filter.Active == true and MUnit:IsActive() == true ) then @@ -2691,7 +2802,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitActive end - + if self.Filter.Coalitions then local MUnitCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -2702,7 +2813,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitCoalition end - + if self.Filter.Categories then local MUnitCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -2713,7 +2824,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitCategory end - + if self.Filter.Types then local MUnitType = false for TypeID, TypeName in pairs( self.Filter.Types ) do @@ -2724,7 +2835,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitType end - + if self.Filter.Countries then local MUnitCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do @@ -2735,7 +2846,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitCountry end - + if self.Filter.UnitPrefixes then local MUnitPrefix = false for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do @@ -2746,7 +2857,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitPrefix end - + if self.Filter.RadarTypes then local MUnitRadar = false for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do @@ -2760,7 +2871,7 @@ do -- SET_UNIT end MUnitInclude = MUnitInclude and MUnitRadar end - + if self.Filter.SEAD then local MUnitSEAD = false if MUnit:HasSEAD() == true then @@ -2770,33 +2881,33 @@ do -- SET_UNIT MUnitInclude = MUnitInclude and MUnitSEAD end end - + self:T2( MUnitInclude ) return MUnitInclude end - - + + --- Retrieve the type names of the @{Wrapper.Unit}s in the SET, delimited by an optional delimiter. -- @param #SET_UNIT self -- @param #string Delimiter (optional) The delimiter, which is default a comma. -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) - + Delimiter = Delimiter or ", " local TypeReport = REPORT:New() local Types = {} - + for UnitName, UnitData in pairs( self:GetSet() ) do - + local Unit = UnitData -- Wrapper.Unit#UNIT local UnitTypeName = Unit:GetTypeName() - + if not Types[UnitTypeName] then Types[UnitTypeName] = UnitTypeName TypeReport:Add( UnitTypeName ) end end - + return TypeReport:Text( Delimiter ) end @@ -2815,7 +2926,7 @@ do -- SET_UNIT end - + end @@ -2823,67 +2934,67 @@ do -- SET_STATIC --- @type SET_STATIC -- @extends Core.Set#SET_BASE - + --- Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain: - -- + -- -- * Coalitions -- * Categories -- * Countries -- * Static types -- * Starting with certain prefix strings. - -- + -- -- ## SET_STATIC constructor -- -- Create a new SET_STATIC object with the @{#SET_STATIC.New} method: - -- + -- -- * @{#SET_STATIC.New}: Creates a new SET_STATIC object. - -- + -- -- ## Add or Remove STATIC(s) from SET_STATIC -- - -- STATICs can be added and removed using the @{Core.Set#SET_STATIC.AddStaticsByName} and @{Core.Set#SET_STATIC.RemoveStaticsByName} respectively. + -- STATICs can be added and removed using the @{Core.Set#SET_STATIC.AddStaticsByName} and @{Core.Set#SET_STATIC.RemoveStaticsByName} respectively. -- These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC. - -- + -- -- ## SET_STATIC filter criteria - -- + -- -- You can set filter criteria to define the set of units within the SET_STATIC. -- Filter criteria are defined by: - -- + -- -- * @{#SET_STATIC.FilterCoalitions}: Builds the SET_STATIC with the units belonging to the coalition(s). -- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies). -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s). -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s). - -- + -- -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: - -- + -- -- * @{#SET_STATIC.FilterStart}: Starts the filtering of the units within the SET_STATIC. - -- + -- -- Planned filter criteria within development are (so these are not yet available): - -- + -- -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Core.Zone#ZONE}. - -- + -- -- ## SET_STATIC iterators - -- + -- -- Once the filters have been defined and the SET_STATIC has been built, you can iterate the SET_STATIC with the available iterator methods. -- The iterator methods will walk the SET_STATIC set, and call for each element within the set a function that you provide. -- The following iterator methods are currently available within the SET_STATIC: - -- + -- -- * @{#SET_STATIC.ForEachStatic}: Calls a function for each alive unit it finds within the SET_STATIC. -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. - -- + -- -- Planned iterators methods in development are (so these are not yet available): - -- + -- -- * @{#SET_STATIC.ForEachStaticInZone}: Calls a function for each unit contained within the SET_STATIC. -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. - -- + -- -- ## SET_STATIC atomic methods - -- + -- -- Various methods exist for a SET_STATIC to perform actions or calculations and retrieve results from the SET_STATIC: - -- + -- -- * @{#SET_STATIC.GetTypeNames}(): Retrieve the type names of the @{Static}s in the SET, delimited by a comma. - -- + -- -- === -- @field #SET_STATIC SET_STATIC SET_STATIC = { @@ -2911,13 +3022,13 @@ do -- SET_STATIC }, }, } - - + + --- Get the first unit from the set. -- @function [parent=#SET_STATIC] GetFirst -- @param #SET_STATIC self -- @return Wrapper.Static#STATIC The STATIC object. - + --- Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_STATIC self -- @return #SET_STATIC @@ -2925,70 +3036,70 @@ do -- SET_STATIC -- -- Define a new SET_STATIC Object. This DBObject will contain a reference to all alive Statics. -- DBObject = SET_STATIC:New() function SET_STATIC:New() - + -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.STATICS ) ) -- Core.Set#SET_STATIC - + return self end - + --- Add STATIC(s) to SET_STATIC. -- @param #SET_STATIC self -- @param #string AddStatic A single STATIC. -- @return #SET_STATIC self function SET_STATIC:AddStatic( AddStatic ) self:F2( AddStatic:GetName() ) - + self:Add( AddStatic:GetName(), AddStatic ) - + return self end - - + + --- Add STATIC(s) to SET_STATIC. -- @param #SET_STATIC self -- @param #string AddStaticNames A single name or an array of STATIC names. -- @return #SET_STATIC self function SET_STATIC:AddStaticsByName( AddStaticNames ) - + local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames } - + self:T( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do self:Add( AddStaticName, STATIC:FindByName( AddStaticName ) ) end - + return self end - + --- Remove STATIC(s) from SET_STATIC. -- @param Core.Set#SET_STATIC self -- @param Wrapper.Static#STATIC RemoveStaticNames A single name or an array of STATIC names. -- @return self function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) - + local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } - + for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do self:Remove( RemoveStaticName ) end - + return self end - - + + --- Finds a Static based on the Static Name. -- @param #SET_STATIC self -- @param #string StaticName -- @return Wrapper.Static#STATIC The found Static. function SET_STATIC:FindStatic( StaticName ) - + local StaticFound = self.Set[StaticName] return StaticFound end - - - + + + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self @@ -3006,8 +3117,8 @@ do -- SET_STATIC end return self end - - + + --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_STATIC self @@ -3025,8 +3136,8 @@ do -- SET_STATIC end return self end - - + + --- Builds a set of units of defined unit types. -- Possible current types are those types known within DCS world. -- @param #SET_STATIC self @@ -3044,8 +3155,8 @@ do -- SET_STATIC end return self end - - + + --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_STATIC self @@ -3063,8 +3174,8 @@ do -- SET_STATIC end return self end - - + + --- Builds a set of units of defined unit prefixes. -- All the units starting with the given prefixes will be included within the set. -- @param #SET_STATIC self @@ -3082,23 +3193,41 @@ do -- SET_STATIC end return self end - - + + --- Starts the filtering. -- @param #SET_STATIC self -- @return #SET_STATIC self function SET_STATIC:FilterStart() - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end - + return self end - + + --- Iterate the SET_STATIC and count how many STATICSs are alive. + -- @param #SET_STATIC self + -- @return #number The number of UNITs alive. + function SET_STATIC:CountAlive() + + local Set = self:GetSet() + + local CountU = 0 + for UnitID, UnitData in pairs(Set) do + if UnitData and UnitData:IsAlive() then + CountU = CountU + 1 + end + + end + + return CountU + end + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_STATIC self @@ -3107,17 +3236,17 @@ do -- SET_STATIC -- @return #table The STATIC function SET_STATIC:AddInDatabase( Event ) self:F3( { Event } ) - + if Event.IniObjectCategory == Object.Category.STATIC then if not self.Database[Event.IniDCSUnitName] then self.Database[Event.IniDCSUnitName] = STATIC:Register( Event.IniDCSUnitName ) self:T3( self.Database[Event.IniDCSUnitName] ) end end - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_STATIC self @@ -3126,91 +3255,91 @@ do -- SET_STATIC -- @return #table The STATIC function SET_STATIC:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - + + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - - + + do -- Is Zone methods - + --- Check if minimal one element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean function SET_STATIC:IsPatriallyInZone( Zone ) - + local IsPartiallyInZone = false - + local function EvaluateZone( ZoneStatic ) - + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsPartiallyInZone = true return false end - + return true end - + return IsPartiallyInZone end - - + + --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @return #boolean function SET_STATIC:IsNotInZone( Zone ) - + local IsNotInZone = true - + local function EvaluateZone( ZoneStatic ) - + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsNotInZone = false return false end - + return true end - + Zone:Search( EvaluateZone ) - + return IsNotInZone end - - + + --- Check if minimal one element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStaticInZone( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - - + + end - - + + --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #SET_STATIC self -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStatic( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - - + + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3218,7 +3347,7 @@ do -- SET_STATIC -- @return #SET_STATIC self function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Static#STATIC StaticObject @@ -3229,10 +3358,10 @@ do -- SET_STATIC return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function. -- @param #SET_STATIC self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3240,7 +3369,7 @@ do -- SET_STATIC -- @return #SET_STATIC self function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Static#STATIC StaticObject @@ -3251,24 +3380,24 @@ do -- SET_STATIC return false end end, { ZoneObject } ) - + return self end - + --- Returns map of unit types. -- @param #SET_STATIC self -- @return #map<#string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found. function SET_STATIC:GetStaticTypes() self:F2() - + local MT = {} -- Message Text local StaticTypes = {} - + for StaticID, StaticData in pairs( self:GetSet() ) do local TextStatic = StaticData -- Wrapper.Static#STATIC if TextStatic:IsAlive() then local StaticType = TextStatic:GetTypeName() - + if not StaticTypes[StaticType] then StaticTypes[StaticType] = 1 else @@ -3276,38 +3405,38 @@ do -- SET_STATIC end end end - + for StaticTypeID, StaticType in pairs( StaticTypes ) do MT[#MT+1] = StaticType .. " of " .. StaticTypeID end - + return StaticTypes end - - + + --- Returns a comma separated string of the unit types with a count in the @{Set}. -- @param #SET_STATIC self -- @return #string The unit types string function SET_STATIC:GetStaticTypesText() self:F2() - + local MT = {} -- Message Text local StaticTypes = self:GetStaticTypes() - + for StaticTypeID, StaticType in pairs( StaticTypes ) do MT[#MT+1] = StaticType .. " of " .. StaticTypeID end - + return table.concat( MT, ", " ) end - + --- Get the center coordinate of the SET_STATIC. -- @param #SET_STATIC self -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units. function SET_STATIC:GetCoordinate() - + local Coordinate = self:GetFirst():GetCoordinate() - + local x1 = Coordinate.x local x2 = Coordinate.x local y1 = Coordinate.y @@ -3317,19 +3446,19 @@ do -- SET_STATIC local MaxVelocity = 0 local AvgHeading = nil local MovingCount = 0 - + for StaticName, StaticData in pairs( self:GetSet() ) do - + local Static = StaticData -- Wrapper.Static#STATIC local Coordinate = Static:GetCoordinate() - + x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1 x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2 y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1 y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2 z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1 z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2 - + local Velocity = Coordinate:GetVelocity() if Velocity ~= 0 then MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity @@ -3338,42 +3467,42 @@ do -- SET_STATIC MovingCount = MovingCount + 1 end end - + AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) - + Coordinate.x = ( x2 - x1 ) / 2 + x1 Coordinate.y = ( y2 - y1 ) / 2 + y1 Coordinate.z = ( z2 - z1 ) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) - + self:F( { Coordinate = Coordinate } ) return Coordinate - + end - + --- Get the maximum velocity of the SET_STATIC. -- @param #SET_STATIC self -- @return #number The speed in mps in case of moving units. function SET_STATIC:GetVelocity() - + return 0 - + end - + --- Get the average heading of the SET_STATIC. -- @param #SET_STATIC self -- @return #number Heading Heading in degrees and speed in mps in case of moving units. function SET_STATIC:GetHeading() - + local HeadingSet = nil local MovingCount = 0 - + for StaticName, StaticData in pairs( self:GetSet() ) do - + local Static = StaticData -- Wrapper.Static#STATIC local Coordinate = Static:GetCoordinate() - + local Velocity = Coordinate:GetVelocity() if Velocity ~= 0 then local Heading = Coordinate:GetHeading() @@ -3386,19 +3515,19 @@ do -- SET_STATIC HeadingSet = nil break end - end + end end end - + return HeadingSet - + end - + --- Calculate the maxium A2G threat level of the SET_STATIC. -- @param #SET_STATIC self -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() - + local MaxThreatLevelA2G = 0 local MaxThreatText = "" for StaticName, StaticData in pairs( self:GetSet() ) do @@ -3409,12 +3538,12 @@ do -- SET_STATIC MaxThreatText = ThreatText end end - + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) return MaxThreatLevelA2G, MaxThreatText - + end - + --- -- @param #SET_STATIC self -- @param Wrapper.Static#STATIC MStatic @@ -3422,7 +3551,7 @@ do -- SET_STATIC function SET_STATIC:IsIncludeObject( MStatic ) self:F2( MStatic ) local MStaticInclude = true - + if self.Filter.Coalitions then local MStaticCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -3433,7 +3562,7 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticCoalition end - + if self.Filter.Categories then local MStaticCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -3444,7 +3573,7 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticCategory end - + if self.Filter.Types then local MStaticType = false for TypeID, TypeName in pairs( self.Filter.Types ) do @@ -3455,7 +3584,7 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticType end - + if self.Filter.Countries then local MStaticCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do @@ -3466,7 +3595,7 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticCountry end - + if self.Filter.StaticPrefixes then local MStaticPrefix = false for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do @@ -3477,36 +3606,36 @@ do -- SET_STATIC end MStaticInclude = MStaticInclude and MStaticPrefix end - + self:T2( MStaticInclude ) return MStaticInclude end - - + + --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter. -- @param #SET_STATIC self -- @param #string Delimiter (optional) The delimiter, which is default a comma. -- @return #string The types of the @{Static}s delimited. function SET_STATIC:GetTypeNames( Delimiter ) - + Delimiter = Delimiter or ", " local TypeReport = REPORT:New() local Types = {} - + for StaticName, StaticData in pairs( self:GetSet() ) do - + local Static = StaticData -- Wrapper.Static#STATIC local StaticTypeName = Static:GetTypeName() - + if not Types[StaticTypeName] then Types[StaticTypeName] = StaticTypeName TypeReport:Add( StaticTypeName ) end end - + return TypeReport:Text( Delimiter ) end - + end @@ -3515,59 +3644,59 @@ do -- SET_CLIENT --- @type SET_CLIENT -- @extends Core.Set#SET_BASE - - - + + + --- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: - -- + -- -- * Coalitions -- * Categories -- * Countries -- * Client types -- * Starting with certain prefix strings. - -- + -- -- ## 1) SET_CLIENT constructor - -- + -- -- Create a new SET_CLIENT object with the @{#SET_CLIENT.New} method: - -- + -- -- * @{#SET_CLIENT.New}: Creates a new SET_CLIENT object. - -- - -- ## 2) Add or Remove CLIENT(s) from SET_CLIENT - -- - -- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. + -- + -- ## 2) Add or Remove CLIENT(s) from SET_CLIENT + -- + -- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. -- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. - -- + -- -- ## 3) SET_CLIENT filter criteria - -- + -- -- You can set filter criteria to define the set of clients within the SET_CLIENT. -- Filter criteria are defined by: - -- + -- -- * @{#SET_CLIENT.FilterCoalitions}: Builds the SET_CLIENT with the clients belonging to the coalition(s). -- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). -- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). -- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). -- * @{#SET_CLIENT.FilterActive}: Builds the SET_CLIENT with the units that are only active. Units that are inactive (late activation) won't be included in the set! - -- + -- -- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: - -- + -- -- * @{#SET_CLIENT.FilterStart}: Starts the filtering of the clients **dynamically**. -- * @{#SET_CLIENT.FilterOnce}: Filters the clients **once**. - -- + -- -- Planned filter criteria within development are (so these are not yet available): - -- + -- -- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. - -- + -- -- ## 4) SET_CLIENT iterators - -- + -- -- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. -- The iterator methods will walk the SET_CLIENT set, and call for each element within the set a function that you provide. -- The following iterator methods are currently available within the SET_CLIENT: - -- + -- -- * @{#SET_CLIENT.ForEachClient}: Calls a function for each alive client it finds within the SET_CLIENT. - -- + -- -- === - -- @field #SET_CLIENT SET_CLIENT + -- @field #SET_CLIENT SET_CLIENT SET_CLIENT = { ClassName = "SET_CLIENT", Clients = {}, @@ -3593,8 +3722,8 @@ do -- SET_CLIENT }, }, } - - + + --- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_CLIENT self -- @return #SET_CLIENT @@ -3604,55 +3733,55 @@ do -- SET_CLIENT function SET_CLIENT:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CLIENTS ) ) -- #SET_CLIENT - + self:FilterActive( false ) - + return self end - + --- Add CLIENT(s) to SET_CLIENT. -- @param Core.Set#SET_CLIENT self -- @param #string AddClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) - + local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } - + for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) end - + return self end - + --- Remove CLIENT(s) from SET_CLIENT. -- @param Core.Set#SET_CLIENT self -- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - + local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } - + for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) end - + return self end - - + + --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName -- @return Wrapper.Client#CLIENT The found Client. function SET_CLIENT:FindClient( ClientName ) - + local ClientFound = self.Set[ClientName] return ClientFound end - - - + + + --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -3670,8 +3799,8 @@ do -- SET_CLIENT end return self end - - + + --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -3689,8 +3818,8 @@ do -- SET_CLIENT end return self end - - + + --- Builds a set of clients of defined client types. -- Possible current types are those types known within DCS world. -- @param #SET_CLIENT self @@ -3708,8 +3837,8 @@ do -- SET_CLIENT end return self end - - + + --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CLIENT self @@ -3727,8 +3856,8 @@ do -- SET_CLIENT end return self end - - + + --- Builds a set of clients of defined client prefixes. -- All the clients starting with the given prefixes will be included within the set. -- @param #SET_CLIENT self @@ -3746,7 +3875,7 @@ do -- SET_CLIENT end return self end - + --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. -- @param #SET_CLIENT self @@ -3754,42 +3883,42 @@ do -- SET_CLIENT -- Include inactive clients if you provide false. -- @return #SET_CLIENT self -- @usage - -- + -- -- -- Include only active clients to the set. -- ClientSet = SET_CLIENT:New():FilterActive():FilterStart() - -- + -- -- -- Include only active clients to the set of the blue coalition, and filter one time. -- ClientSet = SET_CLIENT:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() - -- + -- -- -- Include only active clients to the set of the blue coalition, and filter one time. -- -- Later, reset to include back inactive clients to the set. -- ClientSet = SET_CLIENT:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() -- ... logic ... -- ClientSet = SET_CLIENT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() - -- + -- function SET_CLIENT:FilterActive( Active ) Active = Active or not ( Active == false ) self.Filter.Active = Active return self end - - - + + + --- Starts the filtering. -- @param #SET_CLIENT self -- @return #SET_CLIENT self function SET_CLIENT:FilterStart() - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end - + return self end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CLIENT self @@ -3798,10 +3927,10 @@ do -- SET_CLIENT -- @return #table The CLIENT function SET_CLIENT:AddInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CLIENT self @@ -3810,22 +3939,22 @@ do -- SET_CLIENT -- @return #table The CLIENT function SET_CLIENT:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_CLIENT self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClient( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3833,7 +3962,7 @@ do -- SET_CLIENT -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Client#CLIENT ClientObject @@ -3844,10 +3973,10 @@ do -- SET_CLIENT return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -3855,7 +3984,7 @@ do -- SET_CLIENT -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Client#CLIENT ClientObject @@ -3866,22 +3995,22 @@ do -- SET_CLIENT return false end end, { ZoneObject } ) - + return self end - + --- -- @param #SET_CLIENT self -- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) self:F2( MClient ) - + local MClientInclude = true - + if MClient then local MClientName = MClient.UnitName - + if self.Filter.Active ~= nil then local MClientActive = false if self.Filter.Active == false or ( self.Filter.Active == true and MClient:IsActive() == true ) then @@ -3889,7 +4018,7 @@ do -- SET_CLIENT end MClientInclude = MClientInclude and MClientActive end - + if self.Filter.Coalitions then local MClientCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -3902,7 +4031,7 @@ do -- SET_CLIENT self:T( { "Evaluated Coalition", MClientCoalition } ) MClientInclude = MClientInclude and MClientCoalition end - + if self.Filter.Categories then local MClientCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -3915,7 +4044,7 @@ do -- SET_CLIENT self:T( { "Evaluated Category", MClientCategory } ) MClientInclude = MClientInclude and MClientCategory end - + if self.Filter.Types then local MClientType = false for TypeID, TypeName in pairs( self.Filter.Types ) do @@ -3927,7 +4056,7 @@ do -- SET_CLIENT self:T( { "Evaluated Type", MClientType } ) MClientInclude = MClientInclude and MClientType end - + if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do @@ -3940,7 +4069,7 @@ do -- SET_CLIENT self:T( { "Evaluated Country", MClientCountry } ) MClientInclude = MClientInclude and MClientCountry end - + if self.Filter.ClientPrefixes then local MClientPrefix = false for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do @@ -3953,7 +4082,7 @@ do -- SET_CLIENT MClientInclude = MClientInclude and MClientPrefix end end - + self:T2( MClientInclude ) return MClientInclude end @@ -3965,46 +4094,46 @@ do -- SET_PLAYER --- @type SET_PLAYER -- @extends Core.Set#SET_BASE - - - + + + --- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: - -- + -- -- ## SET_PLAYER constructor - -- + -- -- Create a new SET_PLAYER object with the @{#SET_PLAYER.New} method: - -- + -- -- * @{#SET_PLAYER.New}: Creates a new SET_PLAYER object. - -- + -- -- ## SET_PLAYER filter criteria - -- + -- -- You can set filter criteria to define the set of clients within the SET_PLAYER. -- Filter criteria are defined by: - -- + -- -- * @{#SET_PLAYER.FilterCoalitions}: Builds the SET_PLAYER with the clients belonging to the coalition(s). -- * @{#SET_PLAYER.FilterCategories}: Builds the SET_PLAYER with the clients belonging to the category(ies). -- * @{#SET_PLAYER.FilterTypes}: Builds the SET_PLAYER with the clients belonging to the client type(s). -- * @{#SET_PLAYER.FilterCountries}: Builds the SET_PLAYER with the clients belonging to the country(ies). -- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients starting with the same prefix string(s). - -- + -- -- Once the filter criteria have been set for the SET_PLAYER, you can start filtering using: - -- + -- -- * @{#SET_PLAYER.FilterStart}: Starts the filtering of the clients within the SET_PLAYER. - -- + -- -- Planned filter criteria within development are (so these are not yet available): - -- + -- -- * @{#SET_PLAYER.FilterZones}: Builds the SET_PLAYER with the clients within a @{Core.Zone#ZONE}. - -- + -- -- ## SET_PLAYER iterators - -- + -- -- Once the filters have been defined and the SET_PLAYER has been built, you can iterate the SET_PLAYER with the available iterator methods. -- The iterator methods will walk the SET_PLAYER set, and call for each element within the set a function that you provide. -- The following iterator methods are currently available within the SET_PLAYER: - -- + -- -- * @{#SET_PLAYER.ForEachClient}: Calls a function for each alive client it finds within the SET_PLAYER. - -- + -- -- === - -- @field #SET_PLAYER SET_PLAYER + -- @field #SET_PLAYER SET_PLAYER SET_PLAYER = { ClassName = "SET_PLAYER", Clients = {}, @@ -4030,8 +4159,8 @@ do -- SET_PLAYER }, }, } - - + + --- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_PLAYER self -- @return #SET_PLAYER @@ -4041,53 +4170,53 @@ do -- SET_PLAYER function SET_PLAYER:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.PLAYERS ) ) - + return self end - + --- Add CLIENT(s) to SET_PLAYER. -- @param Core.Set#SET_PLAYER self -- @param #string AddClientNames A single name or an array of CLIENT names. -- @return self function SET_PLAYER:AddClientsByName( AddClientNames ) - + local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } - + for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) end - + return self end - + --- Remove CLIENT(s) from SET_PLAYER. -- @param Core.Set#SET_PLAYER self -- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. -- @return self function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) - + local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } - + for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) end - + return self end - - + + --- Finds a Client based on the Player Name. -- @param #SET_PLAYER self -- @param #string PlayerName -- @return Wrapper.Client#CLIENT The found Client. function SET_PLAYER:FindClient( PlayerName ) - + local ClientFound = self.Set[PlayerName] return ClientFound end - - - + + + --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self @@ -4105,8 +4234,8 @@ do -- SET_PLAYER end return self end - - + + --- Builds a set of clients out of categories joined by players. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_PLAYER self @@ -4124,8 +4253,8 @@ do -- SET_PLAYER end return self end - - + + --- Builds a set of clients of defined client types joined by players. -- Possible current types are those types known within DCS world. -- @param #SET_PLAYER self @@ -4143,8 +4272,8 @@ do -- SET_PLAYER end return self end - - + + --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_PLAYER self @@ -4162,8 +4291,8 @@ do -- SET_PLAYER end return self end - - + + --- Builds a set of clients of defined client prefixes. -- All the clients starting with the given prefixes will be included within the set. -- @param #SET_PLAYER self @@ -4181,25 +4310,25 @@ do -- SET_PLAYER end return self end - - - - + + + + --- Starts the filtering. -- @param #SET_PLAYER self -- @return #SET_PLAYER self function SET_PLAYER:FilterStart() - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) end - + return self end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_PLAYER self @@ -4208,10 +4337,10 @@ do -- SET_PLAYER -- @return #table The CLIENT function SET_PLAYER:AddInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_PLAYER self @@ -4220,22 +4349,22 @@ do -- SET_PLAYER -- @return #table The CLIENT function SET_PLAYER:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Iterate the SET_PLAYER and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. -- @param #SET_PLAYER self -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayer( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_PLAYER self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -4243,7 +4372,7 @@ do -- SET_PLAYER -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayerInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Client#CLIENT ClientObject @@ -4254,10 +4383,10 @@ do -- SET_PLAYER return false end end, { ZoneObject } ) - + return self end - + --- Iterate the SET_PLAYER and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_PLAYER self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -4265,7 +4394,7 @@ do -- SET_PLAYER -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayerNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet(), --- @param Core.Zone#ZONE_BASE ZoneObject -- @param Wrapper.Client#CLIENT ClientObject @@ -4276,22 +4405,22 @@ do -- SET_PLAYER return false end end, { ZoneObject } ) - + return self end - + --- -- @param #SET_PLAYER self -- @param Wrapper.Client#CLIENT MClient -- @return #SET_PLAYER self function SET_PLAYER:IsIncludeObject( MClient ) self:F2( MClient ) - + local MClientInclude = true - + if MClient then local MClientName = MClient.UnitName - + if self.Filter.Coalitions then local MClientCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -4304,7 +4433,7 @@ do -- SET_PLAYER self:T( { "Evaluated Coalition", MClientCoalition } ) MClientInclude = MClientInclude and MClientCoalition end - + if self.Filter.Categories then local MClientCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -4317,7 +4446,7 @@ do -- SET_PLAYER self:T( { "Evaluated Category", MClientCategory } ) MClientInclude = MClientInclude and MClientCategory end - + if self.Filter.Types then local MClientType = false for TypeID, TypeName in pairs( self.Filter.Types ) do @@ -4329,7 +4458,7 @@ do -- SET_PLAYER self:T( { "Evaluated Type", MClientType } ) MClientInclude = MClientInclude and MClientType end - + if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do @@ -4342,7 +4471,7 @@ do -- SET_PLAYER self:T( { "Evaluated Country", MClientCountry } ) MClientInclude = MClientInclude and MClientCountry end - + if self.Filter.ClientPrefixes then local MClientPrefix = false for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do @@ -4355,7 +4484,7 @@ do -- SET_PLAYER MClientInclude = MClientInclude and MClientPrefix end end - + self:T2( MClientInclude ) return MClientInclude end @@ -4367,41 +4496,41 @@ do -- SET_AIRBASE --- @type SET_AIRBASE -- @extends Core.Set#SET_BASE - + --- Mission designers can use the @{Core.Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: - -- + -- -- * Coalitions - -- + -- -- ## SET_AIRBASE constructor - -- + -- -- Create a new SET_AIRBASE object with the @{#SET_AIRBASE.New} method: - -- + -- -- * @{#SET_AIRBASE.New}: Creates a new SET_AIRBASE object. - -- - -- ## Add or Remove AIRBASEs from SET_AIRBASE - -- - -- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. + -- + -- ## Add or Remove AIRBASEs from SET_AIRBASE + -- + -- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. -- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. - -- - -- ## SET_AIRBASE filter criteria - -- + -- + -- ## SET_AIRBASE filter criteria + -- -- You can set filter criteria to define the set of clients within the SET_AIRBASE. -- Filter criteria are defined by: - -- + -- -- * @{#SET_AIRBASE.FilterCoalitions}: Builds the SET_AIRBASE with the airbases belonging to the coalition(s). - -- + -- -- Once the filter criteria have been set for the SET_AIRBASE, you can start filtering using: - -- + -- -- * @{#SET_AIRBASE.FilterStart}: Starts the filtering of the airbases within the SET_AIRBASE. - -- + -- -- ## SET_AIRBASE iterators - -- + -- -- Once the filters have been defined and the SET_AIRBASE has been built, you can iterate the SET_AIRBASE with the available iterator methods. -- The iterator methods will walk the SET_AIRBASE set, and call for each airbase within the set a function that you provide. -- The following iterator methods are currently available within the SET_AIRBASE: - -- + -- -- * @{#SET_AIRBASE.ForEachAirbase}: Calls a function for each airbase it finds within the SET_AIRBASE. - -- + -- -- === -- @field #SET_AIRBASE SET_AIRBASE SET_AIRBASE = { @@ -4423,8 +4552,8 @@ do -- SET_AIRBASE }, }, } - - + + --- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self @@ -4434,103 +4563,103 @@ do -- SET_AIRBASE function SET_AIRBASE:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.AIRBASES ) ) - + return self end - + --- Add an AIRBASE object to SET_AIRBASE. -- @param Core.Set#SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE airbase Airbase that should be added to the set. -- @return self function SET_AIRBASE:AddAirbase( airbase ) - + self:Add( airbase:GetName(), airbase ) - + return self end - + --- Add AIRBASEs to SET_AIRBASE. -- @param Core.Set#SET_AIRBASE self -- @param #string AddAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - + local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } - + for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) end - + return self end - + --- Remove AIRBASEs from SET_AIRBASE. -- @param Core.Set#SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - + local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } - + for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do self:Remove( RemoveAirbaseName ) end - + return self end - - + + --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName -- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:FindAirbase( AirbaseName ) - + local AirbaseFound = self.Set[AirbaseName] return AirbaseFound end - - + + --- Finds an Airbase in range of a coordinate. -- @param #SET_AIRBASE self -- @param Core.Point#COORDINATE Coordinate -- @param #number Range -- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:FindAirbaseInRange( Coordinate, Range ) - + local AirbaseFound = nil - + for AirbaseName, AirbaseObject in pairs( self.Set ) do - + local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - + self:F({Distance=Distance}) - + if Distance <= Range then AirbaseFound = AirbaseObject break end - + end - + return AirbaseFound end - - + + --- Finds a random Airbase in the set. -- @param #SET_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:GetRandomAirbase() - + local RandomAirbase = self:GetRandom() self:F( { RandomAirbase = RandomAirbase:GetName() } ) - + return RandomAirbase end - - - + + + --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self @@ -4548,8 +4677,8 @@ do -- SET_AIRBASE end return self end - - + + --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_AIRBASE self @@ -4567,17 +4696,17 @@ do -- SET_AIRBASE end return self end - + --- Starts the filtering. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self function SET_AIRBASE:FilterStart() - + if _DATABASE then - + -- We use the BaseCaptured event, which is generated by DCS when a base got captured. self:HandleEvent( EVENTS.BaseCaptured ) - + -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then @@ -4587,16 +4716,16 @@ do -- SET_AIRBASE end end end - + return self end - + --- Starts the filtering. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData -- @return #SET_AIRBASE self function SET_AIRBASE:OnEventBaseCaptured(EventData) - + -- When a base got captured, we reevaluate the set. for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then @@ -4607,9 +4736,9 @@ do -- SET_AIRBASE self:RemoveAirbasesByName( ObjectName ) end end - + end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self @@ -4618,10 +4747,10 @@ do -- SET_AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:AddInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_AIRBASE self @@ -4630,47 +4759,47 @@ do -- SET_AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. -- @param #SET_AIRBASE self -- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. -- @return #SET_AIRBASE self function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. -- @param #SET_AIRBASE self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. -- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - + local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) return NearestAirbase end - - - + + + --- -- @param #SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE MAirbase -- @return #SET_AIRBASE self function SET_AIRBASE:IsIncludeObject( MAirbase ) self:F2( MAirbase ) - + local MAirbaseInclude = true - + if MAirbase then local MAirbaseName = MAirbase:GetName() - + if self.Filter.Coalitions then local MAirbaseCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -4683,7 +4812,7 @@ do -- SET_AIRBASE self:T( { "Evaluated Coalition", MAirbaseCoalition } ) MAirbaseInclude = MAirbaseInclude and MAirbaseCoalition end - + if self.Filter.Categories then local MAirbaseCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do @@ -4697,7 +4826,7 @@ do -- SET_AIRBASE MAirbaseInclude = MAirbaseInclude and MAirbaseCategory end end - + self:T2( MAirbaseInclude ) return MAirbaseInclude end @@ -4709,48 +4838,48 @@ do -- SET_CARGO --- @type SET_CARGO -- @extends Core.Set#SET_BASE - + --- Mission designers can use the @{Core.Set#SET_CARGO} class to build sets of cargos optionally belonging to certain: - -- + -- -- * Coalitions -- * Types -- * Name or Prefix - -- + -- -- ## SET_CARGO constructor - -- + -- -- Create a new SET_CARGO object with the @{#SET_CARGO.New} method: - -- + -- -- * @{#SET_CARGO.New}: Creates a new SET_CARGO object. - -- - -- ## Add or Remove CARGOs from SET_CARGO - -- - -- CARGOs can be added and removed using the @{Core.Set#SET_CARGO.AddCargosByName} and @{Core.Set#SET_CARGO.RemoveCargosByName} respectively. + -- + -- ## Add or Remove CARGOs from SET_CARGO + -- + -- CARGOs can be added and removed using the @{Core.Set#SET_CARGO.AddCargosByName} and @{Core.Set#SET_CARGO.RemoveCargosByName} respectively. -- These methods take a single CARGO name or an array of CARGO names to be added or removed from SET_CARGO. - -- - -- ## SET_CARGO filter criteria - -- + -- + -- ## SET_CARGO filter criteria + -- -- You can set filter criteria to automatically maintain the SET_CARGO contents. -- Filter criteria are defined by: - -- + -- -- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s). -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s). -- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s). -- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies). - -- + -- -- Once the filter criteria have been set for the SET_CARGO, you can start filtering using: - -- + -- -- * @{#SET_CARGO.FilterStart}: Starts the filtering of the cargos within the SET_CARGO. - -- + -- -- ## SET_CARGO iterators - -- + -- -- Once the filters have been defined and the SET_CARGO has been built, you can iterate the SET_CARGO with the available iterator methods. -- The iterator methods will walk the SET_CARGO set, and call for each cargo within the set a function that you provide. -- The following iterator methods are currently available within the SET_CARGO: - -- + -- -- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO. - -- + -- -- @field #SET_CARGO SET_CARGO - -- + -- SET_CARGO = { ClassName = "SET_CARGO", Cargos = {}, @@ -4768,8 +4897,8 @@ do -- SET_CARGO }, }, } - - + + --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO @@ -4779,66 +4908,66 @@ do -- SET_CARGO function SET_CARGO:New() --R2.1 -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO - + return self end - - + + --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return self function SET_CARGO:AddCargo( Cargo ) --R2.4 - + self:Add( Cargo:GetName(), Cargo ) - + return self end - - + + --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return self function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 - + local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } - + for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) end - + return self end - + --- (R2.1) Remove CARGOs from SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return self function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 - + local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } - + for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do self:Remove( RemoveCargoName.CargoName ) end - + return self end - - + + --- (R2.1) Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found Cargo. function SET_CARGO:FindCargo( CargoName ) --R2.1 - + local CargoFound = self.Set[CargoName] return CargoFound end - - - + + + --- (R2.1) Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self @@ -4856,7 +4985,7 @@ do -- SET_CARGO end return self end - + --- (R2.1) Builds a set of cargos of defined cargo types. -- Possible current types are those types known within DCS world. -- @param #SET_CARGO self @@ -4874,8 +5003,8 @@ do -- SET_CARGO end return self end - - + + --- (R2.1) Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self @@ -4893,8 +5022,8 @@ do -- SET_CARGO end return self end - - + + --- (R2.1) Builds a set of cargos of defined cargo prefixes. -- All the cargos starting with the given prefixes will be included within the set. -- @param #SET_CARGO self @@ -4912,35 +5041,35 @@ do -- SET_CARGO end return self end - - - + + + --- (R2.1) Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self function SET_CARGO:FilterStart() --R2.1 - + if _DATABASE then self:_FilterStart() self:HandleEvent( EVENTS.NewCargo ) self:HandleEvent( EVENTS.DeleteCargo ) end - + return self end - + --- Stops the filtering for the defined collection. -- @param #SET_CARGO self -- @return #SET_CARGO self function SET_CARGO:FilterStop() - + self:UnHandleEvent( EVENTS.NewCargo ) self:UnHandleEvent( EVENTS.DeleteCargo ) - + return self end - - + + --- (R2.1) Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CARGO self @@ -4949,10 +5078,10 @@ do -- SET_CARGO -- @return #table The CARGO function SET_CARGO:AddInDatabase( Event ) --R2.1 self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- (R2.1) Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CARGO self @@ -4961,62 +5090,62 @@ do -- SET_CARGO -- @return #table The CARGO function SET_CARGO:FindInDatabase( Event ) --R2.1 self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- (R2.1) Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters. -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - + --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#POINT_VEC2}. -- @param #SET_CARGO self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 self:F2( PointVec2 ) - + local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) return NearestCargo end - + function SET_CARGO:FirstCargoWithState( State ) - + local FirstCargo = nil - + for CargoName, Cargo in pairs( self.Set ) do if Cargo:Is( State ) then FirstCargo = Cargo break end end - + return FirstCargo end - + function SET_CARGO:FirstCargoWithStateAndNotDeployed( State ) - + local FirstCargo = nil - + for CargoName, Cargo in pairs( self.Set ) do if Cargo:Is( State ) and not Cargo:IsDeployed() then FirstCargo = Cargo break end end - + return FirstCargo end - - + + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5024,8 +5153,8 @@ do -- SET_CARGO local FirstCargo = self:FirstCargoWithState( "UnLoaded" ) return FirstCargo end - - + + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5033,8 +5162,8 @@ do -- SET_CARGO local FirstCargo = self:FirstCargoWithStateAndNotDeployed( "UnLoaded" ) return FirstCargo end - - + + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5042,8 +5171,8 @@ do -- SET_CARGO local FirstCargo = self:FirstCargoWithState( "Loaded" ) return FirstCargo end - - + + --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5051,22 +5180,22 @@ do -- SET_CARGO local FirstCargo = self:FirstCargoWithState( "Deployed" ) return FirstCargo end - - - - - --- (R2.1) + + + + + --- (R2.1) -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 self:F2( MCargo ) - + local MCargoInclude = true - + if MCargo then local MCargoName = MCargo:GetName() - + if self.Filter.Coalitions then local MCargoCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do @@ -5079,7 +5208,7 @@ do -- SET_CARGO self:F( { "Evaluated Coalition", MCargoCoalition } ) MCargoInclude = MCargoInclude and MCargoCoalition end - + if self.Filter.Types then local MCargoType = false for TypeID, TypeName in pairs( self.Filter.Types ) do @@ -5091,7 +5220,7 @@ do -- SET_CARGO self:F( { "Evaluated Type", MCargoType } ) MCargoInclude = MCargoInclude and MCargoType end - + if self.Filter.CargoPrefixes then local MCargoPrefix = false for CargoPrefixId, CargoPrefix in pairs( self.Filter.CargoPrefixes ) do @@ -5104,35 +5233,35 @@ do -- SET_CARGO MCargoInclude = MCargoInclude and MCargoPrefix end end - + self:T2( MCargoInclude ) return MCargoInclude end - + --- (R2.1) Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 - + self:F( { "New Cargo", EventData } ) - + if EventData.Cargo then if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then self:Add( EventData.Cargo.Name , EventData.Cargo ) end end end - + --- (R2.1) Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 self:F3( { EventData } ) - + if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. -- And this is a problem because it will remove all entries from the SET_CARGOs. @@ -5155,39 +5284,39 @@ do -- SET_ZONE --- @type SET_ZONE -- @extends Core.Set#SET_BASE - + --- Mission designers can use the @{Core.Set#SET_ZONE} class to build sets of zones of various types. - -- + -- -- ## SET_ZONE constructor - -- + -- -- Create a new SET_ZONE object with the @{#SET_ZONE.New} method: - -- + -- -- * @{#SET_ZONE.New}: Creates a new SET_ZONE object. - -- - -- ## Add or Remove ZONEs from SET_ZONE - -- - -- ZONEs can be added and removed using the @{Core.Set#SET_ZONE.AddZonesByName} and @{Core.Set#SET_ZONE.RemoveZonesByName} respectively. + -- + -- ## Add or Remove ZONEs from SET_ZONE + -- + -- ZONEs can be added and removed using the @{Core.Set#SET_ZONE.AddZonesByName} and @{Core.Set#SET_ZONE.RemoveZonesByName} respectively. -- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_ZONE. - -- - -- ## SET_ZONE filter criteria - -- + -- + -- ## SET_ZONE filter criteria + -- -- You can set filter criteria to build the collection of zones in SET_ZONE. -- Filter criteria are defined by: - -- + -- -- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix. - -- + -- -- Once the filter criteria have been set for the SET_ZONE, you can start filtering using: - -- + -- -- * @{#SET_ZONE.FilterStart}: Starts the filtering of the zones within the SET_ZONE. - -- + -- -- ## SET_ZONE iterators - -- + -- -- Once the filters have been defined and the SET_ZONE has been built, you can iterate the SET_ZONE with the available iterator methods. -- The iterator methods will walk the SET_ZONE set, and call for each airbase within the set a function that you provide. -- The following iterator methods are currently available within the SET_ZONE: - -- + -- -- * @{#SET_ZONE.ForEachZone}: Calls a function for each zone it finds within the SET_ZONE. - -- + -- -- === -- @field #SET_ZONE SET_ZONE SET_ZONE = { @@ -5199,8 +5328,8 @@ do -- SET_ZONE FilterMeta = { }, } - - + + --- Creates a new SET_ZONE object, building a set of zones. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5210,90 +5339,90 @@ do -- SET_ZONE function SET_ZONE:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.ZONES ) ) - + return self end - + --- Add ZONEs by a search name to SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param #string AddZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:AddZonesByName( AddZoneNames ) - + local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } - + for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) end - + return self end - + --- Add ZONEs to SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE Zone A ZONE_BASE object. -- @return self function SET_ZONE:AddZone( Zone ) - + self:Add( Zone:GetName(), Zone ) - + return self end - - + + --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) - + local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } - + for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) end - + return self end - - + + --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE self -- @param #string ZoneName -- @return Core.Zone#ZONE_BASE The found Zone. function SET_ZONE:FindZone( ZoneName ) - + local ZoneFound = self.Set[ZoneName] return ZoneFound end - - + + --- Get a random zone from the set. -- @param #SET_ZONE self -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. function SET_ZONE:GetRandomZone() - + if self:Count() ~= 0 then - + local Index = self.Index local ZoneFound = nil -- Core.Zone#ZONE_BASE - + -- Loop until a zone has been found. -- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected. - -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! + -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! while not ZoneFound do local ZoneRandom = math.random( 1, #Index ) - ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() + ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() end - + return ZoneFound end - + return nil end - - + + --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. @@ -5301,10 +5430,10 @@ do -- SET_ZONE local Zone = self:FindZone( ZoneName ) Zone:SetZoneProbability( ZoneProbability ) end - - - - + + + + --- Builds a set of zones of defined zone prefixes. -- All the zones starting with the given prefixes will be included within the set. -- @param #SET_ZONE self @@ -5322,15 +5451,15 @@ do -- SET_ZONE end return self end - - + + --- Starts the filtering. -- @param #SET_ZONE self -- @return #SET_ZONE self function SET_ZONE:FilterStart() - + if _DATABASE then - + -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then @@ -5340,24 +5469,24 @@ do -- SET_ZONE end end end - + self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - + return self end - + --- Stops the filtering for the defined collection. -- @param #SET_ZONE self -- @return #SET_ZONE self function SET_ZONE:FilterStop() - + self:UnHandleEvent( EVENTS.NewZone ) self:UnHandleEvent( EVENTS.DeleteZone ) - + return self end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_ZONE self @@ -5366,10 +5495,10 @@ do -- SET_ZONE -- @return #table The AIRBASE function SET_ZONE:AddInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_ZONE self @@ -5378,35 +5507,35 @@ do -- SET_ZONE -- @return #table The AIRBASE function SET_ZONE:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE self function SET_ZONE:ForEachZone( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - - + + --- -- @param #SET_ZONE self -- @param Core.Zone#ZONE_BASE MZone -- @return #SET_ZONE self function SET_ZONE:IsIncludeObject( MZone ) self:F2( MZone ) - + local MZoneInclude = true - + if MZone then local MZoneName = MZone:GetName() - + if self.Filter.Prefixes then local MZonePrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do @@ -5419,35 +5548,35 @@ do -- SET_ZONE MZoneInclude = MZoneInclude and MZonePrefix end end - + self:T2( MZoneInclude ) return MZoneInclude end - + --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData function SET_ZONE:OnEventNewZone( EventData ) --R2.1 - + self:F( { "New Zone", EventData } ) - + if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then self:Add( EventData.Zone.ZoneName , EventData.Zone ) end end end - + --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 self:F3( { EventData } ) - + if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) if Zone and Zone.ZoneName then - + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. -- And this is a problem because it will remove all entries from the SET_ZONEs. @@ -5462,7 +5591,7 @@ do -- SET_ZONE end end end - + --- Validate if a coordinate is in one of the zones in the set. -- Returns the ZONE object where the coordiante is located. -- If zones overlap, the first zone that validates the test is returned. @@ -5471,14 +5600,14 @@ do -- SET_ZONE -- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location. -- @return #nil No zone has been found. function SET_ZONE:IsCoordinateInZone( Coordinate ) - + for _, Zone in pairs( self:GetSet() ) do local Zone = Zone -- Core.Zone#ZONE_BASE if Zone:IsCoordinateInZone( Coordinate ) then return Zone end end - + return nil end @@ -5488,39 +5617,39 @@ do -- SET_ZONE_GOAL --- @type SET_ZONE_GOAL -- @extends Core.Set#SET_BASE - + --- Mission designers can use the @{Core.Set#SET_ZONE_GOAL} class to build sets of zones of various types. - -- + -- -- ## SET_ZONE_GOAL constructor - -- + -- -- Create a new SET_ZONE_GOAL object with the @{#SET_ZONE_GOAL.New} method: - -- + -- -- * @{#SET_ZONE_GOAL.New}: Creates a new SET_ZONE_GOAL object. - -- - -- ## Add or Remove ZONEs from SET_ZONE_GOAL - -- - -- ZONEs can be added and removed using the @{Core.Set#SET_ZONE_GOAL.AddZonesByName} and @{Core.Set#SET_ZONE_GOAL.RemoveZonesByName} respectively. + -- + -- ## Add or Remove ZONEs from SET_ZONE_GOAL + -- + -- ZONEs can be added and removed using the @{Core.Set#SET_ZONE_GOAL.AddZonesByName} and @{Core.Set#SET_ZONE_GOAL.RemoveZonesByName} respectively. -- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_ZONE_GOAL. - -- - -- ## SET_ZONE_GOAL filter criteria - -- + -- + -- ## SET_ZONE_GOAL filter criteria + -- -- You can set filter criteria to build the collection of zones in SET_ZONE_GOAL. -- Filter criteria are defined by: - -- + -- -- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern of prefix. - -- + -- -- Once the filter criteria have been set for the SET_ZONE_GOAL, you can start filtering using: - -- + -- -- * @{#SET_ZONE_GOAL.FilterStart}: Starts the filtering of the zones within the SET_ZONE_GOAL. - -- + -- -- ## SET_ZONE_GOAL iterators - -- + -- -- Once the filters have been defined and the SET_ZONE_GOAL has been built, you can iterate the SET_ZONE_GOAL with the available iterator methods. -- The iterator methods will walk the SET_ZONE_GOAL set, and call for each airbase within the set a function that you provide. -- The following iterator methods are currently available within the SET_ZONE_GOAL: - -- + -- -- * @{#SET_ZONE_GOAL.ForEachZone}: Calls a function for each zone it finds within the SET_ZONE_GOAL. - -- + -- -- === -- @field #SET_ZONE_GOAL SET_ZONE_GOAL SET_ZONE_GOAL = { @@ -5532,8 +5661,8 @@ do -- SET_ZONE_GOAL FilterMeta = { }, } - - + + --- Creates a new SET_ZONE_GOAL object, building a set of zones. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5543,75 +5672,75 @@ do -- SET_ZONE_GOAL function SET_ZONE_GOAL:New() -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.ZONES_GOAL ) ) - + return self end - + --- Add ZONEs to SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE Zone A ZONE_BASE object. -- @return self function SET_ZONE_GOAL:AddZone( Zone ) - + self:Add( Zone:GetName(), Zone ) - + return self end - - + + --- Remove ZONEs from SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames ) - + local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } - + for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) end - + return self end - - + + --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName -- @return Core.Zone#ZONE_BASE The found Zone. function SET_ZONE_GOAL:FindZone( ZoneName ) - + local ZoneFound = self.Set[ZoneName] return ZoneFound end - - + + --- Get a random zone from the set. -- @param #SET_ZONE_GOAL self -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. function SET_ZONE_GOAL:GetRandomZone() - + if self:Count() ~= 0 then - + local Index = self.Index local ZoneFound = nil -- Core.Zone#ZONE_BASE - + -- Loop until a zone has been found. -- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected. - -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! + -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! while not ZoneFound do local ZoneRandom = math.random( 1, #Index ) - ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() + ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() end - + return ZoneFound end - + return nil end - - + + --- Set a zone probability. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName The name of the zone. @@ -5619,10 +5748,10 @@ do -- SET_ZONE_GOAL local Zone = self:FindZone( ZoneName ) Zone:SetZoneProbability( ZoneProbability ) end - - - - + + + + --- Builds a set of zones of defined zone prefixes. -- All the zones starting with the given prefixes will be included within the set. -- @param #SET_ZONE_GOAL self @@ -5640,15 +5769,15 @@ do -- SET_ZONE_GOAL end return self end - - + + --- Starts the filtering. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:FilterStart() - + if _DATABASE then - + -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then @@ -5658,24 +5787,24 @@ do -- SET_ZONE_GOAL end end end - + self:HandleEvent( EVENTS.NewZoneGoal ) self:HandleEvent( EVENTS.DeleteZoneGoal ) - + return self end - + --- Stops the filtering for the defined collection. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:FilterStop() - + self:UnHandleEvent( EVENTS.NewZoneGoal ) self:UnHandleEvent( EVENTS.DeleteZoneGoal ) - + return self end - + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_ZONE_GOAL self @@ -5684,10 +5813,10 @@ do -- SET_ZONE_GOAL -- @return #table The AIRBASE function SET_ZONE_GOAL:AddInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_ZONE_GOAL self @@ -5696,35 +5825,35 @@ do -- SET_ZONE_GOAL -- @return #table The AIRBASE function SET_ZONE_GOAL:FindInDatabase( Event ) self:F3( { Event } ) - + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - + --- Iterate the SET_ZONE_GOAL and call an interator function for each ZONE, providing the ZONE and optional parameters. -- @param #SET_ZONE_GOAL self -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:ForEachZone( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self:GetSet() ) - + return self end - - + + --- -- @param #SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE MZone -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:IsIncludeObject( MZone ) self:F2( MZone ) - + local MZoneInclude = true - + if MZone then local MZoneName = MZone:GetName() - + if self.Filter.Prefixes then local MZonePrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do @@ -5737,19 +5866,19 @@ do -- SET_ZONE_GOAL MZoneInclude = MZoneInclude and MZonePrefix end end - + self:T2( MZoneInclude ) return MZoneInclude end - + --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData ) - + self:I( { "New Zone Capture Coalition", EventData } ) self:I( { "Zone Capture Coalition", EventData.ZoneGoal } ) - + if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) @@ -5757,17 +5886,17 @@ do -- SET_ZONE_GOAL end end end - + --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1 self:F3( { EventData } ) - + if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) if Zone and Zone.ZoneName then - + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. @@ -5782,7 +5911,7 @@ do -- SET_ZONE_GOAL end end end - + --- Validate if a coordinate is in one of the zones in the set. -- Returns the ZONE object where the coordiante is located. -- If zones overlap, the first zone that validates the test is returned. @@ -5791,14 +5920,14 @@ do -- SET_ZONE_GOAL -- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location. -- @return #nil No zone has been found. function SET_ZONE_GOAL:IsCoordinateInZone( Coordinate ) - + for _, Zone in pairs( self:GetSet() ) do local Zone = Zone -- Core.Zone#ZONE_BASE if Zone:IsCoordinateInZone( Coordinate ) then return Zone end end - + return nil end diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua new file mode 100644 index 000000000..cbf739fa3 --- /dev/null +++ b/Moose Development/Moose/Core/Timer.lua @@ -0,0 +1,250 @@ +--- **Core** - Timer scheduler. +-- +-- **Main Features:** +-- +-- * Delay function calls +-- * Easy set up and little overhead +-- * Set start, stop and time interval +-- * Define max function calls +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Core.Timer +-- @image CORE_Timer.png + + +--- TIMER class. +-- @type TIMER +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number tid Timer ID returned by the DCS API function. +-- @field #function func Timer function. +-- @field #table para Parameters passed to the timer function. +-- @field #number Tstart Relative start time in seconds. +-- @field #number Tstop Relative stop time in seconds. +-- @field #number dT Time interval between function calls in seconds. +-- @field #number ncalls Counter of function calls. +-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. +-- @extends Core.Base#BASE + +--- *Better three hours too soon than a minute too late.* – William Shakespeare +-- +-- === +-- +-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg) +-- +-- # The TIMER Concept +-- +-- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. +-- +-- # Construction +-- +-- A new TIMER is created by the @{#TIMER.New}(*func*, *...*) function +-- +-- local mytimer=TIMER:New(myfunction, a, b) +-- +-- The first parameter *func* is the function that is called followed by the necessary comma separeted parameters that are passed to that function. +-- +-- ## Starting the Timer +-- +-- The timer is started by the @{#TIMER.Start}(*Tstart*, *dT*, *Duration*) function +-- +-- mytimer:Start(5, 1, 20) +-- +-- where +-- +-- * *Tstart* is the relative start time in seconds. In the example, the first function call happens after 5 sec. +-- * *dT* is the time interval between function calls in seconds. Above, the function is called once per second. +-- * *Duration* is the duration in seconds after which the timer is stopped. This is relative to the start time. Here, the timer will run for 20 seconds. +-- +-- Note that +-- +-- * if *Tstart* is not specified (*nil*), the first function call happens immediately. +-- * if *dT* is not specified (*nil*), the function is called only once. +-- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below). +-- +-- For example, +-- +-- mytimer:Start(3) -- Will call the function once after 3 seconds. +-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty. +-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec. +-- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway. +-- +-- ## Stopping the Timer +-- +-- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function +-- +-- mytimer:Stop() +-- +-- If the optional paramter *Delay* is specified, the timer is stopped after *delay* seconds. +-- +-- ## Limit Function Calls +-- +-- The timer can be stopped after a certain amount of function calles with the @{#TIMER.SetMaxFunctionCalls}(*Nmax*) function +-- +-- mytimer:SetMaxFunctionCalls(20) +-- +-- where *Nmax* is the number of calls after which the timer is stopped, here 20. +-- +-- For example, +-- +-- mytimer:SetMaxFunctionCalls(66):Start(1.0, 0.1) +-- +-- will start the timer after one second and call the function every 0.1 seconds. Once the function has been called 66 times, the timer is stopped. +-- +-- +-- @field #TIMER +TIMER = { + ClassName = "TIMER", + lid = nil, +} + +--- TIMER class version. +-- @field #string version +TIMER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. +-- TODO: Write docs. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new TIMER object. +-- @param #TIMER self +-- @param #function Function The function to call. +-- @param ... Parameters passed to the function if any. +-- @return #TIMER self +function TIMER:New(Function, ...) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) --#TIMER + + self.lid="TIMER | " + + -- Function to call. + self.func=Function + + -- Function arguments. + self.para=arg or {} + + -- Number of function calls. + self.ncalls=0 + + return self +end + +--- Create a new TIMER object. +-- @param #TIMER self +-- @param #number Tstart Relative start time in seconds. +-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once. +-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function. +-- @return #TIMER self +function TIMER:Start(Tstart, dT, Duration) + + -- Current time. + local Tnow=timer.getTime() + + -- Start time in sec. + self.Tstart=Tstart or Tnow + + -- Set time interval. + self.dT=dT + + -- Stop time. + if Duration then + self.Tstop=self.Tstart+Duration + end + + -- Call DCS timer function. + self.tid=timer.scheduleFunction(TIMER._Function, self, self.Tstart) + + -- Set log id. + self.lid=string.format("TIMER ID=%d | ", self.tid) + + -- Debug info. + self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop))) + + return self +end + +--- Stop the timer by removing the timer function. +-- @param #TIMER self +-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped. +-- @return #TIMER self +function TIMER:Stop(Delay) + + if Delay and Delay>0 then + + self.Tstop=timer.getTime()+Delay + + else + + if self.tid then + self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) + timer.removeFunction(self.tid) + end + + end + + return self +end + +--- Set max number of function calls. When the function has been called this many times, the TIMER is stopped. +-- @param #TIMER self +-- @param #number Nmax Set number of max function calls. +-- @return #TIMER self +function TIMER:SetMaxFunctionCalls(Nmax) + self.ncallsMax=Nmax + return self +end + +--- Call timer function. +-- @param #TIMER self +-- @param #number time DCS model time in seconds. +-- @return #number Time when the function is called again or `nil` if the timer is stopped. +function TIMER:_Function(time) + + -- Call function. + self.func(unpack(self.para)) + + -- Increase number of calls. + self.ncalls=self.ncalls+1 + + -- Next time. + local Tnext=self.dT and time+self.dT or nil + + -- Check if we stop the timer. + local stopme=false + if Tnext==nil then + -- No next time. + self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls", self.ncalls)) + stopme=true + elseif self.Tstop and Tnext>self.Tstop then + -- Stop time passed. + self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls", Tnext, self.Tstop, self.ncalls)) + stopme=true + elseif self.ncallsMax and self.ncalls>=self.ncallsMax then + -- Number of max function calls reached. + self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls", self.ncallsMax, self.ncalls)) + stopme=true + end + + if stopme then + -- Remove timer function. + self:Stop() + return nil + else + -- Call again in Tnext seconds. + return Tnext + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 206165bd3..15c73c29a 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -95,6 +95,11 @@ do -- world --- Returns a table of mark panels indexed numerically that are present within the mission. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getMarkPanels) -- @function [parent=#world] getMarkPanels -- @return #table Table of marks. + + --- Returns a table of DCS airbase objects. + -- @function [parent=#world] getAirbases + -- @param #number coalitionId The coalition side number ID. Default is all airbases are returned. + -- @return #table Table of DCS airbase objects. end -- world @@ -360,7 +365,7 @@ do -- Types --- Time is given in seconds. -- @type Time - -- @extends #number + -- @extends #number Time in seconds. --- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. -- @type ModelTime @@ -368,20 +373,20 @@ do -- Types --- Mission time is a model time plus time of the mission start. -- @type MissionTime - -- @extends #number + -- @extends #number Time in seconds. --- Distance is given in meters. -- @type Distance - -- @extends #number + -- @extends #number Distance in meters. --- Angle is given in radians. -- @type Angle - -- @extends #number + -- @extends #number Angle in radians. --- Azimuth is an angle of rotation around world axis y counter-clockwise. -- @type Azimuth - -- @extends #number + -- @extends #number Angle in radians. --- Mass is given in kilograms. -- @type Mass @@ -401,15 +406,15 @@ do -- Types --- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: -- @type Position3 - -- @field #Vec3 p - -- @field #Vec3 x - -- @field #Vec3 y - -- @field #Vec3 z + -- @field #Vec3 p 3D position vector. + -- @field #Vec3 x Orientation component of vector pointing East. + -- @field #Vec3 y Orientation component of vector pointing up. + -- @field #Vec3 z Orientation component of vector pointing North. --- 3-dimensional box. -- @type Box3 - -- @field #Vec3 min - -- @field #Vec3 max + -- @field #Vec3 min Min. + -- @field #Vec3 max Max --- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". -- @type TypeName @@ -514,7 +519,7 @@ do -- Object --- Returns object coordinates for current time. -- @function [parent=#Object] getPoint -- @param #Object self - -- @return #Vec3 + -- @return #Vec3 3D position vector with x,y,z components. --- Returns object position for current time. -- @function [parent=#Object] getPosition @@ -524,7 +529,7 @@ do -- Object --- Returns the unit's velocity vector. -- @function [parent=#Object] getVelocity -- @param #Object self - -- @return #Vec3 + -- @return #Vec3 3D velocity vector. --- Returns true if the unit is in air. -- @function [parent=#Object] inAir diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 15e37ed47..29b2918b2 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -513,7 +513,7 @@ ATIS.Sound = { MegaHertz={filename="MegaHertz.ogg", duration=0.87}, Meters={filename="Meters.ogg", duration=0.59}, MetersPerSecond={filename="MetersPerSecond.ogg", duration=1.14}, - Miles={filename="Miles.ogg", duration=1.04}, + Miles={filename="Miles.ogg", duration=0.60}, MillimetersOfMercury={filename="MillimetersOfMercury.ogg", duration=1.53}, Minus={filename="Minus.ogg", duration=0.64}, N0={filename="N-0.ogg", duration=0.55}, @@ -534,6 +534,7 @@ ATIS.Sound = { Right={filename="Right.ogg", duration=0.44}, Snow={filename="Snow.ogg", duration=0.48}, SnowStorm={filename="SnowStorm.ogg", duration=0.82}, + StatuteMiles={filename="StatuteMiles.ogg", duration=1.15}, SunriseAt={filename="SunriseAt.ogg", duration=0.92}, SunsetAt={filename="SunsetAt.ogg", duration=0.95}, Temperature={filename="Temperature.ogg", duration=0.64}, @@ -553,6 +554,7 @@ ATIS.Sound = { TACANChannel={filename="TACANChannel.ogg", duration=0.88}, PRMGChannel={filename="PRMGChannel.ogg", duration=1.18}, RSBNChannel={filename="RSBNChannel.ogg", duration=1.14}, + Zulu={filename="Zulu.ogg", duration=0.62}, } @@ -925,7 +927,7 @@ function ATIS:SetAltimeterQNH(switch) return self end --- Suppresses QFE readout. Default is to report both QNH and QFE. +--- Suppresses QFE readout. Default is to report both QNH and QFE. -- @param #ATIS self -- @return #ATIS self function ATIS:ReportQNHOnly() @@ -995,7 +997,7 @@ function ATIS:SetZuluTimeDifference(delta) return self end --- Suppresses local time, sunrise, and sunset. Default is to report all these times. +--- Suppresses local time, sunrise, and sunset. Default is to report all these times. -- @param #ATIS self -- @return #ATIS self function ATIS:ReportZuluTimeOnly() @@ -1502,7 +1504,7 @@ function ATIS:onafterBroadcast(From, Event, To) -- Zulu Time subtitle=string.format("%s Zulu", ZULU) self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.TimeZulu, 0.2, subtitle) + self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) alltext=alltext..";\n"..subtitle if not self.zulutimeonly then @@ -1557,7 +1559,7 @@ function ATIS:onafterBroadcast(From, Event, To) if self.metric then self:Transmission(ATIS.Sound.Kilometers, 0.2) else - self:Transmission(ATIS.Sound.Miles, 0.2) + self:Transmission(ATIS.Sound.StatuteMiles, 0.2) end alltext=alltext..";\n"..subtitle @@ -1974,13 +1976,6 @@ function ATIS:onafterBroadcast(From, Event, To) alltext=alltext..";\n"..subtitle end - - --[[ - -- End of Information Alpha, Bravo, ... - subtitle=string.format("End of information %s", NATO) - self:Transmission(ATIS.Sound.EndOfInformation, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - --]] -- Advice on initial... subtitle=string.format("Advise on initial contact, you have information %s", NATO) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index e43cd9cc7..c368c7418 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -1345,7 +1345,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) group:InitModex(self.modex) -- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - --SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) self:ScheduleOnce(1, GROUP.RespawnAtCurrentAirbase, group) -- Create tanker beacon and activate TACAN. @@ -1364,7 +1363,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) end -- Initial route. - --SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2) self:ScheduleOnce(2, RECOVERYTANKER._InitRoute, self, -self.distStern+UTILS.NMToMeters(3)) end diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 8e979ccf9..6aaf8d69b 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -876,8 +876,8 @@ function RESCUEHELO:onafterStart(From, Event, To) local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) -- Set modex for spawn. - Spawn:InitModex(self.modex) - + Spawn:InitModex(self.modex) + -- Spawn in air or at airbase. if self.takeoff==SPAWN.Takeoff.Air then diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index eaef38b47..84712dc7a 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -56,6 +56,17 @@ ENUMS.ROT = { AllowAbortMission=4, } +--- Alarm state. +-- @type ENUMS.AlarmState +-- @field #number Auto AI will automatically switch alarm states based on the presence of threats. The AI kind of cheats in this regard. +-- @field #number Green Group is not combat ready. Sensors are stowed if possible. +-- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state. +ENUMS.AlarmState = { + Auto=0, + Green=1, + Red=2, +} + --- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki. -- @type ENUMS.WeaponFlag ENUMS.WeaponFlag={ @@ -222,6 +233,15 @@ ENUMS.Formation.RotaryWing.EchelonLeft={} ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081 ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083 +ENUMS.Formation.Vehicle={} +ENUMS.Formation.Vehicle.Vee="Vee" +ENUMS.Formation.Vehicle.EchelonRight="EchelonR" +ENUMS.Formation.Vehicle.OffRoad="Off Road" +ENUMS.Formation.Vehicle.Rank="Rank" +ENUMS.Formation.Vehicle.EchelonLeft="EchelonL" +ENUMS.Formation.Vehicle.OnRoad="On Road" +ENUMS.Formation.Vehicle.Cone="Cone" +ENUMS.Formation.Vehicle.Diamond="Diamond" --- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings. -- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua new file mode 100644 index 000000000..a4bce95b5 --- /dev/null +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -0,0 +1,539 @@ +--- **Utils** - Lua Profiler. +-- +-- Find out how many times functions are called and how much real time it costs. +-- +-- === +-- +-- ### Author: **TAW CougarNL**, *funkyfranky* +-- +-- @module Utilities.PROFILER +-- @image MOOSE.JPG + + +--- PROFILER class. +-- @type PROFILER +-- @field #string ClassName Name of the class. +-- @field #table Counters Function counters. +-- @field #table dInfo Info. +-- @field #table fTime Function time. +-- @field #table fTimeTotal Total function time. +-- @field #table eventhandler Event handler to get mission end event. +-- @field #number TstartGame Game start time timer.getTime(). +-- @field #number TstartOS OS real start time os.clock. +-- @field #boolean logUnknown Log unknown functions. Default is off. +-- @field #number ThreshCPS Low calls per second threshold. Only write output if function has more calls per second than this value. +-- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value. +-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler". +-- @field #string fileNameSuffix Output file name prefix, e.g. "txt" + +--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? * +-- +-- === +-- +-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) +-- +-- # The PROFILER Concept +-- +-- Profile your lua code. This tells you, which functions are called very often and which consume most real time. +-- With this information you can optimize the perfomance of your code. +-- +-- # Prerequisites +-- +-- The modules **os** and **lfs** need to be desanizied. +-- +-- +-- # Start +-- +-- The profiler can simply be started with the @{#PROFILER.Start}(*Delay, Duration*) function +-- +-- PROFILER.Start() +-- +-- The optional parameter *Delay* can be used to delay the start by a certain amount of seconds and the optional parameter *Duration* can be used to +-- stop the profiler after a certain amount of seconds. +-- +-- # Stop +-- +-- The profiler automatically stops when the mission ends. But it can be stopped any time with the @{#PROFILER.Stop}(*Delay*) function +-- +-- PROFILER.Stop() +-- +-- The optional parameter *Delay* can be used to specify a delay after which the profiler is stopped. +-- +-- When the profiler is stopped, the output is written to a file. +-- +-- # Output +-- +-- The profiler output is written to a file in your DCS home folder +-- +-- X:\User\\Saved Games\DCS OpenBeta\Logs +-- +-- The default file name is "MooseProfiler.txt". If that file exists, the file name is "MooseProfiler-001.txt" etc. +-- +-- ## Data +-- +-- The data in the output file provides information on the functions that were called in the mission. +-- +-- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time. +-- +-- If you only want output for functions that are called more than *X* times per second, you can set +-- +-- PROFILER.ThreshCPS=1.5 +-- +-- With this setting, only functions which are called more than 1.5 times per second are displayed. The default setting is PROFILER.ThreshCPS=0.0 (no threshold). +-- +-- Furthermore, you can limit the output for functions that consumed a certain amount of CPU time in total by +-- +-- PROFILER.ThreshTtot=0.005 +-- +-- With this setting, which is also the default, only functions which in total used more than 5 milliseconds CPU time. +-- +-- @field #PROFILER +PROFILER = { + ClassName = "PROFILER", + Counters = {}, + dInfo = {}, + fTime = {}, + fTimeTotal = {}, + eventHandler = {}, + logUnknown = false, + ThreshCPS = 0.0, + ThreshTtot = 0.005, + fileNamePrefix = "MooseProfiler", + fileNameSuffix = "txt" +} + +--- Waypoint data. +-- @type PROFILER.Data +-- @field #string func The function name. +-- @field #string src The source file. +-- @field #number line The line number +-- @field #number count Number of function calls. +-- @field #number tm Total time in seconds. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start/Stop Profiler +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start profiler. +-- @param #number Delay Delay in seconds before profiler is stated. Default is immediately. +-- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. +function PROFILER.Start(Delay, Duration) + + -- Check if os, io and lfs are available. + local go=true + if not os then + env.error("ERROR: Profiler needs os to be desanitized!") + go=false + end + if not io then + env.error("ERROR: Profiler needs io to be desanitized!") + go=false + end + if not lfs then + env.error("ERROR: Profiler needs lfs to be desanitized!") + go=false + end + if not go then + return + end + + if Delay and Delay>0 then + BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) + else + + -- Set start time. + PROFILER.TstartGame=timer.getTime() + PROFILER.TstartOS=os.clock() + + -- Add event handler. + world.addEventHandler(PROFILER.eventHandler) + + -- Info in log. + env.info('############################ Profiler Started ############################') + if Duration then + env.info(string.format("- Will be running for %d seconds", Duration)) + else + env.info(string.format("- Will be stopped when mission ends")) + end + env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS)) + env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot)) + env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix))) + env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv"))) + env.info('###############################################################################') + + + -- Message on screen + local duration=Duration or 600 + trigger.action.outText("### Profiler running ###", duration) + + -- Set hook. + debug.sethook(PROFILER.hook, "cr") + + -- Auto stop profiler. + if Duration then + PROFILER.Stop(Duration) + end + + end + +end + +--- Stop profiler. +-- @param #number Delay Delay before stop in seconds. +function PROFILER.Stop(Delay) + + if Delay and Delay>0 then + + BASE:ScheduleOnce(Delay, PROFILER.Stop) + + else + + -- Remove hook. + debug.sethook() + + + -- Run time game. + local runTimeGame=timer.getTime()-PROFILER.TstartGame + + -- Run time real OS. + local runTimeOS=os.clock()-PROFILER.TstartOS + + -- Show info. + PROFILER.showInfo(runTimeGame, runTimeOS) + + end + +end + +--- Event handler. +function PROFILER.eventHandler:onEvent(event) + if event.id==world.event.S_EVENT_MISSION_END then + PROFILER.Stop() + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Hook +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Debug hook. +-- @param #table event Event. +function PROFILER.hook(event) + + local f=debug.getinfo(2, "f").func + + if event=='call' then + + if PROFILER.Counters[f]==nil then + + PROFILER.Counters[f]=1 + PROFILER.dInfo[f]=debug.getinfo(2,"Sn") + + if PROFILER.fTimeTotal[f]==nil then + PROFILER.fTimeTotal[f]=0 + end + + else + PROFILER.Counters[f]=PROFILER.Counters[f]+1 + end + + if PROFILER.fTime[f]==nil then + PROFILER.fTime[f]=os.clock() + end + + elseif (event=='return') then + + if PROFILER.fTime[f]~=nil then + PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f]) + PROFILER.fTime[f]=nil + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get data. +-- @param #function func Function. +-- @return #string Function name. +-- @return #string Source file name. +-- @return #string Line number. +-- @return #number Function time in seconds. +function PROFILER.getData(func) + + local n=PROFILER.dInfo[func] + + if n.what=="C" then + return n.name, "?", "?", PROFILER.fTimeTotal[func] + end + + return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func] +end + +--- Write text to log file. +-- @param #function f The file. +-- @param #string txt The text. +function PROFILER._flog(f, txt) + f:write(txt.."\r\n") +end + +--- Show table. +-- @param #table data Data table. +-- @param #function f The file. +-- @param #number runTimeGame Game run time in seconds. +function PROFILER.showTable(data, f, runTimeGame) + + -- Loop over data. + for i=1, #data do + local t=data[i] --#PROFILER.Data + + -- Calls per second. + local cps=t.count/runTimeGame + + local threshCPS=cps>=PROFILER.ThreshCPS + local threshTot=t.tm>=PROFILER.ThreshTtot + + if threshCPS and threshTot then + + -- Output + local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) + PROFILER._flog(f, text) + + end + end + +end + +--- Print csv file. +-- @param #table data Data table. +-- @param #number runTimeGame Game run time in seconds. +function PROFILER.printCSV(data, runTimeGame) + + -- Output file. + local file=PROFILER.getfilename("csv") + local g=io.open(file, 'w') + + -- Header. + local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," + g:write(text.."\r\n") + + -- Loop over data. + for i=1, #data do + local t=data[i] --#PROFILER.Data + + -- Calls per second. + local cps=t.count/runTimeGame + + -- Output + local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) + g:write(txt.."\r\n") + + end + + -- Close file. + g:close() +end + + +--- Write info to output file. +-- @param #string ext Extension. +-- @return #string File name. +function PROFILER.getfilename(ext) + + local dir=lfs.writedir()..[[Logs\]] + + ext=ext or PROFILER.fileNameSuffix + + local file=dir..PROFILER.fileNamePrefix.."."..ext + + if not UTILS.FileExists(file) then + return file + end + + for i=1,999 do + + local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext) + + if not UTILS.FileExists(file) then + return file + end + + end + +end + +--- Write info to output file. +-- @param #number runTimeGame Game time in seconds. +-- @param #number runTimeOS OS time in seconds. +function PROFILER.showInfo(runTimeGame, runTimeOS) + + -- Output file. + local file=PROFILER.getfilename(PROFILER.fileNameSuffix) + local f=io.open(file, 'w') + + -- Gather data. + local Ttot=0 + local Calls=0 + + local t={} + + local tcopy=nil --#PROFILER.Data + local tserialize=nil --#PROFILER.Data + local tforgen=nil --#PROFILER.Data + local tpairs=nil --#PROFILER.Data + + + for func, count in pairs(PROFILER.Counters) do + + local s,src,line,tm=PROFILER.getData(func) + + if PROFILER.logUnknown==true then + if s==nil then s="" end + end + + if s~=nil then + + -- Profile data. + local T= + { func=s, + src=src, + line=line, + count=count, + tm=tm, + } --#PROFILER.Data + + -- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data. + if s=="_copy" then + if tcopy==nil then + tcopy=T + else + tcopy.count=tcopy.count+T.count + tcopy.tm=tcopy.tm+T.tm + end + elseif s=="_Serialize" then + if tserialize==nil then + tserialize=T + else + tserialize.count=tserialize.count+T.count + tserialize.tm=tserialize.tm+T.tm + end + elseif s=="(for generator)" then + if tforgen==nil then + tforgen=T + else + tforgen.count=tforgen.count+T.count + tforgen.tm=tforgen.tm+T.tm + end + elseif s=="pairs" then + if tpairs==nil then + tpairs=T + else + tpairs.count=tpairs.count+T.count + tpairs.tm=tpairs.tm+T.tm + end + else + table.insert(t, T) + end + + -- Total function time. + Ttot=Ttot+tm + + -- Total number of calls. + Calls=Calls+count + + end + + end + + -- Add special cases. + if tcopy then + table.insert(t, tcopy) + end + if tserialize then + table.insert(t, tserialize) + end + if tforgen then + table.insert(t, tforgen) + end + if tpairs then + table.insert(t, tpairs) + end + + env.info('############################ Profiler Stopped ############################') + env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame)) + env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS)) + env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100)) + env.info(string.format("* Total functions : %d", #t)) + env.info(string.format("* Total func calls : %d", Calls)) + env.info(string.format("* Writing to file : \"%s\"", file)) + env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv"))) + env.info("##############################################################################") + + -- Sort by total time. + table.sort(t, function(a,b) return a.tm>b.tm end) + + -- Write data. + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"-------------------------") + PROFILER._flog(f,"---- Profiler Report ----") + PROFILER._flog(f,"-------------------------") + PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame)) + PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS)) + PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100)) + PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Total functions = %d", #t)) + PROFILER._flog(f,string.format("* Total func calls = %d", Calls)) + PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS)) + PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot)) + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, runTimeGame) + + -- Sort by number of calls. + table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end) + + -- Detailed data. + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"--------------------------------------") + PROFILER._flog(f,"---- Data Sorted by Time per Call ----") + PROFILER._flog(f,"--------------------------------------") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, runTimeGame) + + -- Sort by number of calls. + table.sort(t, function(a,b) return a.count>b.count end) + + -- Detailed data. + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"------------------------------------") + PROFILER._flog(f,"---- Data Sorted by Total Calls ----") + PROFILER._flog(f,"------------------------------------") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, runTimeGame) + + -- Closing. + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + -- Close file. + f:close() + + -- Print csv file. + PROFILER.printCSV(t, runTimeGame) +end + diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 354a89221..bc7f32daa 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -191,21 +191,30 @@ end -- @param #table object The input table. -- @return #table Copy of the input table. UTILS.DeepCopy = function(object) + local lookup_table = {} + + -- Copy function. local function _copy(object) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do new_table[_copy(index)] = _copy(value) end + return setmetatable(new_table, getmetatable(object)) end + local objectreturn = _copy(object) + return objectreturn end @@ -359,7 +368,7 @@ UTILS.MpsToMiph = function( mps ) end --- Convert meters per second to knots. --- @param #number knots Speed in m/s. +-- @param #number mps Speed in m/s. -- @return #number Speed in knots. UTILS.MpsToKnots = function( mps ) return mps * 1.94384 --3600 / 1852 @@ -975,6 +984,22 @@ function UTILS.HdgDiff(h1, h2) end +--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. +-- @param DCS#Vec3 a Vector in 3D with x, y, z components. +-- @param #number distance The distance to translate. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec3 Vector rotated in the (x,z) plane. +function UTILS.VecTranslate(a, distance, angle) + + local SX = a.x + local SY = a.z + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY + + return {x=TX, y=a.y, z=TY} +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -996,7 +1021,6 @@ function UTILS.Rotate2D(a, angle) end - --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". -- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X". @@ -1410,4 +1434,14 @@ function UTILS.GetSunset(Day, Month, Year, Latitude, Longitude, Tlocal) local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tlocal) +end + +--- Get OS time. Needs os to be desanitized! +-- @return #number Os time in seconds. +function UTILS.GetOSTime() + if os then + return os.clock() + end + + return nil end \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 0bd6321b6..3e3d7de16 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1,13 +1,13 @@ --- **Wrapper** -- AIRBASE is a wrapper class to handle the DCS Airbase objects. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Wrapper.Airbase -- @image Wrapper_Airbase.JPG @@ -15,44 +15,53 @@ --- @type AIRBASE -- @field #string ClassName Name of the class, i.e. "AIRBASE". -- @field #table CategoryName Names of airbase categories. +-- @field #string AirbaseName Name of the airbase. +-- @field #number AirbaseID Airbase ID. +-- @field #number category Airbase category. +-- @field #table descriptors DCS descriptors. +-- @field #boolean isAirdrome Airbase is an airdrome. +-- @field #boolean isHelipad Airbase is a helipad. +-- @field #boolean isShip Airbase is a ship. +-- @field #table parking Parking spot data. +-- @field #table parkingByID Parking spot data table with ID as key. -- @field #number activerwyno Active runway number (forced). -- @extends Wrapper.Positionable#POSITIONABLE --- Wrapper class to handle the DCS Airbase objects: --- +-- -- * Support all DCS Airbase APIs. -- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- +-- -- ## AIRBASE reference methods --- +-- -- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts). --- +-- -- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference -- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. +-- +-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. -- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. -- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- +-- -- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- +-- -- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. -- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- +-- -- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- +-- -- ## DCS Airbase APIs --- +-- -- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. -- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, -- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSWrapper.Airbase#Airbase.getName}() -- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- +-- -- @field #AIRBASE AIRBASE AIRBASE = { ClassName="AIRBASE", - CategoryName = { + CategoryName = { [Airbase.Category.AIRDROME] = "Airdrome", [Airbase.Category.HELIPAD] = "Helipad", [Airbase.Category.SHIP] = "Ship", @@ -61,9 +70,9 @@ AIRBASE = { } --- Enumeration to identify the airbases in the Caucasus region. --- +-- -- These are all airbases of Caucasus: --- +-- -- * AIRBASE.Caucasus.Gelendzhik -- * AIRBASE.Caucasus.Krasnodar_Pashkovsky -- * AIRBASE.Caucasus.Sukhumi_Babushara @@ -85,7 +94,7 @@ AIRBASE = { -- * AIRBASE.Caucasus.Nalchik -- * AIRBASE.Caucasus.Mozdok -- * AIRBASE.Caucasus.Beslan --- +-- -- @field Caucasus AIRBASE.Caucasus = { ["Gelendzhik"] = "Gelendzhik", @@ -112,7 +121,7 @@ AIRBASE.Caucasus = { } --- These are all airbases of Nevada: --- +-- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB -- * AIRBASE.Nevada.McCarran_International_Airport @@ -130,7 +139,7 @@ AIRBASE.Caucasus = { -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield --- @field Nevada +-- @field Nevada AIRBASE.Nevada = { ["Creech_AFB"] = "Creech AFB", ["Groom_Lake_AFB"] = "Groom Lake AFB", @@ -152,7 +161,7 @@ AIRBASE.Nevada = { } --- These are all airbases of Normandy: --- +-- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles -- * AIRBASE.Normandy.Cretteville @@ -219,11 +228,11 @@ AIRBASE.Normandy = { ["Ford_AF"] = "Ford_AF", ["Goulet"] = "Goulet", ["Argentan"] = "Argentan", - ["Vrigny"] = "Vrigny", + ["Vrigny"] = "Vrigny", ["Essay"] = "Essay", ["Hauterive"] = "Hauterive", ["Barville"] = "Barville", - ["Conches"] = "Conches", + ["Conches"] = "Conches", } --- These are all airbases of the Persion Gulf Map: @@ -269,7 +278,7 @@ AIRBASE.PersianGulf = { ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Lengeh"] = "Bandar Lengeh", ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield", - ["Dubai_Intl"] = "Dubai Intl", + ["Dubai_Intl"] = "Dubai Intl", ["Fujairah_Intl"] = "Fujairah Intl", ["Havadarya"] = "Havadarya", ["Jiroft_Airport"] = "Jiroft Airport", @@ -301,7 +310,7 @@ AIRBASE.PersianGulf = { -- * AIRBASE.TheChannel.Lympne -- * AIRBASE.TheChannel.Detling -- * AIRBASE.TheChannel.High_Halden --- +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", @@ -388,7 +397,7 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", } - + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. @@ -398,11 +407,11 @@ AIRBASE.Syria={ -- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment. -- @field #number TerminalID0 Unknown what this means. If you know, please tell us! -- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID. - + --- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking --- +-- -- Supported types are: --- +-- -- * AIRBASE.TerminalType.Runway = 16: Valid spawn points on runway. -- * AIRBASE.TerminalType.HelicopterOnly = 40: Special spots for Helicopers. -- * AIRBASE.TerminalType.Shelter = 68: Hardened Air Shelter. Currently only on Caucaus map. @@ -411,7 +420,7 @@ AIRBASE.Syria={ -- * AIRBASE.TerminalType.OpenMedOrBig = 176: Combines OpenMed and OpenBig spots. -- * AIRBASE.TerminalType.HelicopterUsable = 216: Combines HelicopterOnly, OpenMed and OpenBig. -- * AIRBASE.TerminalType.FighterAircraft = 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. --- +-- -- @type AIRBASE.TerminalType -- @field #number Runway 16: Valid spawn points on runway. -- @field #number HelicopterOnly 40: Special spots for Helicopers. @@ -440,28 +449,62 @@ AIRBASE.TerminalType = { -- @field Core.Point#COORDINATE position Position of runway start. -- @field Core.Point#COORDINATE endpoint End point of runway. --- Registration. - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Registration +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. --- @return Wrapper.Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) +-- @return #AIRBASE self +function AIRBASE:Register(AirbaseName) - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) --#AIRBASE - self.AirbaseName = AirbaseName - self.AirbaseID = self:GetID(true) + -- Inherit everything from positionable. + local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE + + -- Set airbase name. + self.AirbaseName=AirbaseName + + -- Set airbase ID. + self.AirbaseID=self:GetID(true) + + -- Get descriptors. + self.descriptors=self:GetDesc() + + -- Category. + self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME + + -- Set category. + if self.category==Airbase.Category.AIRDROME then + self.isAirdrome=true + elseif self.category==Airbase.Category.HELIPAD then + self.isHelipad=true + elseif self.category==Airbase.Category.SHIP then + self.isShip=true + else + self:E("ERROR: Unknown airbase category!") + end + + self:_InitParkingSpots() + local vec2=self:GetVec2() + + -- Init coordinate. + self:GetCoordinate() + if vec2 then - self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) + -- TODO: For ships we need a moving zone. + self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end - + return self end --- Reference methods. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Reference methods +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. -- @param #AIRBASE self @@ -479,7 +522,7 @@ end -- @param #string AirbaseName The Airbase Name. -- @return #AIRBASE self function AIRBASE:FindByName( AirbaseName ) - + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end @@ -489,18 +532,18 @@ end -- @param #number id Airbase ID. -- @return #AIRBASE self function AIRBASE:FindByID(id) - + for name,_airbase in pairs(_DATABASE.AIRBASES) do local airbase=_airbase --#AIRBASE - + local aid=tonumber(airbase:GetID(true)) - + if aid==id then return airbase end - + end - + return nil end @@ -508,12 +551,14 @@ end -- @param #AIRBASE self -- @return DCS#Airbase DCS airbase object. function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - + + -- Get the DCS object. + local DCSAirbase = Airbase.getByName(self.AirbaseName) + if DCSAirbase then return DCSAirbase end - + return nil end @@ -529,92 +574,127 @@ end -- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP -- @return #table Table containing all airbase objects of the current map. function AIRBASE.GetAllAirbases(coalition, category) - + local airbases={} for _,_airbase in pairs(_DATABASE.AIRBASES) do local airbase=_airbase --#AIRBASE - if (coalition~=nil and airbase:GetCoalition()==coalition) or coalition==nil then + if coalition==nil or airbase:GetCoalition()==coalition then if category==nil or category==airbase:GetAirbaseCategory() then table.insert(airbases, airbase) end end end - + + return airbases +end + +--- Get all airbase names of the current map. This includes ships and FARPS. +-- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. +-- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP +-- @return #table Table containing all airbase names of the current map. +function AIRBASE.GetAllAirbaseNames(coalition, category) + + local airbases={} + for airbasename,_airbase in pairs(_DATABASE.AIRBASES) do + local airbase=_airbase --#AIRBASE + if coalition==nil or airbase:GetCoalition()==coalition then + if category==nil or category==airbase:GetAirbaseCategory() then + table.insert(airbases, airbasename) + end + end + end + return airbases end --- Get ID of the airbase. -- @param #AIRBASE self --- @param #boolean unique (Optional) If true, ships will get a negative sign as the unit ID might be the same as an airbase ID. Default off! +-- @param #boolean unique (Optional) If true, ships will get a negative sign as the unit ID might be the same as an airbase ID. Default off! -- @return #number The airbase ID. function AIRBASE:GetID(unique) if self.AirbaseID then - + return unique and self.AirbaseID or math.abs(self.AirbaseID) - + else - + for DCSAirbaseId, DCSAirbase in ipairs(world.getAirbases()) do - + -- Get the airbase name. local AirbaseName = DCSAirbase:getName() - + -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! local airbaseID=tonumber(DCSAirbase:getID()) - + local airbaseCategory=self:GetAirbaseCategory() - --env.info(string.format("FF airbase=%s id=%s category=%s", tostring(AirbaseName), tostring(airbaseID), tostring(airbaseCategory))) - - -- No way AFIK to get the DCS version. So we check if the event exists. That should tell us if we are on DCS 2.5.6 or prior to that. - --[[ - if world.event.S_EVENT_KILL and world.event.S_EVENT_KILL>0 and airbaseCategory==Airbase.Category.AIRDROME then - - -- We have to take the key value of this loop! - airbaseID=DCSAirbaseId - - -- Now another quirk: for Caucasus, we need to add 11 to the key value to get the correct ID. See https://forums.eagle.ru/showpost.php?p=4210774&postcount=11 - if UTILS.GetDCSMap()==DCSMAP.Caucasus then - airbaseID=airbaseID+11 - end - end - ]] - if AirbaseName==self.AirbaseName then - if airbaseCategory==Airbase.Category.SHIP then + if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then -- Ships get a negative sign as their unit number might be the same as the ID of another airbase. return unique and -airbaseID or airbaseID else return airbaseID - end + end end - + end - + end return nil end +--- Get category of airbase. +-- @param #AIRBASE self +-- @return #number Category of airbase from GetDesc().category. +function AIRBASE:GetAirbaseCategory() + return self.category +end + +--- Check if airbase is an airdrome. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is an airdrome. +function AIRBASE:IsAirdrome() + return self.isAirdrome +end + +--- Check if airbase is a helipad. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is a helipad. +function AIRBASE:IsHelipad() + return self.isHelipad +end + +--- Check if airbase is a ship. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is a ship. +function AIRBASE:IsShip() + return self.isShip +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Parking +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Returns a table of parking data for a given airbase. If the optional parameter *available* is true only available parking will be returned, otherwise all parking at the base is returned. Term types have the following enumerated values: --- +-- -- * 16 : Valid spawn points on runway --- * 40 : Helicopter only spawn +-- * 40 : Helicopter only spawn -- * 68 : Hardened Air Shelter -- * 72 : Open/Shelter air airplane only -- * 104: Open air spawn --- +-- -- Note that only Caucuses will return 68 as it is the only map currently with hardened air shelters. -- 104 are also generally larger, but does not guarantee a large aircraft like the B-52 or a C-130 are capable of spawning there. --- +-- -- Table entries: --- +-- -- * Term_index is the id for the parking -- * vTerminal pos is its vec3 position in the world -- * fDistToRW is the distance to the take-off position for the active runway from the parking. --- +-- -- @param #AIRBASE self -- @param #boolean available If true, only available parking spots will be returned. -- @return #table Table with parking data. See https://wiki.hoggitworld.com/view/DCS_func_getParking @@ -623,13 +703,13 @@ function AIRBASE:GetParkingData(available) -- Get DCS airbase object. local DCSAirbase=self:GetDCSObject() - + -- Get parking data. local parkingdata=nil if DCSAirbase then parkingdata=DCSAirbase:getParking(available) end - + self:T2({parkingdata=parkingdata}) return parkingdata end @@ -642,27 +722,27 @@ function AIRBASE:GetParkingSpotsNumber(termtype) -- Get free parking spots data. local parkingdata=self:GetParkingData(false) - + local nspots=0 for _,parkingspot in pairs(parkingdata) do if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then nspots=nspots+1 end end - + return nspots end --- Get number of free parking spots at an airbase. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type. --- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. +-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #number Number of free parking spots at this airbase. function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) -- Get free parking spots data. local parkingdata=self:GetParkingData(true) - + local nfree=0 for _,parkingspot in pairs(parkingdata) do -- Spots on runway are not counted unless explicitly requested. @@ -672,7 +752,7 @@ function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) end end end - + return nfree end @@ -685,7 +765,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) -- Get free parking spots data. local parkingdata=self:GetParkingData(true) - + -- Put coordinates of free spots into table. local spots={} for _,parkingspot in pairs(parkingdata) do @@ -696,7 +776,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) end end end - + return spots end @@ -708,26 +788,78 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype) -- Get all parking spots data. local parkingdata=self:GetParkingData(false) - + -- Put coordinates of free spots into table. local spots={} for _,parkingspot in ipairs(parkingdata) do - + -- Coordinates on runway are not returned unless explicitly requested. if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then - + -- Get coordinate from Vec3 terminal position. local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) - + -- Add to table. table.insert(spots, _coord) end - + end - + return spots end +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @return#AIRBASE self +function AIRBASE:_InitParkingSpots() + + -- Get parking data of all spots (free or occupied) + local parkingdata=self:GetParkingData(false) + + -- Init table. + self.parking={} + self.parkingByID={} + + self.NparkingTotal=0 + self.NparkingTerminal={} + for _,terminalType in pairs(AIRBASE.TerminalType) do + self.NparkingTerminal[terminalType]=0 + end + + -- Put coordinates of parking spots into table. + for _,spot in pairs(parkingdata) do + + -- New parking spot. + local park={} --#AIRBASE.ParkingSpot + park.Vec3=spot.vTerminalPos + park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) + park.DistToRwy=spot.fDistToRW + park.Free=nil + park.TerminalID=spot.Term_Index + park.TerminalID0=spot.Term_Index_0 + park.TerminalType=spot.Term_Type + park.TOAC=spot.TO_AC + + for _,terminalType in pairs(AIRBASE.TerminalType) do + if self._CheckTerminalType(terminalType, park.TerminalType) then + self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 + end + end + + self.parkingByID[park.TerminalID]=park + table.insert(self.parking, park) + end + + return self +end + +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @param #number TerminalID Terminal ID. +-- @return #AIRBASE.ParkingSpot Parking spot. +function AIRBASE:_GetParkingSpotByID(TerminalID) + return self.parkingByID[TerminalID] +end --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. -- @param #AIRBASE self @@ -735,11 +867,12 @@ end -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetParkingSpotsTable(termtype) - -- Get parking data of all spots (free or occupied) + -- Get parking data of all spots (free or occupied) local parkingdata=self:GetParkingData(false) + -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) - + -- Function to ckeck if any parking spot is free. local function _isfree(_tocheck) for _,_spot in pairs(parkingfree) do @@ -749,44 +882,53 @@ function AIRBASE:GetParkingSpotsTable(termtype) end return false end - + -- Put coordinates of parking spots into table. local spots={} for _,_spot in pairs(parkingdata) do + if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then - self:T2({_spot=_spot}) - local _free=_isfree(_spot) - local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) - table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) + + local spot=self:_GetParkingSpotByID(_spot.Term_Index) + + spot.Free=_isfree(_spot) -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(spots, spot) end + end - - self:T2({ spots = spots } ) - + return spots end --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type. --- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. +-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) - + -- Put coordinates of free spots into table. local freespots={} for _,_spot in pairs(parkingfree) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then - local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) - table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) + + local spot=self:_GetParkingSpotByID(_spot.Term_Index) + + spot.Free=true -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(freespots, spot) + end end end - + return freespots end @@ -795,14 +937,10 @@ end -- @param #number TerminalID The terminal ID of the parking spot. -- @return #AIRBASE.ParkingSpot Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetParkingSpotData(TerminalID) - self:F({TerminalID=TerminalID}) -- Get parking data. local parkingdata=self:GetParkingSpotsTable() - - -- Debug output. - self:T2({parkingdata=parkingdata}) - + for _,_spot in pairs(parkingdata) do local spot=_spot --#AIRBASE.ParkingSpot self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) @@ -810,7 +948,7 @@ function AIRBASE:GetParkingSpotData(TerminalID) return spot end end - + self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID)) return nil end @@ -832,18 +970,18 @@ function AIRBASE:MarkParkingSpots(termtype, mark) -- Get airbase name. local airbasename=self:GetName() self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype))) - + for _,_spot in pairs(parkingdata) do - + -- Mark text. local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m", _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) - + -- Create mark on the F10 map. if mark then _spot.Coordinate:MarkToAll(_text) end - + -- Info to DCS.log file. local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m", airbasename, _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) @@ -861,7 +999,7 @@ end -- @param #boolean scanstatics (Optional) Scan for statics as obstacles. Default true. -- @param #boolean scanscenery (Optional) Scan for scenery as obstacles. Default false. Can cause problems with e.g. shelters. -- @param #boolean verysafe (Optional) If true, wait until an aircraft has taken off until the parking spot is considered to be free. Defaul false. --- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. +-- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. -- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) @@ -879,8 +1017,8 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, end if verysafe==nil then verysafe=false - end - + end + -- Function calculating the overlap of two (square) objects. local function _overlap(object1, object2, dist) local pos1=object1 --Wrapper.Positionable#POSITIONABLE @@ -888,107 +1026,107 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local r1=pos1:GetBoundingRadius() local r2=pos2:GetBoundingRadius() if r1 and r2 then - local safedist=(r1+r2)*1.1 + local safedist=(r1+r2)*1.1 local safe = (dist > safedist) self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe))) return safe else return true - end + end end - + -- Get airport name. local airport=self:GetName() - + -- Get parking spot data table. This contains free and "non-free" spots. -- Note that there are three major issues with the DCS getParking() function: -- 1. A spot is considered as NOT free until an aircraft that is present has finally taken off. This might be a bit long especiall at smaller airports. -- 2. A "free" spot does not take the aircraft size into accound. So if two big aircraft are spawned on spots next to each other, they might overlap and get destroyed. -- 3. The routine return a free spot, if there a static objects placed on the spot. parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) - + -- Get the aircraft size, i.e. it's longest side of x,z. local aircraft=group:GetUnit(1) local _aircraftsize, ax,ay,az=aircraft:GetObjectSize() - + -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! local _nspots=nspots or group:GetSize() - + -- Debug info. self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) - + -- Table of valid spots. local validspots={} local nvalid=0 - + -- Test other stuff if no parking spot is available. local _test=false if _test then return validspots end - + -- Mark all found obstacles on F10 map for debugging. local markobstacles=false - + -- Loop over all known parking spots for _,parkingspot in pairs(parkingdata) do - + -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - + self:T2({_termid=_termid}) - + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then - + -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then - + -- DCS getParking() routine returned that spot is not free. self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) - + else - + -- Scan a radius of 50 meters around the spot. local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) - + -- Loop over objects within scan radius. local occupied=false - - -- Check all units. + + -- Check all units. for _,unit in pairs(_units) do local _coord=unit:GetCoordinate() - local _dist=_coord:Get2DDistance(_spot) + local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft, unit, _dist) - + if markobstacles then local l,x,y,z=unit:GetObjectSize() _coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:GetName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanunits and not _safe then occupied=true - end + end end - + -- Check all statics. for _,static in pairs(_statics) do local _static=STATIC:Find(static) local _vec3=static:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) - local _dist=_coord:Get2DDistance(_spot) + local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft,_static,_dist) - + if markobstacles then local l,x,y,z=_static:GetObjectSize() _coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanstatics and not _safe then occupied=true - end + end end - + -- Check all scenery. for _,scenery in pairs(_sceneries) do local _scenery=SCENERY:Register(scenery:getTypeName(), scenery) @@ -996,17 +1134,17 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _coord=COORDINATE:NewFromVec3(_vec3) local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft,_scenery,_dist) - + if markobstacles then local l,x,y,z=scenery:GetObjectSize(scenery) _coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanscenery and not _safe then occupied=true - end + end end - + -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. for _,_takenspot in pairs(validspots) do local _dist=_takenspot.Coordinate:Get2DDistance(_spot) @@ -1015,7 +1153,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, occupied=true end end - + --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) if occupied then self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid)) @@ -1027,113 +1165,20 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, nvalid=nvalid+1 self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots)) end - + end -- loop over units - + -- We found enough spots. if nvalid>=_nspots then return validspots end end -- check terminal type - end - + end + -- Retrun spots we found, even if there were not enough. return validspots end ---- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway. --- @param #AIRBASE self --- @param Wrapper.Group#GROUP group Group to be checked. --- @param #number radius Radius around the spawn point to be checked. Default is 50 m. --- @param #boolean despawn If true, the group is destroyed. --- @return #boolean True if group is within radius around spawn points on runway. -function AIRBASE:CheckOnRunWay(group, radius, despawn) - - -- Default radius. - radius=radius or 50 - - -- We only check at real airbases (not FARPS or ships). - if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - return false - end - - if group and group:IsAlive() then - - -- Debug. - self:T(string.format("%s, checking if group %s is on runway?",self:GetName(), group:GetName())) - - -- Get coordinates on runway. - local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - - -- Mark runway spawn points. - --[[ - for _i,_coord in pairs(runwaypoints) do - _coord:MarkToAll(string.format("runway %d",_i)) - end - ]] - - -- Get units of group. - local units=group:GetUnits() - - -- Loop over units. - for _,_unit in pairs(units) do - - local unit=_unit --Wrapper.Unit#UNIT - - -- Check if unit is alive and not in air. - if unit and unit:IsAlive() and not unit:InAir() then - self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(), unit:GetName())) - - -- Loop over runway spawn points. - for _i,_coord in pairs(runwaypoints) do - - -- Distance between unit and spawn pos. - local dist=unit:GetCoordinate():Get2DDistance(_coord) - - -- Mark unit spawn points for debugging. - --unit:GetCoordinate():MarkToAll(string.format("unit %s distance to rwy %d = %d",unit:GetName(),_i, dist)) - - -- Check if unit is withing radius. - if dist radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) - --unit:FlareGreen() - end - - end - else - self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) - end - end - else - self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName())) - end - - return false -end - ---- Get category of airbase. --- @param #AIRBASE self --- @return #number Category of airbase from GetDesc().category. -function AIRBASE:GetAirbaseCategory() - local desc=self:GetDesc() - local category=Airbase.Category.AIRDROME - - if desc and desc.category then - category=desc.category - else - self:E(string.format("ERROR: Cannot get category of airbase %s due to DCS 2.5.6 bug! Assuming it is an AIRDROME for now...", tostring(self.AirbaseName))) - end - return category -end - - --- Helper function to check for the correct terminal type including "artificial" ones. -- @param #number Term_Type Termial type from getParking routine. -- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator. @@ -1153,15 +1198,15 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) return true end end - + -- Init no match. local match=false - - -- Standar case. + + -- Standar case. if Term_Type==termtype then match=true end - + -- Artificial cases. Combination of terminal types. if termtype==AIRBASE.TerminalType.OpenMedOrBig then if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then @@ -1176,10 +1221,14 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) match=true end end - + return match end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Runway +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Get runways data. Only for airdromes! -- @param #AIRBASE self -- @param #number magvar (Optional) Magnetic variation in degrees. @@ -1189,106 +1238,157 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Runway table. local runways={} - + if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then return {} end - -- Get spawn points on runway. + -- Get spawn points on runway. These can be used to determine the runway heading. local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) + -- Debug: For finding the numbers of the spawn points belonging to each runway. + if false then + for i,_coord in pairs(runwaycoords) do + local coord=_coord --Core.Point#COORDINATE + coord:Translate(100, 0):MarkToAll("Runway i="..i) + end + end + -- Magnetic declination. magvar=magvar or UTILS.GetMagneticDeclination() - + + -- Number of runways. local N=#runwaycoords - local dN=2 - local ex=false - + local N2=N/2 + local exception=false + + -- Airbase name. local name=self:GetName() - if name==AIRBASE.Nevada.Jean_Airport or - name==AIRBASE.Nevada.Creech_AFB or + + + -- Exceptions + if name==AIRBASE.Nevada.Jean_Airport or + name==AIRBASE.Nevada.Creech_AFB or name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or - name==AIRBASE.PersianGulf.Kish_International_Airport then - - N=#runwaycoords/2 - dN=1 - ex=true + name==AIRBASE.PersianGulf.Kish_International_Airport + then + + -- 1-->4, 2-->3, 3-->2, 4-->1 + exception=1 + + elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and + name~=AIRBASE.Syria.Minakh and + name~=AIRBASE.Syria.Damascus and + name~=AIRBASE.Syria.Khalkhalah and + name~=AIRBASE.Syria.Marj_Ruhayyil and + name~=AIRBASE.Syria.Beirut_Rafic_Hariri then + + -- 1-->3, 2-->4, 3-->1, 4-->2 + exception=2 + end - - for i=1,N,dN do - - local j=i+1 - if ex then - --j=N+i - j=#runwaycoords-i+1 + local function f(i) + + local j + + if exception==1 then + + j=N-(i+1) -- 1-->4, 2-->3 + + elseif exception==2 then + + if i<=N2 then + j=i+N2 -- 1-->3, 2-->4 + else + j=i-N2 -- 3-->1, 4-->3 + end + + else + + if i%2==0 then + j=i-1 -- even 2-->1, 4-->3 + else + j=i+1 -- odd 1-->2, 3-->4 + end + end + -- Special case where there is no obvious order. + if name==AIRBASE.Syria.Beirut_Rafic_Hariri then + if i==1 then + j=3 + elseif i==2 then + j=6 + elseif i==3 then + j=1 + elseif i==4 then + j=5 + elseif i==5 then + j=4 + elseif i==6 then + j=2 + end + end + + if name==AIRBASE.Syria.Ramat_David then + if i==1 then + j=4 + elseif i==2 then + j=6 + elseif i==3 then + j=5 + elseif i==4 then + j=1 + elseif i==5 then + j=3 + elseif i==6 then + j=2 + end + end + + return j + end + + + for i=1,N do + + -- Get the other spawn point coordinate. + local j=f(i) + -- Coordinates of the two runway points. - local c1=runwaycoords[i] --Core.Point#COORDINATES - local c2=runwaycoords[j] --Core.Point#COORDINATES - + local c1=runwaycoords[i] --Core.Point#COORDINATE + local c2=runwaycoords[j] --Core.Point#COORDINATE + -- Heading of runway. local hdg=c1:HeadingTo(c2) - + -- Runway ID: heading=070° ==> idx="07" local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0)) - + -- Runway table. local runway={} --#AIRBASE.Runway runway.heading=hdg runway.idx=idx - runway.length=c1:Get2DDistance(c2) + runway.length=c1:Get2DDistance(c2) runway.position=c1 runway.endpoint=c2 - + -- Debug info. - self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) - + self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) + -- Debug mark if mark then - runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) + runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d", runway.idx, runway.heading, magvar, runway.length, i, j)) end - + -- Add runway. table.insert(runways, runway) - + end - - -- Get inverse runways - local inverse={} - for _,_runway in pairs(runways) do - local r=_runway --#AIRBASE.Runway - - local runway={} --#AIRBASE.Runway - runway.heading=r.heading-180 - if runway.heading<0 then - runway.heading=runway.heading+360 - end - runway.idx=string.format("%02d", math.max(0, UTILS.Round((runway.heading-magvar)/10, 0))) - runway.length=r.length - runway.position=r.endpoint - runway.endpoint=r.position - - -- Debug info. - self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) - - -- Debug mark - if mark then - runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) - end - - -- Add runway. - table.insert(inverse, runway) - end - - -- Add inverse runway. - for _,runway in pairs(inverse) do - table.insert(runways, runway) - end - + return runways end @@ -1316,48 +1416,115 @@ function AIRBASE:GetActiveRunway(magvar) -- Get wind vector. local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() local norm=UTILS.VecNorm(Vwind) - + -- Active runway number. local iact=1 - + -- Check if wind is blowing (norm>0). if norm>0 then - + -- Normalize wind (not necessary). Vwind.x=Vwind.x/norm Vwind.y=0 Vwind.z=Vwind.z/norm - + -- Loop over runways. local dotmin=nil for i,_runway in pairs(runways) do local runway=_runway --#AIRBASE.Runway - + -- Angle in rad. local alpha=math.rad(runway.heading) - + -- Runway vector. local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} - + -- Dot product: parallel component of the two vectors. local dot=UTILS.VecDot(Vwind, Vrunway) - + -- Debug. --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) - + -- New min? if dotmin==nil or dot radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) + --unit:FlareGreen() + end + + end + else + self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) + end + end + else + self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName())) + end + + return false +end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e1be30eb4..9dded0f5a 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -243,8 +243,7 @@ end --- Returns the initial health. -- @param #CONTROLLABLE self --- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. +-- @return #number The controllable health value (unit or group average) or `nil` if the controllable does not exist. function CONTROLLABLE:GetLife0() self:F2( self.ControllableName ) @@ -296,7 +295,6 @@ end -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuel() self:F( self.ControllableName ) - return nil end @@ -803,7 +801,7 @@ function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) local CommandSetFrequency = { id = 'SetFrequency', params = { - frequency = Frequency, + frequency = Frequency*1000000, modulation = Modulation or radio.modulation.AM, } } @@ -1429,16 +1427,6 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- expendQty = number, - -- expendQtyEnabled = boolean, - -- } - -- } - local DCSTask = { id = 'FireAtPoint', params = { @@ -1458,7 +1446,6 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) DCSTask.params.weaponType=WeaponType end - self:T3( { DCSTask } ) return DCSTask end @@ -1481,8 +1468,8 @@ end -- @param #number WeaponType Bitmask of weapon types, which are allowed to use. -- @param DCS#AI.Task.Designation Designation (Optional) Designation type. -- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @param #number Frequency Frequency used to communicate with the FAC. --- @param #number Modulation Modulation of radio for communication. +-- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz. +-- @param #number Modulation Modulation of radio for communication. Default 0=AM. -- @param #number CallsignName Callsign enumerator name of the FAC. -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**. -- @return DCS#Task The DCS task structure. @@ -1492,11 +1479,11 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, id = 'FAC_AttackGroup', params = { groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - frequency = Frequency, - modulation = Modulation, + weaponType = WeaponType or ENUMS.WeaponFlag.AutoDCS, + designation = Designation or "Auto", + datalink = Datalink and Datalink or true, + frequency = (Frequency or 133)*1000000, + modulation = Modulation or radio.modulation.AM, callname = CallsignName, number = CallsignNumber, } diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9ac9211c2..7ceb235af 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -261,7 +261,9 @@ end -- @param #string GroupName The Group name -- @return #GROUP self function GROUP:Register( GroupName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) -- #GROUP + self.GroupName = GroupName self:SetEventPriority( 4 ) @@ -668,14 +670,15 @@ end -- @param #number UnitNumber The number of the UNIT wrapper class to be returned. -- @return Wrapper.Unit#UNIT The UNIT wrapper class. function GROUP:GetUnit( UnitNumber ) - self:F3( { self.GroupName, UnitNumber } ) local DCSGroup = self:GetDCSObject() if DCSGroup then + local DCSUnit = DCSGroup:getUnit( UnitNumber ) - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T2( UnitFound ) + + local UnitFound = UNIT:Find(DCSUnit) + return UnitFound end @@ -688,13 +691,11 @@ end -- @param #number UnitNumber The number of the DCS Unit to be returned. -- @return DCS#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) - self:F3( { self.GroupName, UnitNumber } ) - local DCSGroup = self:GetDCSObject() + local DCSGroup=self:GetDCSObject() if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) + local DCSUnitFound=DCSGroup:getUnit( UnitNumber ) return DCSUnitFound end @@ -706,14 +707,14 @@ end -- @param #GROUP self -- @return #number The DCS Group size. function GROUP:GetSize() - self:F3( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() if DCSGroup then + local GroupSize = DCSGroup:getSize() if GroupSize then - self:T3( GroupSize ) return GroupSize else return 0 @@ -946,24 +947,29 @@ end -- @param #GROUP self -- @return DCS#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. function GROUP:GetVec2() - self:F2( self.GroupName ) - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 + local Unit=self:GetUnit(1) + + if Unit then + return Unit:GetVec2() + end + end --- Returns the current Vec3 vector of the first DCS Unit in the GROUP. -- @param #GROUP self -- @return DCS#Vec3 Current Vec3 of the first DCS Unit of the GROUP. function GROUP:GetVec3() - self:F2( self.GroupName ) - local GroupVec3 = self:GetUnit(1):GetVec3() - self:T3( GroupVec3 ) - return GroupVec3 + -- Get first unit. + local unit=self:GetUnit(1) + + if unit then + return unit:GetVec3() + end + + self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) + return nil end --- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. @@ -1039,11 +1045,16 @@ function GROUP:GetHeading() local GroupSize = self:GetSize() local HeadingAccumulator = 0 + local n=0 if GroupSize then for i = 1, GroupSize do - HeadingAccumulator = HeadingAccumulator + self:GetUnit(i):GetHeading() + local unit=self:GetUnit(i) + if unit and unit:IsAlive() then + HeadingAccumulator = HeadingAccumulator + unit:GetHeading() + n=n+1 + end end - return math.floor(HeadingAccumulator / GroupSize) + return math.floor(HeadingAccumulator / n) end BASE:E( { "Cannot GetHeading", Group = self, Alive = self:IsAlive() } ) @@ -1161,6 +1172,32 @@ end do -- Is Zone methods + +--- Check if any unit of a group is inside a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if at least one unit is inside the zone or false if no unit is inside. +function GROUP:IsInZone( Zone ) + + if self:IsAlive() then + + for UnitID, UnitData in pairs(self:GetUnits()) do + local Unit = UnitData -- Wrapper.Unit#UNIT + + if Zone:IsVec3InZone(Unit:GetVec3()) then + return true -- At least one unit is in the zone. That is enough. + else + -- This one is not but another could be. + end + + end + + return false + end + + return nil +end + --- Returns true if all units of the group are within a @{Zone}. -- @param #GROUP self -- @param Core.Zone#ZONE_BASE Zone The zone to test. @@ -2095,6 +2132,7 @@ end --- Calculate the maxium A2G threat level of the Group. -- @param #GROUP self +-- @return #number Number between 0 and 10. function GROUP:CalculateThreatLevelA2G() local MaxThreatLevelA2G = 0 @@ -2110,6 +2148,25 @@ function GROUP:CalculateThreatLevelA2G() return MaxThreatLevelA2G end +--- Get threat level of the group. +-- @param #GROUP self +-- @return #number Max threat level (a number between 0 and 10). +function GROUP:GetThreatLevel() + + local threatlevelMax = 0 + for UnitName, UnitData in pairs(self:GetUnits()) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + + local threatlevel = ThreatUnit:GetThreatLevel() + if threatlevel > threatlevelMax then + threatlevelMax=threatlevel + end + end + + return threatlevelMax +end + + --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self -- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua new file mode 100644 index 000000000..5d65d3fbe --- /dev/null +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -0,0 +1,776 @@ +--- **Wrapper** - Markers On the F10 map. +-- +-- **Main Features:** +-- +-- * Convenient handling of markers via multiple user API functions. +-- * Update text and position of marker easily via scripting. +-- * Delay creation and removal of markers via (optional) parameters. +-- * Retrieve data such as text and coordinate. +-- * Marker specific FSM events when a marker is added, removed or changed. +-- * Additional FSM events when marker text or position is changed. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Wrapper.Marker +-- @image Wrapper_Marker.png + + +--- Marker class. +-- @type MARKER +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number mid Marker ID. +-- @field Core.Point#COORDINATE coordinate Coordinate of the mark. +-- @field #string text Text displayed in the mark panel. +-- @field #string message Message dispayed when the mark is added. +-- @field #boolean readonly Marker is read-only. +-- @field #number coalition Coalition to which the marker is displayed. +-- @extends Core.Fsm#FSM + +--- Just because... +-- +-- === +-- +-- ![Banner Image](..\Presentations\MARKER\Marker_Main.jpg) +-- +-- # The MARKER Class Idea +-- +-- The MARKER class simplifies creating, updating and removing of markers on the F10 map. +-- +-- # Create a Marker +-- +-- -- Create a MARKER object at Batumi with a trivial text. +-- local Coordinate=AIRBASE:FindByName("Batumi"):GetCoordinate() +-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield") +-- +-- Now this does **not** show the marker yet. We still need to specifiy to whom it is shown. There are several options, i.e. +-- show the marker to everyone, to a speficic coaliton only, or only to a specific group. +-- +-- ## For Everyone +-- +-- If the marker should be visible to everyone, you can use the :ToAll() function. +-- +-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToAll() +-- +-- ## For a Coaliton +-- +-- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function. +-- +-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToCoaliton(coaliton.side.BLUE) +-- +-- ### To Blue Coaliton +-- +-- ### To Red Coalition +-- +-- This would show the marker only to the Blue coaliton. +-- +-- ## For a Group +-- +-- +-- # Removing a Marker +-- +-- +-- # Updating a Marker +-- +-- The marker text and coordinate can be updated easily as shown below. +-- +-- However, note that **updateing involves to remove and recreate the marker if either text or its coordinate is changed**. +-- *This is a DCS scripting engine limitation.* +-- +-- ## Update Text +-- +-- If you created a marker "mymarker" as shown above, you can update the dispayed test by +-- +-- mymarker:UpdateText("I am the new text at Batumi") +-- +-- The update can also be delayed by, e.g. 90 seconds, using +-- +-- mymarker:UpdateText("I am the new text at Batumi", 90) +-- +-- ## Update Coordinate +-- +-- If you created a marker "mymarker" as shown above, you can update its coordinate on the F10 map by +-- +-- mymarker:UpdateCoordinate(NewCoordinate) +-- +-- The update can also be delayed by, e.g. 60 seconds, using +-- +-- mymarker:UpdateCoordinate(NewCoordinate, 60) +-- +-- # Retrieve Data +-- +-- The important data as the displayed text and the coordinate of the marker can be retrieved easily. +-- +-- ## Text +-- +-- local text=mymarker:GetText() +-- env.info("Marker Text = " .. text) +-- +-- ## Coordinate +-- +-- local Coordinate=mymarker:GetCoordinate() +-- env.info("Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS()) +-- +-- +-- # FSM Events +-- +-- Moose creates addditonal events, so called FSM event, when markers are added, changed, removed, and text or the coordianteis updated. +-- +-- These events can be captured and used for processing via OnAfter functions as shown below. +-- +-- ## Added +-- +-- ## Changed +-- +-- ## Removed +-- +-- ## TextUpdate +-- +-- ## CoordUpdate +-- +-- +-- # Examples +-- +-- +-- @field #MARKER +MARKER = { + ClassName = "MARKER", + Debug = false, + lid = nil, + mid = nil, + coordinate = nil, + text = nil, + message = nil, + readonly = nil, + coalition = nil, +} + +--- Marker ID. Running number. +_MARKERID=0 + +--- Marker class version. +-- @field #string version +MARKER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: User "Get" functions. E.g., :GetCoordinate() +-- DONE: Add delay to user functions. +-- DONE: Handle events. +-- DONE: Create FSM events. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new MARKER class object. +-- @param #MARKER self +-- @param Core.Point#COORDINATE Coordinate Coordinate where to place the marker. +-- @param #string Text Text displayed on the mark panel. +-- @return #MARKER self +function MARKER:New(Coordinate, Text) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #MARKER + + self.coordinate=Coordinate + + self.text=Text + + -- Defaults + self.readonly=false + self.message="" + + -- New marker ID. This is not the one of the actual marker. + _MARKERID=_MARKERID+1 + + self.myid=_MARKERID + + -- Log ID. + self.lid=string.format("Marker #%d | ", self.myid) + + -- Start State. + self:SetStartState("Invisible") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Invisible", "Added", "Visible") -- Marker was added. + self:AddTransition("Visible", "Removed", "Invisible") -- Marker was removed. + self:AddTransition("*", "Changed", "*") -- Marker was changed. + + self:AddTransition("*", "TextUpdate", "*") -- Text updated. + self:AddTransition("*", "CoordUpdate", "*") -- Coordinates updated. + + --- Triggers the FSM event "Added". + -- @function [parent=#MARKER] Added + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- Triggers the delayed FSM event "Added". + -- @function [parent=#MARKER] __Added + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- On after "Added" event user function. + -- @function [parent=#MARKER] OnAfterAdded + -- @param #MARKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Event#EVENTDATA EventData Event data table. + + + --- Triggers the FSM event "Removed". + -- @function [parent=#MARKER] Removed + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- Triggers the delayed FSM event "Removed". + -- @function [parent=#MARKER] __Removed + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- On after "Removed" event user function. + -- @function [parent=#MARKER] OnAfterRemoved + -- @param #MARKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Event#EVENTDATA EventData Event data table. + + + --- Triggers the FSM event "Changed". + -- @function [parent=#MARKER] Changed + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- Triggers the delayed FSM event "Changed". + -- @function [parent=#MARKER] __Changed + -- @param #MARKER self + -- @param Core.Event#EVENTDATA EventData Event data table. + + --- On after "Changed" event user function. + -- @function [parent=#MARKER] OnAfterChanged + -- @param #MARKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Event#EVENTDATA EventData Event data table. + + + --- Triggers the FSM event "TextUpdate". + -- @function [parent=#MARKER] TextUpdate + -- @param #MARKER self + -- @param #string Text The new text. + + --- Triggers the delayed FSM event "TextUpdate". + -- @function [parent=#MARKER] __TextUpdate + -- @param #MARKER self + -- @param #string Text The new text. + + --- On after "TextUpdate" event user function. + -- @function [parent=#MARKER] OnAfterTextUpdate + -- @param #MARKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Text The new text. + + + --- Triggers the FSM event "CoordUpdate". + -- @function [parent=#MARKER] CoordUpdate + -- @param #MARKER self + -- @param Core.Point#COORDINATE Coordinate The new Coordinate. + + --- Triggers the delayed FSM event "CoordUpdate". + -- @function [parent=#MARKER] __CoordUpdate + -- @param #MARKER self + -- @param Core.Point#COORDINATE Coordinate The updated Coordinate. + + --- On after "CoordUpdate" event user function. + -- @function [parent=#MARKER] OnAfterCoordUpdate + -- @param #MARKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Point#COORDINATE Coordinate The updated Coordinate. + + + -- Handle events. + self:HandleEvent(EVENTS.MarkAdded) + self:HandleEvent(EVENTS.MarkRemoved) + self:HandleEvent(EVENTS.MarkChange) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User API Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Marker is readonly. Text cannot be changed and marker cannot be removed. +-- @param #MARKER self +-- @return #MARKER self +function MARKER:ReadOnly() + + self.readonly=true + + return self +end + +--- Set message that is displayed on screen if the marker is added. +-- @param #MARKER self +-- @param #string Text Message displayed when the marker is added. +-- @return #MARKER self +function MARKER:Message(Text) + + self.message=Text or "" + + return self +end + +--- Place marker visible for everyone. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToAll(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.ToAll, self) + else + + self.toall=true + self.tocoaliton=nil + self.coalition=nil + self.togroup=nil + self.groupname=nil + self.groupid=nil + + -- First remove an existing mark. + if self.shown then + self:Remove() + end + + self.mid=UTILS.GetMarkID() + + -- Call DCS function. + trigger.action.markToAll(self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message) + + end + + return self +end + +--- Place marker visible for a specific coalition only. +-- @param #MARKER self +-- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coaliton.side.RED`. +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToCoalition(Coalition, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.ToCoalition, self, Coalition) + else + + self.coalition=Coalition + + self.tocoaliton=true + self.toall=false + self.togroup=false + self.groupname=nil + self.groupid=nil + + -- First remove an existing mark. + if self.shown then + self:Remove() + end + + self.mid=UTILS.GetMarkID() + + -- Call DCS function. + trigger.action.markToCoalition(self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message) + + end + + return self +end + +--- Place marker visible for the blue coalition only. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToBlue(Delay) + self:ToCoalition(coalition.side.BLUE, Delay) + return self +end + +--- Place marker visible for the blue coalition only. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToRed(Delay) + self:ToCoalition(coalition.side.RED, Delay) + return self +end + +--- Place marker visible for the neutral coalition only. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToNeutral(Delay) + self:ToCoalition(coalition.side.NEUTRAL, Delay) + return self +end + + +--- Place marker visible for a specific group only. +-- @param #MARKER self +-- @param Wrapper.Group#GROUP Group The group to which the marker is displayed. +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:ToGroup(Group, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.ToGroup, self, Group) + else + + -- Check if group exists. + if Group and Group:IsAlive()~=nil then + + self.groupid=Group:GetID() + + if self.groupid then + + self.groupname=Group:GetName() + + self.togroup=true + self.tocoaliton=nil + self.coalition=nil + self.toall=nil + + -- First remove an existing mark. + if self.shown then + self:Remove() + end + + self.mid=UTILS.GetMarkID() + + -- Call DCS function. + trigger.action.markToGroup(self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message) + + end + + else + --TODO: Warning! + end + + end + + return self +end + +--- Update the text displayed on the mark panel. +-- @param #MARKER self +-- @param #string Text Updated text. +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:UpdateText(Text, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.UpdateText, self, Text) + else + + self.text=tostring(Text) + + self:Refresh() + + self:TextUpdate(tostring(Text)) + + end + + return self +end + +--- Update the coordinate where the marker is displayed. +-- @param #MARKER self +-- @param Core.Point#COORDINATE Coordinate The new coordinate. +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:UpdateCoordinate(Coordinate, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.UpdateCoordinate, self, Coordinate) + else + + self.coordinate=Coordinate + + self:Refresh() + + self:CoordUpdate(Coordinate) + + end + + return self +end + +--- Refresh the marker. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is created. +-- @return #MARKER self +function MARKER:Refresh(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.Refresh, self) + else + + if self.toall then + + self:ToAll() + + elseif self.tocoaliton then + + self:ToCoalition(self.coalition) + + elseif self.togroup then + + local group=GROUP:FindByName(self.groupname) + + self:ToGroup(group) + + else + self:E(self.lid.."ERROR: unknown To in :Refresh()!") + end + + end + + return self +end + +--- Remove a marker. +-- @param #MARKER self +-- @param #number Delay (Optional) Delay in seconds, before the marker is removed. +-- @return #MARKER self +function MARKER:Remove(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MARKER.Remove, self) + else + + if self.shown then + + -- Call DCS function. + trigger.action.removeMark(self.mid) + + end + + end + + return self +end + +--- Get position of the marker. +-- @param #MARKER self +-- @return Core.Point#COORDINATE The coordinate of the marker. +function MARKER:GetCoordinate() + return self.coordinate +end + +--- Get text that is displayed in the marker panel. +-- @param #MARKER self +-- @return #string Marker text. +function MARKER:GetText() + return self.text +end + +--- Set text that is displayed in the marker panel. Note this does not show the marker. +-- @param #MARKER self +-- @param #string Text Marker text. Default is an empty sting "". +-- @return #MARKER self +function MARKER:SetText(Text) + self.text=Text and tostring(Text) or "" + return self +end + + +--- Check if marker is currently visible on the F10 map. +-- @param #MARKER self +-- @return #boolean True if the marker is currently visible. +function MARKER:IsVisible() + return self:Is("Visible") +end + +--- Check if marker is currently invisible on the F10 map. +-- @param #MARKER self +-- @return +function MARKER:IsInvisible() + return self:Is("Invisible") +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function when a MARKER is added. +-- @param #MARKER self +-- @param Core.Event#EVENTDATA EventData +function MARKER:OnEventMarkAdded(EventData) + + if EventData and EventData.MarkID then + + local MarkID=EventData.MarkID + + self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + + if MarkID==self.mid then + + self.shown=true + + self:Added(EventData) + + end + + end + +end + +--- Event function when a MARKER is removed. +-- @param #MARKER self +-- @param Core.Event#EVENTDATA EventData +function MARKER:OnEventMarkRemoved(EventData) + + if EventData and EventData.MarkID then + + local MarkID=EventData.MarkID + + self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + + if MarkID==self.mid then + + self.shown=false + + self:Removed(EventData) + + end + + end + +end + +--- Event function when a MARKER changed. +-- @param #MARKER self +-- @param Core.Event#EVENTDATA EventData +function MARKER:OnEventMarkChange(EventData) + + if EventData and EventData.MarkID then + + local MarkID=EventData.MarkID + + self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID))) + + if MarkID==self.mid then + + self:Changed(EventData) + + self:TextChanged(tostring(EventData.MarkText)) + + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Added" event. +-- @param #MARKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Event#EVENTDATA EventData Event data table. +function MARKER:onafterAdded(From, Event, To, EventData) + + -- Debug info. + local text=string.format("Captured event MarkAdded for myself:\n") + text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) + text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) + text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) + text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") + text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") + text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) + self:T2(self.lid..text) + +end + +--- On after "Removed" event. +-- @param #MARKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Event#EVENTDATA EventData Event data table. +function MARKER:onafterRemoved(From, Event, To, EventData) + + -- Debug info. + local text=string.format("Captured event MarkRemoved for myself:\n") + text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) + text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) + text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) + text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") + text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") + text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) + self:T2(self.lid..text) + +end + +--- On after "Changed" event. +-- @param #MARKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Event#EVENTDATA EventData Event data table. +function MARKER:onafterChanged(From, Event, To, EventData) + + -- Debug info. + local text=string.format("Captured event MarkChange for myself:\n") + text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) + text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) + text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) + text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") + text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") + text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) + self:T2(self.lid..text) + +end + +--- On after "TextUpdate" event. +-- @param #MARKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Text The updated text, displayed in the mark panel. +function MARKER:onafterTextUpdate(From, Event, To, Text) + + self:T(self.lid..string.format("New Marker Text:\n%s", Text)) + +end + +--- On after "CoordUpdate" event. +-- @param #MARKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate The updated coordinates. +function MARKER:onafterCoordUpdate(From, Event, To, Coordinate) + + self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS())) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index be85454a3..5097935bb 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -15,6 +15,8 @@ -- @extends Wrapper.Identifiable#IDENTIFIABLE --- @type POSITIONABLE +-- @field Core.Point#COORDINATE coordinate Coordinate object. +-- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -45,6 +47,8 @@ POSITIONABLE = { ClassName = "POSITIONABLE", PositionableName = "", + coordinate = nil, + pointvec3 = nil, } --- @field #POSITIONABLE.__ @@ -121,10 +125,17 @@ function POSITIONABLE:Destroy( GenerateEvent ) return nil end +--- Returns the DCS object. Polymorphic for other classes like UNIT, STATIC, GROUP, AIRBASE. +-- @param #POSITIONABLE self +-- @return DCS#Object The DCS object. +function POSITIONABLE:GetDCSObject() + return nil +end + --- Returns a pos3 table of the objects current position and orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation. -- Coordinates are dependent on the position of the maps origin. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Position Table consisting of the point and orientation tables. +-- @return DCS#Position3 Table consisting of the point and orientation tables. function POSITIONABLE:GetPosition() self:F2( self.PositionableName ) @@ -215,27 +226,44 @@ function POSITIONABLE:GetPositionVec3() return nil end ---- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +--- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE or `nil` if it is not existing or alive. +function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - self:T2( PositionableVec2 ) - return PositionableVec2 + local vec3=DCSPositionable:getPoint() + + if vec3 then + return vec3 + else + self:E("ERROR: Cannot get vec3!") + end end - BASE:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } ) + -- ERROR! + self:E( { "Cannot GetVec3", Positionable = self, Alive = self:IsAlive() } ) + return nil +end + +--- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE or #nil if it is not existing or alive. +function POSITIONABLE:GetVec2() + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local Vec3=DCSPositionable:getPoint() --DCS#Vec3 + + return {x=Vec3.x, y=Vec3.z} + end + + self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -258,7 +286,7 @@ function POSITIONABLE:GetPointVec2() return PositionablePointVec2 end - BASE:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) + self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -268,17 +296,29 @@ end -- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() - self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableVec3 = self:GetPositionVec3() - - local PositionablePointVec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) - self:T2( PositionablePointVec3 ) - return PositionablePointVec3 + -- Get 3D vector. + local PositionableVec3 = self:GetPositionVec3() + + if false and self.pointvec3 then + + -- Update vector. + self.pointvec3.x=PositionableVec3.x + self.pointvec3.y=PositionableVec3.y + self.pointvec3.z=PositionableVec3.z + + else + + -- Create a new POINT_VEC3 object. + self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) + + end + + return self.pointvec3 end BASE:E( { "Cannot GetPointVec3", Positionable = self, Alive = self:IsAlive() } ) @@ -289,27 +329,62 @@ end --- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. -function POSITIONABLE:GetCoordinate() - self:F2( self.PositionableName ) +function POSITIONABLE:GetCoord() + -- Get DCS object. local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableVec3 = self:GetPositionVec3() - - local PositionableCoordinate = COORDINATE:NewFromVec3( PositionableVec3 ) - PositionableCoordinate:SetHeading( self:GetHeading() ) - PositionableCoordinate:SetVelocity( self:GetVelocityMPS() ) - self:T2( PositionableCoordinate ) - return PositionableCoordinate + -- Get the current position. + local Vec3 = self:GetVec3() + + if self.coordinate then + + -- Update vector. + self.coordinate.x=Vec3.x + self.coordinate.y=Vec3.y + self.coordinate.z=Vec3.z + + else + + -- New COORDINATE. + self.coordinate=COORDINATE:NewFromVec3(Vec3) + + end + + return self.coordinate end + -- Error message. BASE:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end +--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +function POSITIONABLE:GetCoordinate() + + -- Get DCS object. + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + -- Get the current position. + local PositionableVec3 = self:GetVec3() + + -- Return a new coordiante object. + return COORDINATE:NewFromVec3(PositionableVec3) + + end + + -- Error message. + self:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) + return nil +end + --- Returns a COORDINATE object, which is offset with respect to the orientation of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @param #number x Offset in the direction "the nose" of the unit is pointing in meters. Default 0 m. @@ -384,26 +459,6 @@ function POSITIONABLE:GetRandomVec3( Radius ) return nil end ---- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - BASE:E( { "Cannot GetVec3", Positionable = self, Alive = self:IsAlive() } ) - - return nil -end - --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self @@ -1533,7 +1588,7 @@ end --- Returns true if the unit is within a @{Zone}. --- @param #STPOSITIONABLEATIC self +-- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} function POSITIONABLE:IsInZone( Zone ) From 8e6e9cbb4db4e3962b3ea7adb991c20659276db7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 29 Aug 2020 22:10:45 +0200 Subject: [PATCH 2/2] Updated Moose Modules.lua - Added new class files Profiler, Timer, Marker --- Moose Development/Moose/Modules.lua | 3 +++ Moose Setup/Moose.files | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 22f23bb75..73653489c 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -1,6 +1,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) @@ -24,6 +25,7 @@ __Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) __Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) @@ -37,6 +39,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 4852f9346..15de1d50c 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -1,5 +1,7 @@ Utilities/Routines.lua Utilities/Utils.lua +Utilities/Enums.lua +Utilities/Profiler.lua Core/Base.lua Core/UserFlag.lua @@ -20,6 +22,7 @@ Core/Fsm.lua Core/Radio.lua Core/Spawn.lua Core/SpawnStatic.lua +Core/Timer.lua Core/Goal.lua Core/Spot.lua @@ -33,6 +36,7 @@ Wrapper/Client.lua Wrapper/Static.lua Wrapper/Airbase.lua Wrapper/Scenery.lua +Wrapper/Marker.lua Cargo/Cargo.lua Cargo/CargoUnit.lua