From d11b84fc1c9234deec5abd02cef703932a103db2 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 20 Jul 2026 15:06:14 -0500 Subject: [PATCH] Add text-to-speech Lua API to briefing/debrief/tech room UI sections Bind the existing fsspeech_* wrappers into the ui.Briefing, ui.CommandBriefing, ui.Debriefing, and ui.TechRoom scripting sections so Lua UIs (e.g. SCPUI, which overrides the native screens) can drive synthesized speech again. Each section gets playTextToSpeech, stopTextToSpeech, pauseTextToSpeech, isTextToSpeechPlaying, and isTextToSpeechEnabled. Briefing/command-brief/debrief use FSSPEECH_FROM_BRIEFING; the tech room uses FSSPEECH_FROM_TECHROOM. fsspeech_play already self-gates on the Speech.* options, so these are no-ops when speech is off. --- code/scripting/api/libs/ui.cpp | 229 +++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/code/scripting/api/libs/ui.cpp b/code/scripting/api/libs/ui.cpp index 5f97d18499b..554e70ffab4 100644 --- a/code/scripting/api/libs/ui.cpp +++ b/code/scripting/api/libs/ui.cpp @@ -72,6 +72,7 @@ #include "scripting/api/objs/vecmath.h" #include "scripting/lua/LuaTable.h" #include "sound/audiostr.h" +#include "sound/fsspeech.h" #include "stats/medals.h" #include "stats/stats.h" @@ -682,6 +683,63 @@ ADE_LIB_DERIV(l_UserInterface_Brief, "API for accessing data related to the Briefing UI.", l_UserInterface); +ADE_FUNC(playTextToSpeech, + l_UserInterface_Brief, + "string text", + "Speaks the given text using the engine's text-to-speech voice. Does nothing unless the briefing speech option is enabled. Color codes are stripped automatically.", + nullptr, + nullptr) +{ + const char* text = nullptr; + if (!ade_get_args(L, "s", &text)) + return ADE_RETURN_NIL; + + fsspeech_play(FSSPEECH_FROM_BRIEFING, text); + return ADE_RETURN_NIL; +} + +ADE_FUNC(stopTextToSpeech, l_UserInterface_Brief, nullptr, "Stops any text-to-speech playback.", nullptr, nullptr) +{ + SCP_UNUSED(L); + fsspeech_stop(); + return ADE_RETURN_NIL; +} + +ADE_FUNC(pauseTextToSpeech, + l_UserInterface_Brief, + "boolean pause", + "Pauses (true) or resumes (false) text-to-speech playback.", + nullptr, + nullptr) +{ + bool pause = true; + if (!ade_get_args(L, "b", &pause)) + return ADE_RETURN_NIL; + + fsspeech_pause(pause); + return ADE_RETURN_NIL; +} + +ADE_FUNC(isTextToSpeechPlaying, + l_UserInterface_Brief, + nullptr, + "Returns whether text-to-speech is currently speaking.", + "boolean", + "true if speaking, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_playing()); +} + +ADE_FUNC(isTextToSpeechEnabled, + l_UserInterface_Brief, + nullptr, + "Returns whether briefing text-to-speech is enabled and available.", + "boolean", + "true if enabled, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_play_from(FSSPEECH_FROM_BRIEFING)); +} + ADE_FUNC(getBriefingMusicName, l_UserInterface_Brief, nullptr, @@ -1074,6 +1132,63 @@ ADE_LIB_DERIV(l_UserInterface_CmdBrief, "API for accessing data related to the Command Briefing UI.", l_UserInterface); +ADE_FUNC(playTextToSpeech, + l_UserInterface_CmdBrief, + "string text", + "Speaks the given text using the engine's text-to-speech voice. Does nothing unless the briefing speech option is enabled. Color codes are stripped automatically.", + nullptr, + nullptr) +{ + const char* text = nullptr; + if (!ade_get_args(L, "s", &text)) + return ADE_RETURN_NIL; + + fsspeech_play(FSSPEECH_FROM_BRIEFING, text); + return ADE_RETURN_NIL; +} + +ADE_FUNC(stopTextToSpeech, l_UserInterface_CmdBrief, nullptr, "Stops any text-to-speech playback.", nullptr, nullptr) +{ + SCP_UNUSED(L); + fsspeech_stop(); + return ADE_RETURN_NIL; +} + +ADE_FUNC(pauseTextToSpeech, + l_UserInterface_CmdBrief, + "boolean pause", + "Pauses (true) or resumes (false) text-to-speech playback.", + nullptr, + nullptr) +{ + bool pause = true; + if (!ade_get_args(L, "b", &pause)) + return ADE_RETURN_NIL; + + fsspeech_pause(pause); + return ADE_RETURN_NIL; +} + +ADE_FUNC(isTextToSpeechPlaying, + l_UserInterface_CmdBrief, + nullptr, + "Returns whether text-to-speech is currently speaking.", + "boolean", + "true if speaking, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_playing()); +} + +ADE_FUNC(isTextToSpeechEnabled, + l_UserInterface_CmdBrief, + nullptr, + "Returns whether briefing text-to-speech is enabled and available.", + "boolean", + "true if enabled, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_play_from(FSSPEECH_FROM_BRIEFING)); +} + ADE_FUNC(getCmdBriefing, l_UserInterface_CmdBrief, nullptr, @@ -1092,6 +1207,63 @@ ADE_LIB_DERIV(l_UserInterface_Debrief, "API for accessing data related to the Debriefing UI.", l_UserInterface); +ADE_FUNC(playTextToSpeech, + l_UserInterface_Debrief, + "string text", + "Speaks the given text using the engine's text-to-speech voice. Does nothing unless the briefing speech option is enabled. Color codes are stripped automatically.", + nullptr, + nullptr) +{ + const char* text = nullptr; + if (!ade_get_args(L, "s", &text)) + return ADE_RETURN_NIL; + + fsspeech_play(FSSPEECH_FROM_BRIEFING, text); + return ADE_RETURN_NIL; +} + +ADE_FUNC(stopTextToSpeech, l_UserInterface_Debrief, nullptr, "Stops any text-to-speech playback.", nullptr, nullptr) +{ + SCP_UNUSED(L); + fsspeech_stop(); + return ADE_RETURN_NIL; +} + +ADE_FUNC(pauseTextToSpeech, + l_UserInterface_Debrief, + "boolean pause", + "Pauses (true) or resumes (false) text-to-speech playback.", + nullptr, + nullptr) +{ + bool pause = true; + if (!ade_get_args(L, "b", &pause)) + return ADE_RETURN_NIL; + + fsspeech_pause(pause); + return ADE_RETURN_NIL; +} + +ADE_FUNC(isTextToSpeechPlaying, + l_UserInterface_Debrief, + nullptr, + "Returns whether text-to-speech is currently speaking.", + "boolean", + "true if speaking, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_playing()); +} + +ADE_FUNC(isTextToSpeechEnabled, + l_UserInterface_Debrief, + nullptr, + "Returns whether briefing text-to-speech is enabled and available.", + "boolean", + "true if enabled, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_play_from(FSSPEECH_FROM_BRIEFING)); +} + ADE_FUNC(initDebriefing, l_UserInterface_Debrief, nullptr, @@ -1707,6 +1879,63 @@ ADE_LIB_DERIV(l_UserInterface_TechRoom, "API for accessing data related to the Tech Room UIs.", l_UserInterface); +ADE_FUNC(playTextToSpeech, + l_UserInterface_TechRoom, + "string text", + "Speaks the given text using the engine's text-to-speech voice. Does nothing unless the tech room speech option is enabled. Color codes are stripped automatically.", + nullptr, + nullptr) +{ + const char* text = nullptr; + if (!ade_get_args(L, "s", &text)) + return ADE_RETURN_NIL; + + fsspeech_play(FSSPEECH_FROM_TECHROOM, text); + return ADE_RETURN_NIL; +} + +ADE_FUNC(stopTextToSpeech, l_UserInterface_TechRoom, nullptr, "Stops any text-to-speech playback.", nullptr, nullptr) +{ + SCP_UNUSED(L); + fsspeech_stop(); + return ADE_RETURN_NIL; +} + +ADE_FUNC(pauseTextToSpeech, + l_UserInterface_TechRoom, + "boolean pause", + "Pauses (true) or resumes (false) text-to-speech playback.", + nullptr, + nullptr) +{ + bool pause = true; + if (!ade_get_args(L, "b", &pause)) + return ADE_RETURN_NIL; + + fsspeech_pause(pause); + return ADE_RETURN_NIL; +} + +ADE_FUNC(isTextToSpeechPlaying, + l_UserInterface_TechRoom, + nullptr, + "Returns whether text-to-speech is currently speaking.", + "boolean", + "true if speaking, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_playing()); +} + +ADE_FUNC(isTextToSpeechEnabled, + l_UserInterface_TechRoom, + nullptr, + "Returns whether tech room text-to-speech is enabled and available.", + "boolean", + "true if enabled, false otherwise") +{ + return ade_set_args(L, "b", fsspeech_play_from(FSSPEECH_FROM_TECHROOM)); +} + ADE_FUNC(buildMissionList, l_UserInterface_TechRoom, nullptr,