diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 764f33602..005850ec1 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -480,21 +480,24 @@ _MESSAGESRS = {} -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) - local path = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone" - _MESSAGESRS.MSRS = MSRS:New(path,Frequency,Modulation,Volume) + _MESSAGESRS.MSRS = MSRS:New(PathToSRS,Frequency,Modulation,Volume) _MESSAGESRS.MSRS:SetCoalition(Coalition) _MESSAGESRS.MSRS:SetCoordinate(Coordinate) _MESSAGESRS.MSRS:SetCulture(Culture) - _MESSAGESRS.MSRS:SetFrequencies(Frequency) + _MESSAGESRS.Culture = Culture + --_MESSAGESRS.MSRS:SetFrequencies(Frequency) _MESSAGESRS.MSRS:SetGender(Gender) + _MESSAGESRS.Gender = Gender _MESSAGESRS.MSRS:SetGoogle(PathToCredentials) _MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE") - _MESSAGESRS.MSRS:SetModulations(Modulation) - _MESSAGESRS.MSRS:SetPath(PathToSRS) + --_MESSAGESRS.MSRS:SetModulations(Modulation) + --_MESSAGESRS.MSRS:SetPath(PathToSRS) _MESSAGESRS.MSRS:SetPort(Port) - _MESSAGESRS.MSRS:SetVolume(Volume) + -- _MESSAGESRS.MSRS:SetVolume(Volume) _MESSAGESRS.MSRS:SetVoice(Voice) + _MESSAGESRS.Voice = Voice _MESSAGESRS.SRSQ = MSRSQUEUE:New(Label or "MESSAGE") + env.info(_MESSAGESRS.MSRS.provider,false) end --- Sends a message via SRS. @@ -517,26 +520,8 @@ end -- function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volume,coordinate) if _MESSAGESRS.SRSQ then - _MESSAGESRS.MSRS:SetLabel(self.MessageCategory or _MESSAGESRS.MSRS.Label or "MESSAGE") - if gender then - _MESSAGESRS.MSRS:SetGender(gender) - end - if coalition then - _MESSAGESRS.MSRS:SetCoalition(coalition) - end - if culture then - _MESSAGESRS.MSRS:SetCulture(culture) - end - if volume then - _MESSAGESRS.MSRS:SetVolume(volume) - end - if coordinate then - _MESSAGESRS.MSRS:SetCoordinate(coordinate) - end - if voice then - _MESSAGESRS.MSRS:SetVoice(voice) - end - _MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,nil,1,nil,nil,nil,frequency,modulation) + _MESSAGESRS.MSRS:SetVoice(voice or _MESSAGESRS.Voice) + _MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,nil,nil,nil,nil,nil,frequency,modulation,gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,voice or _MESSAGESRS.Voice,volume,self.MessageCategory) end return self end @@ -606,4 +591,3 @@ function MESSAGE:ToSRSAll(frequency,modulation,gender,culture,voice,volume,coord self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.NEUTRAL,volume,coordinate) return self end - diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 8f110b07f..94bd002fc 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1524,7 +1524,7 @@ end -- @param #string GoogleKey Path to Google JSON-Key. -- @return #ATIS self function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port, GoogleKey) - if PathToSRS then + if PathToSRS or MSRS.path then self.useSRS=true self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) self.msrs:SetGender(Gender) @@ -1555,7 +1555,7 @@ end --- Get the coalition of the associated airbase. -- @param #ATIS self --- @return #number Coalition of the associcated airbase. +-- @return #number Coalition of the associated airbase. function ATIS:GetCoalition() local coal = self.airbase and self.airbase:GetCoalition() or nil return coal diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 28b871633..13550da54 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -41,12 +41,15 @@ -- @field #number volume Volume between 0 (min) and 1 (max). Default 1. -- @field #string culture Culture. Default "en-GB". -- @field #string gender Gender. Default "female". --- @field #string voice Specifc voce. +-- @field #string voice Specific voce. -- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. -- @field #string path Path to the SRS exe. This includes the final slash "/". -- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". -- @field #string Label Label showing up on the SRS radio overlay. Default is "ROBOT". No spaces allowed. -- @field #table AltBackend Table containing functions and variables to enable an alternate backend to transmit to SRS. +-- @field #string ConfigFileName Name of the standard config file +-- @field #string ConfigFilePath Path to the standard config file +-- @field #boolean ConfigLoaded -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -185,11 +188,14 @@ MSRS = { coordinate = nil, Label = "ROBOT", AltBackend = nil, + ConfigFileName = "Moose_MSRS.lua", + ConfigFilePath = "Config\\", + ConfigLoaded = false, } --- MSRS class version. -- @field #string version -MSRS.version="0.1.3" +MSRS.version="0.1.4" --- Voices -- @type MSRS.Voices @@ -292,6 +298,37 @@ MSRS.Voices = { }, } +--- +-- @type MSRS.ProviderOptions +-- @field #string key +-- @field #string secret +-- @field #string region +-- @field #string defaultVoice +-- @field #string voice + +--- GRPC options +-- @type MSRS.GRPCOptions +-- @field #string plaintext +-- @field #string srsClientName +-- @field #table position +-- @field #string coalition +-- @field #MSRS.ProviderOptions gcloud +-- @field #MSRS.ProviderOptions win +-- @field #MSRS.ProviderOptions azure +-- @field #MSRS.ProviderOptions aws +-- @field #string DefaultProvider + +MSRS.GRPCOptions = {} -- #MSRS.GRPCOptions +MSRS.GRPCOptions.gcloud = {} -- #MSRS.ProviderOptions +MSRS.GRPCOptions.win = {} -- #MSRS.ProviderOptions +MSRS.GRPCOptions.azure = {} -- #MSRS.ProviderOptions +MSRS.GRPCOptions.aws = {} -- #MSRS.ProviderOptions + +MSRS.GRPCOptions.win.defaultVoice = "Hedda" +MSRS.GRPCOptions.win.voice = "Hedda" + +MSRS.GRPCOptions.DefaultProvider = "win" + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -299,6 +336,8 @@ MSRS.Voices = { -- TODO: Add functions to remove freqs and modulations. -- DONE: Add coordinate. -- DONE: Add google. +-- DONE: Add gRPC google options +-- DONE: Add loading default config file ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -328,25 +367,49 @@ function MSRS:New(PathToSRS, Frequency, Modulation, Volume, AltBackend) -- Add parameters to vars so alternate backends can use them if applicable Backend.Vars = Backend.Vars or {} - Backend.Vars.PathToSRS = UTILS.DeepCopy(PathToSRS) -- DeepCopy probably unecessary + Backend.Vars.PathToSRS = PathToSRS Backend.Vars.Frequency = UTILS.DeepCopy(Frequency) Backend.Vars.Modulation = UTILS.DeepCopy(Modulation) - Backend.Vars.Volume = UTILS.DeepCopy(Volume) -- DeepCopy probably unecessary + Backend.Vars.Volume = Volume Backend.Functions = Backend.Functions or {} return self:_NewAltBackend(Backend) end - -- If no AltBackend table, the proceed with default initialization - self:SetPath(PathToSRS) - self:SetPort() - self:SetFrequencies(Frequency) - self:SetModulations(Modulation) - self:SetGender() - self:SetCoalition() - self:SetLabel() - self:SetVolume() + local success = self:LoadConfigFile(nil,nil,self.ConfigLoaded) + + if (not success) and (not self.ConfigLoaded) then + + -- If no AltBackend table, the proceed with default initialisation + self:SetPath(PathToSRS) + self:SetPort() + self:SetFrequencies(Frequency) + self:SetModulations(Modulation) + self:SetGender() + self:SetCoalition() + self:SetLabel() + self:SetVolume(Volume) + + else + + -- there might be some overwrites from :New() + + if PathToSRS then + self:SetPath(PathToSRS) + end + + if Frequency then + self:SetFrequencies(Frequency) + self:SetModulations(Modulation) + end + + if Volume then + self:SetVolume(Volume) + end + + end + self.lid = string.format("%s-%s | ", self.name, self.version) if not io or not os then @@ -366,24 +429,25 @@ end -- @return #MSRS self function MSRS:SetPath(Path) - if Path==nil then + if Path==nil and not self.path then self:E("ERROR: No path to SRS directory specified!") return nil end - -- Set path. - self.path=Path - - -- Remove (back)slashes. - local n=1 ; local nmax=1000 - while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do - self.path=self.path:sub(1,#self.path-1) - n=n+1 + if Path then + -- Set path. + self.path=Path + + -- Remove (back)slashes. + local n=1 ; local nmax=1000 + while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do + self.path=self.path:sub(1,#self.path-1) + n=n+1 + end + + -- Debug output. + self:I(string.format("SRS path=%s", self:GetPath())) end - - -- Debug output. - self:T(string.format("SRS path=%s", self:GetPath())) - return self end @@ -579,6 +643,10 @@ function MSRS:SetVoice(Voice) self.voice=Voice + --local defaultprovider = self.provider or self.GRPCOptions.DefaultProvider or MSRS.GRPCOptions.DefaultProvider or "win" + + --self.GRPCOptions[defaultprovider].voice = Voice + return self end @@ -589,6 +657,8 @@ end function MSRS:SetDefaultVoice(Voice) self.defaultVoice=Voice + local provider = self.provider or self.GRPCOptions.DefaultProvider or MSRS.GRPCOptions.DefaultProvider or "win" + self.GRPCOptions[provider].defaultVoice = Voice return self end @@ -606,13 +676,20 @@ end --- Use google text-to-speech. -- @param #MSRS self --- @param #string PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". +-- @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 function MSRS:SetGoogle(PathToCredentials) - - self.google=PathToCredentials - self.APIKey=PathToCredentials - self.provider = "gcloud" + + if PathToCredentials then + + self.google=PathToCredentials + self.APIKey=PathToCredentials + self.provider = "gcloud" + + self.GRPCOptions.DefaultProvider = "gcloud" + self.GRPCOptions.gcloud.key = PathToCredentials + + end return self end @@ -622,10 +699,12 @@ end -- @param #string APIKey API Key, usually a string of length 40 with characters and numbers. -- @return #MSRS self function MSRS:SetGoogleAPIKey(APIKey) - - self.APIKey=APIKey - self.provider = "gcloud" - + if APIKey then + self.APIKey=APIKey + self.provider = "gcloud" + self.GRPCOptions.DefaultProvider = "gcloud" + self.GRPCOptions.gcloud.key = APIKey + end return self end @@ -1041,6 +1120,171 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp return command end +--- Get central SRS configuration to be able to play tts over SRS radio using the `DCS-SR-ExternalAudio.exe`. +-- @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, +-- and also make config easier to use in the code. +-- 1) Create a config file named "Moose_MSRS.lua" at this location "C:\Users\\Saved Games\DCS\Config" (or wherever your Saved Games folder resides). +-- 2) The file needs the following structure: +-- +-- -- 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", +-- }, +-- } +-- } +-- +-- 3) Load the config into the MSRS raw class before you do anything else: +-- +-- MSRS.LoadConfigFile() -- 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) +-- +-- 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: +-- +-- -- Needed once only +-- MESSAGE.SetMSRS(MSRS.path,nil,MSRS.google,243,radio.modulation.AM,nil,nil, +-- MSRS.Voices.Google.Standard.de_DE_Standard_B,coalition.side.BLUE) +-- +-- -- later on in your code +-- +-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS(243,radio.modulation.AM,nil,nil,MSRS.Voices.Google.Standard.fr_FR_Standard_C) +-- +-- -- Create new ATIS as usual +-- atis=ATIS:New(AIRBASE.Caucasus.Batumi, 123, radio.modulation.AM) +-- atis:SetSRS(nil,nil,nil,MSRS.Voices.Google.Standard.en_US_Standard_H) +-- --Start ATIS +-- atis:Start() +function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded) + + local path = Path or lfs.writedir()..MSRS.ConfigFilePath + local file = Filename or MSRS.ConfigFileName or "Moose_MSRS.lua" + + if UTILS.CheckFileExists(path,file) and not 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 + self.frequencies = MSRS_Config.Frequency or {127,243} + self.modulations = MSRS_Config.Modulation or {0,0} + self.coalition = MSRS_Config.Coalition or 0 + if MSRS_Config.Coordinate then + self.coordinate = COORDINATE:New( MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3]) + end + self.culture = MSRS_Config.Culture or "en-GB" + self.gender = MSRS_Config.Gender or "male" + self.google = MSRS_Config.Google + self.Label = MSRS_Config.Label or "MSRS" + self.voice = MSRS_Config.Voice or MSRS.Voices.Microsoft.Hazel + if MSRS_Config.GRPC then + self.provider = MSRS_Config.GRPC.DefaultProvider + if MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider] then + self.APIKey = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].key + self.defaultVoice = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].defaultVoice + self.region = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].secret + self.secret = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].region + end + end + self.ConfigLoaded = true + else + MSRS.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + MSRS.port = MSRS_Config.Port or 5002 + MSRS.frequencies = MSRS_Config.Frequency or {127,243} + MSRS.modulations = MSRS_Config.Modulation or {0,0} + MSRS.coalition = MSRS_Config.Coalition or 0 + if MSRS_Config.Coordinate then + MSRS.coordinate = COORDINATE:New( MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3]) + end + MSRS.culture = MSRS_Config.Culture or "en-GB" + MSRS.gender = MSRS_Config.Gender or "male" + MSRS.google = MSRS_Config.Google + MSRS.Label = MSRS_Config.Label or "MSRS" + MSRS.voice = MSRS_Config.Voice or MSRS.Voices.Microsoft.Hazel + if MSRS_Config.GRPC then + MSRS.provider = MSRS_Config.GRPC.DefaultProvider + if MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider] then + MSRS.APIKey = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].key + MSRS.defaultVoice = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].defaultVoice + MSRS.region = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].secret + MSRS.secret = MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].region + end + end + 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) + return false + end + + return true +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- MSRS DCS-gRPC alternate backend ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1303,7 +1547,7 @@ MSRS_BACKEND_DCSGRPC.Functions._DCSgRPCtts = function (self, Text, Plaintext, Fr BASE:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") BASE:T({Text, Plaintext, Frequencies, Voice, Label}) - local options = {} + local options = self.ProviderOptions or MSRS.ProviderOptions or {} -- #MSRS.GRPCOptions local ssml = Text or '' local XmitFrequencies = Frequencies or self.Frequency @@ -1315,7 +1559,7 @@ MSRS_BACKEND_DCSGRPC.Functions._DCSgRPCtts = function (self, Text, Plaintext, Fr options.srsClientName = Label or self.Label options.position = {} if self.coordinate then - options.position.lat, options.position.lat, options.position.alt = self:_GetLatLongAlt(self.coordinate) + options.position.lat, options.position.lon, options.position.alt = self:_GetLatLongAlt(self.coordinate) end options.position.lat = options.position.lat or 0.0 @@ -1327,19 +1571,23 @@ MSRS_BACKEND_DCSGRPC.Functions._DCSgRPCtts = function (self, Text, Plaintext, Fr elseif UTILS.GetCoalitionName(self.coalition) == 'Red' then options.coalition = 'red' end - - options[self.provider] = {} + + local provider = self.provider or self.GRPCOptions.DefaultProvider or MSRS.GRPCOptions.DefaultProvider + + options.provider = {} + + options.provider[provider] = {} if self.APIKey then - options[self.provider].key = self.APIKey + options.provider[provider].key = self.APIKey end if self.defaultVoice then - options[self.provider].voice = self.defaultVoice + options.provider[provider].defaultVoice = self.defaultVoice end if self.voice then - options[self.provider].voice = Voice or self.voice + options.provider[provider].voice = Voice or self.voice or self.defaultVoice elseif ssml then -- DCS-gRPC doesn't directly support language/gender, but can use SSML -- Only use if a voice isn't explicitly set @@ -1365,7 +1613,7 @@ MSRS_BACKEND_DCSGRPC.Functions._DCSgRPCtts = function (self, Text, Plaintext, Fr BASE:T("GRPC.tts") BASE:T(ssml) BASE:T(freq) - BASE:T(options) + BASE:T({options}) GRPC.tts(ssml, freq, options) end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index a2caa7871..9fc3dd055 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -224,7 +224,7 @@ UTILS = { -- @return #boolean UTILS.IsInstanceOf = function( object, className ) -- Is className NOT a string ? - if not type( className ) == 'string' then + if type( className ) ~= 'string' then -- Is className a Moose class ? if type( className ) == 'table' and className.IsInstanceOf ~= nil then @@ -406,7 +406,7 @@ function UTILS._OneLineSerialize(tbl) elseif type(val) == 'nil' then -- won't ever happen, right? tbl_str[#tbl_str + 1] = 'nil, ' elseif type(val) == 'table' then - --tbl_str[#tbl_str + 1] = UTILS._OneLineSerialize(val) + --tbl_str[#tbl_str + 1] = UTILS.TableShow(tbl,loc,indent,tableshow_tbls) --tbl_str[#tbl_str + 1] = ', ' --I think this is right, I just added it else --log:warn('Unable to serialize value type $1 at index $2', mist.utils.basicSerialize(type(val)), tostring(ind)) @@ -2421,7 +2421,7 @@ function UTILS.CheckFileExists(Path,Filename) -- Check io module is available. if not io then - BASE:E("ERROR: io not desanitized. Can't save current state.") + BASE:E("ERROR: io not desanitized.") return false end