tron@2186: /* $Id$ */ tron@2186: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@2291: #include "currency.h" tron@2163: #include "functions.h" tron@1317: #include "string.h" tron@1309: #include "strings.h" tron@507: #include "table/strings.h" tron@1306: #include "namegen.h" truelight@0: #include "station.h" truelight@0: #include "town.h" truelight@0: #include "vehicle.h" darkvater@6: #include "news.h" tron@430: #include "screenshot.h" truelight@1542: #include "waypoint.h" ludde@2070: #include "industry.h" tron@2159: #include "variables.h" truelight@0: tron@2201: char _userstring[128]; tron@2201: ludde@2063: static char *StationGetSpecialString(char *buff, int x); ludde@2063: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed); ludde@2063: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv); truelight@0: ludde@2087: static char *FormatString(char *buff, const char *str, const int32 *argv, uint casei); truelight@0: darkvater@663: extern const char _openttd_revision[]; darkvater@659: tron@1319: typedef struct LanguagePack { truelight@0: uint32 ident; truelight@0: uint32 version; // 32-bits of auto generated version info which is basically a hash of strings.h truelight@0: char name[32]; // the international name of this language truelight@0: char own_name[32]; // the localized name of this language miham@1376: char isocode[16]; // the ISO code for the language (not country code) truelight@0: uint16 offsets[32]; // the offsets ludde@2082: byte plural_form; // how to compute plural forms ludde@2082: byte pad[3]; // pad header to be a multiple of 4 tron@1319: char data[VARARRAY_SIZE]; tron@1319: } LanguagePack; tron@1319: tron@1319: static char **_langpack_offs; tron@1319: static LanguagePack *_langpack; tron@1319: static uint _langtab_num[32]; // Offset into langpack offs tron@1319: static uint _langtab_start[32]; // Offset into langpack offs truelight@0: tron@1321: static const StringID _cargo_string_list[NUM_LANDSCAPE][NUM_CARGO] = { tron@1316: { /* LT_NORMAL */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_ITEMS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_RES_OTHER tron@1316: }, tron@1316: tron@1316: { /* LT_HILLY */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_ITEMS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_RES_OTHER, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_TONS tron@1316: }, tron@1316: tron@1316: { /* LT_DESERT */ tron@1316: STR_PASSENGERS, tron@1316: STR_LITERS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_TONS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_LITERS, tron@1316: STR_BAGS, tron@1316: STR_TONS tron@1316: }, tron@1316: tron@1316: { /* LT_CANDY */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_NOTHING, tron@1316: STR_NOTHING, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_LITERS, tron@1316: STR_TONS, tron@1316: STR_NOTHING, tron@1316: STR_LITERS, tron@1316: STR_NOTHING tron@1316: } truelight@0: }; truelight@0: truelight@0: ludde@2063: // Read an int64 from the argv array. ludde@2063: static inline int64 GetInt64(const int32 **argv) ludde@2063: { ludde@2063: int64 result; ludde@2063: ludde@2063: assert(argv); ludde@2063: result = (uint32)(*argv)[0] + ((uint64)(uint32)(*argv)[1] << 32); ludde@2063: (*argv)+=2; ludde@2063: return result; ludde@2063: } ludde@2063: ludde@2063: // Read an int32 from the argv array. ludde@2063: static inline int32 GetInt32(const int32 **argv) ludde@2063: { ludde@2063: assert(argv); ludde@2063: return *(*argv)++; ludde@2063: } ludde@2063: ludde@2063: // Read an array from the argv array. ludde@2063: static inline const int32 *GetArgvPtr(const int32 **argv, int n) ludde@2063: { ludde@2063: const int32 *result; ludde@2063: assert(*argv); ludde@2063: result = *argv; ludde@2063: (*argv) += n; ludde@2063: return result; ludde@2063: } ludde@2063: ludde@2063: ludde@2055: #define NUM_BOUND_STRINGS 8 ludde@2055: ludde@2055: // Array to hold the bound strings. ludde@2055: static const char *_bound_strings[NUM_BOUND_STRINGS]; ludde@2055: ludde@2055: // This index is used to implement a "round-robin" allocating of ludde@2055: // slots for BindCString. NUM_BOUND_STRINGS slots are reserved. ludde@2055: // Which means that after NUM_BOUND_STRINGS calls to BindCString, ludde@2055: // the indices will be reused. ludde@2055: static int _bind_index; ludde@2055: tron@1321: static const char *GetStringPtr(StringID string) truelight@0: { truelight@0: return _langpack_offs[_langtab_start[string >> 11] + (string & 0x7FF)]; truelight@0: } truelight@0: ludde@2087: // The highest 8 bits of string contain the "case index". ludde@2087: // These 8 bits will only be set when FormatString wants to print ludde@2087: // the string in a different case. No one else except FormatString ludde@2087: // should set those bits. ludde@2087: char *GetStringWithArgs(char *buffr, uint string, const int32 *argv) truelight@0: { tron@2140: uint index = GB(string, 0, 11); tron@2140: uint tab = GB(string, 11, 5); truelight@0: ludde@2087: if (!(string & 0xFFFF)) { ludde@2063: error("!invalid string id 0 in GetString"); tron@1321: } truelight@0: tron@1321: switch (tab) { tron@1321: case 4: ludde@2063: if (index >= 0xC0) ludde@2063: return GetSpecialTownNameString(buffr, index - 0xC0, GetInt32(&argv)); tron@1321: break; tron@1321: tron@1321: case 14: ludde@2063: if (index >= 0xE4) ludde@2063: return GetSpecialPlayerNameString(buffr, index - 0xE4, argv); tron@1321: break; tron@1321: ludde@2063: // User defined name tron@1321: case 15: tron@1321: return GetName(index, buffr); tron@1321: ludde@2063: case 31: ludde@2063: // dynamic strings. These are NOT to be passed through the formatter, ludde@2063: // but passed through verbatim. ludde@2055: if (index < (STR_SPEC_USERSTRING & 0x7FF)) { ludde@2063: return strecpy(buffr, _bound_strings[index], NULL); ludde@2055: } tron@1321: ludde@2087: return FormatString(buffr, _userstring, NULL, 0); truelight@0: } truelight@0: truelight@0: if (index >= _langtab_num[tab]) tron@1321: error( tron@1321: "!String 0x%X is invalid. " tron@1321: "Probably because an old version of the .lng file.\n", string tron@1321: ); truelight@0: ludde@2087: return FormatString(buffr, GetStringPtr(string&0xFFFF), argv, string >> 24); truelight@0: } truelight@0: ludde@2063: char *GetString(char *buffr, StringID string) ludde@2063: { ludde@2063: return GetStringWithArgs(buffr, string, (int32*)_decode_parameters); ludde@2063: } ludde@2063: ludde@2063: ludde@2055: // This function takes a C-string and allocates a temporary string ID. ludde@2055: // The duration of the bound string is valid only until the next GetString, ludde@2055: // so be careful. ludde@2055: StringID BindCString(const char *str) ludde@2055: { ludde@2055: int idx = (++_bind_index) & (NUM_BOUND_STRINGS - 1); ludde@2055: _bound_strings[idx] = str; ludde@2055: return idx + STR_SPEC_DYNSTRING; ludde@2055: } ludde@2055: ludde@2055: // This function is used to "bind" a C string to a OpenTTD dparam slot. ludde@2055: void SetDParamStr(uint n, const char *str) ludde@2055: { ludde@2055: SetDParam(n, BindCString(str)); ludde@2055: } ludde@2055: tron@1309: void InjectDParam(int amount) truelight@0: { truelight@193: memmove(_decode_parameters + amount, _decode_parameters, sizeof(_decode_parameters) - amount * sizeof(uint32)); truelight@0: } truelight@0: truelight@0: static const uint32 _divisor_table[] = { truelight@0: 1000000000, truelight@0: 100000000, truelight@0: 10000000, truelight@0: 1000000, truelight@0: truelight@0: 100000, truelight@0: 10000, truelight@0: 1000, truelight@0: 100, truelight@0: 10, truelight@0: 1 truelight@0: }; truelight@0: tron@1312: static char *FormatCommaNumber(char *buff, int32 number) truelight@0: { truelight@0: uint32 quot,divisor; truelight@0: int i; truelight@0: uint32 tot; truelight@0: uint32 num; truelight@0: truelight@0: if (number < 0) { truelight@0: *buff++ = '-'; truelight@0: number = -number; truelight@0: } truelight@0: truelight@0: num = number; truelight@0: truelight@0: tot = 0; tron@1316: for (i = 0; i != 10; i++) { truelight@0: divisor = _divisor_table[i]; truelight@0: quot = 0; truelight@0: if (num >= divisor) { truelight@0: quot = num / _divisor_table[i]; truelight@0: num = num % _divisor_table[i]; truelight@0: } tron@1316: if (tot |= quot || i == 9) { tron@1312: *buff++ = '0' + quot; tron@1316: if (i == 0 || i == 3 || i == 6) *buff++ = ','; truelight@0: } truelight@0: } truelight@0: tron@1316: *buff = '\0'; truelight@0: truelight@0: return buff; truelight@0: } truelight@0: tron@1312: static char *FormatNoCommaNumber(char *buff, int32 number) truelight@0: { truelight@0: uint32 quot,divisor; truelight@0: int i; truelight@0: uint32 tot; truelight@0: uint32 num; truelight@0: truelight@0: if (number < 0) { truelight@0: *buff++ = '-'; truelight@0: number = -number; truelight@0: } truelight@0: truelight@0: num = number; truelight@0: truelight@0: tot = 0; tron@1316: for (i = 0; i != 10; i++) { truelight@0: divisor = _divisor_table[i]; truelight@0: quot = 0; truelight@0: if (num >= divisor) { truelight@0: quot = num / _divisor_table[i]; truelight@0: num = num % _divisor_table[i]; truelight@0: } tron@1316: if (tot |= quot || i == 9) { tron@1312: *buff++ = '0' + quot; truelight@0: } truelight@0: } truelight@0: tron@1316: *buff = '\0'; truelight@0: truelight@0: return buff; truelight@0: } truelight@0: truelight@0: tron@1312: static char *FormatYmdString(char *buff, uint16 number) truelight@0: { tron@1312: const char *src; truelight@0: YearMonthDay ymd; truelight@0: truelight@0: ConvertDayToYMD(&ymd, number); truelight@0: tron@1316: for (src = GetStringPtr(ymd.day + STR_01AC_1ST - 1); (*buff++ = *src++) != '\0';) {} truelight@193: truelight@0: buff[-1] = ' '; truelight@0: memcpy(buff, GetStringPtr(STR_0162_JAN + ymd.month), 4); truelight@0: buff[3] = ' '; truelight@0: darkvater@970: return FormatNoCommaNumber(buff+4, ymd.year + MAX_YEAR_BEGIN_REAL); truelight@0: } truelight@0: tron@1312: static char *FormatMonthAndYear(char *buff, uint16 number) truelight@0: { truelight@0: const char *src; truelight@0: YearMonthDay ymd; truelight@0: truelight@0: ConvertDayToYMD(&ymd, number); truelight@0: tron@1316: for (src = GetStringPtr(STR_MONTH_JAN + ymd.month); (*buff++ = *src++) != '\0';) {} truelight@0: buff[-1] = ' '; truelight@0: darkvater@970: return FormatNoCommaNumber(buff, ymd.year + MAX_YEAR_BEGIN_REAL); truelight@0: } truelight@0: tron@1312: static char *FormatTinyDate(char *buff, uint16 number) dominik@1097: { dominik@1097: YearMonthDay ymd; dominik@1097: dominik@1097: ConvertDayToYMD(&ymd, number); dominik@1097: buff += sprintf(buff, " %02i-%02i-%04i", ymd.day, ymd.month + 1, ymd.year + MAX_YEAR_BEGIN_REAL); dominik@1097: dominik@1097: return buff; dominik@1097: } dominik@1097: tron@1312: static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, int64 number, bool compact) truelight@0: { truelight@0: const char *s; truelight@0: char c; truelight@0: char buf[40], *p; truelight@0: int j; truelight@0: truelight@0: // multiply by exchange rate truelight@0: number *= spec->rate; truelight@0: truelight@0: // convert from negative tron@1316: if (number < 0) { tron@1316: *buff++ = '-'; tron@1316: number = -number; tron@1316: } truelight@193: dominik@788: // add prefix part dominik@788: s = spec->prefix; tron@1316: while (s != spec->prefix + lengthof(spec->prefix) && (c = *s++) != '\0') *buff++ = c; truelight@0: truelight@0: // for huge numbers, compact the number into k or M truelight@0: if (compact) { truelight@0: compact = 0; truelight@0: if (number >= 1000000000) { truelight@0: number = (number + 500000) / 1000000; truelight@0: compact = 'M'; truelight@0: } else if (number >= 1000000) { truelight@0: number = (number + 500) / 1000; truelight@0: compact = 'k'; truelight@193: } truelight@0: } truelight@193: truelight@0: // convert to ascii number and add commas truelight@0: p = buf; truelight@0: j = 4; truelight@193: do { tron@1316: if (--j == 0) { tron@1316: *p++ = spec->separator; tron@1316: j = 3; tron@1316: } truelight@0: *p++ = '0' + number % 10; truelight@0: } while (number /= 10); truelight@0: do *buff++ = *--p; while (p != buf); truelight@0: truelight@0: if (compact) *buff++ = compact; truelight@0: dominik@788: // add suffix part dominik@788: s = spec->suffix; tron@1316: while (s != spec->suffix + lengthof(spec->suffix) && (c = *s++) != '\0') *buff++ = c; truelight@0: truelight@0: return buff; truelight@0: } truelight@0: ludde@2082: static int DeterminePluralForm(int32 n) ludde@2082: { ludde@2082: // The absolute value determines plurality ludde@2082: if (n < 0) n = -n; ludde@2082: ludde@2082: switch(_langpack->plural_form) { ludde@2082: // Two forms, singular used for one only ludde@2082: // Used in: ludde@2082: // Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish, ludde@2082: // Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto ludde@2082: case 0: ludde@2082: default: ludde@2082: return n != 1; ludde@2082: ludde@2082: // Only one form ludde@2082: // Used in: ludde@2082: // Hungarian, Japanese, Korean, Turkish ludde@2082: case 1: ludde@2082: return 0; ludde@2082: ludde@2082: // Two forms, singular used for zero and one ludde@2082: // Used in: ludde@2082: // French, Brazilian Portuguese ludde@2082: case 2: ludde@2082: return n > 1; ludde@2082: ludde@2082: // Three forms, special case for zero ludde@2082: // Used in: ludde@2082: // Latvian ludde@2082: case 3: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for one and two ludde@2082: // Used in: ludde@2082: // Gaelige (Irish) ludde@2082: case 4: ludde@2082: return n==1 ? 0 : n==2 ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for numbers ending in 1[2-9] ludde@2082: // Used in: ludde@2082: // Lithuanian ludde@2082: case 5: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] ludde@2082: // Used in: ludde@2082: // Croatian, Czech, Russian, Slovak, Ukrainian ludde@2082: case 6: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for one and some numbers ending in 2, 3, or 4 ludde@2082: // Used in: ludde@2082: // Polish ludde@2082: case 7: ludde@2082: return n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Four forms, special case for one and all numbers ending in 02, 03, or 04 ludde@2082: // Used in: ludde@2082: // Slovenian ludde@2082: case 8: ludde@2082: return n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3; ludde@2082: } ludde@2082: } ludde@2082: ludde@2082: static const char *ParseStringChoice(const char *b, uint form, char *dst, int *dstlen) ludde@2082: { ludde@2082: // {Length of each string} {each string} ludde@2082: uint n = (byte)*b++; ludde@2082: uint pos,i, mylen=0,mypos=0; ludde@2082: for(i=pos=0; i!=n; i++) { ludde@2082: uint len = (byte)*b++; ludde@2082: if (i == form) { ludde@2082: mypos = pos; ludde@2082: mylen = len; ludde@2082: } ludde@2082: pos += len; ludde@2082: } ludde@2082: *dstlen = mylen; ludde@2082: memcpy(dst, b + mypos, mylen); ludde@2082: return b + pos; ludde@2082: } ludde@2082: ludde@2082: ludde@2087: static char *FormatString(char *buff, const char *str, const int32 *argv, uint casei) truelight@0: { truelight@0: byte b; ludde@2063: const int32 *argv_orig = argv; ludde@2087: uint modifier = 0; truelight@0: tron@1316: while ((b = *str++) != '\0') { tron@1316: switch (b) { truelight@0: case 0x1: // {SETX} truelight@0: *buff++ = b; truelight@0: *buff++ = *str++; truelight@0: break; truelight@0: case 0x2: // {SETXY} truelight@0: *buff++ = b; truelight@0: *buff++ = *str++; truelight@0: *buff++ = *str++; truelight@0: break; tron@2410: truelight@0: case 0x81: // {STRINL} truelight@0: str += 2; ludde@2063: buff = GetStringWithArgs(buff, READ_LE_UINT16(str-2), argv); truelight@0: break; truelight@0: case 0x82: // {DATE_LONG} ludde@2063: buff = FormatYmdString(buff, GetInt32(&argv)); truelight@0: break; truelight@0: case 0x83: // {DATE_SHORT} ludde@2063: buff = FormatMonthAndYear(buff, GetInt32(&argv)); truelight@0: break; truelight@0: case 0x84: {// {VELOCITY} ludde@2063: int value = GetInt32(&argv); Darkvater@1704: if (_opt_ptr->kilometers) value = value * 1648 >> 10; truelight@0: buff = FormatCommaNumber(buff, value); Darkvater@1704: if (_opt_ptr->kilometers) { truelight@0: memcpy(buff, " km/h", 5); truelight@0: buff += 5; truelight@0: } else { truelight@0: memcpy(buff, " mph", 4); truelight@0: buff += 4; truelight@0: } truelight@0: break; truelight@0: } truelight@193: // 0x85 is used as escape character.. truelight@0: case 0x85: tron@1316: switch (*str++) { darkvater@236: case 0: /* {CURRCOMPACT} */ tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), true); truelight@0: break; darkvater@236: case 2: /* {REV} */ tron@1853: buff = strecpy(buff, _openttd_revision, NULL); truelight@0: break; darkvater@236: case 3: { /* {SHORTCARGO} */ darkvater@236: // Short description of cargotypes. Layout: truelight@0: // 8-bit = cargo type truelight@0: // 16-bit = cargo count ludde@2063: StringID cargo_str = _cargo_string_list[_opt_ptr->landscape][GetInt32(&argv)]; truelight@0: uint16 multiplier = (cargo_str == STR_LITERS) ? 1000 : 1; truelight@0: // liquid type of cargo is multiplied by 100 to get correct amount ludde@2063: buff = FormatCommaNumber(buff, GetInt32(&argv) * multiplier); tron@1853: buff = strecpy(buff, " ", NULL); tron@1853: buff = strecpy(buff, GetStringPtr(cargo_str), NULL); tron@1316: } break; ludde@2063: case 4: {/* {CURRCOMPACT64} */ darkvater@236: // 64 bit compact currency-unit tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), true); truelight@0: break; ludde@2063: } ludde@2063: case 5: { /* {STRING1} */ ludde@2063: // String that consumes ONE argument ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 1)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 6: { /* {STRING2} */ ludde@2063: // String that consumes TWO arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 2)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 7: { /* {STRING3} */ ludde@2063: // String that consumes THREE arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 3)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 8: { /* {STRING4} */ ludde@2063: // String that consumes FOUR arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 4)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 9: { /* {STRING5} */ ludde@2063: // String that consumes FIVE arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 5)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: ludde@2070: case 10: { /* {STATIONFEATURES} */ ludde@2063: buff = StationGetSpecialString(buff, GetInt32(&argv)); ludde@2063: break; ludde@2063: } truelight@0: ludde@2070: case 11: { /* {INDUSTRY} */ ludde@2070: Industry *i = GetIndustry(GetInt32(&argv)); ludde@2070: int32 args[2]; ludde@2070: ludde@2070: // industry not valid anymore? ludde@2070: if (i->xy == 0) ludde@2070: break; ludde@2070: ludde@2070: // First print the town name and the industry type name ludde@2070: // The string STR_INDUSTRY_PATTERN controls the formatting ludde@2070: args[0] = i->town->index; ludde@2070: args[1] = i->type + STR_4802_COAL_MINE; ludde@2087: buff = FormatString(buff, GetStringPtr(STR_INDUSTRY_FORMAT), args, modifier >> 24); ludde@2087: modifier = 0; ludde@2070: break; ludde@2070: } ludde@2070: ludde@2084: case 12: { // {VOLUME} ludde@2084: buff = FormatCommaNumber(buff, GetInt32(&argv) * 1000); ludde@2084: buff = strecpy(buff, " ", NULL); ludde@2087: buff = FormatString(buff, GetStringPtr(STR_LITERS), NULL, modifier >> 24); ludde@2087: modifier = 0; ludde@2084: break; ludde@2084: } ludde@2084: ludde@2084: case 13: { // {G 0 Der Die Das} ludde@2084: byte *s = (byte*)GetStringPtr(argv_orig[(byte)*str++]); // contains the string that determines gender. ludde@2084: int len; ludde@2084: int gender = 0; ludde@2084: if (s && s[0] == 0x87) ludde@2084: gender = s[1]; ludde@2084: str = ParseStringChoice(str, gender, buff, &len); ludde@2084: buff += len; ludde@2084: break; ludde@2084: } ludde@2084: ludde@2087: case 14: { // {DATE_TINY} ludde@2087: buff = FormatTinyDate(buff, GetInt32(&argv)); ludde@2087: break; ludde@2087: } ludde@2087: ludde@2087: case 15: { // {CARGO} ludde@2087: // Layout now is: ludde@2087: // 8bit - cargo type ludde@2087: // 16-bit - cargo count ludde@2087: StringID cargo_str = _cargoc.names_long[GetInt32(&argv)]; ludde@2131: buff = GetStringWithArgs(buff, cargo_str, argv++); ludde@2087: break; ludde@2087: } ludde@2087: truelight@0: default: truelight@0: error("!invalid escape sequence in string"); truelight@0: } truelight@0: break; truelight@0: truelight@0: case 0x86: // {SKIP} ludde@2063: argv++; truelight@0: break; ludde@2084: ludde@2084: // This sets up the gender for the string. ludde@2087: // We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. ludde@2084: case 0x87: // {GENDER 0} ludde@2084: str++; truelight@0: break; truelight@193: ludde@2063: case 0x88: {// {STRING} ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: // WARNING. It's prohibited for the included string to consume any arguments. ludde@2063: // For included strings that consume argument, you should use STRING1, STRING2 etc. ludde@2063: // To debug stuff you can set argv to NULL and it will tell you ludde@2063: buff = GetStringWithArgs(buff, str, argv); ludde@2087: modifier = 0; truelight@0: break; ludde@2063: } truelight@0: tron@2410: case 0x8B: // {COMMA} tron@2410: buff = FormatCommaNumber(buff, GetInt32(&argv)); tron@2410: break; tron@2410: tron@2410: case 0x8C: // Move argument pointer tron@2410: argv = argv_orig + (byte)*str++; tron@2410: break; tron@2410: tron@2410: case 0x8D: { // {P} tron@2410: int32 v = argv_orig[(byte)*str++]; // contains the number that determines plural tron@2410: int len; tron@2410: str = ParseStringChoice(str, DeterminePluralForm(v), buff, &len); tron@2410: buff += len; tron@2410: break; tron@2410: } tron@2410: tron@2410: case 0x8E: // {NUM} tron@2410: buff = FormatNoCommaNumber(buff, GetInt32(&argv)); tron@2410: break; tron@2410: tron@2410: case 0x8F: // {CURRENCY} tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), false); tron@2410: break; tron@2410: ludde@2087: case 0x99: { // {WAYPOINT} ludde@2087: int32 temp[2]; ludde@2087: Waypoint *wp = GetWaypoint(GetInt32(&argv)); ludde@2087: StringID str; ludde@2087: if (wp->string != STR_NULL) { ludde@2087: str = wp->string; ludde@2087: } else { ludde@2087: temp[0] = wp->town_index; ludde@2087: temp[1] = wp->town_cn + 1; ludde@2087: str = wp->town_cn == 0 ? STR_WAYPOINTNAME_CITY : STR_WAYPOINTNAME_CITY_SERIAL; ludde@2087: } ludde@2087: buff = GetStringWithArgs(buff, str, temp); ludde@2087: } break; truelight@0: truelight@0: case 0x9A: { // {STATION} truelight@0: Station *st; ludde@2063: int32 temp[2]; ludde@2063: ludde@2063: st = GetStation(GetInt32(&argv)); tron@1316: if (st->xy == 0) { // station doesn't exist anymore ludde@2063: buff = GetStringWithArgs(buff, STR_UNKNOWN_DESTINATION, NULL); darkvater@64: break; darkvater@64: } ludde@2063: temp[0] = st->town->townnametype; ludde@2063: temp[1] = st->town->townnameparts; ludde@2063: buff = GetStringWithArgs(buff, st->string_id, temp); truelight@0: break; truelight@0: } truelight@0: case 0x9B: { // {TOWN} truelight@0: Town *t; ludde@2063: int32 temp[1]; ludde@2063: t = GetTown(GetInt32(&argv)); truelight@0: assert(t->xy); ludde@2063: temp[0] = t->townnameparts; ludde@2063: buff = GetStringWithArgs(buff, t->townnametype, temp); truelight@0: break; truelight@0: } truelight@0: truelight@0: case 0x9C: { // {CURRENCY64} tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), false); truelight@0: break; truelight@0: } truelight@0: ludde@2087: case 0x9D: { // {SETCASE} ludde@2087: // This is a pseudo command, it's outputted when someone does {STRING.ack} ludde@2087: // The modifier is added to all subsequent GetStringWithArgs that accept the modifier. ludde@2087: modifier = (byte)*str++ << 24; ludde@2087: break; ludde@2087: } ludde@2087: ludde@2087: case 0x9E: { // {Used to implement case switching} ludde@2087: // <0x9E> ludde@2087: // Each LEN is printed using 2 bytes in big endian order. ludde@2087: uint num = (byte)*str++; ludde@2087: while (num) { Darkvater@2107: if ((byte)str[0] == casei) { ludde@2087: // Found the case, adjust str pointer and continue ludde@2087: str += 3; ludde@2087: break; ludde@2087: } ludde@2087: // Otherwise skip to the next case ludde@2087: str += 3 + (str[1] << 8) + str[2]; ludde@2087: num--; truelight@0: } dominik@1097: break; dominik@1097: } dominik@1097: truelight@0: default: truelight@0: *buff++ = b; truelight@0: } truelight@0: } tron@1316: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: truelight@0: ludde@2063: static char *StationGetSpecialString(char *buff, int x) truelight@0: { tron@2353: if (x & 0x01) *buff++ = '\x94'; tron@2353: if (x & 0x02) *buff++ = '\x95'; tron@2353: if (x & 0x04) *buff++ = '\x96'; tron@2353: if (x & 0x08) *buff++ = '\x97'; tron@2353: if (x & 0x10) *buff++ = '\x98'; tron@1316: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: ludde@2063: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed) tron@1316: { ludde@2063: _town_name_generators[ind](buff, seed); truelight@0: tron@1316: while (*buff != '\0') buff++; truelight@0: return buff; truelight@0: } truelight@0: truelight@0: static const char * const _silly_company_names[] = { truelight@0: "Bloggs Brothers", truelight@0: "Tiny Transport Ltd.", truelight@0: "Express Travel", truelight@0: "Comfy-Coach & Co.", truelight@0: "Crush & Bump Ltd.", truelight@0: "Broken & Late Ltd.", truelight@0: "Sam Speedy & Son", truelight@0: "Supersonic Travel", truelight@0: "Mike's Motors", truelight@0: "Lightning International", truelight@0: "Pannik & Loozit Ltd.", truelight@0: "Inter-City Transport", truelight@0: "Getout & Pushit Ltd.", truelight@0: }; truelight@0: truelight@0: static const char * const _surname_list[] = { truelight@0: "Adams", truelight@0: "Allan", truelight@0: "Baker", truelight@0: "Bigwig", truelight@0: "Black", truelight@0: "Bloggs", truelight@0: "Brown", truelight@0: "Campbell", truelight@0: "Gordon", truelight@0: "Hamilton", truelight@0: "Hawthorn", truelight@0: "Higgins", truelight@0: "Green", truelight@0: "Gribble", truelight@0: "Jones", truelight@0: "McAlpine", truelight@0: "MacDonald", truelight@0: "McIntosh", truelight@0: "Muir", truelight@0: "Murphy", truelight@0: "Nelson", truelight@0: "O'Donnell", truelight@0: "Parker", truelight@0: "Phillips", truelight@0: "Pilkington", truelight@0: "Quigley", truelight@0: "Sharkey", truelight@0: "Thomson", truelight@0: "Watkins", truelight@0: "Grumpy", truelight@0: "Dozy", truelight@0: "Speedy", truelight@0: "Nosey", truelight@0: "Dribble", truelight@0: "Mushroom", truelight@0: "Cabbage", truelight@0: "Sniffle", truelight@0: "Fishy", truelight@0: "Swindle", truelight@0: "Sneaky", truelight@0: "Nutkins", truelight@0: }; truelight@0: tron@1312: static const char _initial_name_letters[] = { tron@1321: 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', tron@1321: 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W', truelight@0: }; truelight@0: ludde@2063: static char *GenAndCoName(char *buff, uint32 arg) truelight@0: { truelight@0: uint base,num; truelight@0: truelight@0: base = 0; truelight@0: num = 29; Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { truelight@0: base = num; truelight@0: num = 12; truelight@0: } truelight@0: ludde@2063: buff = strecpy(buff, _surname_list[base + (num * GB(arg, 16, 8) >> 8)], NULL); tron@1853: buff = strecpy(buff, " & Co.", NULL); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: ludde@2063: static char *GenPresidentName(char *buff, uint32 x) truelight@0: { truelight@0: uint i, base, num; truelight@0: tron@2150: buff[0] = _initial_name_letters[sizeof(_initial_name_letters) * GB(x, 0, 8) >> 8]; truelight@0: buff[1] = '.'; darkvater@233: buff[2] = ' '; // Insert a space after initial and period "I. Firstname" instead of "I.Firstname" darkvater@233: buff += 3; truelight@0: tron@2150: i = (sizeof(_initial_name_letters) + 35) * GB(x, 8, 8) >> 8; truelight@0: if (i < sizeof(_initial_name_letters)) { truelight@0: buff[0] = _initial_name_letters[i]; truelight@0: buff[1] = '.'; darkvater@233: buff[2] = ' '; // Insert a space after initial and period "I. J. Firstname" instead of "I.J.Firstname" darkvater@233: buff += 3; truelight@0: } truelight@0: truelight@0: base = 0; truelight@0: num = 29; Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { truelight@0: base = num; truelight@0: num = 12; truelight@0: } truelight@0: tron@1853: buff = strecpy(buff, _surname_list[base + (num * GB(x, 16, 8) >> 8)], NULL); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: truelight@0: static const char * const _song_names[] = { truelight@0: "Tycoon DELUXE Theme", truelight@0: "Easy Driver", truelight@0: "Little Red Diesel", truelight@0: "Cruise Control", truelight@0: "Don't Walk!", truelight@0: "Fell Apart On Me", truelight@0: "City Groove", truelight@0: "Funk Central", truelight@0: "Stoke It", truelight@0: "Road Hog", truelight@0: "Aliens Ate My Railway", truelight@0: "Snarl Up", truelight@0: "Stroll On", truelight@0: "Can't Get There From Here", truelight@0: "Sawyer's Tune", truelight@0: "Hold That Train!", truelight@0: "Movin' On", truelight@0: "Goss Groove", truelight@0: "Small Town", truelight@0: "Broomer's Oil Rag", truelight@0: "Jammit", truelight@0: "Hard Drivin'" truelight@0: }; truelight@0: ludde@2063: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv) truelight@0: { tron@1316: switch (ind) { tron@1321: case 1: // not used ludde@2063: return strecpy(buff, _silly_company_names[GetInt32(&argv) & 0xFFFF], NULL); truelight@0: tron@1321: case 2: // used for Foobar & Co company names ludde@2063: return GenAndCoName(buff, GetInt32(&argv)); truelight@0: tron@1321: case 3: // President name ludde@2063: return GenPresidentName(buff, GetInt32(&argv)); truelight@0: tron@1321: case 4: // song names ludde@2063: return strecpy(buff, _song_names[GetInt32(&argv) - 1], NULL); truelight@0: } truelight@0: truelight@0: // town name? tron@1316: if (IS_INT_INSIDE(ind - 6, 0, SPECSTR_TOWNNAME_LAST-SPECSTR_TOWNNAME_START + 1)) { ludde@2063: buff = GetSpecialTownNameString(buff, ind - 6, GetInt32(&argv)); tron@1853: return strecpy(buff, " Transport", NULL); truelight@0: } truelight@0: truelight@0: // language name? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_LANGUAGE_START - 0x70E4), (SPECSTR_LANGUAGE_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_LANGUAGE_START - 0x70E4); tron@1853: return strecpy(buff, tron@1853: i == _dynlang.curr ? _langpack->own_name : _dynlang.ent[i].name, NULL); truelight@0: } truelight@0: truelight@0: // resolution size? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_RESOLUTION_START - 0x70E4), (SPECSTR_RESOLUTION_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_RESOLUTION_START - 0x70E4); truelight@0: return buff + sprintf(buff, "%dx%d", _resolutions[i][0], _resolutions[i][1]); truelight@0: } truelight@0: truelight@0: // screenshot format name? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_SCREENSHOT_START - 0x70E4), (SPECSTR_SCREENSHOT_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_SCREENSHOT_START - 0x70E4); tron@1853: return strecpy(buff, GetScreenshotFormatDesc(i), NULL); truelight@0: } truelight@0: truelight@0: assert(0); truelight@0: return NULL; truelight@0: } truelight@0: truelight@0: // remap a string ID from the old format to the new format truelight@0: StringID RemapOldStringID(StringID s) truelight@0: { tron@1321: switch (s) { tron@1321: case 0x0006: return STR_SV_EMPTY; tron@1321: case 0x7000: return STR_SV_UNNAMED; tron@1321: case 0x70E4: return SPECSTR_PLAYERNAME_ENGLISH; tron@1321: case 0x70E9: return SPECSTR_PLAYERNAME_ENGLISH; tron@1321: case 0x8864: return STR_SV_TRAIN_NAME; tron@1321: case 0x902B: return STR_SV_ROADVEH_NAME; tron@1321: case 0x9830: return STR_SV_SHIP_NAME; tron@1321: case 0xA02F: return STR_SV_AIRCRAFT_NAME; tron@1321: tron@1321: default: tron@1321: if (IS_INT_INSIDE(s, 0x300F, 0x3030)) tron@1321: return s - 0x300F + STR_SV_STNAME; tron@1321: else tron@1321: return s; tron@1321: } truelight@0: } truelight@0: tron@1316: bool ReadLanguagePack(int lang_index) tron@1316: { truelight@0: int tot_count, i; tron@1319: LanguagePack *lang_pack; truelight@0: size_t len; tron@1312: char **langpack_offs; tron@1312: char *s; truelight@0: truelight@0: { truelight@0: char *lang = str_fmt("%s%s", _path.lang_dir, _dynlang.ent[lang_index].file); truelight@0: lang_pack = ReadFileToMem(lang, &len, 100000); truelight@0: free(lang); truelight@0: } truelight@0: if (lang_pack == NULL) return false; tron@1319: if (len < sizeof(LanguagePack) || tron@1319: lang_pack->ident != TO_LE32(LANGUAGE_PACK_IDENT) || tron@1319: lang_pack->version != TO_LE32(LANGUAGE_PACK_VERSION)) { truelight@0: free(lang_pack); truelight@0: return false; truelight@0: } truelight@193: truelight@0: #if defined(TTD_BIG_ENDIAN) tron@1316: for (i = 0; i != 32; i++) { tron@1319: lang_pack->offsets[i] = READ_LE_UINT16(&lang_pack->offsets[i]); truelight@0: } truelight@0: #endif truelight@0: truelight@0: tot_count = 0; tron@1316: for (i = 0; i != 32; i++) { tron@1319: uint num = lang_pack->offsets[i]; truelight@0: _langtab_start[i] = tot_count; truelight@0: _langtab_num[i] = num; truelight@0: tot_count += num; truelight@0: } truelight@0: truelight@0: // Allocate offsets tron@1312: langpack_offs = malloc(tot_count * sizeof(*langpack_offs)); truelight@0: truelight@0: // Fill offsets tron@1319: s = lang_pack->data; tron@1316: for (i = 0; i != tot_count; i++) { tron@1312: len = (byte)*s; tron@1316: *s++ = '\0'; // zero terminate the string before. tron@1316: if (len >= 0xC0) len = ((len & 0x3F) << 8) + (byte)*s++; truelight@0: langpack_offs[i] = s; truelight@0: s += len; truelight@0: } truelight@0: tron@1321: free(_langpack); truelight@0: _langpack = lang_pack; truelight@0: tron@1321: free(_langpack_offs); truelight@0: _langpack_offs = langpack_offs; truelight@0: truelight@0: ttd_strlcpy(_dynlang.curr_file, _dynlang.ent[lang_index].file, sizeof(_dynlang.curr_file)); truelight@0: truelight@0: truelight@0: _dynlang.curr = lang_index; truelight@0: return true; truelight@0: } truelight@0: truelight@0: // make a list of the available language packs. put the data in _dynlang struct. tron@1093: void InitializeLanguagePacks(void) truelight@0: { truelight@0: DynamicLanguages *dl = &_dynlang; tron@1321: int i; tron@1321: int n; tron@1321: int m; tron@1321: int def; tron@2257: int fallback; tron@1319: LanguagePack hdr; truelight@0: FILE *in; truelight@0: char *files[32]; tron@2257: uint j; tron@2257: tron@2257: char lang[] = "en"; tron@2257: static const char* env[] = { tron@2257: "LANGUAGE", tron@2257: "LC_ALL", tron@2257: "LC_MESSAGES", tron@2257: "LANG" tron@2257: }; tron@2257: tron@2257: for (j = 0; j < lengthof(env); j++) { tron@2257: const char* envlang = getenv(env[j]); tron@2257: if (envlang != NULL) { tron@2257: snprintf(lang, lengthof(lang), "%.2s", envlang); tron@2257: break; tron@2257: } tron@2257: } truelight@0: truelight@0: n = GetLanguageList(files, lengthof(files)); truelight@0: tron@2257: def = -1; tron@2257: fallback = 0; truelight@0: truelight@0: // go through the language files and make sure that they are valid. tron@1316: for (i = m = 0; i != n; i++) { tron@1321: int j; tron@1321: truelight@0: char *s = str_fmt("%s%s", _path.lang_dir, files[i]); truelight@0: in = fopen(s, "rb"); truelight@0: free(s); tron@1316: if (in == NULL || truelight@0: (j = fread(&hdr, sizeof(hdr), 1, in), fclose(in), j) != 1 || truelight@0: hdr.ident != TO_LE32(LANGUAGE_PACK_IDENT) || truelight@0: hdr.version != TO_LE32(LANGUAGE_PACK_VERSION)) { truelight@0: free(files[i]); truelight@0: continue; truelight@0: } truelight@0: truelight@0: dl->ent[m].file = files[i]; truelight@0: dl->ent[m].name = strdup(hdr.name); truelight@193: tron@2257: if (strcmp(hdr.name, "English") == 0) fallback = m; tron@2257: if (strcmp(hdr.isocode, lang) == 0) def = m; truelight@0: truelight@0: m++; truelight@0: } tron@2257: if (def == -1) def = fallback; truelight@0: truelight@0: if (m == 0) truelight@0: error(n == 0 ? "No available language packs" : "Invalid version of language packs"); truelight@193: truelight@0: dl->num = m; tron@1316: for (i = 0; i != dl->num; i++) truelight@0: dl->dropdown[i] = SPECSTR_LANGUAGE_START + i; truelight@0: dl->dropdown[i] = INVALID_STRING_ID; truelight@0: tron@1316: for (i = 0; i != dl->num; i++) tron@1316: if (strcmp(dl->ent[i].file, dl->curr_file) == 0) { truelight@0: def = i; truelight@0: break; truelight@0: } truelight@0: truelight@0: if (!ReadLanguagePack(def)) truelight@0: error("can't read language pack '%s'", dl->ent[def].file); Darkvater@2075: }