Add a helper FindGRFStringEntry to find a string map
[openttd/fttd.git] / src / newgrf_text.cpp
blob2efc3d7f0d07a4a08e8ec92f12d84a80916c04f8
1 /* $Id$ */
3 /*
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/>.
8 */
10 /**
11 * @file newgrf_text.cpp
12 * Implementation of Action 04 "universal holder" structure and functions.
13 * This file implements a linked-lists of strings,
14 * holding everything that the newgrf action 04 will send over to OpenTTD.
15 * One of the biggest problems is that Dynamic lang Array uses ISO codes
16 * as way to identifying current user lang, while newgrf uses bit shift codes
17 * not related to ISO. So equivalence functionnality had to be set.
20 #include "stdafx.h"
21 #include "newgrf.h"
22 #include "strings_func.h"
23 #include "newgrf_storage.h"
24 #include "newgrf_text.h"
25 #include "newgrf_cargo.h"
26 #include "string.h"
27 #include "date_type.h"
28 #include "debug.h"
29 #include "core/pointer.h"
30 #include "core/flexarray.h"
31 #include "core/alloc_type.hpp"
32 #include "core/smallmap_type.hpp"
33 #include "language.h"
35 #include "table/strings.h"
36 #include "table/control_codes.h"
38 /**
39 * Explains the newgrf shift bit positioning.
40 * the grf base will not be used in order to find the string, but rather for
41 * jumping from standard langID scheme to the new one.
43 enum GRFBaseLanguages {
44 GRFLB_AMERICAN = 0x01,
45 GRFLB_ENGLISH = 0x02,
46 GRFLB_GERMAN = 0x04,
47 GRFLB_FRENCH = 0x08,
48 GRFLB_SPANISH = 0x10,
49 GRFLB_GENERIC = 0x80,
52 enum GRFExtendedLanguages {
53 GRFLX_AMERICAN = 0x00,
54 GRFLX_ENGLISH = 0x01,
55 GRFLX_GERMAN = 0x02,
56 GRFLX_FRENCH = 0x03,
57 GRFLX_SPANISH = 0x04,
58 GRFLX_UNSPECIFIED = 0x7F,
62 /**
63 * Holder of a GRFTextMap.
64 * Putting both grfid and stringid together allows us to avoid duplicates,
65 * since it is NOT SUPPOSED to happen.
67 struct GRFTextEntry {
68 uint32 grfid;
69 uint16 stringid;
70 StringID def_string;
71 GRFTextMap *map;
75 static uint _num_grf_texts = 0;
76 static GRFTextEntry _grf_text[TAB_SIZE_NEWGRF];
77 static byte _currentLangID = GRFLX_ENGLISH; ///< by default, english is used.
79 /**
80 * Get the mapping from the NewGRF supplied ID to OpenTTD's internal ID.
81 * @param newgrf_id The NewGRF ID to map.
82 * @param gender Whether to map genders or cases.
83 * @return The, to OpenTTD's internal ID, mapped index, or -1 if there is no mapping.
85 int LanguageMap::GetMapping(int newgrf_id, bool gender) const
87 const SmallVector<Mapping, 1> &map = gender ? this->gender_map : this->case_map;
88 for (const Mapping *m = map.Begin(); m != map.End(); m++) {
89 if (m->newgrf_id == newgrf_id) return m->openttd_id;
91 return -1;
94 /**
95 * Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
96 * @param openttd_id The OpenTTD ID to map.
97 * @param gender Whether to map genders or cases.
98 * @return The, to the NewGRF supplied ID, mapped index, or -1 if there is no mapping.
100 int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
102 const SmallVector<Mapping, 1> &map = gender ? this->gender_map : this->case_map;
103 for (const Mapping *m = map.Begin(); m != map.End(); m++) {
104 if (m->openttd_id == openttd_id) return m->newgrf_id;
106 return -1;
110 /** Allocated string with (some) built-in bounds checking. */
111 struct mstring : stringb, FlexArray <char> {
112 char data[];
114 private:
115 mstring (size_t n) : stringb (n, this->data)
119 public:
120 static mstring *create (size_t n = 1)
122 return new (n) mstring (n);
127 /** Dump the representation of a switch case mapping. */
128 static void DumpSwitchMapping (stringb *buf, const LanguageMap *lm,
129 const ttd_unique_free_ptr<mstring> (&mapping) [256])
132 * Format for case switch:
133 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
134 * Each LEN is printed using 2 bytes in big endian order.
136 int cases [MAX_NUM_CASES];
138 assert (_current_language->num_cases <= lengthof(cases));
140 /* "<NUM CASES>" */
141 uint count = 0;
142 for (uint8 i = 0; i < _current_language->num_cases; i++) {
143 /* Count the ones we have a mapped string for. */
144 int idx = lm->GetReverseMapping (i, false);
145 cases[i] = idx;
146 if ((idx >= 0) && mapping[idx]) count++;
148 buf->append ((char)count);
150 for (uint8 i = 0; i < _current_language->num_cases; i++) {
151 /* Resolve the string we're looking for. */
152 int idx = cases[i];
153 if ((idx < 0) || !mapping[idx]) continue;
154 const mstring *m = mapping[idx].get();
156 /* "<CASEn>" */
157 buf->append ((char)(i + 1));
159 /* "<LENn>" */
160 size_t len = m->length() + 1;
161 buf->append ((char)GB(len, 8, 8));
162 buf->append ((char)GB(len, 0, 8));
164 /* "<STRINGn>" */
165 buf->append (m->c_str());
166 buf->append ('\0');
169 /* "<STRINGDEFAULT>" */
170 buf->append (mapping[0]->c_str());
171 buf->append ('\0');
174 /** Dump the representation of a choice list. */
175 static void DumpChoiceList (stringb *buf, const LanguageMap *lm,
176 const ttd_unique_free_ptr<mstring> (&mapping) [256],
177 int offset, bool gender)
180 * Format for choice list:
181 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
183 const mstring *strs [(MAX_NUM_GENDERS > LANGUAGE_MAX_PLURAL_FORMS) ? MAX_NUM_GENDERS : LANGUAGE_MAX_PLURAL_FORMS];
185 /* "<OFFSET>" */
186 buf->append ((char)(offset - 0x80));
188 /* "<NUM CHOICES>" */
189 uint count = gender ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS;
190 buf->append ((char)count);
192 assert (count <= lengthof(strs));
194 /* "<LENs>" */
195 for (uint i = 0; i < count; i++) {
196 int idx = gender ? lm->GetReverseMapping (i, true) : i + 1;
197 const mstring *m = mapping[(idx >= 0) && mapping[idx] ? idx : 0].get();
198 strs[i] = m;
199 size_t len = m->length() + 1;
200 if (len > 0xFF) {
201 grfmsg(1, "choice list string is too long");
202 len = 0xFF;
204 buf->append ((char)len);
207 /* "<STRINGs>" */
208 for (uint i = 0; i < count; i++) {
209 const mstring *m = strs[i];
210 /* Limit the length of the string we copy to 0xFE. The length is written above
211 * as a byte and we need room for the final '\0'. */
212 size_t len = min<size_t> (0xFE, m->length());
213 buf->append_fmt ("%.*s", (int)len, m->c_str());
214 buf->append ('\0');
218 /** Dump the representation of a string mapping. */
219 static void DumpMapping (stringb *buf, const LanguageMap *lm,
220 const ttd_unique_free_ptr<mstring> (&mapping) [256],
221 StringControlCode type, int offset)
223 if (lm == NULL) {
224 /* In case there is no mapping, just ignore everything but the default.
225 * A probable cause for this happening is when the language file has
226 * been removed by the user and as such no mapping could be made. */
227 buf->append (mapping[0]->c_str());
228 return;
231 buf->append_utf8 (type);
233 switch (type) {
234 case SCC_SWITCH_CASE:
235 DumpSwitchMapping (buf, lm, mapping);
236 break;
238 case SCC_PLURAL_LIST:
239 buf->append ((char)lm->plural_form);
240 /* fall through */
241 default:
242 DumpChoiceList (buf, lm, mapping, offset, type == SCC_GENDER_LIST);
243 break;
248 * Translate TTDPatch string codes into something we can handle better.
249 * @param out The string to append the result to.
250 * @param str The string to translate.
251 * @param grfid The (NewGRF) ID associated with this string
252 * @param language_id The (NewGRF) language ID associated with this string.
253 * @param allow_newlines Whether newlines are allowed in the string or not.
254 * @param byte80 The control code to use as replacement for the 0x80-value.
256 static void TranslateStringCodes (stringb *out, const char *str, uint32 grfid,
257 uint8 language_id, bool allow_newlines, StringControlCode byte80)
259 enum {
260 CTRL_EOF, ///< End of string.
261 CTRL_HSKIP, ///< Variable space.
262 CTRL_NOP, ///< Ignore.
263 CTRL_NL, ///< New line.
264 CTRL_SETXY, ///< Set string position.
265 CTRL_PRSTK, ///< Print string from stack.
266 CTRL_PRSTR, ///< Print immediate string.
267 CTRL_EXT, ///< Extended format code.
270 assert_compile (CTRL_EXT < 0x20);
272 static const uint16 ctrl [0xBE] = {
273 CTRL_EOF, CTRL_HSKIP, '?', '?', '?', '?', '?', '?',
274 '?', '?', CTRL_NOP, '?',
275 '?', CTRL_NL, SCC_TINYFONT, SCC_BIGFONT,
276 '?', '?', '?', '?', '?', '?', '?', '?',
277 '?', '?', '?', '?', '?', '?', '?', CTRL_SETXY,
278 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
279 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
280 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
281 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
282 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
283 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
284 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
285 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
286 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
287 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
288 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
289 0x78, 0x79, 0x7A, SCC_NEWGRF_PRINT_DWORD_SIGNED,
290 SCC_NEWGRF_PRINT_WORD_SIGNED, // 0x7C
291 SCC_NEWGRF_PRINT_BYTE_SIGNED, // 0x7D
292 SCC_NEWGRF_PRINT_WORD_UNSIGNED, // 0x7E
293 SCC_NEWGRF_PRINT_DWORD_CURRENCY, // 0x7F
294 CTRL_PRSTK, CTRL_PRSTR, // 0x80, 0x81
295 SCC_NEWGRF_PRINT_WORD_DATE_LONG, // 0x82
296 SCC_NEWGRF_PRINT_WORD_DATE_SHORT, // 0x83
297 SCC_NEWGRF_PRINT_WORD_SPEED, // 0x84
298 SCC_NEWGRF_DISCARD_WORD, // 0x85
299 SCC_NEWGRF_ROTATE_TOP_4_WORDS, // 0x86
300 SCC_NEWGRF_PRINT_WORD_VOLUME_LONG, // 0x87
301 SCC_BLUE, SCC_SILVER, SCC_GOLD, SCC_RED, // 0x88
302 SCC_PURPLE, SCC_LTBROWN, SCC_ORANGE, SCC_GREEN,
303 SCC_YELLOW, SCC_DKGREEN, SCC_CREAM, SCC_BROWN, // 0x90
304 SCC_WHITE, SCC_LTBLUE, SCC_GRAY, SCC_DKBLUE,
305 SCC_BLACK, 0x99, CTRL_EXT, 0x9B, 0x9C, 0x9D, 0x20AC, 0x0178,
306 SCC_UP_ARROW, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
307 0xA8, 0xA9, SCC_DOWN_ARROW, 0xAB,
308 SCC_CHECKMARK, SCC_CROSS, 0xAE, SCC_RIGHT_ARROW,
309 0xB0, 0xB1, 0xB2, 0xB3,
310 SCC_TRAIN, SCC_LORRY, SCC_BUS, SCC_PLANE,
311 SCC_SHIP, SCC_SUPERSCRIPT_M1, 0xBA, 0xBB,
312 SCC_SMALL_UP_ARROW, SCC_SMALL_DOWN_ARROW,
315 stringb *buf = out;
317 /* Helper variables for a possible (string) mapping. */
318 StringControlCode mapping_type = (StringControlCode)0;
319 int mapping_offset;
321 /* Mapping of NewGRF-supplied ID to the different strings in the choice list. */
322 ttd_unique_free_ptr<mstring> mapping_strings[256];
324 bool unicode = skip_nfo_utf8_identifier (&str);
326 for (;;) {
327 WChar c;
328 bool try_control = true;
329 if (unicode && Utf8EncodedCharLen(*str) != 0) {
330 c = Utf8Consume(&str);
331 /* 'Magic' range of control codes. */
332 if (GB(c, 8, 8) == 0xE0) {
333 c = GB(c, 0, 8);
334 } else if (c >= 0x20) {
335 try_control = false;
337 } else {
338 c = (byte)*str++;
341 if (try_control && (c < lengthof(ctrl))) {
342 c = ctrl[c];
343 } else if (!IsPrintable (c)) {
344 c = '?';
347 switch (c) {
348 case CTRL_EOF:
349 goto string_end;
351 case CTRL_HSKIP:
352 if (str[0] == '\0') goto string_end;
353 buf->append (' ');
354 str++;
355 break;
357 case CTRL_NOP:
358 break;
360 case CTRL_NL:
361 if (allow_newlines) {
362 buf->append (0x0A);
363 } else {
364 grfmsg(1, "Detected newline in string that does not allow one");
366 break;
368 case CTRL_SETXY:
369 if (str[0] == '\0' || str[1] == '\0') goto string_end;
370 buf->append (' ');
371 str += 2;
372 break;
374 case CTRL_PRSTK:
375 buf->append_utf8 (byte80);
376 break;
378 case CTRL_PRSTR: {
379 if (str[0] == '\0' || str[1] == '\0') goto string_end;
380 StringID string;
381 string = ((uint8)*str++);
382 string |= ((uint8)*str++) << 8;
383 buf->append_utf8 (SCC_NEWGRF_STRINL);
384 buf->append_utf8 (MapGRFStringID (grfid, string));
385 break;
388 case CTRL_EXT: {
389 int code = *str++;
390 switch (code) {
391 case 0x00: goto string_end;
392 case 0x01: buf->append_utf8 (SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
393 /* 0x02: ignore next colour byte is not supported. It works on the final
394 * string and as such hooks into the string drawing routine. At that
395 * point many things already happened, such as splitting up of strings
396 * when drawn over multiple lines or right-to-left translations, which
397 * make the behaviour peculiar, e.g. only happening at specific width
398 * of windows. Or we need to add another pass over the string to just
399 * support this. As such it is not implemented in OpenTTD. */
400 case 0x03: {
401 if (str[0] == '\0' || str[1] == '\0') goto string_end;
402 uint16 tmp = ((uint8)*str++);
403 tmp |= ((uint8)*str++) << 8;
404 buf->append_utf8 (SCC_NEWGRF_PUSH_WORD);
405 buf->append_utf8 (tmp);
406 break;
408 case 0x04:
409 if (str[0] == '\0') goto string_end;
410 buf->append_utf8 (SCC_NEWGRF_UNPRINT);
411 buf->append_utf8 (*str++);
412 break;
413 case 0x06: buf->append_utf8 (SCC_NEWGRF_PRINT_BYTE_HEX); break;
414 case 0x07: buf->append_utf8 (SCC_NEWGRF_PRINT_WORD_HEX); break;
415 case 0x08: buf->append_utf8 (SCC_NEWGRF_PRINT_DWORD_HEX); break;
416 /* 0x09, 0x0A are TTDPatch internal use only string codes. */
417 case 0x0B: buf->append_utf8 (SCC_NEWGRF_PRINT_QWORD_HEX); break;
418 case 0x0C: buf->append_utf8 (SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
419 case 0x0D: buf->append_utf8 (SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break;
420 case 0x0E:
421 case 0x0F: {
422 if (str[0] == '\0') goto string_end;
423 const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
424 int index = *str++;
425 int mapped = lm != NULL ? lm->GetMapping(index, code == 0x0E) : -1;
426 if (mapped >= 0) {
427 buf->append_utf8 (code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
428 buf->append_utf8 (code == 0x0E ? mapped : mapped + 1);
430 break;
433 case 0x10:
434 case 0x11:
435 if (str[0] == '\0') goto string_end;
436 if (mapping_type == 0) {
437 if (code == 0x10) str++; // Skip the index
438 grfmsg(1, "choice list %s marker found when not expected", code == 0x10 ? "next" : "default");
439 break;
440 } else {
441 /* Terminate the previous string. */
442 byte index = (code == 0x10 ? *str++ : 0);
443 if (mapping_strings[index]) {
444 grfmsg(1, "duplicate choice list string, ignoring");
445 buf->append ('\0');
446 } else {
447 mstring *m = mstring::create (strlen(str) * 10 + 1);
448 mapping_strings[index].reset (m);
449 buf = m;
452 break;
454 case 0x12:
455 if (mapping_type == 0) {
456 grfmsg(1, "choice list end marker found when not expected");
457 } else {
458 /* Terminate the previous string. */
459 buf = out;
461 if (!mapping_strings[0]) {
462 /* In case of a (broken) NewGRF without a default,
463 * assume an empty string. */
464 grfmsg(1, "choice list misses default value");
465 mapping_strings[0].reset (mstring::create());
468 /* Now we can start flushing everything and clean everything up. */
469 const LanguageMap *lm = LanguageMap::GetLanguageMap (grfid, language_id);
470 DumpMapping (buf, lm, mapping_strings, mapping_type, mapping_offset);
472 mapping_type = (StringControlCode)0;
473 for (uint i = 0; i < lengthof(mapping_strings); i++) {
474 mapping_strings[i].reset();
477 break;
479 case 0x13:
480 case 0x14:
481 case 0x15:
482 if (str[0] == '\0') goto string_end;
483 if (mapping_type != 0) {
484 grfmsg(1, "choice lists can't be stacked, it's going to get messy now...");
485 if (code != 0x14) str++;
486 } else {
487 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
488 mapping_type = mp[code - 0x13];
489 if (code != 0x14) mapping_offset = *str++;
491 break;
493 case 0x16:
494 case 0x17:
495 case 0x18:
496 case 0x19:
497 case 0x1A:
498 case 0x1B:
499 case 0x1C:
500 case 0x1D:
501 case 0x1E:
502 buf->append_utf8 (SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16);
503 break;
505 default:
506 grfmsg(1, "missing handler for extended format code");
507 break;
509 break;
512 default:
513 buf->append_utf8 (c);
514 break;
518 string_end:
519 if (mapping_type != 0) {
520 grfmsg(1, "choice list was incomplete, the whole list is ignored");
526 * Allocate and assign a new GRFText with the given text,
527 * translating string codes.
528 * @param text The text to store in the new GRFText.
529 * @param grfid The grfid where this string is defined.
530 * @param langid The language of the new text.
531 * @param allow_newlines Whether newlines are allowed in this string.
533 GRFText *GRFText::create (const char *text, uint32 grfid, byte langid,
534 bool allow_newlines)
536 size_t tmp_len = strlen (text) * 10 + 1; // Allocate space to allow for expansion
537 char *tmp = xmalloc (tmp_len);
538 stringb tmp_buf (tmp_len, tmp);
540 TranslateStringCodes (&tmp_buf, text, grfid, langid, allow_newlines,
541 SCC_NEWGRF_PRINT_WORD_STRING_ID);
543 size_t len = tmp_buf.length() + 1;
544 GRFText *newtext = GRFText::create (tmp, len);
545 free (tmp);
546 return newtext;
550 /** Construct a copy of this text map. */
551 GRFTextMap::GRFTextMap (const GRFTextMap &other)
552 : std::map <byte, GRFText *> (other)
554 for (iterator iter = this->begin(); iter != this->end(); iter++) {
555 iter->second = iter->second->clone();
559 /** Destroy the text map. */
560 GRFTextMap::~GRFTextMap()
562 for (iterator iter = this->begin(); iter != this->end(); iter++) {
563 delete iter->second;
567 /** Get the GRFText for the current language, or a default. */
568 const GRFText *GRFTextMap::get_current (void) const
570 byte langs[4] = { _currentLangID, GRFLX_UNSPECIFIED, GRFLX_ENGLISH, GRFLX_AMERICAN };
572 for (uint i = 0; i < lengthof(langs); i++) {
573 const_iterator iter = this->find (langs[i]);
574 if (iter != this->end()) return iter->second;
577 return NULL;
580 /** Add a GRFText to this map. */
581 void GRFTextMap::add (byte langid, GRFText *text)
583 std::pair <iterator, bool> pair =
584 this->insert (std::make_pair (langid, text));
586 if (!pair.second) {
587 /* The langid already existed in the map. */
588 GRFText *old = pair.first->second;
589 pair.first->second = text;
590 delete old;
595 * Add a string to this map.
596 * @param langid The language of the new text.
597 * @param grfid The grfid where this string is defined.
598 * @param allow_newlines Whether newlines are allowed in this string.
599 * @param text The text to add to the list.
600 * @note All text-codes will be translated.
602 void GRFTextMap::add (byte langid, uint32 grfid, bool allow_newlines, const char *text)
604 this->add (langid, GRFText::create (text, grfid, langid, allow_newlines));
609 * Translate TTDPatch string codes into something OpenTTD can handle (better).
610 * @param grfid The (NewGRF) ID associated with this string
611 * @param language_id The (NewGRF) language ID associated with this string.
612 * @param allow_newlines Whether newlines are allowed in the string or not.
613 * @param str The string to translate.
614 * @param byte80 The control code to use as replacement for the 0x80-value.
615 * @return The translated string.
617 char *TranslateTTDPatchCodes (uint32 grfid, uint8 language_id,
618 bool allow_newlines, const char *str, StringControlCode byte80)
620 size_t tmp_len = strlen (str) * 10 + 1; // Allocate space to allow for expansion
621 char *tmp = xmalloc (tmp_len);
622 stringb tmp_buf (tmp_len, tmp);
624 TranslateStringCodes (&tmp_buf, str, grfid, language_id,
625 allow_newlines, byte80);
627 return xrealloc (tmp, tmp_buf.length() + 1);
631 * Add the new read string into our structure.
633 static StringID AddGRFString (uint textid, byte langid, const char *text,
634 bool allow_newlines)
636 GRFTextEntry *entry = &_grf_text[textid];
637 GRFText *newtext = GRFText::create (text, entry->grfid,
638 langid, allow_newlines);
639 entry->map->add (langid, newtext);
641 StringID str = MakeStringID (TEXT_TAB_NEWGRF_START, textid);
643 grfmsg (3, "Added 0x%X: grfid %08X string 0x%X lang 0x%X string '%s' (%X)",
644 textid, entry->grfid, entry->stringid, langid,
645 newtext->text, str);
647 return str;
650 /** Find a GRF string entry. */
651 static uint FindGRFStringEntry (uint32 grfid, uint16 stringid)
653 for (uint id = 0; id < _num_grf_texts; id++) {
654 if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) {
655 return id;
658 return _num_grf_texts;
662 * Add the new read string into our structure.
664 StringID AddGRFString(uint32 grfid, uint16 stringid, byte langid_to_add, bool new_scheme, bool allow_newlines, const char *text_to_add, StringID def_string)
666 uint id = FindGRFStringEntry (grfid, stringid);
668 /* Too many strings allocated, return empty */
669 if (id == lengthof(_grf_text)) return STR_EMPTY;
671 /* If we didn't find our stringid and grfid in the list, allocate a new id */
672 if (id == _num_grf_texts) _num_grf_texts++;
674 if (_grf_text[id].map == NULL) {
675 _grf_text[id].grfid = grfid;
676 _grf_text[id].stringid = stringid;
677 _grf_text[id].def_string = def_string;
678 _grf_text[id].map = new GRFTextMap;
681 /* When working with the old language scheme (grf_version is less
682 * than 7) and English or American is among the set bits, simply
683 * add it as English in the new scheme, i.e. as langid = 1.
684 * If English is set, it is pretty safe to assume the translations
685 * are not actually translated.
687 if (!new_scheme) {
688 if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
689 langid_to_add = GRFLX_ENGLISH;
690 } else {
691 StringID ret = STR_EMPTY;
692 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString (id, GRFLX_GERMAN, text_to_add, allow_newlines);
693 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString (id, GRFLX_FRENCH, text_to_add, allow_newlines);
694 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString (id, GRFLX_SPANISH, text_to_add, allow_newlines);
695 return ret;
699 return AddGRFString (id, langid_to_add, text_to_add, allow_newlines);
703 * Returns the index for this stringid associated with its grfID
705 StringID GetGRFStringID(uint32 grfid, StringID stringid)
707 uint id = FindGRFStringEntry (grfid, stringid);
708 return (id == _num_grf_texts) ? STR_UNDEFINED :
709 MakeStringID (TEXT_TAB_NEWGRF_START, id);
714 * Get a C-string from a stringid set by a newgrf.
716 const char *GetGRFStringPtr(uint16 stringid)
718 assert(_grf_text[stringid].grfid != 0);
720 const char *str = _grf_text[stringid].map->get_string();
721 if (str != NULL) return str;
723 /* Use the default string ID if the fallback string isn't available */
724 return GetStringPtr(_grf_text[stringid].def_string);
728 * Equivalence Setter function between game and newgrf langID.
729 * This function will adjust _currentLangID as to what is the LangID
730 * of the current language set by the user.
731 * This function is called after the user changed language,
732 * from strings.cpp:ReadLanguagePack
733 * @param language_id iso code of current selection
735 void SetCurrentGrfLangID(byte language_id)
737 _currentLangID = language_id;
740 bool CheckGrfLangID(byte lang_id, byte grf_version)
742 if (grf_version < 7) {
743 switch (_currentLangID) {
744 case GRFLX_GERMAN: return (lang_id & GRFLB_GERMAN) != 0;
745 case GRFLX_FRENCH: return (lang_id & GRFLB_FRENCH) != 0;
746 case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
747 default: return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
751 return (lang_id == _currentLangID || lang_id == GRFLX_UNSPECIFIED);
755 * House cleaning.
756 * Remove all strings and reset the text counter.
758 void CleanUpStrings()
760 uint id;
762 for (id = 0; id < _num_grf_texts; id++) {
763 delete _grf_text[id].map;
764 _grf_text[id].grfid = 0;
765 _grf_text[id].stringid = 0;
766 _grf_text[id].map = NULL;
769 _num_grf_texts = 0;
772 struct TextRefStack {
773 byte stack[0x30];
774 byte position;
775 const GRFFile *grffile;
776 bool used;
778 TextRefStack() : position(0), grffile(NULL), used(false) {}
780 TextRefStack(const TextRefStack &stack) :
781 position(stack.position),
782 grffile(stack.grffile),
783 used(stack.used)
785 memcpy(this->stack, stack.stack, sizeof(this->stack));
788 uint8 PopUnsignedByte() { assert(this->position < lengthof(this->stack)); return this->stack[this->position++]; }
789 int8 PopSignedByte() { return (int8)this->PopUnsignedByte(); }
791 uint16 PopUnsignedWord()
793 uint16 val = this->PopUnsignedByte();
794 return val | (this->PopUnsignedByte() << 8);
796 int16 PopSignedWord() { return (int32)this->PopUnsignedWord(); }
798 uint32 PopUnsignedDWord()
800 uint32 val = this->PopUnsignedWord();
801 return val | (this->PopUnsignedWord() << 16);
803 int32 PopSignedDWord() { return (int32)this->PopUnsignedDWord(); }
805 uint64 PopUnsignedQWord()
807 uint64 val = this->PopUnsignedDWord();
808 return val | (((uint64)this->PopUnsignedDWord()) << 32);
810 int64 PopSignedQWord() { return (int64)this->PopUnsignedQWord(); }
812 /** Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3 */
813 void RotateTop4Words()
815 byte tmp[2];
816 for (int i = 0; i < 2; i++) tmp[i] = this->stack[this->position + i + 6];
817 for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i];
818 for (int i = 0; i < 2; i++) this->stack[this->position + i] = tmp[i];
821 void PushWord(uint16 word)
823 if (this->position >= 2) {
824 this->position -= 2;
825 } else {
826 for (int i = lengthof(stack) - 1; i >= this->position + 2; i--) {
827 this->stack[i] = this->stack[i - 2];
830 this->stack[this->position] = GB(word, 0, 8);
831 this->stack[this->position + 1] = GB(word, 8, 8);
834 void ResetStack(const GRFFile *grffile)
836 assert(grffile != NULL);
837 this->position = 0;
838 this->grffile = grffile;
839 this->used = true;
842 void RewindStack() { this->position = 0; }
845 /** The stack that is used for TTDP compatible string code parsing */
846 static TextRefStack _newgrf_textrefstack;
849 * Check whether the NewGRF text stack is in use.
850 * @return True iff the NewGRF text stack is used.
852 bool UsingNewGRFTextStack()
854 return _newgrf_textrefstack.used;
858 * Create a backup of the current NewGRF text stack.
859 * @return A copy of the current text stack.
861 struct TextRefStack *CreateTextRefStackBackup()
863 return new TextRefStack(_newgrf_textrefstack);
867 * Restore a copy of the text stack to the used stack.
868 * @param backup The copy to restore.
870 void RestoreTextRefStackBackup(struct TextRefStack *backup)
872 _newgrf_textrefstack = *backup;
873 delete backup;
877 * Start using the TTDP compatible string code parsing.
879 * On start a number of values is copied on the #TextRefStack.
880 * You can then use #GetString() and the normal string drawing functions,
881 * and they will use the #TextRefStack for NewGRF string codes.
883 * However, when you want to draw a string multiple times using the same stack,
884 * you have to call #RewindTextRefStack() between draws.
886 * After you are done with drawing, you must disable usage of the #TextRefStack
887 * by calling #StopTextRefStackUsage(), so NewGRF string codes operate on the
888 * normal string parameters again.
890 * @param grffile the NewGRF providing the stack data
891 * @param numEntries number of entries to copy from the registers
892 * @param values values to copy onto the stack; if NULL the temporary NewGRF registers will be used instead
894 void StartTextRefStackUsage(const GRFFile *grffile, byte numEntries, const uint32 *values)
896 extern TemporaryStorageArray<int32, 0x110> _temp_store;
898 _newgrf_textrefstack.ResetStack(grffile);
900 byte *p = _newgrf_textrefstack.stack;
901 for (uint i = 0; i < numEntries; i++) {
902 uint32 value = values != NULL ? values[i] : _temp_store.GetValue(0x100 + i);
903 for (uint j = 0; j < 32; j += 8) {
904 *p = GB(value, j, 8);
905 p++;
910 /** Stop using the TTDP compatible string code parsing */
911 void StopTextRefStackUsage()
913 _newgrf_textrefstack.used = false;
916 void RewindTextRefStack()
918 _newgrf_textrefstack.RewindStack();
922 * FormatString for NewGRF specific "magic" string control codes
923 * @param scc the string control code that has been read
924 * @param buf the buffer we're writing to
925 * @param str the string that we need to write
926 * @param argv the OpenTTD stack of values
927 * @param argv_size space on the stack \a argv
928 * @param modify_argv When true, modify the OpenTTD stack.
929 * @return the string control code to "execute" now
931 uint RemapNewGRFStringControlCode (uint scc, stringb *buf, const char **str, int64 *argv, uint argv_size, bool modify_argv)
933 switch (scc) {
934 default: break;
936 case SCC_NEWGRF_PRINT_DWORD_SIGNED:
937 case SCC_NEWGRF_PRINT_WORD_SIGNED:
938 case SCC_NEWGRF_PRINT_BYTE_SIGNED:
939 case SCC_NEWGRF_PRINT_WORD_UNSIGNED:
940 case SCC_NEWGRF_PRINT_BYTE_HEX:
941 case SCC_NEWGRF_PRINT_WORD_HEX:
942 case SCC_NEWGRF_PRINT_DWORD_HEX:
943 case SCC_NEWGRF_PRINT_QWORD_HEX:
944 case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
945 case SCC_NEWGRF_PRINT_QWORD_CURRENCY:
946 case SCC_NEWGRF_PRINT_WORD_STRING_ID:
947 case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
948 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
949 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT:
950 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
951 case SCC_NEWGRF_PRINT_WORD_SPEED:
952 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
953 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
954 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
955 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
956 case SCC_NEWGRF_PRINT_WORD_POWER:
957 case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
958 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME:
959 if (argv_size < 1) {
960 DEBUG(misc, 0, "Too many NewGRF string parameters.");
961 return 0;
963 break;
965 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG:
966 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT:
967 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY:
968 if (argv_size < 2) {
969 DEBUG(misc, 0, "Too many NewGRF string parameters.");
970 return 0;
972 break;
975 if (_newgrf_textrefstack.used && modify_argv) {
976 switch (scc) {
977 default: NOT_REACHED();
978 case SCC_NEWGRF_PRINT_BYTE_SIGNED: *argv = _newgrf_textrefstack.PopSignedByte(); break;
979 case SCC_NEWGRF_PRINT_QWORD_CURRENCY: *argv = _newgrf_textrefstack.PopSignedQWord(); break;
981 case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
982 case SCC_NEWGRF_PRINT_DWORD_SIGNED: *argv = _newgrf_textrefstack.PopSignedDWord(); break;
984 case SCC_NEWGRF_PRINT_BYTE_HEX: *argv = _newgrf_textrefstack.PopUnsignedByte(); break;
985 case SCC_NEWGRF_PRINT_QWORD_HEX: *argv = _newgrf_textrefstack.PopUnsignedQWord(); break;
987 case SCC_NEWGRF_PRINT_WORD_SPEED:
988 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
989 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
990 case SCC_NEWGRF_PRINT_WORD_SIGNED: *argv = _newgrf_textrefstack.PopSignedWord(); break;
992 case SCC_NEWGRF_PRINT_WORD_HEX:
993 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
994 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
995 case SCC_NEWGRF_PRINT_WORD_POWER:
996 case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
997 case SCC_NEWGRF_PRINT_WORD_UNSIGNED: *argv = _newgrf_textrefstack.PopUnsignedWord(); break;
999 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
1000 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
1001 case SCC_NEWGRF_PRINT_DWORD_HEX: *argv = _newgrf_textrefstack.PopUnsignedDWord(); break;
1003 case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
1004 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT: *argv = _newgrf_textrefstack.PopUnsignedWord() + DAYS_TILL_ORIGINAL_BASE_YEAR; break;
1006 case SCC_NEWGRF_DISCARD_WORD: _newgrf_textrefstack.PopUnsignedWord(); break;
1008 case SCC_NEWGRF_ROTATE_TOP_4_WORDS: _newgrf_textrefstack.RotateTop4Words(); break;
1009 case SCC_NEWGRF_PUSH_WORD: _newgrf_textrefstack.PushWord(Utf8Consume(str)); break;
1011 case SCC_NEWGRF_UNPRINT: {
1012 size_t unprint = Utf8Consume(str);
1013 if (unprint < buf->length()) {
1014 buf->truncate (buf->length() - unprint);
1015 } else {
1016 buf->clear();
1018 break;
1021 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG:
1022 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT:
1023 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY:
1024 argv[0] = GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile);
1025 argv[1] = _newgrf_textrefstack.PopUnsignedWord();
1026 break;
1028 case SCC_NEWGRF_PRINT_WORD_STRING_ID:
1029 *argv = MapGRFStringID(_newgrf_textrefstack.grffile->grfid, _newgrf_textrefstack.PopUnsignedWord());
1030 break;
1032 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME: {
1033 CargoID cargo = GetCargoTranslation(_newgrf_textrefstack.PopUnsignedWord(), _newgrf_textrefstack.grffile);
1034 *argv = cargo < NUM_CARGO ? 1 << cargo : 0;
1035 break;
1038 } else {
1039 /* Consume additional parameter characters */
1040 switch (scc) {
1041 default: break;
1043 case SCC_NEWGRF_PUSH_WORD:
1044 case SCC_NEWGRF_UNPRINT:
1045 Utf8Consume(str);
1046 break;
1050 switch (scc) {
1051 default: NOT_REACHED();
1052 case SCC_NEWGRF_PRINT_DWORD_SIGNED:
1053 case SCC_NEWGRF_PRINT_WORD_SIGNED:
1054 case SCC_NEWGRF_PRINT_BYTE_SIGNED:
1055 case SCC_NEWGRF_PRINT_WORD_UNSIGNED:
1056 return SCC_COMMA;
1058 case SCC_NEWGRF_PRINT_BYTE_HEX:
1059 case SCC_NEWGRF_PRINT_WORD_HEX:
1060 case SCC_NEWGRF_PRINT_DWORD_HEX:
1061 case SCC_NEWGRF_PRINT_QWORD_HEX:
1062 return SCC_HEX;
1064 case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
1065 case SCC_NEWGRF_PRINT_QWORD_CURRENCY:
1066 return SCC_CURRENCY_LONG;
1068 case SCC_NEWGRF_PRINT_WORD_STRING_ID:
1069 return SCC_NEWGRF_PRINT_WORD_STRING_ID;
1071 case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
1072 case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
1073 return SCC_DATE_LONG;
1075 case SCC_NEWGRF_PRINT_WORD_DATE_SHORT:
1076 case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
1077 return SCC_DATE_SHORT;
1079 case SCC_NEWGRF_PRINT_WORD_SPEED:
1080 return SCC_VELOCITY;
1082 case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
1083 return SCC_VOLUME_LONG;
1085 case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
1086 return SCC_VOLUME_SHORT;
1088 case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
1089 return SCC_WEIGHT_LONG;
1091 case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
1092 return SCC_WEIGHT_SHORT;
1094 case SCC_NEWGRF_PRINT_WORD_POWER:
1095 return SCC_POWER;
1097 case SCC_NEWGRF_PRINT_WORD_CARGO_LONG:
1098 return SCC_CARGO_LONG;
1100 case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT:
1101 return SCC_CARGO_SHORT;
1103 case SCC_NEWGRF_PRINT_WORD_CARGO_TINY:
1104 return SCC_CARGO_TINY;
1106 case SCC_NEWGRF_PRINT_WORD_CARGO_NAME:
1107 return SCC_CARGO_LIST;
1109 case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
1110 return SCC_STATION_NAME;
1112 case SCC_NEWGRF_DISCARD_WORD:
1113 case SCC_NEWGRF_ROTATE_TOP_4_WORDS:
1114 case SCC_NEWGRF_PUSH_WORD:
1115 case SCC_NEWGRF_UNPRINT:
1116 return 0;