Change: Let AI developers edit non-editable AI/Game Script Parameters (#8895)
[openttd-github.git] / src / strings.cpp
blobc589b0eef55e1cc7bebdc22e6aa28cc30fef12fa
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file strings.cpp Handling of translated strings. */
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "station_base.h"
13 #include "town.h"
14 #include "waypoint_base.h"
15 #include "depot_base.h"
16 #include "industry.h"
17 #include "newgrf_text.h"
18 #include "fileio_func.h"
19 #include "signs_base.h"
20 #include "fontdetection.h"
21 #include "error.h"
22 #include "strings_func.h"
23 #include "rev.h"
24 #include "core/endian_func.hpp"
25 #include "date_func.h"
26 #include "vehicle_base.h"
27 #include "engine_base.h"
28 #include "language.h"
29 #include "townname_func.h"
30 #include "string_func.h"
31 #include "company_base.h"
32 #include "smallmap_gui.h"
33 #include "window_func.h"
34 #include "debug.h"
35 #include "game/game_text.hpp"
36 #include "network/network_content_gui.h"
37 #include <stack>
39 #include "table/strings.h"
40 #include "table/control_codes.h"
42 #include "safeguards.h"
44 std::string _config_language_file; ///< The file (name) stored in the configuration.
45 LanguageList _languages; ///< The actual list of language meta data.
46 const LanguageMetadata *_current_language = nullptr; ///< The currently loaded language.
48 TextDirection _current_text_dir; ///< Text direction of the currently selected language.
50 #ifdef WITH_ICU_I18N
51 std::unique_ptr<icu::Collator> _current_collator; ///< Collator for the language currently in use.
52 #endif /* WITH_ICU_I18N */
54 static uint64 _global_string_params_data[20]; ///< Global array of string parameters. To access, use #SetDParam.
55 static WChar _global_string_params_type[20]; ///< Type of parameters stored in #_global_string_params
56 StringParameters _global_string_params(_global_string_params_data, 20, _global_string_params_type);
58 /** Reset the type array. */
59 void StringParameters::ClearTypeInformation()
61 assert(this->type != nullptr);
62 MemSetT(this->type, 0, this->num_param);
66 /**
67 * Read an int64 from the argument array. The offset is increased
68 * so the next time GetInt64 is called the next value is read.
70 int64 StringParameters::GetInt64(WChar type)
72 if (this->offset >= this->num_param) {
73 Debug(misc, 0, "Trying to read invalid string parameter");
74 return 0;
76 if (this->type != nullptr) {
77 if (this->type[this->offset] != 0 && this->type[this->offset] != type) {
78 Debug(misc, 0, "Trying to read string parameter with wrong type");
79 return 0;
81 this->type[this->offset] = type;
83 return this->data[this->offset++];
86 /**
87 * Set DParam n to some number that is suitable for string size computations.
88 * @param n Index of the string parameter.
89 * @param max_value The biggest value which shall be displayed.
90 * For the result only the number of digits of \a max_value matter.
91 * @param min_count Minimum number of digits independent of \a max.
92 * @param size Font of the number
94 void SetDParamMaxValue(uint n, uint64 max_value, uint min_count, FontSize size)
96 uint num_digits = 1;
97 while (max_value >= 10) {
98 num_digits++;
99 max_value /= 10;
101 SetDParamMaxDigits(n, std::max(min_count, num_digits), size);
105 * Set DParam n to some number that is suitable for string size computations.
106 * @param n Index of the string parameter.
107 * @param count Number of digits which shall be displayable.
108 * @param size Font of the number
110 void SetDParamMaxDigits(uint n, uint count, FontSize size)
112 uint front = 0;
113 uint next = 0;
114 GetBroadestDigit(&front, &next, size);
115 uint64 val = count > 1 ? front : next;
116 for (; count > 1; count--) {
117 val = 10 * val + next;
119 SetDParam(n, val);
123 * Copy \a num string parameters from array \a src into the global string parameter array.
124 * @param offs Index in the global array to copy the first string parameter to.
125 * @param src Source array of string parameters.
126 * @param num Number of string parameters to copy.
128 void CopyInDParam(int offs, const uint64 *src, int num)
130 MemCpyT(_global_string_params.GetPointerToOffset(offs), src, num);
134 * Copy \a num string parameters from the global string parameter array to the \a dst array.
135 * @param dst Destination array of string parameters.
136 * @param offs Index in the global array to copy the first string parameter from.
137 * @param num Number of string parameters to copy.
139 void CopyOutDParam(uint64 *dst, int offs, int num)
141 MemCpyT(dst, _global_string_params.GetPointerToOffset(offs), num);
145 * Copy \a num string parameters from the global string parameter array to the \a dst array.
146 * Furthermore clone raw string parameters into \a strings and amend the data in \a dst.
147 * @param dst Destination array of string parameters.
148 * @param strings Destination array for clone of the raw strings. Must be of same length as dst. Deallocation left to the caller.
149 * @param string The string used to determine where raw strings are and where there are no raw strings.
150 * @param num Number of string parameters to copy.
152 void CopyOutDParam(uint64 *dst, const char **strings, StringID string, int num)
154 char buf[DRAW_STRING_BUFFER];
155 GetString(buf, string, lastof(buf));
157 MemCpyT(dst, _global_string_params.GetPointerToOffset(0), num);
158 for (int i = 0; i < num; i++) {
159 if (_global_string_params.HasTypeInformation() && _global_string_params.GetTypeAtOffset(i) == SCC_RAW_STRING_POINTER) {
160 strings[i] = stredup((const char *)(size_t)_global_string_params.GetParam(i));
161 dst[i] = (size_t)strings[i];
162 } else {
163 strings[i] = nullptr;
168 static char *StationGetSpecialString(char *buff, int x, const char *last);
169 static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed, const char *last);
170 static char *GetSpecialNameString(char *buff, int ind, StringParameters *args, const char *last);
172 static char *FormatString(char *buff, const char *str, StringParameters *args, const char *last, uint case_index = 0, bool game_script = false, bool dry_run = false);
174 struct LanguagePack : public LanguagePackHeader {
175 char data[]; // list of strings
178 struct LanguagePackDeleter {
179 void operator()(LanguagePack *langpack)
181 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
182 delete[] reinterpret_cast<char*>(langpack);
186 struct LoadedLanguagePack {
187 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
189 std::vector<char *> offsets;
191 std::array<uint, TEXT_TAB_END> langtab_num; ///< Offset into langpack offs
192 std::array<uint, TEXT_TAB_END> langtab_start; ///< Offset into langpack offs
195 static LoadedLanguagePack _langpack;
197 static bool _scan_for_gender_data = false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
200 const char *GetStringPtr(StringID string)
202 switch (GetStringTab(string)) {
203 case TEXT_TAB_GAMESCRIPT_START: return GetGameStringPtr(GetStringIndex(string));
204 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
205 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
206 case TEXT_TAB_NEWGRF_START: return GetGRFStringPtr(GetStringIndex(string));
207 default: return _langpack.offsets[_langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string)];
212 * Get a parsed string with most special stringcodes replaced by the string parameters.
213 * @param buffr Pointer to a string buffer where the formatted string should be written to.
214 * @param string
215 * @param args Arguments for the string.
216 * @param last Pointer just past the end of \a buffr.
217 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
218 * @param game_script The string is coming directly from a game script.
219 * @return Pointer to the final zero byte of the formatted string.
221 char *GetStringWithArgs(char *buffr, StringID string, StringParameters *args, const char *last, uint case_index, bool game_script)
223 if (string == 0) return GetStringWithArgs(buffr, STR_UNDEFINED, args, last);
225 uint index = GetStringIndex(string);
226 StringTab tab = GetStringTab(string);
228 switch (tab) {
229 case TEXT_TAB_TOWN:
230 if (index >= 0xC0 && !game_script) {
231 return GetSpecialTownNameString(buffr, index - 0xC0, args->GetInt32(), last);
233 break;
235 case TEXT_TAB_SPECIAL:
236 if (index >= 0xE4 && !game_script) {
237 return GetSpecialNameString(buffr, index - 0xE4, args, last);
239 break;
241 case TEXT_TAB_OLD_CUSTOM:
242 /* Old table for custom names. This is no longer used */
243 if (!game_script) {
244 error("Incorrect conversion of custom name string.");
246 break;
248 case TEXT_TAB_GAMESCRIPT_START:
249 return FormatString(buffr, GetGameStringPtr(index), args, last, case_index, true);
251 case TEXT_TAB_OLD_NEWGRF:
252 NOT_REACHED();
254 case TEXT_TAB_NEWGRF_START:
255 return FormatString(buffr, GetGRFStringPtr(index), args, last, case_index);
257 default:
258 break;
261 if (index >= _langpack.langtab_num[tab]) {
262 if (game_script) {
263 return GetStringWithArgs(buffr, STR_UNDEFINED, args, last);
265 error("String 0x%X is invalid. You are probably using an old version of the .lng file.\n", string);
268 return FormatString(buffr, GetStringPtr(string), args, last, case_index);
271 char *GetString(char *buffr, StringID string, const char *last)
273 _global_string_params.ClearTypeInformation();
274 _global_string_params.offset = 0;
275 return GetStringWithArgs(buffr, string, &_global_string_params, last);
279 * Resolve the given StringID into a std::string with all the associated
280 * DParam lookups and formatting.
281 * @param string The unique identifier of the translatable string.
282 * @return The std::string of the translated string.
284 std::string GetString(StringID string)
286 char buffer[DRAW_STRING_BUFFER];
287 GetString(buffer, string, lastof(buffer));
288 return buffer;
292 * This function is used to "bind" a C string to a OpenTTD dparam slot.
293 * @param n slot of the string
294 * @param str string to bind
296 void SetDParamStr(uint n, const char *str)
298 SetDParam(n, (uint64)(size_t)str);
302 * This function is used to "bind" the C string of a std::string to a OpenTTD dparam slot.
303 * The caller has to ensure that the std::string reference remains valid while the string is shown.
304 * @param n slot of the string
305 * @param str string to bind
307 void SetDParamStr(uint n, const std::string &str)
309 SetDParamStr(n, str.c_str());
313 * Format a number into a string.
314 * @param buff the buffer to write to
315 * @param number the number to write down
316 * @param last the last element in the buffer
317 * @param separator the thousands-separator to use
318 * @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary.
319 * @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted
320 * in front of the \a fractional_digits last digit of \a number.
321 * @return till where we wrote
323 static char *FormatNumber(char *buff, int64 number, const char *last, const char *separator, int zerofill = 1, int fractional_digits = 0)
325 static const int max_digits = 20;
326 uint64 divisor = 10000000000000000000ULL;
327 zerofill += fractional_digits;
328 int thousands_offset = (max_digits - fractional_digits - 1) % 3;
330 if (number < 0) {
331 buff += seprintf(buff, last, "-");
332 number = -number;
335 uint64 num = number;
336 uint64 tot = 0;
337 for (int i = 0; i < max_digits; i++) {
338 if (i == max_digits - fractional_digits) {
339 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
340 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
341 buff += seprintf(buff, last, "%s", decimal_separator);
344 uint64 quot = 0;
345 if (num >= divisor) {
346 quot = num / divisor;
347 num = num % divisor;
349 if ((tot |= quot) || i >= max_digits - zerofill) {
350 buff += seprintf(buff, last, "%i", (int)quot);
351 if ((i % 3) == thousands_offset && i < max_digits - 1 - fractional_digits) buff = strecpy(buff, separator, last);
354 divisor /= 10;
357 *buff = '\0';
359 return buff;
362 static char *FormatCommaNumber(char *buff, int64 number, const char *last, int fractional_digits = 0)
364 const char *separator = _settings_game.locale.digit_group_separator.c_str();
365 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
366 return FormatNumber(buff, number, last, separator, 1, fractional_digits);
369 static char *FormatNoCommaNumber(char *buff, int64 number, const char *last)
371 return FormatNumber(buff, number, last, "");
374 static char *FormatZerofillNumber(char *buff, int64 number, int64 count, const char *last)
376 return FormatNumber(buff, number, last, "", count);
379 static char *FormatHexNumber(char *buff, uint64 number, const char *last)
381 return buff + seprintf(buff, last, "0x" OTTD_PRINTFHEX64, number);
385 * Format a given number as a number of bytes with the SI prefix.
386 * @param buff the buffer to write to
387 * @param number the number of bytes to write down
388 * @param last the last element in the buffer
389 * @return till where we wrote
391 static char *FormatBytes(char *buff, int64 number, const char *last)
393 assert(number >= 0);
395 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
396 const char * const iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
397 uint id = 1;
398 while (number >= 1024 * 1024) {
399 number /= 1024;
400 id++;
403 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
404 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
406 if (number < 1024) {
407 id = 0;
408 buff += seprintf(buff, last, "%i", (int)number);
409 } else if (number < 1024 * 10) {
410 buff += seprintf(buff, last, "%i%s%02i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 100 / 1024);
411 } else if (number < 1024 * 100) {
412 buff += seprintf(buff, last, "%i%s%01i", (int)number / 1024, decimal_separator, (int)(number % 1024) * 10 / 1024);
413 } else {
414 assert(number < 1024 * 1024);
415 buff += seprintf(buff, last, "%i", (int)number / 1024);
418 assert(id < lengthof(iec_prefixes));
419 buff += seprintf(buff, last, NBSP "%sB", iec_prefixes[id]);
421 return buff;
424 static char *FormatYmdString(char *buff, Date date, const char *last, uint case_index)
426 YearMonthDay ymd;
427 ConvertDateToYMD(date, &ymd);
429 int64 args[] = {ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year};
430 StringParameters tmp_params(args);
431 return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_LONG), &tmp_params, last, case_index);
434 static char *FormatMonthAndYear(char *buff, Date date, const char *last, uint case_index)
436 YearMonthDay ymd;
437 ConvertDateToYMD(date, &ymd);
439 int64 args[] = {STR_MONTH_JAN + ymd.month, ymd.year};
440 StringParameters tmp_params(args);
441 return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_SHORT), &tmp_params, last, case_index);
444 static char *FormatTinyOrISODate(char *buff, Date date, StringID str, const char *last)
446 YearMonthDay ymd;
447 ConvertDateToYMD(date, &ymd);
449 char day[3];
450 char month[3];
451 /* We want to zero-pad the days and months */
452 seprintf(day, lastof(day), "%02i", ymd.day);
453 seprintf(month, lastof(month), "%02i", ymd.month + 1);
455 int64 args[] = {(int64)(size_t)day, (int64)(size_t)month, ymd.year};
456 StringParameters tmp_params(args);
457 return FormatString(buff, GetStringPtr(str), &tmp_params, last);
460 static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, Money number, bool compact, const char *last)
462 /* We are going to make number absolute for printing, so
463 * keep this piece of data as we need it later on */
464 bool negative = number < 0;
465 const char *multiplier = "";
467 number *= spec->rate;
469 /* convert from negative */
470 if (number < 0) {
471 if (buff + Utf8CharLen(SCC_PUSH_COLOUR) > last) return buff;
472 buff += Utf8Encode(buff, SCC_PUSH_COLOUR);
473 if (buff + Utf8CharLen(SCC_RED) > last) return buff;
474 buff += Utf8Encode(buff, SCC_RED);
475 buff = strecpy(buff, "-", last);
476 number = -number;
479 /* Add prefix part, following symbol_pos specification.
480 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
481 * The only remaining value is 1 (suffix), so everything that is not 1 */
482 if (spec->symbol_pos != 1) buff = strecpy(buff, spec->prefix.c_str(), last);
484 /* for huge numbers, compact the number into k or M */
485 if (compact) {
486 /* Take care of the 'k' rounding. Having 1 000 000 k
487 * and 1 000 M is inconsistent, so always use 1 000 M. */
488 if (number >= 1000000000 - 500) {
489 number = (number + 500000) / 1000000;
490 multiplier = NBSP "M";
491 } else if (number >= 1000000) {
492 number = (number + 500) / 1000;
493 multiplier = NBSP "k";
497 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
498 if (StrEmpty(separator)) separator = _currency->separator.c_str();
499 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
500 buff = FormatNumber(buff, number, last, separator);
501 buff = strecpy(buff, multiplier, last);
503 /* Add suffix part, following symbol_pos specification.
504 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
505 * The only remaining value is 1 (prefix), so everything that is not 0 */
506 if (spec->symbol_pos != 0) buff = strecpy(buff, spec->suffix.c_str(), last);
508 if (negative) {
509 if (buff + Utf8CharLen(SCC_POP_COLOUR) > last) return buff;
510 buff += Utf8Encode(buff, SCC_POP_COLOUR);
511 *buff = '\0';
514 return buff;
518 * Determine the "plural" index given a plural form and a number.
519 * @param count The number to get the plural index of.
520 * @param plural_form The plural form we want an index for.
521 * @return The plural index for the given form.
523 static int DeterminePluralForm(int64 count, int plural_form)
525 /* The absolute value determines plurality */
526 uint64 n = abs(count);
528 switch (plural_form) {
529 default:
530 NOT_REACHED();
532 /* Two forms: singular used for one only.
533 * Used in:
534 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
535 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
536 case 0:
537 return n != 1 ? 1 : 0;
539 /* Only one form.
540 * Used in:
541 * Hungarian, Japanese, Turkish */
542 case 1:
543 return 0;
545 /* Two forms: singular used for 0 and 1.
546 * Used in:
547 * French, Brazilian Portuguese */
548 case 2:
549 return n > 1 ? 1 : 0;
551 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
552 * Note: Cases are out of order for hysterical reasons. '0' is last.
553 * Used in:
554 * Latvian */
555 case 3:
556 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
558 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
559 * Used in:
560 * Gaelige (Irish) */
561 case 4:
562 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
564 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 9 except when ending in 12 to 19.
565 * Used in:
566 * Lithuanian */
567 case 5:
568 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
570 /* Three forms: special cases for numbers ending in 1 except when ending in 11, and 2 to 4 except when ending in 12 to 14.
571 * Used in:
572 * Croatian, Russian, Ukrainian */
573 case 6:
574 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
576 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
577 * Used in:
578 * Polish */
579 case 7:
580 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
582 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
583 * Used in:
584 * Slovenian */
585 case 8:
586 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
588 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
589 * Used in:
590 * Icelandic */
591 case 9:
592 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
594 /* Three forms: special cases for 1, and 2 to 4
595 * Used in:
596 * Czech, Slovak */
597 case 10:
598 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
600 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
601 * Korean doesn't have the concept of plural, but depending on how a
602 * number is pronounced it needs another version of a particle.
603 * As such the plural system is misused to give this distinction.
605 case 11:
606 switch (n % 10) {
607 case 0: // yeong
608 case 1: // il
609 case 3: // sam
610 case 6: // yuk
611 case 7: // chil
612 case 8: // pal
613 return 0;
615 case 2: // i
616 case 4: // sa
617 case 5: // o
618 case 9: // gu
619 return 1;
621 default:
622 NOT_REACHED();
625 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
626 * Used in:
627 * Maltese */
628 case 12:
629 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
630 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
631 * Used in:
632 * Scottish Gaelic */
633 case 13:
634 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
636 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
637 * Used in:
638 * Romanian */
639 case 14:
640 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
644 static const char *ParseStringChoice(const char *b, uint form, char **dst, const char *last)
646 /* <NUM> {Length of each string} {each string} */
647 uint n = (byte)*b++;
648 uint pos, i, mypos = 0;
650 for (i = pos = 0; i != n; i++) {
651 uint len = (byte)*b++;
652 if (i == form) mypos = pos;
653 pos += len;
656 *dst += seprintf(*dst, last, "%s", b + mypos);
657 return b + pos;
660 /** Helper for unit conversion. */
661 struct UnitConversion {
662 int multiplier; ///< Amount to multiply upon conversion.
663 int shift; ///< Amount to shift upon conversion.
666 * Convert value from OpenTTD's internal unit into the displayed value.
667 * @param input The input to convert.
668 * @param round Whether to round the value or not.
669 * @return The converted value.
671 int64 ToDisplay(int64 input, bool round = true) const
673 return ((input * this->multiplier) + (round && this->shift != 0 ? 1 << (this->shift - 1) : 0)) >> this->shift;
677 * Convert the displayed value back into a value of OpenTTD's internal unit.
678 * @param input The input to convert.
679 * @param round Whether to round the value up or not.
680 * @param divider Divide the return value by this.
681 * @return The converted value.
683 int64 FromDisplay(int64 input, bool round = true, int64 divider = 1) const
685 return ((input << this->shift) + (round ? (this->multiplier * divider) - 1 : 0)) / (this->multiplier * divider);
689 /** Information about a specific unit system. */
690 struct Units {
691 UnitConversion c; ///< Conversion
692 StringID s; ///< String for the unit
693 unsigned int decimal_places; ///< Number of decimal places embedded in the value. For example, 1 if the value is in tenths, and 3 if the value is in thousandths.
696 /** Information about a specific unit system with a long variant. */
697 struct UnitsLong {
698 UnitConversion c; ///< Conversion
699 StringID s; ///< String for the short variant of the unit
700 StringID l; ///< String for the long variant of the unit
703 /** Unit conversions for velocity. */
704 static const Units _units_velocity[] = {
705 { { 1, 0}, STR_UNITS_VELOCITY_IMPERIAL, 0 },
706 { { 103, 6}, STR_UNITS_VELOCITY_METRIC, 0 },
707 { { 1831, 12}, STR_UNITS_VELOCITY_SI, 0 },
708 { {37888, 16}, STR_UNITS_VELOCITY_GAMEUNITS, 1 },
711 /** Unit conversions for velocity. */
712 static const Units _units_power[] = {
713 { { 1, 0}, STR_UNITS_POWER_IMPERIAL, 0 },
714 { {4153, 12}, STR_UNITS_POWER_METRIC, 0 },
715 { {6109, 13}, STR_UNITS_POWER_SI, 0 },
718 /** Unit conversions for weight. */
719 static const UnitsLong _units_weight[] = {
720 { {4515, 12}, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL },
721 { { 1, 0}, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC },
722 { {1000, 0}, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI },
725 /** Unit conversions for volume. */
726 static const UnitsLong _units_volume[] = {
727 { {4227, 4}, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL },
728 { {1000, 0}, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC },
729 { { 1, 0}, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI },
732 /** Unit conversions for force. */
733 static const Units _units_force[] = {
734 { {3597, 4}, STR_UNITS_FORCE_IMPERIAL, 0 },
735 { {3263, 5}, STR_UNITS_FORCE_METRIC, 0 },
736 { { 1, 0}, STR_UNITS_FORCE_SI, 0 },
739 /** Unit conversions for height. */
740 static const Units _units_height[] = {
741 { { 3, 0}, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
742 { { 1, 0}, STR_UNITS_HEIGHT_METRIC, 0 },
743 { { 1, 0}, STR_UNITS_HEIGHT_SI, 0 },
747 * Convert the given (internal) speed to the display speed.
748 * @param speed the speed to convert
749 * @return the converted speed.
751 uint ConvertSpeedToDisplaySpeed(uint speed)
753 /* For historical reasons we don't want to mess with the
754 * conversion for speed. So, don't round it and keep the
755 * original conversion factors instead of the real ones. */
756 return _units_velocity[_settings_game.locale.units_velocity].c.ToDisplay(speed, false);
760 * Convert the given display speed to the (internal) speed.
761 * @param speed the speed to convert
762 * @return the converted speed.
764 uint ConvertDisplaySpeedToSpeed(uint speed)
766 return _units_velocity[_settings_game.locale.units_velocity].c.FromDisplay(speed);
770 * Convert the given km/h-ish speed to the display speed.
771 * @param speed the speed to convert
772 * @return the converted speed.
774 uint ConvertKmhishSpeedToDisplaySpeed(uint speed)
776 return _units_velocity[_settings_game.locale.units_velocity].c.ToDisplay(speed * 10, false) / 16;
780 * Convert the given display speed to the km/h-ish speed.
781 * @param speed the speed to convert
782 * @return the converted speed.
784 uint ConvertDisplaySpeedToKmhishSpeed(uint speed)
786 return _units_velocity[_settings_game.locale.units_velocity].c.FromDisplay(speed * 16, true, 10);
789 * Parse most format codes within a string and write the result to a buffer.
790 * @param buff The buffer to write the final string to.
791 * @param str_arg The original string with format codes.
792 * @param args Pointer to extra arguments used by various string codes.
793 * @param last Pointer to just past the end of the buff array.
794 * @param dry_run True when the argt array is not yet initialized.
796 static char *FormatString(char *buff, const char *str_arg, StringParameters *args, const char *last, uint case_index, bool game_script, bool dry_run)
798 uint orig_offset = args->offset;
800 /* When there is no array with types there is no need to do a dry run. */
801 if (args->HasTypeInformation() && !dry_run) {
802 if (UsingNewGRFTextStack()) {
803 /* Values from the NewGRF text stack are only copied to the normal
804 * argv array at the time they are encountered. That means that if
805 * another string command references a value later in the string it
806 * would fail. We solve that by running FormatString twice. The first
807 * pass makes sure the argv array is correctly filled and the second
808 * pass can reference later values without problems. */
809 struct TextRefStack *backup = CreateTextRefStackBackup();
810 FormatString(buff, str_arg, args, last, case_index, game_script, true);
811 RestoreTextRefStackBackup(backup);
812 } else {
813 FormatString(buff, str_arg, args, last, case_index, game_script, true);
815 /* We have to restore the original offset here to to read the correct values. */
816 args->offset = orig_offset;
818 WChar b = '\0';
819 uint next_substr_case_index = 0;
820 char *buf_start = buff;
821 std::stack<const char *, std::vector<const char *>> str_stack;
822 str_stack.push(str_arg);
824 for (;;) {
825 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
826 str_stack.pop();
828 if (str_stack.empty()) break;
829 const char *&str = str_stack.top();
831 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
832 /* We need to pass some stuff as it might be modified. */
833 //todo: should argve be passed here too?
834 b = RemapNewGRFStringControlCode(b, buf_start, &buff, &str, (int64 *)args->GetDataPointer(), args->GetDataLeft(), dry_run);
835 if (b == 0) continue;
838 switch (b) {
839 case SCC_ENCODED: {
840 uint64 sub_args_data[20];
841 WChar sub_args_type[20];
842 bool sub_args_need_free[20];
843 StringParameters sub_args(sub_args_data, 20, sub_args_type);
845 sub_args.ClearTypeInformation();
846 memset(sub_args_need_free, 0, sizeof(sub_args_need_free));
848 char *p;
849 uint32 stringid = strtoul(str, &p, 16);
850 if (*p != ':' && *p != '\0') {
851 while (*p != '\0') p++;
852 str = p;
853 buff = strecat(buff, "(invalid SCC_ENCODED)", last);
854 break;
856 if (stringid >= TAB_SIZE_GAMESCRIPT) {
857 while (*p != '\0') p++;
858 str = p;
859 buff = strecat(buff, "(invalid StringID)", last);
860 break;
863 int i = 0;
864 while (*p != '\0' && i < 20) {
865 uint64 param;
866 const char *s = ++p;
868 /* Find the next value */
869 bool instring = false;
870 bool escape = false;
871 for (;; p++) {
872 if (*p == '\\') {
873 escape = true;
874 continue;
876 if (*p == '"' && escape) {
877 escape = false;
878 continue;
880 escape = false;
882 if (*p == '"') {
883 instring = !instring;
884 continue;
886 if (instring) {
887 continue;
890 if (*p == ':') break;
891 if (*p == '\0') break;
894 if (*s != '"') {
895 /* Check if we want to look up another string */
896 WChar l;
897 size_t len = Utf8Decode(&l, s);
898 bool lookup = (l == SCC_ENCODED);
899 if (lookup) s += len;
901 param = strtoull(s, &p, 16);
903 if (lookup) {
904 if (param >= TAB_SIZE_GAMESCRIPT) {
905 while (*p != '\0') p++;
906 str = p;
907 buff = strecat(buff, "(invalid sub-StringID)", last);
908 break;
910 param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param);
913 sub_args.SetParam(i++, param);
914 } else {
915 char *g = stredup(s);
916 g[p - s] = '\0';
918 sub_args_need_free[i] = true;
919 sub_args.SetParam(i++, (uint64)(size_t)g);
922 /* If we didn't error out, we can actually print the string. */
923 if (*str != '\0') {
924 str = p;
925 buff = GetStringWithArgs(buff, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), &sub_args, last, true);
928 for (int i = 0; i < 20; i++) {
929 if (sub_args_need_free[i]) free((void *)sub_args.GetParam(i));
931 break;
934 case SCC_NEWGRF_STRINL: {
935 StringID substr = Utf8Consume(&str);
936 str_stack.push(GetStringPtr(substr));
937 break;
940 case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
941 StringID substr = args->GetInt32(SCC_NEWGRF_PRINT_WORD_STRING_ID);
942 str_stack.push(GetStringPtr(substr));
943 case_index = next_substr_case_index;
944 next_substr_case_index = 0;
945 break;
949 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
950 /* First read the meta data from the language file. */
951 uint offset = orig_offset + (byte)*str++;
952 int gender = 0;
953 if (!dry_run && args->GetTypeAtOffset(offset) != 0) {
954 /* Now we need to figure out what text to resolve, i.e.
955 * what do we need to draw? So get the actual raw string
956 * first using the control code to get said string. */
957 char input[4 + 1];
958 char *p = input + Utf8Encode(input, args->GetTypeAtOffset(offset));
959 *p = '\0';
961 /* Now do the string formatting. */
962 char buf[256];
963 bool old_sgd = _scan_for_gender_data;
964 _scan_for_gender_data = true;
965 StringParameters tmp_params(args->GetPointerToOffset(offset), args->num_param - offset, nullptr);
966 p = FormatString(buf, input, &tmp_params, lastof(buf));
967 _scan_for_gender_data = old_sgd;
968 *p = '\0';
970 /* And determine the string. */
971 const char *s = buf;
972 WChar c = Utf8Consume(&s);
973 /* Does this string have a gender, if so, set it */
974 if (c == SCC_GENDER_INDEX) gender = (byte)s[0];
976 str = ParseStringChoice(str, gender, &buff, last);
977 break;
980 /* This sets up the gender for the string.
981 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
982 case SCC_GENDER_INDEX: // {GENDER 0}
983 if (_scan_for_gender_data) {
984 buff += Utf8Encode(buff, SCC_GENDER_INDEX);
985 *buff++ = *str++;
986 } else {
987 str++;
989 break;
991 case SCC_PLURAL_LIST: { // {P}
992 int plural_form = *str++; // contains the plural form for this string
993 uint offset = orig_offset + (byte)*str++;
994 int64 v = *args->GetPointerToOffset(offset); // contains the number that determines plural
995 str = ParseStringChoice(str, DeterminePluralForm(v, plural_form), &buff, last);
996 break;
999 case SCC_ARG_INDEX: { // Move argument pointer
1000 args->offset = orig_offset + (byte)*str++;
1001 break;
1004 case SCC_SET_CASE: { // {SET_CASE}
1005 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1006 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1007 next_substr_case_index = (byte)*str++;
1008 break;
1011 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1012 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1013 * Each LEN is printed using 2 bytes in big endian order. */
1014 uint num = (byte)*str++;
1015 while (num) {
1016 if ((byte)str[0] == case_index) {
1017 /* Found the case, adjust str pointer and continue */
1018 str += 3;
1019 break;
1021 /* Otherwise skip to the next case */
1022 str += 3 + (str[1] << 8) + str[2];
1023 num--;
1025 break;
1028 case SCC_REVISION: // {REV}
1029 buff = strecpy(buff, _openttd_revision, last);
1030 break;
1032 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
1033 if (game_script) break;
1034 const char *str = (const char *)(size_t)args->GetInt64(SCC_RAW_STRING_POINTER);
1035 buff = FormatString(buff, str, args, last);
1036 break;
1039 case SCC_STRING: {// {STRING}
1040 StringID str = args->GetInt32(SCC_STRING);
1041 if (game_script && GetStringTab(str) != TEXT_TAB_GAMESCRIPT_START) break;
1042 /* WARNING. It's prohibited for the included string to consume any arguments.
1043 * For included strings that consume argument, you should use STRING1, STRING2 etc.
1044 * To debug stuff you can set argv to nullptr and it will tell you */
1045 StringParameters tmp_params(args->GetDataPointer(), args->GetDataLeft(), nullptr);
1046 buff = GetStringWithArgs(buff, str, &tmp_params, last, next_substr_case_index, game_script);
1047 next_substr_case_index = 0;
1048 break;
1051 case SCC_STRING1:
1052 case SCC_STRING2:
1053 case SCC_STRING3:
1054 case SCC_STRING4:
1055 case SCC_STRING5:
1056 case SCC_STRING6:
1057 case SCC_STRING7: { // {STRING1..7}
1058 /* Strings that consume arguments */
1059 StringID str = args->GetInt32(b);
1060 if (game_script && GetStringTab(str) != TEXT_TAB_GAMESCRIPT_START) break;
1061 uint size = b - SCC_STRING1 + 1;
1062 if (game_script && size > args->GetDataLeft()) {
1063 buff = strecat(buff, "(too many parameters)", last);
1064 } else {
1065 StringParameters sub_args(*args, size);
1066 buff = GetStringWithArgs(buff, str, &sub_args, last, next_substr_case_index, game_script);
1068 next_substr_case_index = 0;
1069 break;
1072 case SCC_COMMA: // {COMMA}
1073 buff = FormatCommaNumber(buff, args->GetInt64(SCC_COMMA), last);
1074 break;
1076 case SCC_DECIMAL: {// {DECIMAL}
1077 int64 number = args->GetInt64(SCC_DECIMAL);
1078 int digits = args->GetInt32(SCC_DECIMAL);
1079 buff = FormatCommaNumber(buff, number, last, digits);
1080 break;
1083 case SCC_NUM: // {NUM}
1084 buff = FormatNoCommaNumber(buff, args->GetInt64(SCC_NUM), last);
1085 break;
1087 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1088 int64 num = args->GetInt64();
1089 buff = FormatZerofillNumber(buff, num, args->GetInt64(), last);
1090 break;
1093 case SCC_HEX: // {HEX}
1094 buff = FormatHexNumber(buff, (uint64)args->GetInt64(SCC_HEX), last);
1095 break;
1097 case SCC_BYTES: // {BYTES}
1098 buff = FormatBytes(buff, args->GetInt64(), last);
1099 break;
1101 case SCC_CARGO_TINY: { // {CARGO_TINY}
1102 /* Tiny description of cargotypes. Layout:
1103 * param 1: cargo type
1104 * param 2: cargo count */
1105 CargoID cargo = args->GetInt32(SCC_CARGO_TINY);
1106 if (cargo >= CargoSpec::GetArraySize()) break;
1108 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1109 int64 amount = 0;
1110 switch (cargo_str) {
1111 case STR_TONS:
1112 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64());
1113 break;
1115 case STR_LITERS:
1116 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64());
1117 break;
1119 default: {
1120 amount = args->GetInt64();
1121 break;
1125 buff = FormatCommaNumber(buff, amount, last);
1126 break;
1129 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1130 /* Short description of cargotypes. Layout:
1131 * param 1: cargo type
1132 * param 2: cargo count */
1133 CargoID cargo = args->GetInt32(SCC_CARGO_SHORT);
1134 if (cargo >= CargoSpec::GetArraySize()) break;
1136 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1137 switch (cargo_str) {
1138 case STR_TONS: {
1139 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1140 int64 args_array[] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64())};
1141 StringParameters tmp_params(args_array);
1142 buff = FormatString(buff, GetStringPtr(_units_weight[_settings_game.locale.units_weight].l), &tmp_params, last);
1143 break;
1146 case STR_LITERS: {
1147 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1148 int64 args_array[] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64())};
1149 StringParameters tmp_params(args_array);
1150 buff = FormatString(buff, GetStringPtr(_units_volume[_settings_game.locale.units_volume].l), &tmp_params, last);
1151 break;
1154 default: {
1155 StringParameters tmp_params(*args, 1);
1156 buff = GetStringWithArgs(buff, cargo_str, &tmp_params, last);
1157 break;
1160 break;
1163 case SCC_CARGO_LONG: { // {CARGO_LONG}
1164 /* First parameter is cargo type, second parameter is cargo count */
1165 CargoID cargo = args->GetInt32(SCC_CARGO_LONG);
1166 if (cargo != CT_INVALID && cargo >= CargoSpec::GetArraySize()) break;
1168 StringID cargo_str = (cargo == CT_INVALID) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1169 StringParameters tmp_args(*args, 1);
1170 buff = GetStringWithArgs(buff, cargo_str, &tmp_args, last);
1171 break;
1174 case SCC_CARGO_LIST: { // {CARGO_LIST}
1175 CargoTypes cmask = args->GetInt64(SCC_CARGO_LIST);
1176 bool first = true;
1178 for (const auto &cs : _sorted_cargo_specs) {
1179 if (!HasBit(cmask, cs->Index())) continue;
1181 if (buff >= last - 2) break; // ',' and ' '
1183 if (first) {
1184 first = false;
1185 } else {
1186 /* Add a comma if this is not the first item */
1187 *buff++ = ',';
1188 *buff++ = ' ';
1191 buff = GetStringWithArgs(buff, cs->name, args, last, next_substr_case_index, game_script);
1194 /* If first is still true then no cargo is accepted */
1195 if (first) buff = GetStringWithArgs(buff, STR_JUST_NOTHING, args, last, next_substr_case_index, game_script);
1197 *buff = '\0';
1198 next_substr_case_index = 0;
1200 /* Make sure we detect any buffer overflow */
1201 assert(buff < last);
1202 break;
1205 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1206 buff = FormatGenericCurrency(buff, _currency, args->GetInt64(), true, last);
1207 break;
1209 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1210 buff = FormatGenericCurrency(buff, _currency, args->GetInt64(SCC_CURRENCY_LONG), false, last);
1211 break;
1213 case SCC_DATE_TINY: // {DATE_TINY}
1214 buff = FormatTinyOrISODate(buff, args->GetInt32(SCC_DATE_TINY), STR_FORMAT_DATE_TINY, last);
1215 break;
1217 case SCC_DATE_SHORT: // {DATE_SHORT}
1218 buff = FormatMonthAndYear(buff, args->GetInt32(SCC_DATE_SHORT), last, next_substr_case_index);
1219 next_substr_case_index = 0;
1220 break;
1222 case SCC_DATE_LONG: // {DATE_LONG}
1223 buff = FormatYmdString(buff, args->GetInt32(SCC_DATE_LONG), last, next_substr_case_index);
1224 next_substr_case_index = 0;
1225 break;
1227 case SCC_DATE_ISO: // {DATE_ISO}
1228 buff = FormatTinyOrISODate(buff, args->GetInt32(), STR_FORMAT_DATE_ISO, last);
1229 break;
1231 case SCC_FORCE: { // {FORCE}
1232 assert(_settings_game.locale.units_force < lengthof(_units_force));
1233 int64 args_array[1] = {_units_force[_settings_game.locale.units_force].c.ToDisplay(args->GetInt64())};
1234 StringParameters tmp_params(args_array);
1235 buff = FormatString(buff, GetStringPtr(_units_force[_settings_game.locale.units_force].s), &tmp_params, last);
1236 break;
1239 case SCC_HEIGHT: { // {HEIGHT}
1240 assert(_settings_game.locale.units_height < lengthof(_units_height));
1241 int64 args_array[] = {_units_height[_settings_game.locale.units_height].c.ToDisplay(args->GetInt64())};
1242 StringParameters tmp_params(args_array);
1243 buff = FormatString(buff, GetStringPtr(_units_height[_settings_game.locale.units_height].s), &tmp_params, last);
1244 break;
1247 case SCC_POWER: { // {POWER}
1248 assert(_settings_game.locale.units_power < lengthof(_units_power));
1249 int64 args_array[1] = {_units_power[_settings_game.locale.units_power].c.ToDisplay(args->GetInt64())};
1250 StringParameters tmp_params(args_array);
1251 buff = FormatString(buff, GetStringPtr(_units_power[_settings_game.locale.units_power].s), &tmp_params, last);
1252 break;
1255 case SCC_VELOCITY: { // {VELOCITY}
1256 assert(_settings_game.locale.units_velocity < lengthof(_units_velocity));
1257 unsigned int decimal_places = _units_velocity[_settings_game.locale.units_velocity].decimal_places;
1258 uint64 args_array[] = {ConvertKmhishSpeedToDisplaySpeed(args->GetInt64(SCC_VELOCITY)), decimal_places};
1259 StringParameters tmp_params(args_array, decimal_places ? 2 : 1, nullptr);
1260 buff = FormatString(buff, GetStringPtr(_units_velocity[_settings_game.locale.units_velocity].s), &tmp_params, last);
1261 break;
1264 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1265 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1266 int64 args_array[1] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64())};
1267 StringParameters tmp_params(args_array);
1268 buff = FormatString(buff, GetStringPtr(_units_volume[_settings_game.locale.units_volume].s), &tmp_params, last);
1269 break;
1272 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1273 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1274 int64 args_array[1] = {_units_volume[_settings_game.locale.units_volume].c.ToDisplay(args->GetInt64(SCC_VOLUME_LONG))};
1275 StringParameters tmp_params(args_array);
1276 buff = FormatString(buff, GetStringPtr(_units_volume[_settings_game.locale.units_volume].l), &tmp_params, last);
1277 break;
1280 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1281 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1282 int64 args_array[1] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64())};
1283 StringParameters tmp_params(args_array);
1284 buff = FormatString(buff, GetStringPtr(_units_weight[_settings_game.locale.units_weight].s), &tmp_params, last);
1285 break;
1288 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1289 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1290 int64 args_array[1] = {_units_weight[_settings_game.locale.units_weight].c.ToDisplay(args->GetInt64(SCC_WEIGHT_LONG))};
1291 StringParameters tmp_params(args_array);
1292 buff = FormatString(buff, GetStringPtr(_units_weight[_settings_game.locale.units_weight].l), &tmp_params, last);
1293 break;
1296 case SCC_COMPANY_NAME: { // {COMPANY}
1297 const Company *c = Company::GetIfValid(args->GetInt32());
1298 if (c == nullptr) break;
1300 if (!c->name.empty()) {
1301 int64 args_array[] = {(int64)(size_t)c->name.c_str()};
1302 StringParameters tmp_params(args_array);
1303 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1304 } else {
1305 int64 args_array[] = {c->name_2};
1306 StringParameters tmp_params(args_array);
1307 buff = GetStringWithArgs(buff, c->name_1, &tmp_params, last);
1309 break;
1312 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1313 CompanyID company = (CompanyID)args->GetInt32();
1315 /* Nothing is added for AI or inactive companies */
1316 if (Company::IsValidHumanID(company)) {
1317 int64 args_array[] = {company + 1};
1318 StringParameters tmp_params(args_array);
1319 buff = GetStringWithArgs(buff, STR_FORMAT_COMPANY_NUM, &tmp_params, last);
1321 break;
1324 case SCC_DEPOT_NAME: { // {DEPOT}
1325 VehicleType vt = (VehicleType)args->GetInt32(SCC_DEPOT_NAME);
1326 if (vt == VEH_AIRCRAFT) {
1327 uint64 args_array[] = {(uint64)args->GetInt32()};
1328 WChar types_array[] = {SCC_STATION_NAME};
1329 StringParameters tmp_params(args_array, 1, types_array);
1330 buff = GetStringWithArgs(buff, STR_FORMAT_DEPOT_NAME_AIRCRAFT, &tmp_params, last);
1331 break;
1334 const Depot *d = Depot::Get(args->GetInt32());
1335 if (!d->name.empty()) {
1336 int64 args_array[] = {(int64)(size_t)d->name.c_str()};
1337 StringParameters tmp_params(args_array);
1338 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1339 } else {
1340 int64 args_array[] = {d->town->index, d->town_cn + 1};
1341 StringParameters tmp_params(args_array);
1342 buff = GetStringWithArgs(buff, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), &tmp_params, last);
1344 break;
1347 case SCC_ENGINE_NAME: { // {ENGINE}
1348 const Engine *e = Engine::GetIfValid(args->GetInt32(SCC_ENGINE_NAME));
1349 if (e == nullptr) break;
1351 if (!e->name.empty() && e->IsEnabled()) {
1352 int64 args_array[] = {(int64)(size_t)e->name.c_str()};
1353 StringParameters tmp_params(args_array);
1354 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1355 } else {
1356 StringParameters tmp_params(nullptr, 0, nullptr);
1357 buff = GetStringWithArgs(buff, e->info.string_id, &tmp_params, last);
1359 break;
1362 case SCC_GROUP_NAME: { // {GROUP}
1363 const Group *g = Group::GetIfValid(args->GetInt32());
1364 if (g == nullptr) break;
1366 if (!g->name.empty()) {
1367 int64 args_array[] = {(int64)(size_t)g->name.c_str()};
1368 StringParameters tmp_params(args_array);
1369 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1370 } else {
1371 int64 args_array[] = {g->index};
1372 StringParameters tmp_params(args_array);
1374 buff = GetStringWithArgs(buff, STR_FORMAT_GROUP_NAME, &tmp_params, last);
1376 break;
1379 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1380 const Industry *i = Industry::GetIfValid(args->GetInt32(SCC_INDUSTRY_NAME));
1381 if (i == nullptr) break;
1383 if (_scan_for_gender_data) {
1384 /* Gender is defined by the industry type.
1385 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1386 StringParameters tmp_params(nullptr, 0, nullptr);
1387 buff = FormatString(buff, GetStringPtr(GetIndustrySpec(i->type)->name), &tmp_params, last, next_substr_case_index);
1388 } else {
1389 /* First print the town name and the industry type name. */
1390 int64 args_array[2] = {i->town->index, GetIndustrySpec(i->type)->name};
1391 StringParameters tmp_params(args_array);
1393 buff = FormatString(buff, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), &tmp_params, last, next_substr_case_index);
1395 next_substr_case_index = 0;
1396 break;
1399 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1400 const Company *c = Company::GetIfValid(args->GetInt32(SCC_PRESIDENT_NAME));
1401 if (c == nullptr) break;
1403 if (!c->president_name.empty()) {
1404 int64 args_array[] = {(int64)(size_t)c->president_name.c_str()};
1405 StringParameters tmp_params(args_array);
1406 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1407 } else {
1408 int64 args_array[] = {c->president_name_2};
1409 StringParameters tmp_params(args_array);
1410 buff = GetStringWithArgs(buff, c->president_name_1, &tmp_params, last);
1412 break;
1415 case SCC_STATION_NAME: { // {STATION}
1416 StationID sid = args->GetInt32(SCC_STATION_NAME);
1417 const Station *st = Station::GetIfValid(sid);
1419 if (st == nullptr) {
1420 /* The station doesn't exist anymore. The only place where we might
1421 * be "drawing" an invalid station is in the case of cargo that is
1422 * in transit. */
1423 StringParameters tmp_params(nullptr, 0, nullptr);
1424 buff = GetStringWithArgs(buff, STR_UNKNOWN_STATION, &tmp_params, last);
1425 break;
1428 if (!st->name.empty()) {
1429 int64 args_array[] = {(int64)(size_t)st->name.c_str()};
1430 StringParameters tmp_params(args_array);
1431 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1432 } else {
1433 StringID str = st->string_id;
1434 if (st->indtype != IT_INVALID) {
1435 /* Special case where the industry provides the name for the station */
1436 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1438 /* Industry GRFs can change which might remove the station name and
1439 * thus cause very strange things. Here we check for that before we
1440 * actually set the station name. */
1441 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1442 str = indsp->station_name;
1446 uint64 args_array[] = {STR_TOWN_NAME, st->town->index, st->index};
1447 WChar types_array[] = {0, SCC_TOWN_NAME, SCC_NUM};
1448 StringParameters tmp_params(args_array, 3, types_array);
1449 buff = GetStringWithArgs(buff, str, &tmp_params, last);
1451 break;
1454 case SCC_TOWN_NAME: { // {TOWN}
1455 const Town *t = Town::GetIfValid(args->GetInt32(SCC_TOWN_NAME));
1456 if (t == nullptr) break;
1458 if (!t->name.empty()) {
1459 int64 args_array[] = {(int64)(size_t)t->name.c_str()};
1460 StringParameters tmp_params(args_array);
1461 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1462 } else {
1463 buff = GetTownName(buff, t, last);
1465 break;
1468 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1469 Waypoint *wp = Waypoint::GetIfValid(args->GetInt32(SCC_WAYPOINT_NAME));
1470 if (wp == nullptr) break;
1472 if (!wp->name.empty()) {
1473 int64 args_array[] = {(int64)(size_t)wp->name.c_str()};
1474 StringParameters tmp_params(args_array);
1475 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1476 } else {
1477 int64 args_array[] = {wp->town->index, wp->town_cn + 1};
1478 StringParameters tmp_params(args_array);
1479 StringID str = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1480 if (wp->town_cn != 0) str++;
1481 buff = GetStringWithArgs(buff, str, &tmp_params, last);
1483 break;
1486 case SCC_VEHICLE_NAME: { // {VEHICLE}
1487 const Vehicle *v = Vehicle::GetIfValid(args->GetInt32(SCC_VEHICLE_NAME));
1488 if (v == nullptr) break;
1490 if (!v->name.empty()) {
1491 int64 args_array[] = {(int64)(size_t)v->name.c_str()};
1492 StringParameters tmp_params(args_array);
1493 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1494 } else if (v->group_id != DEFAULT_GROUP) {
1495 /* The vehicle has no name, but is member of a group, so print group name */
1496 int64 args_array[] = {v->group_id, v->unitnumber};
1497 StringParameters tmp_params(args_array);
1498 buff = GetStringWithArgs(buff, STR_FORMAT_GROUP_VEHICLE_NAME, &tmp_params, last);
1499 } else {
1500 int64 args_array[] = {v->unitnumber};
1501 StringParameters tmp_params(args_array);
1503 StringID str;
1504 switch (v->type) {
1505 default: str = STR_INVALID_VEHICLE; break;
1506 case VEH_TRAIN: str = STR_SV_TRAIN_NAME; break;
1507 case VEH_ROAD: str = STR_SV_ROAD_VEHICLE_NAME; break;
1508 case VEH_SHIP: str = STR_SV_SHIP_NAME; break;
1509 case VEH_AIRCRAFT: str = STR_SV_AIRCRAFT_NAME; break;
1512 buff = GetStringWithArgs(buff, str, &tmp_params, last);
1514 break;
1517 case SCC_SIGN_NAME: { // {SIGN}
1518 const Sign *si = Sign::GetIfValid(args->GetInt32());
1519 if (si == nullptr) break;
1521 if (!si->name.empty()) {
1522 int64 args_array[] = {(int64)(size_t)si->name.c_str()};
1523 StringParameters tmp_params(args_array);
1524 buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last);
1525 } else {
1526 StringParameters tmp_params(nullptr, 0, nullptr);
1527 buff = GetStringWithArgs(buff, STR_DEFAULT_SIGN_NAME, &tmp_params, last);
1529 break;
1532 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1533 buff = StationGetSpecialString(buff, args->GetInt32(SCC_STATION_FEATURES), last);
1534 break;
1537 default:
1538 if (buff + Utf8CharLen(b) < last) buff += Utf8Encode(buff, b);
1539 break;
1542 *buff = '\0';
1543 return buff;
1547 static char *StationGetSpecialString(char *buff, int x, const char *last)
1549 if ((x & FACIL_TRAIN) && (buff + Utf8CharLen(SCC_TRAIN) < last)) buff += Utf8Encode(buff, SCC_TRAIN);
1550 if ((x & FACIL_TRUCK_STOP) && (buff + Utf8CharLen(SCC_LORRY) < last)) buff += Utf8Encode(buff, SCC_LORRY);
1551 if ((x & FACIL_BUS_STOP) && (buff + Utf8CharLen(SCC_BUS) < last)) buff += Utf8Encode(buff, SCC_BUS);
1552 if ((x & FACIL_DOCK) && (buff + Utf8CharLen(SCC_SHIP) < last)) buff += Utf8Encode(buff, SCC_SHIP);
1553 if ((x & FACIL_AIRPORT) && (buff + Utf8CharLen(SCC_PLANE) < last)) buff += Utf8Encode(buff, SCC_PLANE);
1554 *buff = '\0';
1555 return buff;
1558 static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed, const char *last)
1560 return GenerateTownNameString(buff, last, ind, seed);
1563 static const char * const _silly_company_names[] = {
1564 "Bloggs Brothers",
1565 "Tiny Transport Ltd.",
1566 "Express Travel",
1567 "Comfy-Coach & Co.",
1568 "Crush & Bump Ltd.",
1569 "Broken & Late Ltd.",
1570 "Sam Speedy & Son",
1571 "Supersonic Travel",
1572 "Mike's Motors",
1573 "Lightning International",
1574 "Pannik & Loozit Ltd.",
1575 "Inter-City Transport",
1576 "Getout & Pushit Ltd."
1579 static const char * const _surname_list[] = {
1580 "Adams",
1581 "Allan",
1582 "Baker",
1583 "Bigwig",
1584 "Black",
1585 "Bloggs",
1586 "Brown",
1587 "Campbell",
1588 "Gordon",
1589 "Hamilton",
1590 "Hawthorn",
1591 "Higgins",
1592 "Green",
1593 "Gribble",
1594 "Jones",
1595 "McAlpine",
1596 "MacDonald",
1597 "McIntosh",
1598 "Muir",
1599 "Murphy",
1600 "Nelson",
1601 "O'Donnell",
1602 "Parker",
1603 "Phillips",
1604 "Pilkington",
1605 "Quigley",
1606 "Sharkey",
1607 "Thomson",
1608 "Watkins"
1611 static const char * const _silly_surname_list[] = {
1612 "Grumpy",
1613 "Dozy",
1614 "Speedy",
1615 "Nosey",
1616 "Dribble",
1617 "Mushroom",
1618 "Cabbage",
1619 "Sniffle",
1620 "Fishy",
1621 "Swindle",
1622 "Sneaky",
1623 "Nutkins"
1626 static const char _initial_name_letters[] = {
1627 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1628 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1631 static char *GenAndCoName(char *buff, uint32 arg, const char *last)
1633 const char * const *base;
1634 uint num;
1636 if (_settings_game.game_creation.landscape == LT_TOYLAND) {
1637 base = _silly_surname_list;
1638 num = lengthof(_silly_surname_list);
1639 } else {
1640 base = _surname_list;
1641 num = lengthof(_surname_list);
1644 buff = strecpy(buff, base[num * GB(arg, 16, 8) >> 8], last);
1645 buff = strecpy(buff, " & Co.", last);
1647 return buff;
1650 static char *GenPresidentName(char *buff, uint32 x, const char *last)
1652 char initial[] = "?. ";
1653 const char * const *base;
1654 uint num;
1655 uint i;
1657 initial[0] = _initial_name_letters[sizeof(_initial_name_letters) * GB(x, 0, 8) >> 8];
1658 buff = strecpy(buff, initial, last);
1660 i = (sizeof(_initial_name_letters) + 35) * GB(x, 8, 8) >> 8;
1661 if (i < sizeof(_initial_name_letters)) {
1662 initial[0] = _initial_name_letters[i];
1663 buff = strecpy(buff, initial, last);
1666 if (_settings_game.game_creation.landscape == LT_TOYLAND) {
1667 base = _silly_surname_list;
1668 num = lengthof(_silly_surname_list);
1669 } else {
1670 base = _surname_list;
1671 num = lengthof(_surname_list);
1674 buff = strecpy(buff, base[num * GB(x, 16, 8) >> 8], last);
1676 return buff;
1679 static char *GetSpecialNameString(char *buff, int ind, StringParameters *args, const char *last)
1681 switch (ind) {
1682 case 1: // not used
1683 return strecpy(buff, _silly_company_names[std::min<uint>(args->GetInt32() & 0xFFFF, lengthof(_silly_company_names) - 1)], last);
1685 case 2: // used for Foobar & Co company names
1686 return GenAndCoName(buff, args->GetInt32(), last);
1688 case 3: // President name
1689 return GenPresidentName(buff, args->GetInt32(), last);
1692 /* town name? */
1693 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1694 buff = GetSpecialTownNameString(buff, ind - 6, args->GetInt32(), last);
1695 return strecpy(buff, " Transport", last);
1698 NOT_REACHED();
1702 * Check whether the header is a valid header for OpenTTD.
1703 * @return true iff the header is deemed valid.
1705 bool LanguagePackHeader::IsValid() const
1707 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1708 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1709 this->plural_form < LANGUAGE_MAX_PLURAL &&
1710 this->text_dir <= 1 &&
1711 this->newgrflangid < MAX_LANG &&
1712 this->num_genders < MAX_NUM_GENDERS &&
1713 this->num_cases < MAX_NUM_CASES &&
1714 StrValid(this->name, lastof(this->name)) &&
1715 StrValid(this->own_name, lastof(this->own_name)) &&
1716 StrValid(this->isocode, lastof(this->isocode)) &&
1717 StrValid(this->digit_group_separator, lastof(this->digit_group_separator)) &&
1718 StrValid(this->digit_group_separator_currency, lastof(this->digit_group_separator_currency)) &&
1719 StrValid(this->digit_decimal_separator, lastof(this->digit_decimal_separator));
1723 * Check whether a translation is sufficiently finished to offer it to the public.
1725 bool LanguagePackHeader::IsReasonablyFinished() const
1727 /* "Less than 25% missing" is "sufficiently finished". */
1728 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1732 * Read a particular language.
1733 * @param lang The metadata about the language.
1734 * @return Whether the loading went okay or not.
1736 bool ReadLanguagePack(const LanguageMetadata *lang)
1738 /* Current language pack */
1739 size_t len = 0;
1740 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(lang->file, len, 1U << 20).release()));
1741 if (!lang_pack) return false;
1743 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1744 const char *end = (char *)lang_pack.get() + len + 1;
1746 /* We need at least one byte of lang_pack->data */
1747 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1748 return false;
1751 #if TTD_ENDIAN == TTD_BIG_ENDIAN
1752 for (uint i = 0; i < TEXT_TAB_END; i++) {
1753 lang_pack->offsets[i] = ReadLE16Aligned(&lang_pack->offsets[i]);
1755 #endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */
1757 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1759 uint count = 0;
1760 for (uint i = 0; i < TEXT_TAB_END; i++) {
1761 uint16 num = lang_pack->offsets[i];
1762 if (num > TAB_SIZE) return false;
1764 tab_start[i] = count;
1765 tab_num[i] = num;
1766 count += num;
1769 /* Allocate offsets */
1770 std::vector<char *> offs(count);
1772 /* Fill offsets */
1773 char *s = lang_pack->data;
1774 len = (byte)*s++;
1775 for (uint i = 0; i < count; i++) {
1776 if (s + len >= end) return false;
1778 if (len >= 0xC0) {
1779 len = ((len & 0x3F) << 8) + (byte)*s++;
1780 if (s + len >= end) return false;
1782 offs[i] = s;
1783 s += len;
1784 len = (byte)*s;
1785 *s++ = '\0'; // zero terminate the string
1788 _langpack.langpack = std::move(lang_pack);
1789 _langpack.offsets = std::move(offs);
1790 _langpack.langtab_num = tab_num;
1791 _langpack.langtab_start = tab_start;
1793 _current_language = lang;
1794 _current_text_dir = (TextDirection)_current_language->text_dir;
1795 const char *c_file = strrchr(_current_language->file, PATHSEPCHAR) + 1;
1796 _config_language_file = c_file;
1797 SetCurrentGrfLangID(_current_language->newgrflangid);
1799 #ifdef _WIN32
1800 extern void Win32SetCurrentLocaleName(const char *iso_code);
1801 Win32SetCurrentLocaleName(_current_language->isocode);
1802 #endif
1804 #ifdef WITH_COCOA
1805 extern void MacOSSetCurrentLocaleName(const char *iso_code);
1806 MacOSSetCurrentLocaleName(_current_language->isocode);
1807 #endif
1809 #ifdef WITH_ICU_I18N
1810 /* Create a collator instance for our current locale. */
1811 UErrorCode status = U_ZERO_ERROR;
1812 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
1813 /* Sort number substrings by their numerical value. */
1814 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
1815 /* Avoid using the collator if it is not correctly set. */
1816 if (U_FAILURE(status)) {
1817 _current_collator.reset();
1819 #endif /* WITH_ICU_I18N */
1821 /* Some lists need to be sorted again after a language change. */
1822 ReconsiderGameScriptLanguage();
1823 InitializeSortedCargoSpecs();
1824 SortIndustryTypes();
1825 BuildIndustriesLegend();
1826 BuildContentTypeStringList();
1827 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
1828 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
1829 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
1830 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
1831 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
1832 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
1833 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
1835 return true;
1838 /* Win32 implementation in win32.cpp.
1839 * OS X implementation in os/macosx/macos.mm. */
1840 #if !(defined(_WIN32) || defined(__APPLE__))
1842 * Determine the current charset based on the environment
1843 * First check some default values, after this one we passed ourselves
1844 * and if none exist return the value for $LANG
1845 * @param param environment variable to check conditionally if default ones are not
1846 * set. Pass nullptr if you don't want additional checks.
1847 * @return return string containing current charset, or nullptr if not-determinable
1849 const char *GetCurrentLocale(const char *param)
1851 const char *env;
1853 env = std::getenv("LANGUAGE");
1854 if (env != nullptr) return env;
1856 env = std::getenv("LC_ALL");
1857 if (env != nullptr) return env;
1859 if (param != nullptr) {
1860 env = std::getenv(param);
1861 if (env != nullptr) return env;
1864 return std::getenv("LANG");
1866 #else
1867 const char *GetCurrentLocale(const char *param);
1868 #endif /* !(defined(_WIN32) || defined(__APPLE__)) */
1870 bool StringIDSorter(const StringID &a, const StringID &b)
1872 char stra[512];
1873 char strb[512];
1874 GetString(stra, a, lastof(stra));
1875 GetString(strb, b, lastof(strb));
1877 return strnatcmp(stra, strb) < 0;
1881 * Get the language with the given NewGRF language ID.
1882 * @param newgrflangid NewGRF languages ID to check.
1883 * @return The language's metadata, or nullptr if it is not known.
1885 const LanguageMetadata *GetLanguage(byte newgrflangid)
1887 for (const LanguageMetadata &lang : _languages) {
1888 if (newgrflangid == lang.newgrflangid) return &lang;
1891 return nullptr;
1895 * Reads the language file header and checks compatibility.
1896 * @param file the file to read
1897 * @param hdr the place to write the header information to
1898 * @return true if and only if the language file is of a compatible version
1900 static bool GetLanguageFileHeader(const char *file, LanguagePackHeader *hdr)
1902 FILE *f = fopen(file, "rb");
1903 if (f == nullptr) return false;
1905 size_t read = fread(hdr, sizeof(*hdr), 1, f);
1906 fclose(f);
1908 bool ret = read == 1 && hdr->IsValid();
1910 /* Convert endianness for the windows language ID */
1911 if (ret) {
1912 hdr->missing = FROM_LE16(hdr->missing);
1913 hdr->winlangid = FROM_LE16(hdr->winlangid);
1915 return ret;
1919 * Gets a list of languages from the given directory.
1920 * @param path the base directory to search in
1922 static void GetLanguageList(const char *path)
1924 DIR *dir = ttd_opendir(path);
1925 if (dir != nullptr) {
1926 struct dirent *dirent;
1927 while ((dirent = readdir(dir)) != nullptr) {
1928 std::string d_name = FS2OTTD(dirent->d_name);
1929 const char *extension = strrchr(d_name.c_str(), '.');
1931 /* Not a language file */
1932 if (extension == nullptr || strcmp(extension, ".lng") != 0) continue;
1934 LanguageMetadata lmd;
1935 seprintf(lmd.file, lastof(lmd.file), "%s%s", path, d_name.c_str());
1937 /* Check whether the file is of the correct version */
1938 if (!GetLanguageFileHeader(lmd.file, &lmd)) {
1939 Debug(misc, 3, "{} is not a valid language file", lmd.file);
1940 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
1941 Debug(misc, 3, "{}'s language ID is already known", lmd.file);
1942 } else {
1943 _languages.push_back(lmd);
1946 closedir(dir);
1951 * Make a list of the available language packs. Put the data in
1952 * #_languages list.
1954 void InitializeLanguagePacks()
1956 for (Searchpath sp : _valid_searchpaths) {
1957 std::string path = FioGetDirectory(sp, LANG_DIR);
1958 GetLanguageList(path.c_str());
1960 if (_languages.size() == 0) usererror("No available language packs (invalid versions?)");
1962 /* Acquire the locale of the current system */
1963 const char *lang = GetCurrentLocale("LC_MESSAGES");
1964 if (lang == nullptr) lang = "en_GB";
1966 const LanguageMetadata *chosen_language = nullptr; ///< Matching the language in the configuration file or the current locale
1967 const LanguageMetadata *language_fallback = nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
1968 const LanguageMetadata *en_GB_fallback = _languages.data(); ///< Fallback when no locale-matching language has been found
1970 /* Find a proper language. */
1971 for (const LanguageMetadata &lng : _languages) {
1972 /* We are trying to find a default language. The priority is by
1973 * configuration file, local environment and last, if nothing found,
1974 * English. */
1975 const char *lang_file = strrchr(lng.file, PATHSEPCHAR) + 1;
1976 if (_config_language_file == lang_file) {
1977 chosen_language = &lng;
1978 break;
1981 if (strcmp (lng.isocode, "en_GB") == 0) en_GB_fallback = &lng;
1983 /* Only auto-pick finished translations */
1984 if (!lng.IsReasonablyFinished()) continue;
1986 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
1987 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
1990 /* We haven't found the language in the config nor the one in the locale.
1991 * Now we set it to one of the fallback languages */
1992 if (chosen_language == nullptr) {
1993 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
1996 if (!ReadLanguagePack(chosen_language)) usererror("Can't read language pack '%s'", chosen_language->file);
2000 * Get the ISO language code of the currently loaded language.
2001 * @return the ISO code.
2003 const char *GetCurrentLanguageIsoCode()
2005 return _langpack.langpack->isocode;
2009 * Check whether there are glyphs missing in the current language.
2010 * @return If glyphs are missing, return \c true, else return \c false.
2012 bool MissingGlyphSearcher::FindMissingGlyphs()
2014 InitFontCache(this->Monospace());
2015 const Sprite *question_mark[FS_END];
2017 for (FontSize size = this->Monospace() ? FS_MONO : FS_BEGIN; size < (this->Monospace() ? FS_END : FS_MONO); size++) {
2018 question_mark[size] = GetGlyph(size, '?');
2021 this->Reset();
2022 for (const char *text = this->NextString(); text != nullptr; text = this->NextString()) {
2023 FontSize size = this->DefaultSize();
2024 for (WChar c = Utf8Consume(&text); c != '\0'; c = Utf8Consume(&text)) {
2025 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2026 size = (FontSize)(c - SCC_FIRST_FONT);
2027 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && c != '?' && GetGlyph(size, c) == question_mark[size]) {
2028 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2029 std::string size_name;
2031 switch (size) {
2032 case 0: size_name = "medium"; break;
2033 case 1: size_name = "small"; break;
2034 case 2: size_name = "large"; break;
2035 case 3: size_name = "mono"; break;
2036 default: NOT_REACHED();
2039 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2040 return true;
2044 return false;
2047 /** Helper for searching through the language pack. */
2048 class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
2049 uint i; ///< Iterator for the primary language tables.
2050 uint j; ///< Iterator for the secondary language tables.
2052 void Reset() override
2054 this->i = 0;
2055 this->j = 0;
2058 FontSize DefaultSize() override
2060 return FS_NORMAL;
2063 const char *NextString() override
2065 if (this->i >= TEXT_TAB_END) return nullptr;
2067 const char *ret = _langpack.offsets[_langpack.langtab_start[this->i] + this->j];
2069 this->j++;
2070 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2071 this->i++;
2072 this->j = 0;
2075 return ret;
2078 bool Monospace() override
2080 return false;
2083 void SetFontNames(FontCacheSettings *settings, const char *font_name, const void *os_data) override
2085 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2086 settings->small.font = font_name;
2087 settings->medium.font = font_name;
2088 settings->large.font = font_name;
2090 settings->small.os_handle = os_data;
2091 settings->medium.os_handle = os_data;
2092 settings->large.os_handle = os_data;
2093 #endif
2098 * Check whether the currently loaded language pack
2099 * uses characters that the currently loaded font
2100 * does not support. If this is the case an error
2101 * message will be shown in English. The error
2102 * message will not be localized because that would
2103 * mean it might use characters that are not in the
2104 * font, which is the whole reason this check has
2105 * been added.
2106 * @param base_font Whether to look at the base font as well.
2107 * @param searcher The methods to use to search for strings to check.
2108 * If nullptr the loaded language pack searcher is used.
2110 void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2112 static LanguagePackGlyphSearcher pack_searcher;
2113 if (searcher == nullptr) searcher = &pack_searcher;
2114 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2115 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2116 if (bad_font) {
2117 /* We found an unprintable character... lets try whether we can find
2118 * a fallback font that can print the characters in the current language. */
2119 bool any_font_configured = !_fcsettings.medium.font.empty();
2120 FontCacheSettings backup = _fcsettings;
2122 _fcsettings.mono.os_handle = nullptr;
2123 _fcsettings.medium.os_handle = nullptr;
2125 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2127 _fcsettings = backup;
2129 if (!bad_font && any_font_configured) {
2130 /* If the user configured a bad font, and we found a better one,
2131 * show that we loaded the better font instead of the configured one.
2132 * The colour 'character' might change in the
2133 * future, so for safety we just Utf8 Encode it into the string,
2134 * which takes exactly three characters, so it replaces the "XXX"
2135 * with the colour marker. */
2136 static char *err_str = stredup("XXXThe current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2137 Utf8Encode(err_str, SCC_YELLOW);
2138 SetDParamStr(0, err_str);
2139 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2142 if (bad_font && base_font) {
2143 /* Our fallback font does miss characters too, so keep the
2144 * user chosen font as that is more likely to be any good than
2145 * the wild guess we made */
2146 InitFontCache(searcher->Monospace());
2149 #endif
2151 if (bad_font) {
2152 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2153 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2154 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2155 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2156 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2157 static char *err_str = stredup("XXXThe current font is missing some of the characters used in the texts for this language. Read the readme to see how to solve this.");
2158 Utf8Encode(err_str, SCC_YELLOW);
2159 SetDParamStr(0, err_str);
2160 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2162 /* Reset the font width */
2163 LoadStringWidthTable(searcher->Monospace());
2164 return;
2167 /* Update the font with cache */
2168 LoadStringWidthTable(searcher->Monospace());
2170 #if !defined(WITH_ICU_LX) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2172 * For right-to-left languages we need the ICU library. If
2173 * we do not have support for that library we warn the user
2174 * about it with a message. As we do not want the string to
2175 * be translated by the translators, we 'force' it into the
2176 * binary and 'load' it via a BindCString. To do this
2177 * properly we have to set the colour of the string,
2178 * otherwise we end up with a lot of artifacts. The colour
2179 * 'character' might change in the future, so for safety
2180 * we just Utf8 Encode it into the string, which takes
2181 * exactly three characters, so it replaces the "XXX" with
2182 * the colour marker.
2184 if (_current_text_dir != TD_LTR) {
2185 static char *err_str = stredup("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with icu enabled.");
2186 Utf8Encode(err_str, SCC_YELLOW);
2187 SetDParamStr(0, err_str);
2188 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2190 #endif /* !WITH_ICU_LX */