summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortruebrain <truebrain@openttd.org>2011-12-19 21:05:46 +0000
committertruebrain <truebrain@openttd.org>2011-12-19 21:05:46 +0000
commit2ae87e72131a8e88327dbb0b5b286accddafe05d (patch)
treeec755c0d0c31cbcb470d0df2625db8614efca6b2 /src
parent9b6b2cabc187f3cb72a53d396418ab1b9ebd2933 (diff)
downloadopenttd-2ae87e72131a8e88327dbb0b5b286accddafe05d.tar.xz
(svn r23634) -Add: support language files for GameScript (Rubidium)
Diffstat (limited to 'src')
-rw-r--r--src/core/smallvec_type.hpp31
-rw-r--r--src/game/game.hpp5
-rw-r--r--src/game/game_core.cpp6
-rw-r--r--src/game/game_instance.cpp2
-rw-r--r--src/game/game_text.cpp384
-rw-r--r--src/game/game_text.hpp45
-rw-r--r--src/saveload/game_sl.cpp62
-rw-r--r--src/strings.cpp6
8 files changed, 541 insertions, 0 deletions
diff --git a/src/core/smallvec_type.hpp b/src/core/smallvec_type.hpp
index 00fb7c5a0..40793788d 100644
--- a/src/core/smallvec_type.hpp
+++ b/src/core/smallvec_type.hpp
@@ -326,6 +326,37 @@ public:
}
};
+/**
+ * Simple vector template class, with automatic delete.
+ *
+ * @note There are no asserts in the class so you have
+ * to care about that you grab an item which is
+ * inside the list.
+ *
+ * @param T The type of the items stored, must be a pointer
+ * @param S The steps of allocation
+ */
+template <typename T, uint S>
+class AutoDeleteSmallVector : public SmallVector<T, S> {
+public:
+ ~AutoDeleteSmallVector()
+ {
+ this->Clear();
+ }
+
+ /**
+ * Remove all items from the list.
+ */
+ FORCEINLINE void Clear()
+ {
+ for (uint i = 0; i < this->items; i++) {
+ delete this->data[i];
+ }
+
+ this->items = 0;
+ }
+};
+
typedef AutoFreeSmallVector<char*, 4> StringList; ///< Type for a list of strings.
#endif /* SMALLVEC_TYPE_HPP */
diff --git a/src/game/game.hpp b/src/game/game.hpp
index 9e13bc5b6..50d97b50c 100644
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -91,6 +91,11 @@ public:
*/
static class GameInstance *GetInstance() { return Game::instance; }
+ /**
+ * Get the current active mainscript.
+ */
+ static const char *GetMainScript();
+
#if defined(ENABLE_NETWORK)
/** Wrapper function for GameScanner::HasGame */
static bool HasGame(const struct ContentInfo *ci, bool md5sum);
diff --git a/src/game/game_core.cpp b/src/game/game_core.cpp
index ac24853da..2816a6223 100644
--- a/src/game/game_core.cpp
+++ b/src/game/game_core.cpp
@@ -22,6 +22,7 @@
#include "game_scanner.hpp"
#include "game_config.hpp"
#include "game_instance.hpp"
+#include "game_info.hpp"
/* static */ uint Game::frame_counter = 0;
/* static */ GameInfo *Game::info = NULL;
@@ -29,6 +30,11 @@
/* static */ GameScannerInfo *Game::scanner_info = NULL;
/* static */ GameScannerLibrary *Game::scanner_library = NULL;
+/* static */ const char *Game::GetMainScript()
+{
+ return Game::info->GetMainScript();
+}
+
/* static */ void Game::GameLoop()
{
if (_networking && !_network_server) return;
diff --git a/src/game/game_instance.cpp b/src/game/game_instance.cpp
index 81870603b..e4971238a 100644
--- a/src/game/game_instance.cpp
+++ b/src/game/game_instance.cpp
@@ -19,6 +19,7 @@
#include "game_config.hpp"
#include "game_info.hpp"
#include "game_instance.hpp"
+#include "game_text.hpp"
#include "game.hpp"
/* Convert all Game related classes to Squirrel data.
@@ -180,6 +181,7 @@ void GameInstance::RegisterAPI()
SQGSWaypointList_Vehicle_Register(this->engine);
SQGSWindow_Register(this->engine);
+ RegisterGameTranslation(this->engine);
}
int GameInstance::GetSetting(const char *name)
diff --git a/src/game/game_text.cpp b/src/game/game_text.cpp
new file mode 100644
index 000000000..0b41579f3
--- /dev/null
+++ b/src/game/game_text.cpp
@@ -0,0 +1,384 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/** @file game_text.cpp Implementation of handling translated strings. */
+
+#include "../stdafx.h"
+#include "../language.h"
+#include "../strgen/strgen.h"
+#include "../debug.h"
+#include "../fileio_func.h"
+#include "../script/squirrel_class.hpp"
+#include "../strings_func.h"
+#include "game_text.hpp"
+#include "game.hpp"
+
+#include "table/strings.h"
+
+#include <exception>
+#include <stdarg.h>
+
+void CDECL strgen_warning(const char *s, ...)
+{
+ char buf[1024];
+ va_list va;
+ va_start(va, s);
+ vsnprintf(buf, lengthof(buf), s, va);
+ va_end(va);
+ DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
+ _warnings++;
+}
+
+void CDECL strgen_error(const char *s, ...)
+{
+ char buf[1024];
+ va_list va;
+ va_start(va, s);
+ vsnprintf(buf, lengthof(buf), s, va);
+ va_end(va);
+ DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
+ _errors++;
+}
+
+void NORETURN CDECL strgen_fatal(const char *s, ...)
+{
+ char buf[1024];
+ va_list va;
+ va_start(va, s);
+ vsnprintf(buf, lengthof(buf), s, va);
+ va_end(va);
+ DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
+ throw std::exception();
+}
+
+/**
+ * Create a new container for language strings.
+ * @param language The language name.
+ */
+LanguageStrings::LanguageStrings(const char *language)
+{
+ const char *p = strrchr(language, PATHSEPCHAR);
+ if (p == NULL) {
+ p = language;
+ } else {
+ p++;
+ }
+
+ const char *e = strchr(p, '.');
+ this->language = e == NULL ? strdup(p) : strndup(p, e - p);
+}
+
+/** Free everything. */
+LanguageStrings::~LanguageStrings()
+{
+ free(this->language);
+}
+
+/**
+ * Read all the raw language strings from the given file.
+ * @param file The file to read from.
+ * @return The raw strings, or NULL upon error.
+ */
+LanguageStrings *ReadRawLanguageStrings(const char *file)
+{
+ LanguageStrings *ret = NULL;
+ try {
+ size_t to_read;
+ FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
+ if (fh == NULL) {
+ return NULL;
+ }
+
+ ret = new LanguageStrings(file);
+
+ char buffer[2048];
+ while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
+ size_t len = strlen(buffer);
+
+ /* Remove trailing spaces/newlines from the string. */
+ size_t i = len;
+ while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
+ buffer[i] = '\0';
+
+ *ret->lines.Append() = strndup(buffer, to_read);
+
+ if (len > to_read) {
+ to_read = 0;
+ } else {
+ to_read -= len;
+ }
+ }
+
+ return ret;
+ } catch (...) {
+ delete ret;
+ return NULL;
+ }
+}
+
+
+/** A reader that simply reads using fopen. */
+struct StringListReader : StringReader {
+ const char * const *p; ///< The current location of the iteration.
+ const char * const *end; ///< The end of the iteration.
+
+ /**
+ * Create the reader.
+ * @param data The data to fill during reading.
+ * @param file The file we are reading.
+ * @param master Are we reading the master file?
+ * @param translation Are we reading a translation?
+ */
+ StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
+ StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
+ {
+ }
+
+ /* virtual */ char *ReadLine(char *buffer, size_t size)
+ {
+ if (this->p == this->end) return NULL;
+
+ strncpy(buffer, *this->p, size);
+ this->p++;
+
+ return buffer;
+ }
+
+ /* virtual */ void HandlePragma(char *str)
+ {
+ strgen_fatal("unknown pragma '%s'", str);
+ }
+};
+
+/** Class for writing an encoded language. */
+struct TranslationWriter : LanguageWriter {
+ StringList *strings; ///< The encoded strings.
+
+ /**
+ * Writer for the encoded data.
+ * @param strings The string table to add the strings to.
+ */
+ TranslationWriter(StringList *strings) : strings(strings)
+ {
+ }
+
+ void WriteHeader(const LanguagePackHeader *header)
+ {
+ /* We don't use the header. */
+ }
+
+ void Finalise()
+ {
+ /* Nothing to do. */
+ }
+
+ void WriteLength(uint length)
+ {
+ /* We don't write the length. */
+ }
+
+ void Write(const byte *buffer, size_t length)
+ {
+ *this->strings->Append() = strndup((const char*)buffer, length);
+ }
+};
+
+/** Class for writing the string IDs. */
+struct StringNameWriter : HeaderWriter {
+ StringList *strings; ///< The string names.
+
+ /**
+ * Writer for the string names.
+ * @param strings The string table to add the strings to.
+ */
+ StringNameWriter(StringList *strings) : strings(strings)
+ {
+ }
+
+ void WriteStringID(const char *name, int stringid)
+ {
+ if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
+ }
+
+ void Finalise(const StringData &data)
+ {
+ /* Nothing to do. */
+ }
+};
+
+static void GetBasePath(char *buffer, size_t length)
+{
+ strecpy(buffer, Game::GetMainScript(), buffer + length);
+ char *s = strrchr(buffer, PATHSEPCHAR);
+ if (s != NULL) {
+ /* Keep the PATHSEPCHAR there, remove the rest */
+ s++;
+ *s = '\0';
+ }
+
+ /* Tars dislike opening files with '/' on Windows.. so convert it to '\\' */
+#if (PATHSEPCHAR != '/')
+ for (char *n = buffer; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
+#endif
+}
+
+/**
+ * Scanner to find language files in a GameScript directory.
+ */
+class LanguageScanner : protected FileScanner {
+private:
+ GameStrings *gs;
+ char *exclude;
+
+public:
+ /** Initialise */
+ LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
+ ~LanguageScanner() { free(exclude); }
+
+ /**
+ * Scan.
+ */
+ void Scan(const char *directory)
+ {
+ this->FileScanner::Scan(".txt", directory, false);
+ }
+
+ /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
+ {
+ if (strcmp(filename, exclude) == 0) return true;
+
+ *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
+ return true;
+ }
+};
+
+/**
+ * Load all translations that we know of.
+ * @return Container with all (compiled) translations.
+ */
+GameStrings *LoadTranslations()
+{
+ GameStrings *gs = new GameStrings();
+ try {
+ char filename[512];
+ GetBasePath(filename, sizeof(filename));
+ char *e = filename + strlen(filename);
+
+ seprintf(e, filename + sizeof(filename), "lang" PATHSEP "english.txt");
+ if (!FioCheckFileExists(filename, GAME_DIR)) throw std::exception();
+ *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
+
+ /* Scan for other language files */
+ LanguageScanner scanner(gs, filename);
+ strecpy(e, "lang" PATHSEP, filename + sizeof(filename));
+ scanner.Scan(filename);
+
+ gs->Compile();
+ return gs;
+ } catch (...) {
+ delete gs;
+ return NULL;
+ }
+}
+
+/** Compile the language. */
+void GameStrings::Compile()
+{
+ StringData data(1);
+ StringListReader master_reader(data, this->raw_strings[0], true, false);
+ master_reader.ParseFile();
+ if (_errors != 0) throw std::exception();
+
+ this->version = data.Version();
+
+ StringNameWriter id_writer(&this->string_names);
+ id_writer.WriteHeader(data);
+
+ for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
+ data.FreeTranslation();
+ StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
+ translation_reader.ParseFile();
+ if (_errors != 0) throw std::exception();
+
+ LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
+ TranslationWriter writer(&compiled->lines);
+ writer.WriteLang(data);
+ }
+}
+
+/** The currently loaded game strings. */
+GameStrings *_current_data = NULL;
+
+/**
+ * Get the string pointer of a particular game string.
+ * @param id The ID of the game string.
+ * @return The encoded string.
+ */
+const char *GetGameStringPtr(uint id)
+{
+ if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
+ return _current_data->cur_language->lines[id];
+}
+
+/**
+ * Register the current translation to the Squirrel engine.
+ * @param engine The engine to update/
+ */
+void RegisterGameTranslation(Squirrel *engine)
+{
+ delete _current_data;
+ _current_data = LoadTranslations();
+ if (_current_data == NULL) return;
+
+ HSQUIRRELVM vm = engine->GetVM();
+ sq_pushroottable(vm);
+ sq_pushstring(vm, _SC("GSText"), -1);
+ if (SQ_FAILED(sq_get(vm, -2))) return;
+
+ int idx = 0;
+ for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
+ sq_pushstring(vm, OTTD2SQ(*p), -1);
+ sq_pushinteger(vm, idx);
+ sq_rawset(vm, -3);
+ }
+
+ sq_pop(vm, 2);
+
+ ReconsiderGameScriptLanguage();
+}
+
+/**
+ * Reconsider the game script language, so we use the right one.
+ */
+void ReconsiderGameScriptLanguage()
+{
+ if (_current_data == NULL) return;
+
+ char temp[MAX_PATH];
+ strecpy(temp, _current_language->file, temp + sizeof(temp));
+
+ /* Remove the extension */
+ char *l = strrchr(temp, '.');
+ assert(l != NULL);
+ *l = '\0';
+
+ /* Skip the path */
+ char *language = strrchr(temp, PATHSEPCHAR);
+ assert(language != NULL);
+ language++;
+
+ for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
+ if (strcmp((*p)->language, language) == 0) {
+ _current_data->cur_language = *p;
+ return;
+ }
+ }
+
+ _current_data->cur_language = _current_data->compiled_strings[0];
+}
diff --git a/src/game/game_text.hpp b/src/game/game_text.hpp
new file mode 100644
index 000000000..b367e63f9
--- /dev/null
+++ b/src/game/game_text.hpp
@@ -0,0 +1,45 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/** @file game_text.hpp Base functions regarding game texts. */
+
+#ifndef GAME_TEXT_HPP
+#define GAME_TEXT_HPP
+
+#include "../core/smallvec_type.hpp"
+
+/** The tab we place our strings in. */
+static const uint GAME_TEXT_TAB = 18;
+
+const char *GetGameStringPtr(uint id);
+void RegisterGameTranslation(class Squirrel *engine);
+void ReconsiderGameScriptLanguage();
+
+/** Container for the raw (unencoded) language strings of a language. */
+struct LanguageStrings {
+ const char *language; ///< Name of the language (base filename).
+ StringList lines; ///< The lines of the file to pass into the parser/encoder.
+
+ LanguageStrings(const char *language);
+ ~LanguageStrings();
+};
+
+/** Container for all the game strings. */
+struct GameStrings {
+ uint version; ///< The version of the language strings.
+ LanguageStrings *cur_language; ///< The current (compiled) language.
+
+ AutoDeleteSmallVector<LanguageStrings *, 4> raw_strings; ///< The raw strings per language, first must be English/the master language!.
+ AutoDeleteSmallVector<LanguageStrings *, 4> compiled_strings; ///< The compiled strings per language, first must be English/the master language!.
+ StringList string_names; ///< The names of the compiled strings.
+
+ void Compile();
+};
+
+#endif /* GAME_TEXT_HPP */
diff --git a/src/saveload/game_sl.cpp b/src/saveload/game_sl.cpp
index c479239c1..6b93a46ea 100644
--- a/src/saveload/game_sl.cpp
+++ b/src/saveload/game_sl.cpp
@@ -19,6 +19,7 @@
#include "../game/game_config.hpp"
#include "../network/network.h"
#include "../game/game_instance.hpp"
+#include "../game/game_text.hpp"
static char _game_saveload_name[64];
static int _game_saveload_version;
@@ -111,6 +112,67 @@ static void Save_GSDT()
SlAutolength((AutolengthProc *)SaveReal_GSDT, NULL);
}
+extern GameStrings *_current_data;
+
+static const char *_game_saveload_string;
+static uint _game_saveload_strings;
+
+static const SaveLoad _game_language_header[] = {
+ SLEG_STR(_game_saveload_string, SLE_STR),
+ SLEG_VAR(_game_saveload_strings, SLE_UINT32),
+ SLE_END()
+};
+
+static const SaveLoad _game_language_string[] = {
+ SLEG_STR(_game_saveload_string, SLE_STR | SLF_ALLOW_CONTROL),
+ SLE_END()
+};
+
+static void SaveReal_GSTR(LanguageStrings *ls)
+{
+ _game_saveload_string = ls->language;
+ _game_saveload_strings = ls->lines.Length();
+
+ SlObject(NULL, _game_language_header);
+ for (uint i = 0; i < _game_saveload_strings; i++) {
+ _game_saveload_string = ls->lines[i];
+ SlObject(NULL, _game_language_string);
+ }
+}
+
+static void Load_GSTR()
+{
+ delete _current_data;
+ _current_data = new GameStrings();
+
+ while (SlIterateArray() != -1) {
+ _game_saveload_string = NULL;
+ SlObject(NULL, _game_language_header);
+
+ LanguageStrings *ls = new LanguageStrings(_game_saveload_string);
+ for (uint i = 0; i < _game_saveload_strings; i++) {
+ SlObject(NULL, _game_language_string);
+ *ls->lines.Append() = strdup(_game_saveload_string);
+ }
+
+ *_current_data->raw_strings.Append() = ls;
+ }
+
+ _current_data->Compile();
+ ReconsiderGameScriptLanguage();
+}
+
+static void Save_GSTR()
+{
+ if (_current_data == NULL) return;
+
+ for (uint i = 0; i < _current_data->raw_strings.Length(); i++) {
+ SlSetArrayIndex(i);
+ SlAutolength((AutolengthProc *)SaveReal_GSTR, _current_data->raw_strings[i]);
+ }
+}
+
extern const ChunkHandler _game_chunk_handlers[] = {
+ { 'GSTR', Save_GSTR, Load_GSTR, NULL, NULL, CH_ARRAY },
{ 'GSDT', Save_GSDT, Load_GSDT, NULL, NULL, CH_ARRAY | CH_LAST},
};
diff --git a/src/strings.cpp b/src/strings.cpp
index 907c1e4d4..5e7809c25 100644
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -38,6 +38,7 @@
#include "smallmap_gui.h"
#include "window_func.h"
#include "debug.h"
+#include "game/game_text.hpp"
#include <stack>
#include "table/strings.h"
@@ -140,6 +141,7 @@ static bool _keep_gender_data = false; ///< Should we retain the gender data in
const char *GetStringPtr(StringID string)
{
switch (GB(string, TAB_COUNT_OFFSET, TAB_COUNT_BITS)) {
+ case GAME_TEXT_TAB: return GetGameStringPtr(GB(string, TAB_SIZE_OFFSET, TAB_SIZE_BITS));
/* GetGRFStringPtr doesn't handle 0xD4xx ids, we need to convert those to 0xD0xx. */
case 26: return GetStringPtr(GetGRFStringID(0, 0xD000 + GB(string, TAB_SIZE_OFFSET, 10)));
case 28: return GetGRFStringPtr(GB(string, TAB_SIZE_OFFSET, TAB_SIZE_BITS));
@@ -182,6 +184,9 @@ char *GetStringWithArgs(char *buffr, StringID string, StringParameters *args, co
/* Old table for custom names. This is no longer used */
error("Incorrect conversion of custom name string.");
+ case GAME_TEXT_TAB:
+ return FormatString(buffr, GetGameStringPtr(index), args, last, case_index);
+
case 26:
/* Include string within newgrf text (format code 81) */
if (HasBit(index, 10)) {
@@ -1611,6 +1616,7 @@ bool ReadLanguagePack(const LanguageMetadata *lang)
#endif /* WITH_ICU */
/* Some lists need to be sorted again after a language change. */
+ ReconsiderGameScriptLanguage();
InitializeSortedCargoSpecs();
SortIndustryTypes();
BuildIndustriesLegend();