Update: Translations from eints
[openttd-github.git] / src / newgrf_text.cpp
blob97ec944d80fae06b5a943f8ce5c0bcf1e73dc3fd
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /**
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.
18 #include "stdafx.h"
20 #include "newgrf.h"
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"
27 #include "debug.h"
28 #include "core/alloc_type.hpp"
29 #include "language.h"
30 #include <sstream>
32 #include "table/strings.h"
33 #include "table/control_codes.h"
35 #include "safeguards.h"
37 /**
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,
44 GRFLB_ENGLISH = 0x02,
45 GRFLB_GERMAN = 0x04,
46 GRFLB_FRENCH = 0x08,
47 GRFLB_SPANISH = 0x10,
48 GRFLB_GENERIC = 0x80,
51 enum GRFExtendedLanguages {
52 GRFLX_AMERICAN = 0x00,
53 GRFLX_ENGLISH = 0x01,
54 GRFLX_GERMAN = 0x02,
55 GRFLX_FRENCH = 0x03,
56 GRFLX_SPANISH = 0x04,
57 GRFLX_UNSPECIFIED = 0x7F,
61 /**
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.
66 struct GRFTextEntry {
67 GRFTextList textholder;
68 StringID def_string;
69 uint32_t grfid;
70 uint16_t stringid;
74 static std::vector<GRFTextEntry> _grf_text;
75 static uint8_t _currentLangID = GRFLX_ENGLISH; ///< by default, english is used.
77 /**
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;
89 return -1;
92 /**
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;
104 return -1;
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);
141 if (lm == nullptr) {
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();
146 return;
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.
158 /* "<NUM CASES>" */
159 int count = 0;
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++;
164 *d++ = 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();
172 /* "<CASEn>" */
173 *d++ = i + 1;
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);
180 /* "<STRINGn>" */
181 dest.write(str.c_str(), len);
182 *d++ = '\0';
185 /* "<STRINGDEFAULT>" */
186 dest << this->strings[0].rdbuf() << '\0';
187 } else {
188 if (this->type == SCC_PLURAL_LIST) {
189 *d++ = lm->plural_form;
193 * Format for choice list:
194 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
197 /* "<OFFSET>" */
198 *d++ = this->offset - 0x80;
200 /* "<NUM CHOICES>" */
201 int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
202 *d++ = count;
204 /* "<LENs>" */
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);
213 /* "<STRINGs>" */
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);
221 *d++ = '\0';
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;
245 char32_t marker;
246 size_t len = Utf8Decode(&marker, &*src);
248 if (marker == NFO_UTF8_IDENTIFIER) {
249 unicode = true;
250 src += len;
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()) {
259 char32_t c;
261 if (unicode && Utf8EncodedCharLen(*src) != 0) {
262 c = Utf8Consume(src);
263 /* 'Magic' range of control codes. */
264 if (GB(c, 8, 8) == 0xE0) {
265 c = GB(c, 0, 8);
266 } else if (c >= 0x20) {
267 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
268 Utf8Encode(d, c);
269 continue;
271 } else {
272 c = static_cast<uint8_t>(*src++);
275 if (c == '\0') break;
277 switch (c) {
278 case 0x01:
279 if (*src == '\0') goto string_end;
280 Utf8Encode(d, ' ');
281 src++;
282 break;
283 case 0x0A: break;
284 case 0x0D:
285 if (allow_newlines) {
286 *d++ = 0x0A;
287 } else {
288 GrfMsg(1, "Detected newline in string that does not allow one");
290 break;
291 case 0x0E: Utf8Encode(d, SCC_TINYFONT); break;
292 case 0x0F: Utf8Encode(d, SCC_BIGFONT); break;
293 case 0x1F:
294 if (src[0] == '\0' || src[1] == '\0') goto string_end;
295 Utf8Encode(d, ' ');
296 src += 2;
297 break;
298 case 0x7B:
299 case 0x7C:
300 case 0x7D:
301 case 0x7E:
302 case 0x7F: Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
303 case 0x80: Utf8Encode(d, byte80); break;
304 case 0x81:
306 if (src[0] == '\0' || src[1] == '\0') goto string_end;
307 StringID string;
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));
312 break;
314 case 0x82:
315 case 0x83:
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;
337 case 0x9A:
339 int code = *src++;
340 switch (code) {
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. */
350 case 0x03:
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);
356 Utf8Encode(d, tmp);
357 break;
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;
366 case 0x0E:
367 case 0x0F:
369 if (str[0] == '\0') goto string_end;
370 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
371 int index = *src++;
372 int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1;
373 if (mapped >= 0) {
374 Utf8Encode(d, code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
375 Utf8Encode(d, code == 0x0E ? mapped : mapped + 1);
377 break;
380 case 0x10:
381 case 0x11:
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");
386 break;
387 } else {
388 int index = (code == 0x10 ? *src++ : 0);
389 if (mapping->strings.find(index) != mapping->strings.end()) {
390 GrfMsg(1, "duplicate choice list string, ignoring");
391 } else {
392 d = std::ostreambuf_iterator<char>(mapping->strings[index]);
395 break;
397 case 0x12:
398 if (mapping == nullptr) {
399 GrfMsg(1, "choice list end marker found when not expected");
400 } else {
401 /* Now we can start flushing everything and clean everything up. */
402 mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id), dest);
403 delete mapping;
404 mapping = nullptr;
406 d = std::ostreambuf_iterator<char>(dest);
408 break;
410 case 0x13:
411 case 0x14:
412 case 0x15:
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++;
417 } else {
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++);
421 break;
423 case 0x16:
424 case 0x17:
425 case 0x18:
426 case 0x19:
427 case 0x1A:
428 case 0x1B:
429 case 0x1C:
430 case 0x1D:
431 case 0x1E:
432 Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
433 break;
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;
440 default:
441 GrfMsg(1, "missing handler for extended format code");
442 break;
444 break;
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;
462 default:
463 /* Validate any unhandled character */
464 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
465 Utf8Encode(d, c);
466 break;
470 string_end:
471 if (mapping != nullptr) {
472 GrfMsg(1, "choice list was incomplete, the whole list is ignored");
473 delete mapping;
476 return dest.str();
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;
491 return;
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.
551 if (!new_scheme) {
552 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
553 langid_to_add = GRFLX_ENGLISH;
554 } else {
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);
559 return ret;
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));
570 it->grfid = grfid;
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();
621 return default_text;
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);
679 * House cleaning.
680 * Remove all strings.
682 void CleanUpStrings()
684 _grf_text.clear();
687 struct TextRefStack {
688 std::array<uint8_t, 0x30> stack;
689 uint8_t position;
690 const GRFFile *grffile;
691 bool used;
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()
722 uint8_t tmp[2];
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) {
731 this->position -= 2;
732 } else {
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);
743 this->position = 0;
744 this->grffile = grffile;
745 this->used = true;
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;
777 delete 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);
809 stack_it++;
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 &parameters, bool modify_parameters)
830 switch (scc) {
831 default: break;
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.");
860 return 0;
862 break;
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.");
870 return 0;
872 break;
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. */
878 switch (scc) {
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());
920 break;
922 case SCC_NEWGRF_PRINT_WORD_STRING_ID:
923 parameters.SetParam(0, MapGRFStringID(_newgrf_textrefstack.grffile->grfid, _newgrf_textrefstack.PopUnsignedWord()));
924 break;
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);
929 break;
932 } else {
933 /* Consume additional parameter characters that follow the NewGRF string code. */
934 switch (scc) {
935 default: break;
937 case SCC_NEWGRF_PUSH_WORD:
938 Utf8Consume(str);
939 break;
943 /* Emit OpenTTD's internal string code for the different NewGRF variants. */
944 switch (scc) {
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:
950 return SCC_COMMA;
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:
956 return SCC_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:
974 return SCC_VELOCITY;
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:
989 return SCC_POWER;
991 case SCC_NEWGRF_PRINT_DWORD_FORCE:
992 return SCC_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:
1013 return 0;