src/newgrf_text.c
changeset 5475 2e6990a8c7c4
parent 5380 8ea58542b6e0
equal deleted inserted replaced
5474:ac55aefc54f3 5475:2e6990a8c7c4
       
     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 }