changeset 16487:b3050ee750d3 draft

(svn r21211) -Feature: [NewGRF] Allow specifying a "choice list" for cases and genders
author rubidium <rubidium@openttd.org>
date Tue, 16 Nov 2010 16:58:19 +0000
parents 5213db8704cd
children 35d5a17161bd
files src/newgrf_text.cpp src/newgrf_text.h
diffstat 2 files changed, 199 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/newgrf_text.cpp
+++ b/src/newgrf_text.cpp
@@ -25,6 +25,8 @@
 #include "string_func.h"
 #include "date_type.h"
 #include "debug.h"
+#include "core/alloc_type.hpp"
+#include "core/smallmap_type.hpp"
 #include "language.h"
 
 #include "table/strings.h"
@@ -233,6 +235,150 @@
 }
 
 /**
+ * Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
+ * @param openttd_id The OpenTTD ID to map.
+ * @param gender     Whether to map genders or cases.
+ * @return The, to the NewGRF supplied ID, mapped index, or -1 if there is no mapping.
+ */
+int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
+{
+	const SmallVector<Mapping, 1> &map = gender ? this->gender_map : this->case_map;
+	for (const Mapping *m = map.Begin(); m != map.End(); m++) {
+		if (m->openttd_id == openttd_id) return m->newgrf_id;
+	}
+	return -1;
+}
+
+/** Helper structure for mapping choice lists. */
+struct UnmappedChoiceList : ZeroedMemoryAllocator {
+	/** Clean everything up. */
+	~UnmappedChoiceList()
+	{
+		for (SmallPair<byte, char *> *p = this->strings.Begin(); p < this->strings.End(); p++) {
+			free(p->second);
+		}
+	}
+
+	/**
+	 * Initialise the mapping.
+	 * @param type   The type of mapping.
+	 * @param old_d  The old begin of the string, i.e. from where to start writing again.
+	 * @param offset The offset to get the gender from.
+	 */
+	UnmappedChoiceList(StringControlCode type, char *old_d, int offset) :
+		type(type), old_d(old_d), offset(offset)
+	{
+	}
+
+	StringControlCode type; ///< The type of choice list.
+	char *old_d;            ///< The old/original location of the "d" local variable.
+	int offset;             ///< The offset for the gender form.
+
+	/** Mapping of NewGRF supplied ID to the different strings in the choice list. */
+	SmallMap<byte, char *> strings;
+
+	/**
+	 * Flush this choice list into the old d variable.
+	 * @param lm  The current language mapping.
+	 * @return The new location of the output string.
+	 */
+	char *Flush(const LanguageMap *lm)
+	{
+		if (!this->strings.Contains(0)) {
+			/* In case of a (broken) NewGRF without a default,
+			 * assume an empty string. */
+			grfmsg(1, "choice list misses default value");
+			this->strings[0] = strdup("");
+		}
+
+		char *d = old_d;
+		if (lm == NULL) {
+			NOT_REACHED();
+			/* In case there is no mapping, just ignore everything but the default. */
+			int len = strlen(this->strings[0]);
+			memcpy(d, this->strings[0], len);
+			return d + len;
+		}
+
+		d += Utf8Encode(d, this->type);
+
+		if (this->type == SCC_SWITCH_CASE) {
+			/*
+			 * Format for case switch:
+			 * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
+			 * Each LEN is printed using 2 bytes in big endian order.
+			 */
+
+			/* "<NUM CASES>" */
+			int count = 0;
+			for (uint8 i = 0; i < _current_language->num_cases; i++) {
+				/* Count the ones we have a mapped string for. */
+				if (this->strings.Contains(lm->GetReverseMapping(i, false))) count++;
+			}
+			*d++ = count;
+
+			for (uint8 i = 0; i < _current_language->num_cases; i++) {
+				/* Resolve the string we're looking for. */
+				int idx = lm->GetReverseMapping(i, false);
+				if (!this->strings.Contains(idx)) continue;
+				char *str = this->strings[idx];
+
+				/* "<CASEn>" */
+				*d++ = i;
+
+				/* "<LENn>" */
+				int len = strlen(str);
+				*d++ = GB(len, 8, 8);
+				*d++ = GB(len, 0, 8);
+
+				/* "<STRINGn>" */
+				memcpy(d, str, len);
+				d += len;
+				*d++ = '\0';
+			}
+
+			/* "<STRINGDEFAULT>" */
+			int len = strlen(this->strings[0]);
+			memcpy(d, this->strings[0], len);
+			d += len;
+			*d++ = '\0';
+		} else {
+			/*
+			 * Format for choice list:
+			 * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
+			 */
+
+			/* "<OFFSET>" */
+			*d++ = this->offset - 0x80;
+
+			/* "<NUM CHOICES>" */
+			int count = _current_language->num_genders;
+			*d++ = count;
+
+			/* "<LENs>" */
+			for (int i = 0; i < count; i++) {
+				int idx = lm->GetReverseMapping(i, true);
+				const char *str = this->strings[this->strings.Contains(idx) ? idx : 0];
+				int len = strlen(str) + 1;
+				if (len > 0xFF) grfmsg(1, "choice list string is too long");
+				*d++ = len;
+			}
+
+			/* "<STRINGs>" */
+			for (int i = 0; i < count; i++) {
+				int idx = lm->GetReverseMapping(i, true);
+				const char *str = this->strings[this->strings.Contains(idx) ? idx : 0];
+				int len = strlen(str);
+				memcpy(d, str, len);
+				d += len;
+				*d++ = '\0';
+			}
+		}
+		return d;
+	}
+};
+
+/**
  * Translate TTDPatch string codes into something OpenTTD can handle (better).
  * @param grfid       The (NewGRF) ID associated with this string
  * @param language_id The (NewGRF) language ID associated with this string.
@@ -248,6 +394,9 @@
 	WChar c;
 	size_t len = Utf8Decode(&c, str);
 
+	/* Helper variable for a possible (string) mapping. */
+	UnmappedChoiceList *mapping = NULL;
+
 	if (c == NFO_UTF8_IDENTIFIER) {
 		unicode = true;
 		str += len;
@@ -362,6 +511,50 @@
 						break;
 					}
 
