|
1 /* $Id$ */ |
|
2 |
|
3 /** @file |
|
4 * Implementation of Action 04 "universal holder" structure and functions. |
|
5 * This file implements a linked-lists of strings, |
|
6 * holding everything that the newgrf action 04 will send over to OpenTTD. |
|
7 * One of the biggest problems is that Dynamic lang Array uses ISO codes |
|
8 * as way to identifying current user lang, while newgrf uses bit shift codes |
|
9 * not related to ISO. So equivalence functionnality had to be set. |
|
10 */ |
|
11 |
|
12 #include "stdafx.h" |
|
13 #include "debug.h" |
|
14 #include "openttd.h" |
|
15 #include "string.h" |
|
16 #include "strings.h" |
|
17 #include "variables.h" |
|
18 #include "macros.h" |
|
19 #include "table/strings.h" |
|
20 #include "newgrf.h" |
|
21 #include "newgrf_text.h" |
|
22 #include "table/control_codes.h" |
|
23 |
|
24 #define GRFTAB 28 |
|
25 #define TABSIZE 11 |
|
26 |
|
27 /** |
|
28 * Explains the newgrf shift bit positionning. |
|
29 * the grf base will not be used in order to find the string, but rather for |
|
30 * jumping from standard langID scheme to the new one. |
|
31 */ |
|
32 typedef enum grf_base_languages { |
|
33 GRFLB_AMERICAN = 0x01, |
|
34 GRFLB_ENGLISH = 0x02, |
|
35 GRFLB_GERMAN = 0x04, |
|
36 GRFLB_FRENCH = 0x08, |
|
37 GRFLB_SPANISH = 0x10, |
|
38 GRFLB_GENERIC = 0x80, |
|
39 } grf_base_language; |
|
40 |
|
41 typedef enum grf_extended_languages { |
|
42 GRFLX_AMERICAN = 0x00, |
|
43 GRFLX_ENGLISH = 0x01, |
|
44 GRFLX_GERMAN = 0x02, |
|
45 GRFLX_FRENCH = 0x03, |
|
46 GRFLX_SPANISH = 0x04, |
|
47 GRFLX_RUSSIAN = 0x07, |
|
48 GRFLX_CZECH = 0x15, |
|
49 GRFLX_SLOVAK = 0x16, |
|
50 GRFLX_AFRIKAANS = 0x1B, |
|
51 GRFLX_GREEK = 0x1E, |
|
52 GRFLX_DUTCH = 0x1F, |
|
53 GRFLX_CATALAN = 0x22, |
|
54 GRFLX_HUNGARIAN = 0x24, |
|
55 GRFLX_ITALIAN = 0x27, |
|
56 GRFLX_ROMANIAN = 0x28, |
|
57 GRFLX_ICELANDIC = 0x29, |
|
58 GRFLX_LATVIAN = 0x2A, |
|
59 GRFLX_LITHUANIAN = 0x2B, |
|
60 GRFLX_SLOVENIAN = 0x2C, |
|
61 GRFLX_DANISH = 0x2D, |
|
62 GRFLX_SWEDISH = 0x2E, |
|
63 GRFLX_NORWEGIAN = 0x2F, |
|
64 GRFLX_POLISH = 0x30, |
|
65 GRFLX_GALICIAN = 0x31, |
|
66 GRFLX_FRISIAN = 0x32, |
|
67 GRFLX_UKRAINIAN = 0x33, |
|
68 GRFLX_ESTONIAN = 0x34, |
|
69 GRFLX_FINNISH = 0x35, |
|
70 GRFLX_PORTUGUESE = 0x36, |
|
71 GRFLX_BRAZILIAN = 0x37, |
|
72 GRFLX_CROATIAN = 0x38, |
|
73 GRFLX_TURKISH = 0x3E, |
|
74 GRFLX_UNSPECIFIED = 0x7F, |
|
75 } grf_language; |
|
76 |
|
77 |
|
78 typedef struct iso_grf { |
|
79 char code[6]; |
|
80 byte grfLangID; |
|
81 } iso_grf; |
|
82 |
|
83 /** |
|
84 * ISO code VS NewGrf langID conversion array. |
|
85 * This array is used in two ways: |
|
86 * 1-its ISO part is matching OpenTTD dynamic language id |
|
87 * with newgrf bit positionning language id |
|
88 * 2-its shift part is used to know what is the shift to |
|
89 * watch for when inserting new strings, hence analysing newgrf langid |
|
90 */ |
|
91 const iso_grf iso_codes[] = { |
|
92 {"en_US", GRFLX_AMERICAN}, |
|
93 {"en_GB", GRFLX_ENGLISH}, |
|
94 {"de_DE", GRFLX_GERMAN}, |
|
95 {"fr_FR", GRFLX_FRENCH}, |
|
96 {"es_ES", GRFLX_SPANISH}, |
|
97 {"af_ZA", GRFLX_AFRIKAANS}, |
|
98 {"hr_HR", GRFLX_CROATIAN}, |
|
99 {"cs_CS", GRFLX_CZECH}, |
|
100 {"ca_ES", GRFLX_CATALAN}, |
|
101 {"da_DA", GRFLX_DANISH}, |
|
102 {"nl_NL", GRFLX_DUTCH}, |
|
103 {"et_ET", GRFLX_ESTONIAN}, |
|
104 {"fi_FI", GRFLX_FINNISH}, |
|
105 {"fy_NL", GRFLX_FRISIAN}, |
|
106 {"gl_ES", GRFLX_GALICIAN}, |
|
107 {"el_GR", GRFLX_GREEK}, |
|
108 {"hu_HU", GRFLX_HUNGARIAN}, |
|
109 {"is_IS", GRFLX_ICELANDIC}, |
|
110 {"it_IT", GRFLX_ITALIAN}, |
|
111 {"lv_LV", GRFLX_LATVIAN}, |
|
112 {"lt_LT", GRFLX_LITHUANIAN}, |
|
113 {"nb_NO", GRFLX_NORWEGIAN}, |
|
114 {"pl_PL", GRFLX_POLISH}, |
|
115 {"pt_PT", GRFLX_PORTUGUESE}, |
|
116 {"pt_BR", GRFLX_BRAZILIAN}, |
|
117 {"ro_RO", GRFLX_ROMANIAN}, |
|
118 {"ru_RU", GRFLX_RUSSIAN}, |
|
119 {"sk_SK", GRFLX_SLOVAK}, |
|
120 {"sl_SL", GRFLX_SLOVENIAN}, |
|
121 {"sv_SE", GRFLX_SWEDISH}, |
|
122 {"tr_TR", GRFLX_TURKISH}, |
|
123 {"uk_UA", GRFLX_UKRAINIAN}, |
|
124 {"gen", GRFLB_GENERIC} //this is not iso code, but there has to be something... |
|
125 }; |
|
126 |
|
127 |
|
128 /** |
|
129 * Element of the linked list. |
|
130 * Each of those elements represent the string, |
|
131 * but according to a different lang. |
|
132 */ |
|
133 typedef struct GRFText { |
|
134 struct GRFText *next; |
|
135 byte langid; |
|
136 char text[VARARRAY_SIZE]; |
|
137 } GRFText; |
|
138 |
|
139 |
|
140 /** |
|
141 * Holder of the above structure. |
|
142 * Putting both grfid and stringid together allows us to avoid duplicates, |
|
143 * since it is NOT SUPPOSED to happen. |
|
144 */ |
|
145 typedef struct GRFTextEntry { |
|
146 uint32 grfid; |
|
147 uint16 stringid; |
|
148 StringID def_string; |
|
149 GRFText *textholder; |
|
150 } GRFTextEntry; |
|
151 |
|
152 |
|
153 static uint _num_grf_texts = 0; |
|
154 static GRFTextEntry _grf_text[(1 << TABSIZE) * 3]; |
|
155 static byte _currentLangID = GRFLX_ENGLISH; //by default, english is used. |
|
156 |
|
157 |
|
158 char *TranslateTTDPatchCodes(const char *str) |
|
159 { |
|
160 char *tmp = malloc(strlen(str) * 10 + 1); /* Allocate space to allow for expansion */ |
|
161 char *d = tmp; |
|
162 bool unicode = false; |
|
163 WChar c; |
|
164 size_t len = Utf8Decode(&c, str); |
|
165 |
|
166 if (c == 0x00DE) { |
|
167 /* The thorn ('รพ') indicates a unicode string to TTDPatch */ |
|
168 unicode = true; |
|
169 str += len; |
|
170 } |
|
171 |
|
172 for (;;) { |
|
173 const char *tmp = str; /* Used for UTF-8 decoding */ |
|
174 |
|
175 c = (byte)*str++; |
|
176 if (c == 0) break; |
|
177 |
|
178 switch (c) { |
|
179 case 0x01: |
|
180 d += Utf8Encode(d, SCC_SETX); |
|
181 *d++ = *str++; |
|
182 break; |
|
183 case 0x0A: break; |
|
184 case 0x0D: *d++ = 0x0A; break; |
|
185 case 0x0E: d += Utf8Encode(d, SCC_TINYFONT); break; |
|
186 case 0x0F: d += Utf8Encode(d, SCC_BIGFONT); break; |
|
187 case 0x1F: |
|
188 d += Utf8Encode(d, SCC_SETXY); |
|
189 *d++ = *str++; |
|
190 *d++ = *str++; |
|
191 break; |
|
192 case 0x7B: |
|
193 case 0x7C: |
|
194 case 0x7D: |
|
195 case 0x7E: d += Utf8Encode(d, SCC_NUM); break; |
|
196 case 0x7F: d += Utf8Encode(d, SCC_CURRENCY); break; |
|
197 case 0x80: d += Utf8Encode(d, SCC_STRING); break; |
|
198 case 0x81: { |
|
199 StringID string; |
|
200 string = *str++; |
|
201 string |= *str++ << 8; |
|
202 d += Utf8Encode(d, SCC_STRING_ID); |
|
203 d += Utf8Encode(d, string); |
|
204 break; |
|
205 } |
|
206 case 0x82: d += Utf8Encode(d, SCC_DATE_TINY); break; |
|
207 case 0x83: d += Utf8Encode(d, SCC_DATE_SHORT); break; |
|
208 case 0x84: d += Utf8Encode(d, SCC_VELOCITY); break; |
|
209 case 0x85: d += Utf8Encode(d, SCC_SKIP); break; |
|
210 case 0x86: /* "Rotate down top 4 words on stack" */ break; |
|
211 case 0x87: d += Utf8Encode(d, SCC_VOLUME); break; |
|
212 case 0x88: d += Utf8Encode(d, SCC_BLUE); break; |
|
213 case 0x89: d += Utf8Encode(d, SCC_SILVER); break; |
|
214 case 0x8A: d += Utf8Encode(d, SCC_GOLD); break; |
|
215 case 0x8B: d += Utf8Encode(d, SCC_RED); break; |
|
216 case 0x8C: d += Utf8Encode(d, SCC_PURPLE); break; |
|
217 case 0x8D: d += Utf8Encode(d, SCC_LTBROWN); break; |
|
218 case 0x8E: d += Utf8Encode(d, SCC_ORANGE); break; |
|
219 case 0x8F: d += Utf8Encode(d, SCC_GREEN); break; |
|
220 case 0x90: d += Utf8Encode(d, SCC_YELLOW); break; |
|
221 case 0x91: d += Utf8Encode(d, SCC_DKGREEN); break; |
|
222 case 0x92: d += Utf8Encode(d, SCC_CREAM); break; |
|
223 case 0x93: d += Utf8Encode(d, SCC_BROWN); break; |
|
224 case 0x94: d += Utf8Encode(d, SCC_WHITE); break; |
|
225 case 0x95: d += Utf8Encode(d, SCC_LTBLUE); break; |
|
226 case 0x96: d += Utf8Encode(d, SCC_GRAY); break; |
|
227 case 0x97: d += Utf8Encode(d, SCC_DKBLUE); break; |
|
228 case 0x98: d += Utf8Encode(d, SCC_BLACK); break; |
|
229 case 0x9E: d += Utf8Encode(d, 0x20AC); break; // Euro |
|
230 case 0x9F: d += Utf8Encode(d, 0x0178); break; // Y with diaeresis |
|
231 case 0xA0: d += Utf8Encode(d, SCC_UPARROW); break; |
|
232 case 0xAA: d += Utf8Encode(d, SCC_DOWNARROW); break; |
|
233 case 0xAC: d += Utf8Encode(d, SCC_CHECKMARK); break; |
|
234 case 0xAD: d += Utf8Encode(d, SCC_CROSS); break; |
|
235 case 0xAF: d += Utf8Encode(d, SCC_RIGHTARROW); break; |
|
236 case 0xB4: d += Utf8Encode(d, SCC_TRAIN); break; |
|
237 case 0xB5: d += Utf8Encode(d, SCC_LORRY); break; |
|
238 case 0xB6: d += Utf8Encode(d, SCC_BUS); break; |
|
239 case 0xB7: d += Utf8Encode(d, SCC_PLANE); break; |
|
240 case 0xB8: d += Utf8Encode(d, SCC_SHIP); break; |
|
241 default: |
|
242 if (unicode) { |
|
243 d += Utf8Encode(d, Utf8Consume(&tmp)); |
|
244 str = tmp; |
|
245 break; |
|
246 } |
|
247 |
|
248 /* Validate any unhandled character */ |
|
249 if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; |
|
250 d += Utf8Encode(d, c); |
|
251 break; |
|
252 } |
|
253 } |
|
254 |
|
255 *d = '\0'; |
|
256 return realloc(tmp, strlen(tmp) + 1); |
|
257 } |
|
258 |
|
259 |
|
260 /** |
|
261 * Add the new read string into our structure. |
|
262 */ |
|
263 StringID AddGRFString(uint32 grfid, uint16 stringid, byte langid_to_add, bool new_scheme, const char *text_to_add, StringID def_string) |
|
264 { |
|
265 char *translatedtext; |
|
266 GRFText *newtext; |
|
267 uint id; |
|
268 |
|
269 /* When working with the old language scheme (grf_version is less than 7) and |
|
270 * English or American is among the set bits, simply add it as English in |
|
271 * the new scheme, i.e. as langid = 1. |
|
272 * If English is set, it is pretty safe to assume the translations are not |
|
273 * actually translated. |
|
274 */ |
|
275 if (!new_scheme) { |
|
276 if (HASBITS(langid_to_add, GRFLB_AMERICAN | GRFLB_ENGLISH)) { |
|
277 langid_to_add = GRFLX_ENGLISH; |
|
278 } else { |
|
279 StringID ret = STR_EMPTY; |
|
280 if (langid_to_add & GRFLB_GERMAN) ret = AddGRFString(grfid, stringid, GRFLX_GERMAN, true, text_to_add, def_string); |
|
281 if (langid_to_add & GRFLB_FRENCH) ret = AddGRFString(grfid, stringid, GRFLX_FRENCH, true, text_to_add, def_string); |
|
282 if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, text_to_add, def_string); |
|
283 return ret; |
|
284 } |
|
285 } |
|
286 |
|
287 for (id = 0; id < _num_grf_texts; id++) { |
|
288 if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) { |
|
289 break; |
|
290 } |
|
291 } |
|
292 |
|
293 /* Too many strings allocated, return empty */ |
|
294 if (id == lengthof(_grf_text)) return STR_EMPTY; |
|
295 |
|
296 translatedtext = TranslateTTDPatchCodes(text_to_add); |
|
297 |
|
298 newtext = malloc(sizeof(*newtext) + strlen(translatedtext) + 1); |
|
299 newtext->next = NULL; |
|
300 newtext->langid = langid_to_add; |
|
301 strcpy(newtext->text, translatedtext); |
|
302 |
|
303 free(translatedtext); |
|
304 |
|
305 /* If we didn't find our stringid and grfid in the list, allocate a new id */ |
|
306 if (id == _num_grf_texts) _num_grf_texts++; |
|
307 |
|
308 if (_grf_text[id].textholder == NULL) { |
|
309 _grf_text[id].grfid = grfid; |
|
310 _grf_text[id].stringid = stringid; |
|
311 _grf_text[id].def_string = def_string; |
|
312 _grf_text[id].textholder = newtext; |
|
313 } else { |
|
314 GRFText **ptext, *text; |
|
315 bool replaced = false; |
|
316 |
|
317 /* Loop through all languages and see if we can replace a string */ |
|
318 for (ptext = &_grf_text[id].textholder; (text = *ptext) != NULL; ptext = &text->next) { |
|
319 if (text->langid != langid_to_add) continue; |
|
320 newtext->next = text->next; |
|
321 *ptext = newtext; |
|
322 free(text); |
|
323 replaced = true; |
|
324 break; |
|
325 } |
|
326 |
|
327 /* If a string wasn't replaced, then we must append the new string */ |
|
328 if (!replaced) *ptext = newtext; |
|
329 } |
|
330 |
|
331 grfmsg(3, "Added 0x%X: grfid %08X string 0x%X lang 0x%X string '%s'", id, grfid, stringid, newtext->langid, newtext->text); |
|
332 |
|
333 return (GRFTAB << TABSIZE) + id; |
|
334 } |
|
335 |
|
336 /* Used to remember the grfid that the last retrieved string came from */ |
|
337 static uint32 _last_grfid = 0; |
|
338 |
|
339 /** |
|
340 * Returns the index for this stringid associated with its grfID |
|
341 */ |
|
342 StringID GetGRFStringID(uint32 grfid, uint16 stringid) |
|
343 { |
|
344 uint id; |
|
345 |
|
346 /* grfid is zero when we're being called via an include */ |
|
347 if (grfid == 0) grfid = _last_grfid; |
|
348 |
|
349 for (id = 0; id < _num_grf_texts; id++) { |
|
350 if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) { |
|
351 return (GRFTAB << TABSIZE) + id; |
|
352 } |
|
353 } |
|
354 |
|
355 return STR_UNDEFINED; |
|
356 } |
|
357 |
|
358 |
|
359 char *GetGRFString(char *buff, uint16 stringid, const char* last) |
|
360 { |
|
361 const GRFText *default_text = NULL; |
|
362 const GRFText *search_text; |
|
363 |
|
364 assert(_grf_text[stringid].grfid != 0); |
|
365 |
|
366 /* Remember this grfid in case the string has included text */ |
|
367 _last_grfid = _grf_text[stringid].grfid; |
|
368 |
|
369 /*Search the list of lang-strings of this stringid for current lang */ |
|
370 for (search_text = _grf_text[stringid].textholder; search_text != NULL; search_text = search_text->next) { |
|
371 if (search_text->langid == _currentLangID) { |
|
372 return strecpy(buff, search_text->text, last); |
|
373 } |
|
374 |
|
375 /* If the current string is English or American, set it as the |
|
376 * fallback language if the specific language isn't available. */ |
|
377 if (search_text->langid == GRFLX_UNSPECIFIED || (default_text == NULL && (search_text->langid == GRFLX_ENGLISH || search_text->langid == GRFLX_AMERICAN))) { |
|
378 default_text = search_text; |
|
379 } |
|
380 } |
|
381 |
|
382 /* If there is a fallback string, return that */ |
|
383 if (default_text != NULL) return strecpy(buff, default_text->text, last); |
|
384 |
|
385 /* Use the default string ID if the fallback string isn't available */ |
|
386 return GetString(buff, _grf_text[stringid].def_string, last); |
|
387 } |
|
388 |
|
389 /** |
|
390 * Equivalence Setter function between game and newgrf langID. |
|
391 * This function will adjust _currentLangID as to what is the LangID |
|
392 * of the current language set by the user. |
|
393 * The array iso_codes will be used to find that match. |
|
394 * If not found, it will have to be standard english |
|
395 * This function is called after the user changed language, |
|
396 * from strings.c:ReadLanguagePack |
|
397 * @param iso code of current selection |
|
398 */ |
|
399 void SetCurrentGrfLangID(const char *iso_name) |
|
400 { |
|
401 /* Use English by default, if we can't match up the iso_code. */ |
|
402 byte ret = GRFLX_ENGLISH; |
|
403 byte i; |
|
404 |
|
405 for (i=0; i < lengthof(iso_codes); i++) { |
|
406 if (strncmp(iso_codes[i].code, iso_name, strlen(iso_codes[i].code)) == 0) { |
|
407 /* We found a match, so let's use it. */ |
|
408 ret = i; |
|
409 break; |
|
410 } |
|
411 } |
|
412 _currentLangID = ret; |
|
413 } |
|
414 |
|
415 /** |
|
416 * House cleaning. |
|
417 * Remove all strings and reset the text counter. |
|
418 */ |
|
419 void CleanUpStrings(void) |
|
420 { |
|
421 uint id; |
|
422 |
|
423 for (id = 0; id < _num_grf_texts; id++) { |
|
424 GRFText *grftext = _grf_text[id].textholder; |
|
425 while (grftext != NULL) { |
|
426 GRFText *grftext2 = grftext->next; |
|
427 free(grftext); |
|
428 grftext = grftext2; |
|
429 } |
|
430 _grf_text[id].grfid = 0; |
|
431 _grf_text[id].stringid = 0; |
|
432 _grf_text[id].textholder = NULL; |
|
433 } |
|
434 |
|
435 _num_grf_texts = 0; |
|
436 } |