OpenTTD Source 15.0-beta1
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 <stack>
42#include <charconv>
43
44#include "table/strings.h"
45#include "table/control_codes.h"
46#include "3rdparty/fmt/std.h"
47
48#include "strings_internal.h"
49
50#include "safeguards.h"
51
55
57
58#ifdef WITH_ICU_I18N
59std::unique_ptr<icu::Collator> _current_collator;
60#endif /* WITH_ICU_I18N */
61
62ArrayStringParameters<20> _global_string_params;
63
69{
70 for (auto &param : this->parameters) param.type = 0;
71 this->offset = 0;
72}
73
74
82{
83 assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END));
84 if (this->offset >= this->parameters.size()) {
85 throw std::out_of_range("Trying to read invalid string parameter");
86 }
87
88 auto &param = this->parameters[this->offset++];
89 if (param.type != 0 && param.type != this->next_type) {
90 this->next_type = 0;
91 throw std::out_of_range("Trying to read string parameter with wrong type");
92 }
93 param.type = this->next_type;
94 this->next_type = 0;
95 return param;
96}
97
98
104void SetDParam(size_t n, uint64_t v)
105{
106 _global_string_params.SetParam(n, v);
107}
108
114uint64_t GetDParam(size_t n)
115{
116 return std::get<uint64_t>(_global_string_params.GetParam(n));
117}
118
127void SetDParamMaxValue(size_t n, uint64_t max_value, uint min_count, FontSize size)
128{
129 uint num_digits = 1;
130 while (max_value >= 10) {
131 num_digits++;
132 max_value /= 10;
133 }
134 SetDParamMaxDigits(n, std::max(min_count, num_digits), size);
135}
136
143void SetDParamMaxDigits(size_t n, uint count, FontSize size)
144{
145 uint front = 0;
146 uint next = 0;
147 GetBroadestDigit(&front, &next, size);
148 uint64_t val = count > 1 ? front : next;
149 for (; count > 1; count--) {
150 val = 10 * val + next;
151 }
152 SetDParam(n, val);
153}
154
159void CopyInDParam(const std::span<const StringParameterData> backup)
160{
161 for (size_t i = 0; i < backup.size(); i++) {
162 _global_string_params.SetParam(i, backup[i]);
163 }
164}
165
171void CopyOutDParam(std::vector<StringParameterData> &backup, size_t num)
172{
173 backup.resize(num);
174 for (size_t i = 0; i < backup.size(); i++) {
175 backup[i] = _global_string_params.GetParam(i);
176 }
177}
178
184bool HaveDParamChanged(const std::span<const StringParameterData> backup)
185{
186 for (size_t i = 0; i < backup.size(); i++) {
187 if (backup[i] != _global_string_params.GetParam(i)) return true;
188 }
189 return false;
190}
191
192static void StationGetSpecialString(StringBuilder &builder, StationFacility x);
193static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed);
194static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args);
195
196static void FormatString(StringBuilder &builder, const char *str, StringParameters &args, uint case_index = 0, bool game_script = false, bool dry_run = false);
197
199 char data[]; // list of strings
200};
201
203 void operator()(LanguagePack *langpack)
204 {
205 /* LanguagePack is in fact reinterpreted char[], we need to reinterpret it back to free it properly. */
206 delete[] reinterpret_cast<char*>(langpack);
207 }
208};
209
211 std::unique_ptr<LanguagePack, LanguagePackDeleter> langpack;
212
213 std::vector<char *> offsets;
214
215 std::array<uint, TEXT_TAB_END> langtab_num;
216 std::array<uint, TEXT_TAB_END> langtab_start;
217
218 std::string list_separator;
219};
220
221static LoadedLanguagePack _langpack;
222
223static bool _scan_for_gender_data = false;
224
229std::string_view GetListSeparator()
230{
231 return _langpack.list_separator;
232}
233
234const char *GetStringPtr(StringID string)
235{
236 switch (GetStringTab(string)) {
238 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
239 case TEXT_TAB_OLD_NEWGRF: NOT_REACHED();
241 default: {
242 const uint offset = _langpack.langtab_start[GetStringTab(string)] + GetStringIndex(string);
243 if (offset < _langpack.offsets.size()) return _langpack.offsets[offset];
244 return nullptr;
245 }
246 }
247}
248
257void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
258{
259 if (string == 0) {
260 GetStringWithArgs(builder, STR_UNDEFINED, args);
261 return;
262 }
263
264 uint index = GetStringIndex(string);
265 StringTab tab = GetStringTab(string);
266
267 switch (tab) {
268 case TEXT_TAB_TOWN:
269 if (index >= 0xC0 && !game_script) {
270 try {
271 GetSpecialTownNameString(builder, index - 0xC0, args.GetNextParameter<uint32_t>());
272 } catch (const std::runtime_error &e) {
273 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
274 builder += "(invalid string parameter)";
275 }
276 return;
277 }
278 break;
279
280 case TEXT_TAB_SPECIAL:
281 if (index >= 0xE4 && !game_script) {
282 try {
283 GetSpecialNameString(builder, index - 0xE4, args);
284 } catch (const std::runtime_error &e) {
285 Debug(misc, 0, "GetStringWithArgs: {}", e.what());
286 builder += "(invalid string parameter)";
287 }
288 return;
289 }
290 break;
291
292 case TEXT_TAB_OLD_CUSTOM:
293 /* Old table for custom names. This is no longer used */
294 if (!game_script) {
295 FatalError("Incorrect conversion of custom name string.");
296 }
297 break;
298
300 FormatString(builder, GetGameStringPtr(index), args, case_index, true);
301 return;
302 }
303
304 case TEXT_TAB_OLD_NEWGRF:
305 NOT_REACHED();
306
308 FormatString(builder, GetGRFStringPtr(index), args, case_index);
309 return;
310 }
311
312 default:
313 break;
314 }
315
316 if (index >= _langpack.langtab_num[tab]) {
317 if (game_script) {
318 return GetStringWithArgs(builder, STR_UNDEFINED, args);
319 }
320 FatalError("String 0x{:X} is invalid. You are probably using an old version of the .lng file.\n", string);
321 }
322
323 FormatString(builder, GetStringPtr(string), args, case_index);
324}
325
326
333std::string GetString(StringID string)
334{
335 _global_string_params.PrepareForNextRun();
336 return GetStringWithArgs(string, _global_string_params);
337}
338
345void AppendStringInPlace(std::string &result, StringID string)
346{
347 _global_string_params.PrepareForNextRun();
348 StringBuilder builder(result);
349 GetStringWithArgs(builder, string, _global_string_params);
350}
351
359{
360 std::string result;
361 StringBuilder builder(result);
362 GetStringWithArgs(builder, string, args);
363 return result;
364}
365
371void SetDParamStr(size_t n, const char *str)
372{
373 _global_string_params.SetParam(n, str);
374}
375
382void SetDParamStr(size_t n, const std::string &str)
383{
384 _global_string_params.SetParam(n, str);
385}
386
394void SetDParamStr(size_t n, std::string &&str)
395{
396 _global_string_params.SetParam(n, std::move(str));
397}
398
399static const char *GetDecimalSeparator()
400{
401 const char *decimal_separator = _settings_game.locale.digit_decimal_separator.c_str();
402 if (StrEmpty(decimal_separator)) decimal_separator = _langpack.langpack->digit_decimal_separator;
403 return decimal_separator;
404}
405
412static void FormatNumber(StringBuilder &builder, int64_t number, const char *separator)
413{
414 static const int max_digits = 20;
415 uint64_t divisor = 10000000000000000000ULL;
416 int thousands_offset = (max_digits - 1) % 3;
417
418 if (number < 0) {
419 builder += '-';
420 number = -number;
421 }
422
423 uint64_t num = number;
424 uint64_t tot = 0;
425 for (int i = 0; i < max_digits; i++) {
426 uint64_t quot = 0;
427 if (num >= divisor) {
428 quot = num / divisor;
429 num = num % divisor;
430 }
431 if ((tot |= quot) || i == max_digits - 1) {
432 builder += '0' + quot; // quot is a single digit
433 if ((i % 3) == thousands_offset && i < max_digits - 1) builder += separator;
434 }
435
436 divisor /= 10;
437 }
438}
439
440static void FormatCommaNumber(StringBuilder &builder, int64_t number)
441{
442 const char *separator = _settings_game.locale.digit_group_separator.c_str();
443 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator;
444 FormatNumber(builder, number, separator);
445}
446
447static void FormatNoCommaNumber(StringBuilder &builder, int64_t number)
448{
449 fmt::format_to(builder, "{}", number);
450}
451
452static void FormatZerofillNumber(StringBuilder &builder, int64_t number, int count)
453{
454 fmt::format_to(builder, "{:0{}d}", number, count);
455}
456
457static void FormatHexNumber(StringBuilder &builder, uint64_t number)
458{
459 fmt::format_to(builder, "0x{:X}", number);
460}
461
467static void FormatBytes(StringBuilder &builder, int64_t number)
468{
469 assert(number >= 0);
470
471 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
472 const char * const iec_prefixes[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
473 uint id = 1;
474 while (number >= 1024 * 1024) {
475 number /= 1024;
476 id++;
477 }
478
479 if (number < 1024) {
480 id = 0;
481 fmt::format_to(builder, "{}", number);
482 } else if (number < 1024 * 10) {
483 fmt::format_to(builder, "{}{}{:02}", number / 1024, GetDecimalSeparator(), (number % 1024) * 100 / 1024);
484 } else if (number < 1024 * 100) {
485 fmt::format_to(builder, "{}{}{:01}", number / 1024, GetDecimalSeparator(), (number % 1024) * 10 / 1024);
486 } else {
487 assert(number < 1024 * 1024);
488 fmt::format_to(builder, "{}", number / 1024);
489 }
490
491 assert(id < lengthof(iec_prefixes));
492 fmt::format_to(builder, NBSP "{}B", iec_prefixes[id]);
493}
494
495static void FormatYmdString(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
496{
497 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
498
499 auto tmp_params = MakeParameters(ymd.day + STR_DAY_NUMBER_1ST - 1, STR_MONTH_ABBREV_JAN + ymd.month, ymd.year);
500 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_LONG), tmp_params, case_index);
501}
502
503static void FormatMonthAndYear(StringBuilder &builder, TimerGameCalendar::Date date, uint case_index)
504{
505 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
506
507 auto tmp_params = MakeParameters(STR_MONTH_JAN + ymd.month, ymd.year);
508 FormatString(builder, GetStringPtr(STR_FORMAT_DATE_SHORT), tmp_params, case_index);
509}
510
511static void FormatTinyOrISODate(StringBuilder &builder, TimerGameCalendar::Date date, StringID str)
512{
513 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(date);
514
515 /* Day and month are zero-padded with ZEROFILL_NUM, hence the two 2s. */
516 auto tmp_params = MakeParameters(ymd.day, 2, ymd.month + 1, 2, ymd.year);
517 FormatString(builder, GetStringPtr(str), tmp_params);
518}
519
520static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *spec, Money number, bool compact)
521{
522 /* We are going to make number absolute for printing, so
523 * keep this piece of data as we need it later on */
524 bool negative = number < 0;
525
526 number *= spec->rate;
527
528 /* convert from negative */
529 if (number < 0) {
530 builder.Utf8Encode(SCC_PUSH_COLOUR);
531 builder.Utf8Encode(SCC_RED);
532 builder += '-';
533 number = -number;
534 }
535
536 /* Add prefix part, following symbol_pos specification.
537 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
538 * The only remaining value is 1 (suffix), so everything that is not 1 */
539 if (spec->symbol_pos != 1) builder += spec->prefix;
540
541 StringID number_str = STR_NULL;
542
543 /* For huge numbers, compact the number. */
544 if (compact) {
545 /* Take care of the thousand rounding. Having 1 000 000 k
546 * and 1 000 M is inconsistent, so always use 1 000 M. */
547 if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
548 number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
549 number_str = STR_CURRENCY_SHORT_TERA;
550 } else if (number >= Money(1'000'000'000'000) - 500'000) {
551 number = (number + 500'000'000) / 1'000'000'000;
552 number_str = STR_CURRENCY_SHORT_GIGA;
553 } else if (number >= 1'000'000'000 - 500) {
554 number = (number + 500'000) / 1'000'000;
555 number_str = STR_CURRENCY_SHORT_MEGA;
556 } else if (number >= 1'000'000) {
557 number = (number + 500) / 1'000;
558 number_str = STR_CURRENCY_SHORT_KILO;
559 }
560 }
561
562 const char *separator = _settings_game.locale.digit_group_separator_currency.c_str();
563 if (StrEmpty(separator)) separator = GetCurrency().separator.c_str();
564 if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
565 FormatNumber(builder, number, separator);
566 if (number_str != STR_NULL) {
567 auto tmp_params = ArrayStringParameters<0>();
568 FormatString(builder, GetStringPtr(number_str), tmp_params);
569 }
570
571 /* Add suffix part, following symbol_pos specification.
572 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
573 * The only remaining value is 1 (prefix), so everything that is not 0 */
574 if (spec->symbol_pos != 0) builder += spec->suffix;
575
576 if (negative) {
577 builder.Utf8Encode(SCC_POP_COLOUR);
578 }
579}
580
587static int DeterminePluralForm(int64_t count, int plural_form)
588{
589 /* The absolute value determines plurality */
590 uint64_t n = abs(count);
591
592 switch (plural_form) {
593 default:
594 NOT_REACHED();
595
596 /* Two forms: singular used for one only.
597 * Used in:
598 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
599 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
600 case 0:
601 return n != 1 ? 1 : 0;
602
603 /* Only one form.
604 * Used in:
605 * Hungarian, Japanese, Turkish */
606 case 1:
607 return 0;
608
609 /* Two forms: singular used for 0 and 1.
610 * Used in:
611 * French, Brazilian Portuguese */
612 case 2:
613 return n > 1 ? 1 : 0;
614
615 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
616 * Note: Cases are out of order for hysterical reasons. '0' is last.
617 * Used in:
618 * Latvian */
619 case 3:
620 return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;
621
622 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
623 * Used in:
624 * Gaelige (Irish) */
625 case 4:
626 return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;
627
628 /* 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.
629 * Used in:
630 * Lithuanian */
631 case 5:
632 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
633
634 /* 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.
635 * Used in:
636 * Croatian, Russian, Ukrainian */
637 case 6:
638 return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
639
640 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
641 * Used in:
642 * Polish */
643 case 7:
644 return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
645
646 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
647 * Used in:
648 * Slovenian */
649 case 8:
650 return n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3;
651
652 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
653 * Used in:
654 * Icelandic */
655 case 9:
656 return n % 10 == 1 && n % 100 != 11 ? 0 : 1;
657
658 /* Three forms: special cases for 1, and 2 to 4
659 * Used in:
660 * Czech, Slovak */
661 case 10:
662 return n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2;
663
664 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
665 * Korean doesn't have the concept of plural, but depending on how a
666 * number is pronounced it needs another version of a particle.
667 * As such the plural system is misused to give this distinction.
668 */
669 case 11:
670 switch (n % 10) {
671 case 0: // yeong
672 case 1: // il
673 case 3: // sam
674 case 6: // yuk
675 case 7: // chil
676 case 8: // pal
677 return 0;
678
679 case 2: // i
680 case 4: // sa
681 case 5: // o
682 case 9: // gu
683 return 1;
684
685 default:
686 NOT_REACHED();
687 }
688
689 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
690 * Used in:
691 * Maltese */
692 case 12:
693 return (n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
694 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
695 * Used in:
696 * Scottish Gaelic */
697 case 13:
698 return ((n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : ((n > 2 && n < 11) || (n > 12 && n < 20)) ? 2 : 3);
699
700 /* Three forms: special cases for 1, 0 and numbers ending in 01 to 19.
701 * Used in:
702 * Romanian */
703 case 14:
704 return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;
705 }
706}
707
708static const char *ParseStringChoice(const char *b, uint form, StringBuilder &builder)
709{
710 /* <NUM> {Length of each string} {each string} */
711 uint n = (uint8_t)*b++;
712 uint pos, i, mypos = 0;
713
714 for (i = pos = 0; i != n; i++) {
715 uint len = (uint8_t)*b++;
716 if (i == form) mypos = pos;
717 pos += len;
718 }
719
720 builder += b + mypos;
721 return b + pos;
722}
723
726 double factor;
727
734 int64_t ToDisplay(int64_t input, bool round = true) const
735 {
736 return round
737 ? (int64_t)std::round(input * this->factor)
738 : (int64_t)(input * this->factor);
739 }
740
748 int64_t FromDisplay(int64_t input, bool round = true, int64_t divider = 1) const
749 {
750 return round
751 ? (int64_t)std::round(input / this->factor / divider)
752 : (int64_t)(input / this->factor / divider);
753 }
754};
755
762
770
773 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
774 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
775 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
776 { { 0.578125 }, STR_UNITS_VELOCITY_GAMEUNITS_DAY, 1 },
777 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
778};
779
782 { { 1.0 }, STR_UNITS_VELOCITY_IMPERIAL, 0 },
783 { { 1.609344 }, STR_UNITS_VELOCITY_METRIC, 0 },
784 { { 0.44704 }, STR_UNITS_VELOCITY_SI, 0 },
785 { { 0.289352 }, STR_UNITS_VELOCITY_GAMEUNITS_SEC, 1 },
786 { { 0.868976 }, STR_UNITS_VELOCITY_KNOTS, 0 },
787};
788
790static const Units _units_power[] = {
791 { { 1.0 }, STR_UNITS_POWER_IMPERIAL, 0 },
792 { { 1.01387 }, STR_UNITS_POWER_METRIC, 0 },
793 { { 0.745699 }, STR_UNITS_POWER_SI, 0 },
794};
795
798 { { 0.907185 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_IMPERIAL, 1 },
799 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_METRIC, 1 },
800 { { 1.0 }, STR_UNITS_POWER_IMPERIAL_TO_WEIGHT_SI, 1 },
801 { { 0.919768 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_IMPERIAL, 1 },
802 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_METRIC, 1 },
803 { { 1.01387 }, STR_UNITS_POWER_METRIC_TO_WEIGHT_SI, 1 },
804 { { 0.676487 }, STR_UNITS_POWER_SI_TO_WEIGHT_IMPERIAL, 1 },
805 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_METRIC, 1 },
806 { { 0.745699 }, STR_UNITS_POWER_SI_TO_WEIGHT_SI, 1 },
807};
808
810static const UnitsLong _units_weight[] = {
811 { { 1.102311 }, STR_UNITS_WEIGHT_SHORT_IMPERIAL, STR_UNITS_WEIGHT_LONG_IMPERIAL, 0 },
812 { { 1.0 }, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, 0 },
813 { { 1000.0 }, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, 0 },
814};
815
817static const UnitsLong _units_volume[] = {
818 { { 264.172 }, STR_UNITS_VOLUME_SHORT_IMPERIAL, STR_UNITS_VOLUME_LONG_IMPERIAL, 0 },
819 { { 1000.0 }, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, 0 },
820 { { 1.0 }, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, 0 },
821};
822
824static const Units _units_force[] = {
825 { { 0.224809 }, STR_UNITS_FORCE_IMPERIAL, 0 },
826 { { 0.101972 }, STR_UNITS_FORCE_METRIC, 0 },
827 { { 0.001 }, STR_UNITS_FORCE_SI, 0 },
828};
829
831static const Units _units_height[] = {
832 { { 3.0 }, STR_UNITS_HEIGHT_IMPERIAL, 0 }, // "Wrong" conversion factor for more nicer GUI values
833 { { 1.0 }, STR_UNITS_HEIGHT_METRIC, 0 },
834 { { 1.0 }, STR_UNITS_HEIGHT_SI, 0 },
835};
836
839 { { 1 }, STR_UNITS_DAYS, 0 },
840 { { 2 }, STR_UNITS_SECONDS, 0 },
841};
842
845 { { 1 }, STR_UNITS_MONTHS, 0 },
846 { { 1 }, STR_UNITS_MINUTES, 0 },
847};
848
851 { { 1 }, STR_UNITS_YEARS, 0 },
852 { { 1 }, STR_UNITS_PERIODS, 0 },
853};
854
857 { { 1 }, STR_UNITS_YEARS, 0 },
858 { { 12 }, STR_UNITS_MINUTES, 0 },
859};
860
867{
869
870 assert(setting < lengthof(_units_velocity_calendar));
871 assert(setting < lengthof(_units_velocity_realtime));
872
874
875 return _units_velocity_calendar[setting];
876}
877
884{
885 /* For historical reasons we don't want to mess with the
886 * conversion for speed. So, don't round it and keep the
887 * original conversion factors instead of the real ones. */
888 return GetVelocityUnits(type).c.ToDisplay(speed, false);
889}
890
897{
898 return GetVelocityUnits(type).c.FromDisplay(speed);
899}
900
907{
908 return GetVelocityUnits(type).c.ToDisplay(speed * 10, false) / 16;
909}
910
917{
918 return GetVelocityUnits(type).c.FromDisplay(speed * 16, true, 10);
919}
920
928static void FormatString(StringBuilder &builder, const char *str_arg, StringParameters &args, uint case_index, bool game_script, bool dry_run)
929{
930 size_t orig_offset = args.GetOffset();
931
932 if (!dry_run) {
933 /*
934 * This function is normally called with `dry_run` false, then we call this function again
935 * with `dry_run` being true. The dry run is required for the gender formatting. For the
936 * gender determination we need to format a sub string to get the gender, but for that we
937 * need to know as what string control code type the specific parameter is encoded. Since
938 * gendered words can be before the "parameter" words, this needs to be determined before
939 * the actual formatting.
940 */
941 std::string buffer;
942 StringBuilder dry_run_builder(buffer);
943 if (UsingNewGRFTextStack()) {
944 /* Values from the NewGRF text stack are only copied to the normal
945 * argv array at the time they are encountered. That means that if
946 * another string command references a value later in the string it
947 * would fail. We solve that by running FormatString twice. The first
948 * pass makes sure the argv array is correctly filled and the second
949 * pass can reference later values without problems. */
950 struct TextRefStack *backup = CreateTextRefStackBackup();
951 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
953 } else {
954 FormatString(dry_run_builder, str_arg, args, case_index, game_script, true);
955 }
956 /* We have to restore the original offset here to to read the correct values. */
957 args.SetOffset(orig_offset);
958 }
959 char32_t b = '\0';
960 uint next_substr_case_index = 0;
961 std::stack<const char *, std::vector<const char *>> str_stack;
962 str_stack.push(str_arg);
963
964 for (;;) {
965 try {
966 while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') {
967 str_stack.pop();
968 }
969 if (str_stack.empty()) break;
970 const char *&str = str_stack.top();
971
972 if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) {
973 /* We need to pass some stuff as it might be modified. */
974 StringParameters remaining = args.GetRemainingParameters();
975 b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run);
976 if (b == 0) continue;
977 }
978
979 if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) {
980 builder.Utf8Encode(b);
981 continue;
982 }
983
984 args.SetTypeOfNextParameter(b);
985 switch (b) {
986 case SCC_ENCODED: {
988
989 char *p;
990 uint32_t stringid = std::strtoul(str, &p, 16);
991 if (*p != ':' && *p != '\0') {
992 while (*p != '\0') p++;
993 str = p;
994 builder += "(invalid SCC_ENCODED)";
995 break;
996 }
997 if (stringid >= TAB_SIZE_GAMESCRIPT) {
998 while (*p != '\0') p++;
999 str = p;
1000 builder += "(invalid StringID)";
1001 break;
1002 }
1003
1004 int i = 0;
1005 while (*p != '\0' && i < 20) {
1006 uint64_t param;
1007 const char *s = ++p;
1008
1009 /* Find the next value */
1010 bool instring = false;
1011 bool escape = false;
1012 for (;; p++) {
1013 if (*p == '\\') {
1014 escape = true;
1015 continue;
1016 }
1017 if (*p == '"' && escape) {
1018 escape = false;
1019 continue;
1020 }
1021 escape = false;
1022
1023 if (*p == '"') {
1024 instring = !instring;
1025 continue;
1026 }
1027 if (instring) {
1028 continue;
1029 }
1030
1031 if (*p == ':') break;
1032 if (*p == '\0') break;
1033 }
1034
1035 if (*s != '"') {
1036 /* Check if we want to look up another string */
1037 char32_t l;
1038 size_t len = Utf8Decode(&l, s);
1039 bool lookup = (l == SCC_ENCODED);
1040 if (lookup) s += len;
1041
1042 param = std::strtoull(s, &p, 16);
1043
1044 if (lookup) {
1045 if (param >= TAB_SIZE_GAMESCRIPT) {
1046 while (*p != '\0') p++;
1047 str = p;
1048 builder += "(invalid sub-StringID)";
1049 break;
1050 }
1052 }
1053
1054 sub_args.SetParam(i++, param);
1055 } else {
1056 s++; // skip the leading \"
1057 sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \".
1058 }
1059 }
1060 /* If we didn't error out, we can actually print the string. */
1061 if (*str != '\0') {
1062 str = p;
1063 GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true);
1064 }
1065 break;
1066 }
1067
1068 case SCC_NEWGRF_STRINL: {
1069 StringID substr = Utf8Consume(&str);
1070 const char *ptr = GetStringPtr(substr);
1071 if (ptr == nullptr) {
1072 builder += "(invalid NewGRF string)";
1073 } else {
1074 str_stack.push(ptr);
1075 }
1076 break;
1077 }
1078
1080 StringID substr = args.GetNextParameter<StringID>();
1081 const char *ptr = GetStringPtr(substr);
1082 if (ptr == nullptr) {
1083 builder += "(invalid NewGRF string)";
1084 } else {
1085 str_stack.push(ptr);
1086 }
1087 case_index = next_substr_case_index;
1088 next_substr_case_index = 0;
1089 break;
1090 }
1091
1092
1093 case SCC_GENDER_LIST: { // {G 0 Der Die Das}
1094 /* First read the meta data from the language file. */
1095 size_t offset = orig_offset + (uint8_t)*str++;
1096 int gender = 0;
1097 if (!dry_run && args.GetTypeAtOffset(offset) != 0) {
1098 /* Now we need to figure out what text to resolve, i.e.
1099 * what do we need to draw? So get the actual raw string
1100 * first using the control code to get said string. */
1101 char input[4 + 1];
1102 char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset));
1103 *p = '\0';
1104
1105 /* The gender is stored at the start of the formatted string. */
1106 bool old_sgd = _scan_for_gender_data;
1107 _scan_for_gender_data = true;
1108 std::string buffer;
1109 StringBuilder tmp_builder(buffer);
1110 StringParameters tmp_params = args.GetRemainingParameters(offset);
1111 FormatString(tmp_builder, input, tmp_params);
1112 _scan_for_gender_data = old_sgd;
1113
1114 /* And determine the string. */
1115 const char *s = buffer.c_str();
1116 char32_t c = Utf8Consume(&s);
1117 /* Does this string have a gender, if so, set it */
1118 if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0];
1119 }
1120 str = ParseStringChoice(str, gender, builder);
1121 break;
1122 }
1123
1124 /* This sets up the gender for the string.
1125 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1126 case SCC_GENDER_INDEX: // {GENDER 0}
1128 builder.Utf8Encode(SCC_GENDER_INDEX);
1129 builder += *str++;
1130 } else {
1131 str++;
1132 }
1133 break;
1134
1135 case SCC_PLURAL_LIST: { // {P}
1136 int plural_form = *str++; // contains the plural form for this string
1137 size_t offset = orig_offset + (uint8_t)*str++;
1138 const uint64_t *v = std::get_if<uint64_t>(&args.GetParam(offset)); // contains the number that determines plural
1139 if (v != nullptr) {
1140 str = ParseStringChoice(str, DeterminePluralForm(static_cast<int64_t>(*v), plural_form), builder);
1141 } else {
1142 builder += "(invalid PLURAL parameter)";
1143 }
1144 break;
1145 }
1146
1147 case SCC_ARG_INDEX: { // Move argument pointer
1148 args.SetOffset(orig_offset + (uint8_t)*str++);
1149 break;
1150 }
1151
1152 case SCC_SET_CASE: { // {SET_CASE}
1153 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1154 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1155 next_substr_case_index = (uint8_t)*str++;
1156 break;
1157 }
1158
1159 case SCC_SWITCH_CASE: { // {Used to implement case switching}
1160 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1161 * Each LEN is printed using 2 bytes in big endian order. */
1162 uint num = (uint8_t)*str++;
1163 while (num) {
1164 if ((uint8_t)str[0] == case_index) {
1165 /* Found the case, adjust str pointer and continue */
1166 str += 3;
1167 break;
1168 }
1169 /* Otherwise skip to the next case */
1170 str += 3 + (str[1] << 8) + str[2];
1171 num--;
1172 }
1173 break;
1174 }
1175
1176 case SCC_REVISION: // {REV}
1177 builder += _openttd_revision;
1178 break;
1179
1180 case SCC_RAW_STRING_POINTER: { // {RAW_STRING}
1181 const char *raw_string = args.GetNextParameterString();
1182 /* raw_string can be nullptr. */
1183 if (raw_string == nullptr) {
1184 builder += "(invalid RAW_STRING parameter)";
1185 break;
1186 }
1187 FormatString(builder, raw_string, args);
1188 break;
1189 }
1190
1191 case SCC_STRING: {// {STRING}
1192 StringID string_id = args.GetNextParameter<StringID>();
1193 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1194 /* It's prohibited for the included string to consume any arguments. */
1195 StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0);
1196 GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script);
1197 next_substr_case_index = 0;
1198 break;
1199 }
1200
1201 case SCC_STRING1:
1202 case SCC_STRING2:
1203 case SCC_STRING3:
1204 case SCC_STRING4:
1205 case SCC_STRING5:
1206 case SCC_STRING6:
1207 case SCC_STRING7: { // {STRING1..7}
1208 /* Strings that consume arguments */
1209 StringID string_id = args.GetNextParameter<StringID>();
1210 if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break;
1211 uint size = b - SCC_STRING1 + 1;
1212 if (size > args.GetDataLeft()) {
1213 builder += "(consumed too many parameters)";
1214 } else {
1215 StringParameters sub_args(args, game_script ? args.GetDataLeft() : size);
1216 GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script);
1217 args.AdvanceOffset(size);
1218 }
1219 next_substr_case_index = 0;
1220 break;
1221 }
1222
1223 case SCC_COMMA: // {COMMA}
1224 FormatCommaNumber(builder, args.GetNextParameter<int64_t>());
1225 break;
1226
1227 case SCC_DECIMAL: { // {DECIMAL}
1228 int64_t number = args.GetNextParameter<int64_t>();
1229 int digits = args.GetNextParameter<int>();
1230 if (digits == 0) {
1231 FormatCommaNumber(builder, number);
1232 break;
1233 }
1234
1235 int64_t divisor = PowerOfTen(digits);
1236 int64_t fractional = number % divisor;
1237 number /= divisor;
1238 FormatCommaNumber(builder, number);
1239 fmt::format_to(builder, "{}{:0{}d}", GetDecimalSeparator(), fractional, digits);
1240 break;
1241 }
1242
1243 case SCC_NUM: // {NUM}
1244 FormatNoCommaNumber(builder, args.GetNextParameter<int64_t>());
1245 break;
1246
1247 case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM}
1248 int64_t num = args.GetNextParameter<int64_t>();
1249 FormatZerofillNumber(builder, num, args.GetNextParameter<int>());
1250 break;
1251 }
1252
1253 case SCC_HEX: // {HEX}
1254 FormatHexNumber(builder, args.GetNextParameter<uint64_t>());
1255 break;
1256
1257 case SCC_BYTES: // {BYTES}
1258 FormatBytes(builder, args.GetNextParameter<int64_t>());
1259 break;
1260
1261 case SCC_CARGO_TINY: { // {CARGO_TINY}
1262 /* Tiny description of cargotypes. Layout:
1263 * param 1: cargo type
1264 * param 2: cargo count */
1265 CargoID cargo = args.GetNextParameter<CargoID>();
1266 if (cargo >= CargoSpec::GetArraySize()) break;
1267
1268 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1269 int64_t amount = 0;
1270 switch (cargo_str) {
1271 case STR_TONS:
1273 break;
1274
1275 case STR_LITERS:
1277 break;
1278
1279 default: {
1280 amount = args.GetNextParameter<int64_t>();
1281 break;
1282 }
1283 }
1284
1285 FormatCommaNumber(builder, amount);
1286 break;
1287 }
1288
1289 case SCC_CARGO_SHORT: { // {CARGO_SHORT}
1290 /* Short description of cargotypes. Layout:
1291 * param 1: cargo type
1292 * param 2: cargo count */
1293 CargoID cargo = args.GetNextParameter<CargoID>();
1294 if (cargo >= CargoSpec::GetArraySize()) break;
1295
1296 StringID cargo_str = CargoSpec::Get(cargo)->units_volume;
1297 switch (cargo_str) {
1298 case STR_TONS: {
1301 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1302 FormatString(builder, GetStringPtr(x.l), tmp_params);
1303 break;
1304 }
1305
1306 case STR_LITERS: {
1309 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1310 FormatString(builder, GetStringPtr(x.l), tmp_params);
1311 break;
1312 }
1313
1314 default: {
1315 auto tmp_params = MakeParameters(args.GetNextParameter<int64_t>());
1316 GetStringWithArgs(builder, cargo_str, tmp_params);
1317 break;
1318 }
1319 }
1320 break;
1321 }
1322
1323 case SCC_CARGO_LONG: { // {CARGO_LONG}
1324 /* First parameter is cargo type, second parameter is cargo count */
1325 CargoID cargo = args.GetNextParameter<CargoID>();
1326 if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break;
1327
1328 StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier;
1329 auto tmp_args = MakeParameters(args.GetNextParameter<int64_t>());
1330 GetStringWithArgs(builder, cargo_str, tmp_args);
1331 break;
1332 }
1333
1334 case SCC_CARGO_LIST: { // {CARGO_LIST}
1335 CargoTypes cmask = args.GetNextParameter<CargoTypes>();
1336 bool first = true;
1337
1338 std::string_view list_separator = GetListSeparator();
1339 for (const auto &cs : _sorted_cargo_specs) {
1340 if (!HasBit(cmask, cs->Index())) continue;
1341
1342 if (first) {
1343 first = false;
1344 } else {
1345 /* Add a comma if this is not the first item */
1346 builder += list_separator;
1347 }
1348
1349 GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script);
1350 }
1351
1352 /* If first is still true then no cargo is accepted */
1353 if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script);
1354
1355 next_substr_case_index = 0;
1356 break;
1357 }
1358
1359 case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT}
1360 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), true);
1361 break;
1362
1363 case SCC_CURRENCY_LONG: // {CURRENCY_LONG}
1364 FormatGenericCurrency(builder, &GetCurrency(), args.GetNextParameter<int64_t>(), false);
1365 break;
1366
1367 case SCC_DATE_TINY: // {DATE_TINY}
1368 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_TINY);
1369 break;
1370
1371 case SCC_DATE_SHORT: // {DATE_SHORT}
1372 FormatMonthAndYear(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1373 next_substr_case_index = 0;
1374 break;
1375
1376 case SCC_DATE_LONG: // {DATE_LONG}
1377 FormatYmdString(builder, args.GetNextParameter<TimerGameCalendar::Date>(), next_substr_case_index);
1378 next_substr_case_index = 0;
1379 break;
1380
1381 case SCC_DATE_ISO: // {DATE_ISO}
1382 FormatTinyOrISODate(builder, args.GetNextParameter<TimerGameCalendar::Date>(), STR_FORMAT_DATE_ISO);
1383 break;
1384
1385 case SCC_FORCE: { // {FORCE}
1388 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1389 FormatString(builder, GetStringPtr(x.s), tmp_params);
1390 break;
1391 }
1392
1393 case SCC_HEIGHT: { // {HEIGHT}
1396 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1397 FormatString(builder, GetStringPtr(x.s), tmp_params);
1398 break;
1399 }
1400
1401 case SCC_POWER: { // {POWER}
1404 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1405 FormatString(builder, GetStringPtr(x.s), tmp_params);
1406 break;
1407 }
1408
1409 case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT}
1411 assert(setting < lengthof(_units_power_to_weight));
1412 const auto &x = _units_power_to_weight[setting];
1413 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1414 FormatString(builder, GetStringPtr(x.s), tmp_params);
1415 break;
1416 }
1417
1418 case SCC_VELOCITY: { // {VELOCITY}
1419 int64_t arg = args.GetNextParameter<int64_t>();
1420 // Unpack vehicle type from packed argument to get desired units.
1421 VehicleType vt = static_cast<VehicleType>(GB(arg, 56, 8));
1422 const auto &x = GetVelocityUnits(vt);
1423 auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places);
1424 FormatString(builder, GetStringPtr(x.s), tmp_params);
1425 break;
1426 }
1427
1428 case SCC_VOLUME_SHORT: { // {VOLUME_SHORT}
1431 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1432 FormatString(builder, GetStringPtr(x.s), tmp_params);
1433 break;
1434 }
1435
1436 case SCC_VOLUME_LONG: { // {VOLUME_LONG}
1439 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1440 FormatString(builder, GetStringPtr(x.l), tmp_params);
1441 break;
1442 }
1443
1444 case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT}
1447 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1448 FormatString(builder, GetStringPtr(x.s), tmp_params);
1449 break;
1450 }
1451
1452 case SCC_WEIGHT_LONG: { // {WEIGHT_LONG}
1455 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1456 FormatString(builder, GetStringPtr(x.l), tmp_params);
1457 break;
1458 }
1459
1460 case SCC_UNITS_DAYS_OR_SECONDS: { // {UNITS_DAYS_OR_SECONDS}
1461 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1462 const auto &x = _units_time_days_or_seconds[realtime];
1463 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1464 FormatString(builder, GetStringPtr(x.s), tmp_params);
1465 break;
1466 }
1467
1468 case SCC_UNITS_MONTHS_OR_MINUTES: { // {UNITS_MONTHS_OR_MINUTES}
1469 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1470 const auto &x = _units_time_months_or_minutes[realtime];
1471 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1472 FormatString(builder, GetStringPtr(x.s), tmp_params);
1473 break;
1474 }
1475
1476 case SCC_UNITS_YEARS_OR_PERIODS: { // {UNITS_YEARS_OR_PERIODS}
1477 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1478 const auto &x = _units_time_years_or_periods[realtime];
1479 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1480 FormatString(builder, GetStringPtr(x.s), tmp_params);
1481 break;
1482 }
1483
1484 case SCC_UNITS_YEARS_OR_MINUTES: { // {UNITS_YEARS_OR_MINUTES}
1485 uint8_t realtime = TimerGameEconomy::UsingWallclockUnits(_game_mode == GM_MENU);
1486 const auto &x = _units_time_years_or_minutes[realtime];
1487 auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter<int64_t>()), x.decimal_places);
1488 FormatString(builder, GetStringPtr(x.s), tmp_params);
1489 break;
1490 }
1491
1492 case SCC_COMPANY_NAME: { // {COMPANY}
1494 if (c == nullptr) break;
1495
1496 if (!c->name.empty()) {
1497 auto tmp_params = MakeParameters(c->name);
1498 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1499 } else {
1500 auto tmp_params = MakeParameters(c->name_2);
1501 GetStringWithArgs(builder, c->name_1, tmp_params);
1502 }
1503 break;
1504 }
1505
1506 case SCC_COMPANY_NUM: { // {COMPANY_NUM}
1507 CompanyID company = args.GetNextParameter<CompanyID>();
1508
1509 /* Nothing is added for AI or inactive companies */
1510 if (Company::IsValidHumanID(company)) {
1511 auto tmp_params = MakeParameters(company + 1);
1512 GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params);
1513 }
1514 break;
1515 }
1516
1517 case SCC_DEPOT_NAME: { // {DEPOT}
1519 if (vt == VEH_AIRCRAFT) {
1520 auto tmp_params = MakeParameters(args.GetNextParameter<StationID>());
1521 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params);
1522 break;
1523 }
1524
1525 const Depot *d = Depot::Get(args.GetNextParameter<DepotID>());
1526 if (!d->name.empty()) {
1527 auto tmp_params = MakeParameters(d->name);
1528 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1529 } else {
1530 auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1);
1531 GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params);
1532 }
1533 break;
1534 }
1535
1536 case SCC_ENGINE_NAME: { // {ENGINE}
1537 int64_t arg = args.GetNextParameter<int64_t>();
1538 const Engine *e = Engine::GetIfValid(static_cast<EngineID>(arg));
1539 if (e == nullptr) break;
1540
1541 if (!e->name.empty() && e->IsEnabled()) {
1542 auto tmp_params = MakeParameters(e->name);
1543 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1544 break;
1545 }
1546
1547 if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) {
1548 uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast<uint32_t>(arg >> 32), 0, e->index, nullptr);
1549 /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */
1550 if (callback != CALLBACK_FAILED && callback < 0x400) {
1551 const GRFFile *grffile = e->GetGRF();
1552 assert(grffile != nullptr);
1553
1554 StartTextRefStackUsage(grffile, 6);
1555 ArrayStringParameters<6> tmp_params;
1556 GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params);
1558
1559 break;
1560 }
1561 }
1562
1563 auto tmp_params = ArrayStringParameters<0>();
1564 GetStringWithArgs(builder, e->info.string_id, tmp_params);
1565 break;
1566 }
1567
1568 case SCC_GROUP_NAME: { // {GROUP}
1569 const Group *g = Group::GetIfValid(args.GetNextParameter<GroupID>());
1570 if (g == nullptr) break;
1571
1572 if (!g->name.empty()) {
1573 auto tmp_params = MakeParameters(g->name);
1574 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1575 } else {
1576 auto tmp_params = MakeParameters(g->number);
1577 GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params);
1578 }
1579 break;
1580 }
1581
1582 case SCC_INDUSTRY_NAME: { // {INDUSTRY}
1583 const Industry *i = Industry::GetIfValid(args.GetNextParameter<IndustryID>());
1584 if (i == nullptr) break;
1585
1586 static bool use_cache = true;
1588 /* Gender is defined by the industry type.
1589 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1590 auto tmp_params = ArrayStringParameters<0>();
1591 FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index);
1592 } else if (use_cache) { // Use cached version if first call
1593 AutoRestoreBackup cache_backup(use_cache, false);
1594 builder += i->GetCachedName();
1595 } else {
1596 /* First print the town name and the industry type name. */
1597 auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name);
1598 FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index);
1599 }
1600 next_substr_case_index = 0;
1601 break;
1602 }
1603
1604 case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME}
1606 if (c == nullptr) break;
1607
1608 if (!c->president_name.empty()) {
1609 auto tmp_params = MakeParameters(c->president_name);
1610 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1611 } else {
1612 auto tmp_params = MakeParameters(c->president_name_2);
1613 GetStringWithArgs(builder, c->president_name_1, tmp_params);
1614 }
1615 break;
1616 }
1617
1618 case SCC_STATION_NAME: { // {STATION}
1619 StationID sid = args.GetNextParameter<StationID>();
1620 const Station *st = Station::GetIfValid(sid);
1621
1622 if (st == nullptr) {
1623 /* The station doesn't exist anymore. The only place where we might
1624 * be "drawing" an invalid station is in the case of cargo that is
1625 * in transit. */
1626 auto tmp_params = ArrayStringParameters<0>();
1627 GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params);
1628 break;
1629 }
1630
1631 static bool use_cache = true;
1632 if (use_cache) { // Use cached version if first call
1633 AutoRestoreBackup cache_backup(use_cache, false);
1634 builder += st->GetCachedName();
1635 } else if (!st->name.empty()) {
1636 auto tmp_params = MakeParameters(st->name);
1637 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1638 } else {
1639 StringID string_id = st->string_id;
1640 if (st->indtype != IT_INVALID) {
1641 /* Special case where the industry provides the name for the station */
1642 const IndustrySpec *indsp = GetIndustrySpec(st->indtype);
1643
1644 /* Industry GRFs can change which might remove the station name and
1645 * thus cause very strange things. Here we check for that before we
1646 * actually set the station name. */
1647 if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) {
1648 string_id = indsp->station_name;
1649 }
1650 }
1651
1652 auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index);
1653 GetStringWithArgs(builder, string_id, tmp_params);
1654 }
1655 break;
1656 }
1657
1658 case SCC_TOWN_NAME: { // {TOWN}
1659 const Town *t = Town::GetIfValid(args.GetNextParameter<TownID>());
1660 if (t == nullptr) break;
1661
1662 static bool use_cache = true;
1663 if (use_cache) { // Use cached version if first call
1664 AutoRestoreBackup cache_backup(use_cache, false);
1665 builder += t->GetCachedName();
1666 } else if (!t->name.empty()) {
1667 auto tmp_params = MakeParameters(t->name);
1668 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1669 } else {
1670 GetTownName(builder, t);
1671 }
1672 break;
1673 }
1674
1675 case SCC_WAYPOINT_NAME: { // {WAYPOINT}
1676 Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter<StationID>());
1677 if (wp == nullptr) break;
1678
1679 if (!wp->name.empty()) {
1680 auto tmp_params = MakeParameters(wp->name);
1681 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1682 } else {
1683 auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1);
1684 StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME);
1685 if (wp->town_cn != 0) string_id++;
1686 GetStringWithArgs(builder, string_id, tmp_params);
1687 }
1688 break;
1689 }
1690
1691 case SCC_VEHICLE_NAME: { // {VEHICLE}
1693 if (v == nullptr) break;
1694
1695 if (!v->name.empty()) {
1696 auto tmp_params = MakeParameters(v->name);
1697 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1698 } else if (v->group_id != DEFAULT_GROUP) {
1699 /* The vehicle has no name, but is member of a group, so print group name */
1700 auto tmp_params = MakeParameters(v->group_id, v->unitnumber);
1701 GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params);
1702 } else {
1703 auto tmp_params = MakeParameters(v->unitnumber);
1704
1705 StringID string_id;
1706 switch (v->type) {
1707 default: string_id = STR_INVALID_VEHICLE; break;
1708 case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break;
1709 case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break;
1710 case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break;
1711 case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break;
1712 }
1713
1714 GetStringWithArgs(builder, string_id, tmp_params);
1715 }
1716 break;
1717 }
1718
1719 case SCC_SIGN_NAME: { // {SIGN}
1720 const Sign *si = Sign::GetIfValid(args.GetNextParameter<SignID>());
1721 if (si == nullptr) break;
1722
1723 if (!si->name.empty()) {
1724 auto tmp_params = MakeParameters(si->name);
1725 GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params);
1726 } else {
1727 auto tmp_params = ArrayStringParameters<0>();
1728 GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params);
1729 }
1730 break;
1731 }
1732
1733 case SCC_STATION_FEATURES: { // {STATIONFEATURES}
1734 StationGetSpecialString(builder, args.GetNextParameter<StationFacility>());
1735 break;
1736 }
1737
1738 case SCC_COLOUR: { // {COLOUR}
1739 StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter<Colours>());
1740 if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc);
1741 break;
1742 }
1743
1744 default:
1745 builder.Utf8Encode(b);
1746 break;
1747 }
1748 } catch (std::out_of_range &e) {
1749 Debug(misc, 0, "FormatString: {}", e.what());
1750 builder += "(invalid parameter)";
1751 }
1752 }
1753}
1754
1755
1756static void StationGetSpecialString(StringBuilder &builder, StationFacility x)
1757{
1758 if ((x & FACIL_TRAIN) != 0) builder.Utf8Encode(SCC_TRAIN);
1759 if ((x & FACIL_TRUCK_STOP) != 0) builder.Utf8Encode(SCC_LORRY);
1760 if ((x & FACIL_BUS_STOP) != 0) builder.Utf8Encode(SCC_BUS);
1761 if ((x & FACIL_DOCK) != 0) builder.Utf8Encode(SCC_SHIP);
1762 if ((x & FACIL_AIRPORT) != 0) builder.Utf8Encode(SCC_PLANE);
1763}
1764
1765static void GetSpecialTownNameString(StringBuilder &builder, int ind, uint32_t seed)
1766{
1767 GenerateTownNameString(builder, ind, seed);
1768}
1769
1770static const char * const _silly_company_names[] = {
1771 "Bloggs Brothers",
1772 "Tiny Transport Ltd.",
1773 "Express Travel",
1774 "Comfy-Coach & Co.",
1775 "Crush & Bump Ltd.",
1776 "Broken & Late Ltd.",
1777 "Sam Speedy & Son",
1778 "Supersonic Travel",
1779 "Mike's Motors",
1780 "Lightning International",
1781 "Pannik & Loozit Ltd.",
1782 "Inter-City Transport",
1783 "Getout & Pushit Ltd."
1784};
1785
1786static const char * const _surname_list[] = {
1787 "Adams",
1788 "Allan",
1789 "Baker",
1790 "Bigwig",
1791 "Black",
1792 "Bloggs",
1793 "Brown",
1794 "Campbell",
1795 "Gordon",
1796 "Hamilton",
1797 "Hawthorn",
1798 "Higgins",
1799 "Green",
1800 "Gribble",
1801 "Jones",
1802 "McAlpine",
1803 "MacDonald",
1804 "McIntosh",
1805 "Muir",
1806 "Murphy",
1807 "Nelson",
1808 "O'Donnell",
1809 "Parker",
1810 "Phillips",
1811 "Pilkington",
1812 "Quigley",
1813 "Sharkey",
1814 "Thomson",
1815 "Watkins"
1816};
1817
1818static const char * const _silly_surname_list[] = {
1819 "Grumpy",
1820 "Dozy",
1821 "Speedy",
1822 "Nosey",
1823 "Dribble",
1824 "Mushroom",
1825 "Cabbage",
1826 "Sniffle",
1827 "Fishy",
1828 "Swindle",
1829 "Sneaky",
1830 "Nutkins"
1831};
1832
1833static const char _initial_name_letters[] = {
1834 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1835 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1836};
1837
1838static std::span<const char * const> GetSurnameOptions()
1839{
1840 if (_settings_game.game_creation.landscape == LT_TOYLAND) return _silly_surname_list;
1841 return _surname_list;
1842}
1843
1849static const char *GetSurname(uint32_t seed)
1850{
1851 auto surname_options = GetSurnameOptions();
1852 return surname_options[surname_options.size() * GB(seed, 16, 8) >> 8];
1853}
1854
1855static void GenAndCoName(StringBuilder &builder, uint32_t seed)
1856{
1857 builder += GetSurname(seed);
1858 builder += " & Co.";
1859}
1860
1861static void GenPresidentName(StringBuilder &builder, uint32_t seed)
1862{
1863 builder += _initial_name_letters[std::size(_initial_name_letters) * GB(seed, 0, 8) >> 8];
1864 builder += ". ";
1865
1866 /* The second initial is optional. */
1867 size_t index = (std::size(_initial_name_letters) + 35) * GB(seed, 8, 8) >> 8;
1868 if (index < std::size(_initial_name_letters)) {
1869 builder += _initial_name_letters[index];
1870 builder += ". ";
1871 }
1872
1873 builder += GetSurname(seed);
1874}
1875
1876static void GetSpecialNameString(StringBuilder &builder, int ind, StringParameters &args)
1877{
1878 switch (ind) {
1879 case 1: // not used
1880 builder += _silly_company_names[std::min<size_t>(args.GetNextParameter<uint16_t>(), std::size(_silly_company_names) - 1)];
1881 return;
1882
1883 case 2: // used for Foobar & Co company names
1884 GenAndCoName(builder, args.GetNextParameter<uint32_t>());
1885 return;
1886
1887 case 3: // President name
1888 GenPresidentName(builder, args.GetNextParameter<uint32_t>());
1889 return;
1890 }
1891
1892 /* town name? */
1893 if (IsInsideMM(ind - 6, 0, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1)) {
1894 GetSpecialTownNameString(builder, ind - 6, args.GetNextParameter<uint32_t>());
1895 builder += " Transport";
1896 return;
1897 }
1898
1899 NOT_REACHED();
1900}
1901
1907{
1908 return this->ident == TO_LE32(LanguagePackHeader::IDENT) &&
1909 this->version == TO_LE32(LANGUAGE_PACK_VERSION) &&
1910 this->plural_form < LANGUAGE_MAX_PLURAL &&
1911 this->text_dir <= 1 &&
1912 this->newgrflangid < MAX_LANG &&
1913 this->num_genders < MAX_NUM_GENDERS &&
1914 this->num_cases < MAX_NUM_CASES &&
1915 StrValid(this->name) &&
1916 StrValid(this->own_name) &&
1917 StrValid(this->isocode) &&
1921}
1922
1927{
1928 /* "Less than 25% missing" is "sufficiently finished". */
1929 return 4 * this->missing < LANGUAGE_TOTAL_STRINGS;
1930}
1931
1938{
1939 /* Current language pack */
1940 size_t len = 0;
1941 std::unique_ptr<LanguagePack, LanguagePackDeleter> lang_pack(reinterpret_cast<LanguagePack *>(ReadFileToMem(FS2OTTD(lang->file), len, 1U << 20).release()));
1942 if (!lang_pack) return false;
1943
1944 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1945 const char *end = (char *)lang_pack.get() + len + 1;
1946
1947 /* We need at least one byte of lang_pack->data */
1948 if (end <= lang_pack->data || !lang_pack->IsValid()) {
1949 return false;
1950 }
1951
1952 std::array<uint, TEXT_TAB_END> tab_start, tab_num;
1953
1954 uint count = 0;
1955 for (uint i = 0; i < TEXT_TAB_END; i++) {
1956 uint16_t num = FROM_LE16(lang_pack->offsets[i]);
1957 if (num > TAB_SIZE) return false;
1958
1959 tab_start[i] = count;
1960 tab_num[i] = num;
1961 count += num;
1962 }
1963
1964 /* Allocate offsets */
1965 std::vector<char *> offs(count);
1966
1967 /* Fill offsets */
1968 char *s = lang_pack->data;
1969 len = (uint8_t)*s++;
1970 for (uint i = 0; i < count; i++) {
1971 if (s + len >= end) return false;
1972
1973 if (len >= 0xC0) {
1974 len = ((len & 0x3F) << 8) + (uint8_t)*s++;
1975 if (s + len >= end) return false;
1976 }
1977 offs[i] = s;
1978 s += len;
1979 len = (uint8_t)*s;
1980 *s++ = '\0'; // zero terminate the string
1981 }
1982
1983 _langpack.langpack = std::move(lang_pack);
1984 _langpack.offsets = std::move(offs);
1985 _langpack.langtab_num = tab_num;
1986 _langpack.langtab_start = tab_start;
1987
1988 _current_language = lang;
1992 _langpack.list_separator = GetString(STR_LIST_SEPARATOR);
1993
1994#ifdef _WIN32
1995 extern void Win32SetCurrentLocaleName(std::string iso_code);
1996 Win32SetCurrentLocaleName(_current_language->isocode);
1997#endif
1998
1999#ifdef WITH_COCOA
2000 extern void MacOSSetCurrentLocaleName(const char *iso_code);
2002#endif
2003
2004#ifdef WITH_ICU_I18N
2005 /* Create a collator instance for our current locale. */
2006 UErrorCode status = U_ZERO_ERROR;
2007 _current_collator.reset(icu::Collator::createInstance(icu::Locale(_current_language->isocode), status));
2008 /* Sort number substrings by their numerical value. */
2009 if (_current_collator) _current_collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, status);
2010 /* Avoid using the collator if it is not correctly set. */
2011 if (U_FAILURE(status)) {
2012 _current_collator.reset();
2013 }
2014#endif /* WITH_ICU_I18N */
2015
2017
2018 /* Some lists need to be sorted again after a language change. */
2024 InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Build vehicle window.
2025 InvalidateWindowClassesData(WC_TRAINS_LIST); // Train group window.
2026 InvalidateWindowClassesData(WC_ROADVEH_LIST); // Road vehicle group window.
2027 InvalidateWindowClassesData(WC_SHIPS_LIST); // Ship group window.
2028 InvalidateWindowClassesData(WC_AIRCRAFT_LIST); // Aircraft group window.
2029 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY); // Industry directory window.
2030 InvalidateWindowClassesData(WC_STATION_LIST); // Station list window.
2031
2032 return true;
2033}
2034
2035/* Win32 implementation in win32.cpp.
2036 * OS X implementation in os/macosx/macos.mm. */
2037#if !(defined(_WIN32) || defined(__APPLE__))
2046const char *GetCurrentLocale(const char *param)
2047{
2048 const char *env;
2049
2050 env = std::getenv("LANGUAGE");
2051 if (env != nullptr) return env;
2052
2053 env = std::getenv("LC_ALL");
2054 if (env != nullptr) return env;
2055
2056 if (param != nullptr) {
2057 env = std::getenv(param);
2058 if (env != nullptr) return env;
2059 }
2060
2061 return std::getenv("LANG");
2062}
2063#else
2064const char *GetCurrentLocale(const char *param);
2065#endif /* !(defined(_WIN32) || defined(__APPLE__)) */
2066
2072const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
2073{
2074 for (const LanguageMetadata &lang : _languages) {
2075 if (newgrflangid == lang.newgrflangid) return &lang;
2076 }
2077
2078 return nullptr;
2079}
2080
2087static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
2088{
2089 auto f = FileHandle::Open(file, "rb");
2090 if (!f.has_value()) return false;
2091
2092 size_t read = fread(hdr, sizeof(*hdr), 1, *f);
2093
2094 bool ret = read == 1 && hdr->IsValid();
2095
2096 /* Convert endianness for the windows language ID */
2097 if (ret) {
2098 hdr->missing = FROM_LE16(hdr->missing);
2099 hdr->winlangid = FROM_LE16(hdr->winlangid);
2100 }
2101 return ret;
2102}
2103
2108static void FillLanguageList(const std::string &path)
2109{
2110 std::error_code error_code;
2111 for (const auto &dir_entry : std::filesystem::directory_iterator(OTTD2FS(path), error_code)) {
2112 if (!dir_entry.is_regular_file()) continue;
2113 if (dir_entry.path().extension() != ".lng") continue;
2114
2115 LanguageMetadata lmd;
2116 lmd.file = dir_entry.path();
2117
2118 /* Check whether the file is of the correct version */
2119 if (!GetLanguageFileHeader(FS2OTTD(lmd.file), &lmd)) {
2120 Debug(misc, 3, "{} is not a valid language file", FS2OTTD(lmd.file));
2121 } else if (GetLanguage(lmd.newgrflangid) != nullptr) {
2122 Debug(misc, 3, "{}'s language ID is already known", FS2OTTD(lmd.file));
2123 } else {
2124 _languages.push_back(lmd);
2125 }
2126 }
2127 if (error_code) {
2128 Debug(misc, 9, "Unable to open directory {}: {}", path, error_code.message());
2129 }
2130}
2131
2137{
2138 for (Searchpath sp : _valid_searchpaths) {
2139 FillLanguageList(FioGetDirectory(sp, LANG_DIR));
2140 }
2141 if (_languages.empty()) UserError("No available language packs (invalid versions?)");
2142
2143 /* Acquire the locale of the current system */
2144 const char *lang = GetCurrentLocale("LC_MESSAGES");
2145 if (lang == nullptr) lang = "en_GB";
2146
2147 const LanguageMetadata *chosen_language = nullptr;
2148 const LanguageMetadata *language_fallback = nullptr;
2149 const LanguageMetadata *en_GB_fallback = _languages.data();
2150
2151 /* Find a proper language. */
2152 for (const LanguageMetadata &lng : _languages) {
2153 /* We are trying to find a default language. The priority is by
2154 * configuration file, local environment and last, if nothing found,
2155 * English. */
2156 if (_config_language_file == FS2OTTD(lng.file.filename())) {
2157 chosen_language = &lng;
2158 break;
2159 }
2160
2161 if (strcmp (lng.isocode, "en_GB") == 0) en_GB_fallback = &lng;
2162
2163 /* Only auto-pick finished translations */
2164 if (!lng.IsReasonablyFinished()) continue;
2165
2166 if (strncmp(lng.isocode, lang, 5) == 0) chosen_language = &lng;
2167 if (strncmp(lng.isocode, lang, 2) == 0) language_fallback = &lng;
2168 }
2169
2170 /* We haven't found the language in the config nor the one in the locale.
2171 * Now we set it to one of the fallback languages */
2172 if (chosen_language == nullptr) {
2173 chosen_language = (language_fallback != nullptr) ? language_fallback : en_GB_fallback;
2174 }
2175
2176 if (!ReadLanguagePack(chosen_language)) UserError("Can't read language pack '{}'", FS2OTTD(chosen_language->file));
2177}
2178
2184{
2185 return _langpack.langpack->isocode;
2186}
2187
2193{
2194 InitFontCache(this->Monospace());
2195
2196 this->Reset();
2197 for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2198 auto src = text->cbegin();
2199
2200 FontSize size = this->DefaultSize();
2201 FontCache *fc = FontCache::Get(size);
2202 while (src != text->cend()) {
2203 char32_t c = Utf8Consume(src);
2204
2205 if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2206 size = (FontSize)(c - SCC_FIRST_FONT);
2207 fc = FontCache::Get(size);
2208 } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2209 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2210 std::string size_name;
2211
2212 switch (size) {
2213 case FS_NORMAL: size_name = "medium"; break;
2214 case FS_SMALL: size_name = "small"; break;
2215 case FS_LARGE: size_name = "large"; break;
2216 case FS_MONO: size_name = "mono"; break;
2217 default: NOT_REACHED();
2218 }
2219
2220 Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
2221 return true;
2222 }
2223 }
2224 }
2225 return false;
2226}
2227
2230 uint i;
2231 uint j;
2232
2233 void Reset() override
2234 {
2235 this->i = 0;
2236 this->j = 0;
2237 }
2238
2240 {
2241 return FS_NORMAL;
2242 }
2243
2244 std::optional<std::string_view> NextString() override
2245 {
2246 if (this->i >= TEXT_TAB_END) return std::nullopt;
2247
2248 const char *ret = _langpack.offsets[_langpack.langtab_start[this->i] + this->j];
2249
2250 this->j++;
2251 while (this->i < TEXT_TAB_END && this->j >= _langpack.langtab_num[this->i]) {
2252 this->i++;
2253 this->j = 0;
2254 }
2255
2256 return ret;
2257 }
2258
2259 bool Monospace() override
2260 {
2261 return false;
2262 }
2263
2264 void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] const char *font_name, [[maybe_unused]] const void *os_data) override
2265 {
2266#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2267 settings->small.font = font_name;
2268 settings->medium.font = font_name;
2269 settings->large.font = font_name;
2270
2271 settings->small.os_handle = os_data;
2272 settings->medium.os_handle = os_data;
2273 settings->large.os_handle = os_data;
2274#endif
2275 }
2276};
2277
2291void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
2292{
2293 static LanguagePackGlyphSearcher pack_searcher;
2294 if (searcher == nullptr) searcher = &pack_searcher;
2295 bool bad_font = !base_font || searcher->FindMissingGlyphs();
2296#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
2297 if (bad_font) {
2298 /* We found an unprintable character... lets try whether we can find
2299 * a fallback font that can print the characters in the current language. */
2300 bool any_font_configured = !_fcsettings.medium.font.empty();
2301 FontCacheSettings backup = _fcsettings;
2302
2303 _fcsettings.mono.os_handle = nullptr;
2304 _fcsettings.medium.os_handle = nullptr;
2305
2306 bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, _langpack.langpack->winlangid, searcher);
2307
2308 _fcsettings = backup;
2309
2310 if (!bad_font && any_font_configured) {
2311 /* If the user configured a bad font, and we found a better one,
2312 * show that we loaded the better font instead of the configured one.
2313 * The colour 'character' might change in the
2314 * future, so for safety we just Utf8 Encode it into the string,
2315 * which takes exactly three characters, so it replaces the "XXX"
2316 * with the colour marker. */
2317 static std::string err_str("XXXThe current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
2318 Utf8Encode(err_str.data(), SCC_YELLOW);
2319 SetDParamStr(0, err_str);
2320 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2321 }
2322
2323 if (bad_font && base_font) {
2324 /* Our fallback font does miss characters too, so keep the
2325 * user chosen font as that is more likely to be any good than
2326 * the wild guess we made */
2327 InitFontCache(searcher->Monospace());
2328 }
2329 }
2330#endif
2331
2332 if (bad_font) {
2333 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2334 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2335 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2336 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2337 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2338 static std::string err_str("XXXThe current font is missing some of the characters used in the texts for this language. Read the readme to see how to solve this.");
2339 Utf8Encode(err_str.data(), SCC_YELLOW);
2340 SetDParamStr(0, err_str);
2341 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_WARNING);
2342
2343 /* Reset the font width */
2344 LoadStringWidthTable(searcher->Monospace());
2345 return;
2346 }
2347
2348 /* Update the font with cache */
2349 LoadStringWidthTable(searcher->Monospace());
2350
2351#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
2352 /*
2353 * For right-to-left languages we need the ICU library. If
2354 * we do not have support for that library we warn the user
2355 * about it with a message. As we do not want the string to
2356 * be translated by the translators, we 'force' it into the
2357 * binary and 'load' it via a BindCString. To do this
2358 * properly we have to set the colour of the string,
2359 * otherwise we end up with a lot of artifacts. The colour
2360 * 'character' might change in the future, so for safety
2361 * we just Utf8 Encode it into the string, which takes
2362 * exactly three characters, so it replaces the "XXX" with
2363 * the colour marker.
2364 */
2365 if (_current_text_dir != TD_LTR) {
2366 static std::string err_str("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with ICU + Harfbuzz enabled.");
2367 Utf8Encode(err_str.data(), SCC_YELLOW);
2368 SetDParamStr(0, err_str);
2369 ShowErrorMessage(STR_JUST_RAW_STRING, INVALID_STRING_ID, WL_ERROR);
2370 }
2371#endif /* !(WITH_ICU_I18N && WITH_HARFBUZZ) && !WITH_UNISCRIBE && !WITH_COCOA */
2372}
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 CargoID
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:22
bool IsValidCargoID(CargoID t)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:107
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
void InitializeSortedCargoSpecs()
Initialize the list of sorted cargo specifications.
Extension of StringParameters with its own statically sized buffer for the parameters.
static std::optional< FileHandle > Open(const std::string &filename, const std::string &mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1170
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:2229
bool Monospace() override
Whether to search for a monospace font or not.
Definition strings.cpp:2259
uint j
Iterator for the secondary language tables.
Definition strings.cpp:2231
std::optional< std::string_view > NextString() override
Get the next string to search through.
Definition strings.cpp:2244
FontSize DefaultSize() override
Get the default (font) size of the string.
Definition strings.cpp:2239
void SetFontNames(FontCacheSettings *settings, const char *font_name, const void *os_data) override
Set the right font names.
Definition strings.cpp:2264
uint i
Iterator for the primary language tables.
Definition strings.cpp:2230
void Reset() override
Reset the search, i.e.
Definition strings.cpp:2233
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:2192
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.
Equivalent to the std::back_insert_iterator in function, with some convenience helpers for string con...
void Utf8Encode(char32_t c)
Encode the given Utf8 character into the output buffer.
void SetOffset(size_t offset)
Set the offset within the string from where to return the next result of GetInt64 or GetInt32.
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.
void PrepareForNextRun()
Prepare the string parameters for the next formatting run.
Definition strings.cpp:68
const StringParameter & GetNextParameterReference()
Get the next parameter from our parameters.
Definition strings.cpp:81
void AdvanceOffset(size_t advance)
Advance the offset within the string from where to return the next result of GetInt64 or GetInt32.
size_t offset
Current offset in the parameters span.
T GetNextParameter()
Get the next parameter from our parameters.
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...
const char * GetNextParameterString()
Get the next string parameter from our 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.
Definition of stuff that is very close to a company, like the company struct itself.
Owner
Enum for all companies/owners.
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_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
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
Definition currency.h:117
Functions related to debugging.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
Base for all depots (except hangars)
uint16_t DepotID
Type for the unique identifier of depots.
Definition depot_type.h:13
Function to handling different endian machines.
Base class for engines.
uint16_t EngineID
Unique identification number of an engine.
Definition engine_type.h:21
Functions related to errors.
void ShowErrorMessage(StringID summary_msg, int x, int y, CommandCost cc)
Display an error message in a window.
@ WL_WARNING
Other information.
Definition error.h:25
@ WL_ERROR
Errors (eg. saving/loading failed)
Definition error.h:26
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:1025
Functions for Standard In/Out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
@ LANG_DIR
Subdirectory for all translation files.
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, int winlangid, 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.
const char * GetGameStringPtr(uint id)
Get the string pointer of a particular game string.
Base functions regarding game texts.
void GetBroadestDigit(uint *front, uint *next, FontSize size)
Determine the broadest digits for guessing the maximum width of a n-digit number.
Definition gfx.cpp:1255
void LoadStringWidthTable(bool monospace)
Initialize _stringwidth_table cache.
Definition gfx.cpp:1210
Functions related to laying out the texts.
FontSize
Available font sizes.
Definition gfx_type.h:208
@ FS_MONO
Index of the monospaced font in the font tables.
Definition gfx_type.h:212
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:210
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:209
@ FS_LARGE
Index of the large font in the font tables.
Definition gfx_type.h:211
uint16_t GroupID
Type for all group identifiers.
Definition group_type.h:13
static const GroupID DEFAULT_GROUP
Ungrouped vehicles are in this group.
Definition group_type.h:17
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 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.
constexpr bool IsInsideMM(const T x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
void BuildContentTypeStringList()
Build array of all strings corresponding to the content types.
User interface for downloading files.
@ CBID_VEHICLE_NAME
Called to determine the engine name to show.
@ CBM_VEHICLE_NAME
Engine name.
static const uint CALLBACK_FAILED
Different values for Callback result evaluations.
uint16_t GetVehicleCallback(CallbackID callback, uint32_t param1, uint32_t param2, EngineID engine, const Vehicle *v)
Evaluate a newgrf callback for vehicles.
Functions for NewGRF engines.
struct TextRefStack * CreateTextRefStackBackup()
Create a backup of the current NewGRF text stack.
char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str, StringParameters &parameters, bool modify_parameters)
FormatString for NewGRF specific "magic" string control codes.
void SetCurrentGrfLangID(uint8_t language_id)
Equivalence Setter function between game and newgrf langID.
void StartTextRefStackUsage(const GRFFile *grffile, uint8_t numEntries, const uint32_t *values)
Start using the TTDP compatible string code parsing.
const char * GetGRFStringPtr(uint32_t stringid)
Get a C-string from a stringid set by a newgrf.
void RestoreTextRefStackBackup(struct TextRefStack *backup)
Restore a copy of the text stack to the used stack.
bool UsingNewGRFTextStack()
Check whether the NewGRF text stack is in use.
StringID GetGRFStringID(uint32_t grfid, StringID stringid)
Returns the index for this stringid associated with its grfID.
void StopTextRefStackUsage()
Stop using the TTDP compatible string code parsing.
Header of Action 04 "universal holder" structure and functions.
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:57
Base class for signs.
uint16_t SignID
The type of the IDs of signs.
Definition signs_type.h:14
void BuildIndustriesLegend()
Fills an array for the industries legends.
Smallmap GUI functions.
Base classes/functions for stations.
StationFacility
The facilities a station might be having.
@ FACIL_DOCK
Station with a dock.
@ FACIL_BUS_STOP
Station with bus stops.
@ FACIL_AIRPORT
Station with an airport.
@ FACIL_TRUCK_STOP
Station with truck stops.
@ FACIL_TRAIN
Station with train station.
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:280
bool StrValid(std::span< const char > str)
Checks whether the given string is valid, i.e.
Definition string.cpp:227
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:419
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:460
Functions related to low-level strings.
bool StrEmpty(const char *s)
Check if a string buffer is empty.
Definition string_func.h:57
bool IsTextDirectionChar(char32_t c)
Is the given character a text direction character.
void MacOSSetCurrentLocaleName(const char *iso_code)
Store current language locale as a CoreFoundation locale.
#define NBSP
A non-breaking space.
Definition string_type.h:16
LanguageList _languages
The actual list of language meta data.
Definition strings.cpp:53
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:257
std::string_view GetListSeparator()
Get the list separator string for the current language.
Definition strings.cpp:229
static const Units _units_height[]
Unit conversions for height.
Definition strings.cpp:831
void SetDParamMaxValue(size_t n, uint64_t max_value, uint min_count, FontSize size)
Set DParam n to some number that is suitable for string size computations.
Definition strings.cpp:127
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:54
static void FormatBytes(StringBuilder &builder, int64_t number)
Format a given number as a number of bytes with the SI prefix.
Definition strings.cpp:467
const char * GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
Definition strings.cpp:2183
void AppendStringInPlace(std::string &result, StringID string)
Resolve the given StringID and append in place into an existing std::string with all the associated D...
Definition strings.cpp:345
uint ConvertSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given (internal) speed to the display speed.
Definition strings.cpp:883
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:2291
std::string _config_language_file
The file (name) stored in the configuration.
Definition strings.cpp:52
void SetDParam(size_t n, uint64_t v)
Set a string parameter v at index n in the global string parameter array.
Definition strings.cpp:104
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:866
uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type)
Convert the given display speed to the km/h-ish speed.
Definition strings.cpp:916
static const Units _units_power[]
Unit conversions for power.
Definition strings.cpp:790
uint64_t GetDParam(size_t n)
Get the current string parameter at index n from the global string parameter array.
Definition strings.cpp:114
static void FormatNumber(StringBuilder &builder, int64_t number, const char *separator)
Format a number into a string.
Definition strings.cpp:412
std::string GetString(StringID string)
Resolve the given StringID into a std::string with all the associated DParam lookups and formatting.
Definition strings.cpp:333
static bool _scan_for_gender_data
Are we scanning for the gender of the current string? (instead of formatting it)
Definition strings.cpp:223
static const UnitsLong _units_volume[]
Unit conversions for volume.
Definition strings.cpp:817
static const UnitsLong _units_weight[]
Unit conversions for weight.
Definition strings.cpp:810
void CopyOutDParam(std::vector< StringParameterData > &backup, size_t num)
Copy num string parameters from the global string parameter array to the backup.
Definition strings.cpp:171
void CopyInDParam(const std::span< const StringParameterData > backup)
Copy the parameters from the backup into the global string parameter array.
Definition strings.cpp:159
static const Units _units_velocity_calendar[]
Unit conversions for velocity.
Definition strings.cpp:772
std::unique_ptr< icu::Collator > _current_collator
Collator for the language currently in use.
Definition strings.cpp:59
static void FormatString(StringBuilder &builder, const char *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:928
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:2108
uint ConvertKmhishSpeedToDisplaySpeed(uint speed, VehicleType type)
Convert the given km/h-ish speed to the display speed.
Definition strings.cpp:906
static const Units _units_force[]
Unit conversions for force.
Definition strings.cpp:824
void InitializeLanguagePacks()
Make a list of the available language packs.
Definition strings.cpp:2136
const LanguageMetadata * GetLanguage(uint8_t newgrflangid)
Get the language with the given NewGRF language ID.
Definition strings.cpp:2072
bool HaveDParamChanged(const std::span< const StringParameterData > backup)
Checks whether the global string parameters have changed compared to the given backup.
Definition strings.cpp:184
static const char * GetSurname(uint32_t seed)
Get the surname of the president with the given seed.
Definition strings.cpp:1849
static const Units _units_time_days_or_seconds[]
Unit conversions for time in calendar days or wallclock seconds.
Definition strings.cpp:838
uint ConvertDisplaySpeedToSpeed(uint speed, VehicleType type)
Convert the given display speed to the (internal) speed.
Definition strings.cpp:896
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
static const Units _units_velocity_realtime[]
Unit conversions for velocity.
Definition strings.cpp:781
bool ReadLanguagePack(const LanguageMetadata *lang)
Read a particular language.
Definition strings.cpp:1937
static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
Reads the language file header and checks compatibility.
Definition strings.cpp:2087
static const Units _units_time_months_or_minutes[]
Unit conversions for time in calendar months or wallclock minutes.
Definition strings.cpp:844
static const Units _units_time_years_or_minutes[]
Unit conversions for time in calendar years or wallclock minutes.
Definition strings.cpp:856
static const Units _units_power_to_weight[]
Unit conversions for power to weight.
Definition strings.cpp:797
void SetDParamStr(size_t n, const char *str)
This function is used to "bind" a C string to a OpenTTD dparam slot.
Definition strings.cpp:371
const char * GetCurrentLocale(const char *param)
Determine the current charset based on the environment First check some default values,...
Definition strings.cpp:2046
static const Units _units_time_years_or_periods[]
Unit conversions for time in calendar years or economic periods.
Definition strings.cpp:850
static int DeterminePluralForm(int64_t count, int plural_form)
Determine the "plural" index given a plural form and a number.
Definition strings.cpp:587
void SetDParamMaxDigits(size_t n, uint count, FontSize size)
Set DParam n to some number that is suitable for string size computations.
Definition strings.cpp:143
Functions related to OTTD's strings.
StringTab GetStringTab(StringID str)
Extract the StringTab from a StringID.
StringID MakeStringID(StringTab tab, uint index)
Create a StringID.
uint GetStringIndex(StringID str)
Extract the StringIndex from a StringID.
TextDirection
Directions a text can go to.
@ TD_LTR
Text is written left-to-right by default.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames)
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 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.
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:95
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo ID.
Definition cargotype.h:139
StringID quantifier
Text for multiple units of cargo of this type.
Definition cargotype.h:96
static size_t GetArraySize()
Total number of cargospecs, both valid and invalid.
Definition cargotype.h:129
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(size_t index)
Is this company a valid company, not controlled by a NoAI program?
Specification of a currency.
Definition currency.h:75
std::string separator
The thousands separator for this currency.
Definition currency.h:77
std::string prefix
Prefix to apply when formatting money in this currency.
Definition currency.h:79
std::string suffix
Suffix to apply when formatting money in this currency.
Definition currency.h:80
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:76
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:91
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:108
uint8_t 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:72
std::string name
Group Name.
Definition group.h:73
uint16_t number
Per-company group number.
Definition group.h:84
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:66
IndustryType type
type of industry.
Definition industry.h:102
Town * town
Nearest town.
Definition industry.h:95
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:1906
bool IsReasonablyFinished() const
Check whether a translation is sufficiently finished to offer it to the public.
Definition strings.cpp:1926
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:218
std::array< uint, TEXT_TAB_END > langtab_num
Offset into langpack offs.
Definition strings.cpp:215
std::array< uint, TEXT_TAB_END > langtab_start
Offset into langpack offs.
Definition strings.cpp:216
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
Tindex index
Index of this pool item.
static Titem * GetIfValid(size_t index)
Returns Titem with given index.
static Titem * Get(size_t index)
Returns Titem with given index.
static Station * GetIfValid(size_t 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.
Town data structure.
Definition town.h:54
std::string name
Custom town name. If empty, the town was not renamed and uses the generated name.
Definition town.h:63
Helper for unit conversion.
Definition strings.cpp:725
double factor
Amount to multiply or divide upon conversion.
Definition strings.cpp:726
int64_t ToDisplay(int64_t input, bool round=true) const
Convert value from OpenTTD's internal unit into the displayed value.
Definition strings.cpp:734
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:748
Information about a specific unit system with a long variant.
Definition strings.cpp:764
StringID s
String for the short variant of the unit.
Definition strings.cpp:766
StringID l
String for the long variant of the unit.
Definition strings.cpp:767
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:768
UnitConversion c
Conversion.
Definition strings.cpp:765
Information about a specific unit system.
Definition strings.cpp:757
StringID s
String for the unit.
Definition strings.cpp:759
unsigned int decimal_places
Number of decimal places embedded in the value. For example, 1 if the value is in tenths,...
Definition strings.cpp:760
UnitConversion c
Conversion.
Definition strings.cpp:758
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.
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.
uint32_t VehicleID
The type all our vehicle IDs have.
Base of waypoints.
std::wstring OTTD2FS(const std::string &name)
Convert from OpenTTD's encoding to a wide string.
Definition win32.cpp:354
std::string FS2OTTD(const std::wstring &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:3236
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: