From de00dd403125fdc5b6eb9fa3552ec7846bc2a15e Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 20 Jan 2024 20:27:31 -0800 Subject: [PATCH] Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start. --- desktop_version/src/Game.cpp | 79 ++++++++++++++++++++++++-------- desktop_version/src/Graphics.cpp | 49 ++++++++++++++++++-- desktop_version/src/Graphics.h | 5 +- desktop_version/src/Input.cpp | 4 +- desktop_version/src/Script.cpp | 2 +- desktop_version/src/Textbox.cpp | 70 ++++++++++++++++++++++++---- desktop_version/src/Textbox.h | 32 ++++++++++++- 7 files changed, 204 insertions(+), 37 deletions(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index c31576a3..5a326745 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -695,16 +695,21 @@ void Game::levelcomplete_textbox(void) graphics.addline(" "); graphics.addline(""); graphics.addline(""); + graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); graphics.textboxcenterx(); graphics.setimage(TEXTIMAGE_LEVELCOMPLETE); graphics.setlinegap(0); + graphics.textboxapplyposition(); } -void Game::crewmate_textbox(const int color) +static void compute_crewmate_textbox(textboxclass* THIS) { + THIS->lines.clear(); + THIS->addline(""); + const int extra_cjk_height = (font::height(PR_FONT_INTERFACE) * 4) - 32; - graphics.createtextboxflipme("", -1, 64 + 8 + 16 - extra_cjk_height/2, TEXT_COLOUR("gray")); + THIS->yp = 64 + 8 + 16 - extra_cjk_height/2; /* This is a special case for wrapping, we MUST have two lines. * So just make sure it can't fit in one line. */ @@ -717,23 +722,36 @@ void Game::crewmate_textbox(const int color) size_t pos_n = wrapped.find('\n', startline); size_t pos_p = wrapped.find('|', startline); newline = SDL_min(pos_n, pos_p); - graphics.addline(wrapped.substr(startline, newline-startline)); + THIS->addline(wrapped.substr(startline, newline-startline)); startline = newline+1; } while (newline != std::string::npos); - graphics.addline(""); - graphics.textboxprintflags(PR_FONT_INTERFACE); - graphics.textboxcentertext(); + THIS->addline(""); + THIS->centertext(); float spaces_per_8 = font::len(PR_FONT_INTERFACE, " ")/8.0f; - graphics.textboxpad(SDL_ceilf(5/spaces_per_8), SDL_ceilf(2/spaces_per_8)); - graphics.textboxcenterx(); - graphics.addsprite(14, 12 + extra_cjk_height/2, 0, color); - graphics.setlinegap(0); + THIS->pad(SDL_ceilf(5/spaces_per_8), SDL_ceilf(2/spaces_per_8)); + if (!THIS->sprites.empty()) + { + THIS->sprites[0].y = 12 + extra_cjk_height/2; + } } -void Game::remaining_textbox(void) +void Game::crewmate_textbox(const int color) { - const int remaining = 6 - crewrescued(); + const int extra_cjk_height = (font::height(PR_FONT_INTERFACE) * 4) - 32; + graphics.createtextboxflipme("", -1, 64 + 8 + 16, TEXT_COLOUR("gray")); + graphics.textboxprintflags(PR_FONT_INTERFACE); + graphics.textboxcenterx(); + graphics.addsprite(14, 12 + extra_cjk_height/2, 0, color); + graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_crewmate_textbox); + graphics.setlinegap(0); + graphics.textboxapplyposition(); +} + +static void compute_remaining_textbox(textboxclass* THIS) +{ + extern Game game; + const int remaining = 6 - game.crewrescued(); char buffer[SCREEN_WIDTH_CHARS + 1]; if (remaining > 0) { @@ -744,16 +762,29 @@ void Game::remaining_textbox(void) SDL_strlcpy(buffer, loc::gettext("All Crew Members Rescued!"), sizeof(buffer)); } + THIS->lines.clear(); + THIS->lines.push_back(buffer); + // In CJK, the "You have rescued" box becomes so big we should lower this one a bit... const int cjk_lowering = font::height(PR_FONT_INTERFACE) - 8; - graphics.createtextboxflipme(buffer, -1, 128 + 16 + cjk_lowering, TEXT_COLOUR("gray")); - graphics.textboxprintflags(PR_FONT_INTERFACE); - graphics.textboxpad(2, 2); - graphics.textboxcenterx(); + THIS->yp = 128 + 16 + cjk_lowering; + THIS->pad(2, 2); + } -void Game::actionprompt_textbox(void) +void Game::remaining_textbox(void) { + graphics.createtextboxflipme("", -1, 128 + 16, TEXT_COLOUR("gray")); + graphics.textboxprintflags(PR_FONT_INTERFACE); + graphics.textboxcenterx(); + graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_remaining_textbox); + graphics.textboxapplyposition(); +} + +static void compute_actionprompt_textbox(textboxclass* THIS) +{ + THIS->lines.clear(); + char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf( buffer, sizeof(buffer), @@ -761,10 +792,18 @@ void Game::actionprompt_textbox(void) "button:but", vformat_button(ActionSet_InGame, Action_InGame_ACTION) ); - graphics.createtextboxflipme(buffer, -1, 196, TEXT_COLOUR("cyan")); + + THIS->lines.push_back(buffer); + THIS->pad(1, 1); +} + +void Game::actionprompt_textbox(void) +{ + graphics.createtextboxflipme("", -1, 196, TEXT_COLOUR("cyan")); graphics.textboxprintflags(PR_FONT_INTERFACE); - graphics.textboxpad(1, 1); graphics.textboxcenterx(); + graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_actionprompt_textbox); + graphics.textboxapplyposition(); } void Game::savetele_textbox(void) @@ -2896,10 +2935,12 @@ void Game::updatestate(void) graphics.addline(" "); graphics.addline(""); graphics.addline(""); + graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); graphics.textboxcenterx(); graphics.setimage(TEXTIMAGE_GAMECOMPLETE); graphics.setlinegap(0); + graphics.textboxapplyposition(); break; case 3502: { diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 8fb4fa02..033c7401 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -1495,6 +1495,17 @@ void Graphics::setlarge(bool large) textboxes[m].large = large; } +void Graphics::textboxapplyposition(void) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxapplyposition() out-of-bounds!"); + return; + } + + textboxes[m].applyposition(); +} + void Graphics::textboxadjust(void) { if (!INBOUNDS_VEC(m, textboxes)) @@ -3272,7 +3283,8 @@ void Graphics::textboxpad(size_t left_pad, size_t right_pad) return; } - textboxes[m].pad(left_pad, right_pad); + textboxes[m].spacing.pad_left = left_pad; + textboxes[m].spacing.pad_right = right_pad; } void Graphics::textboxpadtowidth(size_t new_w) @@ -3283,7 +3295,7 @@ void Graphics::textboxpadtowidth(size_t new_w) return; } - textboxes[m].padtowidth(new_w); + textboxes[m].spacing.padtowidth = new_w; } void Graphics::textboxcentertext(void) @@ -3294,7 +3306,7 @@ void Graphics::textboxcentertext(void) return; } - textboxes[m].centertext(); + textboxes[m].spacing.centertext = true; } void Graphics::textboxprintflags(const uint32_t flags) @@ -3342,6 +3354,25 @@ void Graphics::textboxoriginalcontext(const TextboxOriginalContext* original_con textboxes[m].original = *original_context; } +void Graphics::textboxoriginalcontextauto(void) +{ + if (!INBOUNDS_VEC(m, textboxes)) + { + vlog_error("textboxoriginalcontextauto() out-of-bounds!"); + return; + } + + TextboxOriginalContext context = TextboxOriginalContext(); + context.text_case = 1; + context.lines = textboxes[m].lines; + if (script.running) + { + context.script_name = script.scriptname; + } + + textboxes[m].original = context; +} + void Graphics::textboxcase(char text_case) { if (!INBOUNDS_VEC(m, textboxes)) @@ -3353,7 +3384,7 @@ void Graphics::textboxcase(char text_case) textboxes[m].original.text_case = text_case; } -void Graphics::textboxtranslate(void) +void Graphics::textboxtranslate(const TextboxTranslate translate, const TextboxFunction function) { if (!INBOUNDS_VEC(m, textboxes)) { @@ -3361,7 +3392,15 @@ void Graphics::textboxtranslate(void) return; } - textboxes[m].translate(); + if (translate == TEXTTRANSLATE_FUNCTION && function == NULL) + { + SDL_assert(0 && "function is NULL!"); + return; + } + + textboxes[m].translate = translate; + textboxes[m].function = function; + textboxes[m].updatetext(); } void Graphics::textboxcommsrelay(void) diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index 3fca3944..4916f15a 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -117,13 +117,16 @@ public: void textboxcrewmateposition(const TextboxCrewmatePosition* crewmate_position); void textboxoriginalcontext(const TextboxOriginalContext* original_context); + void textboxoriginalcontextauto(void); void textboxcase(char text_case); - void textboxtranslate(void); + void textboxtranslate(TextboxTranslate translate, TextboxFunction function); void textboxcommsrelay(void); + void textboxapplyposition(void); + void textboxadjust(void); void addline(const std::string& t); diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 8ecf1e4a..725c2dc7 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1134,8 +1134,8 @@ static void menuactionpress(void) /* 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! + graphics.textboxes[i].updatetext(); + graphics.textboxes[i].applyposition(); } } diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 1128a244..a49a4906 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -764,7 +764,7 @@ void scriptclass::run(void) } graphics.textboxprintflags(flags); } - graphics.textboxtranslate(); + graphics.textboxtranslate(TEXTTRANSLATE_CUTSCENE, NULL); graphics.textboxadjust(); if (words[0] == "speak_active") diff --git a/desktop_version/src/Textbox.cpp b/desktop_version/src/Textbox.cpp index c329dbe2..f6ad2884 100644 --- a/desktop_version/src/Textbox.cpp +++ b/desktop_version/src/Textbox.cpp @@ -32,6 +32,8 @@ textboxclass::textboxclass(int gap) should_centery = false; print_flags = PR_FONT_LEVEL; + translate = TEXTTRANSLATE_NONE; + function = NULL; fill_buttons = false; sprites.clear(); @@ -41,6 +43,7 @@ textboxclass::textboxclass(int gap) crewmate_position = TextboxCrewmatePosition(); original = TextboxOriginalContext(); original.text_case = 1; + spacing = TextboxSpacing(); } void textboxclass::addsprite(int x, int y, int tile, int col) @@ -71,7 +74,7 @@ void textboxclass::centery(void) resize(); } -void textboxclass::adjust(void) +void textboxclass::applyposition(void) { resize(); repositionfromcrewmate(); @@ -83,6 +86,12 @@ void textboxclass::adjust(void) { centery(); } +} + +void textboxclass::adjust(void) +{ + resize(); + applyposition(); if (xp < 10) xp = 10; if (yp < 10) yp = 10; if (xp + w > 310) xp = 310 - w; @@ -246,16 +255,59 @@ void textboxclass::centertext(void) padtowidth(w-16); } -void textboxclass::translate(void) +void textboxclass::copyoriginaltext(void) +{ + // Copy the original back, but keep the limit of lines in mind + lines.clear(); + for (size_t i = 0; i < original.lines.size(); i++) + { + addline(original.lines[i]); + } +} + +void textboxclass::applyoriginalspacing(void) +{ + if (spacing.centertext) + { + centertext(); + } + if (spacing.pad_left > 0 || spacing.pad_right > 0) + { + pad(spacing.pad_left, spacing.pad_right); + } + if (spacing.padtowidth > 0) + { + padtowidth(spacing.padtowidth); + } +} + +void textboxclass::updatetext(void) +{ + switch (translate) + { + case TEXTTRANSLATE_NONE: + copyoriginaltext(); + applyoriginalspacing(); + break; + case TEXTTRANSLATE_CUTSCENE: + translatecutscene(); + break; + case TEXTTRANSLATE_FUNCTION: + if (function == NULL) + { + SDL_assert(0 && "function is NULL!"); + break; + } + function(this); + } +} + +void textboxclass::translatecutscene(void) { if (!loc::is_cutscene_translated(original.script_name)) { - // Copy the original back, but keep the limit of lines in mind - lines.clear(); - for (size_t i = 0; i < original.lines.size(); i++) - { - addline(original.lines[i]); - } + copyoriginaltext(); + applyoriginalspacing(); return; } @@ -274,6 +326,8 @@ void textboxclass::translate(void) const loc::TextboxFormat* format = loc::gettext_cutscene(original.script_name, eng, original.text_case); if (format == NULL || format->text == NULL || format->text[0] == '\0') { + copyoriginaltext(); + applyoriginalspacing(); return; } diff --git a/desktop_version/src/Textbox.h b/desktop_version/src/Textbox.h index 45d76860..b5c46cd7 100644 --- a/desktop_version/src/Textbox.h +++ b/desktop_version/src/Textbox.h @@ -24,6 +24,15 @@ struct TextboxOriginalContext char text_case; }; +/* Similar to, but NOT the same as a loc::TextboxFormat. */ +struct TextboxSpacing +{ + bool centertext; + unsigned char pad_left; + unsigned char pad_right; + unsigned short padtowidth; +}; + struct TextboxSprite { int x; @@ -39,6 +48,16 @@ enum TextboxImage TEXTIMAGE_GAMECOMPLETE }; +enum TextboxTranslate +{ + TEXTTRANSLATE_NONE, + TEXTTRANSLATE_CUTSCENE, + TEXTTRANSLATE_FUNCTION +}; + +class textboxclass; +typedef void (*TextboxFunction)(textboxclass* THIS); + class textboxclass { public: @@ -52,6 +71,8 @@ public: void centery(void); + void applyposition(void); + void adjust(void); void initcol(int rr, int gg, int bb); @@ -74,7 +95,13 @@ public: void centertext(void); - void translate(void); + void copyoriginaltext(void); + + void applyoriginalspacing(void); + + void updatetext(void); + + void translatecutscene(void); public: //Fundamentals std::vector lines; @@ -98,6 +125,7 @@ public: bool should_centery; uint32_t print_flags; + TextboxTranslate translate; bool fill_buttons; std::vector sprites; @@ -105,6 +133,8 @@ public: TextboxCrewmatePosition crewmate_position; TextboxOriginalContext original; + TextboxSpacing spacing; + TextboxFunction function; }; #endif /* TEXTBOX_H */