belugas@3601: /* $Id$ */ belugas@3601: belugas@6674: /** @file newgrf_text.cpp 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 "variables.h" Darkvater@5568: #include "newgrf.h" belugas@3601: #include "newgrf_text.h" rubidium@8610: #include "strings_func.h" rubidium@8709: #include "core/alloc_func.hpp" rubidium@8709: #include "newgrf_storage.h" rubidium@8710: #include "string_func.h" rubidium@9283: #include "date_type.h" belugas@3601: rubidium@8760: #include "table/strings.h" rubidium@8760: #include "table/control_codes.h" rubidium@8760: 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: */ rubidium@6574: 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, rubidium@6574: }; belugas@3601: rubidium@6574: 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@7060: GRFLX_ESPERANTO = 0x05, belugas@3601: GRFLX_RUSSIAN = 0x07, belugas@3601: GRFLX_CZECH = 0x15, belugas@3601: GRFLX_SLOVAK = 0x16, belugas@7060: GRFLX_BULGARIAN = 0x18, 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@7060: GRFLX_JAPANESE = 0x39, belugas@7060: GRFLX_KOREAN = 0x3A, belugas@3601: GRFLX_TURKISH = 0x3E, peter1138@3641: GRFLX_UNSPECIFIED = 0x7F, rubidium@6574: }; belugas@3601: belugas@3601: rubidium@6574: struct iso_grf { belugas@3601: char code[6]; belugas@3601: byte grfLangID; rubidium@6574: }; 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}, glx@7933: {"cs_CZ", 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@7060: {"eo_EO", GRFLX_ESPERANTO}, belugas@7060: {"bg_BG", GRFLX_BULGARIAN}, belugas@7060: {"ja_JP", GRFLX_JAPANESE}, belugas@7060: {"ko_KR", GRFLX_KOREAN}, belugas@6674: {"gen", GRFLB_GENERIC} ///< this is not iso code, but there has to be something... belugas@3601: }; belugas@3601: belugas@3601: tron@4305: /** tron@4305: * Element of the linked list. tron@4305: * Each of those elements represent the string, tron@4305: * but according to a different lang. tron@4305: */ tron@5906: struct GRFText { tron@5906: public: tron@5906: static GRFText* New(byte langid, const char* text) tron@5906: { tron@5906: return new(strlen(text) + 1) GRFText(langid, text); tron@5906: } tron@5906: tron@5906: private: tron@5906: GRFText(byte langid_, const char* text_) : next(NULL), langid(langid_) tron@5906: { tron@5906: strcpy(text, text_); tron@5906: } tron@5906: tron@5906: void* operator new(size_t size, size_t extra) tron@5906: { tron@5906: return ::operator new(size + extra); tron@5906: } tron@5906: KUDr@5908: public: KUDr@5908: /* dummy operator delete to silence VC8: KUDr@5908: * 'void *GRFText::operator new(size_t,size_t)' : no matching operator delete found; KUDr@5908: * memory will not be freed if initialization throws an exception */ KUDr@5908: void operator delete(void *p, size_t extra) KUDr@5908: { KUDr@5908: return ::operator delete(p); KUDr@5908: } KUDr@5908: tron@5906: public: tron@5906: GRFText *next; tron@5906: byte langid; tron@5906: char text[VARARRAY_SIZE]; tron@5906: }; tron@4305: tron@4305: tron@4305: /** tron@4305: * Holder of the above structure. tron@4305: * Putting both grfid and stringid together allows us to avoid duplicates, tron@4305: * since it is NOT SUPPOSED to happen. tron@4305: */ rubidium@6574: struct GRFTextEntry { tron@4305: uint32 grfid; tron@4305: uint16 stringid; tron@4305: StringID def_string; tron@4305: GRFText *textholder; rubidium@6574: }; tron@4305: tron@4305: belugas@3601: static uint _num_grf_texts = 0; belugas@3601: static GRFTextEntry _grf_text[(1 << TABSIZE) * 3]; belugas@6674: static byte _currentLangID = GRFLX_ENGLISH; ///< by default, english is used. belugas@3601: belugas@3601: peter1138@9152: char *TranslateTTDPatchCodes(uint32 grfid, const char *str) peter1138@3646: { belugas@6674: char *tmp = MallocT(strlen(str) * 10 + 1); // Allocate space to allow for expansion peter1138@5108: char *d = tmp; peter1138@5108: bool unicode = false; peter1138@5208: WChar c; peter1138@5208: size_t len = Utf8Decode(&c, str); peter1138@3646: peter1138@5108: if (c == 0x00DE) { peter1138@5108: /* The thorn ('รพ') indicates a unicode string to TTDPatch */ peter1138@5108: unicode = true; peter1138@5208: str += len; peter1138@5108: } peter1138@5108: peter1138@5108: for (;;) { rubidium@8557: if (unicode && Utf8EncodedCharLen(*str) != 0) { rubidium@8557: c = Utf8Consume(&str); rubidium@8557: /* 'Magic' range of control codes. */ rubidium@8557: if (GB(c, 8, 8) == 0xE0) c = GB(c, 0, 8); rubidium@8557: } else { rubidium@8557: c = (byte)*str++; rubidium@8557: } peter1138@5108: if (c == 0) break; peter1138@5108: peter1138@5108: switch (c) { peter1138@5108: case 0x01: peter1138@5108: d += Utf8Encode(d, SCC_SETX); peter1138@5108: *d++ = *str++; peter1138@5108: break; Darkvater@5344: case 0x0A: break; Darkvater@5344: case 0x0D: *d++ = 0x0A; break; peter1138@5108: case 0x0E: d += Utf8Encode(d, SCC_TINYFONT); break; peter1138@5108: case 0x0F: d += Utf8Encode(d, SCC_BIGFONT); break; peter1138@5108: case 0x1F: peter1138@5108: d += Utf8Encode(d, SCC_SETXY); peter1138@5108: *d++ = *str++; peter1138@5108: *d++ = *str++; peter1138@5108: break; peter1138@3646: case 0x7B: peter1138@3646: case 0x7C: peter1138@3646: case 0x7D: rubidium@8112: case 0x7E: rubidium@8112: case 0x7F: rubidium@8112: case 0x80: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD + c - 0x7B); break; peter1138@5108: case 0x81: { peter1138@5108: StringID string; peter1138@5108: string = *str++; peter1138@5108: string |= *str++ << 8; peter1138@5108: d += Utf8Encode(d, SCC_STRING_ID); peter1138@9152: d += Utf8Encode(d, MapGRFStringID(grfid, string)); peter1138@5108: break; peter1138@5108: } rubidium@8112: case 0x82: rubidium@8112: case 0x83: rubidium@8112: case 0x84: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_SPEED + c - 0x82); break; rubidium@8112: case 0x85: d += Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break; rubidium@8112: case 0x86: d += Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break; rubidium@8112: case 0x87: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_LITRES); break; peter1138@5108: case 0x88: d += Utf8Encode(d, SCC_BLUE); break; peter1138@5108: case 0x89: d += Utf8Encode(d, SCC_SILVER); break; peter1138@5108: case 0x8A: d += Utf8Encode(d, SCC_GOLD); break; peter1138@5108: case 0x8B: d += Utf8Encode(d, SCC_RED); break; peter1138@5108: case 0x8C: d += Utf8Encode(d, SCC_PURPLE); break; peter1138@5108: case 0x8D: d += Utf8Encode(d, SCC_LTBROWN); break; peter1138@5108: case 0x8E: d += Utf8Encode(d, SCC_ORANGE); break; peter1138@5108: case 0x8F: d += Utf8Encode(d, SCC_GREEN); break; peter1138@5108: case 0x90: d += Utf8Encode(d, SCC_YELLOW); break; peter1138@5108: case 0x91: d += Utf8Encode(d, SCC_DKGREEN); break; peter1138@5108: case 0x92: d += Utf8Encode(d, SCC_CREAM); break; peter1138@5108: case 0x93: d += Utf8Encode(d, SCC_BROWN); break; peter1138@5108: case 0x94: d += Utf8Encode(d, SCC_WHITE); break; peter1138@5108: case 0x95: d += Utf8Encode(d, SCC_LTBLUE); break; peter1138@5108: case 0x96: d += Utf8Encode(d, SCC_GRAY); break; peter1138@5108: case 0x97: d += Utf8Encode(d, SCC_DKBLUE); break; peter1138@5108: case 0x98: d += Utf8Encode(d, SCC_BLACK); break; rubidium@8112: case 0x9A: rubidium@8112: switch (*str++) { rubidium@8112: case 0: /* FALL THROUGH */ rubidium@8112: case 1: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break; rubidium@8112: case 3: { rubidium@8112: uint16 tmp = *str++; rubidium@8112: tmp |= (*str++) << 8; rubidium@8112: d += Utf8Encode(d, SCC_NEWGRF_PUSH_WORD); d += Utf8Encode(d, tmp); rubidium@8112: } break; rubidium@8112: case 4: d += Utf8Encode(d, SCC_NEWGRF_UNPRINT); d += Utf8Encode(d, *str++); break; rubidium@8112: default: grfmsg(1, "missing handler for extended format code"); break; rubidium@8112: } rubidium@8112: break; rubidium@8112: peter1138@5108: case 0x9E: d += Utf8Encode(d, 0x20AC); break; // Euro peter1138@5108: case 0x9F: d += Utf8Encode(d, 0x0178); break; // Y with diaeresis peter1138@5108: case 0xA0: d += Utf8Encode(d, SCC_UPARROW); break; peter1138@5108: case 0xAA: d += Utf8Encode(d, SCC_DOWNARROW); break; peter1138@5108: case 0xAC: d += Utf8Encode(d, SCC_CHECKMARK); break; peter1138@5108: case 0xAD: d += Utf8Encode(d, SCC_CROSS); break; peter1138@5108: case 0xAF: d += Utf8Encode(d, SCC_RIGHTARROW); break; peter1138@5108: case 0xB4: d += Utf8Encode(d, SCC_TRAIN); break; peter1138@5108: case 0xB5: d += Utf8Encode(d, SCC_LORRY); break; peter1138@5108: case 0xB6: d += Utf8Encode(d, SCC_BUS); break; peter1138@5108: case 0xB7: d += Utf8Encode(d, SCC_PLANE); break; peter1138@5108: case 0xB8: d += Utf8Encode(d, SCC_SHIP); break; peter1138@8907: case 0xB9: d += Utf8Encode(d, SCC_SUPERSCRIPT_M1); break; peter1138@8907: case 0xBC: d += Utf8Encode(d, SCC_SMALLUPARROW); break; peter1138@8907: case 0xBD: d += Utf8Encode(d, SCC_SMALLDOWNARROW); break; peter1138@3646: default: peter1138@3646: /* Validate any unhandled character */ peter1138@5108: if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; peter1138@5108: d += Utf8Encode(d, c); peter1138@3646: break; peter1138@3646: } peter1138@3646: } peter1138@5108: peter1138@5108: *d = '\0'; KUDr@5860: tmp = ReallocT(tmp, strlen(tmp) + 1); rubidium@5838: return tmp; 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@5108: char *translatedtext; 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@5047: if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, text_to_add, def_string); peter1138@5047: if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, text_to_add, def_string); peter1138@5047: if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, 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: peter1138@9152: translatedtext = TranslateTTDPatchCodes(grfid, text_to_add); peter1138@5108: tron@5906: GRFText *newtext = GRFText::New(langid_to_add, translatedtext); belugas@3856: peter1138@5108: free(translatedtext); 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 { peter1138@4711: GRFText **ptext, *text; peter1138@4711: bool replaced = false; peter1138@4711: peter1138@4711: /* Loop through all languages and see if we can replace a string */ peter1138@4711: for (ptext = &_grf_text[id].textholder; (text = *ptext) != NULL; ptext = &text->next) { peter1138@4992: if (text->langid != langid_to_add) continue; peter1138@4711: newtext->next = text->next; peter1138@4711: *ptext = newtext; tron@5906: delete text; peter1138@4711: replaced = true; peter1138@4711: break; peter1138@4711: } peter1138@4711: peter1138@4711: /* If a string wasn't replaced, then we must append the new string */ peter1138@4711: if (!replaced) *ptext = newtext; belugas@3601: } belugas@3601: Darkvater@5568: grfmsg(3, "Added 0x%X: grfid %08X 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: peter1138@4710: /* Used to remember the grfid that the last retrieved string came from */ peter1138@4710: static uint32 _last_grfid = 0; 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; peter1138@4710: peter1138@4710: /* grfid is zero when we're being called via an include */ peter1138@4710: if (grfid == 0) grfid = _last_grfid; peter1138@4710: 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: peter1138@8941: const char *GetGRFStringPtr(uint16 stringid) belugas@3601: { tron@4305: const GRFText *default_text = NULL; tron@4305: const GRFText *search_text; belugas@3601: belugas@3601: assert(_grf_text[stringid].grfid != 0); peter1138@4710: peter1138@4710: /* Remember this grfid in case the string has included text */ peter1138@4710: _last_grfid = _grf_text[stringid].grfid; peter1138@4710: 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) { peter1138@8941: return search_text->text; 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@4992: if (search_text->langid == GRFLX_UNSPECIFIED || (default_text == NULL && (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@8941: if (default_text != NULL) return default_text->text; peter1138@3821: peter1138@3821: /* Use the default string ID if the fallback string isn't available */ peter1138@8941: return GetStringPtr(_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@6977: * from strings.cpp:ReadLanguagePack belugas@6977: * @param iso_name iso code of current selection belugas@3601: */ peter1138@3605: void SetCurrentGrfLangID(const char *iso_name) belugas@3601: { peter1138@3605: /* Use English by default, if we can't match up the iso_code. */ tron@4305: byte ret = GRFLX_ENGLISH; tron@4305: byte i; 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. */ maedhros@7383: ret = iso_codes[i].grfLangID; belugas@3601: break; belugas@3601: } belugas@3601: } belugas@3601: _currentLangID = ret; belugas@3601: } belugas@3601: maedhros@7369: bool CheckGrfLangID(byte lang_id, byte grf_version) maedhros@7369: { maedhros@7369: if (grf_version < 7) { maedhros@7369: switch (_currentLangID) { maedhros@7369: case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0; maedhros@7369: case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0; maedhros@7369: case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0; maedhros@7369: default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0; maedhros@7369: } maedhros@7369: } maedhros@7369: maedhros@7369: return (lang_id == _currentLangID || lang_id == GRFLX_UNSPECIFIED); maedhros@7369: } maedhros@7369: belugas@3601: /** belugas@3601: * House cleaning. peter1138@3602: * Remove all strings and reset the text counter. belugas@3601: */ rubidium@6573: void CleanUpStrings() 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; tron@5906: delete 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: } rubidium@8112: rubidium@8112: struct TextRefStack { rubidium@8112: byte stack[0x30]; rubidium@8112: byte position; rubidium@8112: bool used; rubidium@8112: rubidium@8112: TextRefStack() : used(false) {} rubidium@8112: rubidium@8112: uint8 PopUnsignedByte() { assert(this->position < lengthof(this->stack)); return this->stack[this->position++]; } rubidium@8112: int8 PopSignedByte() { return (int8)this->PopUnsignedByte(); } rubidium@8112: glx@8562: uint16 PopUnsignedWord() glx@8562: { glx@8562: uint16 val = this->PopUnsignedByte(); glx@8562: return val | (this->PopUnsignedByte() << 8); glx@8562: } rubidium@8112: int16 PopSignedWord() { return (int32)this->PopUnsignedWord(); } rubidium@8112: glx@8562: uint32 PopUnsignedDWord() glx@8562: { glx@8562: uint32 val = this->PopUnsignedWord(); glx@8562: return val | (this->PopUnsignedWord() << 16); glx@8562: } rubidium@8112: int32 PopSignedDWord() { return (int32)this->PopUnsignedDWord(); } rubidium@8112: glx@8562: uint64 PopUnsignedQWord() glx@8562: { glx@8562: uint64 val = this->PopUnsignedDWord(); glx@8562: return val | (((uint64)this->PopUnsignedDWord()) << 32); glx@8562: } rubidium@8112: int64 PopSignedQWord() { return (int64)this->PopUnsignedQWord(); } rubidium@8112: rubidium@8112: /** Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3 */ rubidium@8112: void RotateTop4Words() rubidium@8112: { rubidium@8112: byte tmp[2]; rubidium@8112: for (int i = 0; i < 2; i++) tmp[i] = this->stack[this->position + i + 6]; rubidium@8112: for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i]; rubidium@8112: for (int i = 0; i < 2; i++) this->stack[this->position + i] = tmp[i]; rubidium@8112: } rubidium@8112: rubidium@8112: void PushWord(uint16 word) rubidium@8112: { rubidium@8112: if (this->position >= 2) { rubidium@8112: this->position -= 2; rubidium@8112: } else { rubidium@8112: for (uint i = lengthof(stack) - 3; i >= this->position; i--) { rubidium@8112: this->stack[this->position + 2] = this->stack[this->position]; rubidium@8112: } rubidium@8112: } rubidium@8112: this->stack[this->position] = GB(word, 0, 8); rubidium@8112: this->stack[this->position + 1] = GB(word, 8, 8); rubidium@8112: } rubidium@8112: rubidium@8246: void ResetStack() { this->position = 0; this->used = true; } rubidium@8246: void RewindStack() { this->position = 0; } rubidium@8112: }; rubidium@8112: rubidium@8246: static TextRefStack _newgrf_normal_textrefstack; rubidium@8246: static TextRefStack _newgrf_error_textrefstack; rubidium@8246: rubidium@8112: /** The stack that is used for TTDP compatible string code parsing */ rubidium@8246: static TextRefStack *_newgrf_textrefstack = &_newgrf_normal_textrefstack; rubidium@8112: rubidium@8246: /** rubidium@8246: * Prepare the TTDP compatible string code parsing rubidium@8246: * @param numEntries number of entries to copy from the registers rubidium@8246: */ rubidium@8246: void PrepareTextRefStackUsage(byte numEntries) rubidium@8112: { rubidium@8112: extern TemporaryStorageArray _temp_store; rubidium@8112: rubidium@8246: _newgrf_textrefstack->ResetStack(); rubidium@8112: rubidium@8246: byte *p = _newgrf_textrefstack->stack; rubidium@8246: for (uint i = 0; i < numEntries; i++) { rubidium@8112: for (uint j = 0; j < 32; j += 8) { glx@8218: *p = GB(_temp_store.Get(0x100 + i), j, 8); rubidium@8112: p++; rubidium@8112: } rubidium@8112: } rubidium@8112: } rubidium@8112: rubidium@8112: /** Stop using the TTDP compatible string code parsing */ rubidium@8246: void StopTextRefStackUsage() { _newgrf_textrefstack->used = false; } rubidium@8246: rubidium@8246: void SwitchToNormalRefStack() rubidium@8246: { rubidium@8246: _newgrf_textrefstack = &_newgrf_normal_textrefstack; rubidium@8246: } rubidium@8246: rubidium@8246: void SwitchToErrorRefStack() rubidium@8246: { rubidium@8246: _newgrf_textrefstack = &_newgrf_error_textrefstack; rubidium@8246: } rubidium@8246: rubidium@8246: void RewindTextRefStack() rubidium@8246: { rubidium@8246: _newgrf_textrefstack->RewindStack(); rubidium@8246: } rubidium@8112: rubidium@8112: /** rubidium@8112: * FormatString for NewGRF specific "magic" string control codes rubidium@8112: * @param scc the string control code that has been read rubidium@8112: * @param stack the current "stack" rubidium@8112: * @return the string control code to "execute" now rubidium@8112: */ rubidium@8112: uint RemapNewGRFStringControlCode(uint scc, char **buff, const char **str, int64 *argv) rubidium@8112: { rubidium@8246: if (_newgrf_textrefstack->used) { rubidium@8112: switch (scc) { rubidium@8112: default: NOT_REACHED(); rubidium@8246: case SCC_NEWGRF_PRINT_SIGNED_BYTE: *argv = _newgrf_textrefstack->PopSignedByte(); break; rubidium@8246: case SCC_NEWGRF_PRINT_SIGNED_WORD: *argv = _newgrf_textrefstack->PopSignedWord(); break; rubidium@8246: case SCC_NEWGRF_PRINT_QWORD_CURRENCY: *argv = _newgrf_textrefstack->PopUnsignedQWord(); break; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_DWORD_CURRENCY: rubidium@8246: case SCC_NEWGRF_PRINT_DWORD: *argv = _newgrf_textrefstack->PopSignedDWord(); break; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_WORD_SPEED: rubidium@8112: case SCC_NEWGRF_PRINT_WORD_LITRES: rubidium@8246: case SCC_NEWGRF_PRINT_UNSIGNED_WORD: *argv = _newgrf_textrefstack->PopUnsignedWord(); break; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_DATE: rubidium@8246: case SCC_NEWGRF_PRINT_MONTH_YEAR: *argv = _newgrf_textrefstack->PopSignedWord() + DAYS_TILL_ORIGINAL_BASE_YEAR; break; rubidium@8112: rubidium@8246: case SCC_NEWGRF_DISCARD_WORD: _newgrf_textrefstack->PopUnsignedWord(); break; rubidium@8112: rubidium@8246: case SCC_NEWGRF_ROTATE_TOP_4_WORDS: _newgrf_textrefstack->RotateTop4Words(); break; rubidium@8246: case SCC_NEWGRF_PUSH_WORD: _newgrf_textrefstack->PushWord(Utf8Consume(str)); break; rubidium@8112: case SCC_NEWGRF_UNPRINT: *buff -= Utf8Consume(str); break; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_STRING_ID: rubidium@8246: *argv = _newgrf_textrefstack->PopUnsignedWord(); rubidium@8112: if (*argv == STR_NULL) *argv = STR_EMPTY; rubidium@8112: break; rubidium@8112: } rubidium@8112: } rubidium@8112: rubidium@8112: switch (scc) { rubidium@8112: default: NOT_REACHED(); rubidium@8112: case SCC_NEWGRF_PRINT_DWORD: rubidium@8112: case SCC_NEWGRF_PRINT_SIGNED_WORD: rubidium@8112: case SCC_NEWGRF_PRINT_SIGNED_BYTE: rubidium@8112: case SCC_NEWGRF_PRINT_UNSIGNED_WORD: rubidium@9280: return SCC_COMMA; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_DWORD_CURRENCY: rubidium@8112: case SCC_NEWGRF_PRINT_QWORD_CURRENCY: rubidium@8112: return SCC_CURRENCY; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_STRING_ID: glx@8251: return SCC_STRING1; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_DATE: rubidium@8112: return SCC_DATE_LONG; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_MONTH_YEAR: rubidium@8112: return SCC_DATE_TINY; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_WORD_SPEED: rubidium@8112: return SCC_VELOCITY; rubidium@8112: rubidium@8112: case SCC_NEWGRF_PRINT_WORD_LITRES: rubidium@8112: return SCC_VOLUME; rubidium@8112: rubidium@8112: case SCC_NEWGRF_DISCARD_WORD: rubidium@8112: case SCC_NEWGRF_ROTATE_TOP_4_WORDS: rubidium@8112: case SCC_NEWGRF_PUSH_WORD: rubidium@8112: case SCC_NEWGRF_UNPRINT: rubidium@8112: return 0; rubidium@8112: } rubidium@8112: }