1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-02 19:13:31 +02:00

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.
This commit is contained in:
Misa 2024-01-18 20:21:02 -08:00
parent 3b0757bd82
commit 12518b3c60
9 changed files with 249 additions and 177 deletions

View File

@ -6545,7 +6545,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
option(loc::gettext("audio")); option(loc::gettext("audio"));
option(loc::gettext("game pad")); option(loc::gettext("game pad"));
option(loc::gettext("accessibility")); option(loc::gettext("accessibility"));
option(loc::gettext("language"), graphics.textboxes.empty()); option(loc::gettext("language"));
option(loc::gettext("return")); option(loc::gettext("return"));
menuyoff = 0; menuyoff = 0;
maxspacing = 15; maxspacing = 15;

View File

@ -3320,6 +3320,50 @@ void Graphics::textboxbuttons(void)
textboxes[m].fill_buttons = true; 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) void Graphics::textboxcommsrelay(void)
{ {
// Special treatment for the gamestate textboxes in Comms Relay // Special treatment for the gamestate textboxes in Comms Relay

View File

@ -114,6 +114,14 @@ public:
void textboxbuttons(void); 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 textboxcommsrelay(void);
void textboxadjust(void); void textboxadjust(void);

View File

@ -1044,19 +1044,12 @@ static void menuactionpress(void)
break; break;
case 5: case 5:
//language options //language options
if (graphics.textboxes.empty()) music.playef(Sound_VIRIDIAN);
{ loc::loadlanguagelist();
music.playef(Sound_VIRIDIAN); loc::pre_title_lang_menu = false;
loc::loadlanguagelist(); game.createmenu(Menu::language);
loc::pre_title_lang_menu = false; game.currentmenuoption = loc::languagelist_curlang;
game.createmenu(Menu::language); map.nexttowercolour();
game.currentmenuoption = loc::languagelist_curlang;
map.nexttowercolour();
}
else
{
music.playef(Sound_CRY);
}
break; break;
default: default:
/* Return */ /* Return */
@ -1115,6 +1108,8 @@ static void menuactionpress(void)
break; break;
case Menu::language: case Menu::language:
{ {
std::string prev_lang = std::string(loc::lang);
music.playef(Sound_VIRIDIAN); music.playef(Sound_VIRIDIAN);
if (loc::languagelist.size() != 0 && (unsigned)game.currentmenuoption < loc::languagelist.size()) if (loc::languagelist.size() != 0 && (unsigned)game.currentmenuoption < loc::languagelist.size())
@ -1133,6 +1128,17 @@ static void menuactionpress(void)
game.menustart = false; game.menustart = false;
loc::pre_title_lang_menu = 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(); game.returnmenu();
map.nexttowercolour(); map.nexttowercolour();
game.savestatsandsettings_menu(); game.savestatsandsettings_menu();

View File

@ -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); font::print_wrap(PR_CEN, -1, 65, loc::gettext("Disable screen effects, enable slowdown modes or invincibility."), tr, tg, tb);
break; break;
case 5: case 5:
{
font::print(PR_2X | PR_CEN, -1, 30, loc::gettext("Language"), tr, tg, tb); 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); 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);
}
}
} }
break; break;
case Menu::graphicoptions: case Menu::graphicoptions:

View File

