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. */
14 #include "station_base.h"
16 #include "screenshot.h"
17 #include "waypoint_base.h"
18 #include "depot_base.h"
20 #include "newgrf_text.h"
21 #include "fileio_func.h"
22 #include "signs_base.h"
23 #include "fontdetection.h"
25 #include "strings_func.h"
27 #include "core/endian_func.hpp"
28 #include "date_func.h"
29 #include "vehicle_base.h"
30 #include "engine_base.h"
32 #include "townnamegen.h"
33 #include "townname_func.h"
35 #include "company_base.h"
36 #include "smallmap_gui.h"
37 #include "window_func.h"
39 #include "game/game_text.hpp"
40 #include "network/network_content_gui.h"
43 #include "table/strings.h"
44 #include "table/control_codes.h"
46 char _config_language_file
[MAX_PATH
]; ///< The file (name) stored in the configuration.
47 LanguageList _languages
; ///< The actual list of language meta data.
48 const LanguageMetadata
*_current_language
= NULL
; ///< The currently loaded language.
50 TextDirection _current_text_dir
; ///< Text direction of the currently selected language.
53 Collator
*_current_collator
= NULL
; ///< Collator for the language currently in use.
54 #endif /* WITH_ICU_SORT */
56 static uint64 _global_string_params_data
[20]; ///< Global array of string parameters. To access, use #SetDParam.
57 static WChar _global_string_params_type
[20]; ///< Type of parameters stored in #_decode_parameters
58 StringParameters
_global_string_params(_global_string_params_data
, 20, _global_string_params_type
);
60 /** Reset the type array. */
61 void StringParameters::ClearTypeInformation()
63 assert(this->type
!= NULL
);
64 MemSetT(this->type
, 0, this->num_param
);
69 * Read an int64 from the argument array. The offset is increased
70 * so the next time GetInt64 is called the next value is read.
72 int64
StringParameters::GetInt64(WChar type
)
74 if (this->offset
>= this->num_param
) {
75 DEBUG(misc
, 0, "Trying to read invalid string parameter");
78 if (this->type
!= NULL
) {
79 assert(this->type
[this->offset
] == 0 || this->type
[this->offset
] == type
);
80 this->type
[this->offset
] = type
;
82 return this->data
[this->offset
++];
86 * Set DParam n to some number that is suitable for string size computations.
87 * @param n Index of the string parameter.
88 * @param max_value The biggest value which shall be displayed.
89 * For the result only the number of digits of \a max_value matter.
90 * @param min_count Minimum number of digits independent of \a max.
91 * @param size Font of the number
93 void SetDParamMaxValue(uint n
, uint64 max_value
, uint min_count
, FontSize size
)
96 while (max_value
>= 10) {
100 SetDParamMaxDigits(n
, max(min_count
, num_digits
), size
);
104 * Set DParam n to some number that is suitable for string size computations.
105 * @param n Index of the string parameter.
106 * @param count Number of digits which shall be displayable.
107 * @param size Font of the number
109 void SetDParamMaxDigits(uint n
, uint count
, FontSize size
)
111 SetDParam (n
, FontCache::Get(size
)->GetBroadestValue (count
));
115 * Copy \a num string parameters from array \a src into the global string parameter array.
116 * @param offs Index in the global array to copy the first string parameter to.
117 * @param src Source array of string parameters.
118 * @param num Number of string parameters to copy.
120 void CopyInDParam(int offs
, const uint64
*src
, int num
)
122 MemCpyT(_global_string_params
.GetPointerToOffset(offs
), src
, num
);
126 * Copy \a num string parameters from the global string parameter array to the \a dst array.
127 * @param dst Destination array of string parameters.
128 * @param offs Index in the global array to copy the first string parameter from.
129 * @param num Number of string parameters to copy.
131 void CopyOutDParam(uint64
*dst
, int offs
, int num
)
133 MemCpyT(dst
, _global_string_params
.GetPointerToOffset(offs
), num
);
137 * Copy \a num string parameters from the global string parameter array to the \a dst array.
138 * Furthermore clone raw string parameters into \a strings and amend the data in \a dst.
139 * @param dst Destination array of string parameters.
140 * @param strings Destination array for clone of the raw strings. Must be of same length as dst. Deallocation left to the caller.
141 * @param string The string used to determine where raw strings are and where there are no raw strings.
142 * @param num Number of string parameters to copy.
144 void CopyOutDParam(uint64
*dst
, const char **strings
, StringID string
, int num
)
146 char buf
[DRAW_STRING_BUFFER
];
147 GetString (buf
, string
);
149 MemCpyT(dst
, _global_string_params
.GetPointerToOffset(0), num
);
150 for (int i
= 0; i
< num
; i
++) {
151 if (_global_string_params
.HasTypeInformation() && _global_string_params
.GetTypeAtOffset(i
) == SCC_RAW_STRING_POINTER
) {
152 strings
[i
] = xstrdup((const char *)(size_t)_global_string_params
.GetParam(i
));
153 dst
[i
] = (size_t)strings
[i
];
160 static void AppendStationSpecialString (stringb
*buf
, int x
);
161 static void AppendSpecialNameString (stringb
*buf
, int ind
, StringParameters
*args
);
163 static void FormatString (stringb
*buf
, const char *str
, StringParameters
*args
, uint case_index
= 0, bool game_script
= false, bool dry_run
= false);
165 struct LanguagePack
: public LanguagePackHeader
{
166 char data
[]; // list of strings
169 static char **_langpack_offs
;
170 static LanguagePack
*_langpack
;
171 static uint _langtab_num
[TAB_COUNT
]; ///< Offset into langpack offs
172 static uint _langtab_start
[TAB_COUNT
]; ///< Offset into langpack offs
173 static bool _scan_for_gender_data
= false; ///< Are we scanning for the gender of the current string? (instead of formatting it)
176 const char *GetStringPtr(StringID string
)
178 switch (GB(string
, TAB_COUNT_OFFSET
, TAB_COUNT_BITS
)) {
179 case GAME_TEXT_TAB
: return GetGameStringPtr(GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
));
180 /* 0xD0xx and 0xD4xx IDs have been converted earlier. */
181 case 26: NOT_REACHED();
182 case 28: return GetGRFStringPtr(GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
));
183 case 29: return GetGRFStringPtr(GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
) + 0x0800);
184 case 30: return GetGRFStringPtr(GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
) + 0x1000);
185 default: return _langpack_offs
[_langtab_start
[GB(string
, TAB_COUNT_OFFSET
, TAB_COUNT_BITS
)] + GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
)];
190 * Get a parsed string with most special stringcodes replaced by the string parameters.
191 * @param buf Buffer where the formatted string should be written to.
193 * @param args Arguments for the string.
194 * @param case_index The "case index". This will only be set when FormatString wants to print the string in a different case.
195 * @param game_script The string is coming directly from a game script.
197 void AppendStringWithArgs (stringb
*buf
, StringID string
, StringParameters
*args
, uint case_index
, bool game_script
)
200 AppendStringWithArgs (buf
, STR_UNDEFINED
, args
);
204 uint index
= GB(string
, TAB_SIZE_OFFSET
, TAB_SIZE_BITS
);
205 uint tab
= GB(string
, TAB_COUNT_OFFSET
, TAB_COUNT_BITS
);
209 if (index
>= 0xC0 && !game_script
) {
210 GenerateTownNameString (buf
, index
- 0xC0, args
->GetInt32());
216 if (index
>= 0xE4 && !game_script
) {
217 AppendSpecialNameString (buf
, index
- 0xE4, args
);
223 /* Old table for custom names. This is no longer used */
225 error("Incorrect conversion of custom name string.");
230 FormatString (buf
, GetGameStringPtr(index
), args
, case_index
, true);
237 FormatString (buf
, GetGRFStringPtr(index
), args
, case_index
);
241 FormatString (buf
, GetGRFStringPtr(index
+ 0x0800), args
, case_index
);
245 FormatString (buf
, GetGRFStringPtr(index
+ 0x1000), args
, case_index
);
249 if (index
>= _langtab_num
[tab
]) {
251 AppendStringWithArgs (buf
, STR_UNDEFINED
, args
);
254 error("String 0x%X is invalid. You are probably using an old version of the .lng file.\n", string
);
257 FormatString (buf
, GetStringPtr(string
), args
, case_index
);
260 void AppendString (stringb
*buf
, StringID string
)
262 _global_string_params
.ClearTypeInformation();
263 _global_string_params
.offset
= 0;
264 AppendStringWithArgs (buf
, string
, &_global_string_params
);
269 * This function is used to "bind" a C string to a OpenTTD dparam slot.
270 * @param n slot of the string
271 * @param str string to bind
273 void SetDParamStr(uint n
, const char *str
)
275 SetDParam(n
, (uint64
)(size_t)str
);
279 * Format a number into a string.
280 * @param buf the buffer to write to
281 * @param number the number to write down
282 * @param separator the thousands-separator to use
283 * @param zerofill minimum number of digits to print for the integer part. The number will be filled with zeros at the front if necessary.
284 * @param fractional_digits number of fractional digits to display after a decimal separator. The decimal separator is inserted
285 * in front of the \a fractional_digits last digit of \a number.
287 static void FormatNumber (stringb
*buf
, int64 number
, const char *separator
, int zerofill
= 1, int fractional_digits
= 0)
289 static const int max_digits
= 20;
290 uint64 divisor
= 10000000000000000000ULL;
291 zerofill
+= fractional_digits
;
292 int thousands_offset
= (max_digits
- fractional_digits
- 1) % 3;
301 for (int i
= 0; i
< max_digits
; i
++) {
302 if (i
== max_digits
- fractional_digits
) {
303 const char *decimal_separator
= _settings_game
.locale
.digit_decimal_separator
;
304 if (decimal_separator
== NULL
) decimal_separator
= _langpack
->digit_decimal_separator
;
305 buf
->append (decimal_separator
);
309 if (num
>= divisor
) {
310 quot
= num
/ divisor
;
313 if ((tot
|= quot
) || i
>= max_digits
- zerofill
) {
314 buf
->append_fmt ("%i", (int)quot
);
315 if ((i
% 3) == thousands_offset
&& i
< max_digits
- 1 - fractional_digits
) buf
->append (separator
);
322 static void FormatCommaNumber (stringb
*buf
, int64 number
, int fractional_digits
= 0)
324 const char *separator
= _settings_game
.locale
.digit_group_separator
;
325 if (separator
== NULL
) separator
= _langpack
->digit_group_separator
;
326 FormatNumber (buf
, number
, separator
, 1, fractional_digits
);
329 static void FormatNoCommaNumber (stringb
*buf
, int64 number
)
331 FormatNumber (buf
, number
, "");
334 static void FormatZerofillNumber (stringb
*buf
, int64 number
, int64 count
)
336 FormatNumber (buf
, number
, "", count
);
339 static void FormatHexNumber (stringb
*buf
, uint64 number
)
341 buf
->append_fmt ("0x" OTTD_PRINTFHEX64
, number
);
345 * Format a given number as a number of bytes with the SI prefix.
346 * @param buf the buffer to write to
347 * @param number the number of bytes to write down
349 static void FormatBytes (stringb
*buf
, int64 number
)
354 buf
->append_fmt ("%i" NBSP
"B", (int)number
);
358 /* 2^10 2^20 2^30 2^40 2^50 2^60 */
359 static const char iec_prefixes
[] = {'K', 'M', 'G', 'T', 'P', 'E'};
361 while (number
>= 1024 * 1024) {
366 const char *decimal_separator
= _settings_game
.locale
.digit_decimal_separator
;
367 if (decimal_separator
== NULL
) decimal_separator
= _langpack
->digit_decimal_separator
;
369 if (number
< 1024 * 10) {
370 buf
->append_fmt ("%i%s%02i", (int)number
/ 1024, decimal_separator
, (int)(number
% 1024) * 100 / 1024);
371 } else if (number
< 1024 * 100) {
372 buf
->append_fmt ("%i%s%01i", (int)number
/ 1024, decimal_separator
, (int)(number
% 1024) * 10 / 1024);
374 assert(number
< 1024 * 1024);
375 buf
->append_fmt ("%i", (int)number
/ 1024);
378 assert(id
< lengthof(iec_prefixes
));
379 buf
->append_fmt (NBSP
"%ciB", iec_prefixes
[id
]);
382 static void FormatYmdString (stringb
*buf
, Date date
, uint case_index
)
385 ConvertDateToYMD(date
, &ymd
);
387 int64 args
[] = {ymd
.day
+ STR_DAY_NUMBER_1ST
- 1, STR_MONTH_ABBREV_JAN
+ ymd
.month
, ymd
.year
};
388 StringParameters
tmp_params(args
);
389 FormatString (buf
, GetStringPtr(STR_FORMAT_DATE_LONG
), &tmp_params
, case_index
);
392 static void FormatMonthAndYear (stringb
*buf
, Date date
, uint case_index
)
395 ConvertDateToYMD(date
, &ymd
);
397 int64 args
[] = {STR_MONTH_JAN
+ ymd
.month
, ymd
.year
};
398 StringParameters
tmp_params(args
);
399 FormatString (buf
, GetStringPtr(STR_FORMAT_DATE_SHORT
), &tmp_params
, case_index
);
402 static void FormatTinyOrISODate (stringb
*buf
, Date date
, StringID str
)
405 ConvertDateToYMD(date
, &ymd
);
409 /* We want to zero-pad the days and months */
410 snprintf(day
, lengthof(day
), "%02i", ymd
.day
);
411 snprintf(month
, lengthof(month
), "%02i", ymd
.month
+ 1);
413 int64 args
[] = {(int64
)(size_t)day
, (int64
)(size_t)month
, ymd
.year
};
414 StringParameters
tmp_params(args
);
415 FormatString (buf
, GetStringPtr(str
), &tmp_params
);
418 static void FormatGenericCurrency (stringb
*buf
, const CurrencySpec
*spec
, Money number
, bool compact
)
420 /* We are going to make number absolute for printing, so
421 * keep this piece of data as we need it later on */
422 bool negative
= number
< 0;
423 const char *multiplier
= "";
425 number
*= spec
->rate
;
427 /* convert from negative */
429 if (!buf
->append_utf8(SCC_RED
)) return;
434 /* Add prefix part, following symbol_pos specification.
435 * Here, it can can be either 0 (prefix) or 2 (both prefix and suffix).
436 * The only remaining value is 1 (suffix), so everything that is not 1 */
437 if (spec
->symbol_pos
!= 1) buf
->append (spec
->prefix
);
439 /* for huge numbers, compact the number into k or M */
441 /* Take care of the 'k' rounding. Having 1 000 000 k
442 * and 1 000 M is inconsistent, so always use 1 000 M. */
443 if (number
>= 1000000000 - 500) {
444 number
= (number
+ 500000) / 1000000;
445 multiplier
= NBSP
"M";
446 } else if (number
>= 1000000) {
447 number
= (number
+ 500) / 1000;
448 multiplier
= NBSP
"k";
452 const char *separator
= _settings_game
.locale
.digit_group_separator_currency
;
453 if (separator
== NULL
&& !StrEmpty(_currency
->separator
)) separator
= _currency
->separator
;
454 if (separator
== NULL
) separator
= _langpack
->digit_group_separator_currency
;
455 FormatNumber (buf
, number
, separator
);
456 buf
->append (multiplier
);
458 /* Add suffix part, following symbol_pos specification.
459 * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).
460 * The only remaining value is 1 (prefix), so everything that is not 0 */
461 if (spec
->symbol_pos
!= 0) buf
->append (spec
->suffix
);
463 if (negative
) buf
->append_utf8(SCC_PREVIOUS_COLOUR
);
467 * Determine the "plural" index given a plural form and a number.
468 * @param count The number to get the plural index of.
469 * @param plural_form The plural form we want an index for.
470 * @return The plural index for the given form.
472 static int DeterminePluralForm(int64 count
, int plural_form
)
474 /* The absolute value determines plurality */
475 uint64 n
= abs(count
);
477 switch (plural_form
) {
481 /* Two forms: singular used for one only.
483 * Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish,
484 * Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto */
486 return n
!= 1 ? 1 : 0;
490 * Hungarian, Japanese, Korean, Turkish */
494 /* Two forms: singular used for 0 and 1.
496 * French, Brazilian Portuguese */
498 return n
> 1 ? 1 : 0;
500 /* Three forms: special cases for 0, and numbers ending in 1 except when ending in 11.
501 * Note: Cases are out of order for hysterical reasons. '0' is last.
505 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
!= 0 ? 1 : 2;
507 /* Five forms: special cases for 1, 2, 3 to 6, and 7 to 10.
511 return n
== 1 ? 0 : n
== 2 ? 1 : n
< 7 ? 2 : n
< 11 ? 3 : 4;
513 /* 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.
517 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
519 /* 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.
521 * Croatian, Russian, Ukrainian */
523 return n
% 10 == 1 && n
% 100 != 11 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
525 /* Three forms: special cases for 1, and numbers ending in 2 to 4 except when ending in 12 to 14.
529 return n
== 1 ? 0 : n
% 10 >= 2 && n
% 10 <= 4 && (n
% 100 < 10 || n
% 100 >= 20) ? 1 : 2;
531 /* Four forms: special cases for numbers ending in 01, 02, and 03 to 04.
535 return n
% 100 == 1 ? 0 : n
% 100 == 2 ? 1 : n
% 100 == 3 || n
% 100 == 4 ? 2 : 3;
537 /* Two forms: singular used for numbers ending in 1 except when ending in 11.
541 return n
% 10 == 1 && n
% 100 != 11 ? 0 : 1;
543 /* Three forms: special cases for 1, and 2 to 4
547 return n
== 1 ? 0 : n
>= 2 && n
<= 4 ? 1 : 2;
549 /* Two forms: cases for numbers ending with a consonant, and with a vowel.
550 * Korean doesn't have the concept of plural, but depending on how a
551 * number is pronounced it needs another version of a particle.
552 * As such the plural system is misused to give this distinction.
574 /* Four forms: special cases for 1, 0 and numbers ending in 02 to 10, and numbers ending in 11 to 19.
578 return (n
== 1 ? 0 : n
== 0 || (n
% 100 > 1 && n
% 100 < 11) ? 1 : (n
% 100 > 10 && n
% 100 < 20) ? 2 : 3);
579 /* Four forms: special cases for 1 and 11, 2 and 12, 3 .. 10 and 13 .. 19, other
583 return ((n
== 1 || n
== 11) ? 0 : (n
== 2 || n
== 12) ? 1 : ((n
> 2 && n
< 11) || (n
> 12 && n
< 20)) ? 2 : 3);
587 static const char *ParseStringChoice (const char *b
, uint form
, stringb
*dst
)
589 /* <NUM> {Length of each string} {each string} */
591 uint pos
, i
, mypos
= 0;
593 for (i
= pos
= 0; i
!= n
; i
++) {
594 uint len
= (byte
)*b
++;
595 if (i
== form
) mypos
= pos
;
599 dst
->append (b
+ mypos
);
603 /** Helper for unit conversion. */
604 struct UnitConversion
{
605 int multiplier
; ///< Amount to multiply upon conversion.
606 int shift
; ///< Amount to shift upon conversion.
609 * Convert value from OpenTTD's internal unit into the displayed value.
610 * @param input The input to convert.
611 * @param round Whether to round the value or not.
612 * @return The converted value.
614 int64
ToDisplay(int64 input
, bool round
= true) const
616 return ((input
* this->multiplier
) + (round
&& this->shift
!= 0 ? 1 << (this->shift
- 1) : 0)) >> this->shift
;
620 * Convert the displayed value back into a value of OpenTTD's internal unit.
621 * @param input The input to convert.
622 * @param round Whether to round the value up or not.
623 * @param divider Divide the return value by this.
624 * @return The converted value.
626 int64
FromDisplay(int64 input
, bool round
= true, int64 divider
= 1) const
628 return ((input
<< this->shift
) + (round
? (this->multiplier
* divider
) - 1 : 0)) / (this->multiplier
* divider
);
632 /** Information about a specific unit system. */
634 UnitConversion c
; ///< Conversion
635 StringID s
; ///< String for the unit
638 /** Information about a specific unit system with a long variant. */
640 UnitConversion c
; ///< Conversion
641 StringID s
; ///< String for the short variant of the unit
642 StringID l
; ///< String for the long variant of the unit
645 /** Unit conversions for velocity. */
646 static const Units _units_velocity
[] = {
647 { { 1, 0}, STR_UNITS_VELOCITY_IMPERIAL
},
648 { { 103, 6}, STR_UNITS_VELOCITY_METRIC
},
649 { {1831, 12}, STR_UNITS_VELOCITY_SI
},
652 /** Unit conversions for velocity. */
653 static const Units _units_power
[] = {
654 { { 1, 0}, STR_UNITS_POWER_IMPERIAL
},
655 { {4153, 12}, STR_UNITS_POWER_METRIC
},
656 { {6109, 13}, STR_UNITS_POWER_SI
},
659 /** Unit conversions for weight. */
660 static const UnitsLong _units_weight
[] = {
661 { {4515, 12}, STR_UNITS_WEIGHT_SHORT_IMPERIAL
, STR_UNITS_WEIGHT_LONG_IMPERIAL
},
662 { { 1, 0}, STR_UNITS_WEIGHT_SHORT_METRIC
, STR_UNITS_WEIGHT_LONG_METRIC
},
663 { {1000, 0}, STR_UNITS_WEIGHT_SHORT_SI
, STR_UNITS_WEIGHT_LONG_SI
},
666 /** Unit conversions for volume. */
667 static const UnitsLong _units_volume
[] = {
668 { {4227, 4}, STR_UNITS_VOLUME_SHORT_IMPERIAL
, STR_UNITS_VOLUME_LONG_IMPERIAL
},
669 { {1000, 0}, STR_UNITS_VOLUME_SHORT_METRIC
, STR_UNITS_VOLUME_LONG_METRIC
},
670 { { 1, 0}, STR_UNITS_VOLUME_SHORT_SI
, STR_UNITS_VOLUME_LONG_SI
},
673 /** Unit conversions for force. */
674 static const Units _units_force
[] = {
675 { {3597, 4}, STR_UNITS_FORCE_IMPERIAL
},
676 { {3263, 5}, STR_UNITS_FORCE_METRIC
},
677 { { 1, 0}, STR_UNITS_FORCE_SI
},
680 /** Unit conversions for height. */
681 static const Units _units_height
[] = {
682 { { 3, 0}, STR_UNITS_HEIGHT_IMPERIAL
}, // "Wrong" conversion factor for more nicer GUI values
683 { { 1, 0}, STR_UNITS_HEIGHT_METRIC
},
684 { { 1, 0}, STR_UNITS_HEIGHT_SI
},
688 * Convert the given (internal) speed to the display speed.
689 * @param speed the speed to convert
690 * @return the converted speed.
692 uint
ConvertSpeedToDisplaySpeed(uint speed
)
694 /* For historical reasons we don't want to mess with the
695 * conversion for speed. So, don't round it and keep the
696 * original conversion factors instead of the real ones. */
697 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.ToDisplay(speed
, false);
701 * Convert the given display speed to the (internal) speed.
702 * @param speed the speed to convert
703 * @return the converted speed.
705 uint
ConvertDisplaySpeedToSpeed(uint speed
)
707 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.FromDisplay(speed
);
711 * Convert the given km/h-ish speed to the display speed.
712 * @param speed the speed to convert
713 * @return the converted speed.
715 uint
ConvertKmhishSpeedToDisplaySpeed(uint speed
)
717 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.ToDisplay(speed
* 10, false) / 16;
721 * Convert the given display speed to the km/h-ish speed.
722 * @param speed the speed to convert
723 * @return the converted speed.
725 uint
ConvertDisplaySpeedToKmhishSpeed(uint speed
)
727 return _units_velocity
[_settings_game
.locale
.units_velocity
].c
.FromDisplay(speed
* 16, true, 10);
730 * Parse most format codes within a string and write the result to a buffer.
731 * @param buf The buffer to write the final string to.
732 * @param str The original string with format codes.
733 * @param args Pointer to extra arguments used by various string codes.
735 * @param dry_run True when the argt array is not yet initialized.
737 static void FormatString (stringb
*buf
, const char *str_arg
, StringParameters
*args
, uint case_index
, bool game_script
, bool dry_run
)
739 uint orig_offset
= args
->offset
;
741 /* When there is no array with types there is no need to do a dry run. */
742 if (args
->HasTypeInformation() && !dry_run
) {
743 size_t length
= buf
->length();
744 if (UsingNewGRFTextStack()) {
745 /* Values from the NewGRF text stack are only copied to the normal
746 * argv array at the time they are encountered. That means that if
747 * another string command references a value later in the string it
748 * would fail. We solve that by running FormatString twice. The first
749 * pass makes sure the argv array is correctly filled and the second
750 * pass can reference later values without problems. */
751 struct TextRefStack
*backup
= CreateTextRefStackBackup();
752 FormatString (buf
, str_arg
, args
, case_index
, game_script
, true);
753 RestoreTextRefStackBackup(backup
);
755 FormatString (buf
, str_arg
, args
, case_index
, game_script
, true);
757 buf
->truncate (length
);
758 /* We have to restore the original offset here to to read the correct values. */
759 args
->offset
= orig_offset
;
762 uint next_substr_case_index
= 0;
763 std::stack
<const char *> str_stack
;
764 str_stack
.push(str_arg
);
767 while (!str_stack
.empty() && (b
= Utf8Consume(&str_stack
.top())) == '\0') {
770 if (str_stack
.empty()) break;
771 const char *&str
= str_stack
.top();
773 if (SCC_NEWGRF_FIRST
<= b
&& b
<= SCC_NEWGRF_LAST
) {
774 /* We need to pass some stuff as it might be modified; oh boy. */
775 //todo: should argve be passed here too?
776 b
= RemapNewGRFStringControlCode (b
, buf
, &str
, (int64
*)args
->GetDataPointer(), args
->GetDataLeft(), dry_run
);
777 if (b
== 0) continue;
782 uint64 sub_args_data
[20];
783 WChar sub_args_type
[20];
784 bool sub_args_need_free
[20];
785 StringParameters
sub_args(sub_args_data
, 20, sub_args_type
);
787 sub_args
.ClearTypeInformation();
788 memset(sub_args_need_free
, 0, sizeof(sub_args_need_free
));
793 stringid
= strtol(str
, &p
, 16);
794 if (*p
!= ':' && *p
!= '\0') {
795 while (*p
!= '\0') p
++;
797 buf
->append ("(invalid SCC_ENCODED)");
800 if (stringid
>= TAB_SIZE
) {
801 while (*p
!= '\0') p
++;
803 buf
->append ("(invalid StringID)");
808 while (*p
!= '\0' && i
< 20) {
812 /* Find the next value */
813 bool instring
= false;
820 if (*p
== '"' && escape
) {
827 instring
= !instring
;
834 if (*p
== ':') break;
835 if (*p
== '\0') break;
839 /* Check if we want to look up another string */
841 size_t len
= Utf8Decode(&l
, s
);
842 bool lookup
= (l
== SCC_ENCODED
);
843 if (lookup
) s
+= len
;
845 param
= strtoull(s
, &p
, 16);
848 if (param
>= TAB_SIZE
) {
849 while (*p
!= '\0') p
++;
851 buf
->append ("(invalid sub-StringID)");
854 param
= (GAME_TEXT_TAB
<< TAB_COUNT_OFFSET
) + param
;
857 sub_args
.SetParam(i
++, param
);
859 char *g
= xstrmemdup (s
, p
- s
);
861 sub_args_need_free
[i
] = true;
862 sub_args
.SetParam(i
++, (uint64
)(size_t)g
);
865 /* If we didn't error out, we can actually print the string. */
868 AppendStringWithArgs (buf
, (GAME_TEXT_TAB
<< TAB_COUNT_OFFSET
) + stringid
, &sub_args
, true);
871 for (int i
= 0; i
< 20; i
++) {
872 if (sub_args_need_free
[i
]) free((void *)sub_args
.GetParam(i
));
877 case SCC_NEWGRF_STRINL
: {
878 StringID substr
= Utf8Consume(&str
);
879 str_stack
.push(GetStringPtr(substr
));
883 case SCC_NEWGRF_PRINT_WORD_STRING_ID
: {
884 StringID substr
= args
->GetInt32(SCC_NEWGRF_PRINT_WORD_STRING_ID
);
885 str_stack
.push(GetStringPtr(substr
));
886 case_index
= next_substr_case_index
;
887 next_substr_case_index
= 0;
892 case SCC_GENDER_LIST
: { // {G 0 Der Die Das}
893 /* First read the meta data from the language file. */
894 uint offset
= orig_offset
+ (byte
)*str
++;
896 if (!dry_run
&& args
->GetTypeAtOffset(offset
) != 0) {
897 /* Now we need to figure out what text to resolve, i.e.
898 * what do we need to draw? So get the actual raw string
899 * first using the control code to get said string. */
901 char *p
= input
+ Utf8Encode(input
, args
->GetTypeAtOffset(offset
));
904 /* Now do the string formatting. */
906 bool old_sgd
= _scan_for_gender_data
;
907 _scan_for_gender_data
= true;
908 StringParameters
tmp_params(args
->GetPointerToOffset(offset
), args
->num_param
- offset
, NULL
);
909 FormatString (&tmp
, input
, &tmp_params
);
910 _scan_for_gender_data
= old_sgd
;
912 /* And determine the string. */
913 const char *s
= tmp
.c_str();
914 WChar c
= Utf8Consume(&s
);
915 /* Does this string have a gender, if so, set it */
916 if (c
== SCC_GENDER_INDEX
) gender
= (byte
)s
[0];
918 str
= ParseStringChoice(str
, gender
, buf
);
922 /* This sets up the gender for the string.
923 * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */
924 case SCC_GENDER_INDEX
: // {GENDER 0}
925 if (_scan_for_gender_data
) {
926 buf
->append_utf8 (SCC_GENDER_INDEX
);
927 buf
->append (*str
++);
933 case SCC_PLURAL_LIST
: { // {P}
934 int plural_form
= *str
++; // contains the plural form for this string
935 uint offset
= orig_offset
+ (byte
)*str
++;
936 int64 v
= *args
->GetPointerToOffset(offset
); // contains the number that determines plural
937 str
= ParseStringChoice(str
, DeterminePluralForm(v
, plural_form
), buf
);
941 case SCC_ARG_INDEX
: { // Move argument pointer
942 args
->offset
= orig_offset
+ (byte
)*str
++;
946 case SCC_SET_CASE
: { // {SET_CASE}
947 /* This is a pseudo command, it's outputted when someone does {STRING.ack}
948 * The modifier is added to all subsequent AppendStringWithArgs that accept the modifier. */
949 next_substr_case_index
= (byte
)*str
++;
953 case SCC_SWITCH_CASE
: { // {Used to implement case switching}
954 /* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
955 * Each LEN is printed using 2 bytes in big endian order. */
956 uint num
= (byte
)*str
++;
958 if ((byte
)str
[0] == case_index
) {
959 /* Found the case, adjust str pointer and continue */
963 /* Otherwise skip to the next case */
964 str
+= 3 + (str
[1] << 8) + str
[2];
970 case SCC_REVISION
: // {REV}
971 buf
->append (_openttd_revision
);
974 case SCC_RAW_STRING_POINTER
: { // {RAW_STRING}
975 if (game_script
) break;
976 const char *str
= (const char *)(size_t)args
->GetInt64(SCC_RAW_STRING_POINTER
);
977 FormatString (buf
, str
, args
);
981 case SCC_STRING
: {// {STRING}
982 StringID str
= args
->GetInt32(SCC_STRING
);
983 if (game_script
&& GB(str
, TAB_COUNT_OFFSET
, TAB_COUNT_BITS
) != GAME_TEXT_TAB
) break;
984 /* WARNING. It's prohibited for the included string to consume any arguments.
985 * For included strings that consume argument, you should use STRING1, STRING2 etc.
986 * To debug stuff you can set argv to NULL and it will tell you */
987 StringParameters
tmp_params(args
->GetDataPointer(), args
->GetDataLeft(), NULL
);
988 AppendStringWithArgs (buf
, str
, &tmp_params
, next_substr_case_index
, game_script
);
989 next_substr_case_index
= 0;
999 case SCC_STRING7
: { // {STRING1..7}
1000 /* Strings that consume arguments */
1001 StringID str
= args
->GetInt32(b
);
1002 if (game_script
&& GB(str
, TAB_COUNT_OFFSET
, TAB_COUNT_BITS
) != GAME_TEXT_TAB
) break;
1003 uint size
= b
- SCC_STRING1
+ 1;
1004 if (game_script
&& size
> args
->GetDataLeft()) {
1005 buf
->append ("(too many parameters)");
1007 StringParameters
sub_args(*args
, size
);
1008 AppendStringWithArgs (buf
, str
, &sub_args
, next_substr_case_index
, game_script
);
1010 next_substr_case_index
= 0;
1014 case SCC_COMMA
: // {COMMA}
1015 FormatCommaNumber (buf
, args
->GetInt64(SCC_COMMA
));
1018 case SCC_DECIMAL
: {// {DECIMAL}
1019 int64 number
= args
->GetInt64(SCC_DECIMAL
);
1020 int digits
= args
->GetInt32(SCC_DECIMAL
);
1021 FormatCommaNumber (buf
, number
, digits
);
1025 case SCC_NUM
: // {NUM}
1026 FormatNoCommaNumber (buf
, args
->GetInt64(SCC_NUM
));
1029 case SCC_ZEROFILL_NUM
: { // {ZEROFILL_NUM}
1030 int64 num
= args
->GetInt64();
1031 FormatZerofillNumber (buf
, num
, args
->GetInt64());
1035 case SCC_HEX
: // {HEX}
1036 FormatHexNumber (buf
, (uint64
)args
->GetInt64(SCC_HEX
));
1039 case SCC_BYTES
: // {BYTES}
1040 FormatBytes (buf
, args
->GetInt64());
1043 case SCC_CARGO_TINY
: { // {CARGO_TINY}
1044 /* Tiny description of cargotypes. Layout:
1045 * param 1: cargo type
1046 * param 2: cargo count */
1047 CargoID cargo
= args
->GetInt32(SCC_CARGO_TINY
);
1048 if (cargo
>= CargoSpec::GetArraySize()) break;
1050 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1052 switch (cargo_str
) {
1054 amount
= _units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64());
1058 amount
= _units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64());
1062 amount
= args
->GetInt64();
1067 FormatCommaNumber (buf
, amount
);
1071 case SCC_CARGO_SHORT
: { // {CARGO_SHORT}
1072 /* Short description of cargotypes. Layout:
1073 * param 1: cargo type
1074 * param 2: cargo count */
1075 CargoID cargo
= args
->GetInt32(SCC_CARGO_SHORT
);
1076 if (cargo
>= CargoSpec::GetArraySize()) break;
1078 StringID cargo_str
= CargoSpec::Get(cargo
)->units_volume
;
1079 switch (cargo_str
) {
1081 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1082 int64 args_array
[] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64())};
1083 StringParameters
tmp_params(args_array
);
1084 FormatString (buf
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].l
), &tmp_params
);
1089 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1090 int64 args_array
[] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64())};
1091 StringParameters
tmp_params(args_array
);
1092 FormatString (buf
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].l
), &tmp_params
);
1097 StringParameters
tmp_params(*args
, 1);
1098 AppendStringWithArgs (buf
, cargo_str
, &tmp_params
);
1105 case SCC_CARGO_LONG
: { // {CARGO_LONG}
1106 /* First parameter is cargo type, second parameter is cargo count */
1107 CargoID cargo
= args
->GetInt32(SCC_CARGO_LONG
);
1108 if (cargo
!= CT_INVALID
&& cargo
>= CargoSpec::GetArraySize()) break;
1110 StringID cargo_str
= (cargo
== CT_INVALID
) ? STR_QUANTITY_N_A
: CargoSpec::Get(cargo
)->quantifier
;
1111 StringParameters
tmp_args(*args
, 1);
1112 AppendStringWithArgs (buf
, cargo_str
, &tmp_args
);
1116 case SCC_CARGO_LIST
: { // {CARGO_LIST}
1117 uint32 cmask
= args
->GetInt32(SCC_CARGO_LIST
);
1120 const CargoSpec
*cs
;
1121 FOR_ALL_SORTED_CARGOSPECS(cs
) {
1122 if (!HasBit(cmask
, cs
->Index())) continue;
1124 if (buf
->length() >= buf
->capacity
- 3) break; // ',' and ' '
1129 /* Add a comma if this is not the first item */
1133 AppendStringWithArgs (buf
, cs
->name
, args
, next_substr_case_index
, game_script
);
1136 /* If first is still true then no cargo is accepted */
1137 if (first
) AppendStringWithArgs (buf
, STR_JUST_NOTHING
, args
, next_substr_case_index
, game_script
);
1139 next_substr_case_index
= 0;
1143 case SCC_CURRENCY_SHORT
: // {CURRENCY_SHORT}
1144 FormatGenericCurrency (buf
, _currency
, args
->GetInt64(), true);
1147 case SCC_CURRENCY_LONG
: // {CURRENCY_LONG}
1148 FormatGenericCurrency (buf
, _currency
, args
->GetInt64(SCC_CURRENCY_LONG
), false);
1151 case SCC_DATE_TINY
: // {DATE_TINY}
1152 FormatTinyOrISODate (buf
, args
->GetInt32(SCC_DATE_TINY
), STR_FORMAT_DATE_TINY
);
1155 case SCC_DATE_SHORT
: // {DATE_SHORT}
1156 FormatMonthAndYear (buf
, args
->GetInt32(SCC_DATE_SHORT
), next_substr_case_index
);
1157 next_substr_case_index
= 0;
1160 case SCC_DATE_LONG
: // {DATE_LONG}
1161 FormatYmdString (buf
, args
->GetInt32(SCC_DATE_LONG
), next_substr_case_index
);
1162 next_substr_case_index
= 0;
1165 case SCC_DATE_ISO
: // {DATE_ISO}
1166 FormatTinyOrISODate (buf
, args
->GetInt32(), STR_FORMAT_DATE_ISO
);
1169 case SCC_FORCE
: { // {FORCE}
1170 assert(_settings_game
.locale
.units_force
< lengthof(_units_force
));
1171 int64 args_array
[1] = {_units_force
[_settings_game
.locale
.units_force
].c
.ToDisplay(args
->GetInt64())};
1172 StringParameters
tmp_params(args_array
);
1173 FormatString (buf
, GetStringPtr(_units_force
[_settings_game
.locale
.units_force
].s
), &tmp_params
);
1177 case SCC_HEIGHT
: { // {HEIGHT}
1178 assert(_settings_game
.locale
.units_height
< lengthof(_units_height
));
1179 int64 args_array
[] = {_units_height
[_settings_game
.locale
.units_height
].c
.ToDisplay(args
->GetInt64())};
1180 StringParameters
tmp_params(args_array
);
1181 FormatString (buf
, GetStringPtr(_units_height
[_settings_game
.locale
.units_height
].s
), &tmp_params
);
1185 case SCC_POWER
: { // {POWER}
1186 assert(_settings_game
.locale
.units_power
< lengthof(_units_power
));
1187 int64 args_array
[1] = {_units_power
[_settings_game
.locale
.units_power
].c
.ToDisplay(args
->GetInt64())};
1188 StringParameters
tmp_params(args_array
);
1189 FormatString (buf
, GetStringPtr(_units_power
[_settings_game
.locale
.units_power
].s
), &tmp_params
);
1193 case SCC_VELOCITY
: { // {VELOCITY}
1194 assert(_settings_game
.locale
.units_velocity
< lengthof(_units_velocity
));
1195 int64 args_array
[] = {ConvertKmhishSpeedToDisplaySpeed(args
->GetInt64(SCC_VELOCITY
))};
1196 StringParameters
tmp_params(args_array
);
1197 FormatString (buf
, GetStringPtr(_units_velocity
[_settings_game
.locale
.units_velocity
].s
), &tmp_params
);
1201 case SCC_VOLUME_SHORT
: { // {VOLUME_SHORT}
1202 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1203 int64 args_array
[1] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64())};
1204 StringParameters
tmp_params(args_array
);
1205 FormatString (buf
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].s
), &tmp_params
);
1209 case SCC_VOLUME_LONG
: { // {VOLUME_LONG}
1210 assert(_settings_game
.locale
.units_volume
< lengthof(_units_volume
));
1211 int64 args_array
[1] = {_units_volume
[_settings_game
.locale
.units_volume
].c
.ToDisplay(args
->GetInt64(SCC_VOLUME_LONG
))};
1212 StringParameters
tmp_params(args_array
);
1213 FormatString (buf
, GetStringPtr(_units_volume
[_settings_game
.locale
.units_volume
].l
), &tmp_params
);
1217 case SCC_WEIGHT_SHORT
: { // {WEIGHT_SHORT}
1218 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1219 int64 args_array
[1] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64())};
1220 StringParameters
tmp_params(args_array
);
1221 FormatString (buf
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].s
), &tmp_params
);
1225 case SCC_WEIGHT_LONG
: { // {WEIGHT_LONG}
1226 assert(_settings_game
.locale
.units_weight
< lengthof(_units_weight
));
1227 int64 args_array
[1] = {_units_weight
[_settings_game
.locale
.units_weight
].c
.ToDisplay(args
->GetInt64(SCC_WEIGHT_LONG
))};
1228 StringParameters
tmp_params(args_array
);
1229 FormatString (buf
, GetStringPtr(_units_weight
[_settings_game
.locale
.units_weight
].l
), &tmp_params
);
1233 case SCC_COMPANY_NAME
: { // {COMPANY}
1234 const Company
*c
= Company::GetIfValid(args
->GetInt32());
1235 if (c
== NULL
) break;
1237 if (c
->name
!= NULL
) {
1238 int64 args_array
[] = {(int64
)(size_t)c
->name
};
1239 StringParameters
tmp_params(args_array
);
1240 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1242 int64 args_array
[] = {c
->name_2
};
1243 StringParameters
tmp_params(args_array
);
1244 AppendStringWithArgs (buf
, c
->name_1
, &tmp_params
);
1249 case SCC_COMPANY_NUM
: { // {COMPANY_NUM}
1250 CompanyID company
= (CompanyID
)args
->GetInt32();
1252 /* Nothing is added for AI or inactive companies */
1253 if (Company::IsValidHumanID(company
)) {
1254 int64 args_array
[] = {company
+ 1};
1255 StringParameters
tmp_params(args_array
);
1256 AppendStringWithArgs (buf
, STR_FORMAT_COMPANY_NUM
, &tmp_params
);
1261 case SCC_DEPOT_NAME
: { // {DEPOT}
1262 VehicleType vt
= (VehicleType
)args
->GetInt32(SCC_DEPOT_NAME
);
1263 if (vt
== VEH_AIRCRAFT
) {
1264 uint64 args_array
[] = {(uint64
)args
->GetInt32()};
1265 WChar types_array
[] = {SCC_STATION_NAME
};
1266 StringParameters
tmp_params(args_array
, 1, types_array
);
1267 AppendStringWithArgs (buf
, STR_FORMAT_DEPOT_NAME_AIRCRAFT
, &tmp_params
);
1271 const Depot
*d
= Depot::Get(args
->GetInt32());
1272 if (d
->name
!= NULL
) {
1273 int64 args_array
[] = {(int64
)(size_t)d
->name
};
1274 StringParameters
tmp_params(args_array
);
1275 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1277 int64 args_array
[] = {d
->town
->index
, d
->town_cn
+ 1};
1278 StringParameters
tmp_params(args_array
);
1279 AppendStringWithArgs (buf
, STR_FORMAT_DEPOT_NAME_TRAIN
+ 2 * vt
+ (d
->town_cn
== 0 ? 0 : 1), &tmp_params
);
1284 case SCC_ENGINE_NAME
: { // {ENGINE}
1285 const Engine
*e
= Engine::GetIfValid(args
->GetInt32(SCC_ENGINE_NAME
));
1286 if (e
== NULL
) break;
1288 if (e
->name
!= NULL
&& e
->IsEnabled()) {
1289 int64 args_array
[] = {(int64
)(size_t)e
->name
};
1290 StringParameters
tmp_params(args_array
);
1291 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1293 StringParameters
tmp_params(NULL
, 0, NULL
);
1294 AppendStringWithArgs (buf
, e
->info
.string_id
, &tmp_params
);
1299 case SCC_GROUP_NAME
: { // {GROUP}
1300 const Group
*g
= Group::GetIfValid(args
->GetInt32());
1301 if (g
== NULL
) break;
1303 if (g
->name
!= NULL
) {
1304 int64 args_array
[] = {(int64
)(size_t)g
->name
};
1305 StringParameters
tmp_params(args_array
);
1306 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1308 int64 args_array
[] = {g
->index
};
1309 StringParameters
tmp_params(args_array
);
1311 AppendStringWithArgs (buf
, STR_FORMAT_GROUP_NAME
, &tmp_params
);
1316 case SCC_INDUSTRY_NAME
: { // {INDUSTRY}
1317 const Industry
*i
= Industry::GetIfValid(args
->GetInt32(SCC_INDUSTRY_NAME
));
1318 if (i
== NULL
) break;
1320 if (_scan_for_gender_data
) {
1321 /* Gender is defined by the industry type.
1322 * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */
1323 StringParameters
tmp_params(NULL
, 0, NULL
);
1324 FormatString (buf
, GetStringPtr(GetIndustrySpec(i
->type
)->name
), &tmp_params
, next_substr_case_index
);
1326 /* First print the town name and the industry type name. */
1327 int64 args_array
[2] = {i
->town
->index
, GetIndustrySpec(i
->type
)->name
};
1328 StringParameters
tmp_params(args_array
);
1330 FormatString (buf
, GetStringPtr(STR_FORMAT_INDUSTRY_NAME
), &tmp_params
, next_substr_case_index
);
1332 next_substr_case_index
= 0;
1336 case SCC_PRESIDENT_NAME
: { // {PRESIDENT_NAME}
1337 const Company
*c
= Company::GetIfValid(args
->GetInt32(SCC_PRESIDENT_NAME
));
1338 if (c
== NULL
) break;
1340 if (c
->president_name
!= NULL
) {
1341 int64 args_array
[] = {(int64
)(size_t)c
->president_name
};
1342 StringParameters
tmp_params(args_array
);
1343 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1345 int64 args_array
[] = {c
->president_name_2
};
1346 StringParameters
tmp_params(args_array
);
1347 AppendStringWithArgs (buf
, c
->president_name_1
, &tmp_params
);
1352 case SCC_STATION_NAME
: { // {STATION}
1353 StationID sid
= args
->GetInt32(SCC_STATION_NAME
);
1354 const Station
*st
= Station::GetIfValid(sid
);
1357 /* The station doesn't exist anymore. The only place where we might
1358 * be "drawing" an invalid station is in the case of cargo that is
1360 StringParameters
tmp_params(NULL
, 0, NULL
);
1361 AppendStringWithArgs (buf
, STR_UNKNOWN_STATION
, &tmp_params
);
1365 if (st
->name
!= NULL
) {
1366 int64 args_array
[] = {(int64
)(size_t)st
->name
};
1367 StringParameters
tmp_params(args_array
);
1368 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1370 StringID str
= st
->string_id
;
1371 if (st
->indtype
!= IT_INVALID
) {
1372 /* Special case where the industry provides the name for the station */
1373 const IndustrySpec
*indsp
= GetIndustrySpec(st
->indtype
);
1375 /* Industry GRFs can change which might remove the station name and
1376 * thus cause very strange things. Here we check for that before we
1377 * actually set the station name. */
1378 if (indsp
->station_name
!= STR_NULL
&& indsp
->station_name
!= STR_UNDEFINED
) {
1379 str
= indsp
->station_name
;
1383 int64 args_array
[] = {STR_TOWN_NAME
, st
->town
->index
, st
->index
};
1384 StringParameters
tmp_params(args_array
);
1385 AppendStringWithArgs (buf
, str
, &tmp_params
);
1390 case SCC_TOWN_NAME
: { // {TOWN}
1391 const Town
*t
= Town::GetIfValid(args
->GetInt32(SCC_TOWN_NAME
));
1392 if (t
== NULL
) break;
1394 if (t
->name
!= NULL
) {
1395 int64 args_array
[] = {(int64
)(size_t)t
->name
};
1396 StringParameters
tmp_params(args_array
);
1397 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1399 AppendTownName (buf
, t
);
1404 case SCC_WAYPOINT_NAME
: { // {WAYPOINT}
1405 Waypoint
*wp
= Waypoint::GetIfValid(args
->GetInt32(SCC_WAYPOINT_NAME
));
1406 if (wp
== NULL
) break;
1408 if (wp
->name
!= NULL
) {
1409 int64 args_array
[] = {(int64
)(size_t)wp
->name
};
1410 StringParameters
tmp_params(args_array
);
1411 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1413 int64 args_array
[] = {wp
->town
->index
, wp
->town_cn
+ 1};
1414 StringParameters
tmp_params(args_array
);
1415 StringID str
= ((wp
->string_id
== STR_SV_STNAME_BUOY
) ? STR_FORMAT_BUOY_NAME
: STR_FORMAT_WAYPOINT_NAME
);
1416 if (wp
->town_cn
!= 0) str
++;
1417 AppendStringWithArgs (buf
, str
, &tmp_params
);
1422 case SCC_VEHICLE_NAME
: { // {VEHICLE}
1423 const Vehicle
*v
= Vehicle::GetIfValid(args
->GetInt32(SCC_VEHICLE_NAME
));
1424 if (v
== NULL
) break;
1426 if (v
->name
!= NULL
) {
1427 int64 args_array
[] = {(int64
)(size_t)v
->name
};
1428 StringParameters
tmp_params(args_array
);
1429 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1431 int64 args_array
[] = {v
->unitnumber
};
1432 StringParameters
tmp_params(args_array
);
1436 default: str
= STR_INVALID_VEHICLE
; break;
1437 case VEH_TRAIN
: str
= STR_SV_TRAIN_NAME
; break;
1438 case VEH_ROAD
: str
= STR_SV_ROAD_VEHICLE_NAME
; break;
1439 case VEH_SHIP
: str
= STR_SV_SHIP_NAME
; break;
1440 case VEH_AIRCRAFT
: str
= STR_SV_AIRCRAFT_NAME
; break;
1443 AppendStringWithArgs (buf
, str
, &tmp_params
);
1448 case SCC_SIGN_NAME
: { // {SIGN}
1449 const Sign
*si
= Sign::GetIfValid(args
->GetInt32());
1450 if (si
== NULL
) break;
1452 if (si
->name
!= NULL
) {
1453 int64 args_array
[] = {(int64
)(size_t)si
->name
};
1454 StringParameters
tmp_params(args_array
);
1455 AppendStringWithArgs (buf
, STR_JUST_RAW_STRING
, &tmp_params
);
1457 StringParameters
tmp_params(NULL
, 0, NULL
);
1458 AppendStringWithArgs (buf
, STR_DEFAULT_SIGN_NAME
, &tmp_params
);
1463 case SCC_STATION_FEATURES
: { // {STATIONFEATURES}
1464 AppendStationSpecialString (buf
, args
->GetInt32(SCC_STATION_FEATURES
));
1469 buf
->append_utf8 (b
);
1476 static void AppendStationSpecialString (stringb
*buf
, int x
)
1478 if (x
& FACIL_TRAIN
) buf
->append_utf8 (SCC_TRAIN
);
1479 if (x
& FACIL_TRUCK_STOP
) buf
->append_utf8 (SCC_LORRY
);
1480 if (x
& FACIL_BUS_STOP
) buf
->append_utf8 (SCC_BUS
);
1481 if (x
& FACIL_DOCK
) buf
->append_utf8 (SCC_SHIP
);
1482 if (x
& FACIL_AIRPORT
) buf
->append_utf8 (SCC_PLANE
);
1486 static const char *GenSurname (uint32 arg
)
1488 static const char * const surname_list
[] = {
1489 "Adams", "Allan", "Baker", "Bigwig", "Black",
1490 "Bloggs", "Brown", "Campbell", "Gordon", "Hamilton",
1491 "Hawthorn", "Higgins", "Green", "Gribble", "Jones",
1492 "McAlpine", "MacDonald", "McIntosh", "Muir", "Murphy",
1493 "Nelson", "O'Donnell", "Parker", "Phillips", "Pilkington",
1494 "Quigley", "Sharkey", "Thomson", "Watkins",
1497 static const char * const silly_list
[] = {
1498 "Grumpy", "Dozy", "Speedy", "Nosey", "Dribble", "Mushroom",
1499 "Cabbage", "Sniffle", "Fishy", "Swindle", "Sneaky", "Nutkins",
1502 const char * const *base
;
1505 if (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) {
1507 num
= lengthof(silly_list
);
1509 base
= surname_list
;
1510 num
= lengthof(surname_list
);
1513 return base
[num
* GB(arg
, 16, 8) >> 8];
1516 static void GenAndCoName (stringb
*buf
, uint32 arg
)
1518 buf
->append (GenSurname (arg
));
1519 buf
->append (" & Co.");
1522 static void GenPresidentName (stringb
*buf
, uint32 x
)
1524 static const char initials
[] = {
1525 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
1526 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W',
1529 char initial
[] = "?. ";
1532 initial
[0] = initials
[sizeof(initials
) * GB(x
, 0, 8) >> 8];
1533 buf
->append (initial
);
1535 i
= (sizeof(initials
) + 35) * GB(x
, 8, 8) >> 8;
1536 if (i
< sizeof(initials
)) {
1537 initial
[0] = initials
[i
];
1538 buf
->append (initial
);
1541 buf
->append (GenSurname (x
));
1544 static void AppendSpecialNameString (stringb
*buf
, int ind
, StringParameters
*args
)
1546 static const char * const silly_company_names
[] = {
1548 "Tiny Transport Ltd.",
1550 "Comfy-Coach & Co.",
1551 "Crush & Bump Ltd.",
1552 "Broken & Late Ltd.",
1554 "Supersonic Travel",
1556 "Lightning International",
1557 "Pannik & Loozit Ltd.",
1558 "Inter-City Transport",
1559 "Getout & Pushit Ltd.",
1564 buf
->append (silly_company_names
[min(args
->GetInt32() & 0xFFFF, lengthof(silly_company_names
) - 1)]);
1567 case 2: // used for Foobar & Co company names
1568 GenAndCoName (buf
, args
->GetInt32());
1571 case 3: // President name
1572 GenPresidentName (buf
, args
->GetInt32());
1577 if (IsInsideMM(ind
- 6, 0, SPECSTR_TOWNNAME_LAST
- SPECSTR_TOWNNAME_START
+ 1)) {
1578 GenerateTownNameString (buf
, ind
- 6, args
->GetInt32());
1579 buf
->append (" Transport");
1583 /* language name? */
1584 if (IsInsideMM(ind
, (SPECSTR_LANGUAGE_START
- 0x70E4), (SPECSTR_LANGUAGE_END
- 0x70E4) + 1)) {
1585 int i
= ind
- (SPECSTR_LANGUAGE_START
- 0x70E4);
1586 buf
->append (&_languages
[i
] == _current_language
? _current_language
->own_name
: _languages
[i
].name
);
1590 /* resolution size? */
1591 if (IsInsideMM(ind
, (SPECSTR_RESOLUTION_START
- 0x70E4), (SPECSTR_RESOLUTION_END
- 0x70E4) + 1)) {
1592 int i
= ind
- (SPECSTR_RESOLUTION_START
- 0x70E4);
1593 buf
->append_fmt ("%ux%u", _resolutions
[i
].width
, _resolutions
[i
].height
);
1597 /* screenshot format name? */
1598 if (IsInsideMM(ind
, (SPECSTR_SCREENSHOT_START
- 0x70E4), (SPECSTR_SCREENSHOT_END
- 0x70E4) + 1)) {
1599 int i
= ind
- (SPECSTR_SCREENSHOT_START
- 0x70E4);
1600 buf
->append (GetScreenshotFormatDesc(i
));
1607 #ifdef ENABLE_NETWORK
1608 extern void SortNetworkLanguages();
1609 #else /* ENABLE_NETWORK */
1610 static inline void SortNetworkLanguages() {}
1611 #endif /* ENABLE_NETWORK */
1614 * Check whether the header is a valid header for OpenTTD.
1615 * @return true iff the header is deemed valid.
1617 bool LanguagePackHeader::IsValid() const
1619 return this->ident
== TO_LE32(LanguagePackHeader::IDENT
) &&
1620 this->version
== TO_LE32(LANGUAGE_PACK_VERSION
) &&
1621 this->plural_form
< LANGUAGE_MAX_PLURAL
&&
1622 this->text_dir
<= 1 &&
1623 this->newgrflangid
< MAX_LANG
&&
1624 this->num_genders
< MAX_NUM_GENDERS
&&
1625 this->num_cases
< MAX_NUM_CASES
&&
1626 StrValid(this->name
, lastof(this->name
)) &&
1627 StrValid(this->own_name
, lastof(this->own_name
)) &&
1628 StrValid(this->isocode
, lastof(this->isocode
)) &&
1629 StrValid(this->digit_group_separator
, lastof(this->digit_group_separator
)) &&
1630 StrValid(this->digit_group_separator_currency
, lastof(this->digit_group_separator_currency
)) &&
1631 StrValid(this->digit_decimal_separator
, lastof(this->digit_decimal_separator
));
1635 * Read a particular language.
1636 * @param lang The metadata about the language.
1637 * @return Whether the loading went okay or not.
1639 bool ReadLanguagePack(const LanguageMetadata
*lang
)
1641 /* Current language pack */
1643 LanguagePack
*lang_pack
= (LanguagePack
*)ReadFileToMem(lang
->file
, &len
, 1U << 20);
1644 if (lang_pack
== NULL
) return false;
1646 /* End of read data (+ terminating zero added in ReadFileToMem()) */
1647 const char *end
= (char *)lang_pack
+ len
+ 1;
1649 /* We need at least one byte of lang_pack->data */
1650 if (end
<= lang_pack
->data
|| !lang_pack
->IsValid()) {
1655 #if TTD_ENDIAN == TTD_BIG_ENDIAN
1656 for (uint i
= 0; i
< TAB_COUNT
; i
++) {
1657 lang_pack
->offsets
[i
] = ReadLE16Aligned(&lang_pack
->offsets
[i
]);
1659 #endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */
1662 for (uint i
= 0; i
< TAB_COUNT
; i
++) {
1663 uint16 num
= lang_pack
->offsets
[i
];
1664 if (num
> TAB_SIZE
) {
1669 _langtab_start
[i
] = count
;
1670 _langtab_num
[i
] = num
;
1674 /* Allocate offsets */
1675 char **langpack_offs
= xmalloct
<char *>(count
);
1678 char *s
= lang_pack
->data
;
1680 for (uint i
= 0; i
< count
; i
++) {
1681 if (s
+ len
>= end
) {
1683 free(langpack_offs
);
1687 len
= ((len
& 0x3F) << 8) + (byte
)*s
++;
1688 if (s
+ len
>= end
) {
1690 free(langpack_offs
);
1694 langpack_offs
[i
] = s
;
1697 *s
++ = '\0'; // zero terminate the string
1701 _langpack
= lang_pack
;
1703 free(_langpack_offs
);
1704 _langpack_offs
= langpack_offs
;
1706 _current_language
= lang
;
1707 _current_text_dir
= (TextDirection
)_current_language
->text_dir
;
1708 const char *c_file
= strrchr(_current_language
->file
, PATHSEPCHAR
) + 1;
1709 bstrcpy (_config_language_file
, c_file
);
1710 SetCurrentGrfLangID(_current_language
->newgrflangid
);
1712 #ifdef WITH_ICU_SORT
1713 /* Delete previous collator. */
1714 if (_current_collator
!= NULL
) {
1715 delete _current_collator
;
1716 _current_collator
= NULL
;
1719 /* Create a collator instance for our current locale. */
1720 UErrorCode status
= U_ZERO_ERROR
;
1721 _current_collator
= Collator::createInstance(Locale(_current_language
->isocode
), status
);
1722 /* Sort number substrings by their numerical value. */
1723 if (_current_collator
!= NULL
) _current_collator
->setAttribute(UCOL_NUMERIC_COLLATION
, UCOL_ON
, status
);
1724 /* Avoid using the collator if it is not correctly set. */
1725 if (U_FAILURE(status
)) {
1726 delete _current_collator
;
1727 _current_collator
= NULL
;
1729 #endif /* WITH_ICU_SORT */
1731 /* Some lists need to be sorted again after a language change. */
1732 ReconsiderGameScriptLanguage();
1733 InitializeSortedCargoSpecs();
1734 SortIndustryTypes();
1735 BuildIndustriesLegend();
1736 SortNetworkLanguages();
1737 BuildContentTypeStringList();
1738 InvalidateWindowClassesData(WC_BUILD_VEHICLE
); // Build vehicle window.
1739 InvalidateWindowClassesData(WC_TRAINS_LIST
); // Train group window.
1740 InvalidateWindowClassesData(WC_ROADVEH_LIST
); // Road vehicle group window.
1741 InvalidateWindowClassesData(WC_SHIPS_LIST
); // Ship group window.
1742 InvalidateWindowClassesData(WC_AIRCRAFT_LIST
); // Aircraft group window.
1743 InvalidateWindowClassesData(WC_INDUSTRY_DIRECTORY
); // Industry directory window.
1744 InvalidateWindowClassesData(WC_STATION_LIST
); // Station list window.
1749 /* Win32 implementation in win32.cpp.
1750 * OS X implementation in os/macosx/macos.mm. */
1751 #if !(defined(WIN32) || defined(__APPLE__))
1753 * Determine the current charset based on the environment
1754 * First check some default values, after this one we passed ourselves
1755 * and if none exist return the value for $LANG
1756 * @param param environment variable to check conditionally if default ones are not
1757 * set. Pass NULL if you don't want additional checks.
1758 * @return return string containing current charset, or NULL if not-determinable
1760 const char *GetCurrentLocale(const char *param
)
1764 env
= getenv("LANGUAGE");
1765 if (env
!= NULL
) return env
;
1767 env
= getenv("LC_ALL");
1768 if (env
!= NULL
) return env
;
1770 if (param
!= NULL
) {
1771 env
= getenv(param
);
1772 if (env
!= NULL
) return env
;
1775 return getenv("LANG");
1778 const char *GetCurrentLocale(const char *param
);
1779 #endif /* !(defined(WIN32) || defined(__APPLE__)) */
1781 int CDECL
StringIDSorter(const StringID
*a
, const StringID
*b
)
1785 GetString (stra
, *a
);
1786 GetString (strb
, *b
);
1788 return strnatcmp(stra
, strb
);
1792 * Get the language with the given NewGRF language ID.
1793 * @param newgrflangid NewGRF languages ID to check.
1794 * @return The language's metadata, or NULL if it is not known.
1796 const LanguageMetadata
*GetLanguage(byte newgrflangid
)
1798 for (const LanguageMetadata
*lang
= _languages
.Begin(); lang
!= _languages
.End(); lang
++) {
1799 if (newgrflangid
== lang
->newgrflangid
) return lang
;
1806 * Reads the language file header and checks compatibility.
1807 * @param file the file to read
1808 * @param hdr the place to write the header information to
1809 * @return true if and only if the language file is of a compatible version
1811 static bool GetLanguageFileHeader(const char *file
, LanguagePackHeader
*hdr
)
1813 FILE *f
= fopen(file
, "rb");
1814 if (f
== NULL
) return false;
1816 size_t read
= fread(hdr
, sizeof(*hdr
), 1, f
);
1819 bool ret
= read
== 1 && hdr
->IsValid();
1821 /* Convert endianness for the windows language ID */
1823 hdr
->missing
= FROM_LE16(hdr
->missing
);
1824 hdr
->winlangid
= FROM_LE16(hdr
->winlangid
);
1830 * Gets a list of languages from the given directory.
1831 * @param path the base directory to search in
1833 static void GetLanguageList(const char *path
)
1835 DIR *dir
= ttd_opendir(path
);
1837 struct dirent
*dirent
;
1838 while ((dirent
= readdir(dir
)) != NULL
) {
1839 const char *d_name
= FS2OTTD(dirent
->d_name
);
1840 const char *extension
= strrchr(d_name
, '.');
1842 /* Not a language file */
1843 if (extension
== NULL
|| strcmp(extension
, ".lng") != 0) continue;
1845 LanguageMetadata lmd
;
1846 bstrfmt (lmd
.file
, "%s%s", path
, d_name
);
1848 /* Check whether the file is of the correct version */
1849 if (!GetLanguageFileHeader(lmd
.file
, &lmd
)) {
1850 DEBUG(misc
, 3, "%s is not a valid language file", lmd
.file
);
1851 } else if (GetLanguage(lmd
.newgrflangid
) != NULL
) {
1852 DEBUG(misc
, 3, "%s's language ID is already known", lmd
.file
);
1854 *_languages
.Append() = lmd
;
1862 * Make a list of the available language packs. Put the data in
1865 void InitializeLanguagePacks()
1869 FOR_ALL_SEARCHPATHS(sp
) {
1870 char path
[MAX_PATH
];
1871 FioGetFullPath (path
, lengthof(path
), sp
, LANG_DIR
);
1872 GetLanguageList(path
);
1874 if (_languages
.Length() == 0) usererror("No available language packs (invalid versions?)");
1876 /* Acquire the locale of the current system */
1877 const char *lang
= GetCurrentLocale("LC_MESSAGES");
1878 if (lang
== NULL
) lang
= "en_GB";
1880 const LanguageMetadata
*chosen_language
= NULL
; ///< Matching the language in the configuration file or the current locale
1881 const LanguageMetadata
*language_fallback
= NULL
; ///< Using pt_PT for pt_BR locale when pt_BR is not available
1882 const LanguageMetadata
*en_GB_fallback
= _languages
.Begin(); ///< Fallback when no locale-matching language has been found
1884 /* Find a proper language. */
1885 for (const LanguageMetadata
*lng
= _languages
.Begin(); lng
!= _languages
.End(); lng
++) {
1886 /* We are trying to find a default language. The priority is by
1887 * configuration file, local environment and last, if nothing found,
1889 const char *lang_file
= strrchr(lng
->file
, PATHSEPCHAR
) + 1;
1890 if (strcmp(lang_file
, _config_language_file
) == 0) {
1891 chosen_language
= lng
;
1895 if (strcmp (lng
->isocode
, "en_GB") == 0) en_GB_fallback
= lng
;
1896 if (strncmp(lng
->isocode
, lang
, 5) == 0) chosen_language
= lng
;
1897 if (strncmp(lng
->isocode
, lang
, 2) == 0) language_fallback
= lng
;
1900 /* We haven't found the language in the config nor the one in the locale.
1901 * Now we set it to one of the fallback languages */
1902 if (chosen_language
== NULL
) {
1903 chosen_language
= (language_fallback
!= NULL
) ? language_fallback
: en_GB_fallback
;
1906 if (!ReadLanguagePack(chosen_language
)) usererror("Can't read language pack '%s'", chosen_language
->file
);
1910 * Get the ISO language code of the currently loaded language.
1911 * @return the ISO code.
1913 const char *GetCurrentLanguageIsoCode()
1915 return _langpack
->isocode
;
1919 * Set the right font names.
1920 * @param settings The settings to modify.
1921 * @param font_name The new font name.
1923 void MissingGlyphSearcher::SetFontNames (struct FreeTypeSettings
*settings
, const char *font_name
)
1925 #ifdef WITH_FREETYPE
1926 if (this->Monospace()) {
1927 bstrcpy (settings
->mono
.font
, font_name
);
1929 bstrcpy (settings
->small
.font
, font_name
);
1930 bstrcpy (settings
->medium
.font
, font_name
);
1931 bstrcpy (settings
->large
.font
, font_name
);
1933 #endif /* WITH_FREETYPE */
1937 * Check whether there are glyphs missing in the current language.
1938 * @return If glyphs are missing, return \c true, else return \c false.
1940 bool MissingGlyphSearcher::FindMissingGlyphs (void)
1942 InitFreeType(this->Monospace());
1943 const Sprite
*question_mark
[FS_END
];
1945 for (FontSize size
= this->Monospace() ? FS_MONO
: FS_BEGIN
; size
< (this->Monospace() ? FS_END
: FS_MONO
); size
++) {
1946 question_mark
[size
] = GetGlyph(size
, '?');
1950 for (const char *text
= this->NextString(); text
!= NULL
; text
= this->NextString()) {
1951 FontSize size
= this->DefaultSize();
1953 WChar c
= Utf8Consume(&text
);
1956 } else if (c
== SCC_TINYFONT
) {
1958 } else if (c
== SCC_BIGFONT
) {
1960 } else if (!IsInsideMM(c
, SCC_SPRITE_START
, SCC_SPRITE_END
) && IsPrintable(c
) && !IsTextDirectionChar(c
) && c
!= '?' && GetGlyph(size
, c
) == question_mark
[size
]) {
1961 /* The character is printable, but not in the normal font. This is the case we were testing for. */
1969 /** Helper for searching through the language pack. */
1970 class LanguagePackGlyphSearcher
: public MissingGlyphSearcher
{
1971 uint i
; ///< Iterator for the primary language tables.
1972 uint j
; ///< Iterator for the secondary language tables.
1975 CONSTEXPR
LanguagePackGlyphSearcher()
1976 : MissingGlyphSearcher (FS_NORMAL
, false), i(0), j(0)
1981 /* virtual */ void Reset()
1987 /* virtual */ const char *NextString()
1989 if (this->i
>= TAB_COUNT
) return NULL
;
1991 const char *ret
= _langpack_offs
[_langtab_start
[this->i
] + this->j
];
1994 while (this->i
< TAB_COUNT
&& this->j
>= _langtab_num
[this->i
]) {
2004 * Check whether the currently loaded language pack
2005 * uses characters that the currently loaded font
2006 * does not support. If this is the case an error
2007 * message will be shown in English. The error
2008 * message will not be localized because that would
2009 * mean it might use characters that are not in the
2010 * font, which is the whole reason this check has
2012 * @param base_font Whether to look at the base font as well.
2013 * @param searcher The methods to use to search for strings to check.
2014 * If NULL the loaded language pack searcher is used.
2016 void CheckForMissingGlyphs(bool base_font
, MissingGlyphSearcher
*searcher
)
2018 static LanguagePackGlyphSearcher pack_searcher
;
2019 if (searcher
== NULL
) searcher
= &pack_searcher
;
2020 bool bad_font
= !base_font
|| searcher
->FindMissingGlyphs();
2021 #ifdef WITH_FREETYPE
2023 /* We found an unprintable character... lets try whether we can find
2024 * a fallback font that can print the characters in the current language. */
2025 FreeTypeSettings backup
;
2026 memcpy(&backup
, &_freetype
, sizeof(backup
));
2028 bad_font
= !SetFallbackFont(&_freetype
, _langpack
->isocode
, _langpack
->winlangid
, searcher
);
2030 memcpy(&_freetype
, &backup
, sizeof(backup
));
2032 if (bad_font
&& base_font
) {
2033 /* Our fallback font does miss characters too, so keep the
2034 * user chosen font as that is more likely to be any good than
2035 * the wild guess we made */
2036 InitFreeType(searcher
->Monospace());
2042 /* All attempts have failed. Display an error. */
2044 SCCSTR_YELLOW
"The current font is missing some of "
2045 "the characters used in the texts for this language. "
2046 "Read the readme to see how to solve this.");
2047 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_WARNING
);
2049 /* Reset the font width */
2050 LoadStringWidthTable(searcher
->Monospace());
2054 /* Update the font with cache */
2055 LoadStringWidthTable(searcher
->Monospace());
2057 #if !defined(WITH_ICU_LAYOUT)
2059 * For right-to-left languages we need the ICU library. If
2060 * we do not have support for that library we warn the user
2061 * about it with a message.
2063 if (_current_text_dir
!= TD_LTR
) {
2065 SCCSTR_YELLOW
"This version of OpenTTD does not "
2066 "support right-to-left languages. "
2067 "Recompile with icu enabled.");
2068 ShowErrorMessage(STR_JUST_RAW_STRING
, INVALID_STRING_ID
, WL_ERROR
);
2070 #endif /* !WITH_ICU_LAYOUT */