2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
9 * @file newgrf_text.cpp
10 * Implementation of Action 04 "universal holder" structure and functions.
11 * This file implements a linked-lists of strings,
12 * holding everything that the newgrf action 04 will send over to OpenTTD.
13 * One of the biggest problems is that Dynamic lang Array uses ISO codes
14 * as way to identifying current user lang, while newgrf uses bit shift codes
15 * not related to ISO. So equivalence functionality had to be set.
21 #include "strings_internal.h"
22 #include "newgrf_storage.h"
23 #include "newgrf_text.h"
24 #include "newgrf_cargo.h"
25 #include "string_func.h"
26 #include "timer/timer_game_calendar.h"
28 #include "core/alloc_type.hpp"
32 #include "table/strings.h"
33 #include "table/control_codes.h"
35 #include "safeguards.h"
38 * Explains the newgrf shift bit positioning.
39 * the grf base will not be used in order to find the string, but rather for
40 * jumping from standard langID scheme to the new one.
42 enum GRFBaseLanguages
{
43 GRFLB_AMERICAN
= 0x01,
51 enum GRFExtendedLanguages
{
52 GRFLX_AMERICAN
= 0x00,
57 GRFLX_UNSPECIFIED
= 0x7F,
62 * Holder of the above structure.
63 * Putting both grfid and stringid together allows us to avoid duplicates,
64 * since it is NOT SUPPOSED to happen.
67 GRFTextList textholder
;
74 static std::vector
<GRFTextEntry
> _grf_text
;
75 static uint8_t _currentLangID
= GRFLX_ENGLISH
; ///< by default, english is used.
78 * Get the mapping from the NewGRF supplied ID to OpenTTD's internal ID.
79 * @param newgrf_id The NewGRF ID to map.
80 * @param gender Whether to map genders or cases.
81 * @return The, to OpenTTD's internal ID, mapped index, or -1 if there is no mapping.
83 int LanguageMap::GetMapping(int newgrf_id
, bool gender
) const
85 const std::vector
<Mapping
> &map
= gender
? this->gender_map
: this->case_map
;
86 for (const Mapping
&m
: map
) {
87 if (m
.newgrf_id
== newgrf_id
) return m
.openttd_id
;
93 * Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
94 * @param openttd_id The OpenTTD ID to map.
95 * @param gender Whether to map genders or cases.
96 * @return The, to the NewGRF supplied ID, mapped index, or -1 if there is no mapping.
98 int LanguageMap::GetReverseMapping(int openttd_id
, bool gender
) const
100 const std::vector
<Mapping
> &map
= gender
? this->gender_map
: this->case_map
;
101 for (const Mapping
&m
: map
) {
102 if (m
.openttd_id
== openttd_id
) return m
.newgrf_id
;
107 /** Helper structure for mapping choice lists. */
108 struct UnmappedChoiceList
{
110 * Initialise the mapping.
111 * @param type The type of mapping.
112 * @param offset The offset to get the plural/gender from.
114 UnmappedChoiceList(StringControlCode type
, int offset
) :
115 type(type
), offset(offset
)
119 StringControlCode type
; ///< The type of choice list.
120 int offset
; ///< The offset for the plural/gender form.
122 /** Mapping of NewGRF supplied ID to the different strings in the choice list. */
123 std::map
<uint8_t, std::stringstream
> strings
;
126 * Flush this choice list into the destination string.
127 * @param lm The current language mapping.
128 * @param dest Target to write to.
130 void Flush(const LanguageMap
*lm
, std::ostringstream
&dest
)
132 if (this->strings
.find(0) == this->strings
.end()) {
133 /* In case of a (broken) NewGRF without a default,
134 * assume an empty string. */
135 GrfMsg(1, "choice list misses default value");
136 this->strings
[0] = std::stringstream();
139 std::ostreambuf_iterator
<char> d(dest
);
142 /* In case there is no mapping, just ignore everything but the default.
143 * A probable cause for this happening is when the language file has
144 * been removed by the user and as such no mapping could be made. */
145 dest
<< this->strings
[0].rdbuf();
149 Utf8Encode(d
, this->type
);
151 if (this->type
== SCC_SWITCH_CASE
) {
153 * Format for case switch:
154 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
155 * Each LEN is printed using 2 bytes in big endian order.
160 for (uint8_t i
= 0; i
< _current_language
->num_cases
; i
++) {
161 /* Count the ones we have a mapped string for. */
162 if (this->strings
.find(lm
->GetReverseMapping(i
, false)) != this->strings
.end()) count
++;
166 for (uint8_t i
= 0; i
< _current_language
->num_cases
; i
++) {
167 /* Resolve the string we're looking for. */
168 int idx
= lm
->GetReverseMapping(i
, false);
169 if (this->strings
.find(idx
) == this->strings
.end()) continue;
170 auto str
= this->strings
[idx
].str();
175 /* "<LENn>": Limit the length of the string to 0xFFFE to leave space for the '\0'. */
176 size_t len
= std::min
<size_t>(0xFFFE, str
.size());
177 *d
++ = GB(len
+ 1, 8, 8);
178 *d
++ = GB(len
+ 1, 0, 8);
181 dest
.write(str
.c_str(), len
);
185 /* "<STRINGDEFAULT>" */
186 dest
<< this->strings
[0].rdbuf() << '\0';
188 if (this->type
== SCC_PLURAL_LIST
) {
189 *d
++ = lm
->plural_form
;
193 * Format for choice list:
194 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
198 *d
++ = this->offset
- 0x80;
200 /* "<NUM CHOICES>" */
201 int count
= (this->type
== SCC_GENDER_LIST
? _current_language
->num_genders
: LANGUAGE_MAX_PLURAL_FORMS
);
205 for (int i
= 0; i
< count
; i
++) {
206 int idx
= (this->type
== SCC_GENDER_LIST
? lm
->GetReverseMapping(i
, true) : i
+ 1);
207 const auto &str
= this->strings
[this->strings
.find(idx
) != this->strings
.end() ? idx
: 0].str();
208 size_t len
= str
.size() + 1;
209 if (len
> 0xFF) GrfMsg(1, "choice list string is too long");
210 *d
++ = GB(len
, 0, 8);
214 for (int i
= 0; i
< count
; i
++) {
215 int idx
= (this->type
== SCC_GENDER_LIST
? lm
->GetReverseMapping(i
, true) : i
+ 1);
216 const auto &str
= this->strings
[this->strings
.find(idx
) != this->strings
.end() ? idx
: 0].str();
217 /* Limit the length of the string we copy to 0xFE. The length is written above
218 * as a byte and we need room for the final '\0'. */
219 size_t len
= std::min
<size_t>(0xFE, str
.size());
220 dest
.write(str
.c_str(), len
);
228 * Translate TTDPatch string codes into something OpenTTD can handle (better).
229 * @param grfid The (NewGRF) ID associated with this string
230 * @param language_id The (NewGRF) language ID associated with this string.
231 * @param allow_newlines Whether newlines are allowed in the string or not.
232 * @param str The string to translate.
233 * @param byte80 The control code to use as replacement for the 0x80-value.
234 * @return The translated string.
236 std::string
TranslateTTDPatchCodes(uint32_t grfid
, uint8_t language_id
, bool allow_newlines
, std::string_view str
, StringControlCode byte80
)
238 /* Empty input string? Nothing to do here. */
239 if (str
.empty()) return {};
241 std::string_view::const_iterator src
= str
.cbegin();
243 /* Is this an unicode string? */
244 bool unicode
= false;
246 size_t len
= Utf8Decode(&marker
, &*src
);
248 if (marker
== NFO_UTF8_IDENTIFIER
) {
253 /* Helper variable for a possible (string) mapping. */
254 UnmappedChoiceList
*mapping
= nullptr;
256 std::ostringstream dest
;
257 std::ostreambuf_iterator
<char> d(dest
);
258 while (src
!= str
.cend()) {
261 if (unicode
&& Utf8EncodedCharLen(*src
) != 0) {
262 c
= Utf8Consume(src
);
263 /* 'Magic' range of control codes. */
264 if (GB(c
, 8, 8) == 0xE0) {
266 } else if (c
>= 0x20) {
267 if (!IsValidChar(c
, CS_ALPHANUMERAL
)) c
= '?';
272 c
= static_cast<uint8_t>(*src
++);
275 if (c
== '\0') break;
279 if (*src
== '\0') goto string_end
;
285 if (allow_newlines
) {
288 GrfMsg(1, "Detected newline in string that does not allow one");
291 case 0x0E: Utf8Encode(d
, SCC_TINYFONT
); break;
292 case 0x0F: Utf8Encode(d
, SCC_BIGFONT
); break;
294 if (src
[0] == '\0' || src
[1] == '\0') goto string_end
;
302 case 0x7F: Utf8Encode(d
, SCC_NEWGRF_PRINT_DWORD_SIGNED
+ c
- 0x7B); break;
303 case 0x80: Utf8Encode(d
, byte80
); break;
306 if (src
[0] == '\0' || src
[1] == '\0') goto string_end
;
308 string
= static_cast<uint8_t>(*src
++);
309 string
|= static_cast<uint8_t>(*src
++) << 8;
310 Utf8Encode(d
, SCC_NEWGRF_STRINL
);
311 Utf8Encode(d
, MapGRFStringID(grfid
, string
));
316 case 0x84: Utf8Encode(d
, SCC_NEWGRF_PRINT_WORD_DATE_LONG
+ c
- 0x82); break;
317 case 0x85: Utf8Encode(d
, SCC_NEWGRF_DISCARD_WORD
); break;
318 case 0x86: Utf8Encode(d
, SCC_NEWGRF_ROTATE_TOP_4_WORDS
); break;
319 case 0x87: Utf8Encode(d
, SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
); break;
320 case 0x88: Utf8Encode(d
, SCC_BLUE
); break;
321 case 0x89: Utf8Encode(d
, SCC_SILVER
); break;
322 case 0x8A: Utf8Encode(d
, SCC_GOLD
); break;
323 case 0x8B: Utf8Encode(d
, SCC_RED
); break;
324 case 0x8C: Utf8Encode(d
, SCC_PURPLE
); break;
325 case 0x8D: Utf8Encode(d
, SCC_LTBROWN
); break;
326 case 0x8E: Utf8Encode(d
, SCC_ORANGE
); break;
327 case 0x8F: Utf8Encode(d
, SCC_GREEN
); break;
328 case 0x90: Utf8Encode(d
, SCC_YELLOW
); break;
329 case 0x91: Utf8Encode(d
, SCC_DKGREEN
); break;
330 case 0x92: Utf8Encode(d
, SCC_CREAM
); break;
331 case 0x93: Utf8Encode(d
, SCC_BROWN
); break;
332 case 0x94: Utf8Encode(d
, SCC_WHITE
); break;
333 case 0x95: Utf8Encode(d
, SCC_LTBLUE
); break;
334 case 0x96: Utf8Encode(d
, SCC_GRAY
); break;
335 case 0x97: Utf8Encode(d
, SCC_DKBLUE
); break;
336 case 0x98: Utf8Encode(d
, SCC_BLACK
); break;
341 case 0x00: goto string_end
;
342 case 0x01: Utf8Encode(d
, SCC_NEWGRF_PRINT_QWORD_CURRENCY
); break;
343 /* 0x02: ignore next colour byte is not supported. It works on the final
344 * string and as such hooks into the string drawing routine. At that
345 * point many things already happened, such as splitting up of strings
346 * when drawn over multiple lines or right-to-left translations, which
347 * make the behaviour peculiar, e.g. only happening at specific width
348 * of windows. Or we need to add another pass over the string to just
349 * support this. As such it is not implemented in OpenTTD. */
352 if (src
[0] == '\0' || src
[1] == '\0') goto string_end
;
353 uint16_t tmp
= static_cast<uint8_t>(*src
++);
354 tmp
|= static_cast<uint8_t>(*src
++) << 8;
355 Utf8Encode(d
, SCC_NEWGRF_PUSH_WORD
);
359 case 0x06: Utf8Encode(d
, SCC_NEWGRF_PRINT_BYTE_HEX
); break;
360 case 0x07: Utf8Encode(d
, SCC_NEWGRF_PRINT_WORD_HEX
); break;
361 case 0x08: Utf8Encode(d
, SCC_NEWGRF_PRINT_DWORD_HEX
); break;
362 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
363 case 0x0B: Utf8Encode(d
, SCC_NEWGRF_PRINT_QWORD_HEX
); break;
364 case 0x0C: Utf8Encode(d
, SCC_NEWGRF_PRINT_WORD_STATION_NAME
); break;
365 case 0x0D: Utf8Encode(d
, SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
); break;
369 if (str
[0] == '\0') goto string_end
;
370 const LanguageMap
*lm
= LanguageMap::GetLanguageMap(grfid
, language_id
);
372 int mapped
= lm
!= nullptr ? lm
->GetMapping(index
, code
== 0x0E) : -1;
374 Utf8Encode(d
, code
== 0x0E ? SCC_GENDER_INDEX
: SCC_SET_CASE
);
375 Utf8Encode(d
, code
== 0x0E ? mapped
: mapped
+ 1);
382 if (str
[0] == '\0') goto string_end
;
383 if (mapping
== nullptr) {
384 if (code
== 0x10) src
++; // Skip the index
385 GrfMsg(1, "choice list {} marker found when not expected", code
== 0x10 ? "next" : "default");
388 int index
= (code
== 0x10 ? *src
++ : 0);
389 if (mapping
->strings
.find(index
) != mapping
->strings
.end()) {
390 GrfMsg(1, "duplicate choice list string, ignoring");
392 d
= std::ostreambuf_iterator
<char>(mapping
->strings
[index
]);
398 if (mapping
== nullptr) {
399 GrfMsg(1, "choice list end marker found when not expected");
401 /* Now we can start flushing everything and clean everything up. */
402 mapping
->Flush(LanguageMap::GetLanguageMap(grfid
, language_id
), dest
);
406 d
= std::ostreambuf_iterator
<char>(dest
);
413 if (src
[0] == '\0') goto string_end
;
414 if (mapping
!= nullptr) {
415 GrfMsg(1, "choice lists can't be stacked, it's going to get messy now...");
416 if (code
!= 0x14) src
++;
418 static const StringControlCode mp
[] = { SCC_GENDER_LIST
, SCC_SWITCH_CASE
, SCC_PLURAL_LIST
};
419 mapping
= new UnmappedChoiceList(mp
[code
- 0x13], code
== 0x14 ? 0 : *src
++);
432 Utf8Encode(d
, SCC_NEWGRF_PRINT_DWORD_DATE_LONG
+ code
- 0x16);
435 case 0x1F: Utf8Encode(d
, SCC_PUSH_COLOUR
); break;
436 case 0x20: Utf8Encode(d
, SCC_POP_COLOUR
); break;
438 case 0x21: Utf8Encode(d
, SCC_NEWGRF_PRINT_DWORD_FORCE
); break;
441 GrfMsg(1, "missing handler for extended format code");
447 case 0x9E: Utf8Encode(d
, 0x20AC); break; // Euro
448 case 0x9F: Utf8Encode(d
, 0x0178); break; // Y with diaeresis
449 case 0xA0: Utf8Encode(d
, SCC_UP_ARROW
); break;
450 case 0xAA: Utf8Encode(d
, SCC_DOWN_ARROW
); break;
451 case 0xAC: Utf8Encode(d
, SCC_CHECKMARK
); break;
452 case 0xAD: Utf8Encode(d
, SCC_CROSS
); break;
453 case 0xAF: Utf8Encode(d
, SCC_RIGHT_ARROW
); break;
454 case 0xB4: Utf8Encode(d
, SCC_TRAIN
); break;
455 case 0xB5: Utf8Encode(d
, SCC_LORRY
); break;
456 case 0xB6: Utf8Encode(d
, SCC_BUS
); break;
457 case 0xB7: Utf8Encode(d
, SCC_PLANE
); break;
458 case 0xB8: Utf8Encode(d
, SCC_SHIP
); break;
459 case 0xB9: Utf8Encode(d
, SCC_SUPERSCRIPT_M1
); break;
460 case 0xBC: Utf8Encode(d
, SCC_SMALL_UP_ARROW
); break;
461 case 0xBD: Utf8Encode(d
, SCC_SMALL_DOWN_ARROW
); break;
463 /* Validate any unhandled character */
464 if (!IsValidChar(c
, CS_ALPHANUMERAL
)) c
= '?';
471 if (mapping
!= nullptr) {
472 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
480 * Add a new text to a GRFText list.
481 * @param list The list where the text should be added to.
482 * @param langid The The language of the new text.
483 * @param text_to_add The text to add to the list.
485 static void AddGRFTextToList(GRFTextList
&list
, uint8_t langid
, std::string_view text_to_add
)
487 /* Loop through all languages and see if we can replace a string */
488 for (auto &text
: list
) {
489 if (text
.langid
== langid
) {
490 text
.text
= text_to_add
;
495 /* If a string wasn't replaced, then we must append the new string */
496 list
.push_back(GRFText
{ langid
, std::string(text_to_add
) });
500 * Add a string to a GRFText list.
501 * @param list The list where the text should be added to.
502 * @param langid The language of the new text.
503 * @param grfid The grfid where this string is defined.
504 * @param allow_newlines Whether newlines are allowed in this string.
505 * @param text_to_add The text to add to the list.
506 * @note All text-codes will be translated.
508 void AddGRFTextToList(GRFTextList
&list
, uint8_t langid
, uint32_t grfid
, bool allow_newlines
, std::string_view text_to_add
)
510 AddGRFTextToList(list
, langid
, TranslateTTDPatchCodes(grfid
, langid
, allow_newlines
, text_to_add
));
514 * Add a string to a GRFText list.
515 * @param list The list where the text should be added to.
516 * @param langid The language of the new text.
517 * @param grfid The grfid where this string is defined.
518 * @param allow_newlines Whether newlines are allowed in this string.
519 * @param text_to_add The text to add to the list.
520 * @note All text-codes will be translated.
522 void AddGRFTextToList(GRFTextWrapper
&list
, uint8_t langid
, uint32_t grfid
, bool allow_newlines
, std::string_view text_to_add
)
524 if (!list
) list
.reset(new GRFTextList());
525 AddGRFTextToList(*list
, langid
, grfid
, allow_newlines
, text_to_add
);
529 * Add a GRFText to a GRFText list. The text should not contain any text-codes.
530 * The text will be added as a 'default language'-text.
531 * @param list The list where the text should be added to.
532 * @param text_to_add The text to add to the list.
534 void AddGRFTextToList(GRFTextWrapper
&list
, std::string_view text_to_add
)
536 if (!list
) list
.reset(new GRFTextList());
537 AddGRFTextToList(*list
, GRFLX_UNSPECIFIED
, text_to_add
);
541 * Add the new read string into our structure.
543 StringID
AddGRFString(uint32_t grfid
, uint16_t stringid
, uint8_t langid_to_add
, bool new_scheme
, bool allow_newlines
, std::string_view text_to_add
, StringID def_string
)
545 /* When working with the old language scheme (grf_version is less than 7) and
546 * English or American is among the set bits, simply add it as English in
547 * the new scheme, i.e. as langid = 1.
548 * If English is set, it is pretty safe to assume the translations are not
549 * actually translated.
552 if (langid_to_add
& (GRFLB_AMERICAN
| GRFLB_ENGLISH
)) {
553 langid_to_add
= GRFLX_ENGLISH
;
555 StringID ret
= STR_EMPTY
;
556 if (langid_to_add
& GRFLB_GERMAN
) ret
= AddGRFString(grfid
, stringid
, GRFLX_GERMAN
, true, allow_newlines
, text_to_add
, def_string
);
557 if (langid_to_add
& GRFLB_FRENCH
) ret
= AddGRFString(grfid
, stringid
, GRFLX_FRENCH
, true, allow_newlines
, text_to_add
, def_string
);
558 if (langid_to_add
& GRFLB_SPANISH
) ret
= AddGRFString(grfid
, stringid
, GRFLX_SPANISH
, true, allow_newlines
, text_to_add
, def_string
);
563 auto it
= std::ranges::find_if(_grf_text
, [&grfid
, &stringid
](const GRFTextEntry
&grf_text
) { return grf_text
.grfid
== grfid
&& grf_text
.stringid
== stringid
; });
564 if (it
== std::end(_grf_text
)) {
565 /* Too many strings allocated, return empty. */
566 if (_grf_text
.size() == TAB_SIZE_NEWGRF
) return STR_EMPTY
;
568 /* We didn't find our stringid and grfid in the list, allocate a new id. */
569 it
= _grf_text
.emplace(std::end(_grf_text
));
571 it
->stringid
= stringid
;
572 it
->def_string
= def_string
;
574 uint id
= static_cast<uint
>(it
- std::begin(_grf_text
));
576 std::string newtext
= TranslateTTDPatchCodes(grfid
, langid_to_add
, allow_newlines
, text_to_add
);
577 AddGRFTextToList(it
->textholder
, langid_to_add
, newtext
);
579 GrfMsg(3, "Added 0x{:X} grfid {:08X} string 0x{:X} lang 0x{:X} string '{}' ({:X})", id
, grfid
, stringid
, langid_to_add
, newtext
, MakeStringID(TEXT_TAB_NEWGRF_START
, id
));
581 return MakeStringID(TEXT_TAB_NEWGRF_START
, id
);
585 * Returns the index for this stringid associated with its grfID
587 StringID
GetGRFStringID(uint32_t grfid
, StringID stringid
)
589 auto it
= std::ranges::find_if(_grf_text
, [&grfid
, &stringid
](const GRFTextEntry
&grf_text
) { return grf_text
.grfid
== grfid
&& grf_text
.stringid
== stringid
; });
590 if (it
!= std::end(_grf_text
)) {
591 uint id
= static_cast<uint
>(it
- std::begin(_grf_text
));
592 return MakeStringID(TEXT_TAB_NEWGRF_START
, id
);
595 return STR_UNDEFINED
;
600 * Get a C-string from a GRFText-list. If there is a translation for the
601 * current language it is returned, otherwise the default translation
602 * is returned. If there is neither a default nor a translation for the
603 * current language nullptr is returned.
604 * @param text_list The GRFTextList to get the string from.
606 const char *GetGRFStringFromGRFText(const GRFTextList
&text_list
)
608 const char *default_text
= nullptr;
610 /* Search the list of lang-strings of this stringid for current lang */
611 for (const auto &text
: text_list
) {
612 if (text
.langid
== _currentLangID
) return text
.text
.c_str();
614 /* If the current string is English or American, set it as the
615 * fallback language if the specific language isn't available. */
616 if (text
.langid
== GRFLX_UNSPECIFIED
|| (default_text
== nullptr && (text
.langid
== GRFLX_ENGLISH
|| text
.langid
== GRFLX_AMERICAN
))) {
617 default_text
= text
.text
.c_str();
625 * Get a C-string from a GRFText-list. If there is a translation for the
626 * current language it is returned, otherwise the default translation
627 * is returned. If there is neither a default nor a translation for the
628 * current language nullptr is returned.
629 * @param text The GRFTextList to get the string from.
631 const char *GetGRFStringFromGRFText(const GRFTextWrapper
&text
)
633 return text
? GetGRFStringFromGRFText(*text
) : nullptr;
637 * Get a C-string from a stringid set by a newgrf.
639 const char *GetGRFStringPtr(uint32_t stringid
)
641 assert(stringid
< _grf_text
.size());
642 assert(_grf_text
[stringid
].grfid
!= 0);
644 const char *str
= GetGRFStringFromGRFText(_grf_text
[stringid
].textholder
);
645 if (str
!= nullptr) return str
;
647 /* Use the default string ID if the fallback string isn't available */
648 return GetStringPtr(_grf_text
[stringid
].def_string
);
652 * Equivalence Setter function between game and newgrf langID.
653 * This function will adjust _currentLangID as to what is the LangID
654 * of the current language set by the user.
655 * This function is called after the user changed language,
656 * from strings.cpp:ReadLanguagePack
657 * @param language_id iso code of current selection
659 void SetCurrentGrfLangID(uint8_t language_id
)
661 _currentLangID
= language_id
;
664 bool CheckGrfLangID(uint8_t lang_id
, uint8_t grf_version
)
666 if (grf_version
< 7) {
667 switch (_currentLangID
) {
668 case GRFLX_GERMAN
: return (lang_id
& GRFLB_GERMAN
) != 0;
669 case GRFLX_FRENCH
: return (lang_id
& GRFLB_FRENCH
) != 0;
670 case GRFLX_SPANISH
: return (lang_id
& GRFLB_SPANISH
) != 0;
671 default: return (lang_id
& (GRFLB_ENGLISH
| GRFLB_AMERICAN
)) != 0;
675 return (lang_id
== _currentLangID
|| lang_id
== GRFLX_UNSPECIFIED
);
680 * Remove all strings.
682 void CleanUpStrings()
687 struct TextRefStack
{
688 std::array
<uint8_t, 0x30> stack
;
690 const GRFFile
*grffile
;
693 TextRefStack() : position(0), grffile(nullptr), used(false) {}
695 uint8_t PopUnsignedByte() { assert(this->position
< this->stack
.size()); return this->stack
[this->position
++]; }
696 int8_t PopSignedByte() { return (int8_t)this->PopUnsignedByte(); }
698 uint16_t PopUnsignedWord()
700 uint16_t val
= this->PopUnsignedByte();
701 return val
| (this->PopUnsignedByte() << 8);
703 int16_t PopSignedWord() { return (int32_t)this->PopUnsignedWord(); }
705 uint32_t PopUnsignedDWord()
707 uint32_t val
= this->PopUnsignedWord();
708 return val
| (this->PopUnsignedWord() << 16);
710 int32_t PopSignedDWord() { return (int32_t)this->PopUnsignedDWord(); }
712 uint64_t PopUnsignedQWord()
714 uint64_t val
= this->PopUnsignedDWord();
715 return val
| (((uint64_t)this->PopUnsignedDWord()) << 32);
717 int64_t PopSignedQWord() { return (int64_t)this->PopUnsignedQWord(); }
719 /** Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3 */
720 void RotateTop4Words()
723 for (int i
= 0; i
< 2; i
++) tmp
[i
] = this->stack
[this->position
+ i
+ 6];
724 for (int i
= 5; i
>= 0; i
--) this->stack
[this->position
+ i
+ 2] = this->stack
[this->position
+ i
];
725 for (int i
= 0; i
< 2; i
++) this->stack
[this->position
+ i
] = tmp
[i
];
728 void PushWord(uint16_t word
)
730 if (this->position
>= 2) {
733 // Rotate right 2 positions
734 std::rotate(this->stack
.rbegin(), this->stack
.rbegin() + 2, this->stack
.rend());
736 this->stack
[this->position
] = GB(word
, 0, 8);
737 this->stack
[this->position
+ 1] = GB(word
, 8, 8);
740 void ResetStack(const GRFFile
*grffile
)
742 assert(grffile
!= nullptr);
744 this->grffile
= grffile
;
749 /** The stack that is used for TTDP compatible string code parsing */
750 static TextRefStack _newgrf_textrefstack
;
753 * Check whether the NewGRF text stack is in use.
754 * @return True iff the NewGRF text stack is used.
756 bool UsingNewGRFTextStack()
758 return _newgrf_textrefstack
.used
;
762 * Create a backup of the current NewGRF text stack.
763 * @return A copy of the current text stack.
765 struct TextRefStack
*CreateTextRefStackBackup()
767 return new TextRefStack(_newgrf_textrefstack
);
771 * Restore a copy of the text stack to the used stack.
772 * @param backup The copy to restore.
774 void RestoreTextRefStackBackup(struct TextRefStack
*backup
)
776 _newgrf_textrefstack
= *backup
;
781 * Start using the TTDP compatible string code parsing.
783 * On start a number of values is copied on the #TextRefStack.
784 * You can then use #GetString() and the normal string drawing functions,
785 * and they will use the #TextRefStack for NewGRF string codes.
787 * However, when you want to draw a string multiple times using the same stack,
788 * you have to call #RewindTextRefStack() between draws.
790 * After you are done with drawing, you must disable usage of the #TextRefStack
791 * by calling #StopTextRefStackUsage(), so NewGRF string codes operate on the
792 * normal string parameters again.
794 * @param grffile the NewGRF providing the stack data
795 * @param numEntries number of entries to copy from the registers
796 * @param values values to copy onto the stack; if nullptr the temporary NewGRF registers will be used instead
798 void StartTextRefStackUsage(const GRFFile
*grffile
, uint8_t numEntries
, const uint32_t *values
)
800 extern TemporaryStorageArray
<int32_t, 0x110> _temp_store
;
802 _newgrf_textrefstack
.ResetStack(grffile
);
804 auto stack_it
= _newgrf_textrefstack
.stack
.begin();
805 for (uint i
= 0; i
< numEntries
; i
++) {
806 uint32_t value
= values
!= nullptr ? values
[i
] : _temp_store
.GetValue(0x100 + i
);
807 for (uint j
= 0; j
< 32; j
+= 8) {
808 *stack_it
= GB(value
, j
, 8);
814 /** Stop using the TTDP compatible string code parsing */
815 void StopTextRefStackUsage()
817 _newgrf_textrefstack
.used
= false;
821 * FormatString for NewGRF specific "magic" string control codes
822 * @param scc the string control code that has been read
823 * @param str the string that we need to write
824 * @param parameters the OpenTTD string formatting parameters
825 * @param modify_parameters When true, modify the OpenTTD string formatting parameters.
826 * @return the string control code to "execute" now
828 uint
RemapNewGRFStringControlCode(uint scc
, const char **str
, StringParameters
¶meters
, bool modify_parameters
)
833 /* These control codes take one string parameter, check there are at least that many available. */
834 case SCC_NEWGRF_PRINT_DWORD_SIGNED
:
835 case SCC_NEWGRF_PRINT_WORD_SIGNED
:
836 case SCC_NEWGRF_PRINT_BYTE_SIGNED
:
837 case SCC_NEWGRF_PRINT_WORD_UNSIGNED
:
838 case SCC_NEWGRF_PRINT_BYTE_HEX
:
839 case SCC_NEWGRF_PRINT_WORD_HEX
:
840 case SCC_NEWGRF_PRINT_DWORD_HEX
:
841 case SCC_NEWGRF_PRINT_QWORD_HEX
:
842 case SCC_NEWGRF_PRINT_DWORD_CURRENCY
:
843 case SCC_NEWGRF_PRINT_QWORD_CURRENCY
:
844 case SCC_NEWGRF_PRINT_WORD_STRING_ID
:
845 case SCC_NEWGRF_PRINT_WORD_DATE_LONG
:
846 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG
:
847 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT
:
848 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
:
849 case SCC_NEWGRF_PRINT_WORD_SPEED
:
850 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
:
851 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
:
852 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
:
853 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
:
854 case SCC_NEWGRF_PRINT_WORD_POWER
:
855 case SCC_NEWGRF_PRINT_DWORD_FORCE
:
856 case SCC_NEWGRF_PRINT_WORD_STATION_NAME
:
857 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME
:
858 if (parameters
.GetDataLeft() < 1) {
859 Debug(misc
, 0, "Too many NewGRF string parameters.");
864 /* These string code take two string parameters, check there are at least that many available. */
865 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG
:
866 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
:
867 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY
:
868 if (parameters
.GetDataLeft() < 2) {
869 Debug(misc
, 0, "Too many NewGRF string parameters.");
875 if (_newgrf_textrefstack
.used
&& modify_parameters
) {
876 /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack.
877 * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */
879 default: NOT_REACHED();
880 case SCC_NEWGRF_PRINT_BYTE_SIGNED
: parameters
.SetParam(0, _newgrf_textrefstack
.PopSignedByte()); break;
881 case SCC_NEWGRF_PRINT_QWORD_CURRENCY
: parameters
.SetParam(0, _newgrf_textrefstack
.PopSignedQWord()); break;
883 case SCC_NEWGRF_PRINT_DWORD_CURRENCY
:
884 case SCC_NEWGRF_PRINT_DWORD_SIGNED
: parameters
.SetParam(0, _newgrf_textrefstack
.PopSignedDWord()); break;
886 case SCC_NEWGRF_PRINT_BYTE_HEX
: parameters
.SetParam(0, _newgrf_textrefstack
.PopUnsignedByte()); break;
887 case SCC_NEWGRF_PRINT_QWORD_HEX
: parameters
.SetParam(0, _newgrf_textrefstack
.PopUnsignedQWord()); break;
889 case SCC_NEWGRF_PRINT_WORD_SPEED
:
890 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
:
891 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
:
892 case SCC_NEWGRF_PRINT_WORD_SIGNED
: parameters
.SetParam(0, _newgrf_textrefstack
.PopSignedWord()); break;
894 case SCC_NEWGRF_PRINT_WORD_HEX
:
895 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
:
896 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
:
897 case SCC_NEWGRF_PRINT_WORD_POWER
:
898 case SCC_NEWGRF_PRINT_WORD_STATION_NAME
:
899 case SCC_NEWGRF_PRINT_WORD_UNSIGNED
: parameters
.SetParam(0, _newgrf_textrefstack
.PopUnsignedWord()); break;
901 case SCC_NEWGRF_PRINT_DWORD_FORCE
:
902 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG
:
903 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
:
904 case SCC_NEWGRF_PRINT_DWORD_HEX
: parameters
.SetParam(0, _newgrf_textrefstack
.PopUnsignedDWord()); break;
906 /* Dates from NewGRFs have 1920-01-01 as their zero point, convert it to OpenTTD's epoch. */
907 case SCC_NEWGRF_PRINT_WORD_DATE_LONG
:
908 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT
: parameters
.SetParam(0, _newgrf_textrefstack
.PopUnsignedWord() + CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR
); break;
910 case SCC_NEWGRF_DISCARD_WORD
: _newgrf_textrefstack
.PopUnsignedWord(); break;
912 case SCC_NEWGRF_ROTATE_TOP_4_WORDS
: _newgrf_textrefstack
.RotateTop4Words(); break;
913 case SCC_NEWGRF_PUSH_WORD
: _newgrf_textrefstack
.PushWord(Utf8Consume(str
)); break;
915 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG
:
916 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
:
917 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY
:
918 parameters
.SetParam(0, GetCargoTranslation(_newgrf_textrefstack
.PopUnsignedWord(), _newgrf_textrefstack
.grffile
));
919 parameters
.SetParam(1, _newgrf_textrefstack
.PopUnsignedWord());
922 case SCC_NEWGRF_PRINT_WORD_STRING_ID
:
923 parameters
.SetParam(0, MapGRFStringID(_newgrf_textrefstack
.grffile
->grfid
, _newgrf_textrefstack
.PopUnsignedWord()));
926 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME
: {
927 CargoID cargo
= GetCargoTranslation(_newgrf_textrefstack
.PopUnsignedWord(), _newgrf_textrefstack
.grffile
);
928 parameters
.SetParam(0, cargo
< NUM_CARGO
? 1ULL << cargo
: 0);
933 /* Consume additional parameter characters that follow the NewGRF string code. */
937 case SCC_NEWGRF_PUSH_WORD
:
943 /* Emit OpenTTD's internal string code for the different NewGRF variants. */
945 default: NOT_REACHED();
946 case SCC_NEWGRF_PRINT_DWORD_SIGNED
:
947 case SCC_NEWGRF_PRINT_WORD_SIGNED
:
948 case SCC_NEWGRF_PRINT_BYTE_SIGNED
:
949 case SCC_NEWGRF_PRINT_WORD_UNSIGNED
:
952 case SCC_NEWGRF_PRINT_BYTE_HEX
:
953 case SCC_NEWGRF_PRINT_WORD_HEX
:
954 case SCC_NEWGRF_PRINT_DWORD_HEX
:
955 case SCC_NEWGRF_PRINT_QWORD_HEX
:
958 case SCC_NEWGRF_PRINT_DWORD_CURRENCY
:
959 case SCC_NEWGRF_PRINT_QWORD_CURRENCY
:
960 return SCC_CURRENCY_LONG
;
962 case SCC_NEWGRF_PRINT_WORD_STRING_ID
:
963 return SCC_NEWGRF_PRINT_WORD_STRING_ID
;
965 case SCC_NEWGRF_PRINT_WORD_DATE_LONG
:
966 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG
:
967 return SCC_DATE_LONG
;
969 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT
:
970 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
:
971 return SCC_DATE_SHORT
;
973 case SCC_NEWGRF_PRINT_WORD_SPEED
:
976 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
:
977 return SCC_VOLUME_LONG
;
979 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
:
980 return SCC_VOLUME_SHORT
;
982 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
:
983 return SCC_WEIGHT_LONG
;
985 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
:
986 return SCC_WEIGHT_SHORT
;
988 case SCC_NEWGRF_PRINT_WORD_POWER
:
991 case SCC_NEWGRF_PRINT_DWORD_FORCE
:
994 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG
:
995 return SCC_CARGO_LONG
;
997 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
:
998 return SCC_CARGO_SHORT
;
1000 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY
:
1001 return SCC_CARGO_TINY
;
1003 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME
:
1004 return SCC_CARGO_LIST
;
1006 case SCC_NEWGRF_PRINT_WORD_STATION_NAME
:
1007 return SCC_STATION_NAME
;
1009 /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */
1010 case SCC_NEWGRF_DISCARD_WORD
:
1011 case SCC_NEWGRF_ROTATE_TOP_4_WORDS
:
1012 case SCC_NEWGRF_PUSH_WORD
: