Codefix: Documentation comment in IndustryDirectoryWindow (#13059)
[openttd-github.git] / src / strings.cpp
blobd95ff909b3ca1c331b9096b89d3fff671928fdeb
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 "error_func.h"
23 #include "strings_func.h"
24 #include "rev.h"
25 #include "core/endian_func.hpp"
26 #include "timer/timer_game_calendar.h"
27 #include "vehicle_base.h"
28 #include "engine_base.h"
29 #include "language.h"
30 #include "townname_func.h"
31 #include "string_func.h"
32 #include "company_base.h"
33 #include "smallmap_gui.h"
34 #include "window_func.h"
35 #include "debug.h"
36 #include "game/game_text.hpp"
37 #include "network/network_content_gui.h"
38 #include "newgrf_engine.h"
39 #include "core/backup_type.hpp"
40 #include "gfx_layout.h"
41 #include <stack>
42 #include <charconv>
44 #include "table/strings.h"
45 #include "table/control_codes.h"
46 #include "3rdparty/fmt/std.h"
48 #include "strings_internal.h"
50 #include "safeguards.h"
52 std::string _config_language_file; ///< The file (name) stored in the configuration.
53 LanguageList _languages; ///< The actual list of language meta data.
54 const LanguageMetadata *_current_language = nullptr; ///< The currently loaded language.
56 TextDirection _current_text_dir; ///< Text direction of the currently selected language.
58 #ifdef WITH_ICU_I18N
59 std::unique_ptr<icu::Collator> _current_collator; ///< Collator for the language currently in use.
60 #endif /* WITH_ICU_I18N */
62 ArrayStringParameters<20> _global_string_params;
64 /**
65 * Prepare the string parameters for the next formatting run. This means
66 * resetting the type information and resetting the offset to the begin.
68 void StringParameters::PrepareForNextRun()
70 for (auto &param : this->parameters) param.type = 0;
71 this->offset = 0;
75 /**
76 * Get the next parameter from our parameters.
77 * This updates the offset, so the next time this is called the next parameter
78 * will be read.
79 * @return The next parameter.
81 const StringParameter &StringParameters::GetNextParameterReference()
83 assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END));
84 if (this->offset >= this->parameters.size()) {
85 throw std::out_of_range("Trying to read invalid string parameter");
88 auto &param = this->parameters[this->offset++];
89 if (param.type != 0 && param.type != this->next_type) {
90 this->next_type = 0;
91 throw std::out_of_range("Trying to read string parameter with wrong type");
93 param.type = this->next_type;
94 this->next_type = 0;
95 return param;
99 /**
100 * Set a string parameter \a v at index \a n in the global string parameter array.
101 * @param n Index of the string parameter.
102 * @param v Value of the string parameter.
104 void SetDParam(size_t n, uint64_t v)
106 _global_string_params.SetParam(n, v);
110 * Get the current string parameter at index \a n from the global string parameter array.
111 * @param n Index of the string parameter.
112 * @return Value of the requested string parameter.
114 uint64_t GetDParam(size_t n)
116 return std::get<uint64_t>(_global_string_params.GetParam(n));
120 * Set DParam n to some number that is suitable for string size computations.
121 * @param n Index of the string parameter.
122 * @param max_value The biggest value which shall be displayed.
123 * For the result only the number of digits of \a max_value matter.
124 * @param min_count Minimum number of digits independent of \a max.
125 * @param size Font of the number
127 void SetDParamMaxValue(size_t n, uint64_t max_value, uint min_count, FontSize size)
129 uint num_digits = 1;
130 while (max_value >= 10) {
131 num_digits++;
132 max_value /= 10;
134 SetDParamMaxDigits(n, std::max(min_count, num_digits), size);
138 * Set DParam n to some number that is suitable for string size computations.
139 * @param n Index of the string parameter.
140 * @param count Number of digits which shall be displayable.
141 * @param size Font of the number
143 void SetDParamMaxDigits(size_t n, uint count, FontSize size)
145 uint front = 0;
146 uint next = 0;
147 GetBroadestDigit(&front, &next, size);
148 uint64_t val = count > 1 ? front : next;
149 for (; count > 1; count--) {
150 val = 10 * val + next;
152 SetDParam(n, val);
156 * Copy the parameters from the backup into the global string parameter array.
157 * @param backup The backup to copy from.
159 void CopyInDParam(const std::span<const StringParameterData> backup)
161 for (size_t i = 0; i < backup.size(); i++) {
162 _global_string_params.SetParam(i, backup[i]);
167 * Copy \a num string parameters from the global string parameter array to the \a backup.
168 * @param backup The backup to write to.
169 * @param num Number of string parameters to copy.
171 void CopyOutDParam(std::vector<StringParameterData> &backup, size_t num)
173 backup.resize(num);
174 for (size_t i = 0; i < backup.size(); i++) {
175 backup[i] = _global_string_params.GetParam(i);
180 * Checks whether the global string parameters have changed compared to the given backup.
181 * @param backup The backup to check against.
182 * @return True when the parameters have changed, otherwise false.
184 bool HaveDParamChanged(const std::span<const StringParameterData> backup)
186 for (size_t i = 0; i < backup.size(); i++) {
187 if (backup[i] != _global_string_params.GetParam(i)) return true;
189 return false;
192 static void StationGetSpecialString(StringBuilder &builder, StationFacility x);
193 static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed);
194 static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args);
196 static void FormatString(StringBuilder &builder, const char *str, StringParameters &args, uint case_index = 0, bool game_script = false, bool dry_run = false);
198 struct LanguagePack : public LanguagePackHeader {
199 char data[]; // list of strings
202 struct LanguagePackDeleter {
203 void operator()(LanguagePack *langpack)
205 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
206 delete[] reinterpret_cast<char*>(langpack);
210 struct LoadedLanguagePack {
211 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
213 std::vector<char *> offsets;
215 std::array<uint, TEXT_TAB_END> langtab_num; ///< Offset into langpack offs
216 std::array<uint, TEXT_TAB_END> langtab_start; ///< Offset into langpack offs
219 static LoadedLanguagePack _langpack;
221 static bool _scan_for_gender_data = false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
224 const char *GetStringPtr(StringID string)
226 switch (GetStringTab(string)) {
227 case TEXT_TAB_GAMESCRIPT_START: return GetGameStringPtr(GetStringIndex(string));
228 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
229 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
230 case TEXT_TAB_NEWGRF_START: return GetGRFStringPtr(GetStringIndex(string));
231 default: return _langpack.offsets[_langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string)];
236 * Get a parsed string with most special stringcodes replaced by the string parameters.
237 * @param builder The builder of the string.
238 * @param string The ID of the string to parse.
239 * @param args Arguments for the string.
240 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
241 * @param game_script The string is coming directly from a game script.
243 void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
245 if (string == 0) {
246 GetStringWithArgs(builder, STR_UNDEFINED, args);
247 return;
250 uint index = GetStringIndex(string);
251 StringTab tab = GetStringTab(string);
253 switch (tab) {
254 case TEXT_TAB_TOWN:
255 if (index >= 0xC0 && !game_script) {
256 try {
257 GetSpecialTownNameString(builder, index - 0xC0, args.GetNextParameter<uint32_t>());
258 } catch (const std::runtime_error &e) {
259 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
260 builder += "(invalid string parameter)";
262 return;
264 break;
266 case TEXT_TAB_SPECIAL:
267 if (index >= 0xE4 && !game_script) {
268 try {
269 GetSpecialNameString(builder, index - 0xE4, args);
270 } catch (const std::runtime_error &e) {
271 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
272 builder += "(invalid string parameter)";
274 return;
276 break;
278 case TEXT_TAB_OLD_CUSTOM:
279 /* Old table for custom names. This is no longer used */
280 if (!game_script) {
281 FatalError("Incorrect conversion of custom name string.");
283 break;
285 case TEXT_TAB_GAMESCRIPT_START: {
286 FormatString(builder, GetGameStringPtr(index), args, case_index, true);
287 return;
290 case TEXT_TAB_OLD_NEWGRF:
291 NOT_REACHED();
293 case TEXT_TAB_NEWGRF_START: {
294 FormatString(builder, GetGRFStringPtr(index), args, case_index);
295 return;
298 default:
299 break;
302 if (index >= _langpack.langtab_num[tab]) {
303 if (game_script) {
304 return GetStringWithArgs(builder, STR_UNDEFINED, args);
306 FatalError("String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n", string);
309 FormatString(builder, GetStringPtr(string), args, case_index);
314 * Resolve the given StringID into a std::string with all the associated
315 * DParam lookups and formatting.
316 * @param string The unique identifier of the translatable string.
317 * @return The std::string of the translated string.
319 std::string GetString(StringID string)
321 _global_string_params.PrepareForNextRun();
322 return GetStringWithArgs(string, _global_string_params);
326 * Resolve the given StringID and append in place into an existing std::string with all the associated
327 * DParam lookups and formatting.
328 * @param result The std::string to place the translated string.
329 * @param string The unique identifier of the translatable string.
331 void AppendStringInPlace(std::string &result, StringID string)
333 _global_string_params.PrepareForNextRun();
334 StringBuilder builder(result);
335 GetStringWithArgs(builder, string, _global_string_params);
339 * Get a parsed string with most special stringcodes replaced by the string parameters.
340 * @param string The ID of the string to parse.
341 * @param args Arguments for the string.
342 * @return The parsed string.
344 std::string GetStringWithArgs(StringID string, StringParameters &args)
346 std::string result;
347 StringBuilder builder(result);
348 GetStringWithArgs(builder, string, args);
349 return result;
353 * This function is used to "bind" a C string to a OpenTTD dparam slot.
354 * @param n slot of the string
355 * @param str string to bind
357 void SetDParamStr(size_t n, const char *str)
359 _global_string_params.SetParam(n, str);
363 * This function is used to "bind" the C string of a std::string to a OpenTTD dparam slot.
364 * The caller has to ensure that the std::string reference remains valid while the string is shown.
365 * @param n slot of the string
366 * @param str string to bind
368 void SetDParamStr(size_t n, const std::string &str)
370 _global_string_params.SetParam(n, str);
374 * This function is used to "bind" the std::string to a OpenTTD dparam slot.
375 * Contrary to the other \c SetDParamStr functions, this moves the string into
376 * the parameter slot.
377 * @param n slot of the string
378 * @param str string to bind
380 void SetDParamStr(size_t n, std::string &&str)
382 _global_string_params.SetParam(n, std::move(str));
385 static const char *GetDecimalSeparator()
387 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
388 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
389 return decimal_separator;
393 * Format a number into a string.
394 * @param builder the string builder to write to
395 * @param number the number to write down
396 * @param separator the thousands-separator to use
398 static void FormatNumber(StringBuilder &builder, int64_t number, const char *separator)
400 static const int max_digits = 20;
401 uint64_t divisor = 10000000000000000000ULL;
402 int thousands_offset = (max_digits - 1) % 3;
404 if (number < 0) {
405 builder += '-';
406 number = -number;
409 uint64_t num = number;
410 uint64_t tot = 0;
411 for (int i = 0; i < max_digits; i++) {
412 uint64_t quot = 0;
413 if (num >= divisor) {
414 quot = num / divisor;
415 num = num % divisor;
417 if ((tot |= quot) || i == max_digits - 1) {
418 builder += '0' + quot; // quot is a single digit
419 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
422 divisor /= 10;
426 static void FormatCommaNumber(StringBuilder &builder, int64_t number)
428 const char *separator = _settings_game.locale.digit_group_separator.c_str();
429 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
430 FormatNumber(builder, number, separator);
433 static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
435 fmt::format_to(builder, "{}", number);
438 static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
440 fmt::format_to(builder, "{:0{}d}", number, count);
443 static void FormatHexNumber(StringBuilder &builder, uint64_t number)
445 fmt::format_to(builder, "0x{:X}", number);
449 * Format a given number as a number of bytes with the SI prefix.
450 * @param builder the string builder to write to
451 * @param number the number of bytes to write down
453 static void FormatBytes(StringBuilder &builder, int64_t number)
455 assert(number >= 0);
457 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
458 const char * const iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
459 uint id = 1;
460 while (number >= 1024 * 1024) {
461 number /= 1024;
462 id++;
465 if (number < 1024) {
466 id = 0;
467 fmt::format_to(builder, "{}", number);
468 } else if (number < 1024 * 10) {
469 fmt::format_to(builder, "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
470 } else if (number < 1024 * 100) {
471 fmt::format_to(builder, "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
472 } else {
473 assert(number < 1024 * 1024);
474 fmt::format_to(builder, "{}", number / 1024);
477 assert(id < lengthof(iec_prefixes));
478 fmt::format_to(builder, NBSP "{}B", iec_prefixes[id]);
481 static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
483 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
485 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
486 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
489 static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
491 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
493 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
494 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
497 static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
499 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
501 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
502 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
503 FormatString(builder, GetStringPtr(str), tmp_params);
506 static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
508 /* We are going to make number absolute for printing, so
509 * keep this piece of data as we need it later on */
510 bool negative = number < 0;
512 number *= spec->rate;
514 /* convert from negative */
515 if (number < 0) {
516 builder.Utf8Encode(SCC_PUSH_COLOUR);
517 builder.Utf8Encode(SCC_RED);
518 builder += '-';
519 number = -number;
522 /* Add prefix part, following symbol_pos specification.
523 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
524 * The only remaining value is 1 (suffix), so everything that is not 1 */
525 if (spec->symbol_pos != 1) builder += spec->prefix;
527 StringID number_str = STR_NULL;
529 /* For huge numbers, compact the number. */
530 if (compact) {
531 /* Take care of the thousand rounding. Having 1 000 000 k
532 * and 1 000 M is inconsistent, so always use 1 000 M. */
533 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
534 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
535 number_str = STR_CURRENCY_SHORT_TERA;
536 } else if (number >= Money(1'000'000'000'000) - 500'000) {
537 number = (number + 500'000'000) / 1'000'000'000;
538 number_str = STR_CURRENCY_SHORT_GIGA;
539 } else if (number >= 1'000'000'000 - 500) {
540 number = (number + 500'000) / 1'000'000;
541 number_str = STR_CURRENCY_SHORT_MEGA;
542 } else if (number >= 1'000'000) {
543 number = (number + 500) / 1'000;
544 number_str = STR_CURRENCY_SHORT_KILO;
548 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
549 if (StrEmpty(separator)) separator = GetCurrency().separator.c_str();
550 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
551 FormatNumber(builder, number, separator);
552 if (number_str != STR_NULL) {
553 auto tmp_params = ArrayStringParameters<0>();
554 FormatString(builder, GetStringPtr(number_str), tmp_params);
557 /* Add suffix part, following symbol_pos specification.
558 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
559 * The only remaining value is 1 (prefix), so everything that is not 0 */
560 if (spec->symbol_pos != 0) builder += spec->suffix;
562 if (negative) {
563 builder.Utf8Encode(SCC_POP_COLOUR);
568 * Determine the "plural" index given a plural form and a number.
569 * @param count The number to get the plural index of.
570 * @param plural_form The plural form we want an index for.
571 * @return The plural index for the given form.
573 static int DeterminePluralForm(int64_t count, int plural_form)
575 /* The absolute value determines plurality */
576 uint64_t n = abs(count);
578 switch (plural_form) {
579 default:
580 NOT_REACHED();
582 /* Two forms: singular used for one only.
583 * Used in:
584 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
585 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
586 case 0:
587 return n != 1 ? 1 : 0;
589 /* Only one form.
590 * Used in:
591 * Hungarian, Japanese, Turkish */
592 case 1:
593 return 0;
595 /* Two forms: singular used for 0 and 1.
596 * Used in:
597 * French, Brazilian Portuguese */
598 case 2:
599 return n > 1 ? 1 : 0;
601 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
602 * Note: Cases are out of order for hysterical reasons. '0' is last.
603 * Used in:
604 * Latvian */
605 case 3:
606 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
608 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
609 * Used in:
610 * Gaelige (Irish) */
611 case 4:
612 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
614 /* 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.
615 * Used in:
616 * Lithuanian */
617 case 5:
618 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
620 /* 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.
621 * Used in:
622 * Croatian, Russian, Ukrainian */
623 case 6:
624 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
626 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
627 * Used in:
628 * Polish */
629 case 7:
630 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
632 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
633 * Used in:
634 * Slovenian */
635 case 8:
636 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
638 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
639 * Used in:
640 * Icelandic */
641 case 9:
642 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
644 /* Three forms: special cases for 1, and 2 to 4
645 * Used in:
646 * Czech, Slovak */
647 case 10:
648 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
650 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
651 * Korean doesn't have the concept of plural, but depending on how a
652 * number is pronounced it needs another version of a particle.
653 * As such the plural system is misused to give this distinction.
655 case 11:
656 switch (n % 10) {
657 case 0: // yeong
658 case 1: // il
659 case 3: // sam
660 case 6: // yuk
661 case 7: // chil
662 case 8: // pal
663 return 0;
665 case 2: // i
666 case 4: // sa
667 case 5: // o
668 case 9: // gu
669 return 1;
671 default:
672 NOT_REACHED();
675 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
676 * Used in:
677 * Maltese */
678 case 12:
679 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
680 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
681 * Used in:
682 * Scottish Gaelic */
683 case 13:
684 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
686 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
687 * Used in:
688 * Romanian */
689 case 14:
690 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
694 static const char *ParseStringChoice(const char *b, uint form, StringBuilder &builder)
696 /* <NUM> {Length of each string} {each string} */
697 uint n = (uint8_t)*b++;
698 uint pos, i, mypos = 0;
700 for (i = pos = 0; i != n; i++) {
701 uint len = (uint8_t)*b++;
702 if (i == form) mypos = pos;
703 pos += len;
706 builder += b + mypos;
707 return b + pos;
710 /** Helper for unit conversion. */
711 struct UnitConversion {
712 double factor; ///< Amount to multiply or divide upon conversion.
715 * Convert value from OpenTTD's internal unit into the displayed value.
716 * @param input The input to convert.
717 * @param round Whether to round the value or not.
718 * @return The converted value.
720 int64_t ToDisplay(int64_t input, bool round = true) const
722 return round
723 ? (int64_t)std::round(input * this->factor)
724 : (int64_t)(input * this->factor);
728 * Convert the displayed value back into a value of OpenTTD's internal unit.
729 * @param input The input to convert.
730 * @param round Whether to round the value up or not.
731 * @param divider Divide the return value by this.
732 * @return The converted value.
734 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
736 return round
737 ? (int64_t)std::round(input / this->factor / divider)
738 : (int64_t)(input / this->factor / divider);
742 /** Information about a specific unit system. */
743 struct Units {
744 UnitConversion c; ///< Conversion
745 StringID s; ///< String for the unit
746 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.
749 /** Information about a specific unit system with a long variant. */
750 struct UnitsLong {
751 UnitConversion c; ///< Conversion
752 StringID s; ///< String for the short variant of the unit
753 StringID l; ///< String for the long variant of the unit
754 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.
757 /** Unit conversions for velocity. */
758 static const Units _units_velocity_calendar[] = {
759 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
760 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
761 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
762 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
763 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
766 /** Unit conversions for velocity. */
767 static const Units _units_velocity_realtime[] = {
768 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
769 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
770 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
771 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
772 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
775 /** Unit conversions for power. */
776 static const Units _units_power[] = {
777 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
778 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
779 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
782 /** Unit conversions for power to weight. */
783 static const Units _units_power_to_weight[] = {
784 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
785 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
786 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
787 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
788 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
789 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
790 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
791 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
792 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
795 /** Unit conversions for weight. */
796 static const UnitsLong _units_weight[] = {
797 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
798 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
799 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
802 /** Unit conversions for volume. */
803 static const UnitsLong _units_volume[] = {
804 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
805 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
806 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
809 /** Unit conversions for force. */
810 static const Units _units_force[] = {
811 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
812 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
813 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
816 /** Unit conversions for height. */
817 static const Units _units_height[] = {
818 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
819 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
820 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
823 /** Unit conversions for time in calendar days or wallclock seconds */
824 static const Units _units_time_days_or_seconds[] = {
825 { { 1 }, STR_UNITS_DAYS, 0 },
826 { { 2 }, STR_UNITS_SECONDS, 0 },
829 /** Unit conversions for time in calendar months or wallclock minutes */
830 static const Units _units_time_months_or_minutes[] = {
831 { { 1 }, STR_UNITS_MONTHS, 0 },
832 { { 1 }, STR_UNITS_MINUTES, 0 },
835 /** Unit conversions for time in calendar years or economic periods */
836 static const Units _units_time_years_or_periods[] = {
837 { { 1 }, STR_UNITS_YEARS, 0 },
838 { { 1 }, STR_UNITS_PERIODS, 0 },
841 /** Unit conversions for time in calendar years or wallclock minutes */
842 static const Units _units_time_years_or_minutes[] = {
843 { { 1 }, STR_UNITS_YEARS, 0 },
844 { { 12 }, STR_UNITS_MINUTES, 0 },
848 * Get the correct velocity units depending on the vehicle type and whether we're using real-time units.
849 * @param type VehicleType to convert velocity for.
850 * @return The Units for the proper vehicle and time mode.
852 static const Units GetVelocityUnits(VehicleType type)
854 uint8_t setting = (type == VEH_SHIP || type == VEH_AIRCRAFT) ? _settings_game.locale.units_velocity_nautical : _settings_game.locale.units_velocity;
856 assert(setting < lengthof(_units_velocity_calendar));
857 assert(setting < lengthof(_units_velocity_realtime));
859 if (TimerGameEconomy::UsingWallclockUnits()) return _units_velocity_realtime[setting];
861 return _units_velocity_calendar[setting];
865 * Convert the given (internal) speed to the display speed.
866 * @param speed the speed to convert
867 * @return the converted speed.
869 uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
871 /* For historical reasons we don't want to mess with the
872 * conversion for speed. So, don't round it and keep the
873 * original conversion factors instead of the real ones. */
874 return GetVelocityUnits(type).c.ToDisplay(speed, false);
878 * Convert the given display speed to the (internal) speed.
879 * @param speed the speed to convert
880 * @return the converted speed.
882 uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
884 return GetVelocityUnits(type).c.FromDisplay(speed);
888 * Convert the given km/h-ish speed to the display speed.
889 * @param speed the speed to convert
890 * @return the converted speed.
892 uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
894 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
898 * Convert the given display speed to the km/h-ish speed.
899 * @param speed the speed to convert
900 * @return the converted speed.
902 uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
904 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
908 * Parse most format codes within a string and write the result to a buffer.
909 * @param builder The string builder to write the final string to.
910 * @param str_arg The original string with format codes.
911 * @param args Pointer to extra arguments used by various string codes.
912 * @param dry_run True when the args' type data is not yet initialized.
914 static void FormatString(StringBuilder &builder, const char *str_arg, StringParameters &args, uint case_index, bool game_script, bool dry_run)
916 size_t orig_offset = args.GetOffset();
918 if (!dry_run) {
920 * This function is normally called with `dry_run` false, then we call this function again
921 * with `dry_run` being true. The dry run is required for the gender formatting. For the
922 * gender determination we need to format a sub string to get the gender, but for that we
923 * need to know as what string control code type the specific parameter is encoded. Since
924 * gendered words can be before the "parameter" words, this needs to be determined before
925 * the actual formatting.
927 std::string buffer;
928 StringBuilder dry_run_builder(buffer);
929 if (UsingNewGRFTextStack()) {
930 /* Values from the NewGRF text stack are only copied to the normal
931 * argv array at the time they are encountered. That means that if
932 * another string command references a value later in the string it
933 * would fail. We solve that by running FormatString twice. The first
934 * pass makes sure the argv array is correctly filled and the second
935 * pass can reference later values without problems. */
936 struct TextRefStack *backup = CreateTextRefStackBackup();
937 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
938 RestoreTextRefStackBackup(backup);
939 } else {
940 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
942 /* We have to restore the original offset here to to read the correct values. */
943 args.SetOffset(orig_offset);
945 char32_t b = '\0';
946 uint next_substr_case_index = 0;
947 std::stack<const char *, std::vector<const char *>> str_stack;
948 str_stack.push(str_arg);
950 for (;;) {
951 try {
952 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
953 str_stack.pop();
955 if (str_stack.empty()) break;
956 const char *&str = str_stack.top();
958 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
959 /* We need to pass some stuff as it might be modified. */
960 StringParameters remaining = args.GetRemainingParameters();
961 b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run);
962 if (b == 0) continue;
965 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
966 builder.Utf8Encode(b);
967 continue;
970 args.SetTypeOfNextParameter(b);
971 switch (b) {
972 case SCC_ENCODED: {
973 ArrayStringParameters<20> sub_args;
975 char *p;
976 uint32_t stringid = std::strtoul(str, &p, 16);
977 if (*p != ':' && *p != '\0') {
978 while (*p != '\0') p++;
979 str = p;
980 builder += "(invalid SCC_ENCODED)";
981 break;
983 if (stringid >= TAB_SIZE_GAMESCRIPT) {
984 while (*p != '\0') p++;
985 str = p;
986 builder += "(invalid StringID)";
987 break;
990 int i = 0;
991 while (*p != '\0' && i < 20) {
992 uint64_t param;
993 const char *s = ++p;
995 /* Find the next value */
996 bool instring = false;
997 bool escape = false;
998 for (;; p++) {
999 if (*p == '\\') {
1000 escape = true;
1001 continue;
1003 if (*p == '"' && escape) {
1004 escape = false;
1005 continue;
1007 escape = false;
1009 if (*p == '"') {
1010 instring = !instring;
1011 continue;
1013 if (instring) {
1014 continue;
1017 if (*p == ':') break;
1018 if (*p == '\0') break;
1021 if (*s != '"') {
1022 /* Check if we want to look up another string */
1023 char32_t l;
1024 size_t len = Utf8Decode(&l, s);
1025 bool lookup = (l == SCC_ENCODED);
1026 if (lookup) s += len;
1028 param = std::strtoull(s, &p, 16);
1030 if (lookup) {
1031 if (param >= TAB_SIZE_GAMESCRIPT) {
1032 while (*p != '\0') p++;
1033 str = p;
1034 builder += "(invalid sub-StringID)";
1035 break;
1037 param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param);
1040 sub_args.SetParam(i++, param);
1041 } else {
1042 s++; // skip the leading \"
1043 sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \".
1046 /* If we didn't error out, we can actually print the string. */
1047 if (*str != '\0') {
1048 str = p;
1049 GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true);
1051 break;
1054 case SCC_NEWGRF_STRINL: {
1055 StringID substr = Utf8Consume(&str);
1056 str_stack.push(GetStringPtr(substr));
1057 break;
1060 case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
1061 StringID substr = args.GetNextParameter<StringID>();
1062 str_stack.push(GetStringPtr(substr));
1063 case_index = next_substr_case_index;
1064 next_substr_case_index = 0;
1065 break;
1069 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1070 /* First read the meta data from the language file. */
1071 size_t offset = orig_offset + (uint8_t)*str++;
1072 int gender = 0;
1073 if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1074 /* Now we need to figure out what text to resolve, i.e.
1075 * what do we need to draw? So get the actual raw string
1076 * first using the control code to get said string. */
1077 char input[4 + 1];
1078 char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset));
1079 *p = '\0';
1081 /* The gender is stored at the start of the formatted string. */
1082 bool old_sgd = _scan_for_gender_data;
1083 _scan_for_gender_data = true;
1084 std::string buffer;
1085 StringBuilder tmp_builder(buffer);
1086 StringParameters tmp_params = args.GetRemainingParameters(offset);
1087 FormatString(tmp_builder, input, tmp_params);
1088 _scan_for_gender_data = old_sgd;
1090 /* And determine the string. */
1091 const char *s = buffer.c_str();
1092 char32_t c = Utf8Consume(&s);
1093 /* Does this string have a gender, if so, set it */
1094 if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0];
1096 str = ParseStringChoice(str, gender, builder);
1097 break;
1100 /* This sets up the gender for the string.
1101 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1102 case SCC_GENDER_INDEX: // {GENDER 0}
1103 if (_scan_for_gender_data) {
1104 builder.Utf8Encode(SCC_GENDER_INDEX);
1105 builder += *str++;
1106 } else {
1107 str++;
1109 break;
1111 case SCC_PLURAL_LIST: { // {P}
1112 int plural_form = *str++; // contains the plural form for this string
1113 size_t offset = orig_offset + (uint8_t)*str++;
1114 const uint64_t *v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1115 if (v != nullptr) {
1116 str = ParseStringChoice(str, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1117 } else {
1118 builder += "(invalid PLURAL parameter)";
1120 break;
1123 case SCC_ARG_INDEX: { // Move argument pointer
1124 args.SetOffset(orig_offset + (uint8_t)*str++);
1125 break;
1128 case SCC_SET_CASE: { // {SET_CASE}
1129 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1130 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1131 next_substr_case_index = (uint8_t)*str++;
1132 break;
1135 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1136 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1137 * Each LEN is printed using 2 bytes in big endian order. */
1138 uint num = (uint8_t)*str++;
1139 while (num) {
1140 if ((uint8_t)str[0] == case_index) {
1141 /* Found the case, adjust str pointer and continue */
1142 str += 3;
1143 break;
1145 /* Otherwise skip to the next case */
1146 str += 3 + (str[1] << 8) + str[2];
1147 num--;
1149 break;
1152 case SCC_REVISION: // {REV}
1153 builder += _openttd_revision;
1154 break;
1156 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
1157 const char *raw_string = args.GetNextParameterString();
1158 /* raw_string can be nullptr. */
1159 if (raw_string == nullptr) {
1160 builder += "(invalid RAW_STRING parameter)";
1161 break;
1163 FormatString(builder, raw_string, args);
1164 break;
1167 case SCC_STRING: {// {STRING}
1168 StringID string_id = args.GetNextParameter<StringID>();
1169 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1170 /* It's prohibited for the included string to consume any arguments. */
1171 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1172 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1173 next_substr_case_index = 0;
1174 break;
1177 case SCC_STRING1:
1178 case SCC_STRING2:
1179 case SCC_STRING3:
1180 case SCC_STRING4:
1181 case SCC_STRING5:
1182 case SCC_STRING6:
1183 case SCC_STRING7: { // {STRING1..7}
1184 /* Strings that consume arguments */
1185 StringID string_id = args.GetNextParameter<StringID>();
1186 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1187 uint size = b - SCC_STRING1 + 1;
1188 if (game_script && size > args.GetDataLeft()) {
1189 builder += "(too many parameters)";
1190 } else {
1191 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1192 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1193 args.AdvanceOffset(size);
1195 next_substr_case_index = 0;
1196 break;
1199 case SCC_COMMA: // {COMMA}
1200 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1201 break;
1203 case SCC_DECIMAL: { // {DECIMAL}
1204 int64_t number = args.GetNextParameter<int64_t>();
1205 int digits = args.GetNextParameter<int>();
1206 if (digits == 0) {
1207 FormatCommaNumber(builder, number);
1208 break;
1211 int64_t divisor = PowerOfTen(digits);
1212 int64_t fractional = number % divisor;
1213 number /= divisor;
1214 FormatCommaNumber(builder, number);
1215 fmt::format_to(builder, "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1216 break;
1219 case SCC_NUM: // {NUM}
1220 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1221 break;
1223 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1224 int64_t num = args.GetNextParameter<int64_t>();
1225 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1226 break;
1229 case SCC_HEX: // {HEX}
1230 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1231 break;
1233 case SCC_BYTES: // {BYTES}
1234 FormatBytes(builder, args.GetNextParameter<int64_t>());
1235 break;
1237 case SCC_CARGO_TINY: { // {CARGO_TINY}
1238 /* Tiny description of cargotypes. Layout:
1239 * param 1: cargo type
1240 * param 2: cargo count */
1241 CargoID cargo = args.GetNextParameter<CargoID>();
1242 if (cargo >= CargoSpec::GetArraySize()) break;
1244 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1245 int64_t amount = 0;
1246 switch (cargo_str) {
1247 case STR_TONS:
1248 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args.GetNextParameter<int64_t>());
1249 break;
1251 case STR_LITERS:
1252 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args.GetNextParameter<int64_t>());
1253 break;
1255 default: {
1256 amount = args.GetNextParameter<int64_t>();
1257 break;
1261 FormatCommaNumber(builder, amount);
1262 break;
1265 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1266 /* Short description of cargotypes. Layout:
1267 * param 1: cargo type
1268 * param 2: cargo count */
1269 CargoID cargo = args.GetNextParameter<CargoID>();
1270 if (cargo >= CargoSpec::GetArraySize()) break;
1272 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1273 switch (cargo_str) {
1274 case STR_TONS: {
1275 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1276 const auto &x = _units_weight[_settings_game.locale.units_weight];
1277 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1278 FormatString(builder, GetStringPtr(x.l), tmp_params);
1279 break;
1282 case STR_LITERS: {
1283 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1284 const auto &x = _units_volume[_settings_game.locale.units_volume];
1285 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1286 FormatString(builder, GetStringPtr(x.l), tmp_params);
1287 break;
1290 default: {
1291 auto tmp_params = MakeParameters(args.GetNextParameter<int64_t>());
1292 GetStringWithArgs(builder, cargo_str, tmp_params);
1293 break;
1296 break;
1299 case SCC_CARGO_LONG: { // {CARGO_LONG}
1300 /* First parameter is cargo type, second parameter is cargo count */
1301 CargoID cargo = args.GetNextParameter<CargoID>();
1302 if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break;
1304 StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1305 auto tmp_args = MakeParameters(args.GetNextParameter<int64_t>());
1306 GetStringWithArgs(builder, cargo_str, tmp_args);
1307 break;
1310 case SCC_CARGO_LIST: { // {CARGO_LIST}
1311 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1312 bool first = true;
1314 for (const auto &cs : _sorted_cargo_specs) {
1315 if (!HasBit(cmask, cs->Index())) continue;
1317 if (first) {
1318 first = false;
1319 } else {
1320 /* Add a comma if this is not the first item */
1321 builder += ", ";
1324 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1327 /* If first is still true then no cargo is accepted */
1328 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1330 next_substr_case_index = 0;
1331 break;
1334 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1335 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1336 break;
1338 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1339 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1340 break;
1342 case SCC_DATE_TINY: // {DATE_TINY}
1343 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1344 break;
1346 case SCC_DATE_SHORT: // {DATE_SHORT}
1347 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1348 next_substr_case_index = 0;
1349 break;
1351 case SCC_DATE_LONG: // {DATE_LONG}
1352 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1353 next_substr_case_index = 0;
1354 break;
1356 case SCC_DATE_ISO: // {DATE_ISO}
1357 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1358 break;
1360 case SCC_FORCE: { // {FORCE}
1361 assert(_settings_game.locale.units_force < lengthof(_units_force));
1362 const auto &x = _units_force[_settings_game.locale.units_force];
1363 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1364 FormatString(builder, GetStringPtr(x.s), tmp_params);
1365 break;
1368 case SCC_HEIGHT: { // {HEIGHT}
1369 assert(_settings_game.locale.units_height < lengthof(_units_height));
1370 const auto &x = _units_height[_settings_game.locale.units_height];
1371 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1372 FormatString(builder, GetStringPtr(x.s), tmp_params);
1373 break;
1376 case SCC_POWER: { // {POWER}
1377 assert(_settings_game.locale.units_power < lengthof(_units_power));
1378 const auto &x = _units_power[_settings_game.locale.units_power];
1379 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1380 FormatString(builder, GetStringPtr(x.s), tmp_params);
1381 break;
1384 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1385 auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight;
1386 assert(setting < lengthof(_units_power_to_weight));
1387 const auto &x = _units_power_to_weight[setting];
1388 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1389 FormatString(builder, GetStringPtr(x.s), tmp_params);
1390 break;
1393 case SCC_VELOCITY: { // {VELOCITY}
1394 int64_t arg = args.GetNextParameter<int64_t>();
1395 // Unpack vehicle type from packed argument to get desired units.
1396 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1397 const auto &x = GetVelocityUnits(vt);
1398 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1399 FormatString(builder, GetStringPtr(x.s), tmp_params);
1400 break;
1403 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1404 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1405 const auto &x = _units_volume[_settings_game.locale.units_volume];
1406 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1407 FormatString(builder, GetStringPtr(x.s), tmp_params);
1408 break;
1411 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1412 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1413 const auto &x = _units_volume[_settings_game.locale.units_volume];
1414 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1415 FormatString(builder, GetStringPtr(x.l), tmp_params);
1416 break;
1419 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1420 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1421 const auto &x = _units_weight[_settings_game.locale.units_weight];
1422 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1423 FormatString(builder, GetStringPtr(x.s), tmp_params);
1424 break;
1427 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1428 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1429 const auto &x = _units_weight[_settings_game.locale.units_weight];
1430 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1431 FormatString(builder, GetStringPtr(x.l), tmp_params);
1432 break;
1435 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1436 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1437 const auto &x = _units_time_days_or_seconds[realtime];
1438 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1439 FormatString(builder, GetStringPtr(x.s), tmp_params);
1440 break;
1443 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1444 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1445 const auto &x = _units_time_months_or_minutes[realtime];
1446 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1447 FormatString(builder, GetStringPtr(x.s), tmp_params);
1448 break;
1451 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1452 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1453 const auto &x = _units_time_years_or_periods[realtime];
1454 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1455 FormatString(builder, GetStringPtr(x.s), tmp_params);
1456 break;
1459 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1460 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1461 const auto &x = _units_time_years_or_minutes[realtime];
1462 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1463 FormatString(builder, GetStringPtr(x.s), tmp_params);
1464 break;
1467 case SCC_COMPANY_NAME: { // {COMPANY}
1468 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1469 if (c == nullptr) break;
1471 if (!c->name.empty()) {
1472 auto tmp_params = MakeParameters(c->name);
1473 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1474 } else {
1475 auto tmp_params = MakeParameters(c->name_2);
1476 GetStringWithArgs(builder, c->name_1, tmp_params);
1478 break;
1481 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1482 CompanyID company = args.GetNextParameter<CompanyID>();
1484 /* Nothing is added for AI or inactive companies */
1485 if (Company::IsValidHumanID(company)) {
1486 auto tmp_params = MakeParameters(company + 1);
1487 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1489 break;
1492 case SCC_DEPOT_NAME: { // {DEPOT}
1493 VehicleType vt = args.GetNextParameter<VehicleType>();
1494 if (vt == VEH_AIRCRAFT) {
1495 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1496 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1497 break;
1500 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1501 if (!d->name.empty()) {
1502 auto tmp_params = MakeParameters(d->name);
1503 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1504 } else {
1505 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1506 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1508 break;
1511 case SCC_ENGINE_NAME: { // {ENGINE}
1512 int64_t arg = args.GetNextParameter<int64_t>();
1513 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1514 if (e == nullptr) break;
1516 if (!e->name.empty() && e->IsEnabled()) {
1517 auto tmp_params = MakeParameters(e->name);
1518 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1519 break;
1522 if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) {
1523 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr);
1524 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1525 if (callback != CALLBACK_FAILED && callback < 0x400) {
1526 const GRFFile *grffile = e->GetGRF();
1527 assert(grffile != nullptr);
1529 StartTextRefStackUsage(grffile, 6);
1530 ArrayStringParameters<6> tmp_params;
1531 GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params);
1532 StopTextRefStackUsage();
1534 break;
1538 auto tmp_params = ArrayStringParameters<0>();
1539 GetStringWithArgs(builder, e->info.string_id, tmp_params);
1540 break;
1543 case SCC_GROUP_NAME: { // {GROUP}
1544 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1545 if (g == nullptr) break;
1547 if (!g->name.empty()) {
1548 auto tmp_params = MakeParameters(g->name);
1549 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1550 } else {
1551 auto tmp_params = MakeParameters(g->number);
1552 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1554 break;
1557 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1558 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1559 if (i == nullptr) break;
1561 static bool use_cache = true;
1562 if (_scan_for_gender_data) {
1563 /* Gender is defined by the industry type.
1564 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1565 auto tmp_params = ArrayStringParameters<0>();
1566 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index);
1567 } else if (use_cache) { // Use cached version if first call
1568 AutoRestoreBackup cache_backup(use_cache, false);
1569 builder += i->GetCachedName();
1570 } else {
1571 /* First print the town name and the industry type name. */
1572 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1573 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1575 next_substr_case_index = 0;
1576 break;
1579 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1580 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1581 if (c == nullptr) break;
1583 if (!c->president_name.empty()) {
1584 auto tmp_params = MakeParameters(c->president_name);
1585 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1586 } else {
1587 auto tmp_params = MakeParameters(c->president_name_2);
1588 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1590 break;
1593 case SCC_STATION_NAME: { // {STATION}
1594 StationID sid = args.GetNextParameter<StationID>();
1595 const Station *st = Station::GetIfValid(sid);
1597 if (st == nullptr) {
1598 /* The station doesn't exist anymore. The only place where we might
1599 * be "drawing" an invalid station is in the case of cargo that is
1600 * in transit. */
1601 auto tmp_params = ArrayStringParameters<0>();
1602 GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params);
1603 break;
1606 static bool use_cache = true;
1607 if (use_cache) { // Use cached version if first call
1608 AutoRestoreBackup cache_backup(use_cache, false);
1609 builder += st->GetCachedName();
1610 } else if (!st->name.empty()) {
1611 auto tmp_params = MakeParameters(st->name);
1612 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1613 } else {
1614 StringID string_id = st->string_id;
1615 if (st->indtype != IT_INVALID) {
1616 /* Special case where the industry provides the name for the station */
1617 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1619 /* Industry GRFs can change which might remove the station name and
1620 * thus cause very strange things. Here we check for that before we
1621 * actually set the station name. */
1622 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1623 string_id = indsp->station_name;
1627 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1628 GetStringWithArgs(builder, string_id, tmp_params);
1630 break;
1633 case SCC_TOWN_NAME: { // {TOWN}
1634 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1635 if (t == nullptr) break;
1637 static bool use_cache = true;
1638 if (use_cache) { // Use cached version if first call
1639 AutoRestoreBackup cache_backup(use_cache, false);
1640 builder += t->GetCachedName();
1641 } else if (!t->name.empty()) {
1642 auto tmp_params = MakeParameters(t->name);
1643 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1644 } else {
1645 GetTownName(builder, t);
1647 break;
1650 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1651 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1652 if (wp == nullptr) break;
1654 if (!wp->name.empty()) {
1655 auto tmp_params = MakeParameters(wp->name);
1656 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1657 } else {
1658 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1659 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1660 if (wp->town_cn != 0) string_id++;
1661 GetStringWithArgs(builder, string_id, tmp_params);
1663 break;
1666 case SCC_VEHICLE_NAME: { // {VEHICLE}
1667 const Vehicle *v = Vehicle::GetIfValid(args.GetNextParameter<VehicleID>());
1668 if (v == nullptr) break;
1670 if (!v->name.empty()) {
1671 auto tmp_params = MakeParameters(v->name);
1672 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1673 } else if (v->group_id != DEFAULT_GROUP) {
1674 /* The vehicle has no name, but is member of a group, so print group name */
1675 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1676 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1677 } else {
1678 auto tmp_params = MakeParameters(v->unitnumber);
1680 StringID string_id;
1681 switch (v->type) {
1682 default: string_id = STR_INVALID_VEHICLE; break;
1683 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1684 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1685 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1686 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1689 GetStringWithArgs(builder, string_id, tmp_params);
1691 break;
1694 case SCC_SIGN_NAME: { // {SIGN}
1695 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1696 if (si == nullptr) break;
1698 if (!si->name.empty()) {
1699 auto tmp_params = MakeParameters(si->name);
1700 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1701 } else {
1702 auto tmp_params = ArrayStringParameters<0>();
1703 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params);
1705 break;
1708 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1709 StationGetSpecialString(builder, args.GetNextParameter<StationFacility>());
1710 break;
1713 case SCC_COLOUR: { // {COLOUR}
1714 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1715 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc);
1716 break;
1719 default:
1720 builder.Utf8Encode(b);
1721 break;
1723 } catch (std::out_of_range &e) {
1724 Debug(misc, 0, "FormatString: {}", e.what());
1725 builder += "(invalid parameter)";
1731 static void StationGetSpecialString(StringBuilder &builder, StationFacility x)
1733 if ((x & FACIL_TRAIN) != 0) builder.Utf8Encode(SCC_TRAIN);
1734 if ((x & FACIL_TRUCK_STOP) != 0) builder.Utf8Encode(SCC_LORRY);
1735 if ((x & FACIL_BUS_STOP) != 0) builder.Utf8Encode(SCC_BUS);
1736 if ((x & FACIL_DOCK) != 0) builder.Utf8Encode(SCC_SHIP);
1737 if ((x & FACIL_AIRPORT) != 0) builder.Utf8Encode(SCC_PLANE);
1740 static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed)
1742 GenerateTownNameString(builder, ind, seed);
1745 static const char * const _silly_company_names[] = {
1746 "Bloggs Brothers",
1747 "Tiny Transport Ltd.",
1748 "Express Travel",
1749 "Comfy-Coach & Co.",
1750 "Crush & Bump Ltd.",
1751 "Broken & Late Ltd.",
1752 "Sam Speedy & Son",
1753 "Supersonic Travel",
1754 "Mike's Motors",
1755 "Lightning International",
1756 "Pannik & Loozit Ltd.",
1757 "Inter-City Transport",
1758 "Getout & Pushit Ltd."
1761 static const char * const _surname_list[] = {
1762 "Adams",
1763 "Allan",
1764 "Baker",
1765 "Bigwig",
1766 "Black",
1767 "Bloggs",
1768 "Brown",
1769 "Campbell",
1770 "Gordon",
1771 "Hamilton",
1772 "Hawthorn",
1773 "Higgins",
1774 "Green",
1775 "Gribble",
1776 "Jones",
1777 "McAlpine",
1778 "MacDonald",
1779 "McIntosh",
1780 "Muir",
1781 "Murphy",
1782 "Nelson",
1783 "O'Donnell",
1784 "Parker",
1785 "Phillips",
1786 "Pilkington",
1787 "Quigley",
1788 "Sharkey",
1789 "Thomson",
1790 "Watkins"
1793 static const char * const _silly_surname_list[] = {
1794 "Grumpy",
1795 "Dozy",
1796 "Speedy",
1797 "Nosey",
1798 "Dribble",
1799 "Mushroom",
1800 "Cabbage",
1801 "Sniffle",
1802 "Fishy",
1803 "Swindle",
1804 "Sneaky",
1805 "Nutkins"
1808 static const char _initial_name_letters[] = {
1809 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1810 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1813 static std::span<const char * const> GetSurnameOptions()
1815 if (_settings_game.game_creation.landscape == LT_TOYLAND) return _silly_surname_list;
1816 return _surname_list;
1820 * Get the surname of the president with the given seed.
1821 * @param seed The seed the surname was generated from.
1822 * @return The surname.
1824 static const char *GetSurname(uint32_t seed)
1826 auto surname_options = GetSurnameOptions();
1827 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1830 static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1832 builder += GetSurname(seed);
1833 builder += " & Co.";
1836 static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1838 builder += _initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8];
1839 builder += ". ";
1841 /* The second initial is optional. */
1842 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1843 if (index < std::size(_initial_name_letters)) {
1844 builder += _initial_name_letters[index];
1845 builder += ". ";
1848 builder += GetSurname(seed);
1851 static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args)
1853 switch (ind) {
1854 case 1: // not used
1855 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1856 return;
1858 case 2: // used for Foobar & Co company names
1859 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1860 return;
1862 case 3: // President name
1863 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
1864 return;
1867 /* town name? */
1868 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1869 GetSpecialTownNameString(builder, ind - 6, args.GetNextParameter<uint32_t>());
1870 builder += " Transport";
1871 return;
1874 NOT_REACHED();
1878 * Check whether the header is a valid header for OpenTTD.
1879 * @return true iff the header is deemed valid.
1881 bool LanguagePackHeader::IsValid() const
1883 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1884 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1885 this->plural_form < LANGUAGE_MAX_PLURAL &&
1886 this->text_dir <= 1 &&
1887 this->newgrflangid < MAX_LANG &&
1888 this->num_genders < MAX_NUM_GENDERS &&
1889 this->num_cases < MAX_NUM_CASES &&
1890 StrValid(this->name) &&
1891 StrValid(this->own_name) &&
1892 StrValid(this->isocode) &&
1893 StrValid(this->digit_group_separator) &&
1894 StrValid(this->digit_group_separator_currency) &&
1895 StrValid(this->digit_decimal_separator);
1899 * Check whether a translation is sufficiently finished to offer it to the public.
1901 bool LanguagePackHeader::IsReasonablyFinished() const
1903 /* "Less than 25% missing" is "sufficiently finished". */
1904 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1908 * Read a particular language.
1909 * @param lang The metadata about the language.
1910 * @return Whether the loading went okay or not.
1912 bool ReadLanguagePack(const LanguageMetadata *lang)
1914 /* Current language pack */
1915 size_t len = 0;
1916 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file), len, 1U << 20).release()));
1917 if (!lang_pack) return false;
1919 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1920 const char *end = (char *)lang_pack.get() + len + 1;
1922 /* We need at least one byte of lang_pack->data */
1923 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1924 return false;
1927 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1929 uint count = 0;
1930 for (uint i = 0; i < TEXT_TAB_END; i++) {
1931 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1932 if (num > TAB_SIZE) return false;
1934 tab_start[i] = count;
1935 tab_num[i] = num;
1936 count += num;
1939 /* Allocate offsets */
1940 std::vector<char *> offs(count);
1942 /* Fill offsets */
1943 char *s = lang_pack->data;
1944 len = (uint8_t)*s++;
1945 for (uint i = 0; i < count; i++) {
1946 if (s + len >= end) return false;
1948 if (len >= 0xC0) {
1949 len = ((len & 0x3F) << 8) + (uint8_t)*s++;
1950 if (s + len >= end) return false;
1952 offs[i] = s;
1953 s += len;
1954 len = (uint8_t)*s;
1955 *s++ = '\0'; // zero terminate the string
1958 _langpack.langpack = std::move(lang_pack);
1959 _langpack.offsets = std::move(offs);
1960 _langpack.langtab_num = tab_num;
1961 _langpack.langtab_start = tab_start;
1963 _current_language = lang;
1964 _current_text_dir = (TextDirection)_current_language->text_dir;
1965 _config_language_file = FS2OTTD(_current_language->file.filename());
1966 SetCurrentGrfLangID(_current_language->newgrflangid);
1968 #ifdef _WIN32
1969 extern void Win32SetCurrentLocaleName(std::string iso_code);
1970 Win32SetCurrentLocaleName(_current_language->isocode);
1971 #endif
1973 #ifdef WITH_COCOA
1974 extern void MacOSSetCurrentLocaleName(const char *iso_code);
1975 MacOSSetCurrentLocaleName(_current_language->isocode);
1976 #endif
1978 #ifdef WITH_ICU_I18N
1979 /* Create a collator instance for our current locale. */
1980 UErrorCode status = U_ZERO_ERROR;
1981 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
1982 /* Sort number substrings by their numerical value. */
1983 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
1984 /* Avoid using the collator if it is not correctly set. */
1985 if (U_FAILURE(status)) {
1986 _current_collator.reset();
1988 #endif /* WITH_ICU_I18N */
1990 Layouter::Initialize();
1992 /* Some lists need to be sorted again after a language change. */
1993 ReconsiderGameScriptLanguage();
1994 InitializeSortedCargoSpecs();
1995 SortIndustryTypes();
1996 BuildIndustriesLegend();
1997 BuildContentTypeStringList();
1998 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
1999 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
2000 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
2001 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
2002 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
2003 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
2004 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
2006 return true;
2009 /* Win32 implementation in win32.cpp.
2010 * OS X implementation in os/macosx/macos.mm. */
2011 #if !(defined(_WIN32) || defined(__APPLE__))
2013 * Determine the current charset based on the environment
2014 * First check some default values, after this one we passed ourselves
2015 * and if none exist return the value for $LANG
2016 * @param param environment variable to check conditionally if default ones are not
2017 * set. Pass nullptr if you don't want additional checks.
2018 * @return return string containing current charset, or nullptr if not-determinable
2020 const char *GetCurrentLocale(const char *param)
2022 const char *env;
2024 env = std::getenv("LANGUAGE");
2025 if (env != nullptr) return env;
2027 env = std::getenv("LC_ALL");
2028 if (env != nullptr) return env;
2030 if (param != nullptr) {
2031 env = std::getenv(param);
2032 if (env != nullptr) return env;
2035 return std::getenv("LANG");
2037 #else
2038 const char *GetCurrentLocale(const char *param);
2039 #endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2042 * Get the language with the given NewGRF language ID.
2043 * @param newgrflangid NewGRF languages ID to check.
2044 * @return The language's metadata, or nullptr if it is not known.
2046 const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2048 for (const LanguageMetadata &lang : _languages) {
2049 if (newgrflangid == lang.newgrflangid) return &lang;
2052 return nullptr;
2056 * Reads the language file header and checks compatibility.
2057 * @param file the file to read
2058 * @param hdr the place to write the header information to
2059 * @return true if and only if the language file is of a compatible version
2061 static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2063 auto f = FileHandle::Open(file, "rb");
2064 if (!f.has_value()) return false;
2066 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2068 bool ret = read == 1 && hdr->IsValid();
2070 /* Convert endianness for the windows language ID */
2071 if (ret) {
2072 hdr->missing = FROM_LE16(hdr->missing);
2073 hdr->winlangid = FROM_LE16(hdr->winlangid);
2075 return ret;
2079 * Search for the languages in the given directory and add them to the #_languages list.
2080 * @param path the base directory to search in
2082 static void FillLanguageList(const std::string &path)
2084 std::error_code error_code;
2085 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2086 if (!dir_entry.is_regular_file()) continue;
2087 if (dir_entry.path().extension() != ".lng") continue;
2089 LanguageMetadata lmd;
2090 lmd.file = dir_entry.path();
2092 /* Check whether the file is of the correct version */
2093 if (!GetLanguageFileHeader(FS2OTTD(lmd.file), &lmd)) {
2094 Debug(misc, 3, "{} is not a valid language file", FS2OTTD(lmd.file));
2095 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2096 Debug(misc, 3, "{}'s language ID is already known", FS2OTTD(lmd.file));
2097 } else {
2098 _languages.push_back(lmd);
2101 if (error_code) {
2102 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2107 * Make a list of the available language packs. Put the data in
2108 * #_languages list.
2110 void InitializeLanguagePacks()
2112 for (Searchpath sp : _valid_searchpaths) {
2113 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2115 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2117 /* Acquire the locale of the current system */
2118 const char *lang = GetCurrentLocale("LC_MESSAGES");
2119 if (lang == nullptr) lang = "en_GB";
2121 const LanguageMetadata *chosen_language = nullptr; ///< Matching the language in the configuration file or the current locale
2122 const LanguageMetadata *language_fallback = nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
2123 const LanguageMetadata *en_GB_fallback = _languages.data(); ///< Fallback when no locale-matching language has been found
2125 /* Find a proper language. */
2126 for (const LanguageMetadata &lng : _languages) {
2127 /* We are trying to find a default language. The priority is by
2128 * configuration file, local environment and last, if nothing found,
2129 * English. */
2130 if (_config_language_file == FS2OTTD(lng.file.filename())) {
2131 chosen_language = &lng;
2132 break;
2135 if (strcmp (lng.isocode, "en_GB") == 0) en_GB_fallback = &lng;
2137 /* Only auto-pick finished translations */
2138 if (!lng.IsReasonablyFinished()) continue;
2140 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2141 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2144 /* We haven't found the language in the config nor the one in the locale.
2145 * Now we set it to one of the fallback languages */
2146 if (chosen_language == nullptr) {
2147 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2150 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file));
2154 * Get the ISO language code of the currently loaded language.
2155 * @return the ISO code.
2157 const char *GetCurrentLanguageIsoCode()
2159 return _langpack.langpack->isocode;
2163 * Check whether there are glyphs missing in the current language.
2164 * @return If glyphs are missing, return \c true, else return \c false.
2166 bool MissingGlyphSearcher::FindMissingGlyphs()
2168 InitFontCache(this->Monospace());
2170 this->Reset();
2171 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2172 auto src = text->cbegin();
2174 FontSize size = this->DefaultSize();
2175 FontCache *fc = FontCache::Get(size);
2176 while (src != text->cend()) {
2177 char32_t c = Utf8Consume(src);
2179 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2180 size = (FontSize)(c - SCC_FIRST_FONT);
2181 fc = FontCache::Get(size);
2182 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2183 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2184 std::string size_name;
2186 switch (size) {
2187 case FS_NORMAL: size_name = "medium"; break;
2188 case FS_SMALL: size_name = "small"; break;
2189 case FS_LARGE: size_name = "large"; break;
2190 case FS_MONO: size_name = "mono"; break;
2191 default: NOT_REACHED();
2194 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2195 return true;
2199 return false;
2202 /** Helper for searching through the language pack. */
2203 class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
2204 uint i; ///< Iterator for the primary language tables.
2205 uint j; ///< Iterator for the secondary language tables.
2207 void Reset() override
2209 this->i = 0;
2210 this->j = 0;
2213 FontSize DefaultSize() override
2215 return FS_NORMAL;
2218 std::optional<std::string_view> NextString() override
2220 if (this->i >= TEXT_TAB_END) return std::nullopt;
2222 const char *ret = _langpack.offsets[_langpack.langtab_start[this->i] + this->j];
2224 this->j++;
2225 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2226 this->i++;
2227 this->j = 0;
2230 return ret;
2233 bool Monospace() override
2235 return false;
2238 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] const char *font_name, [[maybe_unused]] const void *os_data) override
2240 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2241 settings->small.font = font_name;
2242 settings->medium.font = font_name;
2243 settings->large.font = font_name;
2245 settings->small.os_handle = os_data;
2246 settings->medium.os_handle = os_data;
2247 settings->large.os_handle = os_data;
2248 #endif
2253 * Check whether the currently loaded language pack
2254 * uses characters that the currently loaded font
2255 * does not support. If this is the case an error
2256 * message will be shown in English. The error
2257 * message will not be localized because that would
2258 * mean it might use characters that are not in the
2259 * font, which is the whole reason this check has
2260 * been added.
2261 * @param base_font Whether to look at the base font as well.
2262 * @param searcher The methods to use to search for strings to check.
2263 * If nullptr the loaded language pack searcher is used.
2265 void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2267 static LanguagePackGlyphSearcher pack_searcher;
2268 if (searcher == nullptr) searcher = &pack_searcher;
2269 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2270 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2271 if (bad_font) {
2272 /* We found an unprintable character... lets try whether we can find
2273 * a fallback font that can print the characters in the current language. */
2274 bool any_font_configured = !_fcsettings.medium.font.empty();
2275 FontCacheSettings backup = _fcsettings;
2277 _fcsettings.mono.os_handle = nullptr;
2278 _fcsettings.medium.os_handle = nullptr;
2280 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2282 _fcsettings = backup;
2284 if (!bad_font && any_font_configured) {
2285 /* If the user configured a bad font, and we found a better one,
2286 * show that we loaded the better font instead of the configured one.
2287 * The colour 'character' might change in the
2288 * future, so for safety we just Utf8 Encode it into the string,
2289 * which takes exactly three characters, so it replaces the "XXX"
2290 * with the colour marker. */
2291 static std::string err_str("XXXThe current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2292 Utf8Encode(err_str.data(), SCC_YELLOW);
2293 SetDParamStr(0, err_str);
2294 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2297 if (bad_font && base_font) {
2298 /* Our fallback font does miss characters too, so keep the
2299 * user chosen font as that is more likely to be any good than
2300 * the wild guess we made */
2301 InitFontCache(searcher->Monospace());
2304 #endif
2306 if (bad_font) {
2307 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2308 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2309 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2310 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2311 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2312 static std::string err_str("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.");
2313 Utf8Encode(err_str.data(), SCC_YELLOW);
2314 SetDParamStr(0, err_str);
2315 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2317 /* Reset the font width */
2318 LoadStringWidthTable(searcher->Monospace());
2319 return;
2322 /* Update the font with cache */
2323 LoadStringWidthTable(searcher->Monospace());
2325 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2327 * For right-to-left languages we need the ICU library. If
2328 * we do not have support for that library we warn the user
2329 * about it with a message. As we do not want the string to
2330 * be translated by the translators, we 'force' it into the
2331 * binary and 'load' it via a BindCString. To do this
2332 * properly we have to set the colour of the string,
2333 * otherwise we end up with a lot of artifacts. The colour
2334 * 'character' might change in the future, so for safety
2335 * we just Utf8 Encode it into the string, which takes
2336 * exactly three characters, so it replaces the "XXX" with
2337 * the colour marker.
2339 if (_current_text_dir != TD_LTR) {
2340 static std::string err_str("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2341 Utf8Encode(err_str.data(), SCC_YELLOW);
2342 SetDParamStr(0, err_str);
2343 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2345 #endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */