belugas@3601: /* $Id$ */ belugas@3601: belugas@3601: /** @file belugas@3601: * Implementation of Action 04 "universal holder" structure and functions. belugas@3601: * This file implements a linked-lists of strings, belugas@3601: * holding everything that the newgrf action 04 will send over to OpenTTD. belugas@3601: * One of the biggest problems is that Dynamic lang Array uses ISO codes belugas@3601: * as way to identifying current user lang, while newgrf uses bit shift codes belugas@3601: * not related to ISO. So equivalence functionnality had to be set. belugas@3601: */ belugas@3601: belugas@3601: #include "stdafx.h" belugas@3601: #include "debug.h" belugas@3601: #include "openttd.h" belugas@3601: #include "string.h" peter1138@3821: #include "strings.h" belugas@3601: #include "variables.h" belugas@3601: #include "macros.h" belugas@3601: #include "table/strings.h" belugas@3601: #include "newgrf_text.h" belugas@3601: belugas@3601: #define GRFTAB 28 belugas@3601: #define TABSIZE 11 belugas@3601: belugas@3601: /** belugas@3601: * Explains the newgrf shift bit positionning. belugas@3601: * the grf base will not be used in order to find the string, but rather for belugas@3601: * jumping from standard langID scheme to the new one. belugas@3601: */ belugas@3601: typedef enum grf_base_languages { belugas@3601: GRFLB_AMERICAN = 0x01, belugas@3601: GRFLB_ENGLISH = 0x02, belugas@3601: GRFLB_GERMAN = 0x04, belugas@3601: GRFLB_FRENCH = 0x08, belugas@3601: GRFLB_SPANISH = 0x10, belugas@3601: GRFLB_GENERIC = 0x80, belugas@3601: } grf_base_language; belugas@3601: belugas@3601: typedef enum grf_extended_languages { belugas@3601: GRFLX_AMERICAN = 0x00, belugas@3601: GRFLX_ENGLISH = 0x01, belugas@3601: GRFLX_GERMAN = 0x02, belugas@3601: GRFLX_FRENCH = 0x03, belugas@3601: GRFLX_SPANISH = 0x04, belugas@3601: GRFLX_RUSSIAN = 0x07, belugas@3601: GRFLX_CZECH = 0x15, belugas@3601: GRFLX_SLOVAK = 0x16, belugas@3856: GRFLX_AFRIKAANS = 0x1B, belugas@3856: GRFLX_GREEK = 0x1E, peter1138@3605: GRFLX_DUTCH = 0x1F, belugas@3601: GRFLX_CATALAN = 0x22, belugas@3601: GRFLX_HUNGARIAN = 0x24, belugas@3601: GRFLX_ITALIAN = 0x27, belugas@3601: GRFLX_ROMANIAN = 0x28, belugas@3601: GRFLX_ICELANDIC = 0x29, belugas@3601: GRFLX_LATVIAN = 0x2A, belugas@3601: GRFLX_LITHUANIAN = 0x2B, belugas@3601: GRFLX_SLOVENIAN = 0x2C, belugas@3601: GRFLX_DANISH = 0x2D, peter1138@3605: GRFLX_SWEDISH = 0x2E, belugas@3601: GRFLX_NORWEGIAN = 0x2F, belugas@3601: GRFLX_POLISH = 0x30, belugas@3601: GRFLX_GALICIAN = 0x31, belugas@3601: GRFLX_FRISIAN = 0x32, belugas@3856: GRFLX_UKRAINIAN = 0x33, belugas@3601: GRFLX_ESTONIAN = 0x34, belugas@3601: GRFLX_FINNISH = 0x35, belugas@3601: GRFLX_PORTUGUESE = 0x36, peter1138@3605: GRFLX_BRAZILIAN = 0x37, belugas@3856: GRFLX_CROATIAN = 0x38, belugas@3601: GRFLX_TURKISH = 0x3E, peter1138@3641: GRFLX_UNSPECIFIED = 0x7F, belugas@3601: } grf_language; belugas@3601: belugas@3601: peter1138@3603: typedef struct iso_grf { belugas@3601: char code[6]; belugas@3601: byte grfLangID; peter1138@3603: } iso_grf; belugas@3601: belugas@3601: /** belugas@3601: * ISO code VS NewGrf langID conversion array. belugas@3601: * This array is used in two ways: belugas@3601: * 1-its ISO part is matching OpenTTD dynamic language id belugas@3601: * with newgrf bit positionning language id belugas@3601: * 2-its shift part is used to know what is the shift to belugas@3601: * watch for when inserting new strings, hence analysing newgrf langid belugas@3601: */ peter1138@3605: const iso_grf iso_codes[] = { belugas@3601: {"en_US", GRFLX_AMERICAN}, belugas@3601: {"en_GB", GRFLX_ENGLISH}, belugas@3856: {"de_DE", GRFLX_GERMAN}, belugas@3856: {"fr_FR", GRFLX_FRENCH}, belugas@3856: {"es_ES", GRFLX_SPANISH}, belugas@3856: {"af_ZA", GRFLX_AFRIKAANS}, belugas@3856: {"hr_HR", GRFLX_CROATIAN}, belugas@3856: {"cs_CS", GRFLX_CZECH}, belugas@3856: {"ca_ES", GRFLX_CATALAN}, belugas@3856: {"da_DA", GRFLX_DANISH}, belugas@3856: {"nl_NL", GRFLX_DUTCH}, belugas@3856: {"et_ET", GRFLX_ESTONIAN}, belugas@3856: {"fi_FI", GRFLX_FINNISH}, belugas@3856: {"fy_NL", GRFLX_FRISIAN}, belugas@3856: {"gl_ES", GRFLX_GALICIAN}, belugas@3856: {"el_GR", GRFLX_GREEK}, belugas@3856: {"hu_HU", GRFLX_HUNGARIAN}, belugas@3856: {"is_IS", GRFLX_ICELANDIC}, belugas@3856: {"it_IT", GRFLX_ITALIAN}, belugas@3856: {"lv_LV", GRFLX_LATVIAN}, belugas@3856: {"lt_LT", GRFLX_LITHUANIAN}, belugas@3856: {"nb_NO", GRFLX_NORWEGIAN}, belugas@3856: {"pl_PL", GRFLX_POLISH}, belugas@3856: {"pt_PT", GRFLX_PORTUGUESE}, peter1138@3605: {"pt_BR", GRFLX_BRAZILIAN}, belugas@3856: {"ro_RO", GRFLX_ROMANIAN}, belugas@3856: {"ru_RU", GRFLX_RUSSIAN}, belugas@3856: {"sk_SK", GRFLX_SLOVAK}, belugas@3856: {"sl_SL", GRFLX_SLOVENIAN}, belugas@3856: {"sv_SE", GRFLX_SWEDISH}, belugas@3856: {"tr_TR", GRFLX_TURKISH}, belugas@3856: {"uk_UA", GRFLX_UKRAINIAN}, belugas@3601: {"gen", GRFLB_GENERIC} //this is not iso code, but there has to be something... belugas@3601: }; belugas@3601: belugas@3601: belugas@3601: static uint _num_grf_texts = 0; belugas@3601: static GRFTextEntry _grf_text[(1 << TABSIZE) * 3]; belugas@3601: static byte _currentLangID = GRFLX_ENGLISH; //by default, english is used. belugas@3601: belugas@3601: peter1138@3646: static void TranslateTTDPatchCodes(char *str) peter1138@3646: { peter1138@3646: char *c; peter1138@3646: peter1138@3646: for (c = str; *c != '\0'; c++) { peter1138@3646: switch ((byte)*c) { peter1138@3646: case 0x01: c++; break; peter1138@3646: case 0x0D: *c = 10; break; peter1138@3646: case 0x0E: *c = 8; break; peter1138@3646: case 0x0F: *c = 9; break; peter1138@3646: case 0x1F: *c = 2; c += 2; break; peter1138@3646: case 0x7B: peter1138@3646: case 0x7C: peter1138@3646: case 0x7D: peter1138@3646: case 0x7E: *c = 0x8E; break; peter1138@3646: case 0x81: c += 2; break; peter1138@3646: case 0x85: *c = 0x86; break; peter1138@3646: case 0x88: *c = 15; break; peter1138@3646: case 0x89: *c = 16; break; peter1138@3646: case 0x8A: *c = 17; break; peter1138@3646: case 0x8B: *c = 18; break; peter1138@3646: case 0x8C: *c = 19; break; peter1138@3646: case 0x8D: *c = 20; break; peter1138@3646: case 0x8E: *c = 21; break; peter1138@3646: case 0x8F: *c = 22; break; peter1138@3646: case 0x90: *c = 23; break; peter1138@3646: case 0x91: *c = 24; break; peter1138@3646: case 0x92: *c = 25; break; peter1138@3646: case 0x93: *c = 26; break; peter1138@3646: case 0x94: *c = 27; break; peter1138@3646: case 0x95: *c = 28; break; peter1138@3646: case 0x96: *c = 29; break; peter1138@3646: case 0x97: *c = 30; break; peter1138@3646: case 0x98: *c = 31; break; peter1138@3646: default: peter1138@3646: /* Validate any unhandled character */ peter1138@3646: if (!IsValidAsciiChar(*c)) *c = '?'; peter1138@3646: break; peter1138@3646: } peter1138@3646: } peter1138@3646: } peter1138@3646: peter1138@3646: belugas@3601: /** peter1138@3641: * Add the new read string into our structure. belugas@3601: */ peter1138@3821: StringID AddGRFString(uint32 grfid, uint16 stringid, byte langid_to_add, bool new_scheme, const char *text_to_add, StringID def_string) belugas@3601: { peter1138@3603: GRFText *newtext; belugas@3601: uint id; belugas@3601: peter1138@3641: /* When working with the old language scheme (grf_version is less than 7) and peter1138@3603: * English or American is among the set bits, simply add it as English in peter1138@3603: * the new scheme, i.e. as langid = 1. peter1138@3603: * If English is set, it is pretty safe to assume the translations are not peter1138@3603: * actually translated. peter1138@3603: */ peter1138@3641: if (!new_scheme) { peter1138@3603: if (HASBITS(langid_to_add, GRFLB_AMERICAN | GRFLB_ENGLISH)) { peter1138@3603: langid_to_add = GRFLX_ENGLISH; peter1138@3603: } else { peter1138@3603: StringID ret = STR_EMPTY; peter1138@3821: if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, 1 << 6 | GRFLX_GERMAN, true, text_to_add, def_string); peter1138@3821: if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, 1 << 6 | GRFLX_FRENCH, true, text_to_add, def_string); peter1138@3821: if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, 1 << 6 | GRFLX_SPANISH, true, text_to_add, def_string); peter1138@3603: return ret; peter1138@3603: } peter1138@3603: } belugas@3601: belugas@3601: for (id = 0; id < _num_grf_texts; id++) { belugas@3601: if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) { belugas@3601: break; belugas@3601: } belugas@3601: } belugas@3601: belugas@3601: /* Too many strings allocated, return empty */ belugas@3601: if (id == lengthof(_grf_text)) return STR_EMPTY; belugas@3601: belugas@3856: newtext = calloc(1, sizeof(*newtext)); belugas@3856: newtext->langid = GB(langid_to_add, 0, 6); belugas@3856: newtext->text = strdup(text_to_add); belugas@3856: newtext->next = NULL; belugas@3856: belugas@3856: TranslateTTDPatchCodes(newtext->text); belugas@3856: peter1138@3603: /* If we didn't find our stringid and grfid in the list, allocate a new id */ belugas@3601: if (id == _num_grf_texts) _num_grf_texts++; belugas@3601: belugas@3601: if (_grf_text[id].textholder == NULL) { belugas@3601: _grf_text[id].grfid = grfid; belugas@3601: _grf_text[id].stringid = stringid; peter1138@3821: _grf_text[id].def_string = def_string; belugas@3601: _grf_text[id].textholder = newtext; belugas@3601: } else { belugas@3601: GRFText *textptr = _grf_text[id].textholder; belugas@3601: while (textptr->next != NULL) textptr = textptr->next; belugas@3601: textptr->next = newtext; belugas@3601: } belugas@3601: Darkvater@3630: DEBUG(grf, 2)("Added 0x%X: grfid 0x%X string 0x%X lang 0x%X string %s", id, grfid, stringid, newtext->langid, newtext->text); belugas@3601: belugas@3601: return (GRFTAB << TABSIZE) + id; belugas@3601: } belugas@3601: belugas@3601: belugas@3601: /** belugas@3601: * Returns the index for this stringid associated with its grfID belugas@3601: */ belugas@3601: StringID GetGRFStringID(uint32 grfid, uint16 stringid) belugas@3601: { belugas@3601: uint id; belugas@3601: for (id = 0; id < _num_grf_texts; id++) { belugas@3601: if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) { belugas@3601: return (GRFTAB << TABSIZE) + id; belugas@3601: } belugas@3601: } belugas@3601: belugas@3601: return STR_UNDEFINED; belugas@3601: } belugas@3601: belugas@3601: belugas@3601: char *GetGRFString(char *buff, uint16 stringid) belugas@3601: { belugas@3601: GRFText *search_text; peter1138@3821: GRFText *default_text = NULL; belugas@3601: belugas@3601: assert(_grf_text[stringid].grfid != 0); belugas@3601: /*Search the list of lang-strings of this stringid for current lang */ belugas@3601: for (search_text = _grf_text[stringid].textholder; search_text != NULL; search_text = search_text->next) { peter1138@3821: if (search_text->langid == _currentLangID) { belugas@3601: return strecpy(buff, search_text->text, NULL); belugas@3601: } peter1138@3821: peter1138@3821: /* If the current string is English or American, set it as the peter1138@3821: * fallback language if the specific language isn't available. */ peter1138@3821: if (search_text->langid == GRFLX_ENGLISH || search_text->langid == GRFLX_AMERICAN) { peter1138@3821: default_text = search_text; peter1138@3821: } belugas@3601: } belugas@3601: peter1138@3821: /* If there is a fallback string, return that */ peter1138@3821: if (default_text != NULL) return strecpy(buff, default_text->text, NULL); peter1138@3821: peter1138@3821: /* Use the default string ID if the fallback string isn't available */ peter1138@3821: return GetString(buff, _grf_text[stringid].def_string); belugas@3601: } belugas@3601: belugas@3601: /** belugas@3601: * Equivalence Setter function between game and newgrf langID. belugas@3601: * This function will adjust _currentLangID as to what is the LangID belugas@3601: * of the current language set by the user. belugas@3601: * The array iso_codes will be used to find that match. belugas@3601: * If not found, it will have to be standard english belugas@3601: * This function is called after the user changed language, belugas@3601: * from strings.c:ReadLanguagePack belugas@3601: * @param iso code of current selection belugas@3601: */ peter1138@3605: void SetCurrentGrfLangID(const char *iso_name) belugas@3601: { belugas@3601: byte ret,i; belugas@3601: peter1138@3605: /* Use English by default, if we can't match up the iso_code. */ peter1138@3605: ret = GRFLX_ENGLISH; belugas@3601: peter1138@3605: for (i=0; i < lengthof(iso_codes); i++) { peter1138@3868: if (strncmp(iso_codes[i].code, iso_name, strlen(iso_codes[i].code)) == 0) { peter1138@3605: /* We found a match, so let's use it. */ peter1138@3605: ret = i; belugas@3601: break; belugas@3601: } belugas@3601: } belugas@3601: _currentLangID = ret; belugas@3601: } belugas@3601: belugas@3601: /** belugas@3601: * House cleaning. peter1138@3602: * Remove all strings and reset the text counter. belugas@3601: */ belugas@3601: void CleanUpStrings(void) belugas@3601: { belugas@3601: uint id; belugas@3601: belugas@3601: for (id = 0; id < _num_grf_texts; id++) { peter1138@3602: GRFText *grftext = _grf_text[id].textholder; peter1138@3602: while (grftext != NULL) { peter1138@3602: GRFText *grftext2 = grftext->next; peter1138@3602: free(grftext->text); peter1138@3602: free(grftext); peter1138@3602: grftext = grftext2; peter1138@3602: } peter1138@3602: _grf_text[id].grfid = 0; peter1138@3602: _grf_text[id].stringid = 0; peter1138@3602: _grf_text[id].textholder = NULL; belugas@3601: } peter1138@3602: peter1138@3602: _num_grf_texts = 0; belugas@3601: }