From 3f8531d7df35ac5188cb58e365309cfa90cab94f Mon Sep 17 00:00:00 2001 From: Magnus Larsen Date: Fri, 25 Oct 2024 21:09:18 -0700 Subject: [PATCH] Support other Languages --- src/game/gui/text_render.c | 2 +- src/game/gui/textselector.c | 15 +- src/game/scenes/mainmenu/menu_configuration.c | 9 +- src/game/scenes/mainmenu/menu_language.c | 136 ++++++++++++++++++ src/game/scenes/mainmenu/menu_language.h | 9 ++ src/game/utils/settings.c | 5 + src/game/utils/settings.h | 5 + src/resources/ids.c | 4 - src/resources/ids.h | 1 - src/resources/languages.c | 11 +- 10 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 src/game/scenes/mainmenu/menu_language.c create mode 100644 src/game/scenes/mainmenu/menu_language.h diff --git a/src/game/gui/text_render.c b/src/game/gui/text_render.c index b3e07a002..1d0378187 100644 --- a/src/game/gui/text_render.c +++ b/src/game/gui/text_render.c @@ -19,7 +19,7 @@ void text_defaults(text_settings *settings) { int text_render_char(const text_settings *settings, text_mode state, int x, int y, char ch) { // Make sure code is valid - int code = ch - 32; + int code = (unsigned char)ch - 32; surface **sur = NULL; if(code < 0) { return 0; diff --git a/src/game/gui/textselector.c b/src/game/gui/textselector.c index 641699375..e2d576e52 100644 --- a/src/game/gui/textselector.c +++ b/src/game/gui/textselector.c @@ -53,13 +53,20 @@ const char *textselector_get_current_text(const component *c) { static void textselector_render(component *c) { textselector *tb = widget_get_obj(c); char buf[100]; + int buf_max = sizeof buf - 1; + buf[buf_max] = '\0'; - // Only render if the selector has options - if(vector_size(&tb->options) > 0) { + if(vector_size(&tb->options) > 0 && tb->text[0] != '\0') { + // label & options char **opt = vector_get(&tb->options, *tb->pos); - snprintf(buf, 100, "%s %s", tb->text, *opt); + snprintf(buf, buf_max, "%s %s", tb->text, *opt); + } else if(vector_size(&tb->options) > 0) { + // no label, just options + char **opt = vector_get(&tb->options, *tb->pos); + snprintf(buf, buf_max, "%s", *opt); } else { - snprintf(buf, 100, "%s -", tb->text); + // no options, just label + snprintf(buf, buf_max, "%s -", tb->text); } // Render text diff --git a/src/game/scenes/mainmenu/menu_configuration.c b/src/game/scenes/mainmenu/menu_configuration.c index 6b1fb78ef..2159f76a4 100644 --- a/src/game/scenes/mainmenu/menu_configuration.c +++ b/src/game/scenes/mainmenu/menu_configuration.c @@ -1,6 +1,7 @@ #include "game/scenes/mainmenu/menu_configuration.h" #include "game/scenes/mainmenu/menu_audio.h" #include "game/scenes/mainmenu/menu_input.h" +#include "game/scenes/mainmenu/menu_language.h" #include "game/scenes/mainmenu/menu_video.h" #include "game/gui/gui.h" @@ -11,6 +12,11 @@ void menu_config_done(component *c, void *u) { m->finished = 1; } +static void menu_enter_language(component *c, void *userdata) { + scene *s = userdata; + menu_set_submenu(c->parent, menu_language_create(s)); +} + void menu_enter_input_1(component *c, void *userdata) { scene *s = userdata; menu_set_submenu(c->parent, menu_input_create(s, 1)); @@ -41,7 +47,8 @@ component *menu_configuration_create(scene *s) { component *menu = menu_create(11); menu_attach(menu, label_create(&tconf, "CONFIGURATION")); menu_attach(menu, filler_create()); - menu_attach(menu, filler_create()); + menu_attach(menu, + textbutton_create(&tconf, "LANGUAGE", "Forstar du ikke engelsk?", COM_ENABLED, menu_enter_language, s)); menu_attach(menu, textbutton_create(&tconf, "PLAYER 1 INPUT", "Choose the control for player 1: keyboard or joystick.", COM_ENABLED, menu_enter_input_1, s)); diff --git a/src/game/scenes/mainmenu/menu_language.c b/src/game/scenes/mainmenu/menu_language.c new file mode 100644 index 000000000..e919bfa3d --- /dev/null +++ b/src/game/scenes/mainmenu/menu_language.c @@ -0,0 +1,136 @@ +#include + +#include "game/scenes/mainmenu/menu_language.h" + +#include "game/gui/gui.h" +#include "game/utils/settings.h" +#include "resources/languages.h" +#include "resources/pathmanager.h" +#include "utils/allocator.h" +#include "utils/list.h" +#include "utils/log.h" +#include "utils/scandir.h" + +typedef struct { + char **language_filenames; + char **language_names; + int language_count; + int selected_language; +} language_menu_data; + +void menu_language_done(component *c, void *u) { + language_menu_data *local = menu_get_userdata(c->parent); + settings_language *l = &settings_get()->language; + + // Set menu as finished + menu *m = sizer_get_obj(c->parent); + m->finished = 1; + + if(strcmp(l->language, local->language_filenames[local->selected_language]) != 0) { + omf_free(l->language); + l->language = local->language_filenames[local->selected_language]; + local->language_filenames[local->selected_language] = NULL; + + // reload language + lang_close(); + lang_init(); + } +} + +void menu_language_free(component *c) { + language_menu_data *local = menu_get_userdata(c); + for(int l = 0; l < local->language_count; l++) { + omf_free(local->language_filenames[l]); + omf_free(local->language_names[l]); + } + omf_free(local->language_filenames); + omf_free(local->language_names); + omf_free(local); + menu_set_userdata(c, local); +} + +void menu_language_submenu_done(component *c, component *submenu) { + menu *m = sizer_get_obj(c); + m->finished = 1; +} + +component *menu_language_create(scene *s) { + // Menu userdata + language_menu_data *local = omf_calloc(1, sizeof(language_menu_data)); + + // Load settings etc. + settings *setting = settings_get(); + + // Find path to languages + const char *dirname = pm_get_local_path(RESOURCE_PATH); + if(dirname == NULL) { + PERROR("Could not find resources path for menu_language!"); + return NULL; + } + + list dirlist; + // Seek all files + list_create(&dirlist); + scan_directory(&dirlist, dirname); + local->language_filenames = omf_malloc(list_size(&dirlist) * sizeof(char *)); + local->language_names = omf_malloc(list_size(&dirlist) * sizeof(char *)); + local->language_count = 0; + + iterator it; + list_iter_begin(&dirlist, &it); + char const *filename; + while((filename = (char *)list_iter_next(&it))) { + char *ext = NULL; + + if(strcmp("ENGLISH.DAT", filename) != 0 && strcmp("GERMAN.DAT", filename) != 0 && + ((ext = strrchr(filename, '.')) == NULL || strcmp(".LNG", ext) != 0)) { + continue; + } + + if(strcmp(setting->language.language, filename) == 0) { + local->selected_language = local->language_count; + } + + int id = local->language_count++; + + // strip .DAT or .LNG + size_t filename_len = strlen(filename); + size_t name_len = filename_len - 4; + local->language_names[id] = omf_malloc(name_len + 1); + memcpy(local->language_names[id], filename, name_len); + local->language_names[id][name_len] = '\0'; + + // move filename into language_filenames + list_node *now = it.vnow; + local->language_filenames[id] = now->data; + now->data = NULL; + } + list_free(&dirlist); + + // Text config + text_settings tconf; + text_defaults(&tconf); + tconf.font = FONT_BIG; + tconf.halign = TEXT_CENTER; + tconf.cforeground = TEXT_MEDIUM_GREEN; + + // Create menu and its header + component *menu = menu_create(11); + menu_attach(menu, label_create(&tconf, "LANGUAGE")); + + menu_attach(menu, + textselector_create_bind_opts(&tconf, "", "Choose a Language.", NULL, NULL, &local->selected_language, + (char const **)local->language_names, local->language_count)); + + menu_attach(menu, filler_create()); + + // Done button + menu_attach(menu, + textbutton_create(&tconf, "DONE", "Return to the main menu.", COM_ENABLED, menu_language_done, s)); + + // Userdata & free function for it + menu_set_userdata(menu, local); + menu_set_free_cb(menu, menu_language_free); + menu_set_submenu_done_cb(menu, menu_language_submenu_done); + return menu; +} diff --git a/src/game/scenes/mainmenu/menu_language.h b/src/game/scenes/mainmenu/menu_language.h new file mode 100644 index 000000000..b8e749047 --- /dev/null +++ b/src/game/scenes/mainmenu/menu_language.h @@ -0,0 +1,9 @@ +#ifndef MENU_LANGUAGE_H +#define MENU_LANGUAGE_H + +#include "game/gui/component.h" +#include "game/protos/scene.h" + +component *menu_language_create(scene *s); + +#endif // MENU_LANGUAGE_H diff --git a/src/game/utils/settings.c b/src/game/utils/settings.c index 71b63daf0..b00af5fb8 100644 --- a/src/game/utils/settings.c +++ b/src/game/utils/settings.c @@ -55,6 +55,10 @@ typedef struct { } struct_to_field; // clang-format off +static const field f_language[] = { + F_STRING(settings_language, language, "ENGLISH.DAT"), +}; + const field f_video[] = { F_INT(settings_video, screen_w, 640), F_INT(settings_video, screen_h, 400), @@ -140,6 +144,7 @@ const field f_net[] = { // Map struct to field const struct_to_field struct_to_fields[] = { + S_2_F(&_settings.language, f_language), S_2_F(&_settings.video, f_video), S_2_F(&_settings.sound, f_sound), S_2_F(&_settings.gameplay, f_gameplay), diff --git a/src/game/utils/settings.h b/src/game/utils/settings.h index e0b8fd213..514fe71eb 100644 --- a/src/game/utils/settings.h +++ b/src/game/utils/settings.h @@ -34,6 +34,10 @@ typedef struct { int music_vol; } settings_sound; +typedef struct { + char *language; +} settings_language; + typedef struct { int screen_w; int screen_h; @@ -111,6 +115,7 @@ typedef struct { } settings_network; typedef struct { + settings_language language; settings_video video; settings_sound sound; settings_gameplay gameplay; diff --git a/src/resources/ids.c b/src/resources/ids.c index e22e07238..5b363af67 100644 --- a/src/resources/ids.c +++ b/src/resources/ids.c @@ -84,8 +84,6 @@ const char *get_resource_file(unsigned int id) { return "ARENA4.PSM"; case DAT_SOUNDS: return "SOUNDS.DAT"; - case DAT_ENGLISH: - return "ENGLISH.DAT"; case DAT_GRAPHCHR: return "GRAPHCHR.DAT"; case DAT_CHARSMAL: @@ -194,8 +192,6 @@ const char *get_resource_name(unsigned int id) { return "PSM_ARENA4"; case DAT_SOUNDS: return "DAT_SOUNDS"; - case DAT_ENGLISH: - return "DAT_ENGLISH"; case DAT_GRAPHCHR: return "DAT_GRAPHCHR"; case DAT_CHARSMAL: diff --git a/src/resources/ids.h b/src/resources/ids.h index e5eb5e730..e677d20f3 100644 --- a/src/resources/ids.h +++ b/src/resources/ids.h @@ -42,7 +42,6 @@ typedef enum resource_id PSM_ARENA3, PSM_ARENA4, DAT_SOUNDS, - DAT_ENGLISH, DAT_GRAPHCHR, DAT_CHARSMAL, DAT_ALTPALS, diff --git a/src/resources/languages.c b/src/resources/languages.c index b9fd35b25..aa6d3fe8c 100644 --- a/src/resources/languages.c +++ b/src/resources/languages.c @@ -1,15 +1,21 @@ #include "resources/languages.h" #include "formats/error.h" #include "formats/language.h" +#include "game/utils/settings.h" #include "resources/pathmanager.h" #include "utils/allocator.h" #include "utils/log.h" +#include "utils/str.h" #include static sd_language *language; int lang_init(void) { - const char *filename = pm_get_resource_path(DAT_ENGLISH); + str filename_str; + const char *dirname = pm_get_local_path(RESOURCE_PATH); + const char *lang = settings_get()->language.language; + str_from_format(&filename_str, "%s%s", dirname, lang); + char const *filename = str_c(&filename_str); // Load up language file language = omf_calloc(1, sizeof(sd_language)); @@ -58,6 +64,8 @@ int lang_init(void) { INFO("Loaded language file '%s'.", filename); + str_free(&filename_str); + // XXX we're wasting 32KB of memory on language->strings[...].description return 0; @@ -65,6 +73,7 @@ int lang_init(void) { error_0: sd_language_free(language); omf_free(language); + str_free(&filename_str); return 1; }