diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -983,6 +983,7 @@ + @@ -1042,6 +1043,7 @@ + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -2178,6 +2178,9 @@ Script API + + Script API + Script API @@ -2355,6 +2358,9 @@ Script API Implementation + + Script API Implementation + Script API Implementation diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -3267,6 +3267,10 @@ > + + @@ -3507,6 +3511,10 @@ > + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -3264,6 +3264,10 @@ > + + @@ -3504,6 +3508,10 @@ > + + diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -761,6 +761,7 @@ script/api/script_stationlist.hpp script/api/script_subsidy.hpp script/api/script_subsidylist.hpp script/api/script_testmode.hpp +script/api/script_text.hpp script/api/script_tile.hpp script/api/script_tilelist.hpp script/api/script_town.hpp @@ -822,6 +823,7 @@ script/api/script_stationlist.cpp script/api/script_subsidy.cpp script/api/script_subsidylist.cpp script/api/script_testmode.cpp +script/api/script_text.cpp script/api/script_tile.cpp script/api/script_tilelist.cpp script/api/script_town.cpp diff --git a/src/game/game_instance.cpp b/src/game/game_instance.cpp --- a/src/game/game_instance.cpp +++ b/src/game/game_instance.cpp @@ -67,6 +67,7 @@ #include "../script/api/game/game_subsidy.hpp.sq" #include "../script/api/game/game_subsidylist.hpp.sq" #include "../script/api/game/game_testmode.hpp.sq" +#include "../script/api/game/game_text.hpp.sq" #include "../script/api/game/game_tile.hpp.sq" #include "../script/api/game/game_tilelist.hpp.sq" #include "../script/api/game/game_town.hpp.sq" @@ -161,6 +162,7 @@ void GameInstance::RegisterAPI() SQGSSubsidy_Register(this->engine); SQGSSubsidyList_Register(this->engine); SQGSTestMode_Register(this->engine); + SQGSText_Register(this->engine); SQGSTile_Register(this->engine); SQGSTileList_Register(this->engine); SQGSTileList_IndustryAccepting_Register(this->engine); diff --git a/src/script/api/game/game_text.hpp.sq b/src/script/api/game/game_text.hpp.sq new file mode 100644 --- /dev/null +++ b/src/script/api/game/game_text.hpp.sq @@ -0,0 +1,31 @@ +/* $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 . + */ + +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "../script_text.hpp" +#include "../template/template_text.hpp.sq" + + +template <> const char *GetClassName() { return "GSText"; } + +void SQGSText_Register(Squirrel *engine) +{ + DefSQClass SQGSText("GSText"); + SQGSText.PreRegister(engine); + SQGSText.AddConstructor(engine, "xi"); + + SQGSText.DefSQConst(engine, ScriptText::SCRIPT_TEXT_MAX_PARAMETERS, "SCRIPT_TEXT_MAX_PARAMETERS"); + + SQGSText.DefSQAdvancedMethod(engine, &ScriptText::_set, "_set"); + SQGSText.DefSQAdvancedMethod(engine, &ScriptText::SetParam, "SetParam"); + SQGSText.DefSQAdvancedMethod(engine, &ScriptText::AddParam, "AddParam"); + + SQGSText.PostRegister(engine); +} diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp new file mode 100644 --- /dev/null +++ b/src/script/api/script_text.cpp @@ -0,0 +1,167 @@ +/* $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 . + */ + +/** @file script_text.cpp Implementation of ScriptText. */ + +#include "../../stdafx.h" +#include "../../string_func.h" +#include "script_text.hpp" +#include "../squirrel.hpp" +#include "../../table/control_codes.h" + +ScriptText::ScriptText(StringID string) : + ZeroedMemoryAllocator(), + string(string) +{ +} + +ScriptText::~ScriptText() +{ + for (int i = 0; i < SCRIPT_TEXT_MAX_PARAMETERS; i++) { + free(this->params[i]); + if (this->paramt[i] != NULL) this->paramt[i]->Release(); + } +} + +SQInteger ScriptText::_SetParam(int parameter, HSQUIRRELVM vm) +{ + if (parameter >= SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR; + + free(this->params[parameter]); + if (this->paramt[parameter] != NULL) this->paramt[parameter]->Release(); + + this->parami[parameter] = 0; + this->params[parameter] = NULL; + this->paramt[parameter] = NULL; + + switch (sq_gettype(vm, -1)) { + case OT_STRING: { + const SQChar *value; + sq_getstring(vm, -1, &value); + + this->params[parameter] = strdup(SQ2OTTD(value)); + break; + } + + case OT_INTEGER: { + SQInteger value; + sq_getinteger(vm, -1, &value); + + this->parami[parameter] = value; + break; + } + + case OT_INSTANCE: { + SQUserPointer real_instance = NULL; + HSQOBJECT instance; + + sq_getstackobj(vm, -1, &instance); + + /* Validate if it is a GSText instance */ + sq_pushroottable(vm); + sq_pushstring(vm, _SC("GSText"), -1); + sq_get(vm, -2); + sq_pushobject(vm, instance); + if (sq_instanceof(vm) != SQTrue) return SQ_ERROR; + sq_pop(vm, 3); + + /* Get the 'real' instance of this class */ + sq_getinstanceup(vm, -1, &real_instance, 0); + if (real_instance == NULL) return SQ_ERROR; + + ScriptText *value = static_cast(real_instance); + value->AddRef(); + this->paramt[parameter] = value; + break; + } + + default: return SQ_ERROR; + } + + if (this->paramc <= parameter) this->paramc = parameter + 1; + return 0; +} + +SQInteger ScriptText::SetParam(HSQUIRRELVM vm) +{ + if (sq_gettype(vm, 2) != OT_INTEGER) return SQ_ERROR; + + SQInteger k; + sq_getinteger(vm, 2, &k); + + if (k > SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR; + if (k < 1) return SQ_ERROR; + k--; + + return this->_SetParam(k, vm); +} + +SQInteger ScriptText::AddParam(HSQUIRRELVM vm) +{ + SQInteger res; + res = this->_SetParam(this->paramc, vm); + if (res != 0) return res; + + /* Push our own instance back on top of the stack */ + sq_push(vm, 1); + return 1; +} + +SQInteger ScriptText::_set(HSQUIRRELVM vm) +{ + int32 k; + + if (sq_gettype(vm, 2) == OT_STRING) { + const SQChar *key; + sq_getstring(vm, 2, &key); + const char *key_string = SQ2OTTD(key); + + if (strncmp(key_string, "param_", 6) != 0 || strlen(key_string) > 8) return SQ_ERROR; + k = atoi(key_string + 6); + } else if (sq_gettype(vm, 2) == OT_INTEGER) { + SQInteger key; + sq_getinteger(vm, 2, &key); + k = (int32)key; + } else { + return SQ_ERROR; + } + + if (k > SCRIPT_TEXT_MAX_PARAMETERS) return SQ_ERROR; + if (k < 1) return SQ_ERROR; + k--; + + return this->_SetParam(k, vm); +} + +const char *ScriptText::GetEncodedText() +{ + static char buf[1024]; + this->_GetEncodedText(buf, lastof(buf)); + return buf; +} + +char *ScriptText::_GetEncodedText(char *p, char *lastofp) +{ + p += Utf8Encode(p, SCC_ENCODED); + p += seprintf(p, lastofp, "%X", this->string); + for (int i = 0; i < this->paramc; i++) { + if (this->params[i] != NULL) { + p += seprintf(p, lastofp, ":\"%s\"", this->params[i]); + continue; + } + if (this->paramt[i] != NULL) { + p += seprintf(p, lastofp, ":"); + p = this->paramt[i]->_GetEncodedText(p, lastofp); + continue; + } + p += seprintf(p, lastofp,":%X", this->parami[i]); + } + + return p; +} diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp new file mode 100644 --- /dev/null +++ b/src/script/api/script_text.hpp @@ -0,0 +1,134 @@ +/* $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 . + */ + +/** @file script_text.hpp Everything to handle text which can be translated. */ + +#ifndef SCRIPT_TEXT_HPP +#define SCRIPT_TEXT_HPP + +#include "script_object.hpp" +#include "../../core/alloc_type.hpp" + +/** + * Internal parent object of all Text-like objects. + * @api -all + */ +class Text : public ScriptObject { +public: + /** + * Convert a ScriptText to a normal string. + * @return A string (in a static buffer), or NULL. + * @api -all + */ + virtual const char *GetEncodedText() = 0; +}; + +/** + * Interal used class to create a raw text in a Text object. + * @api -all + */ +class RawText : public Text { +public: + RawText(const char *text) : + text(strdup(text)) {} + ~RawText() { free(this->text); } + + /* virtual */ const char *GetEncodedText() { return this->text; } +private: + const char *text; +}; + +/** + * Class that handles all text related functions. You can define a language + * file in lang/english.txt, in the same format as OpenTTD does, including + * tags like {BLACK}, {STRING1} etc. The name given to this string is made + * available to you in ScriptText, for example: ScriptText.STR_NEWS, if your + * english.txt contains: STR_NEWS :{BLACK}Welcome {COMPANY}! + * + * In translation files like lang/dutch.txt you can then translate such + * strings, like: STR_NEWS :{BLACK}Hallo {COMPANY}! + * When the user has the dutch language selected, it will automatically use + * the translated string when available. The fallback language is always + * the english language. + * + * If you use parameters in your strings, you will have to define those + * parameters, for example like this: + * local text = ScriptText(ScriptText.STR_NEWS); text.AddParam(1); + * This will set the {COMPANY} to the name of Company 1. + * + * @api game + */ +class ScriptText : public Text , public ZeroedMemoryAllocator { +public: + static const int SCRIPT_TEXT_MAX_PARAMETERS = 20; ///< The maximum amount of parameters you can give to one object. + + /** + * Generate a text from string. You can set parameters to the instance which + * can be required for the string. + * @param string The string of the text. + */ + ScriptText(StringID string); + ~ScriptText(); + +#ifndef DOXYGEN_API + /** + * Used for .param_N and [] set from Squirrel. + */ + SQInteger _set(HSQUIRRELVM vm); + + /** + * Set the parameter. + */ + SQInteger SetParam(HSQUIRRELVM vm); + + /** + * Add an parameter + */ + SQInteger AddParam(HSQUIRRELVM vm); +#else + /** + * Set the parameter to a value. + * @param parameter Which parameter t oset. + * @param value The value of the parameter. Has to be string, integer or an instance of the class ScriptText. + */ + void SetParam(int parameter, Object value); + + /** + * Add a value as parameter (appending it). + * @param value The value of the parameter. Has to be string, integer or an instance of the class ScriptText. + * @return The same object as on which this is called, so you can chain. + */ + ScriptText *AddParam(Object value); +#endif /* DOXYGEN_API */ + + /* virtual */ const char *GetEncodedText(); + +private: + StringID string; + char *params[SCRIPT_TEXT_MAX_PARAMETERS]; + int parami[SCRIPT_TEXT_MAX_PARAMETERS]; + ScriptText *paramt[SCRIPT_TEXT_MAX_PARAMETERS]; + int paramc; + + /** + * Internal function for recursive calling this function over multiple + * instances, while writing in the same buffer. + * @param p The current position in the buffer. + * @param lastofp The last position valid in the buffer. + * @return The new current position in the buffer. + */ + char *_GetEncodedText(char *p, char *lastofp); + + /** + * Set a parameter, where the value is the first item on the stack. + */ + SQInteger _SetParam(int k, HSQUIRRELVM vm); +}; + +#endif /* SCRIPT_TEXT_HPP */ diff --git a/src/script/api/squirrel_export.awk b/src/script/api/squirrel_export.awk --- a/src/script/api/squirrel_export.awk +++ b/src/script/api/squirrel_export.awk @@ -35,6 +35,17 @@ function dump_class_templates(name) print " template <> inline const " name " &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(" name " *)instance; }" if (name == "ScriptEvent") { print " template <> inline int Return<" name " *>(HSQUIRRELVM vm, " name " *res) { if (res == NULL) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, \"" realname "\", res, NULL, DefSQDestructorCallback<" name ">, true); return 1; }" + } else if (name == "ScriptText") { + print "" + print " template <> inline Text *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) {" + print " if (sq_gettype(vm, index) == OT_INSTANCE) {" + print " return GetParam(ForceType(), vm, index, ptr);" + print " }" + print " if (sq_gettype(vm, index) == OT_STRING) {" + print " return new RawText(GetParam(ForceType(), vm, index, ptr));" + print " }" + print " return NULL;" + print " }" } else { print " template <> inline int Return<" name " *>(HSQUIRRELVM vm, " name " *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"" realname "\", res, NULL, DefSQDestructorCallback<" name ">, true); return 1; }" } @@ -324,7 +335,7 @@ BEGIN { print "void SQ" api_cls "_Register(Squirrel *engine)" print "{" print " DefSQClass<" cls ", ST_" toupper(api) "> SQ" api_cls "(\"" api_cls "\");" - if (super_cls == "ScriptObject" || super_cls == "AIAbstractList::Valuator") { + if (super_cls == "Text" || super_cls == "ScriptObject" || super_cls == "AIAbstractList::Valuator") { print " SQ" api_cls ".PreRegister(engine);" } else { print " SQ" api_cls ".PreRegister(engine, \"" api_super_cls "\");" @@ -516,6 +527,8 @@ BEGIN { types = types "a" } else if (match(params[len], "^struct Array")) { types = types "a" + } else if (match(params[len], "^Text")) { + types = types "." } else { types = types "x" } diff --git a/src/script/api/template/template_text.hpp.sq b/src/script/api/template/template_text.hpp.sq new file mode 100644 --- /dev/null +++ b/src/script/api/template/template_text.hpp.sq @@ -0,0 +1,30 @@ +/* $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 . + */ + +/* THIS FILE IS AUTO-GENERATED; PLEASE DO NOT ALTER MANUALLY */ + +#include "../script_text.hpp" + +namespace SQConvert { + /* Allow ScriptText to be used as Squirrel parameter */ + template <> inline ScriptText *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptText *)instance; } + template <> inline ScriptText &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptText *)instance; } + template <> inline const ScriptText *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptText *)instance; } + template <> inline const ScriptText &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptText *)instance; } + + template <> inline Text *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { + if (sq_gettype(vm, index) == OT_INSTANCE) { + return GetParam(ForceType(), vm, index, ptr); + } + if (sq_gettype(vm, index) == OT_STRING) { + return new RawText(GetParam(ForceType(), vm, index, ptr)); + } + return NULL; + } +} // namespace SQConvert diff --git a/src/strings.cpp b/src/strings.cpp --- a/src/strings.cpp +++ b/src/strings.cpp @@ -733,6 +733,103 @@ static char *FormatString(char *buff, co } switch (b) { + case SCC_ENCODED: { + uint64 sub_args_data[20]; + WChar sub_args_type[20]; + bool sub_args_need_free[20]; + StringParameters sub_args(sub_args_data, 20, sub_args_type); + + sub_args.ClearTypeInformation(); + memset(sub_args_need_free, 0, sizeof(sub_args_need_free)); + + uint16 stringid; + const char *s = str; + char *p; + stringid = strtol(str, &p, 16); + if (*p != ':' && *p != '\0') { + while (*p != '\0') p++; + str = p; + buff = strecat(buff, "(invalid SCC_ENCODED)", last); + break; + } + if (stringid >= TAB_SIZE) { + while (*p != '\0') p++; + str = p; + buff = strecat(buff, "(invalid StringID)", last); + break; + } + + int i = 0; + while (*p != '\0') { + uint64 param; + s = ++p; + + /* Find the next value */ + bool instring = false; + bool escape = false; + for (;; p++) { + if (*p == '\\') { + escape = true; + continue; + } + if (*p == '"' && escape) { + escape = false; + continue; + } + escape = false; + + if (*p == '"') { + instring = !instring; + continue; + } + if (instring) { + continue; + } + + if (*p == ':') break; + if (*p == '\0') break; + } + + if (*s != '"') { + /* Check if we want to look up another string */ + WChar l; + size_t len = Utf8Decode(&l, s); + bool lookup = (l == SCC_ENCODED); + if (lookup) s += len; + + param = strtol(s, &p, 16); + + if (lookup) { + if (param >= TAB_SIZE) { + while (*p != '\0') p++; + str = p; + buff = strecat(buff, "(invalid sub-StringID)", last); + break; + } + param = (GAME_TEXT_TAB << TAB_COUNT_OFFSET) + param; + } + + sub_args.SetParam(i++, param); + } else { + char *g = strdup(s); + g[p - s] = '\0'; + + sub_args_need_free[i] = true; + sub_args.SetParam(i++, (uint64)(size_t)g); + } + } + /* We error'd out in the while, to error out in themain too */ + if (*str == '\0') break; + + str = p; + buff = GetStringWithArgs(buff, (GAME_TEXT_TAB << TAB_COUNT_OFFSET) + stringid, &sub_args, last); + + for (int i = 0; i < 20; i++) { + if (sub_args_need_free[i]) free((void *)sub_args.GetParam(i)); + } + break; + } + case SCC_NEWGRF_STRINL: { StringID substr = Utf8Consume(&str); str_stack.push(GetStringPtr(substr)); diff --git a/src/table/control_codes.h b/src/table/control_codes.h --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -71,6 +71,8 @@ enum StringControlCode { SCC_STRING4, SCC_STRING5, + SCC_ENCODED, + SCC_STRING, SCC_COMMA, SCC_DECIMAL,