mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
commit
bfaf88f017
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -20,13 +20,15 @@
|
||||
-- ### Author: FlightControl - Framework Design & Programming
|
||||
-- ### Refactoring to use the Runway auto-detection: Applevangelist
|
||||
-- @date August 2022
|
||||
-- Last Update Nov 2023
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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 +41,8 @@ ATC_GROUND = {
|
||||
AirbaseNames = nil,
|
||||
}
|
||||
|
||||
--- @type ATC_GROUND.AirbaseNames
|
||||
---
|
||||
-- @type ATC_GROUND.AirbaseNames
|
||||
-- @list <#string>
|
||||
|
||||
|
||||
@ -51,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
|
||||
@ -82,7 +85,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 +249,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
|
||||
@ -258,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
|
||||
|
||||
@ -271,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 " ..
|
||||
@ -331,7 +334,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 +366,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,13 +427,20 @@ 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
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #ATC_GROUND
|
||||
self:E( { self.ClassName } )
|
||||
self:T( { self.ClassName } )
|
||||
|
||||
self.Airbases = {}
|
||||
|
||||
@ -440,6 +450,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 +477,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)
|
||||
@ -679,7 +697,7 @@ 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
|
||||
function( Client )
|
||||
@ -689,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
|
||||
|
||||
@ -706,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 " ..
|
||||
@ -766,7 +784,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 +816,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,15 +856,16 @@ 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
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
return self
|
||||
end
|
||||
|
||||
--- @type ATC_GROUND_CAUCASUS
|
||||
---
|
||||
-- @type ATC_GROUND_CAUCASUS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
--- # ATC\_GROUND\_CAUCASUS, extends @{#ATC_GROUND_UNIVERSAL}
|
||||
@ -981,12 +1000,12 @@ 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
|
||||
|
||||
|
||||
|
||||
--- @type ATC_GROUND_NEVADA
|
||||
---
|
||||
-- @type ATC_GROUND_NEVADA
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1120,11 +1139,11 @@ 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
|
||||
|
||||
|
||||
--- @type ATC_GROUND_NORMANDY
|
||||
---
|
||||
-- @type ATC_GROUND_NORMANDY
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1277,10 +1296,11 @@ 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
|
||||
|
||||
--- @type ATC_GROUND_PERSIANGULF
|
||||
---
|
||||
-- @type ATC_GROUND_PERSIANGULF
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1419,11 +1439,11 @@ 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
|
||||
|
||||
|
||||
--- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
@ -1517,7 +1537,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 +1549,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 )
|
||||
@ -1543,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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -377,9 +383,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 +450,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
|
||||
@ -674,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
|
||||
@ -688,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
|
||||
@ -708,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
|
||||
@ -1114,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
|
||||
|
||||
@ -1128,7 +1149,6 @@ end
|
||||
-- @param #MSRS self
|
||||
-- @param #string Path Path to config file, defaults to "C:\Users\<yourname>\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,
|
||||
@ -1138,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
|
||||
@ -1166,14 +1187,18 @@ 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.
|
||||
--
|
||||
-- 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:
|
||||
--
|
||||
@ -1190,46 +1215,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 = "<API Google 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
|
||||
@ -1242,6 +1238,9 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
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
|
||||
@ -1266,6 +1265,9 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
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
|
||||
MSRS.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
|
||||
@ -1280,9 +1282,10 @@ function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
|
||||
MSRS.ConfigLoaded = true
|
||||
end
|
||||
end
|
||||
env.info("MSRS - Sucessfully loaded default configuration from disk!",false)
|
||||
else
|
||||
env.info("MSRS - Cannot load 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)
|
||||
return false
|
||||
end
|
||||
|
||||
@ -1995,6 +1998,7 @@ function MSRSQUEUE:_CheckRadioQueue(delay)
|
||||
|
||||
end
|
||||
|
||||
MSRS.LoadConfigFile()
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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.
|
||||
@ -2391,7 +2401,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
|
||||
|
||||
@ -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
|
||||
|
||||
--- 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
|
||||
|
||||
--- 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 100
|
||||
--- 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
|
||||
|
||||
|
||||
@ -224,6 +224,14 @@ 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 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
|
||||
-- @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 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...
|
||||
-- @param #WEAPON self
|
||||
-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`.
|
||||
@ -713,6 +767,9 @@ 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
|
||||
|
||||
@ -782,6 +839,9 @@ function WEAPON:_TrackWeapon(time)
|
||||
-- Safe impact coordinate.
|
||||
self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
|
||||
|
||||
-- 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
|
||||
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName))
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user