Fix: server menu tooltip shouldn't show language info (#12955)
[openttd-github.git] / src / strings.cpp
blob826a8bd4f638fb9a95d3f761a5d1aea3eb10f86b
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 * Get a parsed string with most special stringcodes replaced by the string parameters.
327 * @param string The ID of the string to parse.
328 * @param args Arguments for the string.
329 * @return The parsed string.
331 std::string GetStringWithArgs(StringID string, StringParameters &args)
333 std::string result;
334 StringBuilder builder(result);
335 GetStringWithArgs(builder, string, args);
336 return result;
340 * This function is used to "bind" a C string to a OpenTTD dparam slot.
341 * @param n slot of the string
342 * @param str string to bind
344 void SetDParamStr(size_t n, const char *str)
346 _global_string_params.SetParam(n, str);
350 * This function is used to "bind" the C string of a std::string to a OpenTTD dparam slot.
351 * The caller has to ensure that the std::string reference remains valid while the string is shown.
352 * @param n slot of the string
353 * @param str string to bind
355 void SetDParamStr(size_t n, const std::string &str)
357 _global_string_params.SetParam(n, str);
361 * This function is used to "bind" the std::string to a OpenTTD dparam slot.
362 * Contrary to the other \c SetDParamStr functions, this moves the string into
363 * the parameter slot.
364 * @param n slot of the string
365 * @param str string to bind
367 void SetDParamStr(size_t n, std::string &&str)
369 _global_string_params.SetParam(n, std::move(str));
372 static const char *GetDecimalSeparator()
374 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
375 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
376 return decimal_separator;
380 * Format a number into a string.
381 * @param builder the string builder to write to
382 * @param number the number to write down
383 * @param separator the thousands-separator to use
385 static void FormatNumber(StringBuilder &builder, int64_t number, const char *separator)
387 static const int max_digits = 20;
388 uint64_t divisor = 10000000000000000000ULL;
389 int thousands_offset = (max_digits - 1) % 3;
391 if (number < 0) {
392 builder += '-';
393 number = -number;
396 uint64_t num = number;
397 uint64_t tot = 0;
398 for (int i = 0; i < max_digits; i++) {
399 uint64_t quot = 0;
400 if (num >= divisor) {
401 quot = num / divisor;
402 num = num % divisor;
404 if ((tot |= quot) || i == max_digits - 1) {
405 builder += '0' + quot; // quot is a single digit
406 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
409 divisor /= 10;
413 static void FormatCommaNumber(StringBuilder &builder, int64_t number)
415 const char *separator = _settings_game.locale.digit_group_separator.c_str();
416 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
417 FormatNumber(builder, number, separator);
420 static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
422 fmt::format_to(builder, "{}", number);
425 static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
427 fmt::format_to(builder, "{:0{}d}", number, count);
430 static void FormatHexNumber(StringBuilder &builder, uint64_t number)
432 fmt::format_to(builder, "0x{:X}", number);
436 * Format a given number as a number of bytes with the SI prefix.
437 * @param builder the string builder to write to
438 * @param number the number of bytes to write down
440 static void FormatBytes(StringBuilder &builder, int64_t number)
442 assert(number >= 0);
444 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
445 const char * const iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
446 uint id = 1;
447 while (number >= 1024 * 1024) {
448 number /= 1024;
449 id++;
452 if (number < 1024) {
453 id = 0;
454 fmt::format_to(builder, "{}", number);
455 } else if (number < 1024 * 10) {
456 fmt::format_to(builder, "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
457 } else if (number < 1024 * 100) {
458 fmt::format_to(builder, "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
459 } else {
460 assert(number < 1024 * 1024);
461 fmt::format_to(builder, "{}", number / 1024);
464 assert(id < lengthof(iec_prefixes));
465 fmt::format_to(builder, NBSP "{}B", iec_prefixes[id]);
468 static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
470 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
472 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
473 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
476 static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
478 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
480 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
481 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
484 static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
486 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
488 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
489 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
490 FormatString(builder, GetStringPtr(str), tmp_params);
493 static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
495 /* We are going to make number absolute for printing, so
496 * keep this piece of data as we need it later on */
497 bool negative = number < 0;
499 number *= spec->rate;
501 /* convert from negative */
502 if (number < 0) {
503 builder.Utf8Encode(SCC_PUSH_COLOUR);
504 builder.Utf8Encode(SCC_RED);
505 builder += '-';
506 number = -number;
509 /* Add prefix part, following symbol_pos specification.
510 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
511 * The only remaining value is 1 (suffix), so everything that is not 1 */
512 if (spec->symbol_pos != 1) builder += spec->prefix;
514 StringID number_str = STR_NULL;
516 /* For huge numbers, compact the number. */
517 if (compact) {
518 /* Take care of the thousand rounding. Having 1 000 000 k
519 * and 1 000 M is inconsistent, so always use 1 000 M. */
520 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
521 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
522 number_str = STR_CURRENCY_SHORT_TERA;
523 } else if (number >= Money(1'000'000'000'000) - 500'000) {
524 number = (number + 500'000'000) / 1'000'000'000;
525 number_str = STR_CURRENCY_SHORT_GIGA;
526 } else if (number >= 1'000'000'000 - 500) {
527 number = (number + 500'000) / 1'000'000;
528 number_str = STR_CURRENCY_SHORT_MEGA;
529 } else if (number >= 1'000'000) {
530 number = (number + 500) / 1'000;
531 number_str = STR_CURRENCY_SHORT_KILO;
535 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
536 if (StrEmpty(separator)) separator = GetCurrency().separator.c_str();
537 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
538 FormatNumber(builder, number, separator);
539 if (number_str != STR_NULL) {
540 auto tmp_params = ArrayStringParameters<0>();
541 FormatString(builder, GetStringPtr(number_str), tmp_params);
544 /* Add suffix part, following symbol_pos specification.
545 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
546 * The only remaining value is 1 (prefix), so everything that is not 0 */
547 if (spec->symbol_pos != 0) builder += spec->suffix;
549 if (negative) {
550 builder.Utf8Encode(SCC_POP_COLOUR);
555 * Determine the "plural" index given a plural form and a number.
556 * @param count The number to get the plural index of.
557 * @param plural_form The plural form we want an index for.
558 * @return The plural index for the given form.
560 static int DeterminePluralForm(int64_t count, int plural_form)
562 /* The absolute value determines plurality */
563 uint64_t n = abs(count);
565 switch (plural_form) {
566 default:
567 NOT_REACHED();
569 /* Two forms: singular used for one only.
570 * Used in:
571 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
572 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
573 case 0:
574 return n != 1 ? 1 : 0;
576 /* Only one form.
577 * Used in:
578 * Hungarian, Japanese, Turkish */
579 case 1:
580 return 0;
582 /* Two forms: singular used for 0 and 1.
583 * Used in:
584 * French, Brazilian Portuguese */
585 case 2:
586 return n > 1 ? 1 : 0;
588 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
589 * Note: Cases are out of order for hysterical reasons. '0' is last.
590 * Used in:
591 * Latvian */
592 case 3:
593 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
595 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
596 * Used in:
597 * Gaelige (Irish) */
598 case 4:
599 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
601 /* 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.
602 * Used in:
603 * Lithuanian */
604 case 5:
605 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
607 /* 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.
608 * Used in:
609 * Croatian, Russian, Ukrainian */
610 case 6:
611 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
613 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
614 * Used in:
615 * Polish */
616 case 7:
617 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
619 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
620 * Used in:
621 * Slovenian */
622 case 8:
623 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
625 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
626 * Used in:
627 * Icelandic */
628 case 9:
629 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
631 /* Three forms: special cases for 1, and 2 to 4
632 * Used in:
633 * Czech, Slovak */
634 case 10:
635 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
637 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
638 * Korean doesn't have the concept of plural, but depending on how a
639 * number is pronounced it needs another version of a particle.
640 * As such the plural system is misused to give this distinction.
642 case 11:
643 switch (n % 10) {
644 case 0: // yeong
645 case 1: // il
646 case 3: // sam
647 case 6: // yuk
648 case 7: // chil
649 case 8: // pal
650 return 0;
652 case 2: // i
653 case 4: // sa
654 case 5: // o
655 case 9: // gu
656 return 1;
658 default:
659 NOT_REACHED();
662 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
663 * Used in:
664 * Maltese */
665 case 12:
666 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
667 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
668 * Used in:
669 * Scottish Gaelic */
670 case 13:
671 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
673 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
674 * Used in:
675 * Romanian */
676 case 14:
677 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
681 static const char *ParseStringChoice(const char *b, uint form, StringBuilder &builder)
683 /* <NUM> {Length of each string} {each string} */
684 uint n = (uint8_t)*b++;
685 uint pos, i, mypos = 0;
687 for (i = pos = 0; i != n; i++) {
688 uint len = (uint8_t)*b++;
689 if (i == form) mypos = pos;
690 pos += len;
693 builder += b + mypos;
694 return b + pos;
697 /** Helper for unit conversion. */
698 struct UnitConversion {
699 double factor; ///< Amount to multiply or divide upon conversion.
702 * Convert value from OpenTTD's internal unit into the displayed value.
703 * @param input The input to convert.
704 * @param round Whether to round the value or not.
705 * @return The converted value.
707 int64_t ToDisplay(int64_t input, bool round = true) const
709 return round
710 ? (int64_t)std::round(input * this->factor)
711 : (int64_t)(input * this->factor);
715 * Convert the displayed value back into a value of OpenTTD's internal unit.
716 * @param input The input to convert.
717 * @param round Whether to round the value up or not.
718 * @param divider Divide the return value by this.
719 * @return The converted value.
721 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
723 return round
724 ? (int64_t)std::round(input / this->factor / divider)
725 : (int64_t)(input / this->factor / divider);
729 /** Information about a specific unit system. */
730 struct Units {
731 UnitConversion c; ///< Conversion
732 StringID s; ///< String for the unit
733 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.
736 /** Information about a specific unit system with a long variant. */
737 struct UnitsLong {
738 UnitConversion c; ///< Conversion
739 StringID s; ///< String for the short variant of the unit
740 StringID l; ///< String for the long variant of the unit
741 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.
744 /** Unit conversions for velocity. */
745 static const Units _units_velocity_calendar[] = {
746 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
747 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
748 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
749 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
750 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
753 /** Unit conversions for velocity. */
754 static const Units _units_velocity_realtime[] = {
755 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
756 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
757 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
758 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
759 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
762 /** Unit conversions for power. */
763 static const Units _units_power[] = {
764 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
765 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
766 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
769 /** Unit conversions for power to weight. */
770 static const Units _units_power_to_weight[] = {
771 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
772 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
773 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
774 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
775 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
776 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
777 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
778 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
779 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
782 /** Unit conversions for weight. */
783 static const UnitsLong _units_weight[] = {
784 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
785 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
786 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
789 /** Unit conversions for volume. */
790 static const UnitsLong _units_volume[] = {
791 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
792 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
793 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
796 /** Unit conversions for force. */
797 static const Units _units_force[] = {
798 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
799 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
800 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
803 /** Unit conversions for height. */
804 static const Units _units_height[] = {
805 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
806 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
807 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
810 /** Unit conversions for time in calendar days or wallclock seconds */
811 static const Units _units_time_days_or_seconds[] = {
812 { { 1 }, STR_UNITS_DAYS, 0 },
813 { { 2 }, STR_UNITS_SECONDS, 0 },
816 /** Unit conversions for time in calendar months or wallclock minutes */
817 static const Units _units_time_months_or_minutes[] = {
818 { { 1 }, STR_UNITS_MONTHS, 0 },
819 { { 1 }, STR_UNITS_MINUTES, 0 },
822 /** Unit conversions for time in calendar years or economic periods */
823 static const Units _units_time_years_or_periods[] = {
824 { { 1 }, STR_UNITS_YEARS, 0 },
825 { { 1 }, STR_UNITS_PERIODS, 0 },
828 /** Unit conversions for time in calendar years or wallclock minutes */
829 static const Units _units_time_years_or_minutes[] = {
830 { { 1 }, STR_UNITS_YEARS, 0 },
831 { { 12 }, STR_UNITS_MINUTES, 0 },
835 * Get the correct velocity units depending on the vehicle type and whether we're using real-time units.
836 * @param type VehicleType to convert velocity for.
837 * @return The Units for the proper vehicle and time mode.
839 static const Units GetVelocityUnits(VehicleType type)
841 uint8_t setting = (type == VEH_SHIP || type == VEH_AIRCRAFT) ? _settings_game.locale.units_velocity_nautical : _settings_game.locale.units_velocity;
843 assert(setting < lengthof(_units_velocity_calendar));
844 assert(setting < lengthof(_units_velocity_realtime));
846 if (TimerGameEconomy::UsingWallclockUnits()) return _units_velocity_realtime[setting];
848 return _units_velocity_calendar[setting];
852 * Convert the given (internal) speed to the display speed.
853 * @param speed the speed to convert
854 * @return the converted speed.
856 uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
858 /* For historical reasons we don't want to mess with the
859 * conversion for speed. So, don't round it and keep the
860 * original conversion factors instead of the real ones. */
861 return GetVelocityUnits(type).c.ToDisplay(speed, false);
865 * Convert the given display speed to the (internal) speed.
866 * @param speed the speed to convert
867 * @return the converted speed.
869 uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
871 return GetVelocityUnits(type).c.FromDisplay(speed);
875 * Convert the given km/h-ish speed to the display speed.
876 * @param speed the speed to convert
877 * @return the converted speed.
879 uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
881 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
885 * Convert the given display speed to the km/h-ish speed.
886 * @param speed the speed to convert
887 * @return the converted speed.
889 uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
891 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
895 * Parse most format codes within a string and write the result to a buffer.
896 * @param builder The string builder to write the final string to.
897 * @param str_arg The original string with format codes.
898 * @param args Pointer to extra arguments used by various string codes.
899 * @param dry_run True when the args' type data is not yet initialized.
901 static void FormatString(StringBuilder &builder, const char *str_arg, StringParameters &args, uint case_index, bool game_script, bool dry_run)
903 size_t orig_offset = args.GetOffset();
905 if (!dry_run) {
907 * This function is normally called with `dry_run` false, then we call this function again
908 * with `dry_run` being true. The dry run is required for the gender formatting. For the
909 * gender determination we need to format a sub string to get the gender, but for that we
910 * need to know as what string control code type the specific parameter is encoded. Since
911 * gendered words can be before the "parameter" words, this needs to be determined before
912 * the actual formatting.
914 std::string buffer;
915 StringBuilder dry_run_builder(buffer);
916 if (UsingNewGRFTextStack()) {
917 /* Values from the NewGRF text stack are only copied to the normal
918 * argv array at the time they are encountered. That means that if
919 * another string command references a value later in the string it
920 * would fail. We solve that by running FormatString twice. The first
921 * pass makes sure the argv array is correctly filled and the second
922 * pass can reference later values without problems. */
923 struct TextRefStack *backup = CreateTextRefStackBackup();
924 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
925 RestoreTextRefStackBackup(backup);
926 } else {
927 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
929 /* We have to restore the original offset here to to read the correct values. */
930 args.SetOffset(orig_offset);
932 char32_t b = '\0';
933 uint next_substr_case_index = 0;
934 std::stack<const char *, std::vector<const char *>> str_stack;
935 str_stack.push(str_arg);
937 for (;;) {
938 try {
939 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
940 str_stack.pop();
942 if (str_stack.empty()) break;
943 const char *&str = str_stack.top();
945 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
946 /* We need to pass some stuff as it might be modified. */
947 StringParameters remaining = args.GetRemainingParameters();
948 b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run);
949 if (b == 0) continue;
952 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
953 builder.Utf8Encode(b);
954 continue;
957 args.SetTypeOfNextParameter(b);
958 switch (b) {
959 case SCC_ENCODED: {
960 ArrayStringParameters<20> sub_args;
962 char *p;
963 uint32_t stringid = std::strtoul(str, &p, 16);
964 if (*p != ':' && *p != '\0') {
965 while (*p != '\0') p++;
966 str = p;
967 builder += "(invalid SCC_ENCODED)";
968 break;
970 if (stringid >= TAB_SIZE_GAMESCRIPT) {
971 while (*p != '\0') p++;
972 str = p;
973 builder += "(invalid StringID)";
974 break;
977 int i = 0;
978 while (*p != '\0' && i < 20) {
979 uint64_t param;
980 const char *s = ++p;
982 /* Find the next value */
983 bool instring = false;
984 bool escape = false;
985 for (;; p++) {
986 if (*p == '\\') {
987 escape = true;
988 continue;
990 if (*p == '"' && escape) {
991 escape = false;
992 continue;
994 escape = false;
996 if (*p == '"') {
997 instring = !instring;
998 continue;
1000 if (instring) {
1001 continue;
1004 if (*p == ':') break;
1005 if (*p == '\0') break;
1008 if (*s != '"') {
1009 /* Check if we want to look up another string */
1010 char32_t l;
1011 size_t len = Utf8Decode(&l, s);
1012 bool lookup = (l == SCC_ENCODED);
1013 if (lookup) s += len;
1015 param = std::strtoull(s, &p, 16);
1017 if (lookup) {
1018 if (param >= TAB_SIZE_GAMESCRIPT) {
1019 while (*p != '\0') p++;
1020 str = p;
1021 builder += "(invalid sub-StringID)";
1022 break;
1024 param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param);
1027 sub_args.SetParam(i++, param);
1028 } else {
1029 s++; // skip the leading \"
1030 sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \".
1033 /* If we didn't error out, we can actually print the string. */
1034 if (*str != '\0') {
1035 str = p;
1036 GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true);
1038 break;
1041 case SCC_NEWGRF_STRINL: {
1042 StringID substr = Utf8Consume(&str);
1043 str_stack.push(GetStringPtr(substr));
1044 break;
1047 case SCC_NEWGRF_PRINT_WORD_STRING_ID: {
1048 StringID substr = args.GetNextParameter<StringID>();
1049 str_stack.push(GetStringPtr(substr));
1050 case_index = next_substr_case_index;
1051 next_substr_case_index = 0;
1052 break;
1056 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1057 /* First read the meta data from the language file. */
1058 size_t offset = orig_offset + (uint8_t)*str++;
1059 int gender = 0;
1060 if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1061 /* Now we need to figure out what text to resolve, i.e.
1062 * what do we need to draw? So get the actual raw string
1063 * first using the control code to get said string. */
1064 char input[4 + 1];
1065 char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset));
1066 *p = '\0';
1068 /* The gender is stored at the start of the formatted string. */
1069 bool old_sgd = _scan_for_gender_data;
1070 _scan_for_gender_data = true;
1071 std::string buffer;
1072 StringBuilder tmp_builder(buffer);
1073 StringParameters tmp_params = args.GetRemainingParameters(offset);
1074 FormatString(tmp_builder, input, tmp_params);
1075 _scan_for_gender_data = old_sgd;
1077 /* And determine the string. */
1078 const char *s = buffer.c_str();
1079 char32_t c = Utf8Consume(&s);
1080 /* Does this string have a gender, if so, set it */
1081 if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0];
1083 str = ParseStringChoice(str, gender, builder);
1084 break;
1087 /* This sets up the gender for the string.
1088 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1089 case SCC_GENDER_INDEX: // {GENDER 0}
1090 if (_scan_for_gender_data) {
1091 builder.Utf8Encode(SCC_GENDER_INDEX);
1092 builder += *str++;
1093 } else {
1094 str++;
1096 break;
1098 case SCC_PLURAL_LIST: { // {P}
1099 int plural_form = *str++; // contains the plural form for this string
1100 size_t offset = orig_offset + (uint8_t)*str++;
1101 int64_t v = std::get<uint64_t>(args.GetParam(offset)); // contains the number that determines plural
1102 str = ParseStringChoice(str, DeterminePluralForm(v, plural_form), builder);
1103 break;
1106 case SCC_ARG_INDEX: { // Move argument pointer
1107 args.SetOffset(orig_offset + (uint8_t)*str++);
1108 break;
1111 case SCC_SET_CASE: { // {SET_CASE}
1112 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1113 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1114 next_substr_case_index = (uint8_t)*str++;
1115 break;
1118 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1119 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1120 * Each LEN is printed using 2 bytes in big endian order. */
1121 uint num = (uint8_t)*str++;
1122 while (num) {
1123 if ((uint8_t)str[0] == case_index) {
1124 /* Found the case, adjust str pointer and continue */
1125 str += 3;
1126 break;
1128 /* Otherwise skip to the next case */
1129 str += 3 + (str[1] << 8) + str[2];
1130 num--;
1132 break;
1135 case SCC_REVISION: // {REV}
1136 builder += _openttd_revision;
1137 break;
1139 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
1140 const char *raw_string = args.GetNextParameterString();
1141 /* raw_string can be nullptr. */
1142 if (raw_string == nullptr) {
1143 builder += "(invalid RAW_STRING parameter)";
1144 break;
1146 FormatString(builder, raw_string, args);
1147 break;
1150 case SCC_STRING: {// {STRING}
1151 StringID string_id = args.GetNextParameter<StringID>();
1152 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1153 /* It's prohibited for the included string to consume any arguments. */
1154 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1155 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1156 next_substr_case_index = 0;
1157 break;
1160 case SCC_STRING1:
1161 case SCC_STRING2:
1162 case SCC_STRING3:
1163 case SCC_STRING4:
1164 case SCC_STRING5:
1165 case SCC_STRING6:
1166 case SCC_STRING7: { // {STRING1..7}
1167 /* Strings that consume arguments */
1168 StringID string_id = args.GetNextParameter<StringID>();
1169 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1170 uint size = b - SCC_STRING1 + 1;
1171 if (game_script && size > args.GetDataLeft()) {
1172 builder += "(too many parameters)";
1173 } else {
1174 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1175 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1176 args.AdvanceOffset(size);
1178 next_substr_case_index = 0;
1179 break;
1182 case SCC_COMMA: // {COMMA}
1183 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1184 break;
1186 case SCC_DECIMAL: { // {DECIMAL}
1187 int64_t number = args.GetNextParameter<int64_t>();
1188 int digits = args.GetNextParameter<int>();
1189 if (digits == 0) {
1190 FormatCommaNumber(builder, number);
1191 break;
1194 int64_t divisor = PowerOfTen(digits);
1195 int64_t fractional = number % divisor;
1196 number /= divisor;
1197 FormatCommaNumber(builder, number);
1198 fmt::format_to(builder, "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1199 break;
1202 case SCC_NUM: // {NUM}
1203 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1204 break;
1206 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1207 int64_t num = args.GetNextParameter<int64_t>();
1208 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1209 break;
1212 case SCC_HEX: // {HEX}
1213 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1214 break;
1216 case SCC_BYTES: // {BYTES}
1217 FormatBytes(builder, args.GetNextParameter<int64_t>());
1218 break;
1220 case SCC_CARGO_TINY: { // {CARGO_TINY}
1221 /* Tiny description of cargotypes. Layout:
1222 * param 1: cargo type
1223 * param 2: cargo count */
1224 CargoID cargo = args.GetNextParameter<CargoID>();
1225 if (cargo >= CargoSpec::GetArraySize()) break;
1227 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1228 int64_t amount = 0;
1229 switch (cargo_str) {
1230 case STR_TONS:
1231 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args.GetNextParameter<int64_t>());
1232 break;
1234 case STR_LITERS:
1235 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args.GetNextParameter<int64_t>());
1236 break;
1238 default: {
1239 amount = args.GetNextParameter<int64_t>();
1240 break;
1244 FormatCommaNumber(builder, amount);
1245 break;
1248 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1249 /* Short description of cargotypes. Layout:
1250 * param 1: cargo type
1251 * param 2: cargo count */
1252 CargoID cargo = args.GetNextParameter<CargoID>();
1253 if (cargo >= CargoSpec::GetArraySize()) break;
1255 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1256 switch (cargo_str) {
1257 case STR_TONS: {
1258 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1259 const auto &x = _units_weight[_settings_game.locale.units_weight];
1260 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1261 FormatString(builder, GetStringPtr(x.l), tmp_params);
1262 break;
1265 case STR_LITERS: {
1266 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1267 const auto &x = _units_volume[_settings_game.locale.units_volume];
1268 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1269 FormatString(builder, GetStringPtr(x.l), tmp_params);
1270 break;
1273 default: {
1274 auto tmp_params = MakeParameters(args.GetNextParameter<int64_t>());
1275 GetStringWithArgs(builder, cargo_str, tmp_params);
1276 break;
1279 break;
1282 case SCC_CARGO_LONG: { // {CARGO_LONG}
1283 /* First parameter is cargo type, second parameter is cargo count */
1284 CargoID cargo = args.GetNextParameter<CargoID>();
1285 if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break;
1287 StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1288 auto tmp_args = MakeParameters(args.GetNextParameter<int64_t>());
1289 GetStringWithArgs(builder, cargo_str, tmp_args);
1290 break;
1293 case SCC_CARGO_LIST: { // {CARGO_LIST}
1294 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1295 bool first = true;
1297 for (const auto &cs : _sorted_cargo_specs) {
1298 if (!HasBit(cmask, cs->Index())) continue;
1300 if (first) {
1301 first = false;
1302 } else {
1303 /* Add a comma if this is not the first item */
1304 builder += ", ";
1307 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1310 /* If first is still true then no cargo is accepted */
1311 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1313 next_substr_case_index = 0;
1314 break;
1317 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1318 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1319 break;
1321 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1322 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1323 break;
1325 case SCC_DATE_TINY: // {DATE_TINY}
1326 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1327 break;
1329 case SCC_DATE_SHORT: // {DATE_SHORT}
1330 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1331 next_substr_case_index = 0;
1332 break;
1334 case SCC_DATE_LONG: // {DATE_LONG}
1335 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1336 next_substr_case_index = 0;
1337 break;
1339 case SCC_DATE_ISO: // {DATE_ISO}
1340 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1341 break;
1343 case SCC_FORCE: { // {FORCE}
1344 assert(_settings_game.locale.units_force < lengthof(_units_force));
1345 const auto &x = _units_force[_settings_game.locale.units_force];
1346 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1347 FormatString(builder, GetStringPtr(x.s), tmp_params);
1348 break;
1351 case SCC_HEIGHT: { // {HEIGHT}
1352 assert(_settings_game.locale.units_height < lengthof(_units_height));
1353 const auto &x = _units_height[_settings_game.locale.units_height];
1354 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1355 FormatString(builder, GetStringPtr(x.s), tmp_params);
1356 break;
1359 case SCC_POWER: { // {POWER}
1360 assert(_settings_game.locale.units_power < lengthof(_units_power));
1361 const auto &x = _units_power[_settings_game.locale.units_power];
1362 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1363 FormatString(builder, GetStringPtr(x.s), tmp_params);
1364 break;
1367 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1368 auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight;
1369 assert(setting < lengthof(_units_power_to_weight));
1370 const auto &x = _units_power_to_weight[setting];
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_VELOCITY: { // {VELOCITY}
1377 int64_t arg = args.GetNextParameter<int64_t>();
1378 // Unpack vehicle type from packed argument to get desired units.
1379 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1380 const auto &x = GetVelocityUnits(vt);
1381 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1382 FormatString(builder, GetStringPtr(x.s), tmp_params);
1383 break;
1386 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1387 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1388 const auto &x = _units_volume[_settings_game.locale.units_volume];
1389 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1390 FormatString(builder, GetStringPtr(x.s), tmp_params);
1391 break;
1394 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1395 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1396 const auto &x = _units_volume[_settings_game.locale.units_volume];
1397 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1398 FormatString(builder, GetStringPtr(x.l), tmp_params);
1399 break;
1402 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1403 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1404 const auto &x = _units_weight[_settings_game.locale.units_weight];
1405 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1406 FormatString(builder, GetStringPtr(x.s), tmp_params);
1407 break;
1410 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1411 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1412 const auto &x = _units_weight[_settings_game.locale.units_weight];
1413 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1414 FormatString(builder, GetStringPtr(x.l), tmp_params);
1415 break;
1418 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1419 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1420 const auto &x = _units_time_days_or_seconds[realtime];
1421 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1422 FormatString(builder, GetStringPtr(x.s), tmp_params);
1423 break;
1426 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1427 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1428 const auto &x = _units_time_months_or_minutes[realtime];
1429 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1430 FormatString(builder, GetStringPtr(x.s), tmp_params);
1431 break;
1434 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1435 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1436 const auto &x = _units_time_years_or_periods[realtime];
1437 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1438 FormatString(builder, GetStringPtr(x.s), tmp_params);
1439 break;
1442 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1443 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1444 const auto &x = _units_time_years_or_minutes[realtime];
1445 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1446 FormatString(builder, GetStringPtr(x.s), tmp_params);
1447 break;
1450 case SCC_COMPANY_NAME: { // {COMPANY}
1451 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1452 if (c == nullptr) break;
1454 if (!c->name.empty()) {
1455 auto tmp_params = MakeParameters(c->name);
1456 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1457 } else {
1458 auto tmp_params = MakeParameters(c->name_2);
1459 GetStringWithArgs(builder, c->name_1, tmp_params);
1461 break;
1464 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1465 CompanyID company = args.GetNextParameter<CompanyID>();
1467 /* Nothing is added for AI or inactive companies */
1468 if (Company::IsValidHumanID(company)) {
1469 auto tmp_params = MakeParameters(company + 1);
1470 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1472 break;
1475 case SCC_DEPOT_NAME: { // {DEPOT}
1476 VehicleType vt = args.GetNextParameter<VehicleType>();
1477 if (vt == VEH_AIRCRAFT) {
1478 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1479 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1480 break;
1483 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1484 if (!d->name.empty()) {
1485 auto tmp_params = MakeParameters(d->name);
1486 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1487 } else {
1488 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1489 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1491 break;
1494 case SCC_ENGINE_NAME: { // {ENGINE}
1495 int64_t arg = args.GetNextParameter<int64_t>();
1496 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1497 if (e == nullptr) break;
1499 if (!e->name.empty() && e->IsEnabled()) {
1500 auto tmp_params = MakeParameters(e->name);
1501 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1502 break;
1505 if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) {
1506 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr);
1507 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1508 if (callback != CALLBACK_FAILED && callback < 0x400) {
1509 const GRFFile *grffile = e->GetGRF();
1510 assert(grffile != nullptr);
1512 StartTextRefStackUsage(grffile, 6);
1513 ArrayStringParameters<6> tmp_params;
1514 GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params);
1515 StopTextRefStackUsage();
1517 break;
1521 auto tmp_params = ArrayStringParameters<0>();
1522 GetStringWithArgs(builder, e->info.string_id, tmp_params);
1523 break;
1526 case SCC_GROUP_NAME: { // {GROUP}
1527 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1528 if (g == nullptr) break;
1530 if (!g->name.empty()) {
1531 auto tmp_params = MakeParameters(g->name);
1532 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1533 } else {
1534 auto tmp_params = MakeParameters(g->number);
1535 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1537 break;
1540 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1541 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1542 if (i == nullptr) break;
1544 static bool use_cache = true;
1545 if (_scan_for_gender_data) {
1546 /* Gender is defined by the industry type.
1547 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1548 auto tmp_params = ArrayStringParameters<0>();
1549 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index);
1550 } else if (use_cache) { // Use cached version if first call
1551 AutoRestoreBackup cache_backup(use_cache, false);
1552 builder += i->GetCachedName();
1553 } else {
1554 /* First print the town name and the industry type name. */
1555 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1556 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1558 next_substr_case_index = 0;
1559 break;
1562 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1563 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1564 if (c == nullptr) break;
1566 if (!c->president_name.empty()) {
1567 auto tmp_params = MakeParameters(c->president_name);
1568 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1569 } else {
1570 auto tmp_params = MakeParameters(c->president_name_2);
1571 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1573 break;
1576 case SCC_STATION_NAME: { // {STATION}
1577 StationID sid = args.GetNextParameter<StationID>();
1578 const Station *st = Station::GetIfValid(sid);
1580 if (st == nullptr) {
1581 /* The station doesn't exist anymore. The only place where we might
1582 * be "drawing" an invalid station is in the case of cargo that is
1583 * in transit. */
1584 auto tmp_params = ArrayStringParameters<0>();
1585 GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params);
1586 break;
1589 static bool use_cache = true;
1590 if (use_cache) { // Use cached version if first call
1591 AutoRestoreBackup cache_backup(use_cache, false);
1592 builder += st->GetCachedName();
1593 } else if (!st->name.empty()) {
1594 auto tmp_params = MakeParameters(st->name);
1595 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1596 } else {
1597 StringID string_id = st->string_id;
1598 if (st->indtype != IT_INVALID) {
1599 /* Special case where the industry provides the name for the station */
1600 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1602 /* Industry GRFs can change which might remove the station name and
1603 * thus cause very strange things. Here we check for that before we
1604 * actually set the station name. */
1605 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1606 string_id = indsp->station_name;
1610 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1611 GetStringWithArgs(builder, string_id, tmp_params);
1613 break;
1616 case SCC_TOWN_NAME: { // {TOWN}
1617 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1618 if (t == nullptr) break;
1620 static bool use_cache = true;
1621 if (use_cache) { // Use cached version if first call
1622 AutoRestoreBackup cache_backup(use_cache, false);
1623 builder += t->GetCachedName();
1624 } else if (!t->name.empty()) {
1625 auto tmp_params = MakeParameters(t->name);
1626 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1627 } else {
1628 GetTownName(builder, t);
1630 break;
1633 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1634 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1635 if (wp == nullptr) break;
1637 if (!wp->name.empty()) {
1638 auto tmp_params = MakeParameters(wp->name);
1639 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1640 } else {
1641 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1642 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1643 if (wp->town_cn != 0) string_id++;
1644 GetStringWithArgs(builder, string_id, tmp_params);
1646 break;
1649 case SCC_VEHICLE_NAME: { // {VEHICLE}
1650 const Vehicle *v = Vehicle::GetIfValid(args.GetNextParameter<VehicleID>());
1651 if (v == nullptr) break;
1653 if (!v->name.empty()) {
1654 auto tmp_params = MakeParameters(v->name);
1655 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1656 } else if (v->group_id != DEFAULT_GROUP) {
1657 /* The vehicle has no name, but is member of a group, so print group name */
1658 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1659 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1660 } else {
1661 auto tmp_params = MakeParameters(v->unitnumber);
1663 StringID string_id;
1664 switch (v->type) {
1665 default: string_id = STR_INVALID_VEHICLE; break;
1666 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1667 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1668 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1669 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1672 GetStringWithArgs(builder, string_id, tmp_params);
1674 break;
1677 case SCC_SIGN_NAME: { // {SIGN}
1678 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1679 if (si == nullptr) break;
1681 if (!si->name.empty()) {
1682 auto tmp_params = MakeParameters(si->name);
1683 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1684 } else {
1685 auto tmp_params = ArrayStringParameters<0>();
1686 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params);
1688 break;
1691 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1692 StationGetSpecialString(builder, args.GetNextParameter<StationFacility>());
1693 break;
1696 case SCC_COLOUR: { // {COLOUR}
1697 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1698 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc);
1699 break;
1702 default:
1703 builder.Utf8Encode(b);
1704 break;
1706 } catch (std::out_of_range &e) {
1707 Debug(misc, 0, "FormatString: {}", e.what());
1708 builder += "(invalid parameter)";
1714 static void StationGetSpecialString(StringBuilder &builder, StationFacility x)
1716 if ((x & FACIL_TRAIN) != 0) builder.Utf8Encode(SCC_TRAIN);
1717 if ((x & FACIL_TRUCK_STOP) != 0) builder.Utf8Encode(SCC_LORRY);
1718 if ((x & FACIL_BUS_STOP) != 0) builder.Utf8Encode(SCC_BUS);
1719 if ((x & FACIL_DOCK) != 0) builder.Utf8Encode(SCC_SHIP);
1720 if ((x & FACIL_AIRPORT) != 0) builder.Utf8Encode(SCC_PLANE);
1723 static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed)
1725 GenerateTownNameString(builder, ind, seed);
1728 static const char * const _silly_company_names[] = {
1729 "Bloggs Brothers",
1730 "Tiny Transport Ltd.",
1731 "Express Travel",
1732 "Comfy-Coach & Co.",
1733 "Crush & Bump Ltd.",
1734 "Broken & Late Ltd.",
1735 "Sam Speedy & Son",
1736 "Supersonic Travel",
1737 "Mike's Motors",
1738 "Lightning International",
1739 "Pannik & Loozit Ltd.",
1740 "Inter-City Transport",
1741 "Getout & Pushit Ltd."
1744 static const char * const _surname_list[] = {
1745 "Adams",
1746 "Allan",
1747 "Baker",
1748 "Bigwig",
1749 "Black",
1750 "Bloggs",
1751 "Brown",
1752 "Campbell",
1753 "Gordon",
1754 "Hamilton",
1755 "Hawthorn",
1756 "Higgins",
1757 "Green",
1758 "Gribble",
1759 "Jones",
1760 "McAlpine",
1761 "MacDonald",
1762 "McIntosh",
1763 "Muir",
1764 "Murphy",
1765 "Nelson",
1766 "O'Donnell",
1767 "Parker",
1768 "Phillips",
1769 "Pilkington",
1770 "Quigley",
1771 "Sharkey",
1772 "Thomson",
1773 "Watkins"
1776 static const char * const _silly_surname_list[] = {
1777 "Grumpy",
1778 "Dozy",
1779 "Speedy",
1780 "Nosey",
1781 "Dribble",
1782 "Mushroom",
1783 "Cabbage",
1784 "Sniffle",
1785 "Fishy",
1786 "Swindle",
1787 "Sneaky",
1788 "Nutkins"
1791 static const char _initial_name_letters[] = {
1792 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1793 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1796 static std::span<const char * const> GetSurnameOptions()
1798 if (_settings_game.game_creation.landscape == LT_TOYLAND) return _silly_surname_list;
1799 return _surname_list;
1803 * Get the surname of the president with the given seed.
1804 * @param seed The seed the surname was generated from.
1805 * @return The surname.
1807 static const char *GetSurname(uint32_t seed)
1809 auto surname_options = GetSurnameOptions();
1810 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1813 static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1815 builder += GetSurname(seed);
1816 builder += " & Co.";
1819 static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1821 builder += _initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8];
1822 builder += ". ";
1824 /* The second initial is optional. */
1825 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1826 if (index < std::size(_initial_name_letters)) {
1827 builder += _initial_name_letters[index];
1828 builder += ". ";
1831 builder += GetSurname(seed);
1834 static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args)
1836 switch (ind) {
1837 case 1: // not used
1838 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1839 return;
1841 case 2: // used for Foobar & Co company names
1842 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1843 return;
1845 case 3: // President name
1846 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
1847 return;
1850 /* town name? */
1851 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1852 GetSpecialTownNameString(builder, ind - 6, args.GetNextParameter<uint32_t>());
1853 builder += " Transport";
1854 return;
1857 NOT_REACHED();
1861 * Check whether the header is a valid header for OpenTTD.
1862 * @return true iff the header is deemed valid.
1864 bool LanguagePackHeader::IsValid() const
1866 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1867 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1868 this->plural_form < LANGUAGE_MAX_PLURAL &&
1869 this->text_dir <= 1 &&
1870 this->newgrflangid < MAX_LANG &&
1871 this->num_genders < MAX_NUM_GENDERS &&
1872 this->num_cases < MAX_NUM_CASES &&
1873 StrValid(this->name) &&
1874 StrValid(this->own_name) &&
1875 StrValid(this->isocode) &&
1876 StrValid(this->digit_group_separator) &&
1877 StrValid(this->digit_group_separator_currency) &&
1878 StrValid(this->digit_decimal_separator);
1882 * Check whether a translation is sufficiently finished to offer it to the public.
1884 bool LanguagePackHeader::IsReasonablyFinished() const
1886 /* "Less than 25% missing" is "sufficiently finished". */
1887 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1891 * Read a particular language.
1892 * @param lang The metadata about the language.
1893 * @return Whether the loading went okay or not.
1895 bool ReadLanguagePack(const LanguageMetadata *lang)
1897 /* Current language pack */
1898 size_t len = 0;
1899 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file), len, 1U << 20).release()));
1900 if (!lang_pack) return false;
1902 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1903 const char *end = (char *)lang_pack.get() + len + 1;
1905 /* We need at least one byte of lang_pack->data */
1906 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1907 return false;
1910 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1912 uint count = 0;
1913 for (uint i = 0; i < TEXT_TAB_END; i++) {
1914 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1915 if (num > TAB_SIZE) return false;
1917 tab_start[i] = count;
1918 tab_num[i] = num;
1919 count += num;
1922 /* Allocate offsets */
1923 std::vector<char *> offs(count);
1925 /* Fill offsets */
1926 char *s = lang_pack->data;
1927 len = (uint8_t)*s++;
1928 for (uint i = 0; i < count; i++) {
1929 if (s + len >= end) return false;
1931 if (len >= 0xC0) {
1932 len = ((len & 0x3F) << 8) + (uint8_t)*s++;
1933 if (s + len >= end) return false;
1935 offs[i] = s;
1936 s += len;
1937 len = (uint8_t)*s;
1938 *s++ = '\0'; // zero terminate the string
1941 _langpack.langpack = std::move(lang_pack);
1942 _langpack.offsets = std::move(offs);
1943 _langpack.langtab_num = tab_num;
1944 _langpack.langtab_start = tab_start;
1946 _current_language = lang;
1947 _current_text_dir = (TextDirection)_current_language->text_dir;
1948 _config_language_file = FS2OTTD(_current_language->file.filename());
1949 SetCurrentGrfLangID(_current_language->newgrflangid);
1951 #ifdef _WIN32
1952 extern void Win32SetCurrentLocaleName(std::string iso_code);
1953 Win32SetCurrentLocaleName(_current_language->isocode);
1954 #endif
1956 #ifdef WITH_COCOA
1957 extern void MacOSSetCurrentLocaleName(const char *iso_code);
1958 MacOSSetCurrentLocaleName(_current_language->isocode);
1959 #endif
1961 #ifdef WITH_ICU_I18N
1962 /* Create a collator instance for our current locale. */
1963 UErrorCode status = U_ZERO_ERROR;
1964 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
1965 /* Sort number substrings by their numerical value. */
1966 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
1967 /* Avoid using the collator if it is not correctly set. */
1968 if (U_FAILURE(status)) {
1969 _current_collator.reset();
1971 #endif /* WITH_ICU_I18N */
1973 Layouter::Initialize();
1975 /* Some lists need to be sorted again after a language change. */
1976 ReconsiderGameScriptLanguage();
1977 InitializeSortedCargoSpecs();
1978 SortIndustryTypes();
1979 BuildIndustriesLegend();
1980 BuildContentTypeStringList();
1981 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
1982 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
1983 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
1984 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
1985 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
1986 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
1987 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
1989 return true;
1992 /* Win32 implementation in win32.cpp.
1993 * OS X implementation in os/macosx/macos.mm. */
1994 #if !(defined(_WIN32) || defined(__APPLE__))
1996 * Determine the current charset based on the environment
1997 * First check some default values, after this one we passed ourselves
1998 * and if none exist return the value for $LANG
1999 * @param param environment variable to check conditionally if default ones are not
2000 * set. Pass nullptr if you don't want additional checks.
2001 * @return return string containing current charset, or nullptr if not-determinable
2003 const char *GetCurrentLocale(const char *param)
2005 const char *env;
2007 env = std::getenv("LANGUAGE");
2008 if (env != nullptr) return env;
2010 env = std::getenv("LC_ALL");
2011 if (env != nullptr) return env;
2013 if (param != nullptr) {
2014 env = std::getenv(param);
2015 if (env != nullptr) return env;
2018 return std::getenv("LANG");
2020 #else
2021 const char *GetCurrentLocale(const char *param);
2022 #endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2025 * Get the language with the given NewGRF language ID.
2026 * @param newgrflangid NewGRF languages ID to check.
2027 * @return The language's metadata, or nullptr if it is not known.
2029 const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2031 for (const LanguageMetadata &lang : _languages) {
2032 if (newgrflangid == lang.newgrflangid) return &lang;
2035 return nullptr;
2039 * Reads the language file header and checks compatibility.
2040 * @param file the file to read
2041 * @param hdr the place to write the header information to
2042 * @return true if and only if the language file is of a compatible version
2044 static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2046 auto f = FileHandle::Open(file, "rb");
2047 if (!f.has_value()) return false;
2049 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2051 bool ret = read == 1 && hdr->IsValid();
2053 /* Convert endianness for the windows language ID */
2054 if (ret) {
2055 hdr->missing = FROM_LE16(hdr->missing);
2056 hdr->winlangid = FROM_LE16(hdr->winlangid);
2058 return ret;
2062 * Search for the languages in the given directory and add them to the #_languages list.
2063 * @param path the base directory to search in
2065 static void FillLanguageList(const std::string &path)
2067 std::error_code error_code;
2068 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2069 if (!dir_entry.is_regular_file()) continue;
2070 if (dir_entry.path().extension() != ".lng") continue;
2072 LanguageMetadata lmd;
2073 lmd.file = dir_entry.path();
2075 /* Check whether the file is of the correct version */
2076 if (!GetLanguageFileHeader(FS2OTTD(lmd.file), &lmd)) {
2077 Debug(misc, 3, "{} is not a valid language file", FS2OTTD(lmd.file));
2078 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2079 Debug(misc, 3, "{}'s language ID is already known", FS2OTTD(lmd.file));
2080 } else {
2081 _languages.push_back(lmd);
2084 if (error_code) {
2085 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2090 * Make a list of the available language packs. Put the data in
2091 * #_languages list.
2093 void InitializeLanguagePacks()
2095 for (Searchpath sp : _valid_searchpaths) {
2096 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2098 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2100 /* Acquire the locale of the current system */
2101 const char *lang = GetCurrentLocale("LC_MESSAGES");
2102 if (lang == nullptr) lang = "en_GB";
2104 const LanguageMetadata *chosen_language = nullptr; ///< Matching the language in the configuration file or the current locale
2105 const LanguageMetadata *language_fallback = nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
2106 const LanguageMetadata *en_GB_fallback = _languages.data(); ///< Fallback when no locale-matching language has been found
2108 /* Find a proper language. */
2109 for (const LanguageMetadata &lng : _languages) {
2110 /* We are trying to find a default language. The priority is by
2111 * configuration file, local environment and last, if nothing found,
2112 * English. */
2113 if (_config_language_file == FS2OTTD(lng.file.filename())) {
2114 chosen_language = &lng;
2115 break;
2118 if (strcmp (lng.isocode, "en_GB") == 0) en_GB_fallback = &lng;
2120 /* Only auto-pick finished translations */
2121 if (!lng.IsReasonablyFinished()) continue;
2123 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2124 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2127 /* We haven't found the language in the config nor the one in the locale.
2128 * Now we set it to one of the fallback languages */
2129 if (chosen_language == nullptr) {
2130 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2133 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file));
2137 * Get the ISO language code of the currently loaded language.
2138 * @return the ISO code.
2140 const char *GetCurrentLanguageIsoCode()
2142 return _langpack.langpack->isocode;
2146 * Check whether there are glyphs missing in the current language.
2147 * @return If glyphs are missing, return \c true, else return \c false.
2149 bool MissingGlyphSearcher::FindMissingGlyphs()
2151 InitFontCache(this->Monospace());
2153 this->Reset();
2154 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2155 auto src = text->cbegin();
2157 FontSize size = this->DefaultSize();
2158 FontCache *fc = FontCache::Get(size);
2159 while (src != text->cend()) {
2160 char32_t c = Utf8Consume(src);
2162 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2163 size = (FontSize)(c - SCC_FIRST_FONT);
2164 fc = FontCache::Get(size);
2165 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2166 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2167 std::string size_name;
2169 switch (size) {
2170 case FS_NORMAL: size_name = "medium"; break;
2171 case FS_SMALL: size_name = "small"; break;
2172 case FS_LARGE: size_name = "large"; break;
2173 case FS_MONO: size_name = "mono"; break;
2174 default: NOT_REACHED();
2177 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2178 return true;
2182 return false;
2185 /** Helper for searching through the language pack. */
2186 class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
2187 uint i; ///< Iterator for the primary language tables.
2188 uint j; ///< Iterator for the secondary language tables.
2190 void Reset() override
2192 this->i = 0;
2193 this->j = 0;
2196 FontSize DefaultSize() override
2198 return FS_NORMAL;
2201 std::optional<std::string_view> NextString() override
2203 if (this->i >= TEXT_TAB_END) return std::nullopt;
2205 const char *ret = _langpack.offsets[_langpack.langtab_start[this->i] + this->j];
2207 this->j++;
2208 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2209 this->i++;
2210 this->j = 0;
2213 return ret;
2216 bool Monospace() override
2218 return false;
2221 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] const char *font_name, [[maybe_unused]] const void *os_data) override
2223 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2224 settings->small.font = font_name;
2225 settings->medium.font = font_name;
2226 settings->large.font = font_name;
2228 settings->small.os_handle = os_data;
2229 settings->medium.os_handle = os_data;
2230 settings->large.os_handle = os_data;
2231 #endif
2236 * Check whether the currently loaded language pack
2237 * uses characters that the currently loaded font
2238 * does not support. If this is the case an error
2239 * message will be shown in English. The error
2240 * message will not be localized because that would
2241 * mean it might use characters that are not in the
2242 * font, which is the whole reason this check has
2243 * been added.
2244 * @param base_font Whether to look at the base font as well.
2245 * @param searcher The methods to use to search for strings to check.
2246 * If nullptr the loaded language pack searcher is used.
2248 void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2250 static LanguagePackGlyphSearcher pack_searcher;
2251 if (searcher == nullptr) searcher = &pack_searcher;
2252 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2253 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2254 if (bad_font) {
2255 /* We found an unprintable character... lets try whether we can find
2256 * a fallback font that can print the characters in the current language. */
2257 bool any_font_configured = !_fcsettings.medium.font.empty();
2258 FontCacheSettings backup = _fcsettings;
2260 _fcsettings.mono.os_handle = nullptr;
2261 _fcsettings.medium.os_handle = nullptr;
2263 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2265 _fcsettings = backup;
2267 if (!bad_font && any_font_configured) {
2268 /* If the user configured a bad font, and we found a better one,
2269 * show that we loaded the better font instead of the configured one.
2270 * The colour 'character' might change in the
2271 * future, so for safety we just Utf8 Encode it into the string,
2272 * which takes exactly three characters, so it replaces the "XXX"
2273 * with the colour marker. */
2274 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.");
2275 Utf8Encode(err_str.data(), SCC_YELLOW);
2276 SetDParamStr(0, err_str);
2277 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2280 if (bad_font && base_font) {
2281 /* Our fallback font does miss characters too, so keep the
2282 * user chosen font as that is more likely to be any good than
2283 * the wild guess we made */
2284 InitFontCache(searcher->Monospace());
2287 #endif
2289 if (bad_font) {
2290 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2291 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2292 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2293 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2294 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2295 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.");
2296 Utf8Encode(err_str.data(), SCC_YELLOW);
2297 SetDParamStr(0, err_str);
2298 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2300 /* Reset the font width */
2301 LoadStringWidthTable(searcher->Monospace());
2302 return;
2305 /* Update the font with cache */
2306 LoadStringWidthTable(searcher->Monospace());
2308 #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2310 * For right-to-left languages we need the ICU library. If
2311 * we do not have support for that library we warn the user
2312 * about it with a message. As we do not want the string to
2313 * be translated by the translators, we 'force' it into the
2314 * binary and 'load' it via a BindCString. To do this
2315 * properly we have to set the colour of the string,
2316 * otherwise we end up with a lot of artifacts. The colour
2317 * 'character' might change in the future, so for safety
2318 * we just Utf8 Encode it into the string, which takes
2319 * exactly three characters, so it replaces the "XXX" with
2320 * the colour marker.
2322 if (_current_text_dir != TD_LTR) {
2323 static std::string err_str("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2324 Utf8Encode(err_str.data(), SCC_YELLOW);
2325 SetDParamStr(0, err_str);
2326 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2328 #endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */