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" belugas@3601: #include "newgrf_text.h" belugas@3616: #include "table/landscape_const.h" peter1138@5108: #include "table/control_codes.h" belugas@4120: #include "music.h" rubidium@4261: #include "date.h" belugas@4942: #include "industry.h" truelight@0: Darkvater@4219: #ifdef WIN32 Darkvater@4219: /* for opendir/readdir/closedir */ Darkvater@4219: # include "fios.h" Darkvater@4219: #else Darkvater@4219: # include Darkvater@4219: # include Darkvater@4219: #endif /* WIN32 */ Darkvater@4219: tron@2201: char _userstring[128]; tron@2201: Darkvater@4912: static char *StationGetSpecialString(char *buff, int x, const char* last); Darkvater@4912: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed, const char* last); Darkvater@4912: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv, const char* last); truelight@0: Darkvater@4912: static char *FormatString(char *buff, const char *str, const int32 *argv, uint casei, const char* last); truelight@0: tron@1319: typedef struct LanguagePack { truelight@0: uint32 ident; rubidium@4434: uint32 version; // 32-bits of auto generated version info which is basically a hash of strings.h rubidium@4434: char name[32]; // the international name of this language rubidium@4434: char own_name[32]; // the localized name of this language rubidium@4434: char isocode[16]; // the ISO code for the language (not country code) rubidium@4434: uint16 offsets[32]; // the offsets rubidium@4434: byte plural_form; // how to compute plural forms rubidium@4434: 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 Darkvater@4405: // should set those bits, therefore string CANNOT be StringID, but uint32. Darkvater@4912: static char *GetStringWithArgs(char *buffr, uint string, const int32 *argv, const char* last) truelight@0: { tron@2140: uint index = GB(string, 0, 11); tron@2140: uint tab = GB(string, 11, 5); peter1138@4710: char buff[512]; truelight@0: tron@2635: if (GB(string, 0, 16) == 0) error("!invalid string id 0 in GetString"); truelight@0: tron@1321: switch (tab) { tron@1321: case 4: ludde@2063: if (index >= 0xC0) Darkvater@4912: return GetSpecialTownNameString(buffr, index - 0xC0, GetInt32(&argv), last); tron@1321: break; tron@1321: tron@1321: case 14: ludde@2063: if (index >= 0xE4) Darkvater@4912: return GetSpecialPlayerNameString(buffr, index - 0xE4, argv, last); tron@1321: break; tron@1321: ludde@2063: // User defined name tron@1321: case 15: Darkvater@4912: return GetName(buffr, index, last); tron@1321: peter1138@4710: case 26: peter1138@4710: /* Include string within newgrf text (format code 81) */ peter1138@4710: if (HASBIT(index, 10)) { peter1138@4710: StringID string = GetGRFStringID(0, 0xD000 + GB(index, 0, 10)); Darkvater@4912: return GetStringWithArgs(buffr, string, argv, last); peter1138@4710: } peter1138@4710: break; peter1138@4710: belugas@3601: case 28: peter1138@4916: GetGRFString(buff, index, lastof(buff)); Darkvater@4912: return FormatString(buffr, buff, argv, 0, last); belugas@3601: belugas@3601: case 29: peter1138@4916: GetGRFString(buff, index + 0x800, lastof(buff)); Darkvater@4912: return FormatString(buffr, buff, argv, 0, last); belugas@3601: belugas@3601: case 30: peter1138@4916: GetGRFString(buff, index + 0x1000, lastof(buff)); Darkvater@4912: return FormatString(buffr, buff, argv, 0, last); belugas@3601: 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)) { Darkvater@4912: return strecpy(buffr, _bound_strings[index], last); ludde@2055: } tron@1321: Darkvater@4912: return FormatString(buffr, _userstring, NULL, 0, last); truelight@0: } truelight@0: tron@2639: 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: ); tron@2639: } truelight@0: Darkvater@4912: return FormatString(buffr, GetStringPtr(GB(string, 0, 16)), argv, GB(string, 24, 8), last); truelight@0: } truelight@0: Darkvater@4912: char *GetString(char *buffr, StringID string, const char* last) ludde@2063: { Darkvater@4912: return GetStringWithArgs(buffr, string, (int32*)_decode_parameters, last); ludde@2063: } ludde@2063: ludde@2063: peter1138@5108: char *InlineString(char *buf, StringID string) peter1138@5108: { peter1138@5108: buf += Utf8Encode(buf, SCC_STRING_ID); peter1138@5108: buf += Utf8Encode(buf, string); peter1138@5108: return buf; peter1138@5108: } peter1138@5108: peter1138@5108: 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: Darkvater@4912: // TODO Darkvater@4912: static char *FormatCommaNumber(char *buff, int32 number, const char* last) 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: Darkvater@4912: // TODO Darkvater@4912: static char *FormatNoCommaNumber(char *buff, int32 number, const char* last) 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) { Darkvater@4912: buff = strecpy(buff, "-", last); 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: Darkvater@4912: static char *FormatYmdString(char *buff, Date date, const char* last) truelight@0: { truelight@0: YearMonthDay ymd; truelight@0: rubidium@4289: ConvertDateToYMD(date, &ymd); truelight@0: Darkvater@4912: buff = strecpy(buff, GetStringPtr(ymd.day + STR_01AC_1ST - 1), last); Darkvater@4912: buff = strecpy(buff, " ", last); Darkvater@4912: buff = strecpy(buff, GetStringPtr(STR_0162_JAN + ymd.month), last); Darkvater@4912: buff = strecpy(buff, " ", last); peter1138@3832: Darkvater@4912: return FormatNoCommaNumber(buff, ymd.year, last); truelight@0: } truelight@0: Darkvater@4912: static char *FormatMonthAndYear(char *buff, Date date, const char* last) truelight@0: { truelight@0: YearMonthDay ymd; truelight@0: rubidium@4289: ConvertDateToYMD(date, &ymd); truelight@0: Darkvater@4912: buff = strecpy(buff, GetStringPtr(STR_MONTH_JAN + ymd.month), last); Darkvater@4912: buff = strecpy(buff, " ", last); truelight@0: Darkvater@4912: return FormatNoCommaNumber(buff, ymd.year, last); truelight@0: } truelight@0: Darkvater@4912: static char *FormatTinyDate(char *buff, Date date, const char* last) dominik@1097: { dominik@1097: YearMonthDay ymd; dominik@1097: rubidium@4289: ConvertDateToYMD(date, &ymd); Darkvater@4912: buff += snprintf( Darkvater@4912: buff, last - buff + 1, Darkvater@4912: " %02i-%02i-%04i", ymd.day, ymd.month + 1, ymd.year Darkvater@4912: ); dominik@1097: dominik@1097: return buff; dominik@1097: } dominik@1097: Darkvater@4912: static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, int64 number, bool compact, const char* last) truelight@0: { Darkvater@4912: const char* multiplier = ""; Darkvater@4912: char buf[40]; Darkvater@4912: char* 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) { Darkvater@4912: buff = strecpy(buff, "-", last); tron@1316: number = -number; tron@1316: } truelight@193: belugas@4602: /* Add prefix part, folowing symbol_pos specification. belugas@4602: * Here, it can can be either 0 (prefix) or 2 (both prefix anf suffix). belugas@4602: * The only remaining value is 1 (suffix), so everything that is not 1 */ Darkvater@4912: if (spec->symbol_pos != 1) buff = strecpy(buff, spec->prefix, last); truelight@0: truelight@0: // for huge numbers, compact the number into k or M truelight@0: if (compact) { truelight@0: if (number >= 1000000000) { truelight@0: number = (number + 500000) / 1000000; Darkvater@4912: multiplier = "M"; truelight@0: } else if (number >= 1000000) { truelight@0: number = (number + 500) / 1000; Darkvater@4912: multiplier = "k"; truelight@193: } truelight@0: } truelight@193: truelight@0: // convert to ascii number and add commas Darkvater@4912: p = endof(buf); Darkvater@4912: *--p = '\0'; truelight@0: j = 4; truelight@193: do { tron@1316: if (--j == 0) { Darkvater@4912: *--p = spec->separator; tron@1316: j = 3; tron@1316: } Darkvater@4912: *--p = '0' + number % 10; Darkvater@4912: } while ((number /= 10) != 0); Darkvater@4912: buff = strecpy(buff, p, last); truelight@0: Darkvater@4912: buff = strecpy(buff, multiplier, last); truelight@0: belugas@4602: /* Add suffix part, folowing symbol_pos specification. belugas@4602: * Here, it can can be either 1 (suffix) or 2 (both prefix anf suffix). belugas@4602: * The only remaining value is 1 (prefix), so everything that is not 0 */ Darkvater@4912: if (spec->symbol_pos != 0) buff = strecpy(buff, spec->suffix, last); 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: tron@2952: 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; tron@2952: tron@2952: 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: peter1138@3342: typedef struct Units { peter1138@3342: int s_m; ///< Multiplier for velocity peter1138@3342: int s_s; ///< Shift for velocity peter1138@3342: StringID velocity; ///< String for velocity peter1138@3342: int p_m; ///< Multiplier for power peter1138@3342: int p_s; ///< Shift for power peter1138@3342: StringID power; ///< String for velocity peter1138@3342: int w_m; ///< Multiplier for weight peter1138@3342: int w_s; ///< Shift for weight peter1138@3342: StringID s_weight; ///< Short string for weight peter1138@3342: StringID l_weight; ///< Long string for weight peter1138@3342: int v_m; ///< Multiplier for volume peter1138@3342: int v_s; ///< Shift for volume peter1138@3342: StringID s_volume; ///< Short string for volume peter1138@3342: StringID l_volume; ///< Long string for volume peter1138@3489: int f_m; ///< Multiplier for force peter1138@3489: int f_s; ///< Shift for force peter1138@3489: StringID force; ///< String for force peter1138@3342: } Units; peter1138@3342: peter1138@3489: /* Unit conversions */ peter1138@3342: static const Units units[] = { celestar@5592: { // Imperial (Original, mph, hp, metric ton, litre, kN) peter1138@3477: 10, 4, STR_UNITS_VELOCITY_IMPERIAL, peter1138@3342: 1, 0, STR_UNITS_POWER_IMPERIAL, peter1138@3342: 1, 0, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, peter1138@3342: 1000, 0, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, celestar@5592: 1, 0, STR_UNITS_FORCE_SI, peter1138@3342: }, celestar@5592: { // Metric (km/h, hp, metric ton, litre, kN) peter1138@3477: 1, 0, STR_UNITS_VELOCITY_METRIC, peter1138@3342: 1, 0, STR_UNITS_POWER_METRIC, peter1138@3342: 1, 0, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, peter1138@3342: 1000, 0, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, celestar@5592: 1, 0, STR_UNITS_FORCE_SI, peter1138@3342: }, peter1138@3489: { // SI (m/s, kilowatt, kilogram, cubic metres, kilonewton) peter1138@3477: 284, 10, STR_UNITS_VELOCITY_SI, peter1138@3342: 764, 10, STR_UNITS_POWER_SI, peter1138@3342: 1000, 0, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, peter1138@3485: 1, 0, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, peter1138@3489: 1, 0, STR_UNITS_FORCE_SI, peter1138@3342: }, peter1138@3342: }; ludde@2082: Darkvater@4912: static char* FormatString(char* buff, const char* str, const int32* argv, uint casei, const char* last) truelight@0: { Darkvater@4840: extern const char _openttd_revision[]; peter1138@5108: WChar b; ludde@2063: const int32 *argv_orig = argv; ludde@2087: uint modifier = 0; truelight@0: peter1138@5108: while ((b = Utf8Consume(&str)) != '\0') { tron@1316: switch (b) { peter1138@5108: case SCC_SETX: // {SETX} peter1138@5108: if (buff + Utf8CharLen(SCC_SETX) + 1 < last) { peter1138@5108: buff += Utf8Encode(buff, SCC_SETX); peter1138@5108: *buff++ = *str++; peter1138@5108: } peter1138@5108: break; tron@2410: peter1138@5108: case SCC_SETXY: // {SETXY} peter1138@5108: if (buff + Utf8CharLen(SCC_SETXY) + 2 < last) { peter1138@5108: buff += Utf8Encode(buff, SCC_SETXY); peter1138@5108: *buff++ = *str++; peter1138@5108: *buff++ = *str++; peter1138@5108: } peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_STRING_ID: // {STRINL} peter1138@5108: buff = GetStringWithArgs(buff, Utf8Consume(&str), argv, last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_DATE_LONG: // {DATE_LONG} peter1138@5108: buff = FormatYmdString(buff, GetInt32(&argv), last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_DATE_SHORT: // {DATE_SHORT} peter1138@5108: buff = FormatMonthAndYear(buff, GetInt32(&argv), last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_VELOCITY: {// {VELOCITY} peter1138@5108: int32 args[1]; peter1138@5108: assert(_opt_ptr->units < lengthof(units)); peter1138@5108: args[0] = GetInt32(&argv) * units[_opt_ptr->units].s_m >> units[_opt_ptr->units].s_s; peter1138@5108: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].velocity), args, modifier >> 24, last); peter1138@5108: modifier = 0; peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_CURRENCY_COMPACT: /* {CURRCOMPACT} */ Darkvater@4912: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), true, last); truelight@0: break; peter1138@5108: peter1138@5108: case SCC_REVISION: /* {REV} */ Darkvater@4912: buff = strecpy(buff, _openttd_revision, last); truelight@0: break; peter1138@5108: peter1138@5108: case SCC_CARGO_SHORT: { /* {SHORTCARGO} */ darkvater@236: // Short description of cargotypes. Layout: truelight@0: // 8-bit = cargo type truelight@0: // 16-bit = cargo count belugas@3616: StringID cargo_str = _cargo_types_base_values[_opt_ptr->landscape].units_volume[GetInt32(&argv)]; peter1138@3342: switch (cargo_str) { peter1138@3342: case STR_TONS: { peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_weight), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: case STR_LITERS: { peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_volume), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: default: Darkvater@4912: buff = FormatCommaNumber(buff, GetInt32(&argv), last); Darkvater@4912: buff = strecpy(buff, " ", last); Darkvater@4912: buff = strecpy(buff, GetStringPtr(cargo_str), last); peter1138@3342: break; peter1138@3342: } tron@1316: } break; peter1138@5108: peter1138@5108: case SCC_CURRENCY_COMPACT_64: { /* {CURRCOMPACT64} */ darkvater@236: // 64 bit compact currency-unit Darkvater@4912: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), true, last); truelight@0: break; ludde@2063: } peter1138@5108: peter1138@5108: case SCC_STRING1: { /* {STRING1} */ ludde@2063: // String that consumes ONE argument ludde@2087: uint str = modifier + GetInt32(&argv); Darkvater@4912: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 1), last); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } peter1138@5108: peter1138@5108: case SCC_STRING2: { /* {STRING2} */ ludde@2063: // String that consumes TWO arguments ludde@2087: uint str = modifier + GetInt32(&argv); Darkvater@4912: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 2), last); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } peter1138@5108: peter1138@5108: case SCC_STRING3: { /* {STRING3} */ ludde@2063: // String that consumes THREE arguments ludde@2087: uint str = modifier + GetInt32(&argv); Darkvater@4912: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 3), last); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } peter1138@5108: peter1138@5108: case SCC_STRING4: { /* {STRING4} */ ludde@2063: // String that consumes FOUR arguments ludde@2087: uint str = modifier + GetInt32(&argv); Darkvater@4912: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 4), last); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } peter1138@5108: peter1138@5108: case SCC_STRING5: { /* {STRING5} */ ludde@2063: // String that consumes FIVE arguments ludde@2087: uint str = modifier + GetInt32(&argv); Darkvater@4912: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 5), last); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: peter1138@5108: case SCC_STATION_FEATURES: { /* {STATIONFEATURES} */ Darkvater@4912: buff = StationGetSpecialString(buff, GetInt32(&argv), last); ludde@2063: break; ludde@2063: } truelight@0: peter1138@5108: case SCC_INDUSTRY_NAME: { /* {INDUSTRY} */ tron@3033: const Industry* i = GetIndustry(GetInt32(&argv)); ludde@2070: int32 args[2]; ludde@2070: ludde@2070: // industry not valid anymore? truelight@4346: if (!IsValidIndustry(i)) 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; belugas@4942: args[1] = GetIndustrySpec(i->type)->name; Darkvater@4912: buff = FormatString(buff, GetStringPtr(STR_INDUSTRY_FORMAT), args, modifier >> 24, last); ludde@2087: modifier = 0; ludde@2070: break; ludde@2070: } ludde@2070: peter1138@5108: case SCC_VOLUME: { // {VOLUME} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_volume), args, modifier >> 24, last); ludde@2087: modifier = 0; ludde@2084: break; ludde@2084: } ludde@2084: peter1138@5108: case SCC_GENDER_LIST: { // {G 0 Der Die Das} peter1138@5108: const char* s = GetStringPtr(argv_orig[(byte)*str++]); // contains the string that determines gender. ludde@2084: int len; ludde@2084: int gender = 0; peter1138@5108: if (s != NULL && Utf8Consume(&s) == SCC_GENDER_INDEX) gender = (byte)s[0]; ludde@2084: str = ParseStringChoice(str, gender, buff, &len); ludde@2084: buff += len; ludde@2084: break; ludde@2084: } ludde@2084: peter1138@5108: case SCC_DATE_TINY: { // {DATE_TINY} Darkvater@4912: buff = FormatTinyDate(buff, GetInt32(&argv), last); ludde@2087: break; ludde@2087: } ludde@2087: peter1138@5108: case SCC_CARGO: { // {CARGO} ludde@2087: // Layout now is: ludde@2087: // 8bit - cargo type ludde@2087: // 16-bit - cargo count peter1138@4898: CargoID cargo = GetInt32(&argv); peter1138@4898: StringID cargo_str = (cargo == CT_INVALID) ? STR_8838_N_A : _cargoc.names_long[cargo]; Darkvater@4912: buff = GetStringWithArgs(buff, cargo_str, argv++, last); ludde@2087: break; ludde@2087: } ludde@2087: peter1138@5108: case SCC_POWER: { // {POWER} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].p_m >> units[_opt_ptr->units].p_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].power), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@5108: case SCC_VOLUME_SHORT: { // {VOLUME_S} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].s_volume), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@5108: case SCC_WEIGHT: { // {WEIGHT} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_weight), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@5108: case SCC_WEIGHT_SHORT: { // {WEIGHT_S} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].s_weight), args, modifier >> 24, last); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@5108: case SCC_FORCE: { // {FORCE} peter1138@3489: int32 args[1]; peter1138@3489: assert(_opt_ptr->units < lengthof(units)); peter1138@3489: args[0] = GetInt32(&argv) * units[_opt_ptr->units].f_m >> units[_opt_ptr->units].f_s; Darkvater@4912: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].force), args, modifier >> 24, last); peter1138@3489: modifier = 0; peter1138@3489: break; peter1138@3489: } peter1138@3489: peter1138@5108: case SCC_SKIP: // {SKIP} peter1138@5108: argv++; peter1138@5108: break; tron@2410: peter1138@5108: // This sets up the gender for the string. peter1138@5108: // We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. peter1138@5108: case SCC_GENDER_INDEX: // {GENDER 0} peter1138@5108: str++; peter1138@5108: break; tron@2630: peter1138@5108: case SCC_STRING: {// {STRING} peter1138@5108: uint str = modifier + GetInt32(&argv); peter1138@5108: // WARNING. It's prohibited for the included string to consume any arguments. peter1138@5108: // For included strings that consume argument, you should use STRING1, STRING2 etc. peter1138@5108: // To debug stuff you can set argv to NULL and it will tell you peter1138@5108: buff = GetStringWithArgs(buff, str, argv, last); peter1138@5108: modifier = 0; peter1138@5108: break; peter1138@5108: } ludde@2087: peter1138@5108: case SCC_COMMA: // {COMMA} peter1138@5108: buff = FormatCommaNumber(buff, GetInt32(&argv), last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_ARG_INDEX: // Move argument pointer peter1138@5108: argv = argv_orig + (byte)*str++; peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_PLURAL_LIST: { // {P} peter1138@5108: int32 v = argv_orig[(byte)*str++]; // contains the number that determines plural peter1138@5108: int len; peter1138@5108: str = ParseStringChoice(str, DeterminePluralForm(v), buff, &len); peter1138@5108: buff += len; peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_NUM: // {NUM} peter1138@5108: buff = FormatNoCommaNumber(buff, GetInt32(&argv), last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_CURRENCY: // {CURRENCY} peter1138@5108: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), false, last); peter1138@5108: break; peter1138@5108: peter1138@5108: case SCC_WAYPOINT_NAME: { // {WAYPOINT} peter1138@5108: int32 temp[2]; peter1138@5108: Waypoint *wp = GetWaypoint(GetInt32(&argv)); peter1138@5108: StringID str; peter1138@5108: if (wp->string != STR_NULL) { peter1138@5108: str = wp->string; peter1138@5108: } else { peter1138@5108: temp[0] = wp->town_index; peter1138@5108: temp[1] = wp->town_cn + 1; peter1138@5108: str = wp->town_cn == 0 ? STR_WAYPOINTNAME_CITY : STR_WAYPOINTNAME_CITY_SERIAL; ludde@2087: } peter1138@5108: buff = GetStringWithArgs(buff, str, temp, last); peter1138@5108: break; truelight@0: } dominik@1097: peter1138@5108: case SCC_STATION_NAME: { // {STATION} peter1138@5108: const Station* st = GetStation(GetInt32(&argv)); peter1138@5108: peter1138@5108: if (!IsValidStation(st)) { // station doesn't exist anymore peter1138@5108: buff = GetStringWithArgs(buff, STR_UNKNOWN_DESTINATION, NULL, last); peter1138@5108: } else { peter1138@5108: int32 temp[2]; peter1138@5108: temp[0] = st->town->townnametype; peter1138@5108: temp[1] = st->town->townnameparts; peter1138@5108: buff = GetStringWithArgs(buff, st->string_id, temp, last); peter1138@5108: } peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_TOWN_NAME: { // {TOWN} peter1138@5108: const Town* t = GetTown(GetInt32(&argv)); peter1138@5108: int32 temp[1]; peter1138@5108: peter1138@5108: assert(IsValidTown(t)); peter1138@5108: peter1138@5108: temp[0] = t->townnameparts; peter1138@5108: buff = GetStringWithArgs(buff, t->townnametype, temp, last); peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_CURRENCY_64: { // {CURRENCY64} peter1138@5108: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), false, last); peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_SETCASE: { // {SETCASE} peter1138@5108: // This is a pseudo command, it's outputted when someone does {STRING.ack} peter1138@5108: // The modifier is added to all subsequent GetStringWithArgs that accept the modifier. peter1138@5108: modifier = (byte)*str++ << 24; peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: case SCC_SWITCH_CASE: { // {Used to implement case switching} peter1138@5108: // <0x9E> peter1138@5108: // Each LEN is printed using 2 bytes in big endian order. peter1138@5108: uint num = (byte)*str++; peter1138@5108: while (num) { peter1138@5108: if ((byte)str[0] == casei) { peter1138@5108: // Found the case, adjust str pointer and continue peter1138@5108: str += 3; peter1138@5108: break; peter1138@5108: } peter1138@5108: // Otherwise skip to the next case peter1138@5108: str += 3 + (str[1] << 8) + str[2]; peter1138@5108: num--; peter1138@5108: } peter1138@5108: break; peter1138@5108: } peter1138@5108: peter1138@5108: default: peter1138@5108: if (buff + Utf8CharLen(b) < last) buff += Utf8Encode(buff, b); peter1138@5108: break; truelight@0: } truelight@0: } tron@1316: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: truelight@0: Darkvater@4912: static char *StationGetSpecialString(char *buff, int x, const char* last) truelight@0: { peter1138@5108: if ((x & 0x01) && (buff + Utf8CharLen(SCC_TRAIN) < last)) buff += Utf8Encode(buff, SCC_TRAIN); peter1138@5108: if ((x & 0x02) && (buff + Utf8CharLen(SCC_LORRY) < last)) buff += Utf8Encode(buff, SCC_LORRY); peter1138@5108: if ((x & 0x04) && (buff + Utf8CharLen(SCC_BUS) < last)) buff += Utf8Encode(buff, SCC_BUS); peter1138@5108: if ((x & 0x08) && (buff + Utf8CharLen(SCC_PLANE) < last)) buff += Utf8Encode(buff, SCC_PLANE); peter1138@5108: if ((x & 0x10) && (buff + Utf8CharLen(SCC_SHIP) < last)) buff += Utf8Encode(buff, SCC_SHIP); peter1138@5108: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: Darkvater@4912: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed, const char* last) tron@1316: { Darkvater@4912: char name[512]; truelight@0: peter1138@4920: _town_name_generators[ind](name, seed, lastof(name)); Darkvater@4912: return strecpy(buff, name, last); truelight@0: } truelight@0: tron@2650: 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", tron@2650: "Getout & Pushit Ltd." truelight@0: }; truelight@0: tron@2650: 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", tron@2650: "Watkins" tron@2650: }; tron@2650: tron@2650: static const char* const _silly_surname_list[] = { 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", tron@2650: "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: Darkvater@4912: static char *GenAndCoName(char *buff, uint32 arg, const char* last) truelight@0: { tron@2650: const char* const* base; tron@2650: uint num; truelight@0: Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { tron@2650: base = _silly_surname_list; tron@2650: num = lengthof(_silly_surname_list); tron@2650: } else { tron@2650: base = _surname_list; tron@2650: num = lengthof(_surname_list); truelight@0: } truelight@0: Darkvater@4912: buff = strecpy(buff, base[num * GB(arg, 16, 8) >> 8], last); Darkvater@4912: buff = strecpy(buff, " & Co.", last); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: Darkvater@4912: static char *GenPresidentName(char *buff, uint32 x, const char* last) truelight@0: { Darkvater@4912: char initial[] = "?. "; tron@2650: const char* const* base; tron@2650: uint num; tron@2650: uint i; truelight@0: Darkvater@4912: initial[0] = _initial_name_letters[sizeof(_initial_name_letters) * GB(x, 0, 8) >> 8]; Darkvater@4912: buff = strecpy(buff, initial, last); truelight@0: tron@2150: i = (sizeof(_initial_name_letters) + 35) * GB(x, 8, 8) >> 8; truelight@0: if (i < sizeof(_initial_name_letters)) { Darkvater@4912: initial[0] = _initial_name_letters[i]; Darkvater@4912: buff = strecpy(buff, initial, last); truelight@0: } truelight@0: Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { tron@2650: base = _silly_surname_list; tron@2650: num = lengthof(_silly_surname_list); tron@2650: } else { tron@2650: base = _surname_list; tron@2650: num = lengthof(_surname_list); truelight@0: } truelight@0: Darkvater@4912: buff = strecpy(buff, base[num * GB(x, 16, 8) >> 8], last); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: Darkvater@4912: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv, const char* last) truelight@0: { tron@1316: switch (ind) { tron@1321: case 1: // not used Darkvater@4912: return strecpy(buff, _silly_company_names[GetInt32(&argv) & 0xFFFF], last); truelight@0: tron@1321: case 2: // used for Foobar & Co company names Darkvater@4912: return GenAndCoName(buff, GetInt32(&argv), last); truelight@0: tron@1321: case 3: // President name Darkvater@4912: return GenPresidentName(buff, GetInt32(&argv), last); truelight@0: tron@1321: case 4: // song names Darkvater@4912: return strecpy(buff, origin_songs_specs[GetInt32(&argv) - 1].song_name, last); truelight@0: } truelight@0: truelight@0: // town name? tron@1316: if (IS_INT_INSIDE(ind - 6, 0, SPECSTR_TOWNNAME_LAST-SPECSTR_TOWNNAME_START + 1)) { Darkvater@4912: buff = GetSpecialTownNameString(buff, ind - 6, GetInt32(&argv), last); Darkvater@4912: return strecpy(buff, " Transport", last); 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, Darkvater@4912: i == _dynlang.curr ? _langpack->own_name : _dynlang.ent[i].name, last); 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); Darkvater@4912: buff += snprintf( Darkvater@4912: buff, last - buff + 1, "%dx%d", _resolutions[i][0], _resolutions[i][1] Darkvater@4912: ); Darkvater@4912: return buff; 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); Darkvater@4912: return strecpy(buff, GetScreenshotFormatDesc(i), last); 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@2951: if (IS_INT_INSIDE(s, 0x300F, 0x3030)) { tron@1321: return s - 0x300F + STR_SV_STNAME; tron@2951: } else { tron@1321: return s; tron@2951: } 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: { Darkvater@5296: char *lang = str_fmt("%s%s", _paths.lang_dir, _dynlang.ent[lang_index].file); peter1138@5108: lang_pack = ReadFileToMem(lang, &len, 200000); 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++) { Darkvater@2966: lang_pack->offsets[i] = ReadLE16Aligned(&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: _dynlang.curr = lang_index; belugas@3601: SetCurrentGrfLangID(_langpack->isocode); truelight@0: return true; truelight@0: } truelight@0: Darkvater@3329: /** Determine the current charset based on the environment Darkvater@3329: * First check some default values, after this one we passed ourselves Darkvater@3329: * and if none exist return the value for $LANG Darkvater@3329: * @param environment variable to check conditionally if default ones are not Darkvater@3329: * set. Pass NULL if you don't want additional checks. Darkvater@3329: * @return return string containing current charset, or NULL if not-determinable */ Darkvater@3329: const char *GetCurrentLocale(const char *param) Darkvater@3329: { Darkvater@3329: const char *env; Darkvater@3329: Darkvater@3329: env = getenv("LANGUAGE"); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: Darkvater@3329: env = getenv("LC_ALL"); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: Darkvater@3329: if (param != NULL) { Darkvater@3329: env = getenv(param); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: } Darkvater@3329: Darkvater@3329: return getenv("LANG"); Darkvater@3329: } Darkvater@3329: Darkvater@4219: static int CDECL LanguageCompareFunc(const void *a, const void *b) Darkvater@4219: { Darkvater@4219: return strcmp(*(const char* const *)a, *(const char* const *)b); Darkvater@4219: } Darkvater@4219: Darkvater@4219: static int GetLanguageList(char **languages, int max) Darkvater@4219: { Darkvater@4219: DIR *dir; Darkvater@4219: struct dirent *dirent; Darkvater@4219: int num = 0; Darkvater@4219: Darkvater@5296: dir = opendir(_paths.lang_dir); Darkvater@4219: if (dir != NULL) { Darkvater@4219: while ((dirent = readdir(dir)) != NULL) { Darkvater@5167: const char *d_name = FS2OTTD(dirent->d_name); Darkvater@5167: char *t = strrchr(d_name, '.'); Darkvater@4219: Darkvater@4219: if (t != NULL && strcmp(t, ".lng") == 0) { Darkvater@5167: languages[num++] = strdup(d_name); Darkvater@4219: if (num == max) break; Darkvater@4219: } Darkvater@4219: } Darkvater@4219: closedir(dir); Darkvater@4219: } Darkvater@4219: Darkvater@4219: qsort(languages, num, sizeof(char*), LanguageCompareFunc); Darkvater@4219: return num; Darkvater@4219: } Darkvater@4219: 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@4505: int def2; tron@2257: int fallback; tron@1319: LanguagePack hdr; truelight@0: FILE *in; peter1138@5108: char *files[MAX_LANG]; tron@4505: const char* lang; tron@2257: tron@4505: lang = GetCurrentLocale("LC_MESSAGES"); tron@4505: if (lang == NULL) lang = "en_GB"; tron@2257: truelight@0: n = GetLanguageList(files, lengthof(files)); truelight@0: tron@2257: def = -1; tron@4505: def2 = -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++) { truelight@4321: size_t j; tron@1321: Darkvater@5296: char *s = str_fmt("%s%s", _paths.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@4505: if (strcmp(hdr.isocode, "en_GB") == 0) fallback = m; tron@4505: if (strncmp(hdr.isocode, lang, 2) == 0) def2 = m; tron@4505: if (strncmp(hdr.isocode, lang, 5) == 0) def = m; truelight@0: truelight@0: m++; truelight@0: } tron@4505: if (def == -1) def = (def2 != -1 ? def2 : 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@3033: for (i = 0; i != dl->num; i++) 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: }