From b346dabdf88d231c2a9e3748972d0cc5752eebbf Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:24:25 +0100 Subject: [PATCH 01/17] Update introduction.md (#2043) --- docs/beginner/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/beginner/introduction.md b/docs/beginner/introduction.md index 38e3334cd..65452f7ac 100644 --- a/docs/beginner/introduction.md +++ b/docs/beginner/introduction.md @@ -37,12 +37,12 @@ videos on YouTube. in applications. It's main advantages are: - It is fast, -- it is portabel (Windows, Linux, MacOS), +- it is portable (Windows, Linux, MacOS), - it is easy to use. -[Lua] is embedded in DCS, so we can use it without any modifacation to the game. +[Lua] is embedded in DCS, so we can use it without any modification to the game. -## What is are scripts, frameworks and classes? +## What are scripts, frameworks and classes? A script is a set of instructions in plain text read by a computer and processed on the fly. Scripts do not need to be compiled before execution, unlike exe From ba1dcfcdbae66d6edd212b0e5dd364c11f612a4b Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:49:16 +0100 Subject: [PATCH 02/17] Update Utils.lua Avoid file loading stop scripts --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ab607f1b7..6e5e05145 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2391,7 +2391,7 @@ function UTILS.LoadFromFile(Path,Filename) -- Check if file exists. local exists=UTILS.CheckFileExists(Path,Filename) if not exists then - BASE:E(string.format("ERROR: File %s does not exist!",filename)) + BASE:I(string.format("ERROR: File %s does not exist!",filename)) return false end From 2e6cac7bee6c0e2960b712a431e13577c9f57381 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:50:12 +0100 Subject: [PATCH 03/17] Update Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ab607f1b7..6e5e05145 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2391,7 +2391,7 @@ function UTILS.LoadFromFile(Path,Filename) -- Check if file exists. local exists=UTILS.CheckFileExists(Path,Filename) if not exists then - BASE:E(string.format("ERROR: File %s does not exist!",filename)) + BASE:I(string.format("ERROR: File %s does not exist!",filename)) return false end From 434f985e778b12bf6338109543825397b34b4b08 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 21 Nov 2023 10:12:46 +0100 Subject: [PATCH 04/17] #MSRS * Cleaner config loading strategy --- Moose Development/Moose/Sound/SRS.lua | 52 ++++++--------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 856595d0b..7652c3128 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -377,9 +377,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation, Volume, AltBackend) return self:_NewAltBackend(Backend) end - local success = self:LoadConfigFile(nil,nil,self.ConfigLoaded) - - if (not success) and (not self.ConfigLoaded) then + if not self.ConfigLoaded then -- If no AltBackend table, the proceed with default initialisation self:SetPath(PathToSRS) @@ -446,7 +444,7 @@ function MSRS:SetPath(Path) end -- Debug output. - self:I(string.format("SRS path=%s", self:GetPath())) + self:T(string.format("SRS path=%s", self:GetPath())) end return self end @@ -1119,7 +1117,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end -- Debug output. - self:I("MSRS command="..command) + self:T("MSRS command="..command) return command end @@ -1128,7 +1126,6 @@ end -- @param #MSRS self -- @param #string Path Path to config file, defaults to "C:\Users\\Saved Games\DCS\Config" -- @param #string Filename File to load, defaults to "Moose_MSRS.lua" --- @param #boolean ConfigLoaded - if true, skip the loading -- @return #boolean success -- @usage -- 0) Benefits: Centralize configuration of SRS, keep paths and keys out of the mission source code, making it safer and easier to move missions to/between servers, @@ -1190,46 +1187,17 @@ end -- atis:SetSRS(nil,nil,nil,MSRS.Voices.Google.Standard.en_US_Standard_H) -- --Start ATIS -- atis:Start() -function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded) +function MSRS:LoadConfigFile(Path,Filename) local path = Path or lfs.writedir()..MSRS.ConfigFilePath local file = Filename or MSRS.ConfigFileName or "Moose_MSRS.lua" + local pathandfile = path..file + local filexsists = UTILS.FileExists(pathandfile) - if UTILS.CheckFileExists(path,file) and not ConfigLoaded then + if filexsists and not MSRS.ConfigLoaded then assert(loadfile(path..file))() -- now we should have a global var MSRS_Config if MSRS_Config then - --[[ - -- Moose MSRS default Config - MSRS_Config = { - Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed - Port = 5002, -- adjust as needed - Frequency = {127,243}, -- must be a table, 1..n entries! - Modulation = {0,0}, -- must be a table, 1..n entries, one for each frequency! - Volume = 1.0, - Coalition = 0, -- 0 = Neutral, 1 = Red, 2 = Blue - Coordinate = {0,0,0}, -- x,y,alt - optional - Culture = "en-GB", - Gender = "male", - Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional - Label = "MSRS", - Voice = "Microsoft Hazel Desktop", - -- gRPC (optional) - GRPC = { -- see https://github.com/DCS-gRPC/rust-server - coalition = "blue", -- blue, red, neutral - DefaultProvider = "gcloud", -- win, gcloud, aws, or azure, some of the values below depend on your cloud provider - gcloud = { - key = "", -- for gRPC Google API key - --secret = "", -- needed for aws - --region = "",-- needed for aws - defaultVoice = MSRS.Voices.Google.Standard.en_GB_Standard_F, - }, - win = { - defaultVoice = "Hazel", - }, - } - } - --]] if self then self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.port = MSRS_Config.Port or 5002 @@ -1281,8 +1249,9 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded) end end env.info("MSRS - Sucessfully loaded default configuration from disk!",false) - else - env.info("MSRS - Cannot load default configuration from disk!",false) + end + if not filexsists then + env.info("MSRS - Cannot find default configuration file!",false) return false end @@ -2013,6 +1982,7 @@ function MSRSQUEUE:_CheckRadioQueue(delay) end +MSRS.LoadConfigFile() ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 6c4a64601f20a8e1d78e8a76c2b8f620b09e32e3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 21 Nov 2023 13:21:22 +0100 Subject: [PATCH 05/17] MSRS Docu fix --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 7652c3128..7377de207 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1170,7 +1170,7 @@ end -- This will populate variables for the MSRS raw class and all instances you create with e.g. `mysrs = MSRS:New()` -- Optionally you can also load this per **single instance** if so needed, i.e. -- --- mysrs:LoadConfig(Path,Filename) +-- mysrs:LoadConfigFile(Path,Filename) -- -- 4) Use the config in your code like so, variable names are basically the same as in the config file, but all lower case, examples: -- From 2dfde7d1fdc0e71790958e41dcdc7b453e2ca8f3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 21 Nov 2023 13:22:51 +0100 Subject: [PATCH 06/17] xxx --- Moose Development/Moose/Ops/PlayerTask.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index c01f75299..28110a7ec 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1579,7 +1579,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.ClusterRadius = 0.5 self.TargetRadius = 500 - self.ClientFilter = ClientFilter or "" + self.ClientFilter = ClientFilter --or "" self.TargetQueue = FIFO:New() -- Utilities.FiFo#FIFO self.TaskQueue = FIFO:New() -- Utilities.FiFo#FIFO From 12d68a41ca23d66a353c1ad21d45887d5779b180 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 22 Nov 2023 17:54:52 +0100 Subject: [PATCH 07/17] #MSRS * Added option to explicitly set/switch the TTS provider between Google and MS (the default) * Added this option to the config file, so you can set up both but switch --- Moose Development/Moose/Sound/SRS.lua | 54 ++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 7377de207..12460f737 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -50,6 +50,7 @@ -- @field #string ConfigFileName Name of the standard config file -- @field #string ConfigFilePath Path to the standard config file -- @field #boolean ConfigLoaded +-- @field #string ttsprovider Default provider TTS backend, e.g. "Google" or "Microsoft", default is Microsoft -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -127,6 +128,10 @@ -- -- Use @{#MSRS.SetVolume} to define the SRS volume. Defaults to 1.0. Allowed values are between 0.0 and 1.0, from silent to loudest. -- +-- ## Config file for many variables, auto-loaded by Moose +-- +-- See @{#MSRS.LoadConfigFile} for details on how to set this up. +-- -- ## Set DCS-gRPC as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS -- -- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend for transmitting text-to-speech over SRS. @@ -191,11 +196,12 @@ MSRS = { ConfigFileName = "Moose_MSRS.lua", ConfigFilePath = "Config\\", ConfigLoaded = false, + ttsprovider = "Microsoft", } --- MSRS class version. -- @field #string version -MSRS.version="0.1.2" +MSRS.version="0.1.3" --- Voices -- @type MSRS.Voices @@ -672,7 +678,7 @@ function MSRS:SetCoordinate(Coordinate) return self end ---- Use google text-to-speech. +--- Use google text-to-speech credentials. Also sets Google as default TTS provider. -- @param #MSRS self -- @param #string PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". Can also be the Google API key. -- @return #MSRS self @@ -686,13 +692,14 @@ function MSRS:SetGoogle(PathToCredentials) self.GRPCOptions.DefaultProvider = "gcloud" self.GRPCOptions.gcloud.key = PathToCredentials + self.ttsprovider = "Google" end return self end ---- Use google text-to-speech. +--- gRPC Backend: Use google text-to-speech set the API key. -- @param #MSRS self -- @param #string APIKey API Key, usually a string of length 40 with characters and numbers. -- @return #MSRS self @@ -706,6 +713,22 @@ function MSRS:SetGoogleAPIKey(APIKey) return self end +--- Use Google text-to-speech as default. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:SetTTSProviderGoogle() + self.ttsprovider = "Google" + return self +end + +--- Use Microsoft text-to-speech as default. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:SetTTSProviderMicrosoft() + self.ttsprovider = "Microsoft" + return self +end + --- Print SRS STTS help to DCS log file. -- @param #MSRS self -- @return #MSRS self @@ -1112,7 +1135,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end -- Set google. - if self.google then + if self.google and self.ttsprovider == "Google" then command=command..string.format(' --ssml -G "%s"', self.google) end @@ -1135,18 +1158,19 @@ end -- -- -- Moose MSRS default Config -- MSRS_Config = { --- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed +-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- adjust as needed, note double \\ -- Port = 5002, -- adjust as needed -- Frequency = {127,243}, -- must be a table, 1..n entries! -- Modulation = {0,0}, -- must be a table, 1..n entries, one for each frequency! --- Volume = 1.0, +-- Volume = 1.0, -- 0.0 to 1.0 -- Coalition = 0, -- 0 = Neutral, 1 = Red, 2 = Blue --- Coordinate = {0,0,0}, -- x,y,alt - optional +-- Coordinate = {0,0,0}, -- x,y,altitude - optional, all in meters -- Culture = "en-GB", -- Gender = "male", --- Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional +-- Google = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- path to google json key file - optional. -- Label = "MSRS", -- Voice = "Microsoft Hazel Desktop", +-- Provider = "Microsoft", -- this is the default TTS provider, e.g. "Google" or "Microsoft" -- -- gRPC (optional) -- GRPC = { -- see https://github.com/DCS-gRPC/rust-server -- coalition = "blue", -- blue, red, neutral @@ -1163,9 +1187,13 @@ end -- } -- } -- --- 3) Load the config into the MSRS raw class before you do anything else: +-- 3) The config file is automatically loaded when Moose starts. YOu can also load the config into the MSRS raw class manually before you do anything else: -- -- MSRS.LoadConfigFile() -- Note the "." here +-- +-- Optionally, your might want to provide a specific path and filename: +-- +-- MSRS.LoadConfigFile(nil,MyPath,MyFilename) -- Note the "." here -- -- This will populate variables for the MSRS raw class and all instances you create with e.g. `mysrs = MSRS:New()` -- Optionally you can also load this per **single instance** if so needed, i.e. @@ -1210,6 +1238,9 @@ function MSRS:LoadConfigFile(Path,Filename) self.culture = MSRS_Config.Culture or "en-GB" self.gender = MSRS_Config.Gender or "male" self.google = MSRS_Config.Google + if MSRS_Config.Provider then + self.ttsprovider = MSRS_Config.Provider + end self.Label = MSRS_Config.Label or "MSRS" self.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel if MSRS_Config.GRPC then @@ -1234,6 +1265,9 @@ function MSRS:LoadConfigFile(Path,Filename) MSRS.culture = MSRS_Config.Culture or "en-GB" MSRS.gender = MSRS_Config.Gender or "male" MSRS.google = MSRS_Config.Google + if MSRS_Config.Provider then + self.ttsprovider = MSRS_Config.Provider + end MSRS.Label = MSRS_Config.Label or "MSRS" MSRS.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel if MSRS_Config.GRPC then @@ -1248,7 +1282,7 @@ function MSRS:LoadConfigFile(Path,Filename) MSRS.ConfigLoaded = true end end - env.info("MSRS - Sucessfully loaded default configuration from disk!",false) + env.info("MSRS - Successfully loaded default configuration from disk!",false) end if not filexsists then env.info("MSRS - Cannot find default configuration file!",false) From 02a87d9fe0e2d74282ab13430dd944103445d87e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 22 Nov 2023 18:35:12 +0100 Subject: [PATCH 08/17] fix --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 12460f737..537825b1b 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1266,7 +1266,7 @@ function MSRS:LoadConfigFile(Path,Filename) MSRS.gender = MSRS_Config.Gender or "male" MSRS.google = MSRS_Config.Google if MSRS_Config.Provider then - self.ttsprovider = MSRS_Config.Provider + MSRS.ttsprovider = MSRS_Config.Provider end MSRS.Label = MSRS_Config.Label or "MSRS" MSRS.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel From f071c674d06f3340203cbdb3ed2779af7794a4d7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 22 Nov 2023 18:35:23 +0100 Subject: [PATCH 09/17] fix --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 951dda27b..fe659a1da 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1266,7 +1266,7 @@ function MSRS:LoadConfigFile(Path,Filename) MSRS.gender = MSRS_Config.Gender or "male" MSRS.google = MSRS_Config.Google if MSRS_Config.Provider then - self.ttsprovider = MSRS_Config.Provider + MSRS.ttsprovider = MSRS_Config.Provider end MSRS.Label = MSRS_Config.Label or "MSRS" MSRS.voice = MSRS_Config.Voice --or MSRS.Voices.Microsoft.Hazel From cd0f854f41e1af876f8e71a577e06aab766178bb Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Wed, 22 Nov 2023 23:48:00 +0100 Subject: [PATCH 10/17] added functions: GetReleaseHeading GetReleaseAltitudeASL GetReleaseCoordinate GetReleasePitch GetImpactHeading --- Moose Development/Moose/Wrapper/Weapon.lua | 61 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 7317d53fd..5ba880123 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -223,7 +223,15 @@ function WEAPON:New(WeaponObject) -- Set log ID. self.lid=string.format("[%s] %s | ", self.typeName, self.name) - + + if self.launcherUnit then + self.releaseHeading = self.launcherUnit:GetHeading() + self.releaseAltitudeASL = self.launcherUnit:GetAltitude() + self.releaseAltitudeAGL = self.launcherUnit:GetAltitude(true) + self.releaseCoordinate = self.launcherUnit:GetCoordinate() + self.releasePitch = self.launcherUnit:GetPitch() + end + -- Set default parameters self:SetTimeStepTrack() self:SetDistanceInterceptPoint() @@ -552,6 +560,52 @@ function WEAPON:GetImpactCoordinate() return self.impactCoord end +--- Get the heading on which the weapon was released +-- @param #WEAPON self +-- @param #bool AccountForMagneticInclination (Optional) If true will account for the magnetic declination of the current map. Default is true +-- @return #number Heading +function WEAPON:GetReleaseHeading(AccountForMagneticInclination) + AccountForMagneticInclination = AccountForMagneticInclination or true + if AccountForMagneticInclination then return self.releaseHeading - UTILS.GetMagneticDeclination() else return self.releaseHeading end +end + +--- Get the altitude above sea level at which the weapon was released +-- @param #WEAPON self +-- @return #number Altitude in meters +function WEAPON:GetReleaseAltitudeASL() + return self.releaseAltitudeASL +end + +--- Get the altitude above ground level at which the weapon was released +-- @param #WEAPON self +-- @return #number Altitude in meters +function WEAPON:GetReleaseAltitudeAGL() + return self.releaseAltitudeAGL +end + +--- Get the coordinate where the weapon was released +-- @param #WEAPON self +-- @return Core.Point#COORDINATE Impact coordinate (if any). +function WEAPON:GetReleaseCoordinate() + return self.releaseCoordinate +end + +--- Get the pitch of the unit when the weapon was released +-- @param #WEAPON self +-- @return #number Degrees +function WEAPON:GetReleasePitch() + return self.releasePitch +end + +--- Get the heading of the weapon when it impacted. Note that this might not exist if the weapon has not impacted yet! +-- @param #WEAPON self +-- @param #bool AccountForMagneticInclination (Optional) If true will account for the magnetic declination of the current map. Default is true +-- @return #number Heading +function WEAPON:GetImpactHeading(AccountForMagneticInclination) + AccountForMagneticInclination = AccountForMagneticInclination or true + if AccountForMagneticInclination then return self.impactHeading - UTILS.GetMagneticDeclination() else return self.impactHeading end +end + --- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS... -- @param #WEAPON self -- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`. @@ -781,7 +835,10 @@ function WEAPON:_TrackWeapon(time) -- Safe impact coordinate. self.impactCoord=COORDINATE:NewFromVec3(self.vec3) - + + -- Safe impact heading + self.impactHeading = UTILS.VecHdg(self:GetVelocityVec3()) + -- Mark impact point on F10 map. if self.impactMark then self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName)) From a50dde7f2b2abffdd4479aae29997cd8ad0776ab Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Thu, 23 Nov 2023 15:18:23 +0100 Subject: [PATCH 11/17] added functions: UTILS.TimeNow UTILS.TimeDifferenceInSeconds UTILS.TimeLaterThan UTILS.TimeBefore UTILS.CombineTimeStrings UTILS.SubtractTimeStrings UTILS.TimeBetween UTILS.PercentageChance UTILS.Clamp UTILS.ClampAngle UTILS.RemapValue UTILS.RandomPointInTriangle UTILS.AngleBetween UTILS.WriteJSON UTILS.ReadJSON UTILS.GetZoneProperties UTILS.RotatePointAroundPivot UTILS.UniqueName string.startswith string.endswith string.split string.contains table.contains_key table.remove_by_value table.remove_key table.index_of table.length table.slice table.count_value table.combine table.merge table.add table.shuffle table.find_key_value_pair --- Moose Development/Moose/Utilities/Utils.lua | 498 ++++++++++++++++++++ 1 file changed, 498 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 6e5e05145..c6c0ee65f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1345,6 +1345,11 @@ function UTILS.VecSubstract(a, b) return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} end +--- Substract is not a word, don't want to rename the original function because it's been around since forever +function UTILS.VecSubtract(a, b) + return UTILS.VecSubstract(a, b) +end + --- Calculate the difference between two 2D vectors by substracting the x,y components from each other. -- @param DCS#Vec2 a Vector in 2D with x, y components. -- @param DCS#Vec2 b Vector in 2D with x, y components. @@ -1353,6 +1358,11 @@ function UTILS.Vec2Substract(a, b) return {x=a.x-b.x, y=a.y-b.y} end +--- Substract is not a word, don't want to rename the original function because it's been around since forever +function UTILS.Vec2Subtract(a, b) + return UTILS.Vec2Substract(a, b) +end + --- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components. @@ -3101,3 +3111,491 @@ function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalitio circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text) end + +--- Get the current time in a "nice" format like 21:01:15 +-- @return #string Returns string with the current time +function UTILS.TimeNow() + return UTILS.SecondsToClock(timer.getAbsTime(), false, false) +end + + +--- Given 2 "nice" time string, returns the difference between the two in seconds +-- @param #string start_time Time string like "07:15:22" +-- @param #string end_time Time string like "08:11:27" +-- @return #number Seconds between start_time and end_time +function UTILS.TimeDifferenceInSeconds(start_time, end_time) + return UTILS.ClockToSeconds(end_time) - UTILS.ClockToSeconds(start_time) +end + +--- Given check if the current time is later than time_string. +-- @param #string start_time Time string like "07:15:22" +-- @return #boolean True if later, False if before +function UTILS.TimeLaterThan(time_string) + if timer.getAbsTime() > UTILS.ClockToSeconds(time_string) then + return true + end + return false +end + +--- Given check if the current time is before time_string. +-- @param #string start_time Time string like "07:15:22" +-- @return #boolean False if later, True if before +function UTILS.TimeBefore(time_string) + if timer.getAbsTime() < UTILS.ClockToSeconds(time_string) then + return true + end + return false +end + + +--- Combines two time strings to give you a new time. For example "15:16:32" and "02:06:24" would return "17:22:56" +-- @param #string time_string_01 Time string like "07:15:22" +-- @param #string time_string_02 Time string like "08:11:27" +-- @return #string Result of the two time string combined +function UTILS.CombineTimeStrings(time_string_01, time_string_02) + local hours1, minutes1, seconds1 = time_string_01:match("(%d+):(%d+):(%d+)") + local hours2, minutes2, seconds2 = time_string_02:match("(%d+):(%d+):(%d+)") + local total_seconds = tonumber(seconds1) + tonumber(seconds2) + tonumber(minutes1) * 60 + tonumber(minutes2) * 60 + tonumber(hours1) * 3600 + tonumber(hours2) * 3600 + + total_seconds = total_seconds % (24 * 3600) + if total_seconds < 0 then + total_seconds = total_seconds + 24 * 3600 + end + + local hours = math.floor(total_seconds / 3600) + total_seconds = total_seconds - hours * 3600 + local minutes = math.floor(total_seconds / 60) + local seconds = total_seconds % 60 + + return string.format("%02d:%02d:%02d", hours, minutes, seconds) +end + + +--- Subtracts two time string to give you a new time. For example "15:16:32" and "02:06:24" would return "13:10:08" +-- @param #string time_string_01 Time string like "07:15:22" +-- @param #string time_string_02 Time string like "08:11:27" +-- @return #string Result of the two time string subtracted +function UTILS.SubtractTimeStrings(time_string_01, time_string_02) + local hours1, minutes1, seconds1 = time_string_01:match("(%d+):(%d+):(%d+)") + local hours2, minutes2, seconds2 = time_string_02:match("(%d+):(%d+):(%d+)") + local total_seconds = tonumber(seconds1) - tonumber(seconds2) + tonumber(minutes1) * 60 - tonumber(minutes2) * 60 + tonumber(hours1) * 3600 - tonumber(hours2) * 3600 + + total_seconds = total_seconds % (24 * 3600) + if total_seconds < 0 then + total_seconds = total_seconds + 24 * 3600 + end + + local hours = math.floor(total_seconds / 3600) + total_seconds = total_seconds - hours * 3600 + local minutes = math.floor(total_seconds / 60) + local seconds = total_seconds % 60 + + return string.format("%02d:%02d:%02d", hours, minutes, seconds) +end + +--- Checks if the current time is in between start_time and end_time +-- @param #string time_string_01 Time string like "07:15:22" +-- @param #string time_string_02 Time string like "08:11:27" +-- @return #bool True if it is, False if it's not +function UTILS.TimeBetween(start_time, end_time) + return UTILS.TimeLaterThan(start_time) and UTILS.TimeBefore(end_time) +end + +--- Easy to read one line to roll the dice on something. 1% is very unlikely to happen, 99% is very likely to happen +-- @param #number chance (optional) Percentage chance you want something to happen. Defaults to a random number if not given +-- @return #bool True if the dice roll was within the given percentage chance of happening +function UTILS.PercentageChance(chance) + chance = chance or math.random(0, 100) + chance = UTILS.Clamp(chance, 0, 100) + local percentage = math.random(0, 100) + if percentage < chance then + return true + end + return false +end + +--- Easy to read one liner to clamp a value +-- @param #number value Input value +-- @param #number min Minimal value that should be respected +-- @param #number max Maximal value that should be respected +-- @return #number Clamped value +function UTILS.Clamp(value, min, max) + if value < min then value = min end + if value > max then value = max end + + return value +end + +--- Clamp an angle so that it's always between 0 and 360 while still being correct +-- @param #number value Input value +-- @return #number Clamped value +function UTILS.ClampAngle(value) + if value > 360 then return value - 360 end + if value < 0 then return value + 360 end + return value +end + +--- Remap an input to a new value in a given range. For example: +--- UTILS.RemapValue(20, 10, 30, 0, 200) would return 50 +--- 20 is 50% between 10 and 30 +--- 50% between 0 and 200 is 100 +-- @param #number value Input value +-- @param #number old_min Min value to remap from +-- @param #number old_max Max value to remap from +-- @param #number new_min Min value to remap to +-- @param #number new_max Max value to remap to +-- @return #number Remapped value +function UTILS.RemapValue(value, old_min, old_max, new_min, new_max) + new_min = new_min or 0 + new_max = new_max or 100 + + local old_range = old_max - old_min + local new_range = new_max - new_min + local percentage = (value - old_min) / old_range + return (new_range * percentage) + new_min +end + +--- Given a triangle made out of 3 vector 2s, return a vec2 that is a random number in this triangle +-- @param #Vec2 pt1 Min value to remap from +-- @param #Vec2 pt2 Max value to remap from +-- @param #Vec2 pt3 Max value to remap from +-- @return #Vec2 Random point in triangle +function UTILS.RandomPointInTriangle(pt1, pt2, pt3) + local pt = {math.random(), math.random()} + table.sort(pt) + local s = pt[1] + local t = pt[2] - pt[1] + local u = 1 - pt[2] + + return {x = s * pt1.x + t * pt2.x + u * pt3.x, + y = s * pt1.y + t * pt2.y + u * pt3.y} +end + +--- Checks if a given angle (heading) is between 2 other angles. Min and max have to be given in clockwise order For example: +--- UTILS.AngleBetween(350, 270, 15) would return True +--- UTILS.AngleBetween(22, 95, 20) would return False +-- @param #number angle Min value to remap from +-- @param #number min Max value to remap from +-- @param #number max Max value to remap from +-- @return #bool +function UTILS.AngleBetween(angle, min, max) + angle = (360 + (angle % 360)) % 360 + min = (360 + min % 360) % 360 + max = (360 + max % 360) % 360 + + if min < max then return min <= angle and angle <= max end + return min <= angle or angle <= max +end + +--- Easy to read one liner to write a JSON file. Everything in @data should be serializable +--- json.lua exists in the DCS install Scripts folder +-- @param #table data table to write +-- @param #string file_path File path +function UTILS.WriteJSON(data, file_path) + package.path = package.path .. ";.\\Scripts\\?.lua" + local JSON = require("json") + local pretty_json_text = JSON:encode_pretty(data) + local write_file = io.open(file_path, "w") + write_file:write(pretty_json_text) + write_file:close() +end + +--- Easy to read one liner to read a JSON file. +--- json.lua exists in the DCS install Scripts folder +-- @param #string file_path File path +-- @return #table +function UTILS.ReadJSON(file_path) + package.path = package.path .. ";.\\Scripts\\?.lua" + local JSON = require("json") + local read_file = io.open(file_path, "r") + local contents = read_file:read( "*a" ) + io.close(read_file) + return JSON:decode(contents) +end + +--- Get the properties names and values of properties set up on a Zone in the Mission Editor. +--- This doesn't work for any zones created in MOOSE +-- @param #string zone_name Name of the zone as set up in the Mission Editor +-- @return #table with all the properties on a zone +function UTILS.GetZoneProperties(zone_name) + local return_table = {} + for _, zone in pairs(env.mission.triggers.zones) do + if zone["name"] == zone_name then + if table.length(zone["properties"]) > 0 then + for _, property in pairs(zone["properties"]) do + return_table[property["key"]] = property["value"] + end + return return_table + else + BASE:I(string.format("%s doesn't have any properties", zone_name)) + return {} + end + end + end +end + +--- Rotates a point around another point with a given angle. Useful if you're loading in groups or +--- statics but you want to rotate them all as a collection. You can get the center point of everything +--- and then rotate all the positions of every object around this center point. +-- @param #Vec2 point Point that you want to rotate +-- @param #Vec2 pivot Pivot point of the rotation +-- @param #number angle How many degrees the point should be rotated +-- @return #Vec Rotated point +function UTILS.RotatePointAroundPivot(point, pivot, angle) + local radians = math.rad(angle) + + local x = point.x - pivot.x + local y = point.y - pivot.y + + local rotated_x = x * math.cos(radians) - y * math.sin(radians) + local rotatex_y = x * math.sin(radians) + y * math.cos(radians) + + local original_x = rotated_x + pivot.x + local original_y = rotatex_y + pivot.y + + return { x = original_x, y = original_y } +end + +--- Makes a string semi-unique by attaching a random number between 0 and 1 million to it +-- @param #string base String you want to unique-fy +-- @return #string Unique string +function UTILS.UniqueName(base) + base = base or "" + local ran = tostring(math.random(0, 1000000)) + + if base == "" then + return ran + end + return base .. "_" .. ran +end + +--- Check if a string starts with something +-- @param #string str String to check +-- @param #string value +-- @return #bool True if str starts with value +function string.startswith(str, value) + return string.sub(str,1,string.len(value)) == value +end + + +--- Check if a string ends with something +-- @param #string str String to check +-- @param #string value +-- @return #bool True if str ends with value +function string.endswith(str, value) + return value == "" or str:sub(-#value) == value +end + +--- Splits a string on a separator. For example: +--- string.split("hello_dcs_world", "-") would return {"hello", "dcs", "world"} +-- @param #string input String to split +-- @param #string separator What to split on +-- @return #table individual strings +function string.split(input, separator) + local parts = {} + for part in input:gmatch("[^" .. separator .. "]+") do + table.insert(parts, part) + end + return parts +end + + +--- Checks if a string contains a substring. Easier to remember for Python people :) +--- string.split("hello_dcs_world", "-") would return {"hello", "dcs", "world"} +-- @param #string str +-- @param #string value +-- @return #bool True if str contains value +function string.contains(str, value) + return string.match(str, value) +end + +--- Given tbl is a indexed table ({"hello", "dcs", "world"}), checks if element exists in the table. +--- The table can be made up out of complex tables or values as well +-- @param #table tbl +-- @param #string element +-- @return #bool True if tbl contains element +function table.contains(tbl, element) + if element == nil or tbl == nil then return false end + + local index = 1 + while tbl[index] do + if tbl[index] == element then + return true + end + index = index + 1 + end + return false +end + +--- Checks if a table contains a specific key. +-- @param #table tbl Table to check +-- @param #string key Key to look for +-- @return #bool True if tbl contains key +function table.contains_key(tbl, key) + if tbl[key] ~= nil then return true else return false end +end + +--- Inserts a unique element into a table. +-- @param #table tbl Table to insert into +-- @param #string element Element to insert +function table.insert_unique(tbl, element) + if element == nil or tbl == nil then return end + + if not table.contains(tbl, element) then + table.insert(tbl, element) + end +end + +--- Removes an element from a table by its value. +-- @param #table tbl Table to remove from +-- @param #string element Element to remove +function table.remove_by_value(tbl, element) + local indices_to_remove = {} + local index = 1 + for _, value in pairs(tbl) do + if value == element then + table.insert(indices_to_remove, index) + end + index = index + 1 + end + + for _, idx in pairs(indices_to_remove) do + table.remove(tbl, idx) + end +end + +--- Removes an element from a table by its key. +-- @param #table table Table to remove from +-- @param #string key Key of the element to remove +-- @return #string Removed element +function table.remove_key(table, key) + local element = table[key] + table[key] = nil + return element +end + +--- Finds the index of an element in a table. +-- @param #table table Table to search +-- @param #string element Element to find +-- @return #int Index of the element, or nil if not found +function table.index_of(table, element) + for i, v in ipairs(table) do + if v == element then + return i + end + end + return nil +end + +--- Counts the number of elements in a table. +-- @param #table T Table to count +-- @return #int Number of elements in the table +function table.length(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +--- Slices a table between two indices, much like Python's my_list[2:-1] +-- @param #table tbl Table to slice +-- @param #int first Starting index +-- @param #int last Ending index +-- @return #table Sliced table +function table.slice(tbl, first, last) + local sliced = {} + local start = first or 1 + local stop = last or table.length(tbl) + local count = 1 + + for key, value in pairs(tbl) do + if count >= start and count <= stop then + sliced[key] = value + end + count = count + 1 + end + + return sliced +end + +--- Counts the number of occurrences of a value in a table. +-- @param #table tbl Table to search +-- @param #string value Value to count +-- @return #int Number of occurrences of the value +function table.count_value(tbl, value) + local count = 0 + for _, item in pairs(tbl) do + if item == value then count = count + 1 end + end + return count +end + +--- Add 2 table together, t2 gets added to t1 +-- @param #table t1 First table +-- @param #table t2 Second table +-- @return #table Combined table +function table.combine(t1, t2) + if t1 == nil and t2 == nil then + BASE:E("Both tables were empty!") + end + + if t1 == nil then return t2 end + if t2 == nil then return t1 end + for i=1,#t2 do + t1[#t1+1] = t2[i] + end + return t1 +end + +--- Merges two tables into one. If a key exists in both t1 and t2, the value of t1 with be overwritten by the value of t2 +-- @param #table t1 First table +-- @param #table t2 Second table +-- @return #table Merged table +function table.merge(t1, t2) + for k, v in pairs(t2) do + if (type(v) == "table") and (type(t1[k] or false) == "table") then + table.merge(t1[k], t2[k]) + else + t1[k] = v + end + end + return t1 +end + +--- Adds an item to the end of a table. +-- @param #table tbl Table to add to +-- @param #string item Item to add +function table.add(tbl, item) + tbl[#tbl + 1] = item +end + +--- Shuffles the elements of a table. +-- @param #table tbl Table to shuffle +-- @return #table Shuffled table +function table.shuffle(tbl) + local new_table = {} + for _, value in ipairs(tbl) do + local pos = math.random(1, #new_table +1) + table.insert(new_table, pos, value) + end + return new_table +end + +--- Finds a key-value pair in a table. +-- @param #table tbl Table to search +-- @param #string key Key to find +-- @param #string value Value to find +-- @return #table Table containing the key-value pair, or nil if not found +function table.find_key_value_pair(tbl, key, value) + for k, v in pairs(tbl) do + if type(v) == "table" then + local result = table.find_key_value_pair(v, key, value) + if result ~= nil then + return result + end + elseif k == key and v == value then + return tbl + end + end + return nil +end + From fdcda6e5f3976b90001ff9fc14379ca921234460 Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Thu, 23 Nov 2023 15:22:14 +0100 Subject: [PATCH 12/17] typos --- Moose Development/Moose/Utilities/Utils.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index c6c0ee65f..28b608d51 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -3127,7 +3127,7 @@ function UTILS.TimeDifferenceInSeconds(start_time, end_time) return UTILS.ClockToSeconds(end_time) - UTILS.ClockToSeconds(start_time) end ---- Given check if the current time is later than time_string. +--- Check if the current time is later than time_string. -- @param #string start_time Time string like "07:15:22" -- @return #boolean True if later, False if before function UTILS.TimeLaterThan(time_string) @@ -3137,7 +3137,7 @@ function UTILS.TimeLaterThan(time_string) return false end ---- Given check if the current time is before time_string. +--- Check if the current time is before time_string. -- @param #string start_time Time string like "07:15:22" -- @return #boolean False if later, True if before function UTILS.TimeBefore(time_string) @@ -3236,7 +3236,7 @@ function UTILS.ClampAngle(value) end --- Remap an input to a new value in a given range. For example: ---- UTILS.RemapValue(20, 10, 30, 0, 200) would return 50 +--- UTILS.RemapValue(20, 10, 30, 0, 200) would return 100 --- 20 is 50% between 10 and 30 --- 50% between 0 and 200 is 100 -- @param #number value Input value From 3e40d72e2593bec3b031f2f5ae4043b73f2b0100 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 23 Nov 2023 17:00:54 +0100 Subject: [PATCH 13/17] #ATC_GROUND --- .../Moose/Functional/ATC_Ground.lua | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 5e89ff144..67a361bb4 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -26,7 +26,8 @@ -- @module Functional.ATC_Ground -- @image Air_Traffic_Control_Ground_Operations.JPG ---- @type ATC_GROUND +--- +-- @type ATC_GROUND -- @field Core.Set#SET_CLIENT SetClient -- @extends Core.Base#BASE @@ -39,7 +40,8 @@ ATC_GROUND = { AirbaseNames = nil, } ---- @type ATC_GROUND.AirbaseNames +--- +-- @type ATC_GROUND.AirbaseNames -- @list <#string> @@ -82,7 +84,7 @@ function ATC_GROUND:New( Airbases, AirbaseList ) end self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client function( Client ) Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0) @@ -246,11 +248,11 @@ function ATC_GROUND:SetMaximumKickSpeedMiph( MaximumKickSpeedMiph, Airbase ) return self end ---- @param #ATC_GROUND self +-- @param #ATC_GROUND self function ATC_GROUND:_AirbaseMonitor() self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client function( Client ) if Client:IsAlive() then @@ -331,7 +333,7 @@ function ATC_GROUND:_AirbaseMonitor() Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) else MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client Client:Destroy() Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0 ) @@ -363,7 +365,7 @@ function ATC_GROUND:_AirbaseMonitor() Client:SetState( self, "OffRunwayWarnings", OffRunwayWarnings + 1 ) else MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client Client:Destroy() Client:SetState( self, "IsOffRunway", false ) Client:SetState( self, "OffRunwayWarnings", 0 ) @@ -424,8 +426,15 @@ ATC_GROUND_UNIVERSAL = { --- Creates a new ATC\_GROUND\_UNIVERSAL object. This works on any map. -- @param #ATC_GROUND_UNIVERSAL self --- @param AirbaseList (Optional) A table of Airbase Names. +-- @param AirbaseList A table of Airbase Names. Leave empty to cover **all** airbases of the map. -- @return #ATC_GROUND_UNIVERSAL self +-- @usage +-- -- define monitoring for one airbase +-- local atc=ATC_GROUND_UNIVERSAL:New({AIRBASE.Syria.Gecitkale}) +-- -- set kick speed +-- atc:SetKickSpeed(UTILS.KnotsToMps(20)) +-- -- start monitoring evey 10 secs +-- atc:Start(10) function ATC_GROUND_UNIVERSAL:New(AirbaseList) -- Inherits from BASE @@ -440,6 +449,13 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self.AirbaseList = AirbaseList + if not self.AirbaseList then + self.AirbaseList = {} + for _name,_ in pairs(_DATABASE.AIRBASES) do + self.AirbaseList[_name]=_name + end + end + self.SetClient = SET_CLIENT:New():FilterCategories( "plane" ):FilterStart() @@ -460,8 +476,9 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self.Airbases[AirbaseName].Monitor = true end + self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client function( Client ) Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0) @@ -681,7 +698,7 @@ end function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client function( Client ) if Client:IsAlive() then @@ -766,7 +783,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) else MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client Client:Destroy() Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0 ) @@ -798,7 +815,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() Client:SetState( self, "OffRunwayWarnings", OffRunwayWarnings + 1 ) else MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - --- @param Wrapper.Client#CLIENT Client + -- @param Wrapper.Client#CLIENT Client Client:Destroy() Client:SetState( self, "IsOffRunway", false ) Client:SetState( self, "OffRunwayWarnings", 0 ) @@ -838,7 +855,7 @@ end --- Start SCHEDULER for ATC_GROUND_UNIVERSAL object. -- @param #ATC_GROUND_UNIVERSAL self --- @param RepeatScanSeconds Time in second for defining occurency of alerts. +-- @param RepeatScanSeconds Time in second for defining schedule of alerts. -- @return #ATC_GROUND_UNIVERSAL self function ATC_GROUND_UNIVERSAL:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 @@ -846,7 +863,8 @@ function ATC_GROUND_UNIVERSAL:Start( RepeatScanSeconds ) return self end ---- @type ATC_GROUND_CAUCASUS +--- +-- @type ATC_GROUND_CAUCASUS -- @extends #ATC_GROUND --- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND_UNIVERSAL} @@ -985,8 +1003,8 @@ function ATC_GROUND_CAUCASUS:Start( RepeatScanSeconds ) end - ---- @type ATC_GROUND_NEVADA +--- +-- @type ATC_GROUND_NEVADA -- @extends #ATC_GROUND @@ -1123,8 +1141,8 @@ function ATC_GROUND_NEVADA:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) end - ---- @type ATC_GROUND_NORMANDY +--- +-- @type ATC_GROUND_NORMANDY -- @extends #ATC_GROUND @@ -1280,7 +1298,8 @@ function ATC_GROUND_NORMANDY:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) end ---- @type ATC_GROUND_PERSIANGULF +--- +-- @type ATC_GROUND_PERSIANGULF -- @extends #ATC_GROUND @@ -1423,7 +1442,7 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) end - --- @type ATC_GROUND_MARIANAISLANDS + -- @type ATC_GROUND_MARIANAISLANDS -- @extends #ATC_GROUND @@ -1517,7 +1536,7 @@ end -- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. -- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. -- ----- @field #ATC_GROUND_MARIANAISLANDS +-- @field #ATC_GROUND_MARIANAISLANDS ATC_GROUND_MARIANAISLANDS = { ClassName = "ATC_GROUND_MARIANAISLANDS", } @@ -1529,7 +1548,7 @@ ATC_GROUND_MARIANAISLANDS = { function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames ) -- Inherits from BASE - local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( self.Airbases, AirbaseNames ) ) + local self = BASE:Inherit( self, ATC_GROUND_UNIVERSAL:New( AirbaseNames ) ) self:SetKickSpeedKmph( 50 ) self:SetMaximumKickSpeedKmph( 150 ) From bbf793febe3663ed59dbbf0016be813fc907125a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 23 Nov 2023 17:01:33 +0100 Subject: [PATCH 14/17] #CLIENTMENU --- Moose Development/Moose/Core/ClientMenu.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index 7d4fecde6..525b3533d 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -396,7 +396,7 @@ end CLIENTMENUMANAGER = { ClassName = "CLIENTMENUMANAGER", lid = "", - version = "0.1.3", + version = "0.1.4", name = nil, clientset = nil, menutree = {}, @@ -439,18 +439,18 @@ function CLIENTMENUMANAGER:_EventHandler(EventData) --self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName)) if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName)) - local Client = _DATABASE:FindClient( EventData.IniPlayerName ) + local Client = _DATABASE:FindClient( EventData.IniUnitName ) if Client then self:ResetMenu(Client) end elseif (EventData.id == EVENTS.PlayerEnterAircraft) and EventData.IniCoalition == self.Coalition then if EventData.IniPlayerName and EventData.IniGroup then - if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniPlayerName ))) then + if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) return self end --self:I(self.lid.."Join event for player: "..EventData.IniPlayerName) - local player = _DATABASE:FindClient( EventData.IniPlayerName ) + local player = _DATABASE:FindClient( EventData.IniUnitName ) self:Propagate(player) end elseif EventData.id == EVENTS.PlayerEnterUnit then @@ -668,7 +668,7 @@ function CLIENTMENUMANAGER:Propagate(Client) for _,_client in pairs(Set) do local client = _client -- Wrapper.Client#CLIENT if client and client:IsAlive() then - local playername = client:GetPlayerName() + local playername = client:GetPlayerName() or "none" if not self.playertree[playername] then self.playertree[playername] = {} end From 343bf05c2cd7bfab316a4f764b39a3b5f2f57caa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 23 Nov 2023 18:14:25 +0100 Subject: [PATCH 15/17] SPAWN - Set correct unit ID in the group callsign --- Moose Development/Moose/Core/Spawn.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 8e62efe4b..c1a845c62 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2783,7 +2783,7 @@ end -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- --- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() +-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() -- -- -- Spawn at the zone center position at the height specified in the ME of the group template! -- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) @@ -3283,6 +3283,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers local CallsignLen = CallsignName:len() + SpawnTemplate.units[UnitID].callsign[2] = UnitID SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex From aa7f26ac79019826731a7156281c2d5fac249d57 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 23 Nov 2023 18:45:36 +0100 Subject: [PATCH 16/17] ATC_GROUND fix for scheduler --- .../Moose/Functional/ATC_Ground.lua | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 67a361bb4..fc65fcce6 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -20,6 +20,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 +-- Last Update Nov 2023 -- -- === -- @@ -53,7 +54,7 @@ function ATC_GROUND:New( Airbases, AirbaseList ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND - self:E( { self.ClassName, Airbases } ) + self:T( { self.ClassName, Airbases } ) self.Airbases = Airbases self.AirbaseList = AirbaseList @@ -260,7 +261,7 @@ function ATC_GROUND:_AirbaseMonitor() local IsOnGround = Client:InAir() == false for AirbaseID, AirbaseMeta in pairs( self.Airbases ) do - self:E( AirbaseID, AirbaseMeta.KickSpeed ) + self:T( AirbaseID, AirbaseMeta.KickSpeed ) if AirbaseMeta.Monitor == true and Client:IsInZone( AirbaseMeta.ZoneBoundary ) then @@ -273,7 +274,7 @@ function ATC_GROUND:_AirbaseMonitor() if IsOnGround then local Taxi = Client:GetState( self, "Taxi" ) - self:E( Taxi ) + self:T( Taxi ) if Taxi == false then local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed ) Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " .. @@ -439,7 +440,7 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND - self:E( { self.ClassName } ) + self:T( { self.ClassName } ) self.Airbases = {} @@ -696,9 +697,9 @@ end -- @param #ATC_GROUND_UNIVERSAL self -- @return #ATC_GROUND_UNIVERSAL self function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() - + self:I("_AirbaseMonitor") self.SetClient:ForEachClient( - -- @param Wrapper.Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) if Client:IsAlive() then @@ -706,7 +707,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() local IsOnGround = Client:InAir() == false for AirbaseID, AirbaseMeta in pairs( self.Airbases ) do - self:E( AirbaseID, AirbaseMeta.KickSpeed ) + self:T( AirbaseID, AirbaseMeta.KickSpeed ) if AirbaseMeta.Monitor == true and Client:IsInZone( AirbaseMeta.ZoneBoundary ) then @@ -723,7 +724,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() if IsOnGround then local Taxi = Client:GetState( self, "Taxi" ) - self:E( Taxi ) + self:T( Taxi ) if Taxi == false then local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed ) Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " .. @@ -859,7 +860,7 @@ end -- @return #ATC_GROUND_UNIVERSAL self function ATC_GROUND_UNIVERSAL:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) return self end @@ -999,7 +1000,7 @@ end -- @return nothing function ATC_GROUND_CAUCASUS:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end @@ -1138,7 +1139,7 @@ end -- @return nothing function ATC_GROUND_NEVADA:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end --- @@ -1295,7 +1296,7 @@ end -- @return nothing function ATC_GROUND_NORMANDY:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end --- @@ -1438,7 +1439,7 @@ end -- @return nothing function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end @@ -1562,5 +1563,5 @@ end -- @return nothing function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds ) RepeatScanSeconds = RepeatScanSeconds or 0.05 - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end From 6f3133d48cc2e3bb8790d72e7ff285e96bce06f9 Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Thu, 23 Nov 2023 21:50:08 +0100 Subject: [PATCH 17/17] bugfix for impactHeading clamping GetImpactHeading and GetReleaseHeading --- Moose Development/Moose/Wrapper/Weapon.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 5ba880123..762159751 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -566,7 +566,7 @@ end -- @return #number Heading function WEAPON:GetReleaseHeading(AccountForMagneticInclination) AccountForMagneticInclination = AccountForMagneticInclination or true - if AccountForMagneticInclination then return self.releaseHeading - UTILS.GetMagneticDeclination() else return self.releaseHeading end + if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading - UTILS.GetMagneticDeclination()) else return UTILS.ClampAngle(self.releaseHeading) end end --- Get the altitude above sea level at which the weapon was released @@ -603,7 +603,7 @@ end -- @return #number Heading function WEAPON:GetImpactHeading(AccountForMagneticInclination) AccountForMagneticInclination = AccountForMagneticInclination or true - if AccountForMagneticInclination then return self.impactHeading - UTILS.GetMagneticDeclination() else return self.impactHeading end + if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading - UTILS.GetMagneticDeclination()) else return self.impactHeading end end --- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS... @@ -766,7 +766,10 @@ function WEAPON:_TrackWeapon(time) -- Update coordinate. self.coordinate:UpdateFromVec3(self.vec3) - + + -- Safe the last velocity of the weapon. This is needed to get the impact heading + self.last_velocity = self.weapon:getVelocity() + -- Keep on tracking by returning the next time below. self.tracking=true @@ -836,8 +839,8 @@ function WEAPON:_TrackWeapon(time) -- Safe impact coordinate. self.impactCoord=COORDINATE:NewFromVec3(self.vec3) - -- Safe impact heading - self.impactHeading = UTILS.VecHdg(self:GetVelocityVec3()) + -- Safe impact heading, using last_velocity because self:GetVelocityVec3() is no longer possible + self.impactHeading = UTILS.VecHdg(self.last_velocity) -- Mark impact point on F10 map. if self.impactMark then