OpenTTD Source 20260206-master-g4d4e37dbf1
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.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 {
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[]; // list of strings
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 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
608 * The only remaining value is 1 (suffix), so everything that is not 1 */
609 if (spec->symbol_pos != 1) builder += spec->prefix;
610
611 StringID number_str = STR_NULL;
612
613 /* For huge numbers, compact the number. */
614 if (compact) {
615 /* Take care of the thousand rounding. Having 1 000 000 k
616 * and 1 000 M is inconsistent, so always use 1 000 M. */
617 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
618 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
619 number_str = STR_CURRENCY_SHORT_TERA;
620 } else if (number >= Money(1'000'000'000'000) - 500'000) {
621 number = (number + 500'000'000) / 1'000'000'000;
622 number_str = STR_CURRENCY_SHORT_GIGA;
623 } else if (number >= 1'000'000'000 - 500) {
624 number = (number + 500'000) / 1'000'000;
625 number_str = STR_CURRENCY_SHORT_MEGA;
626 } else if (number >= 1'000'000) {
627 number = (number + 500) / 1'000;
628 number_str = STR_CURRENCY_SHORT_KILO;
629 }
630 }
631
632 std::string_view separator = _settings_game.locale.digit_group_separator_currency;
633 if (separator.empty()) separator = GetCurrency().separator;
634 if (separator.empty()) separator = _langpack.langpack->digit_group_separator_currency;
635 FormatNumber(builder, number, separator);
636 if (number_str != STR_NULL) {
637 FormatString(builder, GetStringPtr(number_str), {});
638 }
639
640 /* Add suffix part, following symbol_pos specification.
641 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
642 * The only remaining value is 1 (prefix), so everything that is not 0 */
643 if (spec->symbol_pos != 0) builder += spec->suffix;
644
645 if (negative) {
646 builder.PutUtf8(SCC_POP_COLOUR);
647 }
648}
649
656static int DeterminePluralForm(int64_t count, uint plural_form)
657{
658 /* The absolute value determines plurality */
659 uint64_t n = abs(count);
660
661 switch (plural_form) {
662 default:
663 NOT_REACHED();
664
665 /* Two forms: singular used for one only.
666 * Used in:
667 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
668 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
669 case 0:
670 return n != 1 ? 1 : 0;
671
672 /* Only one form.
673 * Used in:
674 * Hungarian, Japanese, Turkish */
675 case 1:
676 return 0;
677
678 /* Two forms: singular used for 0 and 1.
679 * Used in:
680 * French, Brazilian Portuguese */
681 case 2:
682 return n > 1 ? 1 : 0;
683
684 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
685 * Note: Cases are out of order for hysterical reasons. '0' is last.
686 * Used in:
687 * Latvian */
688 case 3:
689 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
690
691 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
692 * Used in:
693 * Gaelige (Irish) */
694 case 4:
695 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
696
697 /* 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.
698 * Used in:
699 * Lithuanian */
700 case 5:
701 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
702
703 /* 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.
704 * Used in:
705 * Croatian, Russian, Ukrainian */
706 case 6:
707 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
708
709 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
710 * Used in:
711 * Polish */
712 case 7:
713 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
714
715 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
716 * Used in:
717 * Slovenian */
718 case 8:
719 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
720
721 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
722 * Used in:
723 * Icelandic */
724 case 9:
725 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
726
727 /* Three forms: special cases for 1, and 2 to 4
728 * Used in:
729 * Czech, Slovak */
730 case 10:
731 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
732
733 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
734 * Korean doesn't have the concept of plural, but depending on how a
735 * number is pronounced it needs another version of a particle.
736 * As such the plural system is misused to give this distinction.
737 */
738 case 11:
739 switch (n % 10) {
740 case 0: // yeong
741 case 1: // il
742 case 3: // sam
743 case 6: // yuk
744 case 7: // chil
745 case 8: // pal
746 return 0;
747
748 case 2: // i
749 case 4: // sa
750 case 5: // o
751 case 9: // gu
752 return 1;
753
754 default:
755 NOT_REACHED();
756 }
757
758 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
759 * Used in:
760 * Maltese */
761 case 12:
762 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
763 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
764 * Used in:
765 * Scottish Gaelic */
766 case 13:
767 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
768
769 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
770 * Used in:
771 * Romanian */
772 case 14:
773 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
774 }
775}
776
777static void SkipStringChoice(StringConsumer &consumer)
778{
779 uint n = consumer.ReadUint8();
780 uint len = 0;
781 for (uint i = 0; i != n; i++) {
782 len += consumer.ReadUint8();
783 }
784 consumer.Skip(len);
785}
786
787static void ParseStringChoice(StringConsumer &consumer, uint form, StringBuilder &builder)
788{
789 /* <NUM> {Length of each string} {each string} */
790 uint n = consumer.ReadUint8();
791 size_t form_pre = 0, form_len = 0, form_post = 0;
792 for (uint i = 0; i != n; i++) {
793 uint len = consumer.ReadUint8();
794 if (i < form) {
795 form_pre += len;
796 } else if (i > form) {
797 form_post += len;
798 } else {
799 form_len = len;
800 }
801 }
802
803 consumer.Skip(form_pre);
804 builder += consumer.Read(form_len);
805 consumer.Skip(form_post);
806}
807
810 double factor;
811
818 int64_t ToDisplay(int64_t input, bool round = true) const
819 {
820 return round
821 ? (int64_t)std::round(input * this->factor)
822 : (int64_t)(input * this->factor);
823 }
824
832 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
833 {
834 return round
835 ? (int64_t)std::round(input / this->factor / divider)
836 : (int64_t)(input / this->factor / divider);
837 }
838};
839
846
854
857 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
858 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
859 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
860 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
861 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
862};
863
866 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
867 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
868 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
869 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
870 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
871};
872
874static const Units _units_power[] = {
875 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
876 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
877 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
878};
879
882 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
883 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
884 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
885 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
886 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
887 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
888 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
889 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
890 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
891};
892
894static const UnitsLong _units_weight[] = {
895 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
896 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
897 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
898};
899
901static const UnitsLong _units_volume[] = {
902 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
903 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
904 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
905};
906
908static const Units _units_force[] = {
909 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
910 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
911 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
912};
913
915static const Units _units_height[] = {
916 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
917 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
918 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
919 { { .02 }, STR_UNITS_HEIGHT_GAMEUNITS,0 },
920};
921
924 { { 1 }, STR_UNITS_DAYS, 0 },
925 { { 2 }, STR_UNITS_SECONDS, 0 },
926};
927
930 { { 1 }, STR_UNITS_MONTHS, 0 },
931 { { 1 }, STR_UNITS_MINUTES, 0 },
932};
933
936 { { 1 }, STR_UNITS_YEARS, 0 },
937 { { 1 }, STR_UNITS_PERIODS, 0 },
938};
939
942 { { 1 }, STR_UNITS_YEARS, 0 },
943 { { 12 }, STR_UNITS_MINUTES, 0 },
944};
945
952{
953 uint8_t setting = (type == VEH_SHIP || type == VEH_AIRCRAFT) ? _settings_game.locale.units_velocity_nautical : _settings_game.locale.units_velocity;
954
955 assert(setting < lengthof(_units_velocity_calendar));
956 assert(setting < lengthof(_units_velocity_realtime));
957
959
960 return _units_velocity_calendar[setting];
961}
962
970{
971 /* For historical reasons we don't want to mess with the
972 * conversion for speed. So, don't round it and keep the
973 * original conversion factors instead of the real ones. */
974 return GetVelocityUnits(type).c.ToDisplay(speed, false);
975}
976
984{
985 return GetVelocityUnits(type).c.FromDisplay(speed);
986}
987
995{
996 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
997}
998
1006{
1007 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
1008}
1009
1016static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
1017{
1018 std::vector<StringParameter> sub_args;
1019
1020 StringIndexInTab id(consumer.ReadIntegerBase<uint32_t>(16));
1021 if (consumer.AnyBytesLeft() && !consumer.PeekUtf8If(SCC_RECORD_SEPARATOR)) {
1022 consumer.SkipAll();
1023 builder += "(invalid SCC_ENCODED)";
1024 return;
1025 }
1026 if (game_script && id >= TAB_SIZE_GAMESCRIPT) {
1027 consumer.SkipAll();
1028 builder += "(invalid StringID)";
1029 return;
1030 }
1031
1032 while (consumer.AnyBytesLeft()) {
1033 consumer.SkipUtf8If(SCC_RECORD_SEPARATOR);
1034 StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::KEEP_SEPARATOR));
1035
1036 if (!record.AnyBytesLeft()) {
1037 /* This is an empty parameter. */
1038 sub_args.emplace_back(std::monostate{});
1039 continue;
1040 }
1041
1042 /* Get the parameter type. */
1043 char32_t parameter_type = record.ReadUtf8();
1044 switch (parameter_type) {
1045 case SCC_ENCODED: {
1046 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1047 if (param >= TAB_SIZE_GAMESCRIPT) {
1048 builder += "(invalid sub-StringID)";
1049 return;
1050 }
1051 assert(!record.AnyBytesLeft());
1053 sub_args.emplace_back(param);
1054 break;
1055 }
1056
1057 case SCC_ENCODED_NUMERIC: {
1058 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1059 assert(!record.AnyBytesLeft());
1060 sub_args.emplace_back(param);
1061 break;
1062 }
1063
1064 case SCC_ENCODED_STRING: {
1065 sub_args.emplace_back(record.Read(StringConsumer::npos));
1066 break;
1067 }
1068
1069 default:
1070 /* Unknown parameter, make it blank. */
1071 sub_args.emplace_back(std::monostate{});
1072 break;
1073 }
1074 }
1075
1076 StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()};
1077 GetStringWithArgs(builder, stringid, sub_args, true);
1078}
1079
1085static bool IsColourSafe(std::string_view buffer)
1086{
1087 int safety = 0;
1088 for (char32_t ch : Utf8View(buffer)) {
1089 if (ch == SCC_PUSH_COLOUR) {
1090 ++safety;
1091 } else if (ch == SCC_POP_COLOUR) {
1092 --safety;
1093 if (safety < 0) return false;
1094 } else if ((ch >= SCC_BLUE && ch <= SCC_BLACK) || ch == SCC_COLOUR) {
1095 if (safety == 0) return false;
1096 }
1097 }
1098 return true;
1099}
1100
1110static void FormatString(StringBuilder &builder, std::string_view str_arg, StringParameters &args, uint orig_case_index, bool game_script, bool dry_run)
1111{
1112 size_t orig_first_param_offset = args.GetOffset();
1113 bool emit_automatic_push_pop = false;
1114
1115 if (!dry_run) {
1116 /*
1117 * This function is normally called with `dry_run` false, then we call this function again
1118 * with `dry_run` being true. The dry run is required for the gender formatting. For the
1119 * gender determination we need to format a sub string to get the gender, but for that we
1120 * need to know as what string control code type the specific parameter is encoded. Since
1121 * gendered words can be before the "parameter" words, this needs to be determined before
1122 * the actual formatting.
1123 */
1124 std::string buffer;
1125 StringBuilder dry_run_builder(buffer);
1126 FormatString(dry_run_builder, str_arg, args, orig_case_index, game_script, true);
1127 emit_automatic_push_pop = !IsColourSafe(buffer);
1128 /* We have to restore the original offset here to to read the correct values. */
1129 args.SetOffset(orig_first_param_offset);
1130 }
1131 uint next_substr_case_index = 0;
1132 struct StrStackItem {
1133 StringConsumer consumer;
1134 size_t first_param_offset;
1135 uint case_index;
1136
1137 StrStackItem(std::string_view view, size_t first_param_offset, uint case_index)
1138 : consumer(view), first_param_offset(first_param_offset), case_index(case_index)
1139 {}
1140 };
1141 std::stack<StrStackItem, std::vector<StrStackItem>> str_stack;
1142 str_stack.emplace(str_arg, orig_first_param_offset, orig_case_index);
1143
1144 if (emit_automatic_push_pop) builder.PutUtf8(SCC_PUSH_COLOUR);
1145
1146 for (;;) {
1147 try {
1148 while (!str_stack.empty() && !str_stack.top().consumer.AnyBytesLeft()) {
1149 str_stack.pop();
1150 }
1151 if (str_stack.empty()) break;
1152 StringConsumer &consumer = str_stack.top().consumer;
1153 const size_t ref_param_offset = str_stack.top().first_param_offset;
1154 const uint case_index = str_stack.top().case_index;
1155 char32_t b = consumer.ReadUtf8();
1156 assert(b != 0);
1157 if (b == 0) {
1158 /* A NUL character should never be encountered, but for non-debug builds handle it gracefully. */
1159 builder += "(unexpected NUL)";
1160 continue;
1161 }
1162
1163 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
1164 /* We need to pass some stuff as it might be modified. */
1165 b = RemapNewGRFStringControlCode(b, consumer);
1166 if (b == 0) continue;
1167 }
1168
1169 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
1170 builder.PutUtf8(b);
1171 continue;
1172 }
1173
1174 args.SetTypeOfNextParameter(b);
1175 switch (b) {
1176 case SCC_ENCODED:
1178 DecodeEncodedString(consumer, b == SCC_ENCODED, builder);
1179 break;
1180
1181 case SCC_NEWGRF_STRINL: {
1182 StringID substr = consumer.ReadUtf8(STR_NULL);
1183 std::string_view ptr = GetStringPtr(substr);
1184 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1185 next_substr_case_index = 0;
1186 break;
1187 }
1188
1190 StringID substr = args.GetNextParameter<StringID>();
1191 std::string_view ptr = GetStringPtr(substr);
1192 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1193 next_substr_case_index = 0;
1194 break;
1195 }
1196
1197 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1198 /* First read the meta data from the language file. */
1199 size_t offset = ref_param_offset + consumer.ReadUint8();
1200 uint8_t gender = 0;
1201 if (offset >= args.GetNumParameters()) {
1202 /* The offset may come from an external NewGRF, and be invalid. */
1203 builder += "(invalid GENDER parameter)";
1204 } else if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1205 /* Now we need to figure out what text to resolve, i.e.
1206 * what do we need to draw? So get the actual raw string
1207 * first using the control code to get said string. */
1208 std::string input;
1209 {
1210 StringBuilder tmp_builder(input);
1211 tmp_builder.PutUtf8(args.GetTypeAtOffset(offset));
1212 }
1213
1214 std::string buffer;
1215 {
1216 AutoRestoreBackup sgd_backup(_scan_for_gender_data, true);
1217 StringBuilder tmp_builder(buffer);
1218 StringParameters tmp_params = args.GetRemainingParameters(offset);
1219 FormatString(tmp_builder, input, tmp_params);
1220 }
1221
1222 /* The gender is stored at the start of the formatted string.
1223 * Does this string have a gender, if so, set it. */
1224 StringConsumer gender_consumer(buffer);
1225 if (gender_consumer.ReadUtf8If(SCC_GENDER_INDEX)) {
1226 gender = gender_consumer.ReadUint8();
1227 }
1228 }
1229 ParseStringChoice(consumer, gender, builder);
1230 break;
1231 }
1232
1233 /* This sets up the gender for the string.
1234 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1235 case SCC_GENDER_INDEX: { // {GENDER 0}
1236 uint8_t gender = consumer.ReadUint8();
1238 builder.PutUtf8(SCC_GENDER_INDEX);
1239 builder.PutUint8(gender);
1240 }
1241 break;
1242 }
1243
1244 case SCC_PLURAL_LIST: { // {P}
1245 uint8_t plural_form = consumer.ReadUint8(); // contains the plural form for this string
1246 size_t offset = ref_param_offset + consumer.ReadUint8();
1247 const uint64_t *v = nullptr;
1248 /* The offset may come from an external NewGRF, and be invalid. */
1249 if (offset < args.GetNumParameters()) {
1250 v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1251 }
1252 if (v != nullptr) {
1253 ParseStringChoice(consumer, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1254 } else {
1255 SkipStringChoice(consumer);
1256 builder += "(invalid PLURAL parameter)";
1257 }
1258 break;
1259 }
1260
1261 case SCC_ARG_INDEX: { // Move argument pointer
1262 args.SetOffset(ref_param_offset + consumer.ReadUint8());
1263 break;
1264 }
1265
1266 case SCC_SET_CASE: { // {SET_CASE}
1267 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1268 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1269 next_substr_case_index = consumer.ReadUint8();
1270 break;
1271 }
1272
1273 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1274 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
1275 * Each LEN is printed using 2 bytes in little endian order. */
1276 uint num = consumer.ReadUint8();
1277 std::optional<std::string_view> found;
1278 for (; num > 0; --num) {
1279 uint8_t index = consumer.ReadUint8();
1280 uint16_t len = consumer.ReadUint16LE();
1281 auto case_str = consumer.Read(len);
1282 if (index == case_index) {
1283 /* Found the case */
1284 found = case_str;
1285 }
1286 }
1287 uint16_t default_len = consumer.ReadUint16LE();
1288 auto default_str = consumer.Read(default_len);
1289 if (!found.has_value()) found = default_str;
1290 str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "consumer"
1291 break;
1292 }
1293
1294 case SCC_REVISION: // {REV}
1295 builder += _openttd_revision;
1296 break;
1297
1298 case SCC_RAW_STRING_POINTER: // {RAW_STRING}
1299 FormatString(builder, args.GetNextParameterString(), args);
1300 break;
1301
1302 case SCC_STRING: {// {STRING}
1303 StringID string_id = args.GetNextParameter<StringID>();
1304 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1305 /* It's prohibited for the included string to consume any arguments. */
1306 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1307 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1308 next_substr_case_index = 0;
1309 break;
1310 }
1311
1312 case SCC_STRING1:
1313 case SCC_STRING2:
1314 case SCC_STRING3:
1315 case SCC_STRING4:
1316 case SCC_STRING5:
1317 case SCC_STRING6:
1318 case SCC_STRING7: { // {STRING1..7}
1319 /* Strings that consume arguments */
1320 StringID string_id = args.GetNextParameter<StringID>();
1321 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1322 uint size = b - SCC_STRING1 + 1;
1323 if (size > args.GetDataLeft()) {
1324 builder += "(consumed too many parameters)";
1325 } else {
1326 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1327 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1328 args.AdvanceOffset(size);
1329 }
1330 next_substr_case_index = 0;
1331 break;
1332 }
1333
1334 case SCC_COMMA: // {COMMA}
1335 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1336 break;
1337
1338 case SCC_DECIMAL: { // {DECIMAL}
1339 int64_t number = args.GetNextParameter<int64_t>();
1340 int digits = args.GetNextParameter<int>();
1341 if (digits == 0) {
1342 FormatCommaNumber(builder, number);
1343 break;
1344 }
1345
1346 int64_t divisor = PowerOfTen(digits);
1347 int64_t fractional = number % divisor;
1348 number /= divisor;
1349 FormatCommaNumber(builder, number);
1350 fmt::format_to(builder.back_inserter(), "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1351 break;
1352 }
1353
1354 case SCC_NUM: // {NUM}
1355 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1356 break;
1357
1358 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1359 int64_t num = args.GetNextParameter<int64_t>();
1360 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1361 break;
1362 }
1363
1364 case SCC_HEX: // {HEX}
1365 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1366 break;
1367
1368 case SCC_BYTES: // {BYTES}
1369 FormatBytes(builder, args.GetNextParameter<int64_t>());
1370 break;
1371
1372 case SCC_CARGO_TINY: { // {CARGO_TINY}
1373 /* Tiny description of cargotypes. Layout:
1374 * param 1: cargo type
1375 * param 2: cargo count */
1376 CargoType cargo = args.GetNextParameter<CargoType>();
1377 int64_t amount = args.GetNextParameter<int64_t>();
1378
1379 if (cargo >= CargoSpec::GetArraySize()) {
1380 builder += "(invalid cargo type)";
1381 break;
1382 }
1383
1384 switch (CargoSpec::Get(cargo)->units_volume) {
1385 case STR_TONS:
1386 amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(amount);
1387 break;
1388
1389 case STR_LITERS:
1390 amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(amount);
1391 break;
1392
1393 default:
1394 break;
1395 }
1396
1397 FormatCommaNumber(builder, amount);
1398 break;
1399 }
1400
1401 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1402 /* Short description of cargotypes. Layout:
1403 * param 1: cargo type
1404 * param 2: cargo count */
1405 CargoType cargo = args.GetNextParameter<CargoType>();
1406 int64_t amount = args.GetNextParameter<int64_t>();
1407
1408 if (cargo >= CargoSpec::GetArraySize()) {
1409 builder += "(invalid cargo type)";
1410 break;
1411 }
1412
1413 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1414 switch (cargo_str) {
1415 case STR_TONS: {
1416 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1417 const auto &x = _units_weight[_settings_game.locale.units_weight];
1418 auto tmp_params = MakeParameters(x.c.ToDisplay(amount), x.decimal_places);
1419 FormatString(builder, GetStringPtr(x.l), tmp_params);
1420 break;
1421 }
1422
1423 case STR_LITERS: {
1424 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1425 const auto &x = _units_volume[_settings_game.locale.units_volume];
1426 auto tmp_params = MakeParameters(x.c.ToDisplay(amount), x.decimal_places);
1427 FormatString(builder, GetStringPtr(x.l), tmp_params);
1428 break;
1429 }
1430
1431 default: {
1432 auto tmp_params = MakeParameters(amount);
1433 GetStringWithArgs(builder, cargo_str, tmp_params);
1434 break;
1435 }
1436 }
1437 break;
1438 }
1439
1440 case SCC_CARGO_LONG: { // {CARGO_LONG}
1441 /* First parameter is cargo type, second parameter is cargo count */
1442 CargoType cargo = args.GetNextParameter<CargoType>();
1443 int64_t amount = args.GetNextParameter<int64_t>();
1444 if (cargo < CargoSpec::GetArraySize()) {
1445 auto tmp_args = MakeParameters(amount);
1446 GetStringWithArgs(builder, CargoSpec::Get(cargo)->quantifier, tmp_args);
1447 } else if (!IsValidCargoType(cargo)) {
1448 GetStringWithArgs(builder, STR_QUANTITY_N_A, {});
1449 } else {
1450 builder += "(invalid cargo type)";
1451 }
1452 break;
1453 }
1454
1455 case SCC_CARGO_LIST: { // {CARGO_LIST}
1456 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1457 bool first = true;
1458
1459 std::string_view list_separator = GetListSeparator();
1460 for (const auto &cs : _sorted_cargo_specs) {
1461 if (!HasBit(cmask, cs->Index())) continue;
1462
1463 if (first) {
1464 first = false;
1465 } else {
1466 /* Add a comma if this is not the first item */
1467 builder += list_separator;
1468 }
1469
1470 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1471 }
1472
1473 /* If first is still true then no cargo is accepted */
1474 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1475
1476 next_substr_case_index = 0;
1477 break;
1478 }
1479
1480 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1481 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1482 break;
1483
1484 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1485 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1486 break;
1487
1488 case SCC_DATE_TINY: // {DATE_TINY}
1489 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1490 break;
1491
1492 case SCC_DATE_SHORT: // {DATE_SHORT}
1493 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1494 next_substr_case_index = 0;
1495 break;
1496
1497 case SCC_DATE_LONG: // {DATE_LONG}
1498 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1499 next_substr_case_index = 0;
1500 break;
1501
1502 case SCC_DATE_ISO: // {DATE_ISO}
1503 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1504 break;
1505
1506 case SCC_FORCE: { // {FORCE}
1507 assert(_settings_game.locale.units_force < lengthof(_units_force));
1508 const auto &x = _units_force[_settings_game.locale.units_force];
1509 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1510 FormatString(builder, GetStringPtr(x.s), tmp_params);
1511 break;
1512 }
1513
1514 case SCC_HEIGHT: { // {HEIGHT}
1515 assert(_settings_game.locale.units_height < lengthof(_units_height));
1516 const auto &x = _units_height[_settings_game.locale.units_height];
1517 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1518 FormatString(builder, GetStringPtr(x.s), tmp_params);
1519 break;
1520 }
1521
1522 case SCC_POWER: { // {POWER}
1523 assert(_settings_game.locale.units_power < lengthof(_units_power));
1524 const auto &x = _units_power[_settings_game.locale.units_power];
1525 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1526 FormatString(builder, GetStringPtr(x.s), tmp_params);
1527 break;
1528 }
1529
1530 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1531 auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight;
1532 assert(setting < lengthof(_units_power_to_weight));
1533 const auto &x = _units_power_to_weight[setting];
1534 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1535 FormatString(builder, GetStringPtr(x.s), tmp_params);
1536 break;
1537 }
1538
1539 case SCC_VELOCITY: { // {VELOCITY}
1540 int64_t arg = args.GetNextParameter<int64_t>();
1541 /* Unpack vehicle type from packed argument to get desired units. */
1542 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1543 const auto &x = GetVelocityUnits(vt);
1544 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1545 FormatString(builder, GetStringPtr(x.s), tmp_params);
1546 break;
1547 }
1548
1549 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1550 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1551 const auto &x = _units_volume[_settings_game.locale.units_volume];
1552 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1553 FormatString(builder, GetStringPtr(x.s), tmp_params);
1554 break;
1555 }
1556
1557 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1558 assert(_settings_game.locale.units_volume < lengthof(_units_volume));
1559 const auto &x = _units_volume[_settings_game.locale.units_volume];
1560 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1561 FormatString(builder, GetStringPtr(x.l), tmp_params);
1562 break;
1563 }
1564
1565 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1566 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1567 const auto &x = _units_weight[_settings_game.locale.units_weight];
1568 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1569 FormatString(builder, GetStringPtr(x.s), tmp_params);
1570 break;
1571 }
1572
1573 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1574 assert(_settings_game.locale.units_weight < lengthof(_units_weight));
1575 const auto &x = _units_weight[_settings_game.locale.units_weight];
1576 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1577 FormatString(builder, GetStringPtr(x.l), tmp_params);
1578 break;
1579 }
1580
1581 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1582 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1583 const auto &x = _units_time_days_or_seconds[realtime];
1584 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1585 FormatString(builder, GetStringPtr(x.s), tmp_params);
1586 break;
1587 }
1588
1589 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1590 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1591 const auto &x = _units_time_months_or_minutes[realtime];
1592 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1593 FormatString(builder, GetStringPtr(x.s), tmp_params);
1594 break;
1595 }
1596
1597 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1598 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1599 const auto &x = _units_time_years_or_periods[realtime];
1600 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1601 FormatString(builder, GetStringPtr(x.s), tmp_params);
1602 break;
1603 }
1604
1605 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1606 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1607 const auto &x = _units_time_years_or_minutes[realtime];
1608 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1609 FormatString(builder, GetStringPtr(x.s), tmp_params);
1610 break;
1611 }
1612
1613 case SCC_COMPANY_NAME: { // {COMPANY}
1614 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1615 if (c == nullptr) break;
1616
1617 if (!c->name.empty()) {
1618 auto tmp_params = MakeParameters(c->name);
1619 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1620 } else {
1621 auto tmp_params = MakeParameters(c->name_2);
1622 GetStringWithArgs(builder, c->name_1, tmp_params);
1623 }
1624 break;
1625 }
1626
1627 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1628 CompanyID company = args.GetNextParameter<CompanyID>();
1629
1630 /* Nothing is added for AI or inactive companies */
1631 if (Company::IsValidHumanID(company)) {
1632 auto tmp_params = MakeParameters(company + 1);
1633 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1634 }
1635 break;
1636 }
1637
1638 case SCC_DEPOT_NAME: { // {DEPOT}
1640 if (vt == VEH_AIRCRAFT) {
1641 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1642 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1643 break;
1644 }
1645
1646 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1647 if (!d->name.empty()) {
1648 auto tmp_params = MakeParameters(d->name);
1649 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1650 } else {
1651 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1652 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1653 }
1654 break;
1655 }
1656
1657 case SCC_ENGINE_NAME: { // {ENGINE}
1658 int64_t arg = args.GetNextParameter<int64_t>();
1659 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1660 if (e == nullptr) break;
1661
1662 if (!e->name.empty() && e->IsEnabled()) {
1663 auto tmp_params = MakeParameters(e->name);
1664 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1665 break;
1666 }
1667
1668 if (e->info.callback_mask.Test(VehicleCallbackMask::Name)) {
1669 std::array<int32_t, 16> regs100;
1670 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr, regs100);
1671 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1672 if (callback == 0x40F) {
1673 const GRFFile *grffile = e->GetGRF();
1674 assert(grffile != nullptr);
1675
1676 builder += GetGRFStringWithTextStack(grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
1677 break;
1678 } else if (callback < 0x400) {
1679 const GRFFile *grffile = e->GetGRF();
1680 assert(grffile != nullptr);
1681
1682 builder += GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100);
1683 break;
1684 }
1685 }
1686
1687 GetStringWithArgs(builder, e->info.string_id, {});
1688 break;
1689 }
1690
1691 case SCC_GROUP_NAME: { // {GROUP}
1692 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1693 if (g == nullptr) break;
1694
1695 if (!g->name.empty()) {
1696 auto tmp_params = MakeParameters(g->name);
1697 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1698 } else {
1699 auto tmp_params = MakeParameters(g->number);
1700 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1701 }
1702 break;
1703 }
1704
1705 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1706 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1707 if (i == nullptr) break;
1708
1709 static bool use_cache = true;
1711 /* Gender is defined by the industry type.
1712 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1713 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), {}, next_substr_case_index);
1714 } else if (use_cache) { // Use cached version if first call
1715 AutoRestoreBackup cache_backup(use_cache, false);
1716 builder += i->GetCachedName();
1717 } else {
1718 /* First print the town name and the industry type name. */
1719 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1720 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1721 }
1722 next_substr_case_index = 0;
1723 break;
1724 }
1725
1726 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1727 const Company *c = Company::GetIfValid(args.GetNextParameter<CompanyID>());
1728 if (c == nullptr) break;
1729
1730 if (!c->president_name.empty()) {
1731 auto tmp_params = MakeParameters(c->president_name);
1732 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1733 } else {
1734 auto tmp_params = MakeParameters(c->president_name_2);
1735 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1736 }
1737 break;
1738 }
1739
1740 case SCC_STATION_NAME: { // {STATION}
1741 StationID sid = args.GetNextParameter<StationID>();
1742 const Station *st = Station::GetIfValid(sid);
1743
1744 if (st == nullptr) {
1745 /* The station doesn't exist anymore. The only place where we might
1746 * be "drawing" an invalid station is in the case of cargo that is
1747 * in transit. */
1748 GetStringWithArgs(builder, STR_UNKNOWN_STATION, {});
1749 break;
1750 }
1751
1752 static bool use_cache = true;
1753 if (use_cache) { // Use cached version if first call
1754 AutoRestoreBackup cache_backup(use_cache, false);
1755 builder += st->GetCachedName();
1756 } else if (!st->name.empty()) {
1757 auto tmp_params = MakeParameters(st->name);
1758 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1759 } else {
1760 StringID string_id = st->string_id;
1761 if (st->indtype != IT_INVALID) {
1762 /* Special case where the industry provides the name for the station */
1763 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1764
1765 /* Industry GRFs can change which might remove the station name and
1766 * thus cause very strange things. Here we check for that before we
1767 * actually set the station name. */
1768 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1769 string_id = indsp->station_name;
1770 }
1771 }
1772
1773 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1774 GetStringWithArgs(builder, string_id, tmp_params);
1775 }
1776 break;
1777 }
1778
1779 case SCC_TOWN_NAME: { // {TOWN}
1780 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1781 if (t == nullptr) break;
1782
1783 static bool use_cache = true;
1784 if (use_cache) { // Use cached version if first call
1785 AutoRestoreBackup cache_backup(use_cache, false);
1786 builder += t->GetCachedName();
1787 } else if (!t->name.empty()) {
1788 auto tmp_params = MakeParameters(t->name);
1789 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1790 } else {
1791 GetTownName(builder, t);
1792 }
1793 break;
1794 }
1795
1796 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1797 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1798 if (wp == nullptr) break;
1799
1800 if (!wp->name.empty()) {
1801 auto tmp_params = MakeParameters(wp->name);
1802 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1803 } else {
1804 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1805 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1806 if (wp->town_cn != 0) string_id++;
1807 GetStringWithArgs(builder, string_id, tmp_params);
1808 }
1809 break;
1810 }
1811
1812 case SCC_VEHICLE_NAME: { // {VEHICLE}
1814 if (v == nullptr) break;
1815
1816 if (!v->name.empty()) {
1817 auto tmp_params = MakeParameters(v->name);
1818 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1819 } else if (v->group_id != DEFAULT_GROUP) {
1820 /* The vehicle has no name, but is member of a group, so print group name */
1821 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1822 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1823 } else {
1824 auto tmp_params = MakeParameters(v->unitnumber);
1825
1826 StringID string_id;
1827 switch (v->type) {
1828 default: string_id = STR_INVALID_VEHICLE; break;
1829 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1830 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1831 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1832 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1833 }
1834
1835 GetStringWithArgs(builder, string_id, tmp_params);
1836 }
1837 break;
1838 }
1839
1840 case SCC_SIGN_NAME: { // {SIGN}
1841 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1842 if (si == nullptr) break;
1843
1844 if (!si->name.empty()) {
1845 auto tmp_params = MakeParameters(si->name);
1846 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1847 } else {
1848 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, {});
1849 }
1850 break;
1851 }
1852
1853 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1854 StationGetSpecialString(builder, args.GetNextParameter<StationFacilities>());
1855 break;
1856 }
1857
1858 case SCC_COLOUR: { // {COLOUR}
1859 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1860 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.PutUtf8(scc);
1861 break;
1862 }
1863
1864 default:
1865 builder.PutUtf8(b);
1866 break;
1867 }
1868 } catch (std::out_of_range &e) {
1869 Debug(misc, 0, "FormatString: {}", e.what());
1870 builder += "(invalid parameter)";
1871 }
1872 }
1873
1874 if (emit_automatic_push_pop) builder.PutUtf8(SCC_POP_COLOUR);
1875}
1876
1877
1878static void StationGetSpecialString(StringBuilder &builder, StationFacilities x)
1879{
1880 if (x.Test(StationFacility::Train)) builder.PutUtf8(SCC_TRAIN);
1881 if (x.Test(StationFacility::TruckStop)) builder.PutUtf8(SCC_LORRY);
1882 if (x.Test(StationFacility::BusStop)) builder.PutUtf8(SCC_BUS);
1883 if (x.Test(StationFacility::Dock)) builder.PutUtf8(SCC_SHIP);
1884 if (x.Test(StationFacility::Airport)) builder.PutUtf8(SCC_PLANE);
1885}
1886
1887static const std::string_view _silly_company_names[] = {
1888 "Bloggs Brothers",
1889 "Tiny Transport Ltd.",
1890 "Express Travel",
1891 "Comfy-Coach & Co.",
1892 "Crush & Bump Ltd.",
1893 "Broken & Late Ltd.",
1894 "Sam Speedy & Son",
1895 "Supersonic Travel",
1896 "Mike's Motors",
1897 "Lightning International",
1898 "Pannik & Loozit Ltd.",
1899 "Inter-City Transport",
1900 "Getout & Pushit Ltd."
1901};
1902
1903static const std::string_view _surname_list[] = {
1904 "Adams",
1905 "Allan",
1906 "Baker",
1907 "Bigwig",
1908 "Black",
1909 "Bloggs",
1910 "Brown",
1911 "Campbell",
1912 "Gordon",
1913 "Hamilton",
1914 "Hawthorn",
1915 "Higgins",
1916 "Green",
1917 "Gribble",
1918 "Jones",
1919 "McAlpine",
1920 "MacDonald",
1921 "McIntosh",
1922 "Muir",
1923 "Murphy",
1924 "Nelson",
1925 "O'Donnell",
1926 "Parker",
1927 "Phillips",
1928 "Pilkington",
1929 "Quigley",
1930 "Sharkey",
1931 "Thomson",
1932 "Watkins"
1933};
1934
1935static const std::string_view _silly_surname_list[] = {
1936 "Grumpy",
1937 "Dozy",
1938 "Speedy",
1939 "Nosey",
1940 "Dribble",
1941 "Mushroom",
1942 "Cabbage",
1943 "Sniffle",
1944 "Fishy",
1945 "Swindle",
1946 "Sneaky",
1947 "Nutkins"
1948};
1949
1950static const char _initial_name_letters[] = {
1951 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1952 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1953};
1954
1955static std::span<const std::string_view> GetSurnameOptions()
1956{
1957 if (_settings_game.game_creation.landscape == LandscapeType::Toyland) return _silly_surname_list;
1958 return _surname_list;
1959}
1960
1966static std::string_view GetSurname(uint32_t seed)
1967{
1968 auto surname_options = GetSurnameOptions();
1969 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1970}
1971
1972static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1973{
1974 builder += GetSurname(seed);
1975 builder += " & Co.";
1976}
1977
1978static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1979{
1980 builder.PutChar(_initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8]);
1981 builder += ". ";
1982
1983 /* The second initial is optional. */
1984 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1985 if (index < std::size(_initial_name_letters)) {
1986 builder.PutChar(_initial_name_letters[index]);
1987 builder += ". ";
1988 }
1989
1990 builder += GetSurname(seed);
1991}
1992
1993static bool GetSpecialNameString(StringBuilder &builder, StringID string, StringParameters &args)
1994{
1995 switch (string) {
1996 case SPECSTR_SILLY_NAME: // Not used in new companies, but retained for old-loader savegames
1997 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1998 return true;
1999
2000 case SPECSTR_ANDCO_NAME: // used for Foobar & Co company names
2001 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
2002 return true;
2003
2004 case SPECSTR_PRESIDENT_NAME: // President name
2005 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
2006 return true;
2007 }
2008
2009 /* TownName Transport company names, with the appropriate town name. */
2010 if (IsInsideMM(string, SPECSTR_COMPANY_NAME_START, SPECSTR_COMPANY_NAME_END)) {
2011 GenerateTownNameString(builder, string - SPECSTR_COMPANY_NAME_START, args.GetNextParameter<uint32_t>());
2012 builder += " Transport";
2013 return true;
2014 }
2015
2016 return false;
2017}
2018
2024{
2025 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
2026 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
2027 this->plural_form < LANGUAGE_MAX_PLURAL &&
2028 this->text_dir <= 1 &&
2029 this->newgrflangid < MAX_LANG &&
2030 this->num_genders < MAX_NUM_GENDERS &&
2031 this->num_cases < MAX_NUM_CASES &&
2032 StrValid(this->name) &&
2033 StrValid(this->own_name) &&
2034 StrValid(this->isocode) &&
2038}
2039
2044{
2045 /* "Less than 25% missing" is "sufficiently finished". */
2046 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
2047}
2048
2055{
2056 /* Current language pack */
2057 size_t total_len = 0;
2058 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file.native()), total_len, 1U << 20).release()));
2059 if (!lang_pack) return false;
2060
2061 /* End of read data (+ terminating zero added in ReadFileToMem()) */
2062 const char *end = (char *)lang_pack.get() + total_len + 1;
2063
2064 /* We need at least one byte of lang_pack->data */
2065 if (end <= lang_pack->data || !lang_pack->IsValid()) {
2066 return false;
2067 }
2068
2069 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
2070
2071 uint count = 0;
2072 for (uint i = 0; i < TEXT_TAB_END; i++) {
2073 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
2074 if (num > TAB_SIZE) return false;
2075
2076 tab_start[i] = count;
2077 tab_num[i] = num;
2078 count += num;
2079 }
2080
2081 /* Allocate offsets */
2082 std::vector<std::string_view> strings;
2083
2084 /* Fill offsets */
2085 char *s = lang_pack->data;
2086 for (uint i = 0; i < count; i++) {
2087 size_t len = static_cast<uint8_t>(*s++);
2088 if (s + len >= end) return false;
2089
2090 if (len >= 0xC0) {
2091 len = ((len & 0x3F) << 8) + static_cast<uint8_t>(*s++);
2092 if (s + len >= end) return false;
2093 }
2094 strings.emplace_back(s, len);
2095 s += len;
2096 }
2097 assert(strings.size() == count);
2098
2099 _langpack.langpack = std::move(lang_pack);
2100 _langpack.strings = std::move(strings);
2101 _langpack.langtab_num = tab_num;
2102 _langpack.langtab_start = tab_start;
2103
2104 _current_language = lang;
2106 _config_language_file = FS2OTTD(_current_language->file.filename().native());
2108 _langpack.list_separator = GetString(STR_LIST_SEPARATOR);
2109 _langpack.ellipsis = GetString(STR_TRUNCATION_ELLIPSIS);
2110
2111#ifdef _WIN32
2112 extern void Win32SetCurrentLocaleName(std::string iso_code);
2113 Win32SetCurrentLocaleName(_current_language->isocode);
2114#endif
2115
2116#ifdef WITH_COCOA
2117 extern void MacOSSetCurrentLocaleName(std::string_view iso_code);
2119#endif
2120
2121#ifdef WITH_ICU_I18N
2122 /* Create a collator instance for our current locale. */
2123 UErrorCode status = U_ZERO_ERROR;
2124 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
2125 /* Sort number substrings by their numerical value. */
2126 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
2127 /* Avoid using the collator if it is not correctly set. */
2128 if (U_FAILURE(status)) {
2129 _current_collator.reset();
2130 }
2131#endif /* WITH_ICU_I18N */
2132
2134
2135 /* Some lists need to be sorted again after a language change. */
2141 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
2142 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
2143 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
2144 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
2145 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
2146 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
2147 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
2148
2149 return true;
2150}
2151
2152/* Win32 implementation in win32.cpp.
2153 * OS X implementation in os/macosx/macos.mm. */
2154#if !(defined(_WIN32) || defined(__APPLE__))
2163std::optional<std::string> GetCurrentLocale(const char *param)
2164{
2165 auto env = GetEnv("LANGUAGE");
2166 if (env.has_value()) return std::string{*env};
2167
2168 env = GetEnv("LC_ALL");
2169 if (env.has_value()) return std::string{*env};
2170
2171 if (param != nullptr) {
2172 env = GetEnv(param);
2173 if (env.has_value()) return std::string{*env};
2174 }
2175
2176 env = GetEnv("LANG");
2177 if (env.has_value()) return std::string{*env};
2178
2179 return std::nullopt;
2180}
2181#else
2182std::optional<std::string> GetCurrentLocale(const char *param);
2183#endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2184
2190const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2191{
2192 for (const LanguageMetadata &lang : _languages) {
2193 if (newgrflangid == lang.newgrflangid) return &lang;
2194 }
2195
2196 return nullptr;
2197}
2198
2205static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2206{
2207 auto f = FileHandle::Open(file, "rb");
2208 if (!f.has_value()) return false;
2209
2210 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2211
2212 bool ret = read == 1 && hdr->IsValid();
2213
2214 /* Convert endianness for the windows language ID */
2215 if (ret) {
2216 hdr->missing = FROM_LE16(hdr->missing);
2217 hdr->winlangid = FROM_LE16(hdr->winlangid);
2218 }
2219 return ret;
2220}
2221
2226static void FillLanguageList(const std::string &path)
2227{
2228 std::error_code error_code;
2229 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2230 if (!dir_entry.is_regular_file()) continue;
2231 if (dir_entry.path().extension() != ".lng") continue;
2232
2233 LanguageMetadata lmd;
2234 lmd.file = dir_entry.path();
2235
2236 /* Check whether the file is of the correct version */
2237 std::string file = FS2OTTD(lmd.file.native());
2238 if (!GetLanguageFileHeader(file, &lmd)) {
2239 Debug(misc, 3, "{} is not a valid language file", file);
2240 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2241 Debug(misc, 3, "{}'s language ID is already known", file);
2242 } else {
2243 _languages.push_back(std::move(lmd));
2244 }
2245 }
2246 if (error_code) {
2247 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2248 }
2249}
2250
2256{
2257 for (Searchpath sp : _valid_searchpaths) {
2258 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2259 }
2260 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2261
2262 /* Acquire the locale of the current system */
2263 auto str_lang = GetCurrentLocale("LC_MESSAGES");
2264 std::string_view lang = str_lang.has_value() ? std::string_view{*str_lang} : "en_GB";
2265
2266 const LanguageMetadata *chosen_language = nullptr;
2267 const LanguageMetadata *language_fallback = nullptr;
2268 const LanguageMetadata *en_GB_fallback = _languages.data();
2269
2270 /* Find a proper language. */
2271 for (const LanguageMetadata &lng : _languages) {
2272 /* We are trying to find a default language. The priority is by
2273 * configuration file, local environment and last, if nothing found,
2274 * English. */
2275 if (_config_language_file == FS2OTTD(lng.file.filename().native())) {
2276 chosen_language = &lng;
2277 break;
2278 }
2279
2280 std::string_view iso_code = lng.isocode;
2281 if (iso_code == "en_GB") en_GB_fallback = &lng;
2282
2283 /* Only auto-pick finished translations */
2284 if (!lng.IsReasonablyFinished()) continue;
2285
2286 if (iso_code.starts_with(lang.substr(0, 5))) chosen_language = &lng;
2287 if (iso_code.starts_with(lang.substr(0, 2))) language_fallback = &lng;
2288 }
2289
2290 /* We haven't found the language in the config nor the one in the locale.
2291 * Now we set it to one of the fallback languages */
2292 if (chosen_language == nullptr) {
2293 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2294 }
2295
2296 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file.native()));
2297}
2298
2304{
2305 return _langpack.langpack->isocode;
2306}
2307
2313{
2315
2316 this->Reset();
2317 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2318 FontSize size = this->DefaultSize();
2319 FontCache *fc = FontCache::Get(size);
2320 for (char32_t c : Utf8View(*text)) {
2321 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2322 size = (FontSize)(c - SCC_FIRST_FONT);
2323 fc = FontCache::Get(size);
2324 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2325 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2326 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", static_cast<uint32_t>(c), FontSizeToName(size));
2327 return true;
2328 }
2329 }
2330 }
2331 return false;
2332}
2333
2336 uint i;
2337 uint j;
2338
2339 void Reset() override
2340 {
2341 this->i = 0;
2342 this->j = 0;
2343 }
2344
2346 {
2347 return FS_NORMAL;
2348 }
2349
2350 std::optional<std::string_view> NextString() override
2351 {
2352 if (this->i >= TEXT_TAB_END) return std::nullopt;
2353
2354 std::string_view ret = _langpack.strings[_langpack.langtab_start[this->i] + this->j];
2355
2356 this->j++;
2357 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2358 this->i++;
2359 this->j = 0;
2360 }
2361
2362 return ret;
2363 }
2364
2365 bool Monospace() override
2366 {
2367 return false;
2368 }
2369
2370 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override
2371 {
2372#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2373 settings->small.font = font_name;
2374 settings->medium.font = font_name;
2375 settings->large.font = font_name;
2376
2377 settings->small.os_handle = os_data;
2378 settings->medium.os_handle = os_data;
2379 settings->large.os_handle = os_data;
2380#endif
2381 }
2382};
2383
2397{
2398 static LanguagePackGlyphSearcher pack_searcher;
2399 if (searcher == nullptr) searcher = &pack_searcher;
2400 bool bad_font = searcher->FindMissingGlyphs();
2401#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2402 if (bad_font) {
2403 /* We found an unprintable character... lets try whether we can find
2404 * a fallback font that can print the characters in the current language. */
2405 bool any_font_configured = !_fcsettings.medium.font.empty();
2406 FontCacheSettings backup = _fcsettings;
2407
2408 _fcsettings.mono.os_handle = nullptr;
2409 _fcsettings.medium.os_handle = nullptr;
2410
2411 bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
2412
2413 _fcsettings = std::move(backup);
2414
2415 if (!bad_font && any_font_configured) {
2416 /* If the user configured a bad font, and we found a better one,
2417 * show that we loaded the better font instead of the configured one.
2418 */
2419 std::string err_str;
2420 StringBuilder builder(err_str);
2421 builder.PutUtf8(SCC_YELLOW);
2422 builder.Put("The current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2423 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
2424 }
2425
2426 if (bad_font) {
2427 /* Our fallback font does miss characters too, so keep the
2428 * user chosen font as that is more likely to be any good than
2429 * the wild guess we made */
2430 FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
2431 }
2432 }
2433#endif
2434
2435 if (bad_font) {
2436 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2437 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2438 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2439 */
2440 std::string err_str;
2441 StringBuilder builder(err_str);
2442 builder.PutUtf8(SCC_YELLOW);
2443 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.");
2444 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
2445
2446 /* Reset the font width */
2447 LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
2448 return;
2449 }
2450
2451 /* Update the font with cache */
2452 LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
2453
2454#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2455 /*
2456 * For right-to-left languages we need the ICU library. If
2457 * we do not have support for that library we warn the user
2458 * about it with a message. As we do not want the string to
2459 * be translated by the translators, we 'force' it into the
2460 * binary and 'load' it via a BindCString. To do this
2461 * properly we have to set the colour of the string,
2462 * otherwise we end up with a lot of artifacts.
2463 */
2464 if (_current_text_dir != TD_LTR) {
2465 std::string err_str;
2466 StringBuilder builder(err_str);
2467 builder.PutUtf8(SCC_YELLOW);
2468 builder.Put("This version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2469 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_ERROR);
2470 }
2471#endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */
2472}
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.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:21
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:104
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
void InitializeSortedCargoSpecs()
Initialize the list of sorted cargo specifications.
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.
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
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1173
Font cache for basic fonts.
Definition fontcache.h:22
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:130
static bool FindFallbackFont(FontCacheSettings *settings, 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:55
Helper for searching through the language pack.
Definition strings.cpp:2335
bool Monospace() override
Whether to search for a monospace font or not.
Definition strings.cpp:2365
void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override
Set the right font names.
Definition strings.cpp:2370
uint j
Iterator for the secondary language tables.
Definition strings.cpp:2337
std::optional< std::string_view > NextString() override
Get the next string to search through.
Definition strings.cpp:2350
FontSize DefaultSize() override
Get the default (font) size of the string.
Definition strings.cpp:2345
uint i
Iterator for the primary language tables.
Definition strings.cpp:2336
void Reset() override
Reset the search, i.e.
Definition strings.cpp:2339
static void Initialize()
Perform initialization of layout engine.
A searcher for missing glyphs.
virtual void Reset()=0
Reset the search, i.e.
virtual FontSize DefaultSize()=0
Get the default (font) size of the string.
bool FindMissingGlyphs()
Check whether there are glyphs missing in the current language.
Definition strings.cpp:2312
virtual bool Monospace()=0
Whether to search for a monospace font or not.
virtual std::optional< std::string_view > NextString()=0
Get the next string to search through.
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.
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.
std::pair< size_t, char32_t > DecodeUtf8(std::string_view buf)
Decode a character from UTF-8.
Definition utf8.cpp:46
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
Definition currency.h:119
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
Functions related to errors.
@ WL_WARNING
Other information.
Definition error.h:25
@ WL_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:1028
Functions for standard in/out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
@ LANG_DIR
Subdirectory for all translation files.
Definition fileio_type.h:98
fluid_settings_t * settings
FluidSynth settings handle.
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:1307
void LoadStringWidthTable(FontSizes fontsizes)
Initialize _stringwidth_table cache for the specified font sizes.
Definition gfx.cpp:1261
Functions related to laying out the texts.
FontSize
Available font sizes.
Definition gfx_type.h:248
@ FS_MONO
Index of the monospaced font in the font tables.
Definition gfx_type.h:252
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
constexpr FontSizes FONTSIZES_REQUIRED
Mask of font sizes required to be present.
Definition gfx_type.h:264
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.
void SetCurrentGrfLangID(uint8_t language_id)
Equivalence Setter function between game and newgrf langID.
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.
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.
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:854
bool StrValid(std::span< const char > str)
Checks whether the given string is valid, i.e.
Definition string.cpp:205
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:2163
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:915
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:969
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:951
static int DeterminePluralForm(int64_t count, uint plural_form)
Determine the "plural" index given a plural form and a number.
Definition strings.cpp:656
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:1005
static const Units _units_power[]
Unit conversions for power.
Definition strings.cpp:874
static std::string_view GetSurname(uint32_t seed)
Get the surname of the president with the given seed.
Definition strings.cpp:1966
void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
Check whether the currently loaded language pack uses characters that the currently loaded font does ...
Definition strings.cpp:2396
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:2303
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:901
static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
Decodes an encoded string during FormatString.
Definition strings.cpp:1016
static const UnitsLong _units_weight[]
Unit conversions for weight.
Definition strings.cpp:894
static const Units _units_velocity_calendar[]
Unit conversions for velocity.
Definition strings.cpp:856
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:2226
uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given km/h-ish speed to the display speed.
Definition strings.cpp:994
static const Units _units_force[]
Unit conversions for force.
Definition strings.cpp:908
void InitializeLanguagePacks()
Make a list of the available language packs.
Definition strings.cpp:2255
const LanguageMetadata * GetLanguage(uint8_t newgrflangid)
Get the language with the given NewGRF language ID.
Definition strings.cpp:2190
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:1085
static const Units _units_time_days_or_seconds[]
Unit conversions for time in calendar days or wallclock seconds.
Definition strings.cpp:923
uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
Convert the given display speed to the (internal) speed.
Definition strings.cpp:983
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:865
bool ReadLanguagePack(const LanguageMetadata *lang)
Read a particular language.
Definition strings.cpp:2054
static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
Reads the language file header and checks compatibility.
Definition strings.cpp:2205
static const Units _units_time_months_or_minutes[]
Unit conversions for time in calendar months or wallclock minutes.
Definition strings.cpp:929
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:1110
static const Units _units_time_years_or_minutes[]
Unit conversions for time in calendar years or wallclock minutes.
Definition strings.cpp:941
static const Units _units_power_to_weight[]
Unit conversions for power to weight.
Definition strings.cpp:881
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:935
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:991
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:93
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
static size_t GetArraySize()
Total number of cargospecs, both valid and invalid.
Definition cargotype.h:127
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.
Definition currency.h:77
std::string separator
The thousands separator for this currency.
Definition currency.h:79
std::string prefix
Prefix to apply when formatting money in this currency.
Definition currency.h:81
std::string suffix
Suffix to apply when formatting money in this currency.
Definition currency.h:82
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:78
uint8_t symbol_pos
The currency symbol is represented by two possible values, prefix and suffix Usage of one or the othe...
Definition currency.h:93
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:180
FontCacheSubSetting mono
The mono space font used for license/readme viewers.
Definition fontcache.h:184
const void * os_handle
Optional native OS font info. Only valid during font search.
Definition fontcache.h:176
Dynamic data of a loaded NewGRF.
Definition newgrf.h:117
Group data.
Definition group.h:74
std::string name
Group Name.
Definition group.h:75
uint16_t number
Per-company group number.
Definition group.h:87
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:62
IndustryType type
type of industry.
Definition industry.h:115
Town * town
Nearest town.
Definition industry.h:107
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:2023
bool IsReasonablyFinished() const
Check whether a translation is sufficiently finished to offer it to the public.
Definition strings.cpp:2043
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
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:63
std::string name
Custom town name. If empty, the town was not renamed and uses the generated name.
Definition town.h:72
Helper for unit conversion.
Definition strings.cpp:809
double factor
Amount to multiply or divide upon conversion.
Definition strings.cpp:810
int64_t ToDisplay(int64_t input, bool round=true) const
Convert value from OpenTTD's internal unit into the displayed value.
Definition strings.cpp:818
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:832
Information about a specific unit system with a long variant.
Definition strings.cpp:848
StringID s
String for the short variant of the unit.
Definition strings.cpp:850
StringID l
String for the long variant of the unit.
Definition strings.cpp:851
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:852
UnitConversion c
Conversion.
Definition strings.cpp:849
Information about a specific unit system.
Definition strings.cpp:841
StringID s
String for the unit.
Definition strings.cpp:843
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:844
UnitConversion c
Conversion.
Definition strings.cpp:842
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.
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.
@ VEH_ROAD
Road vehicle type.
@ VEH_AIRCRAFT
Aircraft vehicle type.
@ VEH_SHIP
Ship vehicle type.
@ VEH_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:356
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:340
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:3328
Window functions not directly related to making/drawing windows.
@ WC_STATION_LIST
Station list; Window numbers:
@ WC_ROADVEH_LIST
Road vehicle list; Window numbers:
@ WC_INDUSTRY_DIRECTORY
Industry directory; Window numbers:
@ WC_SHIPS_LIST
Ships list; Window numbers:
@ WC_TRAINS_LIST
Trains list; Window numbers:
@ WC_BUILD_VEHICLE
Build vehicle; Window numbers:
@ WC_AIRCRAFT_LIST
Aircraft list; Window numbers: