From 2a4bd826052102a0bb8858355e42b9cba06f2991 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:16:56 +1000 Subject: [PATCH] move user-visible voice interface strings into a json file [skip ci] --- .../interface/opensb/voicechat/locale.json | 12 ++++ .../opensb/voicechat/voicechat.config | 8 +-- .../interface/opensb/voicechat/voicechat.lua | 57 ++++++++++++------ assets/opensb/sfx/interface/voice_switch.ogg | Bin 0 -> 6559 bytes .../opensb/sfx/interface/voice_switch_off.ogg | Bin 0 -> 5015 bytes 5 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 assets/opensb/interface/opensb/voicechat/locale.json create mode 100644 assets/opensb/sfx/interface/voice_switch.ogg create mode 100644 assets/opensb/sfx/interface/voice_switch_off.ogg diff --git a/assets/opensb/interface/opensb/voicechat/locale.json b/assets/opensb/interface/opensb/voicechat/locale.json new file mode 100644 index 0000000..f012ae7 --- /dev/null +++ b/assets/opensb/interface/opensb/voicechat/locale.json @@ -0,0 +1,12 @@ +{ + "TheirVolume" : "THEIR VOLUME", + "YourVolume" : "YOUR VOLUME", + "VoiceMode" : "VOICE MODE", + "Threshold" : "THRESHOLD", + "DisableVoiceChat" : "DISABLE VOICE CHAT", + "EnableVoiceChat" : "ENABLE VOICE CHAT", + "PushToTalk" : "PUSH TO TALK", + "Activity" : "ACTIVITY", + "Muted" : "MUTED", + "UseSystemDefault" : "Use System Default" +} \ No newline at end of file diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.config b/assets/opensb/interface/opensb/voicechat/voicechat.config index 20e88d9..087e2eb 100644 --- a/assets/opensb/interface/opensb/voicechat/voicechat.config +++ b/assets/opensb/interface/opensb/voicechat/voicechat.config @@ -27,7 +27,7 @@ "voiceVolumeLabel" : { "type" : "label", - "value" : "THEIR VOLUME", + "value" : "TheirVolume", "position" : [26, 178], "wrapWidth" : 48, "lineSpacing" : 0.75, @@ -43,7 +43,7 @@ "inputVolumeLabel" : { "type" : "label", - "value" : "YOUR VOLUME", + "value" : "YourVolume", "position" : [26, 158], "wrapWidth" : 48, "lineSpacing" : 0.75, @@ -78,7 +78,7 @@ "voiceModeLabel" : { "type" : "label", - "value" : "VOICE MODE", + "value" : "VoiceMode", "position" : [26, 133], "wrapWidth" : 32, "lineSpacing" : 0.75, @@ -123,7 +123,7 @@ "thresholdLevel" : { "type" : "label", - "value" : "THRESHOLD", + "value" : "Threshold", "position" : [26, 109], "wrapWidth" : 48, "lineSpacing" : 0.75, diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.lua b/assets/opensb/interface/opensb/voicechat/voicechat.lua index 974b5b1..0c199c4 100644 --- a/assets/opensb/interface/opensb/voicechat/voicechat.lua +++ b/assets/opensb/interface/opensb/voicechat/voicechat.lua @@ -1,15 +1,23 @@ +local fmt = string.format + --constants local PATH = "/interface/opensb/voicechat/" +local SFX = {} +for i, v in pairs{"SWITCH", "SWITCH_OFF", "ON", "OFF"} do + SFX[v] = fmt("/sfx/interface/voice_%s.ogg", v:lower()) +end local DEVICE_LIST_WIDGET = "devices.list" local DEFAULT_DEVICE_NAME = "Use System Default" -local NULL_DEVICE_NAME = "No Audio Device" local COLD_COLOR = {25, 255, 255, 225} local HOT_COLOR = {255, 96, 96, 225} local MINIMUM_DB = -80 local VOICE_MAX, INPUT_MAX = 1.75, 1.0 local MID = 7.5 -local fmt = string.format +local localization = {} +local function str(a, b) + return b and a .. tostring(localization[b] or b) or tostring(localization[a] or a) +end local debugging = false local function log(...) @@ -17,6 +25,10 @@ local function log(...) sb.logInfo(...) end +local function boolToColor(bool) + return bool and "0f0" or "f00" +end + local function mapToRange(x, min, max) return math.min(1, math.max(0, (x - min)) / max) end @@ -25,6 +37,10 @@ local function linear(a, b, c) return a + (b - a) * c end +local function meow(path) + widget.playSound(path, 0) +end + local settings = {} local function set(k, v) @@ -40,11 +56,12 @@ local widgetsToDevices = {} local nullWidget local function addDeviceToList(deviceName) local name = widget.addListItem(DEVICE_LIST_WIDGET) - widget.setText(fmt("%s.%s.button", DEVICE_LIST_WIDGET, name), deviceName) + local button = fmt("%s.%s.button", DEVICE_LIST_WIDGET, name) + widget.setText(button, deviceName) widgetsToDevices[name] = deviceName devicesToWidgets[deviceName] = name log("Added audio device '%s' to list", deviceName) - return name + return button end function selectDevice() @@ -59,7 +76,9 @@ function selectDevice() if settings.deviceName == deviceName then local inputEnabled = set("inputEnabled", not settings.inputEnabled) widget.setListSelected(DEVICE_LIST_WIDGET, inputEnabled and selected or nullWidget) + meow(inputEnabled and SFX.SWITCH or SFX.SWITCH_OFF) else + meow(SFX.SWITCH) set("deviceName", deviceName) set("inputEnabled", true) end @@ -71,16 +90,17 @@ end local function updateVoiceButton() local enabled = settings.enabled - widget.setText("enableVoiceToggle", enabled and "^#0f0;disable voice chat" or "^#f00;enable voice chat") - widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. (enabled and "0f0" or "f00")) + local color = boolToColor(enabled) + widget.setText("enableVoiceToggle", str(fmt("^#%s;", color), enabled and "DisableVoiceChat" or "EnableVoiceChat")) + widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. color) end local function updateVoiceModeButtons() local pushToTalk = settings.inputMode:lower() == "pushtotalk" - widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. (pushToTalk and "0f0" or "f00")) - widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. (pushToTalk and "f00" or "0f0")) - widget.setText("pushToTalk", pushToTalk and "^#0f0;PUSH TO TALK" or "^#f00;PUSH TO TALK") - widget.setText("voiceActivity", pushToTalk and "^#f00;ACTIVITY" or "^#0f0;ACTIVITY") + widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. boolToColor( pushToTalk)) + widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. boolToColor(not pushToTalk)) + widget.setText("pushToTalk", str(fmt("^#%s;", boolToColor( pushToTalk)), "PushToTalk")) + widget.setText("voiceActivity", str(fmt("^#%s;", boolToColor(not pushToTalk)), "Activity")) end local voiceCanvas, inputCanvas = nil, nil @@ -102,7 +122,7 @@ local function updateVolumeCanvas(canvas, volume, multiplier) canvas:drawLine({1, MID}, {lineEnd, MID}, lineColor, 60) canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60) - local str = volume == 0 and "^#f00,shadow;MUTED" or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1) + local str = volume == 0 and str("^#f00,shadow;", "Muted") or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1) canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255}) end @@ -131,6 +151,12 @@ local function updateThresholdCanvas(canvas, dB) end function init() + localization = root.assetJson(PATH .. "locale.json") + for name, thing in pairs(root.assetJson(PATH .. "voicechat.config:gui")) do + if thing.type == "label" then + widget.setText(name, str(thing.value)) + end + end settings = voice.getSettings() voiceCanvas = widget.bindCanvas("voiceVolume") inputCanvas = widget.bindCanvas("inputVolume") @@ -143,11 +169,8 @@ function displayed() widget.clearListItems(DEVICE_LIST_WIDGET) initCallbacks() - addDeviceToList(DEFAULT_DEVICE_NAME) - - for i, v in pairs(voice.devices()) do - addDeviceToList(v) - end + widget.setText(addDeviceToList(DEFAULT_DEVICE_NAME), str("UseSystemDefault")) + for i, v in pairs(voice.devices()) do addDeviceToList(v) end nullWidget = widget.addListItem(DEVICE_LIST_WIDGET) local nullWidgetPath = fmt("%s.%s", DEVICE_LIST_WIDGET, nullWidget) @@ -223,6 +246,6 @@ end function voiceToggle() set("enabled", not settings.enabled) - widget.playSound(fmt("/sfx/interface/voice_%s.ogg", settings.enabled and "on" or "off"), 0) + meow(settings.enabled and SFX.ON or SFX.OFF) updateVoiceButton() end \ No newline at end of file diff --git a/assets/opensb/sfx/interface/voice_switch.ogg b/assets/opensb/sfx/interface/voice_switch.ogg new file mode 100644 index 0000000000000000000000000000000000000000..bf2f295dc57cb21034022a475e8ff0457889b460 GIT binary patch literal 6559 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