OpenTTD Source 20250529-master-g10c159a79f
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 <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "currency.h"
12#include "station_base.h"
13#include "town.h"
14#include "waypoint_base.h"
15#include "depot_base.h"
16#include "industry.h"
17#include "newgrf_text.h"
18#include "fileio_func.h"
19#include "signs_base.h"
20#include "fontdetection.h"
21#include "error.h"
22#include "error_func.h"
23#include "strings_func.h"
24#include "rev.h"
25#include "core/endian_func.hpp"
27#include "vehicle_base.h"
28#include "engine_base.h"
29#include "language.h"
30#include "townname_func.h"
31#include "string_func.h"
32#include "company_base.h"
33#include "smallmap_gui.h"
34#include "window_func.h"
35#include "debug.h"
36#include "game/game_text.hpp"
38#include "newgrf_engine.h"
39#include "core/backup_type.hpp"
40#include "gfx_layout.h"
41#include "core/utf8.hpp"
43#include <stack>
44
45#include "table/strings.h"
46#include "table/control_codes.h"
47#include "3rdparty/fmt/std.h"
48
49#include "strings_internal.h"
50
51#include "safeguards.h"
52
56
58
59#ifdef WITH_ICU_I18N
60std::unique_ptr<icu::Collator> _current_collator;
61#endif /* WITH_ICU_I18N */
62
70{
71 assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END));
72 if (this->offset >= this->parameters.size()) {
73 throw std::out_of_range("Trying to read invalid string parameter");
74 }
75
76 auto &param = this->parameters[this->offset++];
77 if (param.type != 0 && param.type != this->next_type) {
78 this->next_type = 0;
79 throw std::out_of_range("Trying to read string parameter with wrong type");
80 }
81 param.type = this->next_type;
82 this->next_type = 0;
83 return param;
84}
85
95
103EncodedString GetEncodedStringWithArgs(StringID str, std::span<const StringParameter> params)
104{
105 std::string result;
106 StringBuilder builder(result);
108 builder.PutIntegerBase(str, 16);
109
110 struct visitor {
111 StringBuilder &builder;
112
113 void operator()(const std::monostate &) {}
114
115 void operator()(const uint64_t &arg)
117 this->builder.PutUtf8(SCC_ENCODED_NUMERIC);
118 this->builder.PutIntegerBase(arg, 16);
119 }
120
121 void operator()(const std::string &value)
122 {
123#ifdef WITH_ASSERT
124 /* Don't allow an encoded string to contain another encoded string. */
125 {
126 auto [len, c] = DecodeUtf8(value);
127 assert(len == 0 || (c != SCC_ENCODED && c != SCC_ENCODED_INTERNAL && c != SCC_RECORD_SEPARATOR));
128 }
129#endif /* WITH_ASSERT */
130 this->builder.PutUtf8(SCC_ENCODED_STRING);
131 this->builder += value;
132 }
133 };
134
135 visitor v{builder};
136 for (const auto &param : params) {
137 builder.PutUtf8(SCC_RECORD_SEPARATOR);
138 std::visit(v, param.data);
139 }
140
141 return EncodedString{std::move(result)};
142}
143
152{
153 if (this->empty()) return {};
154
155 std::vector<StringParameter> params;
156 StringConsumer consumer(this->string);
157
158 if (!consumer.ReadUtf8If(SCC_ENCODED_INTERNAL)) return {};
159
160 StringID str;
161 if (auto r = consumer.TryReadIntegerBase<uint32_t>(16); r.has_value()) {
162 str = *r;
163 } else {
164 return {};
165 }
166 if (consumer.AnyBytesLeft() && !consumer.ReadUtf8If(SCC_RECORD_SEPARATOR)) return {};
167
168 while (consumer.AnyBytesLeft()) {
169 StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::SKIP_ONE_SEPARATOR));
170
171 if (!record.AnyBytesLeft()) {
172 /* This is an empty parameter. */
173 params.emplace_back(std::monostate{});
174 continue;
175 }
176
177 /* Get the parameter type. */
178 char32_t parameter_type = record.ReadUtf8();
179 switch (parameter_type) {
180 case SCC_ENCODED_NUMERIC: {
181 uint64_t value = record.ReadIntegerBase<uint64_t>(16);
182 assert(!record.AnyBytesLeft());
183 params.emplace_back(value);
184 break;
185 }
186
187 case SCC_ENCODED_STRING: {
188 params.emplace_back(record.Read(StringConsumer::npos));
189 break;
190 }
191
192 default:
193 /* Unknown parameter, make it blank. */
194 params.emplace_back(std::monostate{});
195 break;
196 }
197 }
198
199 if (param >= std::size(params)) return {};
200 params[param] = data;
201 return GetEncodedStringWithArgs(str, params);
202}
203
209{
210 return GetString(STR_JUST_RAW_STRING, this->string);
211}
212
219uint64_t GetParamMaxDigits(uint count, FontSize size)
220{
221 auto [front, next] = GetBroadestDigit(size);
222 uint64_t val = count > 1 ? front : next;
223 for (; count > 1; count--) {
224 val = 10 * val + next;
225 }
226 return val;
227}
228
237uint64_t GetParamMaxValue(uint64_t max_value, uint min_count, FontSize size)
238{
239 uint num_digits = 1;
240 while (max_value >= 10) {
241 num_digits++;
242 max_value /= 10;
243 }
244 return GetParamMaxDigits(std::max(min_count, num_digits), size);
245}
246
247static void StationGetSpecialString(StringBuilder &builder, StationFacilities x);
248static bool GetSpecialNameString(StringBuilder &builder, StringID string, StringParameters &args);
249
250static void FormatString(StringBuilder &builder, std::string_view str, StringParameters &args, uint case_index = 0, bool game_script = false, bool dry_run = false);
251
262static void FormatString(StringBuilder &builder, std::string_view str, std::span<StringParameter> params, uint case_index = 0, bool game_script = false, bool dry_run = false)
263{
264 StringParameters tmp_params{params};
265 FormatString(builder, str, tmp_params, case_index, game_script, dry_run);
266}
267
269 char data[]; // list of strings
270};
271
273 void operator()(LanguagePack *langpack)
274 {
275 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
276 delete[] reinterpret_cast<char *>(langpack);
277 }
278};
279
281 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
282
283 std::vector<std::string_view> strings;
284
285 std::array<uint, TEXT_TAB_END> langtab_num;
286 std::array<uint, TEXT_TAB_END> langtab_start;
287
288 std::string list_separator;
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
304std::string_view GetStringPtr(StringID string)
305{
306 switch (GetStringTab(string)) {
308 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
309 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
311 default: {
312 const size_t offset = _langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string).base();
313 if (offset < _langpack.strings.size()) return _langpack.strings[offset];
314 return "(undefined string)";
315 }
316 }
317}
318
327void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
328{
329 if (string == 0) {
330 GetStringWithArgs(builder, STR_UNDEFINED, args);
331 return;
332 }
333
334 StringIndexInTab index = GetStringIndex(string);
335 StringTab tab = GetStringTab(string);
336
337 switch (tab) {
338 case TEXT_TAB_TOWN:
339 if (IsInsideMM(string, SPECSTR_TOWNNAME_START, SPECSTR_TOWNNAME_END) && !game_script) {
340 try {
341 GenerateTownNameString(builder, string - SPECSTR_TOWNNAME_START, args.GetNextParameter<uint32_t>());
342 } catch (const std::runtime_error &e) {
343 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
344 builder += "(invalid string parameter)";
345 }
346 return;
347 }
348 break;
349
350 case TEXT_TAB_SPECIAL:
351 if (!game_script) {
352 try {
353 if (GetSpecialNameString(builder, string, args)) return;
354 } catch (const std::runtime_error &e) {
355 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
356 builder += "(invalid string parameter)";
357 return;
358 }
359 }
360 break;
361
362 case TEXT_TAB_OLD_CUSTOM:
363 /* Old table for custom names. This is no longer used */
364 if (!game_script) {
365 FatalError("Incorrect conversion of custom name string.");
366 }
367 break;
368
370 FormatString(builder, GetGameStringPtr(index), args, case_index, true);
371 return;
372 }
373
374 case TEXT_TAB_OLD_NEWGRF:
375 NOT_REACHED();
376
378 FormatString(builder, GetGRFStringPtr(index), args, case_index);
379 return;
380 }
381
382 default:
383 break;
384 }
385
386 if (index >= _langpack.langtab_num[tab]) {
387 if (game_script) {
388 return GetStringWithArgs(builder, STR_UNDEFINED, args);
389 }
390 FatalError("String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n", string);
391 }
392
393 FormatString(builder, GetStringPtr(string), args, case_index);
394}
395
404void GetStringWithArgs(StringBuilder &builder, StringID string, std::span<StringParameter> params, uint case_index, bool game_script)
405{
406 StringParameters tmp_params{params};
407 GetStringWithArgs(builder, string, tmp_params, case_index, game_script);
408}
409
415std::string GetString(StringID string)
416{
417 return GetStringWithArgs(string, {});
418}
419
425void AppendStringInPlace(std::string &result, StringID string)
426{
427 StringBuilder builder(result);
428 GetStringWithArgs(builder, string, {});
429}
430
431void AppendStringWithArgsInPlace(std::string &result, StringID string, std::span<StringParameter> params)
432{
433 StringParameters tmp_params{params};
434 StringBuilder builder(result);
435 GetStringWithArgs(builder, string, tmp_params);
436}
437
445{
446 std::string result;
447 StringBuilder builder(result);
448 GetStringWithArgs(builder, string, args);
449 return result;
450}
451
452std::string GetStringWithArgs(StringID string, std::span<StringParameter> args)
453{
454 std::string result;
455 StringBuilder builder(result);
456 GetStringWithArgs(builder, string, args);
457 return result;
458}
459
460static std::string_view GetDecimalSeparator()
461{
462 std::string_view decimal_separator = _settings_game.locale.digit_decimal_separator;
463 if (decimal_separator.empty()) decimal_separator = _langpack.langpack->digit_decimal_separator;
464 return decimal_separator;
465}
466
473static void FormatNumber(StringBuilder &builder, int64_t number, std::string_view separator)
474{
475 static const int max_digits = 20;
476 uint64_t divisor = 10000000000000000000ULL;
477 int thousands_offset = (max_digits - 1) % 3;
478
479 if (number < 0) {
480 builder.PutChar('-');
481 number = -number;
482 }
483
484 uint64_t num = number;
485 uint64_t tot = 0;
486 for (int i = 0; i < max_digits; i++) {
487 uint64_t quot = 0;
488 if (num >= divisor) {
489 quot = num / divisor;
490 num = num % divisor;
491 }
492 if ((tot |= quot) || i == max_digits - 1) {
493 builder.PutChar('0' + quot); // quot is a single digit
494 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
495 }
496
497 divisor /= 10;
498 }
499}
500
501static void FormatCommaNumber(StringBuilder &builder, int64_t number)
502{
503 std::string_view separator = _settings_game.locale.digit_group_separator;
504 if (separator.empty()) separator = _langpack.langpack->digit_group_separator;
505 FormatNumber(builder, number, separator);
506}
507
508static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
509{
510 fmt::format_to(builder.back_inserter(), "{}", number);
511}
512
513static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
514{
515 fmt::format_to(builder.back_inserter(), "{:0{}d}", number, count);
516}
517
518static void FormatHexNumber(StringBuilder &builder, uint64_t number)
519{
520 fmt::format_to(builder.back_inserter(), "0x{:X}", number);
521}
522
528static void FormatBytes(StringBuilder &builder, int64_t number)
529{
530 assert(number >= 0);
531
532 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
533 static const std::string_view iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
534 uint id = 1;
535 while (number >= 1024 * 1024) {
536 number /= 1024;
537 id++;
538 }
539
540 if (number < 1024) {
541 id = 0;
542 fmt::format_to(builder.back_inserter(), "{}", number);
543 } else if (number < 1024 * 10) {
544 fmt::format_to(builder.back_inserter(), "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
545 } else if (number < 1024 * 100) {
546 fmt::format_to(builder.back_inserter(), "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
547 } else {
548 assert(number < 1024 * 1024);
549 fmt::format_to(builder.back_inserter(), "{}", number / 1024);
550 }
551
552 assert(id < lengthof(iec_prefixes));
553 fmt::format_to(builder.back_inserter(), NBSP "{}B", iec_prefixes[id]);
554}
555
556static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
557{
558 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
559
560 auto tmp_params = MakeParameters(STR_DAY_NUMBER_1ST + ymd.day - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
561 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
562}
563
564static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
565{
566 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
567
568 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
569 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
570}
571
572static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
573{
574 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
575
576 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
577 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
578 FormatString(builder, GetStringPtr(str), tmp_params);
579}
580
581static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
582{
583 /* We are going to make number absolute for printing, so
584 * keep this piece of data as we need it later on */
585 bool negative = number < 0;
586
587 number *= spec->rate;
588
589 /* convert from negative */
590 if (number < 0) {
591 builder.PutUtf8(SCC_PUSH_COLOUR);
592 builder.PutUtf8(SCC_RED);
593 builder.PutChar('-');
594 number = -number;
595 }
596
597 /* Add prefix part, following symbol_pos specification.
598 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
599 * The only remaining value is 1 (suffix), so everything that is not 1 */
600 if (spec->symbol_pos != 1) builder += spec->prefix;
601
602 StringID number_str = STR_NULL;
603
604 /* For huge numbers, compact the number. */
605 if (compact) {
606 /* Take care of the thousand rounding. Having 1 000 000 k
607 * and 1 000 M is inconsistent, so always use 1 000 M. */
608 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
609 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
610 number_str = STR_CURRENCY_SHORT_TERA;
611 } else if (number >= Money(1'000'000'000'000) - 500'000) {
612 number = (number + 500'000'000) / 1'000'000'000;
613 number_str = STR_CURRENCY_SHORT_GIGA;
614 } else if (number >= 1'000'000'000 - 500) {
615 number = (number + 500'000) / 1'000'000;
616 number_str = STR_CURRENCY_SHORT_MEGA;
617 } else if (number >= 1'000'000) {
618 number = (number + 500) / 1'000;
619 number_str = STR_CURRENCY_SHORT_KILO;
620 }
621 }
622
623 std::string_view separator = _settings_game.locale.digit_group_separator_currency;
624 if (separator.empty()) separator = GetCurrency().separator;
625 if (separator.empty()) separator = _langpack.langpack->digit_group_separator_currency;
626 FormatNumber(builder, number, separator);
627 if (number_str != STR_NULL) {
628 FormatString(builder, GetStringPtr(number_str), {});
629 }
630
631 /* Add suffix part, following symbol_pos specification.
632 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
633 * The only remaining value is 1 (prefix), so everything that is not 0 */
634 if (spec->symbol_pos != 0) builder += spec->suffix;
635
636 if (negative) {
637 builder.PutUtf8(SCC_POP_COLOUR);
638 }
639}
640
647static int DeterminePluralForm(int64_t count, uint plural_form)
648{
649 /* The absolute value determines plurality */
650 uint64_t n = abs(count);
651
652 switch (plural_form) {
653 default:
654 NOT_REACHED();
655
656 /* Two forms: singular used for one only.
657 * Used in:
658 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
659 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
660 case 0:
661 return n != 1 ? 1 : 0;
662
663 /* Only one form.
664 * Used in:
665 * Hungarian, Japanese, Turkish */
666 case 1:
667 return 0;
668
669 /* Two forms: singular used for 0 and 1.
670 * Used in:
671 * French, Brazilian Portuguese */
672 case 2:
673 return n > 1 ? 1 : 0;
674
675 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
676 * Note: Cases are out of order for hysterical reasons. '0' is last.
677 * Used in:
678 * Latvian */
679 case 3:
680 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
681
682 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
683 * Used in:
684 * Gaelige (Irish) */
685 case 4:
686 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
687
688 /* 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.
689 * Used in:
690 * Lithuanian */
691 case 5:
692 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
693
694 /* 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.
695 * Used in:
696 * Croatian, Russian, Ukrainian */
697 case 6:
698 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
699
700 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
701 * Used in:
702 * Polish */
703 case 7:
704 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
705
706 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
707 * Used in:
708 * Slovenian */
709 case 8:
710 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
711
712 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
713 * Used in:
714 * Icelandic */
715 case 9:
716 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
717
718 /* Three forms: special cases for 1, and 2 to 4
719 * Used in:
720 * Czech, Slovak */
721 case 10:
722 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
723
724 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
725 * Korean doesn't have the concept of plural, but depending on how a
726 * number is pronounced it needs another version of a particle.
727 * As such the plural system is misused to give this distinction.
728 */
729 case 11:
730 switch (n % 10) {
731 case 0: // yeong
732 case 1: // il
733 case 3: // sam
734 case 6: // yuk
735 case 7: // chil
736 case 8: // pal
737 return 0;
738
739 case 2: // i
740 case 4: // sa
741 case 5: // o
742 case 9: // gu
743 return 1;
744
745 default:
746 NOT_REACHED();
747 }
748
749 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
750 * Used in:
751 * Maltese */
752 case 12:
753 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
754 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
755 * Used in:
756 * Scottish Gaelic */
757 case 13:
758 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
759
760 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
761 * Used in:
762 * Romanian */
763 case 14:
764 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
765 }
766}
767
768static void SkipStringChoice(StringConsumer &consumer)
769{
770 uint n = consumer.ReadUint8();
771 uint len = 0;
772 for (uint i = 0; i != n; i++) {
773 len += consumer.ReadUint8();
774 }
775 consumer.Skip(len);
776}
777
778static void ParseStringChoice(StringConsumer &consumer, uint form, StringBuilder &builder)
779{
780 /* <NUM> {Length of each string} {each string} */
781 uint n = consumer.ReadUint8();
782 size_t form_pre = 0, form_len = 0, form_post = 0;
783 for (uint i = 0; i != n; i++) {
784 uint len = consumer.ReadUint8();
785 if (i < form) {
786 form_pre += len;
787 } else if (i > form) {
788 form_post += len;
789 } else {
790 form_len = len;
791 }
792 }
793
794 consumer.Skip(form_pre);
795 builder += consumer.Read(form_len);
796 consumer.Skip(form_post);
797}
798
801 double factor;
802
809 int64_t ToDisplay(int64_t input, bool round = true) const
810 {
811 return round
812 ? (int64_t)std::round(input * this->factor)
813 : (int64_t)(input * this->factor);
814 }
815
823 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
824 {
825 return round
826 ? (int64_t)std::round(input / this->factor / divider)
827 : (int64_t)(input / this->factor / divider);
828 }
829};
830
837
845
848 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
849 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
850 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
851 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
852 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
853};
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.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
861 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
862};
863
865static const Units _units_power[] = {
866 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
867 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
868 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
869};
870
873 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
874 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
875 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
876 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
877 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
878 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
879 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
880 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
881 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
882};
883
885static const UnitsLong _units_weight[] = {
886 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
887 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
888 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
889};
890
892static const UnitsLong _units_volume[] = {
893 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
894 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
895 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
896};
897
899static const Units _units_force[] = {
900 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
901 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
902 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
903};
904
906static const Units _units_height[] = {
907 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
908 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
909 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
910};
911
914 { { 1 }, STR_UNITS_DAYS, 0 },
915 { { 2 }, STR_UNITS_SECONDS, 0 },
916};
917
920 { { 1 }, STR_UNITS_MONTHS, 0 },
921 { { 1 }, STR_UNITS_MINUTES, 0 },
922};
923
926 { { 1 }, STR_UNITS_YEARS, 0 },
927 { { 1 }, STR_UNITS_PERIODS, 0 },
928};
929
932 { { 1 }, STR_UNITS_YEARS, 0 },
933 { { 12 }, STR_UNITS_MINUTES, 0 },
934};
935
942{
944
945 assert(setting < lengthof(_units_velocity_calendar));
946 assert(setting < lengthof(_units_velocity_realtime));
947
949
950 return _units_velocity_calendar[setting];
951}
952
959{
960 /* For historical reasons we don't want to mess with the
961 * conversion for speed. So, don't round it and keep the
962 * original conversion factors instead of the real ones. */
963 return GetVelocityUnits(type).c.ToDisplay(speed, false);
964}
965
972{
973 return GetVelocityUnits(type).c.FromDisplay(speed);
974}
975
982{
983 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
984}
985
992{
993 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
994}
995
1002static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
1003{
1004 std::vector<StringParameter> sub_args;
1005
1006 StringIndexInTab id(consumer.ReadIntegerBase<uint32_t>(16));
1007 if (consumer.AnyBytesLeft() && !consumer.PeekUtf8If(SCC_RECORD_SEPARATOR)) {
1008 consumer.SkipAll();
1009 builder += "(invalid SCC_ENCODED)";
1010 return;
1011 }
1012 if (game_script && id >= TAB_SIZE_GAMESCRIPT) {
1013 consumer.SkipAll();
1014 builder += "(invalid StringID)";
1015 return;
1016 }
1017
1018 while (consumer.AnyBytesLeft()) {
1019 consumer.SkipUtf8If(SCC_RECORD_SEPARATOR);
1020 StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::KEEP_SEPARATOR));
1021
1022 if (!record.AnyBytesLeft()) {
1023 /* This is an empty parameter. */
1024 sub_args.emplace_back(std::monostate{});
1025 continue;
1026 }
1027
1028 /* Get the parameter type. */
1029 char32_t parameter_type = record.ReadUtf8();
1030 switch (parameter_type) {
1031 case SCC_ENCODED: {
1032 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1033 if (param >= TAB_SIZE_GAMESCRIPT) {
1034 builder += "(invalid sub-StringID)";
1035 return;
1036 }
1037 assert(!record.AnyBytesLeft());
1039 sub_args.emplace_back(param);
1040 break;
1041 }
1042
1043 case SCC_ENCODED_NUMERIC: {
1044 uint64_t param = record.ReadIntegerBase<uint64_t>(16);
1045 assert(!record.AnyBytesLeft());
1046 sub_args.emplace_back(param);
1047 break;
1048 }
1049
1050 case SCC_ENCODED_STRING: {
1051 sub_args.emplace_back(record.Read(StringConsumer::npos));
1052 break;
1053 }
1054
1055 default:
1056 /* Unknown parameter, make it blank. */
1057 sub_args.emplace_back(std::monostate{});
1058 break;
1059 }
1060 }
1061
1062 StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()};
1063 GetStringWithArgs(builder, stringid, sub_args, true);
1064}
1065
1073static void FormatString(StringBuilder &builder, std::string_view str_arg, StringParameters &args, uint orig_case_index, bool game_script, bool dry_run)
1074{
1075 size_t orig_first_param_offset = args.GetOffset();
1076
1077 if (!dry_run) {
1078 /*
1079 * This function is normally called with `dry_run` false, then we call this function again
1080 * with `dry_run` being true. The dry run is required for the gender formatting. For the
1081 * gender determination we need to format a sub string to get the gender, but for that we
1082 * need to know as what string control code type the specific parameter is encoded. Since
1083 * gendered words can be before the "parameter" words, this needs to be determined before
1084 * the actual formatting.
1085 */
1086 std::string buffer;
1087 StringBuilder dry_run_builder(buffer);
1088 FormatString(dry_run_builder, str_arg, args, orig_case_index, game_script, true);
1089 /* We have to restore the original offset here to to read the correct values. */
1090 args.SetOffset(orig_first_param_offset);
1091 }
1092 uint next_substr_case_index = 0;
1093 struct StrStackItem {
1094 StringConsumer consumer;
1095 size_t first_param_offset;
1096 uint case_index;
1097
1098 StrStackItem(std::string_view view, size_t first_param_offset, uint case_index)
1099 : consumer(view), first_param_offset(first_param_offset), case_index(case_index)
1100 {}
1101 };
1102 std::stack<StrStackItem, std::vector<StrStackItem>> str_stack;
1103 str_stack.emplace(str_arg, orig_first_param_offset, orig_case_index);
1104
1105 for (;;) {
1106 try {
1107 while (!str_stack.empty() && !str_stack.top().consumer.AnyBytesLeft()) {
1108 str_stack.pop();
1109 }
1110 if (str_stack.empty()) break;
1111 StringConsumer &consumer = str_stack.top().consumer;
1112 const size_t ref_param_offset = str_stack.top().first_param_offset;
1113 const uint case_index = str_stack.top().case_index;
1114 char32_t b = consumer.ReadUtf8();
1115 assert(b != 0);
1116 if (b == 0) {
1117 /* A NUL character should never be encountered, but for non-debug builds handle it gracefully. */
1118 builder += "(unexpected NUL)";
1119 continue;
1120 }
1121
1122 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
1123 /* We need to pass some stuff as it might be modified. */
1124 b = RemapNewGRFStringControlCode(b, consumer);
1125 if (b == 0) continue;
1126 }
1127
1128 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
1129 builder.PutUtf8(b);
1130 continue;
1131 }
1132
1133 args.SetTypeOfNextParameter(b);
1134 switch (b) {
1135 case SCC_ENCODED:
1137 DecodeEncodedString(consumer, b == SCC_ENCODED, builder);
1138 break;
1139
1140 case SCC_NEWGRF_STRINL: {
1141 StringID substr = consumer.ReadUtf8(STR_NULL);
1142 std::string_view ptr = GetStringPtr(substr);
1143 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1144 next_substr_case_index = 0;
1145 break;
1146 }
1147
1149 StringID substr = args.GetNextParameter<StringID>();
1150 std::string_view ptr = GetStringPtr(substr);
1151 str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer"
1152 next_substr_case_index = 0;
1153 break;
1154 }
1155
1156 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1157 /* First read the meta data from the language file. */
1158 size_t offset = ref_param_offset + consumer.ReadUint8();
1159 uint8_t gender = 0;
1160 if (offset >= args.GetNumParameters()) {
1161 /* The offset may come from an external NewGRF, and be invalid. */
1162 builder += "(invalid GENDER parameter)";
1163 } else if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1164 /* Now we need to figure out what text to resolve, i.e.
1165 * what do we need to draw? So get the actual raw string
1166 * first using the control code to get said string. */
1167 std::string input;
1168 {
1169 StringBuilder tmp_builder(input);
1170 tmp_builder.PutUtf8(args.GetTypeAtOffset(offset));
1171 }
1172
1173 std::string buffer;
1174 {
1175 AutoRestoreBackup sgd_backup(_scan_for_gender_data, true);
1176 StringBuilder tmp_builder(buffer);
1177 StringParameters tmp_params = args.GetRemainingParameters(offset);
1178 FormatString(tmp_builder, input, tmp_params);
1179 }
1180
1181 /* The gender is stored at the start of the formatted string.
1182 * Does this string have a gender, if so, set it. */
1183 StringConsumer gender_consumer(buffer);
1184 if (gender_consumer.ReadUtf8If(SCC_GENDER_INDEX)) {
1185 gender = gender_consumer.ReadUint8();
1186 }
1187 }
1188 ParseStringChoice(consumer, gender, builder);
1189 break;
1190 }
1191
1192 /* This sets up the gender for the string.
1193 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1194 case SCC_GENDER_INDEX: { // {GENDER 0}
1195 uint8_t gender = consumer.ReadUint8();
1197 builder.PutUtf8(SCC_GENDER_INDEX);
1198 builder.PutUint8(gender);
1199 }
1200 break;
1201 }
1202
1203 case SCC_PLURAL_LIST: { // {P}
1204 uint8_t plural_form = consumer.ReadUint8(); // contains the plural form for this string
1205 size_t offset = ref_param_offset + consumer.ReadUint8();
1206 const uint64_t *v = nullptr;
1207 /* The offset may come from an external NewGRF, and be invalid. */
1208 if (offset < args.GetNumParameters()) {
1209 v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1210 }
1211 if (v != nullptr) {
1212 ParseStringChoice(consumer, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1213 } else {
1214 SkipStringChoice(consumer);
1215 builder += "(invalid PLURAL parameter)";
1216 }
1217 break;
1218 }
1219
1220 case SCC_ARG_INDEX: { // Move argument pointer
1221 args.SetOffset(ref_param_offset + consumer.ReadUint8());
1222 break;
1223 }
1224
1225 case SCC_SET_CASE: { // {SET_CASE}
1226 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1227 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1228 next_substr_case_index = consumer.ReadUint8();
1229 break;
1230 }
1231
1232 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1233 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <LENDEFAULT> <STRINGDEFAULT>
1234 * Each LEN is printed using 2 bytes in little endian order. */
1235 uint num = consumer.ReadUint8();
1236 std::optional<std::string_view> found;
1237 for (; num > 0; --num) {
1238 uint8_t index = consumer.ReadUint8();
1239 uint16_t len = consumer.ReadUint16LE();
1240 auto case_str = consumer.Read(len);
1241 if (index == case_index) {
1242 /* Found the case */
1243 found = case_str;
1244 }
1245 }
1246 uint16_t default_len = consumer.ReadUint16LE();
1247 auto default_str = consumer.Read(default_len);
1248 if (!found.has_value()) found = default_str;
1249 str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "consumer"
1250 break;
1251 }
1252
1253 case SCC_REVISION: // {REV}
1254 builder += _openttd_revision;
1255 break;
1256
1257 case SCC_RAW_STRING_POINTER: // {RAW_STRING}
1258 FormatString(builder, args.GetNextParameterString(), args);
1259 break;
1260
1261 case SCC_STRING: {// {STRING}
1262 StringID string_id = args.GetNextParameter<StringID>();
1263 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1264 /* It's prohibited for the included string to consume any arguments. */
1265 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1266 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1267 next_substr_case_index = 0;
1268 break;
1269 }
1270
1271 case SCC_STRING1:
1272 case SCC_STRING2:
1273 case SCC_STRING3:
1274 case SCC_STRING4:
1275 case SCC_STRING5:
1276 case SCC_STRING6:
1277 case SCC_STRING7: { // {STRING1..7}
1278 /* Strings that consume arguments */
1279 StringID string_id = args.GetNextParameter<StringID>();
1280 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1281 uint size = b - SCC_STRING1 + 1;
1282 if (size > args.GetDataLeft()) {
1283 builder += "(consumed too many parameters)";
1284 } else {
1285 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1286 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1287 args.AdvanceOffset(size);
1288 }
1289 next_substr_case_index = 0;
1290 break;
1291 }
1292
1293 case SCC_COMMA: // {COMMA}
1294 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1295 break;
1296
1297 case SCC_DECIMAL: { // {DECIMAL}
1298 int64_t number = args.GetNextParameter<int64_t>();
1299 int digits = args.GetNextParameter<int>();
1300 if (digits == 0) {
1301 FormatCommaNumber(builder, number);
1302 break;
1303 }
1304
1305 int64_t divisor = PowerOfTen(digits);
1306 int64_t fractional = number % divisor;
1307 number /= divisor;
1308 FormatCommaNumber(builder, number);
1309 fmt::format_to(builder.back_inserter(), "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1310 break;
1311 }
1312
1313 case SCC_NUM: // {NUM}
1314 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1315 break;
1316
1317 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1318 int64_t num = args.GetNextParameter<int64_t>();
1319 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1320 break;
1321 }
1322
1323 case SCC_HEX: // {HEX}
1324 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1325 break;
1326
1327 case SCC_BYTES: // {BYTES}
1328 FormatBytes(builder, args.GetNextParameter<int64_t>());
1329 break;
1330
1331 case SCC_CARGO_TINY: { // {CARGO_TINY}
1332 /* Tiny description of cargotypes. Layout:
1333 * param 1: cargo type
1334 * param 2: cargo count */
1335 CargoType cargo = args.GetNextParameter<CargoType>();
1336 if (cargo >= CargoSpec::GetArraySize()) break;
1337
1338 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1339 int64_t amount = 0;
1340 switch (cargo_str) {
1341 case STR_TONS:
1343 break;
1344
1345 case STR_LITERS:
1347 break;
1348
1349 default: {
1350 amount = args.GetNextParameter<int64_t>();
1351 break;
1352 }
1353 }
1354
1355 FormatCommaNumber(builder, amount);
1356 break;
1357 }
1358
1359 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1360 /* Short description of cargotypes. Layout:
1361 * param 1: cargo type
1362 * param 2: cargo count */
1363 CargoType cargo = args.GetNextParameter<CargoType>();
1364 if (cargo >= CargoSpec::GetArraySize()) break;
1365
1366 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1367 switch (cargo_str) {
1368 case STR_TONS: {
1371 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1372 FormatString(builder, GetStringPtr(x.l), tmp_params);
1373 break;
1374 }
1375
1376 case STR_LITERS: {
1379 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1380 FormatString(builder, GetStringPtr(x.l), tmp_params);
1381 break;
1382 }
1383
1384 default: {
1385 auto tmp_params = MakeParameters(args.GetNextParameter<int64_t>());
1386 GetStringWithArgs(builder, cargo_str, tmp_params);
1387 break;
1388 }
1389 }
1390 break;
1391 }
1392
1393 case SCC_CARGO_LONG: { // {CARGO_LONG}
1394 /* First parameter is cargo type, second parameter is cargo count */
1395 CargoType cargo = args.GetNextParameter<CargoType>();
1396 if (IsValidCargoType(cargo) && cargo >= CargoSpec::GetArraySize()) break;
1397
1398 StringID cargo_str = !IsValidCargoType(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1399 auto tmp_args = MakeParameters(args.GetNextParameter<int64_t>());
1400 GetStringWithArgs(builder, cargo_str, tmp_args);
1401 break;
1402 }
1403
1404 case SCC_CARGO_LIST: { // {CARGO_LIST}
1405 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1406 bool first = true;
1407
1408 std::string_view list_separator = GetListSeparator();
1409 for (const auto &cs : _sorted_cargo_specs) {
1410 if (!HasBit(cmask, cs->Index())) continue;
1411
1412 if (first) {
1413 first = false;
1414 } else {
1415 /* Add a comma if this is not the first item */
1416 builder += list_separator;
1417 }
1418
1419 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1420 }
1421
1422 /* If first is still true then no cargo is accepted */
1423 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1424
1425 next_substr_case_index = 0;
1426 break;
1427 }
1428
1429 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1430 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1431 break;
1432
1433 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1434 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1435 break;
1436
1437 case SCC_DATE_TINY: // {DATE_TINY}
1438 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1439 break;
1440
1441 case SCC_DATE_SHORT: // {DATE_SHORT}
1442 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1443 next_substr_case_index = 0;
1444 break;
1445
1446 case SCC_DATE_LONG: // {DATE_LONG}
1447 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1448 next_substr_case_index = 0;
1449 break;
1450
1451 case SCC_DATE_ISO: // {DATE_ISO}
1452 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1453 break;
1454
1455 case SCC_FORCE: { // {FORCE}
1458 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1459 FormatString(builder, GetStringPtr(x.s), tmp_params);
1460 break;
1461 }
1462
1463 case SCC_HEIGHT: { // {HEIGHT}
1466 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1467 FormatString(builder, GetStringPtr(x.s), tmp_params);
1468 break;
1469 }
1470
1471 case SCC_POWER: { // {POWER}
1474 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1475 FormatString(builder, GetStringPtr(x.s), tmp_params);
1476 break;
1477 }
1478
1479 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1481 assert(setting < lengthof(_units_power_to_weight));
1482 const auto &x = _units_power_to_weight[setting];
1483 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1484 FormatString(builder, GetStringPtr(x.s), tmp_params);
1485 break;
1486 }
1487
1488 case SCC_VELOCITY: { // {VELOCITY}
1489 int64_t arg = args.GetNextParameter<int64_t>();
1490 /* Unpack vehicle type from packed argument to get desired units. */
1491 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1492 const auto &x = GetVelocityUnits(vt);
1493 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1494 FormatString(builder, GetStringPtr(x.s), tmp_params);
1495 break;
1496 }
1497
1498 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1501 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1502 FormatString(builder, GetStringPtr(x.s), tmp_params);
1503 break;
1504 }
1505
1506 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1509 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1510 FormatString(builder, GetStringPtr(x.l), tmp_params);
1511 break;
1512 }
1513
1514 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
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_WEIGHT_LONG: { // {WEIGHT_LONG}
1525 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1526 FormatString(builder, GetStringPtr(x.l), tmp_params);
1527 break;
1528 }
1529
1530 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1531 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1532 const auto &x = _units_time_days_or_seconds[realtime];
1533 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1534 FormatString(builder, GetStringPtr(x.s), tmp_params);
1535 break;
1536 }
1537
1538 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1539 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1540 const auto &x = _units_time_months_or_minutes[realtime];
1541 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1542 FormatString(builder, GetStringPtr(x.s), tmp_params);
1543 break;
1544 }
1545
1546 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1547 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1548 const auto &x = _units_time_years_or_periods[realtime];
1549 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1550 FormatString(builder, GetStringPtr(x.s), tmp_params);
1551 break;
1552 }
1553
1554 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1555 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1556 const auto &x = _units_time_years_or_minutes[realtime];
1557 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1558 FormatString(builder, GetStringPtr(x.s), tmp_params);
1559 break;
1560 }
1561
1562 case SCC_COMPANY_NAME: { // {COMPANY}
1564 if (c == nullptr) break;
1565
1566 if (!c->name.empty()) {
1567 auto tmp_params = MakeParameters(c->name);
1568 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1569 } else {
1570 auto tmp_params = MakeParameters(c->name_2);
1571 GetStringWithArgs(builder, c->name_1, tmp_params);
1572 }
1573 break;
1574 }
1575
1576 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1577 CompanyID company = args.GetNextParameter<CompanyID>();
1578
1579 /* Nothing is added for AI or inactive companies */
1580 if (Company::IsValidHumanID(company)) {
1581 auto tmp_params = MakeParameters(company + 1);
1582 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1583 }
1584 break;
1585 }
1586
1587 case SCC_DEPOT_NAME: { // {DEPOT}
1589 if (vt == VEH_AIRCRAFT) {
1590 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1591 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1592 break;
1593 }
1594
1595 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1596 if (!d->name.empty()) {
1597 auto tmp_params = MakeParameters(d->name);
1598 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1599 } else {
1600 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1601 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1602 }
1603 break;
1604 }
1605
1606 case SCC_ENGINE_NAME: { // {ENGINE}
1607 int64_t arg = args.GetNextParameter<int64_t>();
1608 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1609 if (e == nullptr) break;
1610
1611 if (!e->name.empty() && e->IsEnabled()) {
1612 auto tmp_params = MakeParameters(e->name);
1613 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1614 break;
1615 }
1616
1617 if (e->info.callback_mask.Test(VehicleCallbackMask::Name)) {
1618 std::array<int32_t, 16> regs100;
1619 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr, regs100);
1620 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1621 if (callback == 0x40F) {
1622 const GRFFile *grffile = e->GetGRF();
1623 assert(grffile != nullptr);
1624
1625 builder += GetGRFStringWithTextStack(grffile, static_cast<GRFStringID>(regs100[0]), std::span{regs100}.subspan(1));
1626 break;
1627 } else if (callback < 0x400) {
1628 const GRFFile *grffile = e->GetGRF();
1629 assert(grffile != nullptr);
1630
1631 builder += GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100);
1632 break;
1633 }
1634 }
1635
1636 GetStringWithArgs(builder, e->info.string_id, {});
1637 break;
1638 }
1639
1640 case SCC_GROUP_NAME: { // {GROUP}
1641 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1642 if (g == nullptr) break;
1643
1644 if (!g->name.empty()) {
1645 auto tmp_params = MakeParameters(g->name);
1646 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1647 } else {
1648 auto tmp_params = MakeParameters(g->number);
1649 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1650 }
1651 break;
1652 }
1653
1654 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1656 if (i == nullptr) break;
1657
1658 static bool use_cache = true;
1660 /* Gender is defined by the industry type.
1661 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1662 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), {}, next_substr_case_index);
1663 } else if (use_cache) { // Use cached version if first call
1664 AutoRestoreBackup cache_backup(use_cache, false);
1665 builder += i->GetCachedName();
1666 } else {
1667 /* First print the town name and the industry type name. */
1668 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1669 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1670 }
1671 next_substr_case_index = 0;
1672 break;
1673 }
1674
1675 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1677 if (c == nullptr) break;
1678
1679 if (!c->president_name.empty()) {
1680 auto tmp_params = MakeParameters(c->president_name);
1681 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1682 } else {
1683 auto tmp_params = MakeParameters(c->president_name_2);
1684 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1685 }
1686 break;
1687 }
1688
1689 case SCC_STATION_NAME: { // {STATION}
1690 StationID sid = args.GetNextParameter<StationID>();
1691 const Station *st = Station::GetIfValid(sid);
1692
1693 if (st == nullptr) {
1694 /* The station doesn't exist anymore. The only place where we might
1695 * be "drawing" an invalid station is in the case of cargo that is
1696 * in transit. */
1697 GetStringWithArgs(builder, STR_UNKNOWN_STATION, {});
1698 break;
1699 }
1700
1701 static bool use_cache = true;
1702 if (use_cache) { // Use cached version if first call
1703 AutoRestoreBackup cache_backup(use_cache, false);
1704 builder += st->GetCachedName();
1705 } else if (!st->name.empty()) {
1706 auto tmp_params = MakeParameters(st->name);
1707 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1708 } else {
1709 StringID string_id = st->string_id;
1710 if (st->indtype != IT_INVALID) {
1711 /* Special case where the industry provides the name for the station */
1712 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1713
1714 /* Industry GRFs can change which might remove the station name and
1715 * thus cause very strange things. Here we check for that before we
1716 * actually set the station name. */
1717 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1718 string_id = indsp->station_name;
1719 }
1720 }
1721
1722 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1723 GetStringWithArgs(builder, string_id, tmp_params);
1724 }
1725 break;
1726 }
1727
1728 case SCC_TOWN_NAME: { // {TOWN}
1729 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1730 if (t == nullptr) break;
1731
1732 static bool use_cache = true;
1733 if (use_cache) { // Use cached version if first call
1734 AutoRestoreBackup cache_backup(use_cache, false);
1735 builder += t->GetCachedName();
1736 } else if (!t->name.empty()) {
1737 auto tmp_params = MakeParameters(t->name);
1738 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1739 } else {
1740 GetTownName(builder, t);
1741 }
1742 break;
1743 }
1744
1745 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1747 if (wp == nullptr) break;
1748
1749 if (!wp->name.empty()) {
1750 auto tmp_params = MakeParameters(wp->name);
1751 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1752 } else {
1753 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1754 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1755 if (wp->town_cn != 0) string_id++;
1756 GetStringWithArgs(builder, string_id, tmp_params);
1757 }
1758 break;
1759 }
1760
1761 case SCC_VEHICLE_NAME: { // {VEHICLE}
1763 if (v == nullptr) break;
1764
1765 if (!v->name.empty()) {
1766 auto tmp_params = MakeParameters(v->name);
1767 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1768 } else if (v->group_id != DEFAULT_GROUP) {
1769 /* The vehicle has no name, but is member of a group, so print group name */
1770 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1771 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1772 } else {
1773 auto tmp_params = MakeParameters(v->unitnumber);
1774
1775 StringID string_id;
1776 switch (v->type) {
1777 default: string_id = STR_INVALID_VEHICLE; break;
1778 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1779 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1780 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1781 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1782 }
1783
1784 GetStringWithArgs(builder, string_id, tmp_params);
1785 }
1786 break;
1787 }
1788
1789 case SCC_SIGN_NAME: { // {SIGN}
1790 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1791 if (si == nullptr) break;
1792
1793 if (!si->name.empty()) {
1794 auto tmp_params = MakeParameters(si->name);
1795 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1796 } else {
1797 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, {});
1798 }
1799 break;
1800 }
1801
1802 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1803 StationGetSpecialString(builder, args.GetNextParameter<StationFacilities>());
1804 break;
1805 }
1806
1807 case SCC_COLOUR: { // {COLOUR}
1808 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1809 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.PutUtf8(scc);
1810 break;
1811 }
1812
1813 default:
1814 builder.PutUtf8(b);
1815 break;
1816 }
1817 } catch (std::out_of_range &e) {
1818 Debug(misc, 0, "FormatString: {}", e.what());
1819 builder += "(invalid parameter)";
1820 }
1821 }
1822}
1823
1824
1825static void StationGetSpecialString(StringBuilder &builder, StationFacilities x)
1826{
1827 if (x.Test(StationFacility::Train)) builder.PutUtf8(SCC_TRAIN);
1828 if (x.Test(StationFacility::TruckStop)) builder.PutUtf8(SCC_LORRY);
1829 if (x.Test(StationFacility::BusStop)) builder.PutUtf8(SCC_BUS);
1830 if (x.Test(StationFacility::Dock)) builder.PutUtf8(SCC_SHIP);
1831 if (x.Test(StationFacility::Airport)) builder.PutUtf8(SCC_PLANE);
1832}
1833
1834static const std::string_view _silly_company_names[] = {
1835 "Bloggs Brothers",
1836 "Tiny Transport Ltd.",
1837 "Express Travel",
1838 "Comfy-Coach & Co.",
1839 "Crush & Bump Ltd.",
1840 "Broken & Late Ltd.",
1841 "Sam Speedy & Son",
1842 "Supersonic Travel",
1843 "Mike's Motors",
1844 "Lightning International",
1845 "Pannik & Loozit Ltd.",
1846 "Inter-City Transport",
1847 "Getout & Pushit Ltd."
1848};
1849
1850static const std::string_view _surname_list[] = {
1851 "Adams",
1852 "Allan",
1853 "Baker",
1854 "Bigwig",
1855 "Black",
1856 "Bloggs",
1857 "Brown",
1858 "Campbell",
1859 "Gordon",
1860 "Hamilton",
1861 "Hawthorn",
1862 "Higgins",
1863 "Green",
1864 "Gribble",
1865 "Jones",
1866 "McAlpine",
1867 "MacDonald",
1868 "McIntosh",
1869 "Muir",
1870 "Murphy",
1871 "Nelson",
1872 "O'Donnell",
1873 "Parker",
1874 "Phillips",
1875 "Pilkington",
1876 "Quigley",
1877 "Sharkey",
1878 "Thomson",
1879 "Watkins"
1880};
1881
1882static const std::string_view _silly_surname_list[] = {
1883 "Grumpy",
1884 "Dozy",
1885 "Speedy",
1886 "Nosey",
1887 "Dribble",
1888 "Mushroom",
1889 "Cabbage",
1890 "Sniffle",
1891 "Fishy",
1892 "Swindle",
1893 "Sneaky",
1894 "Nutkins"
1895};
1896
1897static const char _initial_name_letters[] = {
1898 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1899 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1900};
1901
1902static std::span<const std::string_view> GetSurnameOptions()
1903{
1904 if (_settings_game.game_creation.landscape == LandscapeType::Toyland) return _silly_surname_list;
1905 return _surname_list;
1906}
1907
1913static std::string_view GetSurname(uint32_t seed)
1914{
1915 auto surname_options = GetSurnameOptions();
1916 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1917}
1918
1919static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1920{
1921 builder += GetSurname(seed);
1922 builder += " & Co.";
1923}
1924
1925static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1926{
1927 builder.PutChar(_initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8]);
1928 builder += ". ";
1929
1930 /* The second initial is optional. */
1931 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1932 if (index < std::size(_initial_name_letters)) {
1933 builder.PutChar(_initial_name_letters[index]);
1934 builder += ". ";
1935 }
1936
1937 builder += GetSurname(seed);
1938}
1939
1940static bool GetSpecialNameString(StringBuilder &builder, StringID string, StringParameters &args)
1941{
1942 switch (string) {
1943 case SPECSTR_SILLY_NAME: // Not used in new companies, but retained for old-loader savegames
1944 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1945 return true;
1946
1947 case SPECSTR_ANDCO_NAME: // used for Foobar & Co company names
1948 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1949 return true;
1950
1951 case SPECSTR_PRESIDENT_NAME: // President name
1952 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
1953 return true;
1954 }
1955
1956 /* TownName Transport company names, with the appropriate town name. */
1957 if (IsInsideMM(string, SPECSTR_COMPANY_NAME_START, SPECSTR_COMPANY_NAME_END)) {
1958 GenerateTownNameString(builder, string - SPECSTR_COMPANY_NAME_START, args.GetNextParameter<uint32_t>());
1959 builder += " Transport";
1960 return true;
1961 }
1962
1963 return false;
1964}
1965
1971{
1972 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1973 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1974 this->plural_form < LANGUAGE_MAX_PLURAL &&
1975 this->text_dir <= 1 &&
1976 this->newgrflangid < MAX_LANG &&
1977 this->num_genders < MAX_NUM_GENDERS &&
1978 this->num_cases < MAX_NUM_CASES &&
1979 StrValid(this->name) &&
1980 StrValid(this->own_name) &&
1981 StrValid(this->isocode) &&
1985}
1986
1991{
1992 /* "Less than 25% missing" is "sufficiently finished". */
1993 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1994}
1995
2002{
2003 /* Current language pack */
2004 size_t total_len = 0;
2005 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file.native()), total_len, 1U << 20).release()));
2006 if (!lang_pack) return false;
2007
2008 /* End of read data (+ terminating zero added in ReadFileToMem()) */
2009 const char *end = (char *)lang_pack.get() + total_len + 1;
2010
2011 /* We need at least one byte of lang_pack->data */
2012 if (end <= lang_pack->data || !lang_pack->IsValid()) {
2013 return false;
2014 }
2015
2016 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
2017
2018 uint count = 0;
2019 for (uint i = 0; i < TEXT_TAB_END; i++) {
2020 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
2021 if (num > TAB_SIZE) return false;
2022
2023 tab_start[i] = count;
2024 tab_num[i] = num;
2025 count += num;
2026 }
2027
2028 /* Allocate offsets */
2029 std::vector<std::string_view> strings;
2030
2031 /* Fill offsets */
2032 char *s = lang_pack->data;
2033 for (uint i = 0; i < count; i++) {
2034 size_t len = static_cast<uint8_t>(*s++);
2035 if (s + len >= end) return false;
2036
2037 if (len >= 0xC0) {
2038 len = ((len & 0x3F) << 8) + static_cast<uint8_t>(*s++);
2039 if (s + len >= end) return false;
2040 }
2041 strings.emplace_back(s, len);
2042 s += len;
2043 }
2044 assert(strings.size() == count);
2045
2046 _langpack.langpack = std::move(lang_pack);
2047 _langpack.strings = std::move(strings);
2048 _langpack.langtab_num = tab_num;
2049 _langpack.langtab_start = tab_start;
2050
2051 _current_language = lang;
2053 _config_language_file = FS2OTTD(_current_language->file.filename().native());
2055 _langpack.list_separator = GetString(STR_LIST_SEPARATOR);
2056
2057#ifdef _WIN32
2058 extern void Win32SetCurrentLocaleName(std::string iso_code);
2059 Win32SetCurrentLocaleName(_current_language->isocode);
2060#endif
2061
2062#ifdef WITH_COCOA
2063 extern void MacOSSetCurrentLocaleName(std::string_view iso_code);
2065#endif
2066
2067#ifdef WITH_ICU_I18N
2068 /* Create a collator instance for our current locale. */
2069 UErrorCode status = U_ZERO_ERROR;
2070 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
2071 /* Sort number substrings by their numerical value. */
2072 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
2073 /* Avoid using the collator if it is not correctly set. */
2074 if (U_FAILURE(status)) {
2075 _current_collator.reset();
2076 }
2077#endif /* WITH_ICU_I18N */
2078
2080
2081 /* Some lists need to be sorted again after a language change. */
2087 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
2088 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
2089 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
2090 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
2091 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
2092 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
2093 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
2094
2095 return true;
2096}
2097
2098/* Win32 implementation in win32.cpp.
2099 * OS X implementation in os/macosx/macos.mm. */
2100#if !(defined(_WIN32) || defined(__APPLE__))
2109std::optional<std::string> GetCurrentLocale(const char *param)
2110{
2111 auto env = GetEnv("LANGUAGE");
2112 if (env.has_value()) return std::string{*env};
2113
2114 env = GetEnv("LC_ALL");
2115 if (env.has_value()) return std::string{*env};
2116
2117 if (param != nullptr) {
2118 env = GetEnv(param);
2119 if (env.has_value()) return std::string{*env};
2120 }
2121
2122 env = GetEnv("LANG");
2123 if (env.has_value()) return std::string{*env};
2124
2125 return std::nullopt;
2126}
2127#else
2128std::optional<std::string> GetCurrentLocale(const char *param);
2129#endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2130
2136const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2137{
2138 for (const LanguageMetadata &lang : _languages) {
2139 if (newgrflangid == lang.newgrflangid) return &lang;
2140 }
2141
2142 return nullptr;
2143}
2144
2151static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2152{
2153 auto f = FileHandle::Open(file, "rb");
2154 if (!f.has_value()) return false;
2155
2156 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2157
2158 bool ret = read == 1 && hdr->IsValid();
2159
2160 /* Convert endianness for the windows language ID */
2161 if (ret) {
2162 hdr->missing = FROM_LE16(hdr->missing);
2163 hdr->winlangid = FROM_LE16(hdr->winlangid);
2164 }
2165 return ret;
2166}
2167
2172static void FillLanguageList(const std::string &path)
2173{
2174 std::error_code error_code;
2175 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2176 if (!dir_entry.is_regular_file()) continue;
2177 if (dir_entry.path().extension() != ".lng") continue;
2178
2179 LanguageMetadata lmd;
2180 lmd.file = dir_entry.path();
2181
2182 /* Check whether the file is of the correct version */
2183 std::string file = FS2OTTD(lmd.file.native());
2184 if (!GetLanguageFileHeader(file, &lmd)) {
2185 Debug(misc, 3, "{} is not a valid language file", file);
2186 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2187 Debug(misc, 3, "{}'s language ID is already known", file);
2188 } else {
2189 _languages.push_back(std::move(lmd));
2190 }
2191 }
2192 if (error_code) {
2193 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2194 }
2195}
2196
2202{
2203 for (Searchpath sp : _valid_searchpaths) {
2204 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2205 }
2206 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2207
2208 /* Acquire the locale of the current system */
2209 auto str_lang = GetCurrentLocale("LC_MESSAGES");
2210 std::string_view lang = str_lang.has_value() ? std::string_view{*str_lang} : "en_GB";
2211
2212 const LanguageMetadata *chosen_language = nullptr;
2213 const LanguageMetadata *language_fallback = nullptr;
2214 const LanguageMetadata *en_GB_fallback = _languages.data();
2215
2216 /* Find a proper language. */
2217 for (const LanguageMetadata &lng : _languages) {
2218 /* We are trying to find a default language. The priority is by
2219 * configuration file, local environment and last, if nothing found,
2220 * English. */
2221 if (_config_language_file == FS2OTTD(lng.file.filename().native())) {
2222 chosen_language = &lng;
2223 break;
2224 }
2225
2226 std::string_view iso_code = lng.isocode;
2227 if (iso_code == "en_GB") en_GB_fallback = &lng;
2228
2229 /* Only auto-pick finished translations */
2230 if (!lng.IsReasonablyFinished()) continue;
2231
2232 if (iso_code.starts_with(lang.substr(0, 5))) chosen_language = &lng;
2233 if (iso_code.starts_with(lang.substr(0, 2))) language_fallback = &lng;
2234 }
2235
2236 /* We haven't found the language in the config nor the one in the locale.
2237 * Now we set it to one of the fallback languages */
2238 if (chosen_language == nullptr) {
2239 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2240 }
2241
2242 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file.native()));
2243}
2244
2250{
2251 return _langpack.langpack->isocode;
2252}
2253
2259{
2260 InitFontCache(this->Monospace());
2261
2262 this->Reset();
2263 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2264 FontSize size = this->DefaultSize();
2265 FontCache *fc = FontCache::Get(size);
2266 for (char32_t c : Utf8View(*text)) {
2267 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2268 size = (FontSize)(c - SCC_FIRST_FONT);
2269 fc = FontCache::Get(size);
2270 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2271 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2272 std::string size_name;
2273
2274 switch (size) {
2275 case FS_NORMAL: size_name = "medium"; break;
2276 case FS_SMALL: size_name = "small"; break;
2277 case FS_LARGE: size_name = "large"; break;
2278 case FS_MONO: size_name = "mono"; break;
2279 default: NOT_REACHED();
2280 }
2281
2282 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2283 return true;
2284 }
2285 }
2286 }
2287 return false;
2288}
2289
2292 uint i;
2293 uint j;
2294
2295 void Reset() override
2296 {
2297 this->i = 0;
2298 this->j = 0;
2299 }
2300
2302 {
2303 return FS_NORMAL;
2304 }
2305
2306 std::optional<std::string_view> NextString() override
2307 {
2308 if (this->i >= TEXT_TAB_END) return std::nullopt;
2309
2310 std::string_view ret = _langpack.strings[_langpack.langtab_start[this->i] + this->j];
2311
2312 this->j++;
2313 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2314 this->i++;
2315 this->j = 0;
2316 }
2317
2318 return ret;
2319 }
2320
2321 bool Monospace() override
2322 {
2323 return false;
2324 }
2325
2326 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override
2327 {
2328#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2329 settings->small.font = font_name;
2330 settings->medium.font = font_name;
2331 settings->large.font = font_name;
2332
2333 settings->small.os_handle = os_data;
2334 settings->medium.os_handle = os_data;
2335 settings->large.os_handle = os_data;
2336#endif
2337 }
2338};
2339
2353void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2354{
2355 static LanguagePackGlyphSearcher pack_searcher;
2356 if (searcher == nullptr) searcher = &pack_searcher;
2357 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2358#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2359 if (bad_font) {
2360 /* We found an unprintable character... lets try whether we can find
2361 * a fallback font that can print the characters in the current language. */
2362 bool any_font_configured = !_fcsettings.medium.font.empty();
2363 FontCacheSettings backup = _fcsettings;
2364
2365 _fcsettings.mono.os_handle = nullptr;
2366 _fcsettings.medium.os_handle = nullptr;
2367
2368 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
2369
2370 _fcsettings = std::move(backup);
2371
2372 if (!bad_font && any_font_configured) {
2373 /* If the user configured a bad font, and we found a better one,
2374 * show that we loaded the better font instead of the configured one.
2375 */
2376 std::string err_str;
2377 StringBuilder builder(err_str);
2378 builder.PutUtf8(SCC_YELLOW);
2379 builder.Put("The current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2380 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
2381 }
2382
2383 if (bad_font && base_font) {
2384 /* Our fallback font does miss characters too, so keep the
2385 * user chosen font as that is more likely to be any good than
2386 * the wild guess we made */
2387 InitFontCache(searcher->Monospace());
2388 }
2389 }
2390#endif
2391
2392 if (bad_font) {
2393 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2394 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2395 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2396 */
2397 std::string err_str;
2398 StringBuilder builder(err_str);
2399 builder.PutUtf8(SCC_YELLOW);
2400 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.");
2401 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
2402
2403 /* Reset the font width */
2404 LoadStringWidthTable(searcher->Monospace());
2405 return;
2406 }
2407
2408 /* Update the font with cache */
2409 LoadStringWidthTable(searcher->Monospace());
2410
2411#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2412 /*
2413 * For right-to-left languages we need the ICU library. If
2414 * we do not have support for that library we warn the user
2415 * about it with a message. As we do not want the string to
2416 * be translated by the translators, we 'force' it into the
2417 * binary and 'load' it via a BindCString. To do this
2418 * properly we have to set the colour of the string,
2419 * otherwise we end up with a lot of artifacts.
2420 */
2421 if (_current_text_dir != TD_LTR) {
2422 std::string err_str;
2423 StringBuilder builder(err_str);
2424 builder.PutUtf8(SCC_YELLOW);
2425 builder.Put("This version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2426 ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_ERROR);
2427 }
2428#endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */
2429}
Class for backupping variables and making sure they are restored later.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
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:103
std::string GetDecodedString() const
Decode the encoded string.
Definition strings.cpp:208
EncodedString ReplaceParam(size_t param, StringParameter &&value) const
Replace a parameter of this EncodedString.
Definition strings.cpp:151
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1168
Font cache for basic fonts.
Definition fontcache.h:21
virtual GlyphID MapCharToGlyph(char32_t key, bool fallback=true)=0
Map a character into a glyph.
static FontCache * Get(FontSize fs)
Get the font cache of a given font size.
Definition fontcache.h:129
Helper for searching through the language pack.
Definition strings.cpp:2291
bool Monospace() override
Whether to search for a monospace font or not.
Definition strings.cpp:2321
void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override
Set the right font names.
Definition strings.cpp:2326
uint j
Iterator for the secondary language tables.
Definition strings.cpp:2293
std::optional< std::string_view > NextString() override
Get the next string to search through.
Definition strings.cpp:2306
FontSize DefaultSize() override
Get the default (font) size of the string.
Definition strings.cpp:2301
uint i
Iterator for the primary language tables.
Definition strings.cpp:2292
void Reset() override
Reset the search, i.e.
Definition strings.cpp:2295
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:2258
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:69
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:30
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:48
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
Definition currency.h:118
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)
Function to handling different endian machines.
Base class for engines.
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:1023
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:97
fluid_settings_t * settings
FluidSynth settings handle.
void InitFontCache(bool monospace)
(Re)initialize the font cache related things, i.e.
Functions related to detecting/finding the right font.
bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback)
We would like to have a fallback font as the current one doesn't contain all characters we need.
Definition font_osx.cpp:27
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:1292
void LoadStringWidthTable(bool monospace)
Initialize _stringwidth_table cache.
Definition gfx.cpp:1246
Functions related to laying out the texts.
FontSize
Available font sizes.
Definition gfx_type.h:250
@ FS_MONO
Index of the monospaced font in the font tables.
Definition gfx_type.h:254
@ FS_SMALL
Index of the small 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:251
@ FS_LARGE
Index of the large font in the font tables.
Definition gfx_type.h:253
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.
Information about languages and their files.
static const uint8_t MAX_NUM_GENDERS
Maximum number of supported genders.
Definition language.h:20
std::vector< LanguageMetadata > LanguageList
Type for the list of language meta data.
Definition language.h:98
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.
@ Name
Engine name.
@ 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.
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.
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:860
bool StrValid(std::span< const char > str)
Checks whether the given string is valid, i.e.
Definition string.cpp:203
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:237
LanguageList _languages
The actual list of language meta data.
Definition strings.cpp:54
static void FormatNumber(StringBuilder &builder, int64_t number, std::string_view separator)
Format a number into a string.
Definition strings.cpp:473
EncodedString GetEncodedStringWithArgs(StringID str, std::span< const StringParameter > params)
Encode a string with its parameters into an encoded string.
Definition strings.cpp:103
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:327
std::optional< std::string > GetCurrentLocale(const char *param)
Determine the current charset based on the environment First check some default values,...
Definition strings.cpp:2109
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:906
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:55
static void FormatBytes(StringBuilder &builder, int64_t number)
Format a given number as a number of bytes with the SI prefix.
Definition strings.cpp:528
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:425
uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given (internal) speed to the display speed.
Definition strings.cpp:958
void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
Check whether the currently loaded language pack uses characters that the currently loaded font does ...
Definition strings.cpp:2353
std::string _config_language_file
The file (name) stored in the configuration.
Definition strings.cpp:53
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:941
static int DeterminePluralForm(int64_t count, uint plural_form)
Determine the "plural" index given a plural form and a number.
Definition strings.cpp:647
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91
uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
Convert the given display speed to the km/h-ish speed.
Definition strings.cpp:991
static const Units _units_power[]
Unit conversions for power.
Definition strings.cpp:865
static std::string_view GetSurname(uint32_t seed)
Get the surname of the president with the given seed.
Definition strings.cpp:1913
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:415
std::string_view GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
Definition strings.cpp:2249
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:892
static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder)
Decodes an encoded string during FormatString.
Definition strings.cpp:1002
static const UnitsLong _units_weight[]
Unit conversions for weight.
Definition strings.cpp:885
static const Units _units_velocity_calendar[]
Unit conversions for velocity.
Definition strings.cpp:847
std::unique_ptr< icu::Collator > _current_collator
Collator for the language currently in use.
Definition strings.cpp:60
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:2172
uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given km/h-ish speed to the display speed.
Definition strings.cpp:981
static const Units _units_force[]
Unit conversions for force.
Definition strings.cpp:899
void InitializeLanguagePacks()
Make a list of the available language packs.
Definition strings.cpp:2201
const LanguageMetadata * GetLanguage(uint8_t newgrflangid)
Get the language with the given NewGRF language ID.
Definition strings.cpp:2136
static const Units _units_time_days_or_seconds[]
Unit conversions for time in calendar days or wallclock seconds.
Definition strings.cpp:913
uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
Convert the given display speed to the (internal) speed.
Definition strings.cpp:971
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
static const Units _units_velocity_realtime[]
Unit conversions for velocity.
Definition strings.cpp:856
bool ReadLanguagePack(const LanguageMetadata *lang)
Read a particular language.
Definition strings.cpp:2001
static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
Reads the language file header and checks compatibility.
Definition strings.cpp:2151
static const Units _units_time_months_or_minutes[]
Unit conversions for time in calendar months or wallclock minutes.
Definition strings.cpp:919
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:1073
static const Units _units_time_years_or_minutes[]
Unit conversions for time in calendar years or wallclock minutes.
Definition strings.cpp:931
static const Units _units_power_to_weight[]
Unit conversions for power to weight.
Definition strings.cpp:872
uint64_t GetParamMaxDigits(uint count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:219
static const Units _units_time_years_or_periods[]
Unit conversions for time in calendar years or economic periods.
Definition strings.cpp:925
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.
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
StringID quantifier
Text for multiple units of cargo of this type.
Definition cargotype.h:94
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:76
std::string separator
The thousands separator for this currency.
Definition currency.h:78
std::string prefix
Prefix to apply when formatting money in this currency.
Definition currency.h:80
std::string suffix
Suffix to apply when formatting money in this currency.
Definition currency.h:81
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:77
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:92
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:200
FontCacheSubSetting mono
The mono space font used for license/readme viewers.
Definition fontcache.h:204
FontCacheSubSetting medium
The normal font size.
Definition fontcache.h:202
std::string font
The name of the font, or path to the font.
Definition fontcache.h:193
const void * os_handle
Optional native OS font info. Only valid during font search.
Definition fontcache.h:196
Dynamic data of a loaded NewGRF.
Definition newgrf.h:115
LandscapeType landscape
the landscape we're currently in
LocaleSettings locale
settings related to used currency/unit system in the current game
GameCreationSettings game_creation
settings used during the creation of a game (map)
Group data.
Definition group.h:73
std::string name
Group Name.
Definition group.h:74
uint16_t number
Per-company group number.
Definition group.h:86
Defines the data structure for constructing industry.
StringID name
Displayed name of the industry.
StringID station_name
Default name for nearby station.
Defines the internal data of a functional industry.
Definition industry.h:64
IndustryType type
type of industry.
Definition industry.h:100
Town * town
Nearest town.
Definition industry.h:93
Make sure the size is right.
Definition language.h:93
std::filesystem::path file
Name of the file we read this data from.
Definition language.h:94
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:1970
bool IsReasonablyFinished() const
Check whether a translation is sufficiently finished to offer it to the public.
Definition strings.cpp:1990
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:288
std::array< uint, TEXT_TAB_END > langtab_num
Offset into langpack offs.
Definition strings.cpp:285
std::array< uint, TEXT_TAB_END > langtab_start
Offset into langpack offs.
Definition strings.cpp:286
uint8_t units_velocity_nautical
unit system for velocity of ships and aircraft
uint8_t units_velocity
unit system for velocity of trains and road vehicles
std::string digit_decimal_separator
decimal separator
std::string digit_group_separator_currency
thousand separator for currencies
uint8_t units_power
unit system for power
uint8_t units_height
unit system for height
uint8_t units_force
unit system for force
uint8_t units_volume
unit system for volume
uint8_t units_weight
unit system for weight
std::string digit_group_separator
thousand separator for non-currencies
static Titem * Get(auto index)
Returns Titem with given index.
Tindex index
Index of this pool item.
static Titem * GetIfValid(auto index)
Returns Titem with given index.
static Station * GetIfValid(auto index)
Returns station if the index is a valid index for this station type.
Station data structure.
The data required to format and validate a single parameter of a string.
Templated helper to make a type-safe 'typedef' representing a single POD value.
Town data structure.
Definition town.h:52
std::string name
Custom town name. If empty, the town was not renamed and uses the generated name.
Definition town.h:61
Helper for unit conversion.
Definition strings.cpp:800
double factor
Amount to multiply or divide upon conversion.
Definition strings.cpp:801
int64_t ToDisplay(int64_t input, bool round=true) const
Convert value from OpenTTD's internal unit into the displayed value.
Definition strings.cpp:809
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:823
Information about a specific unit system with a long variant.
Definition strings.cpp:839
StringID s
String for the short variant of the unit.
Definition strings.cpp:841
StringID l
String for the long variant of the unit.
Definition strings.cpp:842
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:843
UnitConversion c
Conversion.
Definition strings.cpp:840
Information about a specific unit system.
Definition strings.cpp:832
StringID s
String for the unit.
Definition strings.cpp:834
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:835
UnitConversion c
Conversion.
Definition strings.cpp:833
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.
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:354
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:337
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:3282
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: