From 68075dfbd5956c01545cf11a6c1e87131040d826 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Fri, 13 Feb 2026 00:56:41 -0500 Subject: [PATCH] consistent handling of text token replacement Just as special tokens are replaced in debriefings, replace them in command briefings and briefings as well. Change `string_replace_tokens_with_keys` to `message_translate_tokens` (since it's the same thing) and add a `replaceContainers` script API function to complement `replaceVariables` and `replaceTokens`. Also replace every category of token in `sexp_modify_variable` and add comments to places where token replacement might be expected but happens elsewhere. --- code/camera/camera.cpp | 1 + code/mission/missiontraining.cpp | 3 ++- code/mission/missiontraining.h | 2 +- code/missionui/missionbrief.cpp | 17 +++++++++++++---- code/missionui/missioncmdbrief.cpp | 27 ++++++++++++++++++++------- code/missionui/missiondebrief.cpp | 5 ++--- code/parse/sexp.cpp | 10 ++++++---- code/scripting/api/libs/base.cpp | 18 ++++++++++++++++++ code/scripting/api/objs/message.cpp | 3 ++- 9 files changed, 65 insertions(+), 21 deletions(-) diff --git a/code/camera/camera.cpp b/code/camera/camera.cpp index c87feb45e4f..bb0fcc712f6 100644 --- a/code/camera/camera.cpp +++ b/code/camera/camera.cpp @@ -732,6 +732,7 @@ subtitle::subtitle(int in_x_pos, int in_y_pos, const char* in_text, const char* text_buf = in_text; sexp_replace_variable_names_with_values(text_buf); sexp_container_replace_refs_with_values(text_buf); + // (message_translate_tokens is called when the subtitle is queued, so does not need to be called here) in_text = text_buf.c_str(); } diff --git a/code/mission/missiontraining.cpp b/code/mission/missiontraining.cpp index 4f2b58d792c..27af1b78c19 100644 --- a/code/mission/missiontraining.cpp +++ b/code/mission/missiontraining.cpp @@ -720,7 +720,8 @@ char *translate_message_token(char *str) return NULL; } -void string_replace_tokens_with_keys(SCP_string& text) { +void message_translate_tokens(SCP_string &text) +{ text = message_translate_tokens(text.c_str()); } diff --git a/code/mission/missiontraining.h b/code/mission/missiontraining.h index 6e929bdd0e8..239ff19c6f7 100644 --- a/code/mission/missiontraining.h +++ b/code/mission/missiontraining.h @@ -41,7 +41,7 @@ void training_mission_shutdown(); void training_check_objectives(); void message_training_queue(const char *text, TIMESTAMP timestamp, int length = -1); SCP_string message_translate_tokens(const char *text); -void string_replace_tokens_with_keys(SCP_string& text); +void message_translate_tokens(SCP_string &text); void training_fail(); void message_training_update_frame(); diff --git a/code/missionui/missionbrief.cpp b/code/missionui/missionbrief.cpp index da08601d249..eeb65224dd4 100644 --- a/code/missionui/missionbrief.cpp +++ b/code/missionui/missionbrief.cpp @@ -33,6 +33,7 @@ #include "mission/missioncampaign.h" #include "mission/missiongoals.h" #include "mission/missionmessage.h" +#include "mission/missiontraining.h" #include "missionui/chatbox.h" #include "missionui/missionbrief.h" #include "missionui/missionscreencommon.h" @@ -311,6 +312,7 @@ int Brief_max_line_width[GR_NUM_RESOLUTIONS] = { int brief_setup_closeup(brief_icon *bi, bool api_access = false); void brief_maybe_blit_scene_cut(float frametime); void brief_transition_reset(); +void brief_replace_stage_text(brief_stage &stage); const char *brief_tooltip_handler(const char *str) { @@ -800,10 +802,7 @@ void brief_compact_stages() num = 0; while ( num < Briefing->num_stages ) { if ( eval_sexp(Briefing->stages[num].formula) ) { - // Goober5000 - replace any variables (probably persistent variables) with their values - sexp_replace_variable_names_with_values(Briefing->stages[num].text); - // karajorma/jg18 - replace container references as well - sexp_container_replace_refs_with_values(Briefing->stages[num].text); + brief_replace_stage_text(Briefing->stages[num]); } else { // clean up unused briefing stage Briefing->stages[num].text = ""; @@ -2232,3 +2231,13 @@ int brief_only_allow_briefing() return 0; } + +// Goober5000 - replace any variables (probably persistent variables) with their values +// karajorma/jg18 - replace container references as well +// Goober5000 - replace keybinds also +void brief_replace_stage_text(brief_stage &stage) +{ + sexp_replace_variable_names_with_values(stage.text); + sexp_container_replace_refs_with_values(stage.text); + message_translate_tokens(stage.text); +} diff --git a/code/missionui/missioncmdbrief.cpp b/code/missionui/missioncmdbrief.cpp index 4b047dcb5a2..dca9a300f03 100644 --- a/code/missionui/missioncmdbrief.cpp +++ b/code/missionui/missioncmdbrief.cpp @@ -21,6 +21,7 @@ #include "io/timer.h" #include "mission/missionparse.h" #include "mission/missionbriefcommon.h" +#include "mission/missiontraining.h" #include "missionui/missioncmdbrief.h" #include "missionui/missionscreencommon.h" #include "missionui/missionshipchoice.h" @@ -191,6 +192,11 @@ static int Uses_scroll_buttons = 0; int Cmd_brief_overlay_id; +// -------------------------------------------------------------------------------------- +// Forward declarations +// -------------------------------------------------------------------------------------- +void cmd_brief_replace_stage_text(cmd_brief_stage &stage); + void cmd_brief_init_voice() { int i; @@ -562,16 +568,13 @@ void cmd_brief_init(int team) Cmd_brief_inited = 0; Cur_cmd_brief = &Cmd_briefs[team]; - // Goober5000 - replace any variables (probably persistent variables) with their values - // karajorma/jg18 - replace container references as well - for (i = 0; i < Cur_cmd_brief->num_stages; i++) { - sexp_replace_variable_names_with_values(Cur_cmd_brief->stage[i].text); - sexp_container_replace_refs_with_values(Cur_cmd_brief->stage[i].text); - } - if (Cur_cmd_brief->num_stages <= 0) return; + for (i = 0; i < Cur_cmd_brief->num_stages; i++) { + cmd_brief_replace_stage_text(Cur_cmd_brief->stage[i]); + } + // for multiplayer, change the state in my netplayer structure if (Game_mode & GM_MULTIPLAYER) { Net_player->state = NETPLAYER_STATE_CMD_BRIEFING; @@ -779,3 +782,13 @@ int mission_has_cmd_brief() { return (Cur_cmd_brief != NULL && Cur_cmd_brief->num_stages > 0); } + +// Goober5000 - replace any variables (probably persistent variables) with their values +// karajorma/jg18 - replace container references as well +// Goober5000 - replace keybinds also +void cmd_brief_replace_stage_text(cmd_brief_stage &stage) +{ + sexp_replace_variable_names_with_values(stage.text); + sexp_container_replace_refs_with_values(stage.text); + message_translate_tokens(stage.text); +} diff --git a/code/missionui/missiondebrief.cpp b/code/missionui/missiondebrief.cpp index 1c2fcb85bcd..30144f000b5 100644 --- a/code/missionui/missiondebrief.cpp +++ b/code/missionui/missiondebrief.cpp @@ -2606,7 +2606,6 @@ void debrief_replace_stage_text(debrief_stage &stage) sexp_replace_variable_names_with_values(stage.recommendation_text); sexp_container_replace_refs_with_values(stage.text); sexp_container_replace_refs_with_values(stage.recommendation_text); - - stage.text = message_translate_tokens(stage.text.c_str()); - stage.recommendation_text = message_translate_tokens(stage.recommendation_text.c_str()); + message_translate_tokens(stage.text); + message_translate_tokens(stage.recommendation_text); } diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 0023f9977ca..de0afc15057 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -13415,9 +13415,9 @@ void sexp_hud_set_xstr(int n) lcl_ext_localize(xstr, translated_string); // Now replace tokens and variables - string_replace_tokens_with_keys(translated_string); sexp_replace_variable_names_with_values(translated_string); sexp_container_replace_refs_with_values(translated_string); + message_translate_tokens(translated_string); HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { @@ -13437,9 +13437,9 @@ void sexp_hud_set_message(int n) if ( !stricmp(text, Messages[i].name) ) { message = Messages[i].message; - string_replace_tokens_with_keys(message); sexp_replace_variable_names_with_values(message); sexp_container_replace_refs_with_values(message); + message_translate_tokens(message); HudGauge* cg = hud_get_custom_gauge(gaugename); if (cg) { @@ -35520,11 +35520,13 @@ void sexp_modify_variable(const char *text, int index, bool sexp_callback) Assert( !MULTIPLAYER_CLIENT ); const size_t maxCopyLen = TOKEN_LENGTH - 1; - if (strchr(text, '$') != nullptr) + if (strchr(text, '$') != nullptr || strchr(text, sexp_container::DELIM) != nullptr) { - // we want to use the same variable substitution that's in messages etc. + // we want to use the same text substitution that's in messages etc. SCP_string temp_text = text; sexp_replace_variable_names_with_values(temp_text); + sexp_container_replace_refs_with_values(temp_text); + message_translate_tokens(temp_text); if (temp_text.length() > maxCopyLen) Warning(LOCATION, "String too long. Only " SIZE_T_ARG " characters will be assigned to %s.\n\nOriginal string:\n%s", maxCopyLen, Sexp_variables[index].variable_name, temp_text.c_str()); diff --git a/code/scripting/api/libs/base.cpp b/code/scripting/api/libs/base.cpp index a29916d6cd8..3e91589dd70 100644 --- a/code/scripting/api/libs/base.cpp +++ b/code/scripting/api/libs/base.cpp @@ -584,6 +584,24 @@ ADE_FUNC(replaceVariables, return ade_set_args(L, "s", translated_str.c_str()); } +ADE_FUNC(replaceContainers, + l_Base, + "string text", + "Returns a string that replaces any container reference with the container value (same as text in Briefings, Debriefings, or Messages). Container ref must be preceded by '&' for replacement to work.", + "string", + "Updated string or nil if invalid") +{ + const char* untranslated_str; + if (!ade_get_args(L, "s", &untranslated_str)) { + return ADE_RETURN_NIL; + } + + SCP_string translated_str = untranslated_str; + sexp_container_replace_refs_with_values(translated_str); + + return ade_set_args(L, "s", translated_str.c_str()); +} + ADE_FUNC(inMissionEditor, l_Base, nullptr, "Determine if the current script is running in the mission editor (e.g. FRED2). This should be used to control which code paths will be executed even if running in the editor.", "boolean", "true when we are in the mission editor, false otherwise") { return ade_set_args(L, "b", Fred_running != 0); } diff --git a/code/scripting/api/objs/message.cpp b/code/scripting/api/objs/message.cpp index 5e8c99f37b5..8deee92e4c6 100644 --- a/code/scripting/api/objs/message.cpp +++ b/code/scripting/api/objs/message.cpp @@ -191,7 +191,7 @@ ADE_VIRTVAR(Persona, l_Message, "persona", "The persona of the message", "person return ade_set_args(L, "o", l_Persona.Set(Messages[idx].persona_index)); } -ADE_FUNC(getMessage, l_Message, "[boolean replaceVars = true]", "Gets the text of the message and optionally replaces SEXP variables with their respective values.", "string", "The message or an empty string if handle is invalid") +ADE_FUNC(getMessage, l_Message, "[boolean replaceStuff = true]", "Gets the text of the message and optionally replaces SEXP variable and container references with their respective values.", "string", "The message or an empty string if handle is invalid") { int idx = -1; bool replace = true; @@ -210,6 +210,7 @@ ADE_FUNC(getMessage, l_Message, "[boolean replaceVars = true]", "Gets the text o sexp_replace_variable_names_with_values(temp_buf, MESSAGE_LENGTH - 1); sexp_container_replace_refs_with_values(temp_buf, MESSAGE_LENGTH - 1); + // (message_translate_tokens is called in message_queue_process) return ade_set_args(L, "s", temp_buf); }