+					case 0x10:
+					case 0x11:
+						if (mapping == NULL) {
+							if (code == 0x10) str++; // Skip the index
+							grfmsg(1, "choice list %s marker found when not expected", code == 0x10 ? "next" : "default");
+							break;
+						} else {
+							/* Terminate the previous string. */
+							*d = '\0';
+							int index = (code == 0x10 ? *str++ : 0);
+							if (mapping->strings.Contains(index)) {
+								grfmsg(1, "duplicate choice list string, ignoring");
+								d++;
+							} else {
+								d = mapping->strings[index] = MallocT<char>(strlen(str) * 10 + 1);
+							}
+						}
+						break;
+
+					case 0x12:
+						if (mapping == NULL) {
+							grfmsg(1, "choice list end marker found when not expected");
+						} else {
+							/* Terminate the previous string. */
+							*d = '\0';
+
+							/* Now we can start flushing everything and clean everything up. */
+							d = mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id));
+							delete mapping;
+							mapping = NULL;
+						}
+						break;
+
+					case 0x13:
+					case 0x14:
+						if (mapping != NULL) {
+							grfmsg(1, "choice lists can't be stacked, it's going to get messy now...");
+							if (code != 0x14) str++;
+						} else {
+							static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE };
+							mapping = new UnmappedChoiceList(mp[code - 0x13], d, code == 0x14 ? 0 : *str++);
+						}
+						break;
+
 					default:
 						grfmsg(1, "missing handler for extended format code");
 						break;
@@ -392,6 +585,11 @@
 		}
 	}
 
+	if (mapping != NULL) {
+		grfmsg(1, "choice list was incomplete, the whole list is ignored");
+		delete mapping;
+	}
+
 	*d = '\0';
 	if (olen != NULL) *olen = d - tmp + 1;
 	tmp = ReallocT(tmp, d - tmp + 1);
--- a/src/newgrf_text.h
+++ b/src/newgrf_text.h
@@ -61,6 +61,7 @@
 	SmallVector<Mapping, 1> case_map;   ///< Mapping of NewGRF and OpenTTD IDs for cases.
 
 	int GetMapping(int newgrf_id, bool gender) const;
+	int GetReverseMapping(int openttd_id, bool gender) const;
 	static const LanguageMap *GetLanguageMap(uint32 grfid, uint8 language_id);
 };