@ -42,14 +42,12 @@ scriptclass::scriptclass(void)
r = 0; r = 0;
textx = 0; textx = 0;
texty = 0; texty = 0;
textcrewmateposition = TextboxCrewmatePosition();
textoriginalcontext = TextboxOriginalContext();
textbox_colours.clear(); textbox_colours.clear();
add_default_colours(); add_default_colours();
textflipme = false; textflipme = false;
textcentertext = false;
textboxtimer = 0; textboxtimer = 0;
textpad_left = 0;
textpad_right = 0;
textpadtowidth = 0;
textcase = 1; textcase = 1;
textbuttons = false; textbuttons = false;
textlarge = false; textlarge = false;
@ -525,15 +523,10 @@ void scriptclass::run(void)
} }
} }
textcentertext = false;
textpad_left = 0;
textpad_right = 0;
textpadtowidth = 0;
textboxtimer = 0; textboxtimer = 0;
textcrewmateposition = TextboxCrewmatePosition();
textbox_sprites.clear(); textbox_sprites.clear();
textbox_image = TEXTIMAGE_NONE; textbox_image = TEXTIMAGE_NONE;
translate_dialogue();
} }
else if (words[0] == "position") else if (words[0] == "position")
{ {
@ -569,35 +562,19 @@ void scriptclass::run(void)
} }
} }
int linegap = graphics.getlinegap(); if (INBOUNDS_VEC(i, obj.entities) && (j == 0 || j == 1))
//next is whether to position above or below
if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above")
{ {
if (j == 1) //left if (words[2] == "above")
{ {
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later textcrewmateposition.text_above = true;
texty = obj.entities[i].yp - 16 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap); }
} textcrewmateposition.x = obj.entities[i].xp;
else if (j == 0) //Right textcrewmateposition.override_x = true;
{ textcrewmateposition.y = obj.entities[i].yp;
textx = obj.entities[i].xp - 16; textcrewmateposition.override_y = true;
texty = obj.entities[i].yp - 18 - (txt.size() * (font::height(PR_FONT_LEVEL) + linegap) - linegap);
} textcrewmateposition.dir = j;
}
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;
}
} }
} }
else if (words[0] == "customposition") else if (words[0] == "customposition")
@ -669,34 +646,19 @@ void scriptclass::run(void)
texty = -500; texty = -500;
} }
int linegap = graphics.getlinegap(); if (INBOUNDS_VEC(i, obj.entities) && (j == 0 || j == 1))
//next is whether to position above or below
if (INBOUNDS_VEC(i, obj.entities) && words[2] == "above")
{ {
if (j == 1) //left if (words[2] == "above")
{ {
textx = obj.entities[i].xp -10000; //tells the box to be oriented correctly later textcrewmateposition.text_above = true;
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.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") else if (words[0] == "backgroundtext")
@ -769,40 +731,26 @@ void scriptclass::run(void)
graphics.setimage(textbox_image); 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) if (textx == -500 || textx == -1)
{ {
graphics.textboxcenterx(); graphics.textboxcenterx();
textcrewmateposition.override_x = false;
} }
if (texty == -500) if (texty == -500)
{ {
graphics.textboxcentery(); graphics.textboxcentery();
textcrewmateposition.override_y = false;
} }
TextboxOriginalContext context = TextboxOriginalContext();
context.text_case = textcase;
context.lines = std::vector<std::string>(txt);
context.script_name = scriptname;
graphics.textboxcrewmateposition(&textcrewmateposition);
graphics.textboxoriginalcontext(&context);
graphics.textboxcase(textcase);
if (map.custommode) if (map.custommode)
{ {
uint32_t flags = PR_FONT_IDX(font::font_idx_level, cl.rtl); uint32_t flags = PR_FONT_IDX(font::font_idx_level, cl.rtl);
@ -812,6 +760,7 @@ void scriptclass::run(void)
} }
graphics.textboxprintflags(flags); graphics.textboxprintflags(flags);
} }
graphics.textboxtranslate();
graphics.textboxadjust(); graphics.textboxadjust();
if (words[0] == "speak_active") if (words[0] == "speak_active")
@ -1985,8 +1934,6 @@ void scriptclass::run(void)
} }
break; break;
} }
translate_dialogue();
} }
else if (words[0] == "trinketbluecontrol") 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) static void gotoerrorloadinglevel(void)
{ {
game.quittomenu(); game.quittomenu();

View File

@ -91,8 +91,6 @@ public:
void run(void); void run(void);
void translate_dialogue(void);
void startgamemode(enum StartMode mode); void startgamemode(enum StartMode mode);
void teleport(void); void teleport(void);
@ -114,12 +112,10 @@ public:
std::map<std::string, SDL_Color> textbox_colours; std::map<std::string, SDL_Color> textbox_colours;
int textx; int textx;
int texty; int texty;
TextboxCrewmatePosition textcrewmateposition;
TextboxOriginalContext textoriginalcontext;
int r,g,b; int r,g,b;
bool textflipme; bool textflipme;
bool textcentertext;
size_t textpad_left;
size_t textpad_right;
size_t textpadtowidth;
char textcase; char textcase;
bool textbuttons; bool textbuttons;
bool textlarge; bool textlarge;

View File

@ -3,6 +3,7 @@
#include <SDL.h> #include <SDL.h>
#include "Font.h" #include "Font.h"
#include "Localization.h"
#include "UTF8.h" #include "UTF8.h"
textboxclass::textboxclass(int gap) textboxclass::textboxclass(int gap)
@ -33,6 +34,10 @@ textboxclass::textboxclass(int gap)
sprites.clear(); sprites.clear();
image = TEXTIMAGE_NONE; image = TEXTIMAGE_NONE;
crewmate_position = TextboxCrewmatePosition();
original = TextboxOriginalContext();
original.text_case = 1;
} }
void textboxclass::addsprite(int x, int y, int tile, int col) void textboxclass::addsprite(int x, int y, int tile, int col)
@ -66,6 +71,7 @@ void textboxclass::centery(void)
void textboxclass::adjust(void) void textboxclass::adjust(void)
{ {
resize(); resize();
repositionfromcrewmate();
if (xp < 10) xp = 10; if (xp < 10) xp = 10;
if (yp < 10) yp = 10; if (yp < 10) yp = 10;
if (xp + w > 310) xp = 310 - w; 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; 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) void textboxclass::addline(const std::string& t)
{ {
lines.push_back(t); lines.push_back(t);
@ -192,3 +234,75 @@ void textboxclass::centertext(void)
{ {
padtowidth(w-16); padtowidth(w-16);
} }
void textboxclass::translate(void)
{
if (!loc::is_cutscene_translated(original.script_name))
{
// Copy the original back
lines = std::vector<std::string>(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);
}
}

View File

@ -5,6 +5,25 @@
#include <string> #include <string>
#include <vector> #include <vector>
/* 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<std::string> lines;
std::string script_name;
char text_case;
};
struct TextboxSprite struct TextboxSprite
{ {
int x; int x;
@ -45,6 +64,8 @@ public:
void resize(void); void resize(void);
void repositionfromcrewmate(void);
void addline(const std::string& t); void addline(const std::string& t);
void pad(size_t left_pad, size_t right_pad); void pad(size_t left_pad, size_t right_pad);
@ -52,6 +73,8 @@ public:
void padtowidth(size_t new_w); void padtowidth(size_t new_w);
void centertext(void); void centertext(void);
void translate(void);
public: public:
//Fundamentals //Fundamentals
std::vector<std::string> lines; std::vector<std::string> lines;
@ -76,6 +99,9 @@ public:
std::vector<TextboxSprite> sprites; std::vector<TextboxSprite> sprites;
TextboxImage image; TextboxImage image;
TextboxCrewmatePosition crewmate_position;
TextboxOriginalContext original;
}; };
#endif /* TEXTBOX_H */ #endif /* TEXTBOX_H */