1 /* $Id: strings.cpp 26242 2014-01-12 18:00:55Z frosch $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file strings.cpp Handling of translated strings. */
16 #include "station_base.h"
18 #include "waypoint_base.h"
19 #include "depot_base.h"
21 #include "newgrf_text.h"
22 #include "fileio_func.h"
23 #include "signs_base.h"
24 #include "fontdetection.h"
26 #include "strings_func.h"
28 #include "core/endian_func.hpp"
29 #include "date_func.h"
30 #include "vehicle_base.h"
31 #include "engine_base.h"
33 #include "townname_func.h"
34 #include "string_func.h"
35 #include "company_base.h"
36 #include "smallmap_gui.h"
37 #include "window_func.h"
39 #include "tracerestrict.h"
40 #include "game/game_text.hpp"
42 # include "network/network_content_gui.h"
43 #endif /* ENABLE_NETWORK */
46 #include "table/strings.h"
47 #include "table/control_codes.h"
49 #include "safeguards.h"
51 char _config_language_file
[MAX_PATH
]; ///< The file (name) stored in the configuration.
52 LanguageList _languages
; ///< The actual list of language meta data.
53 const LanguageMetadata
*_current_language
= nullptr; ///< The currently loaded language.
55 TextDirection _current_text_dir
; ///< Text direction of the currently selected language.
58 Collator
*_current_collator
= nullptr; ///< Collator for the language currently in use.
59 #endif /* WITH_ICU_SORT */
61 static uint64 _global_string_params_data
[20]; ///< Global array of string parameters. To access, use #SetDParam.
62 static WChar _global_string_params_type
[20]; ///< Type of parameters stored in #_decode_parameters
63 StringParameters
_global_string_params(_global_string_params_data
, 20, _global_string_params_type
);
65 /** Reset the type array. */
66 void StringParameters::ClearTypeInformation()
68 assert(this->type
!= nullptr);
69 MemSetT(this->type
, 0, this->num_param
);
74 * Read an int64 from the argument array. The offset is increased
75 * so the next time GetInt64 is called the next value is read.
77 int64
StringParameters::GetInt64(WChar type
)
79 if (this->offset
>= this->num_param
) {
80 DEBUG(misc
, 0, "Trying to read invalid string parameter");
83 if (this->type
!= nullptr) {
84 assert(this->type
[this->offset
] == 0 || this->type
[this->offset
] == type
);
85 this->type
[this->offset
] = type
;
87 return this->data
[this->offset
++];
91 * Shift all data in the data array by the given amount to make
92 * room for some extra parameters.
94 void StringParameters::ShiftParameters(uint amount
)
96 assert(amount
<= this->num_param
);
97 MemMoveT(this->data
+ amount
, this->data
, this->num_param
- amount
);
101 * Set DParam n to some number that is suitable for string size computations.
102 * @param n Index of the string parameter.
103 * @param max_value The biggest value which shall be displayed.
104 * For the result only the number of digits of \a max_value matter.
105 * @param min_count Minimum number of digits independent of \a max.
106 * @param size Font of the number
108 void SetDParamMaxValue(uint n
, uint64 max_value
, uint min_count
, FontSize size
)
111 while (max_value
>= 10) {
115 SetDParamMaxDigits(n
, max(min_count
, num_digits
), size
);
119 * Set DParam n to some number that is suitable for string size computations.
120 * @param n Index of the string parameter.
121 * @param count Number of digits which shall be displayable.
122 * @param size Font of the number
124 void SetDParamMaxDigits(uint n
, uint count
, FontSize size
)
127 GetBroadestDigit(&front
, &next
, size
);
128 uint64 val
= count
> 1 ? front
: next
;
129 for (; count
> 1; count
--) {
130 val
= 10 * val
+ next
;
136 * Copy \a num string parameters from array \a src into the global string parameter array.
137 * @param offs Index in the global array to copy the first string parameter to.
138 * @param src Source array of string parameters.
139 * @param num Number of string parameters to copy.
141 void CopyInDParam(int offs
, const uint64
*src
, int num
)
143 MemCpyT(_global_string_params
.GetPointerToOffset(offs
), src
, num
);
147 * Copy \a num string parameters from the global string parameter array to the \a dst array.
148 * @param dst Destination array of string parameters.
149 * @param offs Index in the global array to copy the first string parameter from.
150 * @param num Number of string parameters to copy.
152 void CopyOutDParam(uint64
*dst
, int offs
, int num
)
154 MemCpyT(dst
, _global_string_params
.GetPointerToOffset(offs
), num
);
158 * Copy \a num string parameters from the global string parameter array to the \a dst array.
159 * Furthermore clone raw string parameters into \a strings and amend the data in \a dst.
160 * @param dst Destination array of string parameters.
161 * @param strings Destination array for clone of the raw strings. Must be of same length as dst. Deallocation left to the caller.
162 * @param string The string used to determine where raw strings are and where there are no raw strings.
163 * @param num Number of string parameters to copy.
165 void CopyOutDParam(uint64
*dst
, const char **strings
, StringID string
, int num
)
167 char buf
[DRAW_STRING_BUFFER
];
168 GetString(buf
, string
, lastof(buf
));
170 MemCpyT(dst
, _global_string_params
.GetPointerToOffset(0), num
);
171 for (int i
= 0; i
< num
; i
++) {
172 if (_global_string_params
.HasTypeInformation() && _global_string_params
.GetTypeAtOffset(i
) == SCC_RAW_STRING_POINTER
) {
173 strings
[i
] = stredup((const char *)(size_t)_global_string_params
.GetParam(i
));
174 dst
[i
] = (size_t)strings
[i
];
176 strings
[i
] = nullptr;
181 static char *StationGetSpecialString(char *buff
, int x
, const char *last
);
182 static char *GetSpecialTownNameString(char *buff
, int ind
, uint32 seed
, const char *last
);
183 static char *GetSpecialNameString(char *buff
, int ind
, StringParameters
*args
, const char *last
);
185 static char *FormatString(char *buff
, const char *str
, StringParameters
*args
, const char *last
, uint case_index
= 0, bool game_script
= false, bool dry_run
= false);
187 struct LanguagePack
: public LanguagePackHeader
{
188 char data
[]; // list of strings
191 static char **_langpack_offs
;
192 static LanguagePack
*_langpack
;
193 static uint _langtab_num
[TEXT_TAB_END
]; ///< Offset into langpack offs
194 static uint _langtab_start
[TEXT_TAB_END
]; ///< Offset into langpack offs
195 static bool _scan_for_gender_data
= false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
198 const char *GetStringPtr(StringID string
)
200 switch (GetStringTab(string
)) {
201 case TEXT_TAB_GAMESCRIPT_START
: return GetGameStringPtr(GetStringIndex(string
));
202 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
203 case TEXT_TAB_OLD_NEWGRF
: NOT_REACHED();
204 case TEXT_TAB_NEWGRF_START
: return GetGRFStringPtr(GetStringIndex(string
));
205 default: return _langpack_offs
[_langtab_start
[GetStringTab(string
)] + GetStringIndex(string
)];
210 * Get a parsed string with most special stringcodes replaced by the string parameters.
211 * @param buffr Pointer to a string buffer where the formatted string should be written to.
213 * @param args Arguments for the string.
214 * @param last Pointer just past the end of \a buffr.
215 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
216 * @param game_script The string is coming directly from a game script.
217 * @return Pointer to the final zero byte of the formatted string.
219 char *GetStringWithArgs(char *buffr
, StringID string
, StringParameters
*args
, const char *last
, uint case_index
, bool game_script
)
221 if (string
== 0) return GetStringWithArgs(buffr
, STR_UNDEFINED
, args
, last
);
223 uint index
= GetStringIndex(string
);
224 StringTab tab
= GetStringTab(string
);
228 if (index
>= 0xC0 && !game_script
) {
229 return GetSpecialTownNameString(buffr
, index
- 0xC0, args
->GetInt32(), last
);
233 case TEXT_TAB_SPECIAL
:
234 if (index
>= 0xE4 && !game_script
) {
235 return GetSpecialNameString(buffr
, index
- 0xE4, args
, last
);
239 case TEXT_TAB_OLD_CUSTOM
:
240 /* Old table for custom names. This is no longer used */
242 error("Incorrect conversion of custom name string.");
246 case TEXT_TAB_GAMESCRIPT_START
:
247 return FormatString(buffr
, GetGameStringPtr(index
), args
, last
, case_index
, true);
249 case TEXT_TAB_OLD_NEWGRF
:
252 case TEXT_TAB_NEWGRF_START
:
253 return FormatString(buffr
, GetGRFStringPtr(index
), args
, last
, case_index
);
259 if (index
>= _langtab_num
[tab
]) {
261 return GetStringWithArgs(buffr
, STR_UNDEFINED
, args
, last
);
263 error("String 0x%X is invalid. You are probably using an old version of the .lng file.\n", string
);
266 return FormatString(buffr
, GetStringPtr(string
), args
, last
, case_index
);
269 char *GetString(char *buffr
, StringID string
, const char *last
)
271 _global_string_params
.ClearTypeInformation();
272 _global_string_params
.offset
= 0;
273 return GetStringWithArgs(buffr
, string
, &_global_string_params
, last
);
278 * This function is used to "bind" a C string to a OpenTTD dparam slot.
279 * @param n slot of the string
280 * @param str string to bind
282 void SetDParamStr(uint n
, const char *str
)
284 SetDParam(n
, (uint64
)(size_t)str
);
288 * Shift the string parameters in the global string parameter array by \a amount positions, making room at the beginning.
289 * @param amount Number of positions to shift.
291 void InjectDParam(uint amount
)
293 _global_string_params
.ShiftParameters(amount
);
297 * Format a number into a string.
298 * @param buff the buffer to write to
299 * @param number the number to write down
300 * @param last the last element in the buffer
301 * @param separator the thousands-separator to use
302 * @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary.
303 * @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted
304 * in front of the \a fractional_digits last digit of \a number.
305 * @return till where we wrote
307 static char *FormatNumber(char *buff
, int64 number
, const char *last
, const char *separator
, int zerofill
= 1, int fractional_digits
= 0)
309 static const int max_digits
= 20;
310 uint64 divisor
= 10000000000000000000ULL;
311 zerofill
+= fractional_digits
;
312 int thousands_offset
= (max_digits
- fractional_digits
- 1) % 3;
315 buff
+= seprintf(buff
, last
, "-");
321 for (int i
= 0; i
< max_digits
; i
++) {
322 if (i
== max_digits
- fractional_digits
) {
323 const char *decimal_separator
= _settings_game
.locale
.digit_decimal_separator
;
324 if (decimal_separator
== nullptr) decimal_separator
= _langpack
->digit_decimal_separator
;
325 buff
+= seprintf(buff
, last
, "%s", decimal_separator
);
329 if (num
>= divisor
) {
330 quot
= num
/ divisor
;
333 if ((tot
|= quot
) || i
>= max_digits
- zerofill
) {
334 buff
+= seprintf(buff
, last
, "%i", (int)quot
);
335 if ((i
% 3) == thousands_offset
&& i
< max_digits
- 1 - fractional_digits
) buff
= strecpy(buff
, separator
, last
);
346 static char *FormatCommaNumber(char *buff
, int64 number
, const char *last
, int fractional_digits
= 0)
348 const char *separator
= _settings_game
.locale
.digit_group_separator
;
349 if (separator
== nullptr) separator
= _langpack
->digit_group_separator
;
350 return FormatNumber(buff
, number
, last
, separator
, 1, fractional_digits
);
353 static char *FormatNoCommaNumber(char *buff
, int64 number
, const char *last
)
355 return FormatNumber(buff
, number
, last
, "");
358 static char *FormatZerofillNumber(char *buff
, int64 number
, int64 count
, const char *last
)
360 return FormatNumber(buff
, number
, last
, "", count
);
363 static char *FormatHexNumber(char *buff
, uint64 number
, const char *last
)
365 return buff
+ seprintf(buff
, last
, "0x" OTTD_PRINTFHEX64
, number
);
369 * Format a given number as a number of bytes with the SI prefix.
370 * @param buff the buffer to write to
371 * @param number the number of bytes to write down
372 * @param last the last element in the buffer
373 * @return till where we wrote
375 static char *FormatBytes(char *buff
, int64 number
, const char *last
)
379 /* 1 2^10 2^20 2^30 2^40 2^50 2^60 */
380 const char * const iec_prefixes
[] = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"};
382 while (number
>= 1024 * 1024) {
387 const char *decimal_separator
= _settings_game
.locale
.digit_decimal_separator
;
388 if (decimal_separator
== nullptr) decimal_separator
= _langpack
->digit_decimal_separator
;
392 buff
+= seprintf(buff
, last
, "%i", (int)number
);
393 } else if (number
< 1024 * 10) {
394 buff
+= seprintf(buff
, last
, "%i%s%02i", (int)number
/ 1024, decimal_separator
, (int)(number
% 1024) * 100 / 1024);
395 } else if (number
< 1024 * 100) {
396 buff
+= seprintf(buff
, last
, "%i%s%01i", (int)number
/ 1024, decimal_separator
, (int)(number
% 1024) * 10 / 1024);
398 assert(number
< 1024 * 1024);
399 buff
+= seprintf(buff
, last
, "%i", (int)number
/ 1024);
402 assert(id
< lengthof(iec_prefixes
));
403 buff
+= seprintf(buff
, last
, NBSP
"%sB", iec_prefixes
[id
]);
408 static char *FormatTimeString(char *buff
, Ticks ticks
, const char *last
, uint case_index
)
410 auto minutes
= ticks
/ _settings_client
.gui
.ticks_per_minute
;
411 char hour
[3], minute
[3];
412 seprintf(hour
, lastof(hour
), "%02i", ((minutes
/ 60) % 24));
413 seprintf(minute
, lastof(minute
), "%02i", (minutes
% 60));
415 int64 args
[2] = { (int64
)hour
, (int64
)minute
};
416 StringParameters
tmp_params(args
);
418 return FormatString(buff
, GetStringPtr(STR_FORMAT_TIME
), &tmp_params
, last
, case_index
);
421 static char *FormatTimeDurationString(char *buff
, bool color_red
, Ticks ticks
, const char *last
, uint case_index
)
423 auto total_minutes
= ticks
/ _settings_client
.gui
.ticks_per_minute
;
425 auto minutes
= total_minutes
% 60;
426 auto hours
= total_minutes
/ 60;
429 int64 args
[1] = { (int64
)minutes
};
430 StringParameters
tmp_params(args
);
432 return FormatString(buff
, GetStringPtr(color_red
? STR_FORMAT_TIME_MINUTES_RED
: STR_FORMAT_TIME_MINUTES
), &tmp_params
, last
, case_index
);
435 int64 args
[2] = { (int64
)hours
, (int64
)minutes
};
436 StringParameters
tmp_params(args
);
438 return FormatString(buff
, GetStringPtr(color_red
? STR_FORMAT_TIME_HOURS_MINUTES_RED
: STR_FORMAT_TIME_HOURS_MINUTES
), &tmp_params
, last
, case_index
);
442 static char *FormatTicksString(char *buff
, bool color_red
, Ticks ticks
, const char *last
, uint case_index
)
444 int64 args
[1] = { (int64
)ticks
};
445 StringParameters
tmp_params(args
);
447 return FormatString(buff
, GetStringPtr(color_red
? STR_FORMAT_TIME_TICKS_RED
: STR_FORMAT_TIME_TICKS
), &tmp_params
, last
, case_index
);
450 static char *FormatYmdString(char *buff
, Date date
, const char *last
, uint case_index
)
453 ConvertDateToYMD(date
, &ymd
);
455 int64 args
[] = {ymd
.day
+ STR_DAY_NUMBER_1ST
- 1, STR_MONTH_ABBREV_JAN
+ ymd
.month
, ymd
.year
};
456 StringParameters
tmp_params(args
);
457 return FormatString(buff
, GetStringPtr(STR_FORMAT_DATE_LONG
), &tmp_params
, last
, case_index
);
460 static char *FormatMonthAndYear(char *buff
, Date date
, const char *last
, uint case_index
)
463 ConvertDateToYMD(date
, &ymd
);
465 int64 args
[] = {STR_MONTH_JAN
+ ymd
.month
, ymd
.year
};
466 StringParameters
tmp_params(args
);
467 return FormatString(buff
, GetStringPtr(STR_FORMAT_DATE_SHORT
), &tmp_params
, last
, case_index
);
470 static char *FormatNormalOrISODate(char *buff
, Date date
, StringID str
, const char *last
)
473 ConvertDateToYMD(date
, &ymd
);
477 /* We want to zero-pad the days and months */
478 seprintf(day
, lastof(day
), "%02i", ymd
.day
);
479 seprintf(month
, lastof(month
), "%02i", ymd
.month
+ 1);
481 int64 args
[] = {(int64
)(size_t)day
, (int64
)(size_t)month
, ymd
.year
};
482 StringParameters
tmp_params(args
);
483 return FormatString(buff
, GetStringPtr(str
), &tmp_params
, last
);
486 static char *FormatGenericCurrency(char *buff
, const CurrencySpec
*spec
, Money number
, bool compact
, const char *last
)
488 /* We are going to make number absolute for printing, so
489 * keep this piece of data as we need it later on */
490 bool negative
= number
< 0;
491 const char *multiplier
= "";
493 number
*= spec
->rate
;
495 /* convert from negative */
497 if (buff
+ Utf8CharLen(SCC_RED
) > last
) return buff
;
498 buff
+= Utf8Encode(buff
, SCC_RED
);
499 buff
= strecpy(buff
, "-", last
);
503 /* Add prefix part, following symbol_pos specification.
504 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
505 * The only remaining value is 1 (suffix), so everything that is not 1 */
506 if (spec
->symbol_pos
!= 1) buff
= strecpy(buff
, spec
->prefix
, last
);
508 /* for huge numbers, compact the number into k or M */
510 /* Take care of the 'k' rounding. Having 1 000 000 k
511 * and 1 000 M is inconsistent, so always use 1 000 M. */
512 if (number
>= 1000000000 - 500) {
513 number
= (number
+ 500000) / 1000000;
514 multiplier
= NBSP
"M";
515 } else if (number
>= 1000000) {
516 number
= (number
+ 500) / 1000;
517 multiplier
= NBSP
"k";
521 const char *separator
= _settings_game
.locale
.digit_group_separator_currency
;
522 if (separator
== nullptr && !StrEmpty(_currency
->separator
)) separator
= _currency
->separator
;
523 if (separator
== nullptr) separator
= _langpack
->digit_group_separator_currency
;
524 buff
= FormatNumber(buff
, number
, last
, separator
);
525 buff
= strecpy(buff
, multiplier
, last
);
527 /* Add suffix part, following symbol_pos specification.
528 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
529 * The only remaining value is 1 (prefix), so everything that is not 0 */
530 if (spec
->symbol_pos
!= 0) buff
= strecpy(buff
, spec
->suffix
, last
);
533 if (buff
+ Utf8CharLen(SCC_PREVIOUS_COLOUR
) > last
) return buff
;
534 buff
+= Utf8Encode(buff
, SCC_PREVIOUS_COLOUR
);
541 void MakeCamelCase(char line
[]) {
544 for (int i
= 0; line
[i
] != '\0'; i
++) {
545 if (std::isalpha(line
[i
])) {
547 line
[i
] = std::toupper(line
[i
]);
551 line
[i
] = std::tolower(line
[i
]);
554 else if (line
[i
] == ' ') {
561 * Determine the "plural" index given a plural form and a number.
562 * @param count The number to get the plural index of.
563 * @param plural_form The plural form we want an index for.
564 * @return The plural index for the given form.
566 static int DeterminePluralForm(int64 count
, int plural_form
)
568 /* The absolute value determines plurality */
569 uint64 n
= abs(count
);
571 switch (plural_form
) {
575 /* Two forms: singular used for one only.
577 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
578 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
580 return n
!= 1 ? 1 : 0;
584 * Hungarian, Japanese, Korean, Turkish */
588 /* Two forms: singular used for 0 and 1.
590 * French, Brazilian Portuguese */
592 return n
> 1 ? 1 : 0;
594 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
595 * Note: Cases are out of order for hysterical reasons. '0' is last.
599 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
!= 0 ? 1 : 2;
601 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
605 return n
== 1 ? 0 : n
== 2 ? 1 : n
< 7 ? 2 : n
< 11 ? 3 : 4;
607 /* 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.
611 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
613 /* 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.
615 * Croatian, Russian, Ukrainian */
617 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
619 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
623 return n
== 1 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
625 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
629 return n
% 100 == 1 ? 0 : n
% 100 == 2 ? 1 : n
% 100 == 3 || n
% 100 == 4 ? 2 : 3;
631 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
635 return n
% 10 == 1 && n
% 100 != 11 ? 0 : 1;
637 /* Three forms: special cases for 1, and 2 to 4
641 return n
== 1 ? 0 : n
>= 2 && n
<= 4 ? 1 : 2;
643 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
644 * Korean doesn't have the concept of plural, but depending on how a
645 * number is pronounced it needs another version of a particle.
646 * As such the plural system is misused to give this distinction.
668 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
672 return (n
== 1 ? 0 : n
== 0 || (n
% 100 > 1 && n
% 100 < 11) ? 1 : (n
% 100 > 10 && n
% 100 < 20) ? 2 : 3);
673 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
677 return ((n
== 1 || n
== 11) ? 0 : (n
== 2 || n
== 12) ? 1 : ((n
> 2 && n
< 11) || (n
> 12 && n
< 20)) ? 2 : 3);
681 static const char *ParseStringChoice(const char *b
, uint form
, char **dst
, const char *last
)
683 /* <NUM> {Length of each string} {each string} */
685 uint pos
, i
, mypos
= 0;
687 for (i
= pos
= 0; i
!= n
; i
++) {
688 uint len
= (byte
)*b
++;
689 if (i
== form
) mypos
= pos
;
693 *dst
+= seprintf(*dst
, last
, "%s", b
+ mypos
);
697 /** Helper for unit conversion. */
698 struct UnitConversion
{
699 int multiplier
; ///< Amount to multiply upon conversion.
700 int shift
; ///< Amount to shift upon conversion.
703 * Convert value from OpenTTD's internal unit into the displayed value.
704 * @param input The input to convert.
705 * @param round Whether to round the value or not.
706 * @return The converted value.
708 int64
ToDisplay(int64 input
, bool round
= true) const
710 return ((input
* this->multiplier
) + (round
&& this->shift
!= 0 ? 1 << (this->shift
- 1) : 0)) >> this->shift
;
714 * Convert the displayed value back into a value of OpenTTD's internal unit.
715 * @param input The input to convert.
716 * @param round Whether to round the value up or not.
717 * @param divider Divide the return value by this.
718 * @return The converted value.
720 int64
FromDisplay(int64 input
, bool round
= true, int64 divider
= 1) const
722 return ((input
<< this->shift
) + (round
? (this->multiplier
* divider
) - 1 : 0)) / (this->multiplier
* divider
);
726 /** Information about a specific unit system. */
728 UnitConversion c
; ///< Conversion
729 StringID s
; ///< String for the unit
732 /** Information about a specific unit system with a long variant. */
734 UnitConversion c
; ///< Conversion
735 StringID s
; ///< String for the short variant of the unit
736 StringID l
; ///< String for the long variant of the unit
739 /** Unit conversions for velocity. */
740 static const Units _units_velocity
[] = {
741 { { 1, 0}, STR_UNITS_VELOCITY_IMPERIAL
},
742 { { 103, 6}, STR_UNITS_VELOCITY_METRIC
},
743 { {1831, 12}, STR_UNITS_VELOCITY_SI
},
746 /** Unit conversions for velocity. */
747 static const Units _units_power
[] = {
748 { { 1, 0}, STR_UNITS_POWER_IMPERIAL
},
749 { {4153, 12}, STR_UNITS_POWER_METRIC
},
750 { {6109, 13}, STR_UNITS_POWER_SI
},
753 /** Unit conversions for weight. */
754 static const UnitsLong _units_weight
[] = {
755 { {4515, 12}, STR_UNITS_WEIGHT_SHORT_IMPERIAL
, STR_UNITS_WEIGHT_LONG_IMPERIAL
},
756 { { 1, 0}, STR_UNITS_WEIGHT_SHORT_METRIC
, STR_UNITS_WEIGHT_LONG_METRIC
},
757 { {1000, 0}, STR_UNITS_WEIGHT_SHORT_SI
, STR_UNITS_WEIGHT_LONG_SI
},
760 /** Unit conversions for volume. */
761 static const UnitsLong _units_volume
[] = {
762 { {4227, 4}, STR_UNITS_VOLUME_SHORT_IMPERIAL
, STR_UNITS_VOLUME_LONG_IMPERIAL
},
763 { {1000, 0}, STR_UNITS_VOLUME_SHORT_METRIC
, STR_UNITS_VOLUME_LONG_METRIC
},
764 { { 1, 0}, STR_UNITS_VOLUME_SHORT_SI
, STR_UNITS_VOLUME_LONG_SI
},
767 /** Unit conversions for force. */
768 static const Units _units_force
[] = {
769 { {3597, 4}, STR_UNITS_FORCE_IMPERIAL
},
770 { {3263, 5}, STR_UNITS_FORCE_METRIC
},
771 { { 1, 0}, STR_UNITS_FORCE_SI
},
774 /** Unit conversions for height. */
775 static const Units _units_height
[] = {
776 { { 3, 0}, STR_UNITS_HEIGHT_IMPERIAL
}, // "Wrong" conversion factor for more nicer GUI values
777 { { 1, 0}, STR_UNITS_HEIGHT_METRIC
},
778 { { 1, 0}, STR_UNITS_HEIGHT_SI
},
782 * Convert the given (internal) speed to the display speed.
783 * @param speed the speed to convert
784 * @return the converted speed.
786 uint
ConvertSpeedToDisplaySpeed(uint speed
)
788 /* For historical reasons we don't want to mess with the
789 * conversion for speed. So, don't round it and keep the
790 * original conversion factors instead of the real ones. */
791 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.ToDisplay(speed
, false);
795 * Convert the given display speed to the (internal) speed.
796 * @param speed the speed to convert
797 * @return the converted speed.
799 uint
ConvertDisplaySpeedToSpeed(uint speed
)
801 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.FromDisplay(speed
);
805 * Convert the given km/h-ish speed to the display speed.
806 * @param speed the speed to convert
807 * @return the converted speed.
809 uint
ConvertKmhishSpeedToDisplaySpeed(uint speed
)
811 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.ToDisplay(speed
* 10, false) / 16;
815 * Convert the given display speed to the km/h-ish speed.
816 * @param speed the speed to convert
817 * @return the converted speed.
819 uint
ConvertDisplaySpeedToKmhishSpeed(uint speed
)
821 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.FromDisplay(speed
* 16, true, 10);
824 * Parse most format codes within a string and write the result to a buffer.
825 * @param buff The buffer to write the final string to.
826 * @param str The original string with format codes.
827 * @param args Pointer to extra arguments used by various string codes.
829 * @param last Pointer to just past the end of the buff array.
830 * @param dry_run True when the argt array is not yet initialized.
832 static char *FormatString(char *buff
, const char *str_arg
, StringParameters
*args
, const char *last
, uint case_index
, bool game_script
, bool dry_run
)
834 uint orig_offset
= args
->offset
;
836 /* When there is no array with types there is no need to do a dry run. */
837 if (args
->HasTypeInformation() && !dry_run
) {
838 if (UsingNewGRFTextStack()) {
839 /* Values from the NewGRF text stack are only copied to the normal
840 * argv array at the time they are encountered. That means that if
841 * another string command references a value later in the string it
842 * would fail. We solve that by running FormatString twice. The first
843 * pass makes sure the argv array is correctly filled and the second
844 * pass can reference later values without problems. */
845 struct TextRefStack
*backup
= CreateTextRefStackBackup();
846 FormatString(buff
, str_arg
, args
, last
, case_index
, game_script
, true);
847 RestoreTextRefStackBackup(backup
);
849 FormatString(buff
, str_arg
, args
, last
, case_index
, game_script
, true);
851 /* We have to restore the original offset here to to read the correct values. */
852 args
->offset
= orig_offset
;
855 uint next_substr_case_index
= 0;
856 char *buf_start
= buff
;
857 std::stack
<const char *> str_stack
;
858 str_stack
.push(str_arg
);
861 while (!str_stack
.empty() && (b
= Utf8Consume(&str_stack
.top())) == '\0') {
864 if (str_stack
.empty()) break;
865 const char *&str
= str_stack
.top();
867 if (SCC_NEWGRF_FIRST
<= b
&& b
<= SCC_NEWGRF_LAST
) {
868 /* We need to pass some stuff as it might be modified; oh boy. */
869 //todo: should argve be passed here too?
870 b
= RemapNewGRFStringControlCode(b
, buf_start
, &buff
, &str
, (int64
*)args
->GetDataPointer(), args
->GetDataLeft(), dry_run
);
871 if (b
== 0) continue;
876 uint64 sub_args_data
[20];
877 WChar sub_args_type
[20];
878 bool sub_args_need_free
[20];
879 StringParameters
sub_args(sub_args_data
, 20, sub_args_type
);
881 sub_args
.ClearTypeInformation();
882 memset(sub_args_need_free
, 0, sizeof(sub_args_need_free
));
886 uint32 stringid
= strtoul(str
, &p
, 16);
887 if (*p
!= ':' && *p
!= '\0') {
888 while (*p
!= '\0') p
++;
890 buff
= strecat(buff
, "(invalid SCC_ENCODED)", last
);
893 if (stringid
>= TAB_SIZE_GAMESCRIPT
) {
894 while (*p
!= '\0') p
++;
896 buff
= strecat(buff
, "(invalid StringID)", last
);
901 while (*p
!= '\0' && i
< 20) {
905 /* Find the next value */
906 bool instring
= false;
913 if (*p
== '"' && escape
) {
920 instring
= !instring
;
927 if (*p
== ':') break;
928 if (*p
== '\0') break;
932 /* Check if we want to look up another string */
934 size_t len
= Utf8Decode(&l
, s
);
935 bool lookup
= (l
== SCC_ENCODED
);
936 if (lookup
) s
+= len
;
938 param
= strtoull(s
, &p
, 16);
941 if (param
>= TAB_SIZE_GAMESCRIPT
) {
942 while (*p
!= '\0') p
++;
944 buff
= strecat(buff
, "(invalid sub-StringID)", last
);
947 param
= MakeStringID(TEXT_TAB_GAMESCRIPT_START
, param
);
950 sub_args
.SetParam(i
++, param
);
952 char *g
= stredup(s
);
955 sub_args_need_free
[i
] = true;
956 sub_args
.SetParam(i
++, (uint64
)(size_t)g
);
959 /* If we didn't error out, we can actually print the string. */
962 buff
= GetStringWithArgs(buff
, MakeStringID(TEXT_TAB_GAMESCRIPT_START
, stringid
), &sub_args
, last
, true);
965 for (int i
= 0; i
< 20; i
++) {
966 if (sub_args_need_free
[i
]) free((void *)sub_args
.GetParam(i
));
971 case SCC_NEWGRF_STRINL
: {
972 StringID substr
= Utf8Consume(&str
);
973 str_stack
.push(GetStringPtr(substr
));
977 case SCC_NEWGRF_PRINT_WORD_STRING_ID
: {
978 StringID substr
= args
->GetInt32(SCC_NEWGRF_PRINT_WORD_STRING_ID
);
979 str_stack
.push(GetStringPtr(substr
));
980 case_index
= next_substr_case_index
;
981 next_substr_case_index
= 0;
986 case SCC_GENDER_LIST
: { // {G 0 Der Die Das}
987 /* First read the meta data from the language file. */
988 uint offset
= orig_offset
+ (byte
)*str
++;
990 if (!dry_run
&& args
->GetTypeAtOffset(offset
) != 0) {
991 /* Now we need to figure out what text to resolve, i.e.
992 * what do we need to draw? So get the actual raw string
993 * first using the control code to get said string. */
995 char *p
= input
+ Utf8Encode(input
, args
->GetTypeAtOffset(offset
));
998 /* Now do the string formatting. */
1000 bool old_sgd
= _scan_for_gender_data
;
1001 _scan_for_gender_data
= true;
1002 StringParameters
tmp_params(args
->GetPointerToOffset(offset
), args
->num_param
- offset
, nullptr);
1003 p
= FormatString(buf
, input
, &tmp_params
, lastof(buf
));
1004 _scan_for_gender_data
= old_sgd
;
1007 /* And determine the string. */
1008 const char *s
= buf
;
1009 WChar c
= Utf8Consume(&s
);
1010 /* Does this string have a gender, if so, set it */
1011 if (c
== SCC_GENDER_INDEX
) gender
= (byte
)s
[0];
1013 str
= ParseStringChoice(str
, gender
, &buff
, last
);
1017 /* This sets up the gender for the string.
1018 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
1019 case SCC_GENDER_INDEX
: // {GENDER 0}
1020 if (_scan_for_gender_data
) {
1021 buff
+= Utf8Encode(buff
, SCC_GENDER_INDEX
);
1028 case SCC_PLURAL_LIST
: { // {P}
1029 int plural_form
= *str
++; // contains the plural form for this string
1030 uint offset
= orig_offset
+ (byte
)*str
++;
1031 int64 v
= *args
->GetPointerToOffset(offset
); // contains the number that determines plural
1032 str
= ParseStringChoice(str
, DeterminePluralForm(v
, plural_form
), &buff
, last
);
1036 case SCC_ARG_INDEX
: { // Move argument pointer
1037 args
->offset
= orig_offset
+ (byte
)*str
++;
1041 case SCC_SET_CASE
: { // {SET_CASE}
1042 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
1043 * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */
1044 next_substr_case_index
= (byte
)*str
++;
1048 case SCC_SWITCH_CASE
: { // {Used to implement case switching}
1049 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
1050 * Each LEN is printed using 2 bytes in big endian order. */
1051 uint num
= (byte
)*str
++;
1053 if ((byte
)str
[0] == case_index
) {
1054 /* Found the case, adjust str pointer and continue */
1058 /* Otherwise skip to the next case */
1059 str
+= 3 + (str
[1] << 8) + str
[2];
1065 case SCC_REVISION
: // {REV}
1066 buff
= strecpy(buff
, _openttd_revision
, last
);
1069 case SCC_RAW_STRING_POINTER
: { // {RAW_STRING}
1070 if (game_script
) break;
1071 const char *str
= (const char *)(size_t)args
->GetInt64(SCC_RAW_STRING_POINTER
);
1072 buff
= FormatString(buff
, str
, args
, last
);
1076 case SCC_STRING
: {// {STRING}
1077 StringID str
= args
->GetInt32(SCC_STRING
);
1078 if (game_script
&& GetStringTab(str
) != TEXT_TAB_GAMESCRIPT_START
) break;
1079 /* WARNING. It's prohibited for the included string to consume any arguments.
1080 * For included strings that consume argument, you should use STRING1, STRING2 etc.
1081 * To debug stuff you can set argv to nullptr and it will tell you */
1082 StringParameters
tmp_params(args
->GetDataPointer(), args
->GetDataLeft(), nullptr);
1083 buff
= GetStringWithArgs(buff
, str
, &tmp_params
, last
, next_substr_case_index
, game_script
);
1084 next_substr_case_index
= 0;
1094 case SCC_STRING7
: { // {STRING1..7}
1095 /* Strings that consume arguments */
1096 StringID str
= args
->GetInt32(b
);
1097 if (game_script
&& GetStringTab(str
) != TEXT_TAB_GAMESCRIPT_START
) break;
1098 uint size
= b
- SCC_STRING1
+ 1;
1099 if (game_script
&& size
> args
->GetDataLeft()) {
1100 buff
= strecat(buff
, "(too many parameters)", last
);
1102 StringParameters
sub_args(*args
, size
);
1103 buff
= GetStringWithArgs(buff
, str
, &sub_args
, last
, next_substr_case_index
, game_script
);
1105 next_substr_case_index
= 0;
1109 case SCC_COMMA
: // {COMMA}
1110 buff
= FormatCommaNumber(buff
, args
->GetInt64(SCC_COMMA
), last
);
1113 case SCC_DECIMAL
: {// {DECIMAL}
1114 int64 number
= args
->GetInt64(SCC_DECIMAL
);
1115 int digits
= args
->GetInt32(SCC_DECIMAL
);
1116 buff
= FormatCommaNumber(buff
, number
, last
, digits
);
1120 case SCC_NUM
: // {NUM}
1121 buff
= FormatNoCommaNumber(buff
, args
->GetInt64(SCC_NUM
), last
);
1124 case SCC_ZEROFILL_NUM
: { // {ZEROFILL_NUM}
1125 int64 num
= args
->GetInt64();
1126 buff
= FormatZerofillNumber(buff
, num
, args
->GetInt64(), last
);
1130 case SCC_HEX
: // {HEX}
1131 buff
= FormatHexNumber(buff
, (uint64
)args
->GetInt64(SCC_HEX
), last
);
1134 case SCC_BYTES
: // {BYTES}
1135 buff
= FormatBytes(buff
, args
->GetInt64(), last
);
1138 case SCC_CARGO_TINY
: { // {CARGO_TINY}
1139 /* Tiny description of cargotypes. Layout:
1140 * param 1: cargo type
1141 * param 2: cargo count */
1142 CargoID cargo
= args
->GetInt32(SCC_CARGO_TINY
);
1143 if (cargo
>= CargoSpec::GetArraySize()) break;
1145 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1147 switch (cargo_str
) {
1149 amount
= _units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64());
1153 amount
= _units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64());
1157 amount
= args
->GetInt64();
1162 buff
= FormatCommaNumber(buff
, amount
, last
);
1166 case SCC_CARGO_SHORT
: { // {CARGO_SHORT}
1167 /* Short description of cargotypes. Layout:
1168 * param 1: cargo type
1169 * param 2: cargo count */
1170 CargoID cargo
= args
->GetInt32(SCC_CARGO_SHORT
);
1171 if (cargo
>= CargoSpec::GetArraySize()) break;
1173 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1174 switch (cargo_str
) {
1176 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1177 int64 args_array
[] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64())};
1178 StringParameters
tmp_params(args_array
);
1179 buff
= FormatString(buff
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].l
), &tmp_params
, last
);
1184 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1185 int64 args_array
[] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64())};
1186 StringParameters
tmp_params(args_array
);
1187 buff
= FormatString(buff
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].l
), &tmp_params
, last
);
1192 StringParameters
tmp_params(*args
, 1);
1193 buff
= GetStringWithArgs(buff
, cargo_str
, &tmp_params
, last
);
1200 case SCC_CARGO_LONG
: { // {CARGO_LONG}
1201 /* First parameter is cargo type, second parameter is cargo count */
1202 CargoID cargo
= args
->GetInt32(SCC_CARGO_LONG
);
1203 if (cargo
!= CT_INVALID
&& cargo
>= CargoSpec::GetArraySize()) break;
1205 StringID cargo_str
= (cargo
== CT_INVALID
) ? STR_QUANTITY_N_A
: CargoSpec::Get(cargo
)->quantifier
;
1206 StringParameters
tmp_args(*args
, 1);
1207 buff
= GetStringWithArgs(buff
, cargo_str
, &tmp_args
, last
);
1211 case SCC_CARGO_LIST
: { // {CARGO_LIST}
1212 uint32 cmask
= args
->GetInt32(SCC_CARGO_LIST
);
1215 const CargoSpec
*cs
;
1216 FOR_ALL_SORTED_CARGOSPECS(cs
) {
1217 if (!HasBit(cmask
, cs
->Index())) continue;
1219 if (buff
>= last
- 2) break; // ',' and ' '
1224 /* Add a comma if this is not the first item */
1229 buff
= GetStringWithArgs(buff
, cs
->name
, args
, last
, next_substr_case_index
, game_script
);
1232 /* If first is still true then no cargo is accepted */
1233 if (first
) buff
= GetStringWithArgs(buff
, STR_JUST_NOTHING
, args
, last
, next_substr_case_index
, game_script
);
1236 next_substr_case_index
= 0;
1238 /* Make sure we detect any buffer overflow */
1239 assert(buff
< last
);
1243 case SCC_CURRENCY_SHORT
: // {CURRENCY_SHORT}
1244 buff
= FormatGenericCurrency(buff
, _currency
, args
->GetInt64(), true, last
);
1247 case SCC_CURRENCY_LONG
: // {CURRENCY_LONG}
1248 buff
= FormatGenericCurrency(buff
, _currency
, args
->GetInt64(SCC_CURRENCY_LONG
), false, last
);
1251 case SCC_DATE
: // {DATE}
1252 buff
= FormatNormalOrISODate(buff
, args
->GetInt32(SCC_DATE
), STR_FORMAT_DATE
, last
);
1255 case SCC_DATE_SHORT
: // {DATE_SHORT}
1256 buff
= FormatMonthAndYear(buff
, args
->GetInt32(SCC_DATE_SHORT
), last
, next_substr_case_index
);
1257 next_substr_case_index
= 0;
1260 case SCC_DATE_LONG
: // {DATE_LONG}
1261 buff
= FormatYmdString(buff
, args
->GetInt32(SCC_DATE_LONG
), last
, next_substr_case_index
);
1262 next_substr_case_index
= 0;
1265 case SCC_DATE_ISO
: // {DATE_ISO}
1266 buff
= FormatNormalOrISODate(buff
, args
->GetInt32(), STR_FORMAT_DATE_ISO
, last
);
1269 case SCC_TIME
: // {TIME}
1270 buff
= FormatTimeString(buff
, args
->GetInt32(), last
, next_substr_case_index
);
1273 case SCC_TIME_DURATION
: // {TIME_DURATION}
1274 case SCC_TIME_DURATION_RED
: // {TIME_DURATION_RED}
1275 buff
= FormatTimeDurationString(buff
, b
== SCC_TIME_DURATION_RED
, args
->GetInt32(), last
, next_substr_case_index
);
1278 case SCC_TICKS
: // {TICKS}
1279 case SCC_TICKS_RED
: // {TICKS_RED}
1280 buff
= FormatTicksString(buff
, b
== SCC_TICKS_RED
, args
->GetInt32(), last
, next_substr_case_index
);
1283 case SCC_FORCE
: { // {FORCE}
1284 assert(_settings_game
.locale
.units_force
< lengthof(_units_force
));
1285 int64 args_array
[1] = {_units_force
[_settings_game
.locale
.units_force
].c
.ToDisplay(args
->GetInt64())};
1286 StringParameters
tmp_params(args_array
);
1287 buff
= FormatString(buff
, GetStringPtr(_units_force
[_settings_game
.locale
.units_force
].s
), &tmp_params
, last
);
1291 case SCC_HEIGHT
: { // {HEIGHT}
1292 assert(_settings_game
.locale
.units_height
< lengthof(_units_height
));
1293 int64 args_array
[] = {_units_height
[_settings_game
.locale
.units_height
].c
.ToDisplay(args
->GetInt64())};
1294 StringParameters
tmp_params(args_array
);
1295 buff
= FormatString(buff
, GetStringPtr(_units_height
[_settings_game
.locale
.units_height
].s
), &tmp_params
, last
);
1299 case SCC_POWER
: { // {POWER}
1300 assert(_settings_game
.locale
.units_power
< lengthof(_units_power
));
1301 int64 args_array
[1] = {_units_power
[_settings_game
.locale
.units_power
].c
.ToDisplay(args
->GetInt64())};
1302 StringParameters
tmp_params(args_array
);
1303 buff
= FormatString(buff
, GetStringPtr(_units_power
[_settings_game
.locale
.units_power
].s
), &tmp_params
, last
);
1307 case SCC_VELOCITY
: { // {VELOCITY}
1308 assert(_settings_game
.locale
.units_velocity
< lengthof(_units_velocity
));
1309 int64 args_array
[] = {ConvertKmhishSpeedToDisplaySpeed(args
->GetInt64(SCC_VELOCITY
))};
1310 StringParameters
tmp_params(args_array
);
1311 buff
= FormatString(buff
, GetStringPtr(_units_velocity
[_settings_game
.locale
.units_velocity
].s
), &tmp_params
, last
);
1315 case SCC_VOLUME_SHORT
: { // {VOLUME_SHORT}
1316 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1317 int64 args_array
[1] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64())};
1318 StringParameters
tmp_params(args_array
);
1319 buff
= FormatString(buff
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].s
), &tmp_params
, last
);
1323 case SCC_VOLUME_LONG
: { // {VOLUME_LONG}
1324 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1325 int64 args_array
[1] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64(SCC_VOLUME_LONG
))};
1326 StringParameters
tmp_params(args_array
);
1327 buff
= FormatString(buff
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].l
), &tmp_params
, last
);
1331 case SCC_WEIGHT_SHORT
: { // {WEIGHT_SHORT}
1332 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1333 int64 args_array
[1] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64())};
1334 StringParameters
tmp_params(args_array
);
1335 buff
= FormatString(buff
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].s
), &tmp_params
, last
);
1339 case SCC_WEIGHT_LONG
: { // {WEIGHT_LONG}
1340 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1341 int64 args_array
[1] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64(SCC_WEIGHT_LONG
))};
1342 StringParameters
tmp_params(args_array
);
1343 buff
= FormatString(buff
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].l
), &tmp_params
, last
);
1347 case SCC_COMPANY_NAME
: { // {COMPANY}
1348 const Company
*c
= Company::GetIfValid(args
->GetInt32());
1349 if (c
== nullptr) break;
1351 if (c
->name
!= nullptr) {
1352 int64 args_array
[] = {(int64
)(size_t)c
->name
};
1353 StringParameters
tmp_params(args_array
);
1354 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1356 int64 args_array
[] = {c
->name_2
};
1357 StringParameters
tmp_params(args_array
);
1358 buff
= GetStringWithArgs(buff
, c
->name_1
, &tmp_params
, last
);
1363 case SCC_COMPANY_NUM
: { // {COMPANY_NUM}
1364 CompanyID company
= (CompanyID
)args
->GetInt32();
1366 /* Nothing is added for AI or inactive companies */
1367 if (Company::IsValidHumanID(company
)) {
1368 int64 args_array
[] = {company
+ 1};
1369 StringParameters
tmp_params(args_array
);
1370 buff
= GetStringWithArgs(buff
, STR_FORMAT_COMPANY_NUM
, &tmp_params
, last
);
1375 case SCC_DEPOT_NAME
: { // {DEPOT}
1376 VehicleType vt
= (VehicleType
)args
->GetInt32(SCC_DEPOT_NAME
);
1377 if (vt
== VEH_AIRCRAFT
) {
1378 uint64 args_array
[] = {(uint64
)args
->GetInt32()};
1379 WChar types_array
[] = {SCC_STATION_NAME
};
1380 StringParameters
tmp_params(args_array
, 1, types_array
);
1381 buff
= GetStringWithArgs(buff
, STR_FORMAT_DEPOT_NAME_AIRCRAFT
, &tmp_params
, last
);
1385 const Depot
*d
= Depot::Get(args
->GetInt32());
1386 if (d
->name
!= nullptr) {
1387 int64 args_array
[] = {(int64
)(size_t)d
->name
};
1388 StringParameters
tmp_params(args_array
);
1389 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1391 int64 args_array
[] = {d
->town
->index
, d
->town_cn
+ 1};
1392 StringParameters
tmp_params(args_array
);
1393 buff
= GetStringWithArgs(buff
, STR_FORMAT_DEPOT_NAME_TRAIN
+ 2 * vt
+ (d
->town_cn
== 0 ? 0 : 1), &tmp_params
, last
);
1398 case SCC_ENGINE_NAME
: { // {ENGINE}
1399 const Engine
*e
= Engine::GetIfValid(args
->GetInt32(SCC_ENGINE_NAME
));
1400 if (e
== nullptr) break;
1402 if (e
->name
!= nullptr && e
->IsEnabled()) {
1403 int64 args_array
[] = {(int64
)(size_t)e
->name
};
1404 StringParameters
tmp_params(args_array
);
1405 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1407 StringParameters
tmp_params(nullptr, 0, nullptr);
1408 buff
= GetStringWithArgs(buff
, e
->info
.string_id
, &tmp_params
, last
);
1413 case SCC_GROUP_NAME
: { // {GROUP}
1414 const Group
*g
= Group::GetIfValid(args
->GetInt32());
1415 if (g
== nullptr) break;
1417 if (g
->name
!= nullptr) {
1418 int64 args_array
[] = {(int64
)(size_t)g
->name
};
1419 StringParameters
tmp_params(args_array
);
1420 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1422 int64 args_array
[] = {g
->index
};
1423 StringParameters
tmp_params(args_array
);
1425 buff
= GetStringWithArgs(buff
, STR_FORMAT_GROUP_NAME
, &tmp_params
, last
);
1430 case SCC_INDUSTRY_NAME
: { // {INDUSTRY}
1431 const Industry
*i
= Industry::GetIfValid(args
->GetInt32(SCC_INDUSTRY_NAME
));
1432 if (i
== nullptr) break;
1434 if (_scan_for_gender_data
) {
1435 /* Gender is defined by the industry type.
1436 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1437 StringParameters
tmp_params(nullptr, 0, nullptr);
1438 buff
= FormatString(buff
, GetStringPtr(GetIndustrySpec(i
->type
)->name
), &tmp_params
, last
, next_substr_case_index
);
1440 /* First print the town name and the industry type name. */
1441 int64 args_array
[2] = {i
->town
->index
, GetIndustrySpec(i
->type
)->name
};
1442 StringParameters
tmp_params(args_array
);
1444 buff
= FormatString(buff
, GetStringPtr(STR_FORMAT_INDUSTRY_NAME
), &tmp_params
, last
, next_substr_case_index
);
1446 next_substr_case_index
= 0;
1450 case SCC_PRESIDENT_NAME
: { // {PRESIDENT_NAME}
1451 const Company
*c
= Company::GetIfValid(args
->GetInt32(SCC_PRESIDENT_NAME
));
1452 if (c
== nullptr) break;
1454 if (c
->president_name
!= nullptr) {
1455 int64 args_array
[] = {(int64
)(size_t)c
->president_name
};
1456 StringParameters
tmp_params(args_array
);
1457 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1459 int64 args_array
[] = {c
->president_name_2
};
1460 StringParameters
tmp_params(args_array
);
1461 buff
= GetStringWithArgs(buff
, c
->president_name_1
, &tmp_params
, last
);
1466 case SCC_STATION_NAME
: { // {STATION}
1467 StationID sid
= args
->GetInt32(SCC_STATION_NAME
);
1468 const Station
*st
= Station::GetIfValid(sid
);
1470 if (st
== nullptr) {
1471 /* The station doesn't exist anymore. The only place where we might
1472 * be "drawing" an invalid station is in the case of cargo that is
1474 StringParameters
tmp_params(nullptr, 0, nullptr);
1475 buff
= GetStringWithArgs(buff
, STR_UNKNOWN_STATION
, &tmp_params
, last
);
1479 if (st
->name
!= nullptr) {
1480 int64 args_array
[] = {(int64
)(size_t)st
->name
};
1481 StringParameters
tmp_params(args_array
);
1482 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1484 StringID str
= st
->string_id
;
1485 if (st
->indtype
!= IT_INVALID
) {
1486 /* Special case where the industry provides the name for the station */
1487 const IndustrySpec
*indsp
= GetIndustrySpec(st
->indtype
);
1489 /* Industry GRFs can change which might remove the station name and
1490 * thus cause very strange things. Here we check for that before we
1491 * actually set the station name. */
1492 if (indsp
->station_name
!= STR_NULL
&& indsp
->station_name
!= STR_UNDEFINED
) {
1493 str
= indsp
->station_name
;
1497 int64 args_array
[] = {STR_TOWN_NAME
, st
->town
->index
, st
->index
};
1498 StringParameters
tmp_params(args_array
);
1499 buff
= GetStringWithArgs(buff
, str
, &tmp_params
, last
);
1504 case SCC_TOWN_NAME
: { // {TOWN}
1505 const Town
*t
= Town::GetIfValid(args
->GetInt32(SCC_TOWN_NAME
));
1506 if (t
== nullptr) break;
1508 if (t
->name
!= nullptr) {
1509 int64 args_array
[] = {(int64
)(size_t)t
->name
};
1510 StringParameters
tmp_params(args_array
);
1511 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1513 buff
= GetTownName(buff
, t
, last
);
1518 case SCC_WAYPOINT_NAME
: { // {WAYPOINT}
1519 Waypoint
*wp
= Waypoint::GetIfValid(args
->GetInt32(SCC_WAYPOINT_NAME
));
1520 if (wp
== nullptr) break;
1522 if (wp
->name
!= nullptr) {
1523 int64 args_array
[] = {(int64
)(size_t)wp
->name
};
1524 StringParameters
tmp_params(args_array
);
1525 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1527 int64 args_array
[] = {wp
->town
->index
, wp
->town_cn
+ 1};
1528 StringParameters
tmp_params(args_array
);
1529 StringID str
= ((wp
->string_id
== STR_SV_STNAME_BUOY
) ? STR_FORMAT_BUOY_NAME
: STR_FORMAT_WAYPOINT_NAME
);
1530 if (wp
->town_cn
!= 0) str
++;
1531 buff
= GetStringWithArgs(buff
, str
, &tmp_params
, last
);
1536 case SCC_VEHICLE_NAME
: { // {VEHICLE}
1537 const Vehicle
*v
= Vehicle::GetIfValid(args
->GetInt32(SCC_VEHICLE_NAME
));
1538 if (v
== nullptr) break;
1540 if (v
->name
!= nullptr) {
1541 int64 args_array
[] = {(int64
)(size_t)v
->name
};
1542 StringParameters
tmp_params(args_array
);
1543 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1544 } else if (v
->group_id
!= DEFAULT_GROUP
) {
1545 /* The vehicle has no name, but is member of a group, so print group name */
1546 int64 args_array
[] = {(uint64
)(size_t)v
->group_id
, v
->unitnumber
};
1547 StringParameters
tmp_params(args_array
);
1548 buff
= GetStringWithArgs(buff
, STR_FORMAT_GROUP_VEHICLE
, &tmp_params
, last
);
1550 int64 args_array
[] = {v
->unitnumber
};
1551 StringParameters
tmp_params(args_array
);
1555 default: str
= STR_INVALID_VEHICLE
; break;
1556 case VEH_TRAIN
: str
= STR_SV_TRAIN_NAME
; break;
1557 case VEH_ROAD
: str
= STR_SV_ROAD_VEHICLE_NAME
; break;
1558 case VEH_SHIP
: str
= STR_SV_SHIP_NAME
; break;
1559 case VEH_AIRCRAFT
: str
= STR_SV_AIRCRAFT_NAME
; break;
1562 buff
= GetStringWithArgs(buff
, str
, &tmp_params
, last
);
1567 case SCC_SIGN_NAME
: { // {SIGN}
1568 const Sign
*si
= Sign::GetIfValid(args
->GetInt32());
1569 if (si
== nullptr) break;
1571 if (si
->name
!= nullptr) {
1572 int64 args_array
[] = {(int64
)(size_t)si
->name
};
1573 StringParameters
tmp_params(args_array
);
1574 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1576 StringParameters
tmp_params(nullptr, 0, nullptr);
1577 buff
= GetStringWithArgs(buff
, STR_DEFAULT_SIGN_NAME
, &tmp_params
, last
);
1582 case SCC_TR_SLOT_NAME
: { // {TRSLOT}
1583 const TraceRestrictSlot
*slot
= TraceRestrictSlot::GetIfValid(args
->GetInt32(SCC_TR_SLOT_NAME
));
1584 if (slot
== nullptr) break;
1585 int64 args_array
[] = {(int64
)(size_t)slot
->name
.c_str()};
1586 StringParameters
tmp_params(args_array
);
1587 buff
= GetStringWithArgs(buff
, STR_JUST_RAW_STRING
, &tmp_params
, last
);
1591 case SCC_STATION_FEATURES
: { // {STATIONFEATURES}
1592 buff
= StationGetSpecialString(buff
, args
->GetInt32(SCC_STATION_FEATURES
), last
);
1596 case SCC_CONSUME_ARG
:
1601 if (buff
+ Utf8CharLen(b
) < last
) buff
+= Utf8Encode(buff
, b
);
1610 static char *StationGetSpecialString(char *buff
, int x
, const char *last
)
1612 if ((x
& FACIL_TRAIN
) && (buff
+ Utf8CharLen(SCC_TRAIN
) < last
)) buff
+= Utf8Encode(buff
, SCC_TRAIN
);
1613 if ((x
& FACIL_TRUCK_STOP
) && (buff
+ Utf8CharLen(SCC_LORRY
) < last
)) buff
+= Utf8Encode(buff
, SCC_LORRY
);
1614 if ((x
& FACIL_BUS_STOP
) && (buff
+ Utf8CharLen(SCC_BUS
) < last
)) buff
+= Utf8Encode(buff
, SCC_BUS
);
1615 if ((x
& FACIL_DOCK
) && (buff
+ Utf8CharLen(SCC_SHIP
) < last
)) buff
+= Utf8Encode(buff
, SCC_SHIP
);
1616 if ((x
& FACIL_AIRPORT
) && (buff
+ Utf8CharLen(SCC_PLANE
) < last
)) buff
+= Utf8Encode(buff
, SCC_PLANE
);
1621 static char *GetSpecialTownNameString(char *buff
, int ind
, uint32 seed
, const char *last
)
1623 return GenerateTownNameString(buff
, last
, ind
, seed
);
1626 static const char * const _silly_company_names
[] = {
1628 "Tiny Transport Ltd.",
1630 "Comfy-Coach & Co.",
1631 "Crush & Bump Ltd.",
1632 "Broken & Late Ltd.",
1634 "Supersonic Travel",
1636 "Lightning International",
1637 "Pannik & Loozit Ltd.",
1638 "Inter-City Transport",
1639 "Getout & Pushit Ltd."
1642 static const char * const _surname_list
[] = {
1674 static const char * const _silly_surname_list
[] = {
1689 static const char _initial_name_letters
[] = {
1690 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1691 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1694 static char *GenAndCoName(char *buff
, uint32 arg
, const char *last
)
1696 const char * const *base
;
1699 if (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) {
1700 base
= _silly_surname_list
;
1701 num
= lengthof(_silly_surname_list
);
1703 base
= _surname_list
;
1704 num
= lengthof(_surname_list
);
1707 buff
= strecpy(buff
, base
[num
* GB(arg
, 16, 8) >> 8], last
);
1708 buff
= strecpy(buff
, " & Co.", last
);
1713 static char *GenPresidentName(char *buff
, uint32 x
, const char *last
)
1715 char initial
[] = "?. ";
1716 const char * const *base
;
1720 initial
[0] = _initial_name_letters
[sizeof(_initial_name_letters
) * GB(x
, 0, 8) >> 8];
1721 buff
= strecpy(buff
, initial
, last
);
1723 i
= (sizeof(_initial_name_letters
) + 35) * GB(x
, 8, 8) >> 8;
1724 if (i
< sizeof(_initial_name_letters
)) {
1725 initial
[0] = _initial_name_letters
[i
];
1726 buff
= strecpy(buff
, initial
, last
);
1729 if (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) {
1730 base
= _silly_surname_list
;
1731 num
= lengthof(_silly_surname_list
);
1733 base
= _surname_list
;
1734 num
= lengthof(_surname_list
);
1737 buff
= strecpy(buff
, base
[num
* GB(x
, 16, 8) >> 8], last
);
1742 static char *GetSpecialNameString(char *buff
, int ind
, StringParameters
*args
, const char *last
)
1746 return strecpy(buff
, _silly_company_names
[min(args
->GetInt32() & 0xFFFF, lengthof(_silly_company_names
) - 1)], last
);
1748 case 2: // used for Foobar & Co company names
1749 return GenAndCoName(buff
, args
->GetInt32(), last
);
1751 case 3: // President name
1752 return GenPresidentName(buff
, args
->GetInt32(), last
);
1756 if (IsInsideMM(ind
- 6, 0, SPECSTR_TOWNNAME_LAST
- SPECSTR_TOWNNAME_START
+ 1)) {
1757 buff
= GetSpecialTownNameString(buff
, ind
- 6, args
->GetInt32(), last
);
1758 return strecpy(buff
, " Transport", last
);
1761 /* language name? */
1762 if (IsInsideMM(ind
, (SPECSTR_LANGUAGE_START
- 0x70E4), (SPECSTR_LANGUAGE_END
- 0x70E4) + 1)) {
1763 int i
= ind
- (SPECSTR_LANGUAGE_START
- 0x70E4);
1764 return strecpy(buff
,
1765 &_languages
[i
] == _current_language
? _current_language
->own_name
: _languages
[i
].name
, last
);
1768 /* resolution size? */
1769 if (IsInsideMM(ind
, (SPECSTR_RESOLUTION_START
- 0x70E4), (SPECSTR_RESOLUTION_END
- 0x70E4) + 1)) {
1770 int i
= ind
- (SPECSTR_RESOLUTION_START
- 0x70E4);
1772 buff
, last
, "%ux%u", _resolutions
[i
].width
, _resolutions
[i
].height
1780 #ifdef ENABLE_NETWORK
1781 extern void SortNetworkLanguages();
1782 #else /* ENABLE_NETWORK */
1783 static inline void SortNetworkLanguages() {}
1784 #endif /* ENABLE_NETWORK */
1787 * Check whether the header is a valid header for OpenTTD.
1788 * @return true iff the header is deemed valid.
1790 bool LanguagePackHeader::IsValid() const
1792 return this->ident
== TO_LE32(LanguagePackHeader::IDENT
) &&
1793 this->version
== TO_LE32(LANGUAGE_PACK_VERSION
) &&
1794 this->plural_form
< LANGUAGE_MAX_PLURAL
&&
1795 this->text_dir
<= 1 &&
1796 this->newgrflangid
< MAX_LANG
&&
1797 this->num_genders
< MAX_NUM_GENDERS
&&
1798 this->num_cases
< MAX_NUM_CASES
&&
1799 StrValid(this->name
, lastof(this->name
)) &&
1800 StrValid(this->own_name
, lastof(this->own_name
)) &&
1801 StrValid(this->isocode
, lastof(this->isocode
)) &&
1802 StrValid(this->digit_group_separator
, lastof(this->digit_group_separator
)) &&
1803 StrValid(this->digit_group_separator_currency
, lastof(this->digit_group_separator_currency
)) &&
1804 StrValid(this->digit_decimal_separator
, lastof(this->digit_decimal_separator
));
1808 * Read a particular language.
1809 * @param lang The metadata about the language.
1810 * @return Whether the loading went okay or not.
1812 bool ReadLanguagePack(const LanguageMetadata
*lang
)
1814 /* Current language pack */
1816 LanguagePack
*lang_pack
= (LanguagePack
*)ReadFileToMem(lang
->file
, &len
, 1U << 20);
1817 if (lang_pack
== nullptr) return false;
1819 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1820 const char *end
= (char *)lang_pack
+ len
+ 1;
1822 /* We need at least one byte of lang_pack->data */
1823 if (end
<= lang_pack
->data
|| !lang_pack
->IsValid()) {
1828 #if TTD_ENDIAN == TTD_BIG_ENDIAN
1829 for (uint i
= 0; i
< TEXT_TAB_END
; i
++) {
1830 lang_pack
->offsets
[i
] = ReadLE16Aligned(&lang_pack
->offsets
[i
]);
1832 #endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */
1835 for (uint i
= 0; i
< TEXT_TAB_END
; i
++) {
1836 uint16 num
= lang_pack
->offsets
[i
];
1837 if (num
> TAB_SIZE
) {
1842 _langtab_start
[i
] = count
;
1843 _langtab_num
[i
] = num
;
1847 /* Allocate offsets */
1848 char **langpack_offs
= MallocT
<char *>(count
);
1851 char *s
= lang_pack
->data
;
1853 for (uint i
= 0; i
< count
; i
++) {
1854 if (s
+ len
>= end
) {
1856 free(langpack_offs
);
1860 len
= ((len
& 0x3F) << 8) + (byte
)*s
++;
1861 if (s
+ len
>= end
) {
1863 free(langpack_offs
);
1867 langpack_offs
[i
] = s
;
1870 *s
++ = '\0'; // zero terminate the string
1874 _langpack
= lang_pack
;
1876 free(_langpack_offs
);
1877 _langpack_offs
= langpack_offs
;
1879 _current_language
= lang
;
1880 _current_text_dir
= (TextDirection
)_current_language
->text_dir
;
1881 const char *c_file
= strrchr(_current_language
->file
, PATHSEPCHAR
) + 1;
1882 strecpy(_config_language_file
, c_file
, lastof(_config_language_file
));
1883 SetCurrentGrfLangID(_current_language
->newgrflangid
);
1885 #ifdef WITH_ICU_SORT
1886 /* Delete previous collator. */
1887 if (_current_collator
!= nullptr) {
1888 delete _current_collator
;
1889 _current_collator
= nullptr;
1892 /* Create a collator instance for our current locale. */
1893 UErrorCode status
= U_ZERO_ERROR
;
1894 _current_collator
= Collator::createInstance(Locale(_current_language
->isocode
), status
);
1895 /* Sort number substrings by their numerical value. */
1896 if (_current_collator
!= nullptr) _current_collator
->setAttribute(UCOL_NUMERIC_COLLATION
, UCOL_ON
, status
);
1897 /* Avoid using the collator if it is not correctly set. */
1898 if (U_FAILURE(status
)) {
1899 delete _current_collator
;
1900 _current_collator
= nullptr;
1902 #endif /* WITH_ICU_SORT */
1904 /* Some lists need to be sorted again after a language change. */
1905 ReconsiderGameScriptLanguage();
1906 InitializeSortedCargoSpecs();
1907 SortIndustryTypes();
1908 BuildIndustriesLegend();
1909 SortNetworkLanguages();
1910 #ifdef ENABLE_NETWORK
1911 BuildContentTypeStringList();
1912 #endif /* ENABLE_NETWORK */
1913 InvalidateWindowClassesData(WC_BUILD_VEHICLE
); // Build vehicle window.
1914 InvalidateWindowClassesData(WC_TRAINS_LIST
); // Train group window.
1915 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS
);
1916 InvalidateWindowClassesData(WC_ROADVEH_LIST
); // Road vehicle group window.
1917 InvalidateWindowClassesData(WC_SHIPS_LIST
); // Ship group window.
1918 InvalidateWindowClassesData(WC_AIRCRAFT_LIST
); // Aircraft group window.
1919 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY
); // Industry directory window.
1920 InvalidateWindowClassesData(WC_STATION_LIST
); // Station list window.
1925 /* Win32 implementation in win32.cpp.
1926 * OS X implementation in os/macosx/macos.mm. */
1927 #if !(defined(WIN32) || defined(__APPLE__))
1929 * Determine the current charset based on the environment
1930 * First check some default values, after this one we passed ourselves
1931 * and if none exist return the value for $LANG
1932 * @param param environment variable to check conditionally if default ones are not
1933 * set. Pass nullptr if you don't want additional checks.
1934 * @return return string containing current charset, or nullptr if not-determinable
1936 const char *GetCurrentLocale(const char *param
)
1940 env
= getenv("LANGUAGE");
1941 if (env
!= nullptr) return env
;
1943 env
= getenv("LC_ALL");
1944 if (env
!= nullptr) return env
;
1946 if (param
!= nullptr) {
1947 env
= getenv(param
);
1948 if (env
!= nullptr) return env
;
1951 return getenv("LANG");
1954 const char *GetCurrentLocale(const char *param
);
1955 #endif /* !(defined(WIN32) || defined(__APPLE__)) */
1957 int CDECL
StringIDSorter(const StringID
*a
, const StringID
*b
)
1961 GetString(stra
, *a
, lastof(stra
));
1962 GetString(strb
, *b
, lastof(strb
));
1964 return strnatcmp(stra
, strb
);
1968 * Get the language with the given NewGRF language ID.
1969 * @param newgrflangid NewGRF languages ID to check.
1970 * @return The language's metadata, or nullptr if it is not known.
1972 const LanguageMetadata
*GetLanguage(byte newgrflangid
)
1974 for (const LanguageMetadata
*lang
= _languages
.Begin(); lang
!= _languages
.End(); lang
++) {
1975 if (newgrflangid
== lang
->newgrflangid
) return lang
;
1982 * Reads the language file header and checks compatibility.
1983 * @param file the file to read
1984 * @param hdr the place to write the header information to
1985 * @return true if and only if the language file is of a compatible version
1987 static bool GetLanguageFileHeader(const char *file
, LanguagePackHeader
*hdr
)
1989 FILE *f
= fopen(file
, "rb");
1990 if (f
== nullptr) return false;
1992 size_t read
= fread(hdr
, sizeof(*hdr
), 1, f
);
1995 bool ret
= read
== 1 && hdr
->IsValid();
1997 /* Convert endianness for the windows language ID */
1999 hdr
->missing
= FROM_LE16(hdr
->missing
);
2000 hdr
->winlangid
= FROM_LE16(hdr
->winlangid
);
2006 * Gets a list of languages from the given directory.
2007 * @param path the base directory to search in
2009 static void GetLanguageList(const char *path
)
2011 DIR *dir
= ttd_opendir(path
);
2012 if (dir
!= nullptr) {
2013 struct dirent
*dirent
;
2014 while ((dirent
= readdir(dir
)) != nullptr) {
2015 const char *d_name
= FS2OTTD(dirent
->d_name
);
2016 const char *extension
= strrchr(d_name
, '.');
2018 /* Not a language file */
2019 if (extension
== nullptr || strcmp(extension
, ".lng") != 0) continue;
2021 LanguageMetadata lmd
;
2022 seprintf(lmd
.file
, lastof(lmd
.file
), "%s%s", path
, d_name
);
2024 /* Check whether the file is of the correct version */
2025 if (!GetLanguageFileHeader(lmd
.file
, &lmd
)) {
2026 DEBUG(misc
, 3, "%s is not a valid language file", lmd
.file
);
2027 } else if (GetLanguage(lmd
.newgrflangid
) != nullptr) {
2028 DEBUG(misc
, 3, "%s's language ID is already known", lmd
.file
);
2030 *_languages
.Append() = lmd
;
2038 * Make a list of the available language packs. Put the data in
2041 void InitializeLanguagePacks()
2045 FOR_ALL_SEARCHPATHS(sp
) {
2046 char path
[MAX_PATH
];
2047 FioAppendDirectory(path
, lastof(path
), sp
, LANG_DIR
);
2048 GetLanguageList(path
);
2050 if (_languages
.Length() == 0) usererror("No available language packs (invalid versions?)");
2052 /* Acquire the locale of the current system */
2053 const char *lang
= GetCurrentLocale("LC_MESSAGES");
2054 if (lang
== nullptr) lang
= "en_GB";
2056 const LanguageMetadata
*chosen_language
= nullptr; ///< Matching the language in the configuration file or the current locale
2057 const LanguageMetadata
*language_fallback
= nullptr; ///< Using pt_PT for pt_BR locale when pt_BR is not available
2058 const LanguageMetadata
*en_GB_fallback
= _languages
.Begin(); ///< Fallback when no locale-matching language has been found
2060 /* Find a proper language. */
2061 for (const LanguageMetadata
*lng
= _languages
.Begin(); lng
!= _languages
.End(); lng
++) {
2062 /* We are trying to find a default language. The priority is by
2063 * configuration file, local environment and last, if nothing found,
2065 const char *lang_file
= strrchr(lng
->file
, PATHSEPCHAR
) + 1;
2066 if (strcmp(lang_file
, _config_language_file
) == 0) {
2067 chosen_language
= lng
;
2071 if (strcmp (lng
->isocode
, "en_GB") == 0) en_GB_fallback
= lng
;
2072 if (strncmp(lng
->isocode
, lang
, 5) == 0) chosen_language
= lng
;
2073 if (strncmp(lng
->isocode
, lang
, 2) == 0) language_fallback
= lng
;
2076 /* We haven't found the language in the config nor the one in the locale.
2077 * Now we set it to one of the fallback languages */
2078 if (chosen_language
== nullptr) {
2079 chosen_language
= (language_fallback
!= nullptr) ? language_fallback
: en_GB_fallback
;
2082 if (!ReadLanguagePack(chosen_language
)) usererror("Can't read language pack '%s'", chosen_language
->file
);
2086 * Get the ISO language code of the currently loaded language.
2087 * @return the ISO code.
2089 const char *GetCurrentLanguageIsoCode()
2091 return _langpack
->isocode
;
2095 * Check whether there are glyphs missing in the current language.
2096 * @param Pointer to an address for storing the text pointer.
2097 * @return If glyphs are missing, return \c true, else return \c false.
2098 * @post If \c true is returned and str is not nullptr, *str points to a string that is found to contain at least one missing glyph.
2100 bool MissingGlyphSearcher::FindMissingGlyphs(const char **str
)
2102 InitFreeType(this->Monospace());
2103 const Sprite
*question_mark
[FS_END
];
2105 for (FontSize size
= this->Monospace() ? FS_MONO
: FS_BEGIN
; size
< (this->Monospace() ? FS_END
: FS_MONO
); size
++) {
2106 question_mark
[size
] = GetGlyph(size
, '?');
2110 for (const char *text
= this->NextString(); text
!= nullptr; text
= this->NextString()) {
2111 FontSize size
= this->DefaultSize();
2112 if (str
!= nullptr) *str
= text
;
2113 for (WChar c
= Utf8Consume(&text
); c
!= '\0'; c
= Utf8Consume(&text
)) {
2114 if (c
== SCC_TINYFONT
) {
2116 } else if (c
== SCC_BIGFONT
) {
2118 } else if (!IsInsideMM(c
, SCC_SPRITE_START
, SCC_SPRITE_END
) && IsPrintable(c
) && !IsTextDirectionChar(c
) && c
!= '?' && GetGlyph(size
, c
) == question_mark
[size
]) {
2119 /* The character is printable, but not in the normal font. This is the case we were testing for. */
2127 /** Helper for searching through the language pack. */
2128 class LanguagePackGlyphSearcher
: public MissingGlyphSearcher
{
2129 uint i
; ///< Iterator for the primary language tables.
2130 uint j
; ///< Iterator for the secondary language tables.
2132 /* virtual */ void Reset()
2138 /* virtual */ FontSize
DefaultSize()
2143 /* virtual */ const char *NextString()
2145 if (this->i
>= TEXT_TAB_END
) return nullptr;
2147 const char *ret
= _langpack_offs
[_langtab_start
[this->i
] + this->j
];
2150 while (this->i
< TEXT_TAB_END
&& this->j
>= _langtab_num
[this->i
]) {
2158 /* virtual */ bool Monospace()
2163 /* virtual */ void SetFontNames(FreeTypeSettings
*settings
, const char *font_name
)
2165 #ifdef WITH_FREETYPE
2166 strecpy(settings
->small
.font
, font_name
, lastof(settings
->small
.font
));
2167 strecpy(settings
->medium
.font
, font_name
, lastof(settings
->medium
.font
));
2168 strecpy(settings
->large
.font
, font_name
, lastof(settings
->large
.font
));
2169 #endif /* WITH_FREETYPE */
2174 * Check whether the currently loaded language pack
2175 * uses characters that the currently loaded font
2176 * does not support. If this is the case an error
2177 * message will be shown in English. The error
2178 * message will not be localized because that would
2179 * mean it might use characters that are not in the
2180 * font, which is the whole reason this check has
2182 * @param base_font Whether to look at the base font as well.
2183 * @param searcher The methods to use to search for strings to check.
2184 * If nullptr the loaded language pack searcher is used.
2186 void CheckForMissingGlyphs(bool base_font
, MissingGlyphSearcher
*searcher
)
2188 static LanguagePackGlyphSearcher pack_searcher
;
2189 if (searcher
== nullptr) searcher
= &pack_searcher
;
2190 bool bad_font
= !base_font
|| searcher
->FindMissingGlyphs(nullptr);
2191 #ifdef WITH_FREETYPE
2193 /* We found an unprintable character... lets try whether we can find
2194 * a fallback font that can print the characters in the current language. */
2195 FreeTypeSettings backup
;
2196 memcpy(&backup
, &_freetype
, sizeof(backup
));
2198 bad_font
= !SetFallbackFont(&_freetype
, _langpack
->isocode
, _langpack
->winlangid
, searcher
);
2200 memcpy(&_freetype
, &backup
, sizeof(backup
));
2202 if (bad_font
&& base_font
) {
2203 /* Our fallback font does miss characters too, so keep the
2204 * user chosen font as that is more likely to be any good than
2205 * the wild guess we made */
2206 InitFreeType(searcher
->Monospace());
2212 /* All attempts have failed. Display an error. As we do not want the string to be translated by
2213 * the translators, we 'force' it into the binary and 'load' it via a BindCString. To do this
2214 * properly we have to set the colour of the string, otherwise we end up with a lot of artifacts.
2215 * The colour 'character' might change in the future, so for safety we just Utf8 Encode it into
2216 * the string, which takes exactly three characters, so it replaces the "XXX" with the colour marker. */
2217 static char *err_str
= stredup("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.");
2218 Utf8Encode(err_str
, SCC_YELLOW
);
2219 SetDParamStr(0, err_str
);
2220 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_WARNING
);
2222 /* Reset the font width */
2223 LoadStringWidthTable(searcher
->Monospace());
2227 /* Update the font with cache */
2228 LoadStringWidthTable(searcher
->Monospace());
2230 #if !defined(WITH_ICU_LAYOUT)
2232 * For right-to-left languages we need the ICU library. If
2233 * we do not have support for that library we warn the user
2234 * about it with a message. As we do not want the string to
2235 * be translated by the translators, we 'force' it into the
2236 * binary and 'load' it via a BindCString. To do this
2237 * properly we have to set the colour of the string,
2238 * otherwise we end up with a lot of artifacts. The colour
2239 * 'character' might change in the future, so for safety
2240 * we just Utf8 Encode it into the string, which takes
2241 * exactly three characters, so it replaces the "XXX" with
2242 * the colour marker.
2244 if (_current_text_dir
!= TD_LTR
) {
2245 static char *err_str
= stredup("XXXThis version of OpenTTD does not support right-to-left languages. Recompile with icu enabled.");
2246 Utf8Encode(err_str
, SCC_YELLOW
);
2247 SetDParamStr(0, err_str
);
2248 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_ERROR
);
2250 #endif /* !WITH_ICU_LAYOUT */