OpenTTD Source 20260621-master-g720d10536d
strings.cpp
Go to the documentation of this file.
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
11#include "currency_func.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 "error.h"
21#include "error_func.h"
22#include "strings_func.h"
23#include "rev.h"
24#include "core/endian_func.hpp"
26#include "vehicle_base.h"
27#include "engine_base.h"
28#include "language.h"
29#include "townname_func.h"
30#include "string_func.h"
31#include "company_base.h"
32#include "smallmap_gui.h"
33#include "window_func.h"
34#include "debug.h"
35#include "game/game_text.hpp"
37#include "newgrf_engine.h"
38#include "core/backup_type.hpp"
39#include "gfx_layout.h"
40#include "core/utf8.hpp"
42#include <stack>
43
44#include "table/strings.h"
45#include "table/control_codes.h"
46#include "3rdparty/fmt/std.h"
47
48#include "strings_internal.h"
49
50#include "safeguards.h"
51
55
57
58#ifdef WITH_ICU_I18N
59std::unique_ptr<icu::Collator> _current_collator;
60#endif /* WITH_ICU_I18N */
61
69{
70 assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END));
71 if (this->offset >= this->parameters.size()) {
72 throw std::out_of_range("Trying to read invalid string parameter");
73 }
74
75 auto &param = this->parameters[this->offset++];
76 if (param.type != 0 && param.type != this->next_type) {
77 this->next_type = 0;
78 throw std::out_of_range("Trying to read string parameter with wrong type");
79 }
80 param.type = this->next_type;
81 this->next_type = 0;
82 return param;
83}
84
94
102EncodedString GetEncodedStringWithArgs(StringID str, std::span<const StringParameter> params)
103{
104 std::string result;
105 StringBuilder builder(result);
107 builder.PutIntegerBase(str, 16);
108
109 struct visitor {
110 StringBuilder &builder;
111
112 void operator()(const std::monostate &) {}
113
114 void operator()(const uint64_t &arg)
115 {
116 this->builder.PutUtf8(SCC_ENCODED_NUMERIC);
117 this->builder.PutIntegerBase(arg, 16);
118 }
119
120 void operator()(const std::string &value)
121 {
122#ifdef WITH_ASSERT
123 /* Don't allow an encoded string to contain another encoded string. */
124 {
125 auto [len, c] = DecodeUtf8(value);
126 assert(len == 0 || (c != SCC_ENCODED && c != SCC_ENCODED_INTERNAL && c != SCC_RECORD_SEPARATOR));
127 }
128#endif /* WITH_ASSERT */
129 this->builder.PutUtf8(SCC_ENCODED_STRING);
130 this->builder += value;
131 }
132 };
133
134 visitor v{builder};
135 for (const auto &param : params) {
136 builder.PutUtf8(SCC_RECORD_SEPARATOR);
137 std::visit(v, param.data);
138 }
139
140 return EncodedString{std::move(result)};
141}
142
150EncodedString EncodedString::ReplaceParam(size_t param, StringParameter &&data) const
151{
152 if (this->empty()) return {};
153
154 std::vector<StringParameter> params;
155 StringConsumer consumer(this->string);
156
157 if (!consumer.ReadUtf8If(SCC_ENCODED_INTERNAL)) return {};
158
159 StringID str;
160 if (auto r = consumer.TryReadIntegerBase<uint32_t>(16); r.has_value()) {
161 str = *r;
162 } else {
163 return {};
164 }
165 if (consumer.AnyBytesLeft() && !consumer.ReadUtf8If(SCC_RECORD_SEPARATOR)) return {};
166
167 while (consumer.AnyBytesLeft()) {
168 StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::SKIP_ONE_SEPARATOR));
169
170 if (!record.AnyBytesLeft()) {
171 /* This is an empty parameter. */
172 params.emplace_back(std::monostate{});
173 continue;
174 }
175
176 /* Get the parameter type. */
177 char32_t parameter_type = record.ReadUtf8();
178 switch (parameter_type) {
179 case SCC_ENCODED_NUMERIC: {
180 uint64_t value = record.ReadIntegerBase<uint64_t>(16);
181 assert(!record.AnyBytesLeft());
182 params.emplace_back(value);
183 break;
184 }
185
186 case SCC_ENCODED_STRING: {
187 params.emplace_back(record.Read(StringConsumer::npos));
188 break;
189 }
190
191 default:
192 /* Unknown parameter, make it blank. */
193 params.emplace_back(std::monostate{});
194 break;
195 }
196 }
197
198 if (param >= std::size(params)) return {};
199 params[param] = data;
200 return GetEncodedStringWithArgs(str, params);
201}
202
208{
209 return GetString(STR_JUST_RAW_STRING, this->string);
210}
211
218uint64_t GetParamMaxDigits(uint count, FontSize size)
219{
220 auto [front, next] = GetBroadestDigit(size);
221 uint64_t val = count > 1 ? front : next;
222 for (; count > 1; count--) {
223 val = 10 * val + next;
224 }
225 return val;
226}
227
236uint64_t GetParamMaxValue(uint64_t max_value, uint min_count, FontSize size)
237{
238 uint num_digits = 1;
239 while (max_value >= 10) {
240 num_digits++;
241 max_value /= 10;
242 }
243 return GetParamMaxDigits(std::max(min_count, num_digits), size);
244}
245
246static void StationGetSpecialString(StringBuilder &builder, StationFacilities x);
247static bool GetSpecialNameString(StringBuilder &builder, StringID string, StringParameters &args);
248
249static void FormatString(StringBuilder &builder, std::string_view str, StringParameters &args, uint case_index = 0, bool game_script = false, bool dry_run = false);
250
261static void FormatString(StringBuilder &builder, std::string_view str, std::span<StringParameter> params, uint case_index = 0, bool game_script = false, bool dry_run = false)
262{
263 StringParameters tmp_params{params};
264 FormatString(builder, str, tmp_params, case_index, game_script, dry_run);
265}
266
268 char data[];
269};
270
272 void operator()(LanguagePack *langpack)
273 {
274 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
275 delete[] reinterpret_cast<char *>(langpack);
276 }
277};
278
280 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
281
282 std::vector<std::string_view> strings;
283
284 std::array<uint, TEXT_TAB_END> langtab_num;
285 std::array<uint, TEXT_TAB_END> langtab_start;
286
287 std::string list_separator;
288 std::string ellipsis;
289};
290
291static LoadedLanguagePack _langpack;
292
293static bool _scan_for_gender_data = false;
294
299std::string_view GetListSeparator()
300{
301 return _langpack.list_separator;
302}
303
308std::string_view GetEllipsis()
309{
310 return _langpack.ellipsis;
311}
312
313std::string_view GetStringPtr(StringID string)
314{
315 switch (GetStringTab(string)) {
317 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
318 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
320 default: {
321 const size_t offset = _langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string).base();
322 if (offset < _langpack.strings.size()) return _langpack.strings[offset];
323 return "(undefined string)";
324 }
325 }
326}
327
336void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
337{
338 if (string == 0) {
339 GetStringWithArgs(builder, STR_UNDEFINED, args);
340 return;
341 }
342
343 StringIndexInTab index = GetStringIndex(string);
344 StringTab tab = GetStringTab(string);
345
346 switch (tab) {
347 case TEXT_TAB_TOWN:
348 if (IsInsideMM(string, SPECSTR_TOWNNAME_START, SPECSTR_TOWNNAME_END) && !game_script) {
349 try {
350 GenerateTownNameString(builder, string - SPECSTR_TOWNNAME_START, args.GetNextParameter<uint32_t>());
351 } catch (const std::runtime_error &e) {
352 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
353 builder += "(invalid string parameter)";
354 }
355 return;
356 }
357 break;
358
359 case TEXT_TAB_SPECIAL:
360 if (!game_script) {
361 try {
362 if (GetSpecialNameString(builder, string, args)) return;
363 } catch (const std::runtime_error &e) {
364 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
365 builder += "(invalid string parameter)";
366 return;
367 }
368 }
369 break;
370
371 case TEXT_TAB_OLD_CUSTOM:
372 /* Old table for custom names. This is no longer used */
373 if (!game_script) {
374 FatalError("Incorrect conversion of custom name string.");
375 }
376 break;
377
379 FormatString(builder, GetGameStringPtr(index), args, case_index, true);
380 return;
381 }
382
383 case TEXT_TAB_OLD_NEWGRF:
384 NOT_REACHED();
385
387 FormatString(builder, GetGRFStringPtr(index), args, case_index);
388 return;
389 }
390
391 default:
392 break;
393 }
394
395 if (index >= _langpack.langtab_num[tab]) {
396 if (game_script) {
397 return GetStringWithArgs(builder, STR_UNDEFINED, args);
398 }
399 FatalError("String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n", string);
400 }
401
402 FormatString(builder, GetStringPtr(string), args, case_index);
403}
404
413void GetStringWithArgs(StringBuilder &builder, StringID string, std::span<StringParameter> params, uint case_index, bool game_script)
414{
415 StringParameters tmp_params{params};
416 GetStringWithArgs(builder, string, tmp_params, case_index, game_script);
417}
418
424std::string GetString(StringID string)
425{
426 return GetStringWithArgs(string, {});
427}
428
434void AppendStringInPlace(std::string &result, StringID string)
435{
436 StringBuilder builder(result);
437 GetStringWithArgs(builder, string, {});
438}
439
440void AppendStringWithArgsInPlace(std::string &result, StringID string, std::span<StringParameter> params)
441{
442 StringParameters tmp_params{params};
443 StringBuilder builder(result);
444 GetStringWithArgs(builder, string, tmp_params);
445}
446
454{
455 std::string result;
456 StringBuilder builder(result);
457 GetStringWithArgs(builder, string, args);
458 return result;
459}
460
461std::string GetStringWithArgs(StringID string, std::span<StringParameter> args)
462{
463 std::string result;
464 StringBuilder builder(result);
465 GetStringWithArgs(builder, string, args);
466 return result;
467}
468
469static std::string_view GetDecimalSeparator()
470{
471 std::string_view decimal_separator = _settings_game.locale.digit_decimal_separator;
472 if (decimal_separator.empty()) decimal_separator = _langpack.langpack->digit_decimal_separator;
473 return decimal_separator;
474}
475
482static void FormatNumber(StringBuilder &builder, int64_t number, std::string_view separator)
483{
484 static const int max_digits = 20;
485 uint64_t divisor = 10000000000000000000ULL;
486 int thousands_offset = (max_digits - 1) % 3;
487
488 if (number < 0) {
489 builder.PutChar('-');
490 number = -number;
491 }
492
493 uint64_t num = number;
494 uint64_t tot = 0;
495 for (int i = 0; i < max_digits; i++) {
496 uint64_t quot = 0;
497 if (num >= divisor) {
498 quot = num / divisor;
499 num = num % divisor;
500 }
501 if ((tot |= quot) || i == max_digits - 1) {
502 builder.PutChar('0' + quot); // quot is a single digit
503 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
504 }
505
506 divisor /= 10;
507 }
508}
509
510static void FormatCommaNumber(StringBuilder &builder, int64_t number)
511{
512 std::string_view separator = _settings_game.locale.digit_group_separator;
513 if (separator.empty()) separator = _langpack.langpack->digit_group_separator;
514 FormatNumber(builder, number, separator);
515}
516
517static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
518{
519 fmt::format_to(builder.back_inserter(), "{}", number);
520}
521
522static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
523{
524 fmt::format_to(builder.back_inserter(), "{:0{}d}", number, count);
525}
526
527static void FormatHexNumber(StringBuilder &builder, uint64_t number)
528{
529 fmt::format_to(builder.back_inserter(), "0x{:X}", number);
530}
531
537static void FormatBytes(StringBuilder &builder, int64_t number)
538{
539 assert(number >= 0);
540
541 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
542 static const std::string_view iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
543 uint id = 1;
544 while (number >= 1024 * 1024) {
545 number /= 1024;
546 id++;
547 }
548
549 if (number < 1024) {
550 id = 0;
551 fmt::format_to(builder.back_inserter(), "{}", number);
552 } else if (number < 1024 * 10) {
553 fmt::format_to(builder.back_inserter(), "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
554 } else if (number < 1024 * 100) {
555 fmt::format_to(builder.back_inserter(), "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
556 } else {
557 assert(number < 1024 * 1024);
558 fmt::format_to(builder.back_inserter(), "{}", number / 1024);
559 }
560
561 assert(id < lengthof(iec_prefixes));
562 fmt::format_to(builder.back_inserter(), NBSP "{}B", iec_prefixes[id]);
563}
564
565static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
566{
567 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
568
569 auto tmp_params = MakeParameters(STR_DAY_NUMBER_1ST + ymd.day - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
570 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
571}
572
573static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
574{
575 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
576
577 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
578 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
579}
580
581static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
582{
583 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
584
585 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
586 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
587 FormatString(builder, GetStringPtr(str), tmp_params);
588}
589
590static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
591{
592 /* We are going to make number absolute for printing, so
593 * keep this piece of data as we need it later on */
594 bool negative = number < 0;
595
596 number *= spec->rate;
597
598 /* convert from negative */
599 if (number < 0) {
600 builder.PutUtf8(SCC_PUSH_COLOUR);
601 builder.PutUtf8(SCC_RED);
602 builder.PutChar('-');
603 number = -number;
604 }
605
606 /* Add prefix part, following symbol_pos specification. */
607 if (spec->symbol_pos.Test(CurrencySymbolPosition::Prefix)) builder += spec->prefix;
608
609 StringID number_str = STR_NULL;
610
611 /* For huge numbers, compact the number. */
612 if (compact) {
613 /* Take care of the thousand rounding. Having 1 000 000 k
614 * and 1 000 M is inconsistent, so always use 1 000 M. */
615 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
616 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
617 number_str = STR_CURRENCY_SHORT_TERA;
618 } else if (number >= Money(1'000'000'000'000) - 500'000) {
619 number = (number + 500'000'000) / 1'000'000'000;
620 number_str = STR_CURRENCY_SHORT_GIGA;
621 } else if (number >= 1'000'000'000 - 500) {
622 number = (number + 500'000) / 1'000'000;
623 number_str = STR_CURRENCY_SHORT_MEGA;
624 } else if (number >= 1'000'000) {
625 number = (number + 500) / 1'000;
626 number_str = STR_CURRENCY_SHORT_KILO;
627 }
628 }
629
630 std::string_view separator = _settings_game.locale.digit_group_separator_currency;
631 if (separator.empty()) separator = GetCurrency().separator;
632 if (separator.empty()) separator = _langpack.langpack->digit_group_separator_currency;
633 FormatNumber(builder, number, separator);
634 if (number_str != STR_NULL) {
635 FormatString(builder, GetStringPtr(number_str), {});
636 }
637
638 /* Add suffix part, following symbol_pos specification. */
639 if (spec->symbol_pos.Test(CurrencySymbolPosition::Suffix)) builder += spec->suffix;
640
641 if (negative) {
642 builder.PutUtf8(SCC_POP_COLOUR);
643 }
644}
645
652static int DeterminePluralForm(int64_t count, uint plural_form)
653{
654 /* The absolute value determines plurality */
655 uint64_t n = abs(count);
656
657 switch (plural_form) {
658 default:
659 NOT_REACHED();
660
661 /* Two forms: singular used for one only.
662 * Used in:
663 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
664 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
665 case 0:
666 return n != 1 ? 1 : 0;
667
668 /* Only one form.
669 * Used in:
670 * Hungarian, Japanese, Turkish */
671 case 1:
672 return 0;
673
674 /* Two forms: singular used for 0 and 1.
675 * Used in:
676 * French, Brazilian Portuguese */
677 case 2:
678 return n > 1 ? 1 : 0;
679
680 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
681 * Note: Cases are out of order for hysterical reasons. '0' is last.
682 * Used in:
683 * Latvian */
684 case 3:
685 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
686
687 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
688 * Used in:
689 * Gaelige (Irish) */
690 case 4:
691 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
692
693 /* 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.
694 * Used in:
695 * Lithuanian */
696 case 5:
697 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
698
699 /* 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.
700 * Used in:
701 * Croatian, Russian, Ukrainian */
702 case 6:
703 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
704
705 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
706 * Used in:
707 * Polish */
708 case 7:
709 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
710
711 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
712 * Used in:
713 * Slovenian */
714 case 8:
715 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
716
717 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
718 * Used in:
719 * Icelandic */
720 case 9:
721 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
722
723 /* Three forms: special cases for 1, and 2 to 4
724 * Used in:
725 * Czech, Slovak */
726 case 10:
727 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
728
729 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
730 * Korean doesn't have the concept of plural, but depending on how a
731 * number is pronounced it needs another version of a particle.
732 * As such the plural system is misused to give this distinction.
733 */
734 case 11:
735 switch (n % 10) {
736 case 0: // yeong
737 case 1: // il
738 case 3: // sam
739 case 6: // yuk
740 case 7: // chil
741 case 8: // pal
742 return 0;
743
744 case 2: // i
745 case 4: // sa
746 case 5: // o
747 case 9: // gu
748 return 1;
749
750 default:
751 NOT_REACHED();
752 }
753
754 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
755 * Used in:
756 * Maltese */
757 case 12:
758 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
759 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
760 * Used in:
761 * Scottish Gaelic */
762 case 13:
763 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
764
765 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
766 * Used in:
767 * Romanian */
768 case 14:
769 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
770 }
771}
772
773static void SkipStringChoice(StringConsumer &consumer)
774{
775 uint n = consumer.ReadUint8();
776 uint len = 0;
777 for (uint i = 0; i != n; i++) {
778 len += consumer.ReadUint8();
779 }
780 consumer.Skip(len);
781}
782
783static void ParseStringChoice(StringConsumer &consumer, uint form, StringBuilder &builder)
784{
785 /* <NUM> {Length of each string} {each string} */
786 uint n = consumer.ReadUint8();
787 size_t form_pre = 0, form_len = 0, form_post = 0;
788 for (uint i = 0; i != n; i++) {
789 uint len = consumer.ReadUint8();
790 if (i < form) {
791 form_pre += len;
792 } else if (i > form) {
793 form_post += len;
794 } else {
795 form_len = len;
796 }
797 }
798
799 consumer.Skip(form_pre);
800 builder += consumer.Read(form_len);
801 consumer.Skip(form_post);
802}
803
806 double factor;
807
814 int64_t ToDisplay(int64_t input, bool round = true) const
815 {
816 return round
817 ? (int64_t)std::round(input * this->factor)
818 : (int64_t)(input * this->factor);
819 }
820
828 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
829 {
830 return round
831 ? (int64_t)std::round(input / this->factor / divider)
832 : (int64_t)(input / this->factor / divider);
833 }
834};
835
842
850
853 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
854 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
855 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
856 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
857 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
858};
859
862 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
863 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
864 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
865 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
866 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
867};
868
870static const Units _units_power[] = {
871 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
872 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
873 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
874};
875
878 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
879 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
880 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
881 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
882 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
883 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
884 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
885 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
886 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
887};
888
890static const UnitsLong _units_weight[] = {
891 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
892 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
893 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
894};
895
897static const UnitsLong _units_volume[] = {
898 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
899 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
900 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
901};
902
904static const Units _units_force[] = {
905 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
906 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
907 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
908};
909
911static const Units _units_height[] = {
912 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
913 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
914 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
915 { { .02 }, STR_UNITS_HEIGHT_GAMEUNITS,0 },
916};
917
920 { { 1 }, STR_UNITS_DAYS, 0 },
921 { { 2 }, STR_UNITS_SECONDS, 0 },
922};
923
926 { { 1 }, STR_UNITS_MONTHS, 0 },
927 { { 1 }, STR_UNITS_MINUTES, 0 },
928};
929
932 { { 1 }, STR_UNITS_YEARS, 0 },
933 { { 1 }, STR_UNITS_PERIODS, 0 },
934};
935
938 { { 1 }, STR_UNITS_YEARS, 0 },
939 { { 12 }, STR_UNITS_MINUTES, 0 },
940};
941
948{
949 uint8_t setting = (type == VehicleType::Ship || type == VehicleType::Aircraft) ? _settings_game.locale.units_velocity_nautical : _settings_game.locale.units_velocity;
950
951 assert(setting < lengthof(_units_velocity_calendar));
952 assert(setting < lengthof(_units_velocity_realtime));
953
955
956 return _units_velocity_calendar[setting];
957}
958
966{
967 /* For historical reasons we don't want to mess with the
968 * conversion for speed. So, don't round it and keep the
969 * original conversion factors instead of the real ones. */
970 return GetVelocityUnits(type).c.ToDisplay(speed, false);
971}
972
980{
981 return GetVelocityUnits(type).c.FromDisplay(speed);
982}
983
991{
992 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
993}
994
1002{
1003 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
1004}
1005
1012static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
1013{
1014 std::vector<StringParameter> sub_args;
1015
1016 StringIndexInTab id(consumer.ReadIntegerBase<uint32_t>(16));
1017 if (consumer.AnyBytesLeft() && !consumer.PeekUtf8If(SCC_RECORD_SEPARATOR)) {
1018 consumer.SkipAll();
1019 builder += "(invalid SCC_ENCODED)";
1020 return;
1021 }
1022 if (game_script && id >= TAB_SIZE_GAMESCRIPT) {
1023 consumer.SkipAll();
1024 builder += "(invalid StringID)";
1025 return;
1026 }
1027
1028 while (consumer.AnyBytesLeft()) {
1029 consumer.SkipUtf8If(SCC_RECORD_SEPARATOR);
1030 StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::KEEP_SEPARATOR));
1031
1032 if (!record.AnyBytesLeft()) {
1033 /* This is an empty parameter. */
1034 sub_args.emplace_back(std::monostate{});
1035 continue;
1036 }
1037
1038 /* Get the parameter type. */
1039 char32_t parameter_type = record.ReadUtf8();
1040 switch (parameter_type) {
1041 case SCC_ENCODED: {
1042 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1043 if (param >= TAB_SIZE_GAMESCRIPT) {
1044 builder += "(invalid sub-StringID)";
1045 return;
1046 }
1047 assert(!record.AnyBytesLeft());
1049 sub_args.emplace_back(param);
1050 break;
1051 }
1052
1053 case SCC_ENCODED_NUMERIC: {
1054 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1055 assert(!record.AnyBytesLeft());
1056 sub_args.emplace_back(param);
1057 break;
1058 }
1059
1060 case SCC_ENCODED_STRING: {
1061 sub_args.emplace_back(record.Read(StringConsumer::npos));
1062 break;
1063 }
1064
1065 default:
1066 /* Unknown parameter, make it blank. */
1067 sub_args.emplace_back(std::monostate{});
1068 break;
1069 }
1070 }
1071
1072 StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()};
1073 GetStringWithArgs(builder, stringid, sub_args, true);
1074}
1075
1081static bool IsColourSafe(std::string_view buffer)
1082{
1083 int safety = 0;
1084 for (char32_t ch : Utf8View(buffer)) {
1085 if (ch == SCC_PUSH_COLOUR) {
1086 ++safety;
1087 } else if (ch == SCC_POP_COLOUR) {
1088 --safety;
1089 if (safety < 0) return false;
1090 } else if ((ch >= SCC_BLUE && ch <= SCC_BLACK) || ch == SCC_COLOUR) {
1091 if (safety == 0) return false;
1092 }
1093 }
1094 return true;
1095}
1096
1106static void FormatString(StringBuilder &builder, std::string_view str_arg, StringParameters &args, uint orig_case_index, bool game_script, bool dry_run)
1107{
1108 size_t orig_first_param_offset = args.GetOffset();
1109 bool emit_automatic_push_pop = false;
1110
1111 if (!dry_run) {
1112 /*
1113 * This function is normally called with `dry_run` false, then we call this function again
1114 * with `dry_run` being true. The dry run is required for the gender formatting. For the
1115 * gender determination we need to format a sub string to get the gender, but for that we
1116 * need to know as what string control code type the specific parameter is encoded. Since
1117 * gendered words can be before the "parameter" words, this needs to be determined before
1118 * the actual formatting.
1119 */
1120 std::string buffer;
1121 StringBuilder dry_run_builder(buffer);
1122 FormatString(dry_run_builder, str_arg, args, orig_case_index, game_script, true);
1123 emit_automatic_push_pop = !IsColourSafe(buffer);
1124 /* We have to restore the original offset here to to read the correct values. */
1125 args.SetOffset(orig_first_param_offset);
1126 }
1127 uint next_substr_case_index = 0;
1128 struct StrStackItem {
1129 StringConsumer consumer;
1130 size_t first_param_offset;
1131 uint case_index;
1132
1133 StrStackItem(std::string_view view, size_t first_param_offset, uint case_index)
1134 : consumer(view), first_param_offset(first_param_offset), case_index(case_index)
1135 {}
1136 };
1137 std::stack<StrStackItem, std::vector<StrStackItem>> str_stack;
1138 str_stack.emplace(str_arg, orig_first_param_offset, orig_case_index);
1139
1140 if (emit_automatic_push_pop) builder.PutUtf8(SCC_PUSH_COLOUR);
1141
1142 for (;;) {
1143 try {
1144 while (!str_stack.empty() && !str_stack.top().consumer.AnyBytesLeft()) {
1145 str_stack.pop();
1146 }
1147 if (str_stack.empty()) break;
1148 StringConsumer &consumer = str_stack.top().consumer;
1149 const size_t ref_param_offset = str_stack.top().first_param_offset;
1150 const uint case_index = str_stack.top().case_index;
1151 char32_t b = consumer.ReadUtf8();
1152 assert(b != 0);
1153 if (b == 0) {
1154 /* A NUL character should never be encountered, but for non-debug builds handle it gracefully. */
1155 builder += "(unexpected NUL)";
1156 continue;
1157 }
1158
1159 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
1160 /* We need to pass some stuff as it might be modified. */
1161 b = RemapNewGRFStringControlCode(b, consumer);
1162 if (b == 0) continue;
1163 }
1164
1165 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
1166 builder.PutUtf8(b);
1167 continue;
1168 }
1169
1170 args.SetTypeOfNextParameter(b);
1171 switch (b) {
1172 case SCC_ENCODED:
1174 DecodeEncodedString(consumer, b == SCC_ENCODED, builder);
1175 break;
1176
1177 case SCC_NEWGRF_STRINL: {
1178 StringID substr = consumer.ReadUtf8(STR_NULL);
1179 std::string_view ptr = GetStringPtr(substr);
1180 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1181 next_substr_case_index = 0;
1182 break;
1183 }
1184
1186 StringID substr = args.GetNextParameter<StringID>();
1187 std::string_view ptr = GetStringPtr(substr);
1188 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1189 next_substr_case_index = 0;
1190 break;
1191 }
1192
1193 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1194 /* First read the meta data from the language file. */
1195 size_t offset = ref_param_offset + consumer.ReadUint8();
1196 uint8_t gender = 0;
1197 if (offset >= args.GetNumParameters()) {
1198 /* The offset may come from an external NewGRF, and be invalid. */
1199 builder += "(invalid GENDER parameter)";
1200 } else if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1201 /* Now we need to figure out what text to resolve, i.e.
1202 * what do we need to draw? So get the actual raw string
1203 * first using the control code to get said string. */
1204 std::string input;
1205 {
1206 StringBuilder tmp_builder(input);
1207 tmp_builder.PutUtf8(args.GetTypeAtOffset(offset));
1208 }
1209
1210 std::string buffer;
1211 {
1212 AutoRestoreBackup sgd_backup(_scan_for_gender_data, true);
1213 StringBuilder tmp_builder(buffer);
1214 StringParameters tmp_params = args.GetRemainingParameters(offset);
1215 FormatString(tmp_builder, input, tmp_params);
1216 }
1217
1218 /* The gender is stored at the start of the formatted string.
1219 * Does this string have a gender, if so, set it. */
1220 StringConsumer gender_consumer(buffer);
1221 if (gender_consumer.ReadUtf8If(SCC_GENDER_INDEX)) {
1222 gender = gender_consumer.ReadUint8();
1223 }
1224 }
1225 ParseStringChoice(consumer, gender, builder);
1226 break;
1227 }
1228
1229 /* This sets up the gender for the string.
1230 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1231 case SCC_GENDER_INDEX: { // {GENDER 0}
1232 uint8_t gender = consumer.ReadUint8();
1234 builder.PutUtf8(SCC_GENDER_INDEX);
1235 builder.PutUint8(gender);
1236 }
1237 break;
1238 }
1239
1240 case SCC_PLURAL_LIST: { // {P}
1241 uint8_t plural_form = consumer.ReadUint8(); // contains the plural form for this string
1242 size_t offset = ref_param_offset + consumer.ReadUint8();
1243 const uint64_t *v = nullptr;
1244 /* The offset may come from an external NewGRF, and be invalid. */
1245 if (offset < args.GetNumParameters()) {
1246 v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1247 }
1248 if (v != nullptr) {
1249 ParseStringChoice(consumer, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1250 } else {
1251 SkipStringChoice(consumer);
1252 builder += "(invalid PLURAL parameter)";
1253 }
1254 break;
1255 }
1256
1257 case SCC_ARG_INDEX: { // Move argument pointer
1258 args.SetOffset(ref_param_offset + consumer.ReadUint8());
1259 break;
1260 }
1261
1262 case SCC_SET_CASE: { // {SET_CASE}
1263 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1264 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1265 next_substr_case_index = consumer.ReadUint8();
1266 break;
1267 }
1268
1269 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1270 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
1271 * Each LEN is printed using 2 bytes in little endian order. */
1272 uint num = consumer.ReadUint8();
1273 std::optional<std::string_view> found;
1274 for (; num > 0; --num) {
1275 uint8_t index = consumer.ReadUint8();
1276 uint16_t len = consumer.ReadUint16LE();
1277 auto case_str = consumer.Read(len);
1278 if (index == case_index) {
1279 /* Found the case */
1280 found = case_str;
1281 }
1282 }
1283 uint16_t default_len = consumer.ReadUint16LE();
1284 auto default_str = consumer.Read(default_len);
1285 if (!found.has_value()) found = default_str;
1286 str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "consumer"
1287 break;
1288 }
1289
1290 case SCC_REVISION: // {REV}
1291 builder += _openttd_revision;
1292 break;
1293
1294 case SCC_RAW_STRING_POINTER: // {RAW_STRING}
1295 FormatString(builder, args.GetNextParameterString(), args);
1296 break;
1297
1298 case SCC_STRING: {// {STRING}
1299 StringID string_id = args.GetNextParameter<StringID>();
1300 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1301 /* It's prohibited for the included string to consume any arguments. */
1302 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1303 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1304 next_substr_case_index = 0;
1305 break;
1306 }
1307
1308 case SCC_STRING1:
1309 case SCC_STRING2:
1310 case SCC_STRING3:
1311 case SCC_STRING4:
1312 case SCC_STRING5:
1313 case SCC_STRING6:
1314 case SCC_STRING7: { // {STRING1..7}
1315 /* Strings that consume arguments */
1316 StringID string_id = args.GetNextParameter<StringID>();
1317 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1318 uint size = b - SCC_STRING1 + 1;
1319 if (size > args.GetDataLeft()) {
1320 builder += "(consumed too many parameters)";
1321 } else {
1322 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1323 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1324 args.AdvanceOffset(size);
1325 }
1326 next_substr_case_index = 0;
1327 break;
1328 }
1329
1330 case SCC_COMMA: // {COMMA}
1331 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1332 break;
1333
1334 case SCC_DECIMAL: { // {DECIMAL}
1335 int64_t number = args.GetNextParameter<int64_t>();
1336 int digits = args.GetNextParameter<int>();
1337 if (digits == 0) {
1338 FormatCommaNumber(builder, number);
1339 break;
1340 }
1341
1342 int64_t divisor = PowerOfTen(digits);
1343 int64_t fractional = number % divisor;
1344 number /= divisor;
1345 FormatCommaNumber(builder, number);
1346 fmt::format_to(builder.back_inserter(), "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1347 break;
1348 }
1349
1350 case SCC_NUM: // {NUM}
1351 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1352 break;
1353
1354 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1355 int64_t num = args.GetNextParameter<int64_t>();
1356 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1357 break;
1358 }
1359
1360 case SCC_HEX: // {HEX}
1361 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1362 break;
1363
1364 case SCC_BYTES: // {BYTES}
1365 FormatBytes(builder, args.GetNextParameter<int64_t>());
1366 break;
1367
1368 case SCC_CARGO_TINY: { // {CARGO_TINY}
1369 /* Tiny description of cargotypes. Layout:
1370 * param 1: cargo type
1371 * param 2: cargo count */
1372 CargoType cargo = args.GetNextParameter<CargoType>();
1373 int64_t amount = args.GetNextParameter<int64_t>();
1374
1375 if (cargo >= CargoSpec::GetArraySize()) {
1376 builder += "(invalid cargo type)";
1377 break;
1378 }
1379
1380 switch (CargoSpec::Get(cargo)->units_volume) {
1381 case STR_TONS:
1382 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(amount);
1383 break;
1384
1385 case STR_LITERS:
1386 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(amount);
1387 break;
1388
1389 default:
1390 break;
1391 }
1392
1393 FormatCommaNumber(builder, amount);
1394 break;
1395 }
1396
1397 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1398 /* Short description of cargotypes. Layout:
1399 * param 1: cargo type
1400 * param 2: cargo count */
1401 CargoType cargo = args.GetNextParameter<CargoType>();
1402 int64_t amount = args.GetNextParameter<int64_t>();
1403
1404 if (cargo >= CargoSpec::GetArraySize()) {
1405 builder += "(invalid cargo type)";
1406 break;
1407 }
1408
1409 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1410 switch (cargo_str) {
1411 case STR_TONS: {
1412 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1413 const auto &x = _units_weight[_settings_game.locale.units_weight];
1414 auto tmp_params = MakeParameters(x.c.ToDisplay(amount), x.decimal_places);
1415 FormatString(builder, GetStringPtr(x.l), tmp_params);
1416 break;
1417 }
1418
1419 case STR_LITERS: {
1420 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1421 const auto &x = _units_volume[_settings_game.locale.units_volume];
1422 auto tmp_params = MakeParameters(x.c.ToDisplay(amount), x.decimal_places);
1423 FormatString(builder, GetStringPtr(x.l), tmp_params);
1424 break;
1425 }
1426
1427 default: {
1428 auto tmp_params = MakeParameters(amount);
1429 GetStringWithArgs(builder, cargo_str, tmp_params);
1430 break;
1431 }
1432 }
1433 break;
1434 }
1435
1436 case SCC_CARGO_LONG: { // {CARGO_LONG}
1437 /* First parameter is cargo type, second parameter is cargo count */
1438 CargoType cargo = args.GetNextParameter<CargoType>();
1439 int64_t amount = args.GetNextParameter<int64_t>();
1440 if (cargo < CargoSpec::GetArraySize()) {
1441 auto tmp_args = MakeParameters(amount);
1442 GetStringWithArgs(builder, CargoSpec::Get(cargo)->quantifier, tmp_args);
1443 } else if (!IsValidCargoType(cargo)) {
1444 GetStringWithArgs(builder, STR_QUANTITY_N_A, {});
1445 } else {
1446 builder += "(invalid cargo type)";
1447 }
1448 break;
1449 }
1450
1451 case SCC_CARGO_LIST: { // {CARGO_LIST}
1452 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1453 bool first = true;
1454
1455 std::string_view list_separator = GetListSeparator();
1456 for (const auto &cs : _sorted_cargo_specs) {
1457 if (!cmask.Test(cs->Index())) continue;
1458
1459 if (first) {
1460 first = false;
1461 } else {
1462 /* Add a comma if this is not the first item */
1463 builder += list_separator;
1464 }
1465
1466 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1467 }
1468
1469 /* If first is still true then no cargo is accepted */
1470 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1471
1472 next_substr_case_index = 0;
1473 break;
1474 }
1475
1476 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1477 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1478 break;
1479
1480 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1481 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1482 break;
1483
1484 case SCC_DATE_TINY: // {DATE_TINY}
1485 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1486 break;
1487
1488 case SCC_DATE_SHORT: // {DATE_SHORT}
1489 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1490 next_substr_case_index = 0;
1491 break;
1492
1493 case SCC_DATE_LONG: // {DATE_LONG}
1494 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1495 next_substr_case_index = 0;
1496 break;
1497
1498 case SCC_DATE_ISO: // {DATE_ISO}
1499 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1500 break;
1501
1502 case SCC_FORCE: { // {FORCE}
1503 assert(_settings_game.locale.units_force < lengthof(_units_force));
1504 const auto &x = _units_force[_settings_game.locale.units_force];
1505 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1506 FormatString(builder, GetStringPtr(x.s), tmp_params);
1507 break;
1508 }
1509
1510 case SCC_HEIGHT: { // {HEIGHT}
1511 assert(_settings_game.locale.units_height < lengthof(_units_height));
1512 const auto &x = _units_height[_settings_game.locale.units_height];
1513 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1514 FormatString(builder, GetStringPtr(x.s), tmp_params);
1515 break;
1516 }
1517
1518 case SCC_POWER: { // {POWER}
1519 assert(_settings_game.locale.units_power < lengthof(_units_power));
1520 const auto &x = _units_power[_settings_game.locale.units_power];
1521 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1522 FormatString(builder, GetStringPtr(x.s), tmp_params);
1523 break;
1524 }
1525
1526 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1527 auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight;
1528 assert(setting < lengthof(_units_power_to_weight));
1529 const auto &x = _units_power_to_weight[setting];
1530 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1531 FormatString(builder, GetStringPtr(x.s), tmp_params);
1532 break;
1533 }
1534
1535 case SCC_VELOCITY: { // {VELOCITY}
1536 int64_t arg = args.GetNextParameter<int64_t>();
1537 /* Unpack vehicle type from packed argument to get desired units. */
1538 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1539 const auto &x = GetVelocityUnits(vt);
1540 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1541 FormatString(builder, GetStringPtr(x.s), tmp_params);
1542 break;
1543 }
1544
1545 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1546 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1547 const auto &x = _units_volume[_settings_game.locale.units_volume];
1548 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1549 FormatString(builder, GetStringPtr(x.s), tmp_params);
1550 break;
1551 }
1552
1553 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1554 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1555 const auto &x = _units_volume[_settings_game.locale.units_volume];
1556 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1557 FormatString(builder, GetStringPtr(x.l), tmp_params);
1558 break;
1559 }
1560
1561 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1562 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1563 const auto &x = _units_weight[_settings_game.locale.units_weight];
1564 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1565 FormatString(builder, GetStringPtr(x.s), tmp_params);
1566 break;
1567 }
1568
1569 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1570 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1571 const auto &x = _units_weight[_settings_game.locale.units_weight];
1572 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1573 FormatString(builder, GetStringPtr(x.l), tmp_params);
1574 break;
1575 }
1576
1577 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1578 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GameMode::Menu);
1579 const auto &x = _units_time_days_or_seconds[realtime];
1580 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1581 FormatString(builder, GetStringPtr(x.s), tmp_params);
1582 break;
1583 }
1584
1585 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1586 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GameMode::Menu);
1587 const auto &x = _units_time_months_or_minutes[realtime];
1588 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1589 FormatString(builder, GetStringPtr(x.s), tmp_params);
1590 break;
1591 }
1592
1593 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1594 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GameMode::Menu);
1595 const auto &x = _units_time_years_or_periods[realtime];
1596 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1597 FormatString(builder, GetStringPtr(x.s), tmp_params);
1598 break;
1599 }
1600
1601 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1602 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GameMode::Menu);
1603 const auto &x = _units_time_years_or_minutes[realtime];
1604 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1605 FormatString(builder, GetStringPtr(x.s), tmp_params);
1606 break;
1607 }
1608
1609 case SCC_COMPANY_NAME: { // {COMPANY}
1610 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1611 if (c == nullptr) break;
1612
1613 if (!c->name.empty()) {
1614 auto tmp_params = MakeParameters(c->name);
1615 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1616 } else {
1617 auto tmp_params = MakeParameters(c->name_2);
1618 GetStringWithArgs(builder, c->name_1, tmp_params);
1619 }
1620 break;
1621 }
1622
1623 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1624 CompanyID company = args.GetNextParameter<CompanyID>();
1625
1626 /* Nothing is added for AI or inactive companies */
1627 if (Company::IsValidHumanID(company)) {
1628 auto tmp_params = MakeParameters(company + 1);
1629 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1630 }
1631 break;
1632 }
1633
1634 case SCC_DEPOT_NAME: { // {DEPOT}
1636 if (vt == VehicleType::Aircraft) {
1637 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1638 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1639 break;
1640 }
1641
1642 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1643 if (!d->name.empty()) {
1644 auto tmp_params = MakeParameters(d->name);
1645 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1646 } else {
1647 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1648 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * to_underlying(vt) + (d->town_cn == 0 ? 0 : 1), tmp_params);
1649 }
1650 break;
1651 }
1652
1653 case SCC_ENGINE_NAME: { // {ENGINE}
1654 int64_t arg = args.GetNextParameter<int64_t>();
1655 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1656 if (e == nullptr) break;
1657
1658 if (!e->name.empty() && e->IsEnabled()) {
1659 auto tmp_params = MakeParameters(e->name);
1660 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1661 break;
1662 }
1663
1664 if (e->info.callback_mask.Test(VehicleCallbackMask::Name)) {
1665 std::array<int32_t, 16> regs100;
1666 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr, regs100);
1667 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1668 if (callback == 0x40F) {
1669 const GRFFile *grffile = e->GetGRF();
1670 assert(grffile != nullptr);
1671
1672 builder += GetGRFStringWithTextStack(grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
1673 break;
1674 } else if (callback < 0x400) {
1675 const GRFFile *grffile = e->GetGRF();
1676 assert(grffile != nullptr);
1677
1678 builder += GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100);
1679 break;
1680 }
1681 }
1682
1683 GetStringWithArgs(builder, e->info.string_id, {});
1684 break;
1685 }
1686
1687 case SCC_GROUP_NAME: { // {GROUP}
1688 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1689 if (g == nullptr) break;
1690
1691 if (!g->name.empty()) {
1692 auto tmp_params = MakeParameters(g->name);
1693 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1694 } else {
1695 auto tmp_params = MakeParameters(g->number);
1696 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1697 }
1698 break;
1699 }
1700
1701 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1702 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1703 if (i == nullptr) break;
1704
1705 static bool use_cache = true;
1707 /* Gender is defined by the industry type.
1708 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1709 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), {}, next_substr_case_index);
1710 } else if (use_cache) { // Use cached version if first call
1711 AutoRestoreBackup cache_backup(use_cache, false);
1712 builder += i->GetCachedName();
1713 } else {
1714 /* First print the town name and the industry type name. */
1715 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1716 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1717 }
1718 next_substr_case_index = 0;
1719 break;
1720 }
1721
1722 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1723 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1724 if (c == nullptr) break;
1725
1726 if (!c->president_name.empty()) {
1727 auto tmp_params = MakeParameters(c->president_name);
1728 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1729 } else {
1730 auto tmp_params = MakeParameters(c->president_name_2);
1731 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1732 }
1733 break;
1734 }
1735
1736 case SCC_STATION_NAME: { // {STATION}
1737 StationID sid = args.GetNextParameter<StationID>();
1738 const Station *st = Station::GetIfValid(sid);
1739
1740 if (st == nullptr) {
1741 /* The station doesn't exist anymore. The only place where we might
1742 * be "drawing" an invalid station is in the case of cargo that is
1743 * in transit. */
1744 GetStringWithArgs(builder, STR_UNKNOWN_STATION, {});
1745 break;
1746 }
1747
1748 static bool use_cache = true;
1749 if (use_cache) { // Use cached version if first call
1750 AutoRestoreBackup cache_backup(use_cache, false);
1751 builder += st->GetCachedName();
1752 } else if (!st->name.empty()) {
1753 auto tmp_params = MakeParameters(st->name);
1754 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1755 } else {
1756 StringID string_id = st->string_id;
1757 if (st->indtype != IT_INVALID) {
1758 /* Special case where the industry provides the name for the station */
1759 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1760
1761 /* Industry GRFs can change which might remove the station name and
1762 * thus cause very strange things. Here we check for that before we
1763 * actually set the station name. */
1764 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1765 string_id = indsp->station_name;
1766 }
1767 }
1768
1769 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1770 GetStringWithArgs(builder, string_id, tmp_params);
1771 }
1772 break;
1773 }
1774
1775 case SCC_TOWN_NAME: { // {TOWN}
1776 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1777 if (t == nullptr) break;
1778
1779 static bool use_cache = true;
1780 if (use_cache) { // Use cached version if first call
1781 AutoRestoreBackup cache_backup(use_cache, false);
1782 builder += t->GetCachedName();
1783 } else if (!t->name.empty()) {
1784 auto tmp_params = MakeParameters(t->name);
1785 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1786 } else {
1787 GetTownName(builder, t);
1788 }
1789 break;
1790 }
1791
1792 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1793 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1794 if (wp == nullptr) break;
1795
1796 if (!wp->name.empty()) {
1797 auto tmp_params = MakeParameters(wp->name);
1798 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1799 } else {
1800 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1801 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1802 if (wp->town_cn != 0) string_id++;
1803 GetStringWithArgs(builder, string_id, tmp_params);
1804 }
1805 break;
1806 }
1807
1808 case SCC_VEHICLE_NAME: { // {VEHICLE}
1810 if (v == nullptr) break;
1811
1812 if (!v->name.empty()) {
1813 auto tmp_params = MakeParameters(v->name);
1814 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1815 } else if (v->group_id != DEFAULT_GROUP) {
1816 /* The vehicle has no name, but is member of a group, so print group name */
1817 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1818 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1819 } else {
1820 auto tmp_params = MakeParameters(v->unitnumber);
1821
1822 StringID string_id;
1823 switch (v->type) {
1824 default: string_id = STR_INVALID_VEHICLE; break;
1825 case VehicleType::Train: string_id = STR_SV_TRAIN_NAME; break;
1826 case VehicleType::Road: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1827 case VehicleType::Ship: string_id = STR_SV_SHIP_NAME; break;
1828 case VehicleType::Aircraft: string_id = STR_SV_AIRCRAFT_NAME; break;
1829 }
1830
1831 GetStringWithArgs(builder, string_id, tmp_params);
1832 }
1833 break;
1834 }
1835
1836 case SCC_SIGN_NAME: { // {SIGN}
1837 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1838 if (si == nullptr) break;
1839
1840 if (!si->name.empty()) {
1841 auto tmp_params = MakeParameters(si->name);
1842 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1843 } else {
1844 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, {});
1845 }
1846 break;
1847 }
1848
1849 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1850 StationGetSpecialString(builder, args.GetNextParameter<StationFacilities>());
1851 break;
1852 }
1853
1854 case SCC_COLOUR: { // {COLOUR}
1855 StringControlCode scc = static_cast<StringControlCode>(SCC_BLUE + to_underlying(args.GetNextParameter<Colours>()));
1856 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.PutUtf8(scc);
1857 break;
1858 }
1859
1860 default:
1861 builder.PutUtf8(b);
1862 break;
1863 }
1864 } catch (std::out_of_range &e) {
1865 Debug(misc, 0, "FormatString: {}", e.what());
1866 builder += "(invalid parameter)";
1867 }
1868 }
1869
1870 if (emit_automatic_push_pop) builder.PutUtf8(SCC_POP_COLOUR);
1871}
1872
1873
1874static void StationGetSpecialString(StringBuilder &builder, StationFacilities x)
1875{
1876 if (x.Test(StationFacility::Train)) builder.PutUtf8(SCC_TRAIN);
1877 if (x.Test(StationFacility::TruckStop)) builder.PutUtf8(SCC_LORRY);
1878 if (x.Test(StationFacility::BusStop)) builder.PutUtf8(SCC_BUS);
1879 if (x.Test(StationFacility::Dock)) builder.PutUtf8(SCC_SHIP);
1880 if (x.Test(StationFacility::Airport)) builder.PutUtf8(SCC_PLANE);
1881}
1882
1883static const std::string_view _silly_company_names[] = {
1884 "Bloggs Brothers",
1885 "Tiny Transport Ltd.",
1886 "Express Travel",
1887 "Comfy-Coach & Co.",
1888 "Crush & Bump Ltd.",
1889 "Broken & Late Ltd.",
1890 "Sam Speedy & Son",
1891 "Supersonic Travel",
1892 "Mike's Motors",
1893 "Lightning International",
1894 "Pannik & Loozit Ltd.",
1895 "Inter-City Transport",
1896 "Getout & Pushit Ltd."
1897};
1898
1899static const std::string_view _surname_list[] = {
1900 "Adams",
1901 "Allan",
1902 "Baker",
1903 "Bigwig",
1904 "Black",
1905 "Bloggs",
1906 "Brown",
1907 "Campbell",
1908 "Gordon",
1909 "Hamilton",
1910 "Hawthorn",
1911 "Higgins",
1912 "Green",
1913 "Gribble",
1914 "Jones",
1915 "McAlpine",
1916 "MacDonald",
1917 "McIntosh",
1918 "Muir",
1919 "Murphy",
1920 "Nelson",
1921 "O'Donnell",
1922 "Parker",
1923 "Phillips",
1924 "Pilkington",
1925 "Quigley",
1926 "Sharkey",
1927 "Thomson",
1928 "Watkins"
1929};
1930
1931static const std::string_view _silly_surname_list[] = {
1932 "Grumpy",
1933 "Dozy",
1934 "Speedy",
1935 "Nosey",
1936 "Dribble",
1937 "Mushroom",
1938 "Cabbage",
1939 "Sniffle",
1940 "Fishy",
1941 "Swindle",
1942 "Sneaky",
1943 "Nutkins"
1944};
1945
1946static const char _initial_name_letters[] = {
1947 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1948 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1949};
1950
1951static std::span<const std::string_view> GetSurnameOptions()
1952{
1953 if (_settings_game.game_creation.landscape == LandscapeType::Toyland) return _silly_surname_list;
1954 return _surname_list;
1955}
1956
1962static std::string_view GetSurname(uint32_t seed)
1963{
1964 auto surname_options = GetSurnameOptions();
1965 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1966}
1967
1968static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1969{
1970 builder += GetSurname(seed);
1971 builder += " & Co.";
1972}
1973
1974static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1975{
1976 builder.PutChar(_initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8]);
1977 builder += ". ";
1978
1979 /* The second initial is optional. */
1980 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1981 if (index < std::size(_initial_name_letters)) {
1982 builder.PutChar(_initial_name_letters[index]);
1983 builder += ". ";
1984 }
1985
1986 builder += GetSurname(seed);
1987}
1988
1989static bool GetSpecialNameString(StringBuilder &builder, StringID string, StringParameters &args)
1990{
1991 switch (string) {
1992 case SPECSTR_SILLY_NAME: // Not used in new companies, but retained for old-loader savegames
1993 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1994 return true;
1995
1996 case SPECSTR_ANDCO_NAME: // used for Foobar & Co company names
1997 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1998 return true;
1999
2000 case SPECSTR_PRESIDENT_NAME: // President name
2001 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
2002 return true;
2003 }
2004
2005 /* TownName Transport company names, with the appropriate town name. */
2006 if (IsInsideMM(string, SPECSTR_COMPANY_NAME_START, SPECSTR_COMPANY_NAME_END)) {
2007 GenerateTownNameString(builder, string - SPECSTR_COMPANY_NAME_START, args.GetNextParameter<uint32_t>());
2008 builder += " Transport";
2009 return true;
2010 }
2011
2012 return false;
2013}
2014
2020{
2021 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
2022 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
2023 this->plural_form < LANGUAGE_MAX_PLURAL &&
2024 this->text_dir <= 1 &&
2025 this->newgrflangid < MAX_LANG &&
2026 this->num_genders < MAX_NUM_GENDERS &&
2027 this->num_cases < MAX_NUM_CASES &&
2028 StrValid(this->name) &&
2029 StrValid(this->own_name) &&
2030 StrValid(this->isocode) &&
2034}
2035
2041{
2042 /* "Less than 25% missing" is "sufficiently finished". */
2043 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
2044}
2045
2052{
2053 /* Current language pack */
2054 size_t total_len = 0;
2055 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file.native()), total_len, 1U << 20).release()));
2056 if (!lang_pack) return false;
2057
2058 /* End of read data (+ terminating zero added in ReadFileToMem()) */
2059 const char *end = (char *)lang_pack.get() + total_len + 1;
2060
2061 /* We need at least one byte of lang_pack->data */
2062 if (end <= lang_pack->data || !lang_pack->IsValid()) {
2063 return false;
2064 }
2065
2066 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
2067
2068 uint count = 0;
2069 for (StringTab i : EnumRange(TEXT_TAB_END)) {
2070 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
2071 if (num > TAB_SIZE) return false;
2072
2073 tab_start[i] = count;
2074 tab_num[i] = num;
2075 count += num;
2076 }
2077
2078 /* Allocate offsets */
2079 std::vector<std::string_view> strings;
2080
2081 /* Fill offsets */
2082 char *s = lang_pack->data;
2083 for (uint i = 0; i < count; i++) {
2084 size_t len = static_cast<uint8_t>(*s++);
2085 if (s + len >= end) return false;
2086
2087 if (len >= 0xC0) {
2088 len = ((len & 0x3F) << 8) + static_cast<uint8_t>(*s++);
2089 if (s + len >= end) return false;
2090 }
2091 strings.emplace_back(s, len);
2092 s += len;
2093 }
2094 assert(strings.size() == count);
2095
2096 _langpack.langpack = std::move(lang_pack);
2097 _langpack.strings = std::move(strings);
2098 _langpack.langtab_num = tab_num;
2099 _langpack.langtab_start = tab_start;
2100
2101 _current_language = lang;
2103 _config_language_file = FS2OTTD(_current_language->file.filename().native());
2104 _langpack.list_separator = GetString(STR_LIST_SEPARATOR);
2105 _langpack.ellipsis = GetString(STR_TRUNCATION_ELLIPSIS);
2106
2107#ifdef _WIN32
2108 extern void Win32SetCurrentLocaleName(std::string iso_code);
2109 Win32SetCurrentLocaleName(_current_language->isocode);
2110#endif
2111
2112#ifdef WITH_COCOA
2113 extern void MacOSSetCurrentLocaleName(std::string_view iso_code);
2115#endif
2116
2117#ifdef WITH_ICU_I18N
2118 /* Create a collator instance for our current locale. */
2119 UErrorCode status = U_ZERO_ERROR;
2120 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
2121 /* Sort number substrings by their numerical value. */
2122 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
2123 /* Avoid using the collator if it is not correctly set. */
2124 if (U_FAILURE(status)) {
2125 _current_collator.reset();
2126 }
2127#endif /* WITH_ICU_I18N */
2128
2130
2131 /* Some lists need to be sorted again after a language change. */
2137 InvalidateWindowClassesData(WindowClass::BuildVehicle); // Build vehicle window.
2138 InvalidateWindowClassesData(WindowClass::TrainList); // Train group window.
2139 InvalidateWindowClassesData(WindowClass::RoadVehicleList); // Road vehicle group window.
2140 InvalidateWindowClassesData(WindowClass::ShipList); // Ship group window.
2141 InvalidateWindowClassesData(WindowClass::AircraftList); // Aircraft group window.
2142 InvalidateWindowClassesData(WindowClass::IndustryDirectory); // Industry directory window.
2143 InvalidateWindowClassesData(WindowClass::StationList); // Station list window.
2144
2145 return true;
2146}
2147
2148/* Win32 implementation in win32.cpp.
2149 * OS X implementation in os/macosx/macos.mm. */
2150#if !(defined(_WIN32) || defined(__APPLE__))
2159std::optional<std::string> GetCurrentLocale(const char *param)
2160{
2161 auto env = GetEnv("LANGUAGE");
2162 if (env.has_value()) return std::string{*env};
2163
2164 env = GetEnv("LC_ALL");
2165 if (env.has_value()) return std::string{*env};
2166
2167 if (param != nullptr) {
2168 env = GetEnv(param);
2169 if (env.has_value()) return std::string{*env};
2170 }
2171
2172 env = GetEnv("LANG");
2173 if (env.has_value()) return std::string{*env};
2174
2175 return std::nullopt;
2176}
2177#else
2178std::optional<std::string> GetCurrentLocale(const char *param);
2179#endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2180
2186const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2187{
2188 for (const LanguageMetadata &lang : _languages) {
2189 if (newgrflangid == lang.newgrflangid) return &lang;
2190 }
2191
2192 return nullptr;
2193}
2194
2201static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2202{
2203 auto f = FileHandle::Open(file, "rb");
2204 if (!f.has_value()) return false;
2205
2206 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2207
2208 bool ret = read == 1 && hdr->IsValid();
2209
2210 /* Convert endianness for the windows language ID */
2211 if (ret) {
2212 hdr->missing = FROM_LE16(hdr->missing);
2213 hdr->winlangid = FROM_LE16(hdr->winlangid);
2214 }
2215 return ret;
2216}
2217
2222static void FillLanguageList(const std::string &path)
2223{
2224 std::error_code error_code;
2225 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2226 if (!dir_entry.is_regular_file()) continue;
2227 if (dir_entry.path().extension() != ".lng") continue;
2228
2229 LanguageMetadata lmd;
2230 lmd.file = dir_entry.path();
2231
2232 /* Check whether the file is of the correct version */
2233 std::string file = FS2OTTD(lmd.file.native());
2234 if (!GetLanguageFileHeader(file, &lmd)) {
2235 Debug(misc, 3, "{} is not a valid language file", file);
2236 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2237 Debug(misc, 3, "{}'s language ID is already known", file);
2238 } else {
2239 _languages.push_back(std::move(lmd));
2240 }
2241 }
2242 if (error_code) {
2243 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2244 }
2245}
2246
2252{
2253 for (Searchpath sp : _valid_searchpaths) {
2254 FillLanguageList(FioGetDirectory(sp, Subdirectory::Lang));
2255 }
2256 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2257
2258 /* Acquire the locale of the current system */
2259 auto str_lang = GetCurrentLocale("LC_MESSAGES");
2260 std::string_view lang = str_lang.has_value() ? std::string_view{*str_lang} : "en_GB";
2261
2262 const LanguageMetadata *chosen_language = nullptr;
2263 const LanguageMetadata *language_fallback = nullptr;
2264 const LanguageMetadata *en_GB_fallback = _languages.data();
2265
2266 /* Find a proper language. */
2267 for (const LanguageMetadata &lng : _languages) {
2268 /* We are trying to find a default language. The priority is by
2269 * configuration file, local environment and last, if nothing found,
2270 * English. */
2271 if (_config_language_file == FS2OTTD(lng.file.filename().native())) {
2272 chosen_language = &lng;
2273 break;
2274 }
2275
2276 std::string_view iso_code = lng.isocode;
2277 if (iso_code == "en_GB") en_GB_fallback = &lng;
2278
2279 /* Only auto-pick finished translations */
2280 if (!lng.IsReasonablyFinished()) continue;
2281
2282 if (iso_code.starts_with(lang.substr(0, 5))) chosen_language = &lng;
2283 if (iso_code.starts_with(lang.substr(0, 2))) language_fallback = &lng;
2284 }
2285
2286 /* We haven't found the language in the config nor the one in the locale.
2287 * Now we set it to one of the fallback languages */
2288 if (chosen_language == nullptr) {
2289 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2290 }
2291
2292 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file.native()));
2293}
2294
2300{
2301 return _langpack.langpack->isocode;
2302}
2303
2305{
2306 this->missing_fontsizes.Reset();
2307 this->missing_glyphs.clear();
2308
2309 this->Reset();
2310 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2311 FontSize fs = this->DefaultSize();
2312 FontCache *fc = FontCache::Get(fs);
2313 for (char32_t c : Utf8View(*text)) {
2314 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2315 fs = (FontSize)(c - SCC_FIRST_FONT);
2316 fc = FontCache::Get(fs);
2317 continue;
2318 }
2319
2320 if (!fontsizes.Test(fs)) continue;
2321 if (!IsPrintable(c) || IsTextDirectionChar(c)) continue;
2322 if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue;
2323 if (fc->MapCharToGlyph(c, false) != 0) continue;
2324
2325 this->missing_fontsizes.Set(fs);
2326 this->missing_glyphs.insert(c);
2327 }
2328 }
2329}
2330
2333public:
2338
2339private:
2340 uint i;
2341 uint j;
2342
2343 void Reset() override
2344 {
2345 this->i = 0;
2346 this->j = 0;
2347 }
2348
2350 {
2351 return FontSize::Normal;
2352 }
2353
2354 std::optional<std::string_view> NextString() override
2355 {
2356 if (this->i >= TEXT_TAB_END) return std::nullopt;
2357
2358 std::string_view ret = _langpack.strings[_langpack.langtab_start[this->i] + this->j];
2359
2360 this->j++;
2361 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2362 this->i++;
2363 this->j = 0;
2364 }
2365
2366 return ret;
2367 }
2368};
2369
2383{
2384 static LanguagePackGlyphSearcher pack_searcher;
2385 if (searcher == nullptr) searcher = &pack_searcher;
2386
2388
2389 searcher->DetermineRequiredGlyphs(searcher->fontsizes);
2390 bool bad_font = searcher->missing_fontsizes.Any();
2391
2392#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2393 if (bad_font) {
2394 /* We found an unprintable character... lets try whether we can find
2395 * a fallback font that can print the characters in the current language. */
2396 bool any_font_configured = !_fcsettings.medium.font.empty();
2397 FontCacheSettings backup = _fcsettings;
2398
2399 bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, searcher);
2400 if (!bad_font) {
2402 }
2403
2404 _fcsettings = std::move(backup);
2405
2406 if (!bad_font && any_font_configured) {
2407 /* If the user configured a bad font, and we found a better one,
2408 * show that we loaded the better font instead of the configured one.
2409 */
2410 std::string err_str;
2411 StringBuilder builder(err_str);
2412 builder.PutUtf8(SCC_YELLOW);
2413 builder.Put("The current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2414 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WarningLevel::Warning);
2415 }
2416 }
2417#endif
2418
2419 /* Update the font width cache */
2421
2422 if (bad_font) {
2423 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2424 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2425 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2426 */
2427 std::string err_str;
2428 StringBuilder builder(err_str);
2429 builder.PutUtf8(SCC_YELLOW);
2430 builder.Put("The current font is missing some of the characters used in the texts for this language. Go to Help & Manuals > Fonts, or read the file docs/fonts.md in your OpenTTD directory, to see how to solve this.");
2431 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WarningLevel::Warning);
2432 return;
2433 }
2434
2435#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2436 /*
2437 * For right-to-left languages we need the ICU library. If
2438 * we do not have support for that library we warn the user
2439 * about it with a message. As we do not want the string to
2440 * be translated by the translators, we 'force' it into the
2441 * binary and 'load' it via a BindCString. To do this
2442 * properly we have to set the colour of the string,
2443 * otherwise we end up with a lot of artifacts.
2444 */
2445 if (_current_text_dir != TD_LTR) {
2446 std::string err_str;
2447 StringBuilder builder(err_str);
2448 builder.PutUtf8(SCC_YELLOW);
2449 builder.Put("This version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2450 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WarningLevel::Error);
2451 }
2452#endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */
2453}
Class for backupping variables and making sure they are restored later.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:110
EnumBitSet< CargoType, uint64_t > CargoTypes
Bitset of CargoType elements.
Definition cargo_type.h:113
CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:22
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
void InitializeSortedCargoSpecs()
Initialize the list of sorted cargo specifications.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
void PutUtf8(char32_t c)
Append UTF-8 char.
void Put(std::string_view str)
Append string.
void PutIntegerBase(T value, int base)
Append integer 'value' in given number 'base'.
void PutChar(char c)
Append 8-bit char.
void PutUint8(uint8_t value)
Append binary uint8.
virtual FontSize DefaultSize()=0
Get the default (font) size of the string.
virtual void Reset()=0
Reset the search, i.e.
BaseStringMissingGlyphSearcher(FontSizes fontsizes)
Create this string glyph searcher.
virtual std::optional< std::string_view > NextString()=0
Get the next string to search through.
void DetermineRequiredGlyphs(FontSizes fontsizes) override
Determine set of glyphs required for the current language.
Definition strings.cpp:2304
Container for an encoded string, created by GetEncodedString.
friend EncodedString GetEncodedStringWithArgs(StringID str, std::span< const StringParameter > params)
Encode a string with its parameters into an encoded string.
Definition strings.cpp:102
std::string GetDecodedString() const
Decode the encoded string.
Definition strings.cpp:207
EncodedString ReplaceParam(size_t param, StringParameter &&value) const
Replace a parameter of this EncodedString.
Definition strings.cpp:150
Iterate a range of enum values.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1219
Font cache for basic fonts.
Definition fontcache.h:23
virtual GlyphID MapCharToGlyph(char32_t key, bool fallback=true)=0
Map a character into a glyph.
static void LoadFontCaches(FontSizes fontsizes)
(Re)initialize the font cache related things, i.e.
static FontCache * Get(FontSize fs)
Get the font cache of a given font size.
Definition fontcache.h:136
static bool FindFallbackFont(const std::string &language_isocode, MissingGlyphSearcher *callback)
We would like to have a fallback font as the current one doesn't contain all characters we need.
Definition fontcache.cpp:56
Helper for searching through the language pack.
Definition strings.cpp:2332
LanguagePackGlyphSearcher()
Create this language pack glyph searcher.
Definition strings.cpp:2337
uint j
Iterator for the secondary language tables.
Definition strings.cpp:2341
std::optional< std::string_view > NextString() override
Get the next string to search through.
Definition strings.cpp:2354
FontSize DefaultSize() override
Get the default (font) size of the string.
Definition strings.cpp:2349
uint i
Iterator for the primary language tables.
Definition strings.cpp:2340
void Reset() override
Reset the search, i.e.
Definition strings.cpp:2343
static void Initialize()
Perform initialization of layout engine.
A searcher for missing glyphs.
const FontSizes fontsizes
Font sizes this searcher will try to find.
virtual void DetermineRequiredGlyphs(FontSizes fontsizes)=0
Determine set of glyphs required for the current language.
std::set< char32_t > missing_glyphs
Glyphs to search for.
FontSizes missing_fontsizes
Font sizes to actually search for.
Compose data into a growing std::string.
back_insert_iterator back_inserter()
Create a back-insert-iterator.
Parse data from a string / buffer.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
bool PeekUtf8If(char32_t c) const
Check whether the next UTF-8 char matches 'c'.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
@ SKIP_ONE_SEPARATOR
Read and discard one separator, do not include it in the result.
@ KEEP_SEPARATOR
Keep the separator in the data as next value to be read.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
uint8_t ReadUint8(uint8_t def=0)
Read binary uint8, and advance reader.
uint16_t ReadUint16LE(uint16_t def=0)
Read binary uint16 using little endian, and advance reader.
void SkipAll()
Discard all remaining data.
T ReadIntegerBase(int base, T def=0, bool clamp=false)
Read and parse an integer in number 'base', and advance the reader.
std::string_view Read(size_type len)
Read the next 'len' bytes, and advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
static constexpr size_type npos
Special value for "end of data".
void Skip(size_type len)
Discard some bytes.
void SkipUtf8If(char32_t c)
If the next data matches the UTF-8 char 'c', then skip it.
std::string_view ReadUntilUtf8(char32_t c, SeparatorUsage sep)
Read data until the first occurrence of UTF-8 char 'c', and advance reader.
void SetOffset(size_t offset)
Set the offset within the string from where to return the next result of GetInt64 or GetInt32.
std::string_view GetNextParameterString()
Get the next string parameter from our parameters.
StringParameters GetRemainingParameters()
Get a new instance of StringParameters that is a "range" into the remaining existing parameters.
std::span< StringParameter > parameters
Array with the actual parameters.
const StringParameter & GetNextParameterReference()
Get the next parameter from our parameters.
Definition strings.cpp:68
void AdvanceOffset(size_t advance)
Advance the offset within the string from where to return the next result of GetInt64 or GetInt32.
uint64_t GetNextParameter()
Get the next parameter from our parameters.
size_t offset
Current offset in the parameters span.
char32_t next_type
The type of the next data that is retrieved.
size_t GetOffset()
Get the current offset, so it can be backed up for certain processing steps, or be used to offset the...
size_t GetNumParameters() const
Return the number of parameters.
size_t GetDataLeft() const
Return the amount of elements which can still be read.
char32_t GetTypeAtOffset(size_t offset) const
Get the type of a specific element.
static YearMonthDay ConvertDateToYMD(Date date)
Converts a Date to a Year, Month & Day.
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
StrongType::Typedef< int32_t, DateTag< struct Calendar >, StrongType::Compare, StrongType::Integer > Date
Constant span of UTF-8 encoded data.
Definition utf8.hpp:28
Definition of stuff that is very close to a company, like the company struct itself.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
@ SCC_ENCODED
Encoded string marker and sub-string parameter.
@ SCC_ENCODED_NUMERIC
Encoded numeric parameter.
@ SCC_ENCODED_STRING
Encoded string parameter.
@ SCC_NEWGRF_STRINL
Inline another string at the current position, StringID is encoded in the string.
@ SCC_NEWGRF_FIRST
The next variables are part of a NewGRF subsystem for creating text strings.
@ SCC_NEWGRF_PRINT_WORD_STRING_ID
81: Read 2 bytes from the stack as String ID
@ SCC_ENCODED_INTERNAL
Encoded text from OpenTTD.
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
@ Suffix
Show the suffix value.
@ Prefix
Show the prefix value.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Base for all depots (except hangars).
PoolID< uint16_t, struct DepotIDTag, 64000, 0xFFFF > DepotID
Type for the unique identifier of depots.
Definition depot_type.h:15
Function to handling different endian machines.
Base class for engines.
PoolID< uint16_t, struct EngineIDTag, 64000, 0xFFFF > EngineID
Unique identification number of an engine.
Definition engine_type.h:26
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
Functions related to errors.
@ Warning
Other information.
Definition error.h:25
@ Error
Errors (eg. saving/loading failed).
Definition error.h:26
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
Error reporting related functions.
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
Definition fileio.cpp:1079
Functions for standard in/out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
@ Lang
Subdirectory for all translation files.
Definition fileio_type.h:98
void ReconsiderGameScriptLanguage()
Reconsider the game script language, so we use the right one.
std::string_view GetGameStringPtr(StringIndexInTab id)
Get the string pointer of a particular game string.
Base functions regarding game texts.
std::pair< uint8_t, uint8_t > GetBroadestDigit(FontSize size)
Determine the broadest digits for guessing the maximum width of a n-digit number.
Definition gfx.cpp:1306
void LoadStringWidthTable(FontSizes fontsizes)
Initialize _stringwidth_table cache for the specified font sizes.
Definition gfx.cpp:1260
Functions related to laying out the texts.
FontSize
Available font sizes.
Definition gfx_type.h:248
@ Normal
Index of the normal font in the font tables.
Definition gfx_type.h:249
Colours
One of 16 base colours used for companies and windows/widgets.
Definition gfx_type.h:284
EnumBitSet< FontSize, uint8_t > FontSizes
Bitset of FontSize elements.
Definition gfx_type.h:260
constexpr FontSizes FONTSIZES_REQUIRED
Mask of font sizes required to be present.
Definition gfx_type.h:265
static constexpr GroupID DEFAULT_GROUP
Ungrouped vehicles are in this group.
Definition group_type.h:18
Base of all industries.
const IndustrySpec * GetIndustrySpec(IndustryType thistype)
Accessor for array _industry_specs.
void SortIndustryTypes()
Initialize the list of sorted industry types.
@ Toyland
Landscape with funky industries and vehicles.
Information about languages and their files.
LanguageList _languages
The actual list of language meta data.
Definition strings.cpp:53
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:54
static const uint8_t MAX_NUM_GENDERS
Maximum number of supported genders.
Definition language.h:20
std::unique_ptr< icu::Collator > _current_collator
Collator for the language currently in use.
Definition strings.cpp:59
std::vector< LanguageMetadata > LanguageList
Type for the list of language meta data.
Definition language.h:94
static const uint8_t MAX_NUM_CASES
Maximum number of supported cases.
Definition language.h:21
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
constexpr uint64_t PowerOfTen(int power)
Computes ten to the given power.
void BuildContentTypeStringList()
Build array of all strings corresponding to the content types.
User interface for downloading files.
@ CBID_VEHICLE_NAME
Called to determine the engine name to show.
uint16_t GetVehicleCallback(CallbackID callback, uint32_t param1, uint32_t param2, EngineID engine, const Vehicle *v, std::span< int32_t > regs100)
Evaluate a newgrf callback for vehicles.
Functions for NewGRF engines.
std::string_view GetGRFStringPtr(StringIndexInTab stringid)
Get a C-string from a stringid set by a newgrf.
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span< const int32_t > textstack)
Format a GRF string using the text ref stack for parameters.
char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
Emit OpenTTD's internal string code for the different NewGRF string codes.
Header of Action 04 "universal holder" structure and functions.
StrongType::Typedef< uint32_t, struct GRFStringIDTag, StrongType::Compare, StrongType::Integer > GRFStringID
Type for GRF-internal string IDs.
static constexpr GRFStringID GRFSTR_MISC_GRF_TEXT
Miscellaneous GRF text range.
@ Menu
In the main menu.
Definition openttd.h:19
Declaration of OTTD revision dependent variables.
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
Base class for signs.
PoolID< uint16_t, struct SignIDTag, 64000, 0xFFFF > SignID
The type of the IDs of signs.
Definition signs_type.h:16
void BuildIndustriesLegend()
Fills an array for the industries legends.
Smallmap GUI functions.
Base classes/functions for stations.
@ Dock
Station with a dock.
@ TruckStop
Station with truck stops.
@ Train
Station with train station.
@ Airport
Station with an airport.
@ BusStop
Station with bus stops.
EnumBitSet< StationFacility, uint8_t > StationFacilities
Bitset of StationFacility elements.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
std::optional< std::string_view > GetEnv(const char *variable)
Get the environment variable using std::getenv and when it is an empty string (or nullptr),...
Definition string.cpp:856
bool StrValid(std::span< const char > str)
Checks whether the given string is valid, i.e.
Definition string.cpp:207
Parse strings.
Functions related to low-level strings.
bool IsTextDirectionChar(char32_t c)
Is the given character a text direction character.
void MacOSSetCurrentLocaleName(std::string_view iso_code)
Store current language locale as a CoreFoundation locale.
#define NBSP
A non-breaking space.
Definition string_type.h:16
uint64_t GetParamMaxValue(uint64_t max_value, uint min_count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:236
static void FormatNumber(StringBuilder &builder, int64_t number, std::string_view separator)
Format a number into a string.
Definition strings.cpp:482
EncodedString GetEncodedStringWithArgs(StringID str, std::span< const StringParameter > params)
Encode a string with its parameters into an encoded string.
Definition strings.cpp:102
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
Definition strings.cpp:336
std::optional< std::string > GetCurrentLocale(const char *param)
Determine the current charset based on the environment First check some default values,...
Definition strings.cpp:2159
std::string_view GetListSeparator()
Get the list separator string for the current language.
Definition strings.cpp:299
static const Units _units_height[]
Unit conversions for height.
Definition strings.cpp:911
static void FormatBytes(StringBuilder &builder, int64_t number)
Format a given number as a number of bytes with the SI prefix.
Definition strings.cpp:537
void AppendStringInPlace(std::string &result, StringID string)
Resolve the given StringID and append in place into an existing std::string with formatting but no pa...
Definition strings.cpp:434
uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given (internal) speed to the display speed.
Definition strings.cpp:965
std::string _config_language_file
The file (name) stored in the configuration.
Definition strings.cpp:52
static const Units GetVelocityUnits(VehicleType type)
Get the correct velocity units depending on the vehicle type and whether we're using real-time units.
Definition strings.cpp:947
static int DeterminePluralForm(int64_t count, uint plural_form)
Determine the "plural" index given a plural form and a number.
Definition strings.cpp:652
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
Convert the given display speed to the km/h-ish speed.
Definition strings.cpp:1001
static const Units _units_power[]
Unit conversions for power.
Definition strings.cpp:870
static std::string_view GetSurname(uint32_t seed)
Get the surname of the president with the given seed.
Definition strings.cpp:1962
void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
Check whether the currently loaded language pack uses characters that the currently loaded font does ...
Definition strings.cpp:2382
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
std::string_view GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
Definition strings.cpp:2299
static bool _scan_for_gender_data
Are we scanning for the gender of the current string? (instead of formatting it).
Definition strings.cpp:293
static const UnitsLong _units_volume[]
Unit conversions for volume.
Definition strings.cpp:897
static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
Decodes an encoded string during FormatString.
Definition strings.cpp:1012
static const UnitsLong _units_weight[]
Unit conversions for weight.
Definition strings.cpp:890
static const Units _units_velocity_calendar[]
Unit conversions for velocity.
Definition strings.cpp:852
static void FillLanguageList(const std::string &path)
Search for the languages in the given directory and add them to the _languages list.
Definition strings.cpp:2222
uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given km/h-ish speed to the display speed.
Definition strings.cpp:990
static const Units _units_force[]
Unit conversions for force.
Definition strings.cpp:904
void InitializeLanguagePacks()
Make a list of the available language packs.
Definition strings.cpp:2251
const LanguageMetadata * GetLanguage(uint8_t newgrflangid)
Get the language with the given NewGRF language ID.
Definition strings.cpp:2186
static bool IsColourSafe(std::string_view buffer)
Test if a string contains colour codes, and is not wrapped by push/pop codes.
Definition strings.cpp:1081
static const Units _units_time_days_or_seconds[]
Unit conversions for time in calendar days or wallclock seconds.
Definition strings.cpp:919
uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
Convert the given display speed to the (internal) speed.
Definition strings.cpp:979
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
static const Units _units_velocity_realtime[]
Unit conversions for velocity.
Definition strings.cpp:861
bool ReadLanguagePack(const LanguageMetadata *lang)
Read a particular language.
Definition strings.cpp:2051
static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
Reads the language file header and checks compatibility.
Definition strings.cpp:2201
static const Units _units_time_months_or_minutes[]
Unit conversions for time in calendar months or wallclock minutes.
Definition strings.cpp:925
static void FormatString(StringBuilder &builder, std::string_view str, StringParameters &args, uint case_index=0, bool game_script=false, bool dry_run=false)
Parse most format codes within a string and write the result to a buffer.
Definition strings.cpp:1106
static const Units _units_time_years_or_minutes[]
Unit conversions for time in calendar years or wallclock minutes.
Definition strings.cpp:937
static const Units _units_power_to_weight[]
Unit conversions for power to weight.
Definition strings.cpp:877
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:218
std::string_view GetEllipsis()
Get the ellipsis string for the current language.
Definition strings.cpp:308
static const Units _units_time_years_or_periods[]
Unit conversions for time in calendar years or economic periods.
Definition strings.cpp:931
Functions related to OTTD's strings.
StringTab GetStringTab(StringID str)
Extract the StringTab from a StringID.
auto MakeParameters(Args &&... args)
Helper to create the StringParameters with its own buffer with the given parameter values.
StringID MakeStringID(StringTab tab, StringIndexInTab index)
Create a StringID.
StringIndexInTab GetStringIndex(StringID str)
Extract the StringIndex from a StringID.
Types and functions related to the internal workings of formatting OpenTTD's strings.
void GenerateTownNameString(StringBuilder &builder, size_t lang, uint32_t seed)
Generates town name from given seed.
Definition townname.cpp:975
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
StrongType::Typedef< uint32_t, struct StringIndexInTabTag, StrongType::Compare, StrongType::Integer > StringIndexInTab
The index/offset of a string within a StringTab.
static constexpr StringID SPECSTR_COMPANY_NAME_START
Special strings for company names on the form "TownName transport".
static constexpr StringID SPECSTR_SILLY_NAME
Special string for silly company names.
static const uint TAB_SIZE_GAMESCRIPT
Number of strings for GameScripts.
static const uint MAX_LANG
Maximum number of languages supported by the game, and the NewGRF specs.
static constexpr StringID SPECSTR_ANDCO_NAME
Special string for Surname & Co company names.
static constexpr StringID SPECSTR_PRESIDENT_NAME
Special string for the president's name.
static constexpr StringID SPECSTR_TOWNNAME_START
Special strings for town names.
static const uint TAB_SIZE
Number of strings per StringTab.
StringTab
StringTabs to group StringIDs.
@ TEXT_TAB_NEWGRF_START
Start of NewGRF supplied strings.
@ TEXT_TAB_GAMESCRIPT_START
Start of GameScript supplied strings.
@ TEXT_TAB_END
End of language files.
TextDirection
Directions a text can go to.
@ TD_LTR
Text is written left-to-right by default.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
std::string name
Name of vehicle.
StringID string_id
Default name (town area) of station.
Town * town
The town this station is associated with.
std::string name
Custom name.
VehicleType type
Type of vehicle.
StringID units_volume
Name of a single unit of cargo of this type.
Definition cargotype.h:96
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:141
static size_t GetArraySize()
Total number of cargospecs, both valid and invalid.
Definition cargotype.h:130
std::string president_name
Name of the president if the user changed it.
uint32_t name_2
Parameter of name_1.
uint32_t president_name_2
Parameter of president_name_1.
StringID name_1
Name of the company if the user did not change it.
StringID president_name_1
Name of the president if the user did not change it.
std::string name
Name of the company if the user changed it.
static bool IsValidHumanID(auto index)
Is this company a valid company, not controlled by a NoAI program?
Specification of a currency.
std::string separator
The thousands separator for this currency.
CurrencySymbolPositions symbol_pos
Which currency symbols should we show?
std::string prefix
Prefix to apply when formatting money in this currency.
std::string suffix
Suffix to apply when formatting money in this currency.
uint16_t rate
The conversion rate compared to the base currency.
uint16_t town_cn
The N-1th depot for this town (consecutive number).
Definition depot_base.h:22
Settings for the four different fonts.
Definition fontcache.h:198
Dynamic data of a loaded NewGRF.
Definition newgrf.h:128
Group data.
Definition group.h:76
std::string name
Group Name.
Definition group.h:77
uint16_t number
Per-company group number.
Definition group.h:89
Defines the data structure for constructing industry.
StringID name
Displayed name of the industry.
StringID station_name
Default name for nearby station.
Defines the internal data of a functional industry.
Definition industry.h:64
IndustryType type
type of industry.
Definition industry.h:117
Town * town
Nearest town.
Definition industry.h:109
Make sure the size is right.
Definition language.h:89
std::filesystem::path file
Name of the file we read this data from.
Definition language.h:90
Header of a language file.
Definition language.h:24
uint8_t plural_form
plural form index
Definition language.h:41
bool IsValid() const
Check whether the header is a valid header for OpenTTD.
Definition strings.cpp:2019
bool IsReasonablyFinished() const
Check whether a translation is sufficiently finished to offer it to the public.
Definition strings.cpp:2040
uint32_t version
32-bits of auto generated version info which is basically a hash of strings.h
Definition language.h:28
char isocode[16]
the ISO code for the language (not country code)
Definition language.h:31
char own_name[32]
the localized name of this language
Definition language.h:30
char name[32]
the international name of this language
Definition language.h:29
uint16_t winlangid
Windows language ID: Windows cannot and will not convert isocodes to something it can use to determin...
Definition language.h:51
uint8_t num_genders
the number of genders of this language
Definition language.h:53
uint16_t missing
number of missing strings.
Definition language.h:40
char digit_group_separator[8]
Thousand separator used for anything not currencies.
Definition language.h:35
uint8_t newgrflangid
newgrf language id
Definition language.h:52
uint8_t text_dir
default direction of the text
Definition language.h:42
uint8_t num_cases
the number of cases of this language
Definition language.h:54
uint32_t ident
32-bits identifier
Definition language.h:27
char digit_decimal_separator[8]
Decimal separator.
Definition language.h:39
char digit_group_separator_currency[8]
Thousand separator used for currencies.
Definition language.h:37
static const uint32_t IDENT
Identifier for OpenTTD language files, big endian for "LANG".
Definition language.h:25
char data[]
List of strings.
Definition strings.cpp:268
std::string list_separator
Current list separator string.
Definition strings.cpp:287
std::string ellipsis
Current ellipsis string.
Definition strings.cpp:288
std::array< uint, TEXT_TAB_END > langtab_num
Offset into langpack offs.
Definition strings.cpp:284
std::array< uint, TEXT_TAB_END > langtab_start
Offset into langpack offs.
Definition strings.cpp:285
static Depot * Get(auto index)
static Company * GetIfValid(auto index)
static Station * GetIfValid(auto index)
Station data structure.
The data required to format and validate a single parameter of a string.
Town data structure.
Definition town.h:64
std::string name
Custom town name. If empty, the town was not renamed and uses the generated name.
Definition town.h:74
Helper for unit conversion.
Definition strings.cpp:805
double factor
Amount to multiply or divide upon conversion.
Definition strings.cpp:806
int64_t ToDisplay(int64_t input, bool round=true) const
Convert value from OpenTTD's internal unit into the displayed value.
Definition strings.cpp:814
int64_t FromDisplay(int64_t input, bool round=true, int64_t divider=1) const
Convert the displayed value back into a value of OpenTTD's internal unit.
Definition strings.cpp:828
Information about a specific unit system with a long variant.
Definition strings.cpp:844
StringID s
String for the short variant of the unit.
Definition strings.cpp:846
StringID l
String for the long variant of the unit.
Definition strings.cpp:847
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:848
UnitConversion c
Conversion.
Definition strings.cpp:845
Information about a specific unit system.
Definition strings.cpp:837
StringID s
String for the unit.
Definition strings.cpp:839
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:840
UnitConversion c
Conversion.
Definition strings.cpp:838
Vehicle data structure.
GroupID group_id
Index of group Pool array.
UnitID unitnumber
unit number, for display purposes only
Representation of a waypoint.
uint16_t town_cn
The N-1th waypoint for this town (consecutive number).
Definition of the game-calendar-timer.
Base of the town class.
Town name generator stuff.
std::pair< size_t, char32_t > DecodeUtf8(std::string_view buf)
Decode a character from UTF-8.
Definition utf8.cpp:46
Handling of UTF-8 encoded data.
Base class for all vehicles.
PoolID< uint32_t, struct VehicleIDTag, 0xFF000, 0xFFFFF > VehicleID
The type all our vehicle IDs have.
VehicleType
Available vehicle types.
@ Ship
Ship vehicle type.
@ Aircraft
Aircraft vehicle type.
@ Road
Road vehicle type.
@ Train
Train vehicle type.
Base of waypoints.
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
Definition win32.cpp:387
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:371
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition window.cpp:3333
Window functions not directly related to making/drawing windows.