add ability to change language

This commit is contained in:
IntelOrca 2014-08-14 23:46:36 +01:00
parent b306e1e28c
commit e66b686f75
13 changed files with 3729 additions and 132 deletions

View File

@ -2774,9 +2774,9 @@ STR_2772 :Hogere spelsnelheid
STR_2773 :???
STR_2774 :???
STR_2775 :???
STR_2776 :???
STR_2777 :???
STR_2778 :???
STR_2776 :Language
STR_2777 :{MOVE_X}{SMALLFONT}{STRING}
STR_2778 :{RIGHTGUILLEMET}{MOVE_X}{SMALLFONT}{STRING}
STR_2779 :???
STR_2780 :???
STR_2781 :{STRINGID}:{MOVE_X}{195}{STRINGID}{STRINGID}

3447
data/language/english_uk.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2774,9 +2774,9 @@ STR_2772 :Faster Gamespeed
STR_2773 :???
STR_2774 :???
STR_2775 :???
STR_2776 :???
STR_2777 :???
STR_2778 :???
STR_2776 :Language
STR_2777 :{MOVE_X}{SMALLFONT}{STRING}
STR_2778 :{RIGHTGUILLEMET}{MOVE_X}{SMALLFONT}{STRING}
STR_2779 :???
STR_2780 :???
STR_2781 :{STRINGID}:{MOVE_X}{195}{STRINGID}{STRINGID}

View File

@ -29,6 +29,7 @@
<ClInclude Include="..\src\gfx.h" />
<ClInclude Include="..\src\graph.h" />
<ClInclude Include="..\src\intro.h" />
<ClInclude Include="..\src\language.h" />
<ClInclude Include="..\src\map.h" />
<ClInclude Include="..\src\marketing.h" />
<ClInclude Include="..\src\news_item.h" />
@ -73,6 +74,7 @@
<ClCompile Include="..\src\gfx.c" />
<ClCompile Include="..\src\graph.c" />
<ClCompile Include="..\src\intro.c" />
<ClCompile Include="..\src\language.c" />
<ClCompile Include="..\src\map.c" />
<ClCompile Include="..\src\marketing.c" />
<ClCompile Include="..\src\news_item.c" />

View File

@ -153,6 +153,9 @@
<ClInclude Include="..\src\graph.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\language.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\game.c">
@ -359,6 +362,9 @@
<ClCompile Include="..\src\window_new_campaign.c">
<Filter>Windows</Filter>
</ClCompile>
<ClCompile Include="..\src\language.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\openrct2.exe">

View File

@ -23,6 +23,7 @@
#include <ctype.h>
#include "addresses.h"
#include "config.h"
#include "language.h"
#include "rct2.h"
@ -90,6 +91,7 @@ general_configuration_t gGeneral_config_default = {
0, // show_height_as_units
1, // save_plugin_data
0, // fullscreen mode (default: windowed)
LANGUAGE_ENGLISH_UK
};
sound_configuration_t gSound_config;
@ -381,6 +383,8 @@ void config_write_ini_general(FILE *fp)
fprintf(fp, "fullscreen_mode = fullscreen\n");
else
fprintf(fp, "fullscreen_mode = borderless_fullscreen\n");
fprintf(fp, "language = %d\n", gGeneral_config.language);
}
/**
@ -621,6 +625,9 @@ static void config_general(char *setting, char *value){
else
gGeneral_config.fullscreen_mode = 2;
}
else if (strcmp(setting, "language") == 0) {
gGeneral_config.language = atoi(value);
}
}
/**

View File

@ -130,6 +130,7 @@ typedef struct general_configuration {
//new
uint8 fullscreen_mode;
uint16 language;
} general_configuration_t;
static const struct { char *key; int value; } _currencyLookupTable[] = {

165
src/language.c Normal file
View File

@ -0,0 +1,165 @@
/*****************************************************************************
* Copyright (c) 2014 Ted John
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* This file is part of OpenRCT2.
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#include <stdio.h>
#include "addresses.h"
#include "language.h"
#include "string_ids.h"
const char *language_names[LANGUAGE_COUNT] = {
"", // LANGUAGE_UNDEFINED
"English (UK)", // LANGUAGE_ENGLISH_UK
"English (US)", // LANGUAGE_ENGLISH_US
"Nederlands" // LANGUAGE_DUTCH
};
const char *language_filenames[LANGUAGE_COUNT] = {
"", // LANGUAGE_UNDEFINED
"english_uk", // LANGUAGE_ENGLISH_UK
"english_us", // LANGUAGE_ENGLISH_US
"dutch" // LANGUAGE_DUTCH
};
int gCurrentLanguage = LANGUAGE_UNDEFINED;
// Buffer storing all the string data
long language_buffer_size = 0;
char *language_buffer = NULL;
// List of string pointers into the string data
int language_num_strings = 0;
char **language_strings = NULL;
static int language_open_file(const char *filename);
const char *language_get_string(rct_string_id id)
{
const char *rct = RCT2_ADDRESS(0x009BF2D4, const char*)[id];
const char *openrct = language_strings == NULL ? NULL : language_strings[id];
const char *str = (openrct == NULL || strlen(openrct) == 0 ? rct : openrct);
return str == NULL ? "" : str;
}
int language_open(int id)
{
char filename[_MAX_PATH];
language_close();
if (id == LANGUAGE_UNDEFINED)
return 1;
sprintf(filename, "data/language/%s.txt", language_filenames[id]);
if (language_open_file(filename)) {
gCurrentLanguage = id;
return 1;
}
return 0;
}
/**
* Partial support to open a uncompiled language file which parses tokens and converts them to the corresponding character
* code. Due to resource strings (strings in scenarios and objects) being written to the original game's string table,
* get_string will use those if the same entry in the loaded language is empty.
*
* Unsure at how the original game decides which entries to write resource strings to, but this could affect adding new
* strings for the time being. Further investigation is required.
*
* Also note that all strings are currently still ASCII. It probably can't be converted to UTF-8 until all game functions that
* read / write strings in some way is decompiled. The original game used a DIY extended 8-bit extended ASCII set for special
* characters, format codes and accents.
*
* In terms of reading the language files, the STR_XXXX part is completely ignored at the moment. It just parses each line from
* the colon and thus not allowing gaps in the string indices.
*/
static int language_open_file(const char *filename)
{
FILE *f = fopen(filename, "rb");
if (f == NULL)
return 0;
fseek(f, 0, SEEK_END);
language_buffer_size = ftell(f);
language_buffer = calloc(1, language_buffer_size);
fseek(f, 0, SEEK_SET);
fread(language_buffer, language_buffer_size, 1, f);
fclose(f);
language_strings = calloc(STR_COUNT, sizeof(char*));
char *dst, *token;
char tokenBuffer[64];
int i, stringIndex = 0, mode = 0;
for (i = 0; i < language_buffer_size; i++) {
char *src = &language_buffer[i];
switch (mode) {
case 0:
// Search for colon
if (*src == ':') {
dst = src + 1;
language_strings[stringIndex++] = dst;
mode = 1;
}
break;
case 1:
// Copy string over, stop at line break
if (*src == '{') {
token = src + 1;
mode = 2;
} else if (*src == '\n' || *src == '\r') {
*dst = 0;
mode = 0;
} else {
*dst++ = *src;
}
break;
case 2:
// Read token, convert to code
if (*src == '}') {
int tokenLength = min(src - token, sizeof(tokenBuffer) - 1);
memcpy(tokenBuffer, token, tokenLength);
tokenBuffer[tokenLength] = 0;
char code = format_get_code(tokenBuffer);
if (code == 0)
code = atoi(tokenBuffer);
*dst++ = code;
mode = 1;
}
break;
}
}
language_num_strings = stringIndex;
return 1;
}
void language_close()
{
if (language_buffer != NULL)
free(language_buffer);
language_buffer_size = 0;
if (language_strings != NULL)
free(language_strings);
language_num_strings = 0;
gCurrentLanguage = LANGUAGE_UNDEFINED;
}

42
src/language.h Normal file
View File

@ -0,0 +1,42 @@
/*****************************************************************************
* Copyright (c) 2014 Ted John
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* This file is part of OpenRCT2.
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#ifndef _LANGUAGE_H_
#define _LANGUAGE_H_
#include "rct2.h"
#include "string_ids.h"
enum {
LANGUAGE_UNDEFINED,
LANGUAGE_ENGLISH_UK,
LANGUAGE_ENGLISH_US,
LANGUAGE_DUTCH,
LANGUAGE_COUNT
};
extern const char *language_names[LANGUAGE_COUNT];
extern int gCurrentLanguage;
const char *language_get_string(rct_string_id id);
int language_open(int id);
void language_close();
#endif

View File

@ -37,6 +37,7 @@
#include "game.h"
#include "gfx.h"
#include "intro.h"
#include "language.h"
#include "map.h"
#include "news_item.h"
#include "object.h"
@ -76,9 +77,6 @@ __declspec(dllexport) int StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInsta
{
print_launch_information();
// OpenRCT2 initialisation
language_open("data/language/english.txt");
// Begin RCT2
RCT2_GLOBAL(RCT2_ADDRESS_HINSTANCE, HINSTANCE) = hInstance;
RCT2_GLOBAL(RCT2_ADDRESS_CMDLINE, LPSTR) = lpCmdLine;
@ -88,6 +86,7 @@ __declspec(dllexport) int StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInsta
audio_get_devices();
RCT2_CALLPROC(0x0040502E); // get_dsound_devices()
config_init();
language_open(gGeneral_config.language);
rct2_init();
rct2_loop();
osinterface_free();

View File

@ -25,6 +25,7 @@
#include "currency.h"
#include "game.h"
#include "date.h"
#include "language.h"
#include "rct2.h"
#include "string_ids.h"
#include "util.h"
@ -1471,7 +1472,7 @@ void format_string_code(unsigned char format_code, char **dest, char **args)
value = *((uint16*)*args);
*args += 2;
strcpy(*dest, get_string(STR_MONTH_MARCH + date_get_month(value)));
strcpy(*dest, language_get_string(STR_MONTH_MARCH + date_get_month(value)));
*dest += strlen(*dest);
break;
case FORMAT_VELOCITY:
@ -1591,7 +1592,7 @@ void format_string_part(char **dest, rct_string_id format, char **args)
{
if (format < 0x8000) {
// Language string
format_string_part_from_raw(dest, get_string(format), args);
format_string_part_from_raw(dest, language_get_string(format), args);
} else if (format < 0x9000) {
// Custom string
format -= 0x8000;
@ -1690,108 +1691,4 @@ void reset_saved_strings() {
for (int i = 0; i < 1024; i++) {
RCT2_ADDRESS(0x135A8F4, uint8)[i * 32] = 0;
}
}
// Buffer storing all the string data
long language_buffer_size = 0;
char *language_buffer = NULL;
// List of string pointers into the string data
int language_num_strings = 0;
char **language_strings = NULL;
const char *get_string(rct_string_id id)
{
const char *rct = RCT2_ADDRESS(0x009BF2D4, const char*)[id];
const char *openrct = language_strings == NULL ? NULL : language_strings[id];
const char *str = (openrct == NULL || strlen(openrct) == 0 ? rct : openrct);
return str == NULL ? "" : str;
}
/**
* Partial support to open a uncompiled language file which parses tokens and converts them to the corresponding character
* code. Due to resource strings (strings in scenarios and objects) being written to the original game's string table,
* get_string will use those if the same entry in the loaded language is empty.
*
* Unsure at how the original game decides which entries to write resource strings to, but this could affect adding new
* strings for the time being. Further investigation is required.
*
* Also note that all strings are currently still ASCII. It probably can't be converted to UTF-8 until all game functions that
* read / write strings in some way is decompiled. The original game used a DIY extended 8-bit extended ASCII set for special
* characters, format codes and accents.
*
* In terms of reading the language files, the STR_XXXX part is completely ignored at the moment. It just parses each line from
* the colon and thus not allowing gaps in the string indices.
*/
int language_open(const char *filename)
{
FILE *f = fopen(filename, "rb");
if (f == NULL)
return 0;
fseek(f, 0, SEEK_END);
language_buffer_size = ftell(f);
language_buffer = calloc(1, language_buffer_size);
fseek(f, 0, SEEK_SET);
fread(language_buffer, language_buffer_size, 1, f);
fclose(f);
language_strings = calloc(STR_COUNT, sizeof(char*));
char *dst, *token;
char tokenBuffer[64];
int i, stringIndex = 0, mode = 0;
for (i = 0; i < language_buffer_size; i++) {
char *src = &language_buffer[i];
switch (mode) {
case 0:
// Search for colon
if (*src == ':') {
dst = src + 1;
language_strings[stringIndex++] = dst;
mode = 1;
}
break;
case 1:
// Copy string over, stop at line break
if (*src == '{') {
token = src + 1;
mode = 2;
} else if (*src == '\n' || *src == '\r') {
*dst = 0;
mode = 0;
} else {
*dst++ = *src;
}
break;
case 2:
// Read token, convert to code
if (*src == '}') {
int tokenLength = min(src - token, sizeof(tokenBuffer) - 1);
memcpy(tokenBuffer, token, tokenLength);
tokenBuffer[tokenLength] = 0;
char code = format_get_code(tokenBuffer);
if (code == 0)
code = atoi(tokenBuffer);
*dst++ = code;
mode = 1;
}
break;
}
}
language_num_strings = stringIndex;
return 1;
}
void language_close()
{
if (language_buffer != NULL)
free(language_buffer);
language_buffer_size = 0;
if (language_strings != NULL)
free(language_strings);
language_num_strings = 0;
}

