From 8b03fbd9f48295886fe8d999503e00c2a646f208 Mon Sep 17 00:00:00 2001 From: Misa Date: Thu, 18 Jan 2024 20:21:02 -0800 Subject: [PATCH] Save textbox state, allow lang switches w/ textbox This allows switching languages while a text box is on screen by saving the necessary state for a text box to be retranslated when the language is switched. This saves the state of the position and direction of the crewmate that the text box position is based off of (if applicable), and the text case of the text box, the script name of the script, and the original (English) lines of the text box. I did not explicitly label the original lines as English lines except in a main game context, because technically, custom levels could have original lines in a different language. Unfortunately, this doesn't work for every text box in the game. Notably, the Level Complete, Game Complete, number of crewmates remaining, trinket collection, Intermission 1 guides, etc. text boxes are special and require further fixes, but that will be coming in later commits. --- desktop_version/src/Game.cpp | 2 +- desktop_version/src/Graphics.cpp | 44 ++++++++ desktop_version/src/Graphics.h | 8 ++ desktop_version/src/Input.cpp | 32 +++--- desktop_version/src/Render.cpp | 9 +- desktop_version/src/Script.cpp | 183 ++++++------------------------- desktop_version/src/Script.h | 8 +- desktop_version/src/Textbox.cpp | 114 +++++++++++++++++++ desktop_version/src/Textbox.h | 26 +++++ 9 files changed, 249 insertions(+), 177 deletions(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index 0014ad91..c31576a3 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -6545,7 +6545,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ ) option(loc::gettext("audio")); option(loc::gettext("game pad")); option(loc::gettext("accessibility")); - option(loc::gettext("language"), graphics.textboxes.empty()); + option(loc::gettext("language")); option(loc::gettext("return")); menuyoff = 0; maxspacing = 15; diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 3813c162..b6f4272c 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -3320,6 +3320,50 @@ void Graphics::textboxbuttons(void) textboxes[m].fill_buttons = true; } +void Graphics::textboxcrewmateposition(const TextboxCrewmatePosition* crewmate_position) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxcrewmateposition() out-of-bounds!"); + return; + } + + textboxes[m].crewmate_position = *crewmate_position; +} + +void Graphics::textboxoriginalcontext(const TextboxOriginalContext* original_context) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxoriginalcontext() out-of-bounds!"); + return; + } + + textboxes[m].original = *original_context; +} + +void Graphics::textboxcase(char text_case) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxcase() out-of-bounds!"); + return; + } + + textboxes[m].original.text_case = text_case; +} + +void Graphics::textboxtranslate(void) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxtranslate() out-of-bounds!"); + return; + } + + textboxes[m].translate(); +} + void Graphics::textboxcommsrelay(void) { // Special treatment for the gamestate textboxes in Comms Relay diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index 7d02e9d8..3fca3944 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -114,6 +114,14 @@ public: void textboxbuttons(void); + void textboxcrewmateposition(const TextboxCrewmatePosition* crewmate_position); + + void textboxoriginalcontext(const TextboxOriginalContext* original_context); + + void textboxcase(char text_case); + + void textboxtranslate(void); + void textboxcommsrelay(void); void textboxadjust(void); diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index aaa09e0b..8ecf1e4a 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1044,19 +1044,12 @@ static void menuactionpress(void) break; case 5: //language options - if (graphics.textboxes.empty()) - { - music.playef(Sound_VIRIDIAN); - loc::loadlanguagelist(); - loc::pre_title_lang_menu = false; - game.createmenu(Menu::language); - game.currentmenuoption = loc::languagelist_curlang; - map.nexttowercolour(); - } - else - { - music.playef(Sound_CRY); - } + music.playef(Sound_VIRIDIAN); + loc::loadlanguagelist(); + loc::pre_title_lang_menu = false; + game.createmenu(Menu::language); + game.currentmenuoption = loc::languagelist_curlang; + map.nexttowercolour(); break; default: /* Return */ @@ -1115,6 +1108,8 @@ static void menuactionpress(void) break; case Menu::language: { + std::string prev_lang = std::string(loc::lang); + music.playef(Sound_VIRIDIAN); if (loc::languagelist.size() != 0 && (unsigned)game.currentmenuoption < loc::languagelist.size()) @@ -1133,6 +1128,17 @@ static void menuactionpress(void) game.menustart = false; loc::pre_title_lang_menu = false; } + + if (prev_lang != loc::lang) + { + /* Retranslate and reposition all text boxes. */ + for (size_t i = 0; i < graphics.textboxes.size(); i++) + { + graphics.textboxes[i].translate(); + graphics.textboxes[i].adjust(); // FIXME: not all textboxes obey the 10-pixel inner border! + } + } + game.returnmenu(); map.nexttowercolour(); game.savestatsandsettings_menu(); diff --git a/desktop_version/src/Render.cpp b/desktop_version/src/Render.cpp index 41191eab..2c578c5e 100644 --- a/desktop_version/src/Render.cpp +++ b/desktop_version/src/Render.cpp @@ -385,15 +385,8 @@ static void menurender(void) font::print_wrap(PR_CEN, -1, 65, loc::gettext("Disable screen effects, enable slowdown modes or invincibility."), tr, tg, tb); break; case 5: - { font::print(PR_2X | PR_CEN, -1, 30, loc::gettext("Language"), tr, tg, tb); - int next_y = font::print_wrap(PR_CEN, -1, 65, loc::gettext("Change the language."), tr, tg, tb); - - if (!graphics.textboxes.empty()) - { - font::print_wrap(PR_CEN, -1, next_y, loc::gettext("Can not change the language while a textbox is displayed in-game."), tr, tg, tb); - } - } + font::print_wrap(PR_CEN, -1, 65, loc::gettext("Change the language."), tr, tg, tb); } break; case Menu::graphicoptions: diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 10b3ae17..690a2a54 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -42,14 +42,12 @@ scriptclass::scriptclass(void) r = 0; textx = 0; texty = 0; + textcrewmateposition = TextboxCrewmatePosition(); + textoriginalcontext = TextboxOriginalContext(); textbox_colours.clear(); add_default_colours(); textflipme = false; - textcentertext = false; textboxtimer = 0; - textpad_left = 0; - textpad_right = 0; - textpadtowidth = 0; textcase = 1; textbuttons = false; textlarge = false; @@ -525,15 +523,10 @@ void scriptclass::run(void) } } - textcentertext = false; - textpad_left = 0; - textpad_right = 0; - textpadtowidth = 0; textboxtimer = 0; + textcrewmateposition = TextboxCrewmatePosition(); textbox_sprites.clear(); textbox_image = TEXTIMAGE_NONE; - - translate_dialogue(); } else if (words[0] == "position") { @@ -569,35 +562,19 @@ void scriptclass::run(void) } } - int linegap = graphics.getlinegap(); - - //next is whether to position above or below - if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above") + if (INBOUNDS_VEC(i, obj.entities) && (j == 0 || j == 1)) { - if (j == 1) //left + if (words[2] == "above") { - textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later - texty = obj.entities[i].yp - 16 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap); + textcrewmateposition.text_above = true; + } - } - else if (j == 0) //Right - { - textx = obj.entities[i].xp - 16; - texty = obj.entities[i].yp - 18 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap); - } - } - else if (INBOUNDS_VEC(i, obj.entities)) - { - if (j == 1) //left - { - textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later - texty = obj.entities[i].yp + 26; - } - else if (j == 0) //Right - { - textx = obj.entities[i].xp - 16; - texty = obj.entities[i].yp + 26; - } + textcrewmateposition.x = obj.entities[i].xp; + textcrewmateposition.override_x = true; + textcrewmateposition.y = obj.entities[i].yp; + textcrewmateposition.override_y = true; + + textcrewmateposition.dir = j; } } else if (words[0] == "customposition") @@ -669,34 +646,19 @@ void scriptclass::run(void) texty = -500; } - int linegap = graphics.getlinegap(); - - //next is whether to position above or below - if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above") + if (INBOUNDS_VEC(i, obj.entities) && (j == 0 || j == 1)) { - if (j == 1) //left + if (words[2] == "above") { - textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later - texty = obj.entities[i].yp - 16 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap); - } - else if (j == 0) //Right - { - textx = obj.entities[i].xp - 16; - texty = obj.entities[i].yp - 18 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap); - } - } - else if (INBOUNDS_VEC(i, obj.entities)) - { - if (j == 1) //left - { - textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later - texty = obj.entities[i].yp + 26; - } - else if (j == 0) //Right - { - textx = obj.entities[i].xp - 16; - texty = obj.entities[i].yp + 26; + textcrewmateposition.text_above = true; } + + textcrewmateposition.x = obj.entities[i].xp; + textcrewmateposition.override_x = true; + textcrewmateposition.y = obj.entities[i].xp; + textcrewmateposition.override_y = true; + + textcrewmateposition.dir = j; } } else if (words[0] == "backgroundtext") @@ -769,40 +731,26 @@ void scriptclass::run(void) graphics.setimage(textbox_image); - // Some textbox formatting that can be set by translations... - if (textcentertext) - { - graphics.textboxcentertext(); - } - if (textpad_left > 0 || textpad_right > 0) - { - graphics.textboxpad(textpad_left, textpad_right); - } - if (textpadtowidth > 0) - { - graphics.textboxpadtowidth(textpadtowidth); - } - - //the textbox cannot be outside the screen. Fix if it is. - if (textx <= -1000) - { - //position to the left of the player - textx += 10000; - textx -= graphics.textboxwidth(); - textx += 16; - graphics.textboxmoveto(textx); - } - if (textx == -500 || textx == -1) { graphics.textboxcenterx(); + textcrewmateposition.override_x = false; } if (texty == -500) { graphics.textboxcentery(); + textcrewmateposition.override_y = false; } + TextboxOriginalContext context = TextboxOriginalContext(); + context.text_case = textcase; + context.lines = std::vector(txt); + context.script_name = scriptname; + + graphics.textboxcrewmateposition(&textcrewmateposition); + graphics.textboxoriginalcontext(&context); + graphics.textboxcase(textcase); if (map.custommode) { uint32_t flags = PR_FONT_IDX(font::font_idx_level, cl.rtl); @@ -812,6 +760,7 @@ void scriptclass::run(void) } graphics.textboxprintflags(flags); } + graphics.textboxtranslate(); graphics.textboxadjust(); if (words[0] == "speak_active") @@ -1985,8 +1934,6 @@ void scriptclass::run(void) } break; } - - translate_dialogue(); } else if (words[0] == "trinketbluecontrol") { @@ -2557,68 +2504,6 @@ void scriptclass::run(void) } } -void scriptclass::translate_dialogue(void) -{ - char tc = textcase; - textcase = 1; - - if (!loc::is_cutscene_translated(scriptname)) - { - return; - } - - // English text needs to be un-wordwrapped, translated, and re-wordwrapped - std::string eng; - for (size_t i = 0; i < txt.size(); i++) - { - if (i != 0) - { - eng.append("\n"); - } - eng.append(txt[i]); - } - - eng = font::string_unwordwrap(eng); - const loc::TextboxFormat* format = loc::gettext_cutscene(scriptname, eng, tc); - if (format == NULL || format->text == NULL || format->text[0] == '\0') - { - return; - } - std::string tra; - if (format->tt) - { - tra = std::string(format->text); - size_t pipe; - while (true) - { - pipe = tra.find('|', 0); - if (pipe == std::string::npos) - { - break; - } - tra.replace(pipe, 1, "\n"); - } - } - else - { - tra = font::string_wordwrap_balanced(PR_FONT_LEVEL, format->text, format->wraplimit); - } - - textcentertext = format->centertext; - textpad_left = format->pad_left; - textpad_right = format->pad_right; - textpadtowidth = format->padtowidth; - - txt.clear(); - size_t startline = 0; - size_t newline; - do { - newline = tra.find('\n', startline); - txt.push_back(tra.substr(startline, newline-startline)); - startline = newline+1; - } while (newline != std::string::npos); -} - static void gotoerrorloadinglevel(void) { game.quittomenu(); diff --git a/desktop_version/src/Script.h b/desktop_version/src/Script.h index bf9f8d97..aa71220d 100644 --- a/desktop_version/src/Script.h +++ b/desktop_version/src/Script.h @@ -91,8 +91,6 @@ public: void run(void); - void translate_dialogue(void); - void startgamemode(enum StartMode mode); void teleport(void); @@ -114,12 +112,10 @@ public: std::map textbox_colours; int textx; int texty; + TextboxCrewmatePosition textcrewmateposition; + TextboxOriginalContext textoriginalcontext; int r,g,b; bool textflipme; - bool textcentertext; - size_t textpad_left; - size_t textpad_right; - size_t textpadtowidth; char textcase; bool textbuttons; bool textlarge; diff --git a/desktop_version/src/Textbox.cpp b/desktop_version/src/Textbox.cpp index d49f40aa..592090ea 100644 --- a/desktop_version/src/Textbox.cpp +++ b/desktop_version/src/Textbox.cpp @@ -3,6 +3,7 @@ #include #include "Font.h" +#include "Localization.h" #include "UTF8.h" textboxclass::textboxclass(int gap) @@ -33,6 +34,10 @@ textboxclass::textboxclass(int gap) sprites.clear(); image = TEXTIMAGE_NONE; + + crewmate_position = TextboxCrewmatePosition(); + original = TextboxOriginalContext(); + original.text_case = 1; } void textboxclass::addsprite(int x, int y, int tile, int col) @@ -66,6 +71,7 @@ void textboxclass::centery(void) void textboxclass::adjust(void) { resize(); + repositionfromcrewmate(); if (xp < 10) xp = 10; if (yp < 10) yp = 10; if (xp + w > 310) xp = 310 - w; @@ -136,6 +142,42 @@ void textboxclass::resize(void) h = lines.size()*(font::height(print_flags) + linegap) + 16 - linegap; } +void textboxclass::repositionfromcrewmate(void) +{ + const int font_height = font::height(print_flags); + + // Reposition based off crewmate position, if applicable + if (crewmate_position.override_x) + { + if (crewmate_position.dir == 1) // left + { + xp = crewmate_position.x - w + 16; + } + else if (crewmate_position.dir == 0) // right + { + xp = crewmate_position.x - 16; + } + } + if (crewmate_position.override_y) + { + if (crewmate_position.text_above) + { + if (crewmate_position.dir == 1) // left + { + yp = crewmate_position.y - 16 - (lines.size() * (font_height + linegap) - linegap); + } + else if (crewmate_position.dir == 0) // right + { + yp = crewmate_position.y - 18 - (lines.size() * (font_height + linegap) - linegap); + } + } + else + { + yp = crewmate_position.y + 26; + } + } +} + void textboxclass::addline(const std::string& t) { lines.push_back(t); @@ -192,3 +234,75 @@ void textboxclass::centertext(void) { padtowidth(w-16); } + +void textboxclass::translate(void) +{ + if (!loc::is_cutscene_translated(original.script_name)) + { + // Copy the original back + lines = std::vector(original.lines); + return; + } + + // English text needs to be un-wordwrapped, translated, and re-wordwrapped + std::string eng; + for (size_t i = 0; i < original.lines.size(); i++) + { + if (i != 0) + { + eng.append("\n"); + } + eng.append(original.lines[i]); + } + + eng = font::string_unwordwrap(eng); + const loc::TextboxFormat* format = loc::gettext_cutscene(original.script_name, eng, original.text_case); + if (format == NULL || format->text == NULL || format->text[0] == '\0') + { + return; + } + + std::string tra; + if (format->tt) + { + tra = std::string(format->text); + size_t pipe; + while (true) + { + pipe = tra.find('|', 0); + if (pipe == std::string::npos) + { + break; + } + tra.replace(pipe, 1, "\n"); + } + } + else + { + tra = font::string_wordwrap_balanced(PR_FONT_LEVEL, format->text, format->wraplimit); + } + + lines.clear(); + size_t startline = 0; + size_t newline; + do + { + newline = tra.find('\n', startline); + lines.push_back(tra.substr(startline, newline - startline)); + startline = newline + 1; + } + while (newline != std::string::npos); + + if (format->centertext) + { + centertext(); + } + if (format->pad_left > 0 || format->pad_right > 0) + { + pad(format->pad_left, format->pad_right); + } + if (format->padtowidth > 0) + { + padtowidth(format->padtowidth); + } +} diff --git a/desktop_version/src/Textbox.h b/desktop_version/src/Textbox.h index 176885b7..4a6ef58f 100644 --- a/desktop_version/src/Textbox.h +++ b/desktop_version/src/Textbox.h @@ -5,6 +5,25 @@ #include #include +/* Position of the crewmate that the text box position is based off of. + * NOT a crewmate sprite inside the text box (that's a TextboxSprite). */ +struct TextboxCrewmatePosition +{ + bool override_x; + bool override_y; + int x; + int y; + int dir; + bool text_above; +}; + +struct TextboxOriginalContext +{ + std::vector lines; + std::string script_name; + char text_case; +}; + struct TextboxSprite { int x; @@ -45,6 +64,8 @@ public: void resize(void); + void repositionfromcrewmate(void); + void addline(const std::string& t); void pad(size_t left_pad, size_t right_pad); @@ -52,6 +73,8 @@ public: void padtowidth(size_t new_w); void centertext(void); + + void translate(void); public: //Fundamentals std::vector lines; @@ -76,6 +99,9 @@ public: std::vector sprites; TextboxImage image; + + TextboxCrewmatePosition crewmate_position; + TextboxOriginalContext original; }; #endif /* TEXTBOX_H */