View File

@ -28,9 +28,8 @@ void generate_string_file();
void reset_saved_strings();
void error_string_quit(int error, rct_string_id format);
const char *get_string(rct_string_id id);
int language_open(const char *filename);
void language_close();
char format_get_code(const char *token);
const char *format_get_token(char code);
enum {
// Font format codes

View File

@ -22,6 +22,7 @@
#include "audio.h"
#include "config.h"
#include "gfx.h"
#include "language.h"
#include "osinterface.h"
#include "string_ids.h"
#include "viewport.h"
@ -67,11 +68,14 @@ enum WINDOW_OPTIONS_WIDGET_IDX {
WIDX_HOTKEY_DROPDOWN,
WIDX_GENERAL_GROUP,
WIDX_REAL_NAME_CHECKBOX,
WIDX_SAVE_PLUGIN_DATA_CHECKBOX
WIDX_SAVE_PLUGIN_DATA_CHECKBOX,
WIDX_LANGUAGE_GROUP,
WIDX_LANGUAGE_DROPDOWN,
WIDX_LANGUAGE_DROPDOWN_BUTTON
};
#define WW 310
#define WH 399
#define WH 437
static rct_widget window_options_widgets[] = {
{ WWT_FRAME, 0, 0, WW - 1, 0, WH - 1, STR_NONE, STR_NONE },
@ -110,6 +114,9 @@ static rct_widget window_options_widgets[] = {
{ WWT_GROUPBOX, 0, 3, 306, 344, 392, STR_GENERAL, STR_NONE },
{ WWT_CHECKBOX, 2, 10, 299, 358, 369, STR_REAL_NAME, STR_REAL_NAME_TIP },
{ WWT_CHECKBOX, 2, 10, 299, 372, 384, STR_SAVE_PLUGIN_DATA, STR_SAVE_PLUGIN_DATA_TIP },
{ WWT_GROUPBOX, 0, 3, 306, 399, 430, 2776, STR_NONE },
{ WWT_DROPDOWN, 0, 10, 299, 413, 424, STR_NONE, STR_NONE }, // language
{ WWT_DROPDOWN_BUTTON, 0, 288, 298, 414, 423, 0x36C, STR_NONE },
{ WIDGETS_END },
};
@ -119,7 +126,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
static void window_options_dropdown();
static void window_options_invalidate();
static void window_options_paint();
static void window_options_draw_dropdown_box(rct_window *w, rct_widget *widget, int num_items);
static void window_options_show_dropdown(rct_window *w, rct_widget *widget, int num_items);
static void window_options_update_height_markers();
static void* window_options_events[] = {
@ -326,7 +333,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[i] = 1170 | ((uint64)(intptr_t)gAudioDevices[i].name << 16);
}
window_options_draw_dropdown_box(w, widget, gAudioDeviceCount);
window_options_show_dropdown(w, widget, gAudioDeviceCount);
gDropdownItemsChecked |= (1 << RCT2_GLOBAL(0x9AF280, uint32));
break;
@ -336,7 +343,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[0] = STR_UNITS;
gDropdownItemsArgs[1] = STR_REAL_VALUES;
window_options_draw_dropdown_box(w, widget, 2);
window_options_show_dropdown(w, widget, 2);
gDropdownItemsChecked =
(RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_FLAGS, uint8) &
@ -348,7 +355,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[0] = STR_OFF;
gDropdownItemsArgs[1] = STR_ON;
window_options_draw_dropdown_box(w, widget, 2);
window_options_show_dropdown(w, widget, 2);
gDropdownItemsChecked = 1 << RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_MUSIC, uint8);
break;
@ -360,7 +367,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[i] = STR_SOUND_LOW + i; // low, medium, high
}
window_options_draw_dropdown_box(w, widget, num_items);
window_options_show_dropdown(w, widget, num_items);
gDropdownItemsChecked = 1 << RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_SOUND_QUALITY, uint8);
break;
@ -372,7 +379,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[i] = STR_POUNDS + i; // all different currencies
}
window_options_draw_dropdown_box(w, widget, num_items);
window_options_show_dropdown(w, widget, num_items);
gDropdownItemsChecked = 1 << (RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_CURRENCY, uint8) & 0x3F);
break;
@ -382,7 +389,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[0] = STR_IMPERIAL;
gDropdownItemsArgs[1] = STR_METRIC;
window_options_draw_dropdown_box(w, widget, 2);
window_options_show_dropdown(w, widget, 2);
gDropdownItemsChecked = 1 << RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_METRIC, uint8);
break;
@ -397,7 +404,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[1] = STR_FAHRENHEIT;
gDropdownItemsArgs[2] = STR_METRIC;
window_options_draw_dropdown_box(w, widget, 3);
window_options_show_dropdown(w, widget, 3);
gDropdownItemsChecked = 1 << gGeneral_config.fullscreen_mode;
break;
@ -407,7 +414,7 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[0] = STR_CELSIUS;
gDropdownItemsArgs[1] = STR_FAHRENHEIT;
window_options_draw_dropdown_box(w, widget, 2);
window_options_show_dropdown(w, widget, 2);
gDropdownItemsChecked = 1 << RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_TEMPERATURE, uint8);
break;
@ -417,10 +424,18 @@ static void window_options_mousedown(int widgetIndex, rct_window*w, rct_widget*
gDropdownItemsArgs[0] = STR_WHITE;
gDropdownItemsArgs[1] = STR_TRANSLUCENT;
window_options_draw_dropdown_box(w, widget, 2);
window_options_show_dropdown(w, widget, 2);
gDropdownItemsChecked = 1 << RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_CONSTRUCTION_MARKER, uint8);
break;
case WIDX_LANGUAGE_DROPDOWN_BUTTON:
for (i = 1; i < LANGUAGE_COUNT; i++) {
gDropdownItemsFormat[i - 1] = 2777;
gDropdownItemsArgs[i - 1] = (sint64)language_names[i];
}
window_options_show_dropdown(w, widget, LANGUAGE_COUNT - 1);
gDropdownItemsChecked = 1 << (gCurrentLanguage - 1);
break;
}
}
@ -544,6 +559,14 @@ static void window_options_dropdown()
gfx_invalidate_screen();
}
break;
case WIDX_LANGUAGE_DROPDOWN_BUTTON:
if (dropdownIndex != gCurrentLanguage - 1) {
language_open(dropdownIndex + 1);
gGeneral_config.language = dropdownIndex + 1;
config_save();
gfx_invalidate_screen();
}
break;
}
}
@ -708,10 +731,19 @@ static void window_options_paint()
w->y + window_options_widgets[WIDX_MUSIC].top + 1);
gfx_draw_string_left(dpi, STR_SOUND_QUALITY, w, 0, w->x + 10,
w->y + window_options_widgets[WIDX_SOUND_QUALITY].top + 1);
// language
gfx_draw_string(
dpi,
(char*)language_names[gCurrentLanguage],
12,
w->x + window_options_widgets[WIDX_LANGUAGE_DROPDOWN].left + 1,
w->y + window_options_widgets[WIDX_LANGUAGE_DROPDOWN].top
);
}
// helper function, all dropdown boxes have similar properties
static void window_options_draw_dropdown_box(rct_window *w, rct_widget *widget, int num_items)
static void window_options_show_dropdown(rct_window *w, rct_widget *widget, int num_items)
{
window_dropdown_show_text_custom_width(
w->x + widget->left,
@ -721,7 +753,7 @@ static void window_options_draw_dropdown_box(rct_window *w, rct_widget *widget,
0x80,
num_items,
widget->right - widget->left - 3
);
);
}
static void window_options_update_height_markers()