diff --git a/README.md b/README.md index a7da780bf7..c47457296b 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,18 @@ the game, including new versions, custom maps and much more. Originally written by Magnus Auvinen. +tw06mod-localization branch +--------------------------- +Author: Necropotame, FlowerFell-Sans + +Features: +- Localization System (In InfClass) (Necropotame) +- Chat command for Switch languages (FlowerFell-Sans) +- Json Example (FlowerFell-Sans) + tw06server branch --------------------------- -Author: necropotame, heinrich5991 +Author: Necropotame, Heinrich5991 Features: - Content related to client removed (necropotame) diff --git a/bam.lua b/bam.lua index 3b4b093260..56623e3dd1 100644 --- a/bam.lua +++ b/bam.lua @@ -101,9 +101,36 @@ AddDependency(server_content_source, server_content_header) nethash = CHash("src/game/generated/nethash.cpp", "src/engine/shared/protocol.h", "src/game/generated/protocol.h", "src/game/tuning.h", "src/game/gamecore.cpp", network_header) +icu_depends = {} server_link_other = {} if family == "windows" then + if platform == "win32" then + -- Add ICU because its a HAVE to + if config.compiler.driver == "cl" then + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib32/icudt53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib32/icuin53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib32/icuuc53.dll")) + elseif config.compiler.driver == "gcc" then + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib32/icudt53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib32/icuin53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib32/icuuc53.dll")) + end + else + -- Add ICU because its a HAVE to + if config.compiler.driver == "cl" then + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib64/icudt53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib64/icuin53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/vc/lib64/icuuc53.dll")) + elseif config.compiler.driver == "gcc" then + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib64/icudt53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib64/icuin53.dll")) + table.insert(icu_depends, CopyToDirectory(".", "other/icu/gcc/lib64/icuuc53.dll")) + end + end + table.insert(server_sql_depends, CopyToDirectory(".", "other/mysql/vc2005libs/mysqlcppconn.dll")) + table.insert(server_sql_depends, CopyToDirectory(".", "other/mysql/vc2005libs/libmysql.dll")) + if config.compiler.driver == "cl" then server_link_other = {ResCompile("other/icons/teeworlds_srv_cl.rc")} elseif config.compiler.driver == "gcc" then @@ -147,10 +174,22 @@ function build(settings) if family == "unix" then if platform == "macosx" then + settings.cc.flags_cxx:Add("-stdlib=libc++") + settings.cc.includes:Add("/usr/local/opt/icu4c/include") + settings.link.libs:Add("icui18n") + settings.link.libs:Add("icuuc") + settings.link.libs:Add("c++") + settings.link.libpath:Add("/usr/local/opt/icu4c/lib") settings.link.frameworks:Add("Carbon") settings.link.frameworks:Add("AppKit") else settings.link.libs:Add("pthread") + -- add ICU for linux + if ExecuteSilent("pkg-config icu-uc icu-i18n") == 0 then + end + + settings.cc.flags:Add("`pkg-config --cflags icu-uc icu-i18n`") + settings.link.flags:Add("`pkg-config --libs icu-uc icu-i18n`") end if platform == "solaris" then @@ -163,6 +202,10 @@ function build(settings) settings.link.libs:Add("ws2_32") settings.link.libs:Add("ole32") settings.link.libs:Add("shell32") + settings.link.libs:Add("advapi32") + + -- add ICU also here + settings.cc.includes:Add("other\\icu\\include") end -- compile zlib if needed @@ -178,6 +221,7 @@ function build(settings) end -- build the small libraries + json = Compile(settings, "src/engine/external/json-parser/json.c") md5 = Compile(settings, Collect("src/engine/external/md5/*.c")) -- build game components @@ -189,10 +233,33 @@ function build(settings) if platform == "macosx" then launcher_settings.link.frameworks:Add("Cocoa") end + + elseif family == "windows" then + -- Add ICU because its a HAVE to + if platform == "win32" then + if config.compiler.driver == "cl" then + server_settings.link.libpath:Add("other/icu/vc/lib32") + elseif config.compiler.driver == "gcc" then + server_settings.link.libpath:Add("other/icu/gcc/lib32") + end + server_settings.link.libs:Add("icudt") + server_settings.link.libs:Add("icuin") + server_settings.link.libs:Add("icuuc") + else + if config.compiler.driver == "cl" then + server_settings.link.libpath:Add("other/icu/vc/lib64") + elseif config.compiler.driver == "gcc" then + server_settings.link.libpath:Add("other/icu/gcc/lib64") + end + server_settings.link.libs:Add("icudt") + server_settings.link.libs:Add("icuin") + server_settings.link.libs:Add("icuuc") + end end engine = Compile(engine_settings, Collect("src/engine/shared/*.cpp", "src/base/*.c")) server = Compile(server_settings, Collect("src/engine/server/*.cpp")) + teeuniverses = Compile(server_settings, Collect("src/teeuniverses/*.cpp", "src/teeuniverses/components/*.cpp", "src/teeuniverses/system/*.cpp")) game_shared = Compile(settings, Collect("src/game/*.cpp"), nethash, network_source) game_server = Compile(settings, CollectRecursive("src/game/server/*.cpp"), server_content_source) @@ -204,7 +271,7 @@ function build(settings) -- build server server_exe = Link(server_settings, "teeworlds_srv", engine, server, - game_shared, game_server, zlib, md5, server_link_other) + game_shared, game_server, zlib, server_link_other, teeuniverses, json, md5) serverlaunch = {} if platform == "macosx" then @@ -212,7 +279,7 @@ function build(settings) end -- make targets - s = PseudoTarget("server".."_"..settings.config_name, server_exe, serverlaunch) + s = PseudoTarget("server".."_"..settings.config_name, server_exe, serverlaunch, icu_depends) all = PseudoTarget(settings.config_name, c, s, v, m, t) return all diff --git a/maps/ctf1.map b/maps/ctf1.map new file mode 100644 index 0000000000..acf9835a56 Binary files /dev/null and b/maps/ctf1.map differ diff --git a/maps/ctf2.map b/maps/ctf2.map new file mode 100644 index 0000000000..1e3bd323c3 Binary files /dev/null and b/maps/ctf2.map differ diff --git a/maps/ctf3.map b/maps/ctf3.map new file mode 100644 index 0000000000..b0ec2d65f3 Binary files /dev/null and b/maps/ctf3.map differ diff --git a/maps/ctf4.map b/maps/ctf4.map new file mode 100644 index 0000000000..9e04fad5ae Binary files /dev/null and b/maps/ctf4.map differ diff --git a/maps/ctf5.map b/maps/ctf5.map new file mode 100644 index 0000000000..f2e64a9194 Binary files /dev/null and b/maps/ctf5.map differ diff --git a/maps/ctf6.map b/maps/ctf6.map new file mode 100644 index 0000000000..5bc1111293 Binary files /dev/null and b/maps/ctf6.map differ diff --git a/maps/ctf7.map b/maps/ctf7.map new file mode 100644 index 0000000000..e5211aeafe Binary files /dev/null and b/maps/ctf7.map differ diff --git a/maps/dd1.map b/maps/dd1.map new file mode 100644 index 0000000000..515582e950 Binary files /dev/null and b/maps/dd1.map differ diff --git a/maps/dm2.map b/maps/dm2.map new file mode 100644 index 0000000000..c279f7ee74 Binary files /dev/null and b/maps/dm2.map differ diff --git a/maps/dm6.map b/maps/dm6.map new file mode 100644 index 0000000000..3e9ce88582 Binary files /dev/null and b/maps/dm6.map differ diff --git a/maps/dm7.map b/maps/dm7.map new file mode 100644 index 0000000000..1a2e602c2e Binary files /dev/null and b/maps/dm7.map differ diff --git a/maps/dm8.map b/maps/dm8.map new file mode 100644 index 0000000000..0aa29109aa Binary files /dev/null and b/maps/dm8.map differ diff --git a/maps/dm9.map b/maps/dm9.map new file mode 100644 index 0000000000..918c41e1ba Binary files /dev/null and b/maps/dm9.map differ diff --git a/server_lang/cn.json b/server_lang/cn.json new file mode 100644 index 0000000000..05460edb31 --- /dev/null +++ b/server_lang/cn.json @@ -0,0 +1,12 @@ +{"translation": + [ + { + "key": "Language successfully switched to English", + "value": "语言成功切换为中文" + }, + { + "key": "Available languages: {str:ListOfLanguage}", + "value": "可用语言: {str:ListOfLanguage}" + }, + ] +} diff --git a/server_lang/index.json b/server_lang/index.json new file mode 100644 index 0000000000..e380ed8b83 --- /dev/null +++ b/server_lang/index.json @@ -0,0 +1,14 @@ +{ + "language indices": + [ + { + "file": "en", + "name": "english" + }, + { + "file": "cn", + "name": "Chinese", + "parent": "en" + } + ] +} diff --git a/src/base/math.h b/src/base/math.h index 07b0639654..b04b237264 100644 --- a/src/base/math.h +++ b/src/base/math.h @@ -4,6 +4,7 @@ #define BASE_MATH_H #include +#include template inline T clamp(T val, T min, T max) @@ -33,6 +34,9 @@ inline T mix(const T a, const T b, TB amount) return a + (b-a)*amount; } +float random_float(); +bool random_prob(float f); +int random_int(int Min, int Max); inline float frandom() { return rand()/(float)(RAND_MAX); } // float to fixed @@ -65,6 +69,36 @@ const float pi = 3.1415926535897932384626433f; template inline T min(T a, T b) { return a inline T max(T a, T b) { return a>b?a:b; } -template inline T absolute(T a) { return a inline T mt_absolute(T a) { return a +constexpr inline T minimum(T a, T b) +{ + return a < b ? a : b; +} + +template +constexpr inline T minimum(T a, T b, T c) +{ + return minimum(minimum(a, b), c); +} + +template +constexpr inline T maximum(T a, T b) +{ + return a > b ? a : b; +} + +template +constexpr inline T maximum(T a, T b, T c) +{ + return maximum(maximum(a, b), c); +} + +template +constexpr inline T absolute(T a) +{ + return a < T(0) ? -a : a; +} #endif // BASE_MATH_H diff --git a/src/base/system.c b/src/base/system.c index e1b56cf937..579aa6fc44 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -1871,6 +1871,29 @@ const char *str_utf8_skip_whitespaces(const char *str) return str; } +//TeeUniverses +void str_append_num(char *dst, const char *src, int dst_size, int num) +{ + int s = strlen(dst); + int i = 0; + while(s < dst_size) + { + if(i>=num) + { + dst[s] = 0; + return; + } + + dst[s] = src[i]; + if(!src[i]) /* check for null termination */ + return; + s++; + i++; + } + + dst[dst_size-1] = 0; /* assure null termination */ +} + static int str_utf8_isstart(char c) { if((c&0xC0) == 0x80) /* 10xxxxxx */ diff --git a/src/base/system.h b/src/base/system.h index 348093a1d5..50c55a6542 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -734,6 +734,10 @@ int net_tcp_close(NETSOCKET sock); */ void str_append(char *dst, const char *src, int dst_size); +//TeeUniverses +void str_append_num(char *dst, const char *src, int dst_size, int num); + + /* Function: str_copy Copies a string to another. diff --git a/src/base/tl/array.h b/src/base/tl/array.h index 4f4b2fc31a..2681a9a520 100644 --- a/src/base/tl/array.h +++ b/src/base/tl/array.h @@ -57,6 +57,12 @@ class array : private ALLOCATOR list = 0x0; } + T& increment() + { + incsize(); + set_size(size()+1); + return list[num_elements-1]; + } /* Function: delete_all @@ -242,7 +248,7 @@ class array : private ALLOCATOR */ void set_size(int new_size) { - if(list_size < new_size) + if(list_size < new_size) alloc(new_size); num_elements = new_size; } diff --git a/src/engine/console.h b/src/engine/console.h index c6e3cfe57a..19e4aa1491 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -35,6 +35,7 @@ class IConsole : public IInterface { protected: unsigned m_NumArgs; + int m_ClientID; public: IResult() { m_NumArgs = 0; } virtual ~IResult() {} @@ -43,6 +44,9 @@ class IConsole : public IInterface virtual float GetFloat(unsigned Index) = 0; virtual const char *GetString(unsigned Index) = 0; + void SetClientID(int ClientID) { m_ClientID = ClientID; } + int GetClientID() { return m_ClientID; } + int NumArguments() const { return m_NumArgs; } }; @@ -80,10 +84,10 @@ class IConsole : public IInterface virtual void StoreCommands(bool Store) = 0; virtual bool LineIsValid(const char *pStr) = 0; - virtual void ExecuteLine(const char *Sptr) = 0; - virtual void ExecuteLineFlag(const char *Sptr, int FlasgMask) = 0; + virtual void ExecuteLine(const char *Sptr, int ClientID) = 0; + virtual void ExecuteLineFlag(const char *Sptr, int ClientID, int FlasgMask) = 0; virtual void ExecuteLineClient(const char *pStr, int ClientID, int Level, int FlagMask) = 0; - virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0; + virtual void ExecuteLineStroked(int Stroke, const char *pStr, int ClientID) = 0; virtual void ExecuteFile(const char *pFilename) = 0; virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0; diff --git a/src/engine/external/json-parser/VERSION b/src/engine/external/json-parser/VERSION new file mode 100644 index 0000000000..061499c4fe --- /dev/null +++ b/src/engine/external/json-parser/VERSION @@ -0,0 +1 @@ +unmarked version: 17.12.2012 diff --git a/src/engine/external/json-parser/json.c b/src/engine/external/json-parser/json.c new file mode 100644 index 0000000000..2c15e903bb --- /dev/null +++ b/src/engine/external/json-parser/json.c @@ -0,0 +1,841 @@ + +/* vim: set et ts=3 sw=3 ft=c: + * + * Copyright (C) 2012 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "json.h" + +#ifdef _MSC_VER + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#ifdef __cplusplus + const struct _json_value json_value_none; /* zero-d by ctor */ +#else + const struct _json_value json_value_none = { 0 }; +#endif + +#include +#include +#include +#include +#include + +typedef unsigned short json_uchar; + +static unsigned char hex_value (json_char c) +{ + if (c >= 'A' && c <= 'F') + return (c - 'A') + 10; + + if (c >= 'a' && c <= 'f') + return (c - 'a') + 10; + + if (c >= '0' && c <= '9') + return c - '0'; + + return 0xFF; +} + +typedef struct +{ + json_settings settings; + int first_pass; + + unsigned long used_memory; + + unsigned int uint_max; + unsigned long ulong_max; + +} json_state; + +static void * json_alloc (json_state * state, unsigned long size, int zero) +{ + void * mem; + + if ((state->ulong_max - state->used_memory) < size) + return 0; + + if (state->settings.max_memory + && (state->used_memory += size) > state->settings.max_memory) + { + return 0; + } + + if (! (mem = zero ? calloc (size, 1) : malloc (size))) + return 0; + + return mem; +} + +static int new_value + (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) +{ + json_value * value; + int values_size; + + if (!state->first_pass) + { + value = *top = *alloc; + *alloc = (*alloc)->_reserved.next_alloc; + + if (!*root) + *root = value; + + switch (value->type) + { + case json_array: + + if (! (value->u.array.values = (json_value **) json_alloc + (state, value->u.array.length * sizeof (json_value *), 0)) ) + { + return 0; + } + + value->u.array.length = 0; + break; + + case json_object: + + values_size = sizeof (*value->u.object.values) * value->u.object.length; + + if (! ((*(void **) &value->u.object.values) = json_alloc + (state, values_size + ((unsigned long) value->u.object.values), 0)) ) + { + return 0; + } + + value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; + + value->u.object.length = 0; + break; + + case json_string: + + if (! (value->u.string.ptr = (json_char *) json_alloc + (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) + { + return 0; + } + + value->u.string.length = 0; + break; + + default: + break; + }; + + return 1; + } + + value = (json_value *) json_alloc (state, sizeof (json_value), 1); + + if (!value) + return 0; + + if (!*root) + *root = value; + + value->type = type; + value->parent = *top; + + if (*alloc) + (*alloc)->_reserved.next_alloc = value; + + *alloc = *top = value; + + return 1; +} + +#define e_off \ + ((int) (i - cur_line_begin)) + +#define whitespace \ + case '\n': ++ cur_line; cur_line_begin = i; \ + case ' ': case '\t': case '\r' + +#define string_add(b) \ + do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); + +const static long + flag_next = 1, flag_reproc = 2, flag_need_comma = 4, flag_seek_value = 8, + flag_escaped = 16, flag_string = 32, flag_need_colon = 64, flag_done = 128, + flag_num_negative = 256, flag_num_zero = 512, flag_num_e = 1024, + flag_num_e_got_sign = 2048, flag_num_e_negative = 4096; + +json_value * json_parse_ex (json_settings * settings, const json_char * json, char * error_buf) +{ + json_char error [128]; + unsigned int cur_line; + const json_char * cur_line_begin, * i; + json_value * top, * root, * alloc = 0; + json_state state; + long flags; + long num_digits, num_fraction, num_e; + + error[0] = '\0'; + num_digits = num_fraction = num_e = 0; + + memset (&state, 0, sizeof (json_state)); + memcpy (&state.settings, settings, sizeof (json_settings)); + + memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); + memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); + + state.uint_max -= 8; /* limit of how much can be added before next check */ + state.ulong_max -= 8; + + for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) + { + json_uchar uchar; + unsigned char uc_b1, uc_b2, uc_b3, uc_b4; + json_char * string; + unsigned int string_length; + + top = root = 0; + flags = flag_seek_value; + string_length = 0; + string = 0; + + cur_line = 1; + cur_line_begin = json; + + for (i = json ;; ++ i) + { + json_char b = *i; + + if (flags & flag_done) + { + if (!b) + break; + + switch (b) + { + whitespace: + continue; + + default: + sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); + goto e_failed; + }; + } + + if (flags & flag_string) + { + if (!b) + { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); + goto e_failed; + } + + if (string_length > state.uint_max) + goto e_overflow; + + if (flags & flag_escaped) + { + flags &= ~ flag_escaped; + + switch (b) + { + case 'b': string_add ('\b'); break; + case 'f': string_add ('\f'); break; + case 'n': string_add ('\n'); break; + case 'r': string_add ('\r'); break; + case 't': string_add ('\t'); break; + case 'u': + + if ((uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF + || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF) + { + sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); + goto e_failed; + } + + uc_b1 = uc_b1 * 16 + uc_b2; + uc_b2 = uc_b3 * 16 + uc_b4; + + uchar = ((json_char) uc_b1) * 256 + uc_b2; + + if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F)) + { + string_add ((json_char) uchar); + break; + } + + if (uchar <= 0x7FF) + { + if (state.first_pass) + string_length += 2; + else + { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + } + + if (state.first_pass) + string_length += 3; + else + { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4); + string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + + default: + string_add (b); + }; + + continue; + } + + if (b == '\\') + { + flags |= flag_escaped; + continue; + } + + if (b == '"') + { + if (!state.first_pass) + string [string_length] = 0; + + flags &= ~ flag_string; + string = 0; + + switch (top->type) + { + case json_string: + + top->u.string.length = string_length; + flags |= flag_next; + + break; + + case json_object: + + if (state.first_pass) + (*(json_char **) &top->u.object.values) += string_length + 1; + else + { + top->u.object.values [top->u.object.length].name + = (json_char *) top->_reserved.object_mem; + + (*(json_char **) &top->_reserved.object_mem) += string_length + 1; + } + + flags |= flag_seek_value | flag_need_colon; + continue; + + default: + break; + }; + } + else + { + string_add (b); + continue; + } + } + + if (flags & flag_seek_value) + { + switch (b) + { + whitespace: + continue; + + case ']': + + if (top->type == json_array) + flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; + else if (!state.settings.settings & json_relaxed_commas) + { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off); + goto e_failed; + } + + break; + + default: + + if (flags & flag_need_comma) + { + if (b == ',') + { flags &= ~ flag_need_comma; + continue; + } + else + { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b); + goto e_failed; + } + } + + if (flags & flag_need_colon) + { + if (b == ':') + { flags &= ~ flag_need_colon; + continue; + } + else + { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b); + goto e_failed; + } + } + + flags &= ~ flag_seek_value; + + switch (b) + { + case '{': + + if (!new_value (&state, &top, &root, &alloc, json_object)) + goto e_alloc_failure; + + continue; + + case '[': + + if (!new_value (&state, &top, &root, &alloc, json_array)) + goto e_alloc_failure; + + flags |= flag_seek_value; + continue; + + case '"': + + if (!new_value (&state, &top, &root, &alloc, json_string)) + goto e_alloc_failure; + + flags |= flag_string; + + string = top->u.string.ptr; + string_length = 0; + + continue; + + case 't': + + if (*(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + top->u.boolean = 1; + + flags |= flag_next; + break; + + case 'f': + + if (*(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + case 'n': + + if (*(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_null)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + default: + + if (isdigit (b) || b == '-') + { + if (!new_value (&state, &top, &root, &alloc, json_integer)) + goto e_alloc_failure; + + if (!state.first_pass) + { + while (isdigit (b) || b == '+' || b == '-' + || b == 'e' || b == 'E' || b == '.') + { + b = *++ i; + } + + flags |= flag_next | flag_reproc; + break; + } + + flags &= ~ (flag_num_negative | flag_num_e | + flag_num_e_got_sign | flag_num_e_negative | + flag_num_zero); + + num_digits = 0; + num_fraction = 0; + num_e = 0; + + if (b != '-') + { + flags |= flag_reproc; + break; + } + + flags |= flag_num_negative; + continue; + } + else + { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); + goto e_failed; + } + }; + }; + } + else + { + switch (top->type) + { + case json_object: + + switch (b) + { + whitespace: + continue; + + case '"': + + if (flags & flag_need_comma && (!state.settings.settings & json_relaxed_commas)) + { + sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off); + goto e_failed; + } + + flags |= flag_string; + + string = (json_char *) top->_reserved.object_mem; + string_length = 0; + + break; + + case '}': + + flags = (flags & ~ flag_need_comma) | flag_next; + break; + + case ',': + + if (flags & flag_need_comma) + { + flags &= ~ flag_need_comma; + break; + } + + default: + + sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); + goto e_failed; + }; + + break; + + case json_integer: + case json_double: + + if (isdigit (b)) + { + ++ num_digits; + + if (top->type == json_integer || flags & flag_num_e) + { + if (! (flags & flag_num_e)) + { + if (flags & flag_num_zero) + { sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b); + goto e_failed; + } + + if (num_digits == 1 && b == '0') + flags |= flag_num_zero; + } + else + { + flags |= flag_num_e_got_sign; + num_e = (num_e * 10) + (b - '0'); + continue; + } + + top->u.integer = (top->u.integer * 10) + (b - '0'); + continue; + } + + num_fraction = (num_fraction * 10) + (b - '0'); + continue; + } + + if (b == '+' || b == '-') + { + if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) + { + flags |= flag_num_e_got_sign; + + if (b == '-') + flags |= flag_num_e_negative; + + continue; + } + } + else if (b == '.' && top->type == json_integer) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off); + goto e_failed; + } + + top->type = json_double; + top->u.dbl = top->u.integer; + + num_digits = 0; + continue; + } + + if (! (flags & flag_num_e)) + { + if (top->type == json_double) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off); + goto e_failed; + } + + top->u.dbl += ((double) num_fraction) / (pow (10, num_digits)); + } + + if (b == 'e' || b == 'E') + { + flags |= flag_num_e; + + if (top->type == json_integer) + { + top->type = json_double; + top->u.dbl = top->u.integer; + } + + num_digits = 0; + flags &= ~ flag_num_zero; + + continue; + } + } + else + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off); + goto e_failed; + } + + top->u.dbl *= pow (10, flags & flag_num_e_negative ? - num_e : num_e); + } + + if (flags & flag_num_negative) + { + if (top->type == json_integer) + top->u.integer = - top->u.integer; + else + top->u.dbl = - top->u.dbl; + } + + flags |= flag_next | flag_reproc; + break; + + default: + break; + }; + } + + if (flags & flag_reproc) + { + flags &= ~ flag_reproc; + -- i; + } + + if (flags & flag_next) + { + flags = (flags & ~ flag_next) | flag_need_comma; + + if (!top->parent) + { + /* root value done */ + + flags |= flag_done; + continue; + } + + if (top->parent->type == json_array) + flags |= flag_seek_value; + + if (!state.first_pass) + { + json_value * parent = top->parent; + + switch (parent->type) + { + case json_object: + + parent->u.object.values + [parent->u.object.length].value = top; + + break; + + case json_array: + + parent->u.array.values + [parent->u.array.length] = top; + + break; + + default: + break; + }; + } + + if ( (++ top->parent->u.array.length) > state.uint_max) + goto e_overflow; + + top = top->parent; + + continue; + } + } + + alloc = root; + } + + return root; + +e_unknown_value: + + sprintf (error, "%d:%d: Unknown value", cur_line, e_off); + goto e_failed; + +e_alloc_failure: + + strcpy (error, "Memory allocation failure"); + goto e_failed; + +e_overflow: + + sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off); + goto e_failed; + +e_failed: + + if (error_buf) + { + if (*error) + strcpy (error_buf, error); + else + strcpy (error_buf, "Unknown error"); + } + + if (state.first_pass) + alloc = root; + + while (alloc) + { + top = alloc->_reserved.next_alloc; + free (alloc); + alloc = top; + } + + if (!state.first_pass) + json_value_free (root); + + return 0; +} + +json_value * json_parse (const json_char * json) +{ + json_settings settings; + memset (&settings, 0, sizeof (json_settings)); + + return json_parse_ex (&settings, json, 0); +} + +void json_value_free (json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + free (value->u.array.values); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + free (value->u.object.values); + break; + } + + value = value->u.object.values [-- value->u.object.length].value; + continue; + + case json_string: + + free (value->u.string.ptr); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + free (cur_value); + } +} + diff --git a/src/engine/external/json-parser/json.h b/src/engine/external/json-parser/json.h new file mode 100644 index 0000000000..cbddc6932e --- /dev/null +++ b/src/engine/external/json-parser/json.h @@ -0,0 +1,192 @@ + +/* vim: set et ts=3 sw=3 ft=c: + * + * Copyright (C) 2012 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _JSON_H +#define _JSON_H + +#ifndef json_char + #define json_char char +#endif + +#ifdef __cplusplus + + #include + + extern "C" + { + +#endif + +typedef struct +{ + unsigned long max_memory; + int settings; + +} json_settings; + +#define json_relaxed_commas 1 + +typedef enum +{ + json_none, + json_object, + json_array, + json_integer, + json_double, + json_string, + json_boolean, + json_null + +} json_type; + +extern const struct _json_value json_value_none; + +typedef struct _json_value +{ + struct _json_value * parent; + + json_type type; + + union + { + int boolean; + long integer; + double dbl; + + struct + { + unsigned int length; + json_char * ptr; /* null terminated */ + + } string; + + struct + { + unsigned int length; + + struct + { + json_char * name; + struct _json_value * value; + + } * values; + + } object; + + struct + { + unsigned int length; + struct _json_value ** values; + + } array; + + } u; + + union + { + struct _json_value * next_alloc; + void * object_mem; + + } _reserved; + + + /* Some C++ operator sugar */ + + #ifdef __cplusplus + + public: + + inline _json_value () + { memset (this, 0, sizeof (_json_value)); + } + + inline const struct _json_value &operator [] (int index) const + { + if (type != json_array || index < 0 + || ((unsigned int) index) >= u.array.length) + { + return json_value_none; + } + + return *u.array.values [index]; + } + + inline const struct _json_value &operator [] (const char * index) const + { + if (type != json_object) + return json_value_none; + + for (unsigned int i = 0; i < u.object.length; ++ i) + if (!strcmp (u.object.values [i].name, index)) + return *u.object.values [i].value; + + return json_value_none; + } + + inline operator const char * () const + { + switch (type) + { + case json_string: + return u.string.ptr; + + default: + return ""; + }; + } + + inline operator long () const + { return u.integer; + } + + inline operator bool () const + { return u.boolean != 0; + } + + #endif + +} json_value; + +json_value * json_parse + (const json_char * json); + +json_value * json_parse_ex + (json_settings * settings, const json_char * json, char * error); + +void json_value_free (json_value *); + + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif + + diff --git a/src/engine/server.h b/src/engine/server.h index 511af564db..990b548ca6 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -15,6 +15,14 @@ class IServer : public IInterface int m_CurrentGameTick; int m_TickSpeed; +public: + class CLocalization* m_pLocalization; + enum + { + AUTHED_NO=0, + AUTHED_MOD, + AUTHED_ADMIN, + }; public: /* Structure: CClientInfo @@ -26,6 +34,8 @@ class IServer : public IInterface bool m_CustClt; }; + inline class CLocalization* Localization() { return m_pLocalization; } + int Tick() const { return m_CurrentGameTick; } int TickSpeed() const { return m_TickSpeed; } @@ -155,6 +165,8 @@ class IServer : public IInterface virtual void DemoRecorder_HandleAutoStart() = 0; virtual bool DemoRecorder_IsRecording() = 0; + virtual const char* GetClientLanguage(int ClientID) = 0; + virtual void SetClientLanguage(int ClientID, const char* pLanguage) = 0; virtual int* GetIdMap(int ClientID) = 0; virtual void SetCustClt(int ClientID) = 0; }; @@ -188,8 +200,9 @@ class IGameServer : public IInterface virtual const char *Version() = 0; virtual const char *NetVersion() = 0; + virtual void OnSetAuthed(int ClientID, int Level) = 0; virtual class CLayers *Layers() = 0; }; extern IGameServer *CreateGameServer(); -#endif +#endif \ No newline at end of file diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 1d2c31aebf..a546294154 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -30,6 +30,13 @@ #include "register.h" #include "server.h" +#include +#include +#include +#include +#include +#include + #if defined(CONF_FAMILY_WINDOWS) #define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN @@ -268,6 +275,17 @@ void CServer::CClient::Reset() m_LastInputTick = -1; m_SnapRate = CClient::SNAPRATE_INIT; m_Score = 0; + str_copy(m_aLanguage, "en", sizeof(m_aLanguage)); +} + +const char* CServer::GetClientLanguage(int ClientID) +{ + return m_aClients[ClientID].m_aLanguage; +} + +void CServer::SetClientLanguage(int ClientID, const char* pLanguage) +{ + str_copy(m_aClients[ClientID].m_aLanguage, pLanguage, sizeof(m_aClients[ClientID].m_aLanguage)); } CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta) @@ -998,8 +1016,18 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; m_RconAuthLevel = m_aClients[ClientID].m_Authed; - Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD); - Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER); + switch(m_aClients[ClientID].m_Authed) + { + case AUTHED_ADMIN: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); + break; + case AUTHED_MOD: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_MOD); + break; + default: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); + } + Console()->ExecuteLineFlag(pCmd, ClientID, CFGFLAG_SERVER); Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = IServer::RCON_CID_SERV; m_RconAuthLevel = AUTHED_ADMIN; @@ -1025,6 +1053,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); m_aClients[ClientID].m_Authed = AUTHED_ADMIN; + GameServer()->OnSetAuthed(ClientID, m_aClients[ClientID].m_Authed); int SendRconCmds = Unpacker.GetInt(); if(Unpacker.Error() == 0 && SendRconCmds) m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_ADMIN, CFGFLAG_SERVER); @@ -1812,6 +1841,14 @@ int main(int argc, const char **argv) // ignore_convention IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_SERVER, argc, argv); // ignore_convention IConfig *pConfig = CreateConfig(); + pServer->m_pLocalization = new CLocalization(pStorage); + pServer->m_pLocalization->InitConfig(0, NULL); + if(!pServer->m_pLocalization->Init()) + { + dbg_msg("localization", "could not initialize localization"); + return -1; + } + pServer->InitRegister(&pServer->m_NetServer, pEngineMasterServer, pConsole); { @@ -1857,6 +1894,8 @@ int main(int argc, const char **argv) // ignore_convention pServer->Run(); // free + delete pServer->m_pLocalization; + delete pServer; delete pKernel; delete pEngineMap; diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 011decca36..09d48d8a51 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -111,6 +111,7 @@ class CServer : public IServer int m_LastInputTick; CSnapshotStorage m_Snapshots; + CInput m_LatestInput; CInput m_aInputs[200]; // TODO: handle input better int m_CurrentInput; @@ -126,6 +127,7 @@ class CServer : public IServer void Reset(); + char m_aLanguage[16]; NETADDR m_Addr; bool m_CustClt; }; @@ -243,8 +245,11 @@ class CServer : public IServer virtual void *SnapNewItem(int Type, int ID, int Size); void SnapSetStaticsize(int ItemType, int Size); +public: + virtual const char* GetClientLanguage(int ClientID); + virtual void SetClientLanguage(int ClientID, const char* pLanguage); virtual int* GetIdMap(int ClientID); virtual void SetCustClt(int ClientID); }; -#endif +#endif \ No newline at end of file diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 1f6dee8b1b..0bd7a2ac27 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -268,7 +268,7 @@ bool CConsole::LineIsValid(const char *pStr) return true; } -void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) +void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID) { int OutputLevel = OUTPUT_LEVEL_STANDARD; if(m_FlagMask&CFGFLAG_CHAT) @@ -277,6 +277,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) while(pStr && *pStr) { CResult Result; + Result.SetClientID(ClientID); const char *pEnd = pStr; const char *pNextPart = 0; int InString = 0; @@ -387,17 +388,17 @@ CConsole::CCommand *CConsole::FindCommand(const char *pName, int FlagMask) return 0x0; } -void CConsole::ExecuteLine(const char *pStr) +void CConsole::ExecuteLine(const char *pStr, int ClientID) { - CConsole::ExecuteLineStroked(1, pStr); // press it - CConsole::ExecuteLineStroked(0, pStr); // then release it + CConsole::ExecuteLineStroked(1, pStr, ClientID); // press it + CConsole::ExecuteLineStroked(0, pStr, ClientID); // then release it } -void CConsole::ExecuteLineFlag(const char *pStr, int FlagMask) +void CConsole::ExecuteLineFlag(const char *pStr, int ClientID, int FlagMask) { int Temp = m_FlagMask; m_FlagMask = FlagMask; - ExecuteLine(pStr); + ExecuteLine(pStr, ClientID); m_FlagMask = Temp; } @@ -409,7 +410,7 @@ void CConsole::ExecuteLineClient(const char *pStr, int ClientID, int Level, int m_FlagMask = FlagMask; m_AccessLevel = Level; m_ClientID = ClientID; - ExecuteLine(pStr); + ExecuteLine(pStr, ClientID); m_ClientID = TmpClientID; m_AccessLevel = TmpLevel; m_FlagMask = TmpMask; @@ -448,7 +449,7 @@ void CConsole::ExecuteFile(const char *pFilename) lr.Init(File); while((pLine = lr.Get())) - ExecuteLine(pLine); + ExecuteLine(pLine, -1); io_close(File); } @@ -627,7 +628,7 @@ void CConsole::ConToggle(IConsole::IResult *pResult, void *pUser) CIntVariableData *pData = static_cast(pUserData); int Val = *(pData->m_pVariable)==pResult->GetInteger(1) ? pResult->GetInteger(2) : pResult->GetInteger(1); str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(0), Val); - pConsole->ExecuteLine(aBuf); + pConsole->ExecuteLine(aBuf, -1); aBuf[0] = 0; } else @@ -660,7 +661,7 @@ void CConsole::ConToggleStroke(IConsole::IResult *pResult, void *pUser) { int Val = pResult->GetInteger(0)==0 ? pResult->GetInteger(3) : pResult->GetInteger(2); str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(1), Val); - pConsole->ExecuteLine(aBuf); + pConsole->ExecuteLine(aBuf, -1); aBuf[0] = 0; } else @@ -738,7 +739,7 @@ void CConsole::ParseArguments(int NumArgs, const char **ppArguments) else { // search arguments for overrides - ExecuteLine(ppArguments[i]); + ExecuteLine(ppArguments[i], -1); } } } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index a23c62c9eb..9db9aaea30 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -61,7 +61,7 @@ class CConsole : public IConsole static void ConModCommandStatus(IConsole::IResult *pResult, void *pUser); void ExecuteFileRecurse(const char *pFilename); - void ExecuteLineStroked(int Stroke, const char *pStr); + void ExecuteLineStroked(int Stroke, const char *pStr, int ClientID); struct { @@ -171,8 +171,8 @@ class CConsole : public IConsole virtual void StoreCommands(bool Store); virtual bool LineIsValid(const char *pStr); - virtual void ExecuteLine(const char *pStr); - virtual void ExecuteLineFlag(const char *pStr, int FlagMask); + virtual void ExecuteLine(const char *pStr, int ClientID); + virtual void ExecuteLineFlag(const char *pStr, int ClientID, int FlagMask); virtual void ExecuteLineClient(const char *pStr, int ClientID, int Level, int FlagMask); virtual void ExecuteFile(const char *pFilename); diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp index da2e87bea6..e02ae76ca5 100644 --- a/src/engine/shared/econ.cpp +++ b/src/engine/shared/econ.cpp @@ -147,7 +147,7 @@ void CEcon::Update() str_format(aFormatted, sizeof(aFormatted), "cid=%d cmd='%s'", ClientID, aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted); m_UserClientID = ClientID; - Console()->ExecuteLine(aBuf); + Console()->ExecuteLine(aBuf, ClientID); m_UserClientID = -1; } } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 1db786bb5b..ad27100355 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -11,6 +11,8 @@ #include #include "gamemodes/mod.h" +#include + enum { RESET, @@ -56,6 +58,12 @@ CGameContext::~CGameContext() delete m_pVoteOptionHeap; } +void CGameContext::OnSetAuthed(int ClientID, int Level) +{ + if(m_apPlayers[ClientID]) + m_apPlayers[ClientID]->m_Authed = Level; +} + void CGameContext::Clear() { CHeap *pVoteOptionHeap = m_pVoteOptionHeap; @@ -216,13 +224,33 @@ void CGameContext::CreateSoundGlobal(int Sound, int Target) } -void CGameContext::SendChatTarget(int To, const char *pText) +void CGameContext::SendChatTarget(int To, const char *pText, ...) { + int Start = (To < 0 ? 0 : To); + int End = (To < 0 ? MAX_CLIENTS : To+1); + CNetMsg_Sv_Chat Msg; Msg.m_Team = 0; Msg.m_ClientID = -1; - Msg.m_pMessage = pText; - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, To); + + dynamic_string Buffer; + + va_list VarArgs; + va_start(VarArgs, pText); + + for(int i = Start; i < End; i++) + { + if(m_apPlayers[i]) + { + Buffer.clear(); + Server()->Localization()->Format_VL(Buffer, m_apPlayers[i]->GetLanguage(), pText, VarArgs); + + Msg.m_pMessage = Buffer.buffer(); + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, i); + } + } + + va_end(VarArgs); } @@ -472,7 +500,7 @@ void CGameContext::OnTick() if(m_VoteEnforce == VOTE_ENFORCE_YES) { Server()->SetRconCID(IServer::RCON_CID_VOTE); - Console()->ExecuteLine(m_aVoteCommand); + Console()->ExecuteLine(m_aVoteCommand, -1); Server()->SetRconCID(IServer::RCON_CID_SERV); EndVote(); SendChat(-1, CGameContext::CHAT_ALL, "Vote passed"); @@ -645,14 +673,23 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) pPlayer->m_LastChat = Server()->Tick(); - if(pMsg->m_pMessage[0]=='/') + if(pMsg->m_pMessage[0] == '/' || pMsg->m_pMessage[0] == '\\') { - if(m_ConsoleOutputHandle_ChatPrint >= 0) + switch(m_apPlayers[ClientID]->m_Authed) { - m_ConsoleOutput_Target = ClientID; - Console()->ExecuteLineClient(pMsg->m_pMessage + 1, IConsole::ACCESS_LEVEL_USER, ClientID, CFGFLAG_CHAT); - m_ConsoleOutput_Target = -1; - } + case IServer::AUTHED_ADMIN: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); + break; + case IServer::AUTHED_MOD: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_MOD); + break; + default: + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); + } + + Console()->ExecuteLineFlag(pMsg->m_pMessage + 1, ClientID, CFGFLAG_CHAT); + + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); } else { @@ -1358,7 +1395,7 @@ void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData) { str_format(aBuf, sizeof(aBuf), "admin forced server option '%s' (%s)", pValue, pReason); pSelf->SendChatTarget(-1, aBuf); - pSelf->Console()->ExecuteLine(pOption->m_aCommand); + pSelf->Console()->ExecuteLine(pOption->m_aCommand, -1); break; } @@ -1384,14 +1421,14 @@ void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData) if (!g_Config.m_SvVoteKickBantime) { str_format(aBuf, sizeof(aBuf), "kick %d %s", KickID, pReason); - pSelf->Console()->ExecuteLine(aBuf); + pSelf->Console()->ExecuteLine(aBuf, -1); } else { char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; pSelf->Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr)); str_format(aBuf, sizeof(aBuf), "ban %s %d %s", aAddrStr, g_Config.m_SvVoteKickBantime, pReason); - pSelf->Console()->ExecuteLine(aBuf); + pSelf->Console()->ExecuteLine(aBuf, -1); } } else if(str_comp_nocase(pType, "spectate") == 0) @@ -1406,7 +1443,7 @@ void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData) str_format(aBuf, sizeof(aBuf), "admin moved '%s' to spectator (%s)", pSelf->Server()->ClientName(SpectateID), pReason); pSelf->SendChatTarget(-1, aBuf); str_format(aBuf, sizeof(aBuf), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay); - pSelf->Console()->ExecuteLine(aBuf); + pSelf->Console()->ExecuteLine(aBuf, -1); } } @@ -1481,6 +1518,70 @@ void CGameContext::ConAbout(IConsole::IResult *pResult, void *pUserData) } } +void CGameContext::ConLanguage(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + + int ClientID = pResult->GetClientID(); + + const char *pLanguageCode = (pResult->NumArguments()>0) ? pResult->GetString(0) : 0x0; + char aFinalLanguageCode[8]; + aFinalLanguageCode[0] = 0; + + if(pLanguageCode) + { + if(str_comp_nocase(pLanguageCode, "ua") == 0) + str_copy(aFinalLanguageCode, "uk", sizeof(aFinalLanguageCode)); + else + { + for(int i=0; iServer()->Localization()->m_pLanguages.size(); i++) + { + if(str_comp_nocase(pLanguageCode, pSelf->Server()->Localization()->m_pLanguages[i]->GetFilename()) == 0) + str_copy(aFinalLanguageCode, pLanguageCode, sizeof(aFinalLanguageCode)); + } + } + } + + if(aFinalLanguageCode[0]) + { + pSelf->SetClientLanguage(ClientID, aFinalLanguageCode); + pSelf->SendChatTarget(ClientID, _("Language successfully switched to English")); + } + else + { + const char* pLanguage = pSelf->m_apPlayers[ClientID]->GetLanguage(); + const char* pTxtUnknownLanguage = pSelf->Server()->Localization()->Localize(pLanguage, _("Unknown language")); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "language", pTxtUnknownLanguage); + + dynamic_string BufferList; + int BufferIter = 0; + for(int i=0; iServer()->Localization()->m_pLanguages.size(); i++) + { + if(i>0) + BufferIter = BufferList.append_at(BufferIter, ", "); + BufferIter = BufferList.append_at(BufferIter, pSelf->Server()->Localization()->m_pLanguages[i]->GetFilename()); + } + + dynamic_string Buffer; + pSelf->Server()->Localization()->Format_L(Buffer, pLanguage, _("Available languages: {str:ListOfLanguage}"), "ListOfLanguage", BufferList.buffer(), NULL); + + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "language", Buffer.buffer()); + + pSelf->SendChatTarget(ClientID, Buffer.buffer()); + } + + return; +} + +void CGameContext::SetClientLanguage(int ClientID, const char *pLanguage) +{ + Server()->SetClientLanguage(ClientID, pLanguage); + if(m_apPlayers[ClientID]) + { + m_apPlayers[ClientID]->SetLanguage(pLanguage); + } +} + void CGameContext::ConsoleOutputCallback_Chat(const char *pStr, void *pUser) { CGameContext* pThis = (CGameContext*) pUser; @@ -1518,6 +1619,7 @@ void CGameContext::OnConsoleInit() Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "Force a vote to yes/no"); Console()->Register("about", "", CFGFLAG_CHAT, ConAbout, this, "Show information about the mod"); + Console()->Register("language", "s", CFGFLAG_CHAT, ConLanguage, this, "Show information about the mod"); Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this); } diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index c47ed62373..a4a7aea8ad 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -56,6 +58,7 @@ class CGameContext : public IGameServer static void ConsoleOutputCallback_Chat(const char *pStr, void *pUser); + static void ConLanguage(IConsole::IResult *pResult, void *pUserData); static void ConAbout(IConsole::IResult *pResult, void *pUserData); static void ConTuneParam(IConsole::IResult *pResult, void *pUserData); static void ConTuneReset(IConsole::IResult *pResult, void *pUserData); @@ -155,11 +158,13 @@ class CGameContext : public IGameServer }; // network - void SendChatTarget(int To, const char *pText); + void SendChatTarget(int To, const char *pText, ...); void SendChat(int ClientID, int Team, const char *pText); void SendEmoticon(int ClientID, int Emoticon); void SendWeaponPickup(int ClientID, int Weapon); void SendBroadcast(const char *pText, int ClientID); + void SetClientLanguage(int ClientID, const char *pLanguage); + // @@ -190,6 +195,8 @@ class CGameContext : public IGameServer virtual bool IsClientReady(int ClientID); virtual bool IsClientPlayer(int ClientID); + virtual void OnSetAuthed(int ClientID,int Level); + virtual const char *GameType(); virtual const char *Version(); virtual const char *NetVersion(); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 80479967e1..82c4cac6ec 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -21,6 +21,9 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team) m_SpectatorID = SPEC_FREEVIEW; m_LastActionTick = Server()->Tick(); m_TeamChangeTick = Server()->Tick(); + SetLanguage(Server()->GetClientLanguage(ClientID)); + + m_Authed = IServer::AUTHED_NO; m_PrevTuningParams = *pGameServer->Tuning(); m_NextTuningParams = m_PrevTuningParams; @@ -67,6 +70,7 @@ void CPlayer::Tick() return; Server()->SetClientScore(m_ClientID, m_Score); + Server()->SetClientLanguage(m_ClientID, m_aLanguage); // do latency stuff { @@ -343,3 +347,13 @@ void CPlayer::TryRespawn() m_pCharacter->Spawn(this, SpawnPos); GameServer()->CreatePlayerSpawn(SpawnPos); } + +const char* CPlayer::GetLanguage() +{ + return m_aLanguage; +} + +void CPlayer::SetLanguage(const char* pLanguage) +{ + str_copy(m_aLanguage, pLanguage, sizeof(m_aLanguage)); +} \ No newline at end of file diff --git a/src/game/server/player.h b/src/game/server/player.h index af9dd297ea..3dc7dabee7 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -37,6 +37,9 @@ class CPlayer void KillCharacter(int Weapon = WEAPON_GAME); CCharacter *GetCharacter(); + const char* GetLanguage(); + void SetLanguage(const char* pLanguage); + //--------------------------------------------------------- // this is used for snapping so we know how we can clip the view for the player vec2 m_ViewPos; @@ -98,6 +101,8 @@ class CPlayer int m_Max; } m_Latency; + int m_Authed; + private: CCharacter *m_pCharacter; CGameContext *m_pGameServer; @@ -110,11 +115,13 @@ class CPlayer int m_ClientID; int m_Team; -private: + char m_aLanguage[16]; + + private: CTuningParams m_PrevTuningParams; CTuningParams m_NextTuningParams; -void HandleTuningParams(); //This function will send the new parameters if needed + void HandleTuningParams(); //This function will send the new parameters if needed public: CTuningParams* GetNextTuningParams() { return &m_NextTuningParams; }; diff --git a/src/teeuniverses/components/localization.cpp b/src/teeuniverses/components/localization.cpp new file mode 100644 index 0000000000..807207777c --- /dev/null +++ b/src/teeuniverses/components/localization.cpp @@ -0,0 +1,965 @@ +#include "localization.h" + +/* BEGIN EDIT *********************************************************/ +#include +#include +#include +#include +/* END EDIT ***********************************************************/ + +/* LANGUAGE ***********************************************************/ + +CLocalization::CLanguage::CLanguage() : + m_Loaded(false), + m_Direction(CLocalization::DIRECTION_LTR), + m_pPluralRules(NULL), + m_pNumberFormater(NULL), + m_pPercentFormater(NULL), + m_pTimeUnitFormater(NULL) +{ + m_aName[0] = 0; + m_aFilename[0] = 0; + m_aParentFilename[0] = 0; +} + +CLocalization::CLanguage::CLanguage(const char* pName, const char* pFilename, const char* pParentFilename) : + m_Loaded(false), + m_Direction(CLocalization::DIRECTION_LTR), + m_pPluralRules(NULL), + m_pNumberFormater(NULL), + m_pPercentFormater(NULL) +{ + str_copy(m_aName, pName, sizeof(m_aName)); + str_copy(m_aFilename, pFilename, sizeof(m_aFilename)); + str_copy(m_aParentFilename, pParentFilename, sizeof(m_aParentFilename)); + + UErrorCode Status; + + Status = U_ZERO_ERROR; + m_pNumberFormater = unum_open(UNUM_DECIMAL, NULL, -1, m_aFilename, NULL, &Status); + if(U_FAILURE(Status)) + { + if(m_pNumberFormater) + { + unum_close(m_pNumberFormater); + m_pNumberFormater = NULL; + } + dbg_msg("Localization", "Can't create number formater for %s (error #%d)", m_aFilename, Status); + } + + Status = U_ZERO_ERROR; + m_pPercentFormater = unum_open(UNUM_PERCENT, NULL, -1, m_aFilename, NULL, &Status); + if(U_FAILURE(Status)) + { + if(m_pPercentFormater) + { + unum_close(m_pPercentFormater); + m_pPercentFormater = NULL; + } + dbg_msg("Localization", "Can't create percent formater for %s (error #%d)", m_aFilename, Status); + } + + Status = U_ZERO_ERROR; + m_pPluralRules = uplrules_openForType(m_aFilename, UPLURAL_TYPE_CARDINAL, &Status); + if(U_FAILURE(Status)) + { + if(m_pPluralRules) + { + uplrules_close(m_pPluralRules); + m_pPluralRules = NULL; + } + dbg_msg("Localization", "Can't create plural rules for %s (error #%d)", m_aFilename, Status); + } + + //Time unit for second formater + Status = U_ZERO_ERROR; + m_pTimeUnitFormater = new icu::TimeUnitFormat(m_aFilename, UTMUTFMT_ABBREVIATED_STYLE, Status); + if(U_FAILURE(Status)) + { + dbg_msg("Localization", "Can't create timeunit formater %s (error #%d)", pFilename, Status); + delete m_pTimeUnitFormater; + m_pTimeUnitFormater = NULL; + } +} + +CLocalization::CLanguage::~CLanguage() +{ + hashtable< CEntry, 128 >::iterator Iter = m_Translations.begin(); + while(Iter != m_Translations.end()) + { + if(Iter.data()) + Iter.data()->Free(); + + ++Iter; + } + + if(m_pNumberFormater) + unum_close(m_pNumberFormater); + + if(m_pPercentFormater) + unum_close(m_pPercentFormater); + + if(m_pPluralRules) + uplrules_close(m_pPluralRules); + + if(m_pTimeUnitFormater) + delete m_pTimeUnitFormater; +} + +/* BEGIN EDIT *********************************************************/ +bool CLocalization::CLanguage::Load(CLocalization* pLocalization, CStorage* pStorage) +/* END EDIT ***********************************************************/ +{ + // read file data into buffer + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "./server_lang/%s.json", m_aFilename); + + IOHANDLE File = pStorage->OpenFile(aBuf, IOFLAG_READ, CStorage::TYPE_ALL); + if(!File) + return false; + + // load the file as a string + int FileSize = (int)io_length(File); + char *pFileData = new char[FileSize+1]; + io_read(File, pFileData, FileSize); + pFileData[FileSize] = 0; + io_close(File); + + // parse json data + json_settings JsonSettings; + mem_zero(&JsonSettings, sizeof(JsonSettings)); + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, pFileData, aError); + if(pJsonData == 0) + { + dbg_msg("Localization", "Can't load the localization file %s : %s", aBuf, aError); + delete[] pFileData; + return false; + } + + dynamic_string Buffer; + int Length; + + // extract data + const json_value &rStart = (*pJsonData)["translation"]; + if(rStart.type == json_array) + { + for(unsigned i = 0; i < rStart.u.array.length; ++i) + { + const char* pKey = rStart[i]["key"]; + if(pKey && pKey[0]) + { + CEntry* pEntry = m_Translations.set(pKey); + + const char* pSingular = rStart[i]["value"]; + if(pSingular && pSingular[0]) + { + Length = str_length(pSingular)+1; + pEntry->m_apVersions[PLURALTYPE_NONE] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_NONE], pSingular, Length); + } + else + { + const char* pPlural; + + //Zero + pPlural = rStart[i]["zero"]; + if(pPlural && pPlural[PLURALTYPE_ZERO]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_ZERO] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_ZERO], pPlural, Length); + } + //One + pPlural = rStart[i]["one"]; + if(pPlural && pPlural[PLURALTYPE_ONE]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_ONE] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_ONE], pPlural, Length); + } + //Two + pPlural = rStart[i]["two"]; + if(pPlural && pPlural[PLURALTYPE_TWO]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_TWO] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_TWO], pPlural, Length); + } + //Few + pPlural = rStart[i]["few"]; + if(pPlural && pPlural[PLURALTYPE_FEW]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_FEW] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_FEW], pPlural, Length); + } + //Many + pPlural = rStart[i]["many"]; + if(pPlural && pPlural[PLURALTYPE_MANY]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_MANY] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_MANY], pPlural, Length); + } + //Other + pPlural = rStart[i]["other"]; + if(pPlural && pPlural[PLURALTYPE_OTHER]) + { + Length = str_length(pPlural)+1; + pEntry->m_apVersions[PLURALTYPE_OTHER] = new char[Length]; + str_copy(pEntry->m_apVersions[PLURALTYPE_OTHER], pPlural, Length); + } + } + } + } + } + + // clean up + json_value_free(pJsonData); + delete[] pFileData; + + m_Loaded = true; + + return true; +} + +const char* CLocalization::CLanguage::Localize(const char* pText) const +{ + const CEntry* pEntry = m_Translations.get(pText); + if(!pEntry) + return NULL; + + return pEntry->m_apVersions[PLURALTYPE_NONE]; +} + +const char* CLocalization::CLanguage::Localize_P(int Number, const char* pText) const +{ + const CEntry* pEntry = m_Translations.get(pText); + if(!pEntry) + return NULL; + + UChar aPluralKeyWord[6]; + UErrorCode Status = U_ZERO_ERROR; + uplrules_select(m_pPluralRules, static_cast(Number), aPluralKeyWord, 6, &Status); + + if(U_FAILURE(Status)) + return NULL; + + int PluralCode = PLURALTYPE_NONE; + + if(aPluralKeyWord[0] == 0x007A) //z + PluralCode = PLURALTYPE_ZERO; + else if(aPluralKeyWord[0] == 0x0074) //t + PluralCode = PLURALTYPE_TWO; + else if(aPluralKeyWord[0] == 0x0066) //f + PluralCode = PLURALTYPE_FEW; + else if(aPluralKeyWord[0] == 0x006D) //m + PluralCode = PLURALTYPE_MANY; + else if(aPluralKeyWord[0] == 0x006F) //o + { + if(aPluralKeyWord[1] == 0x0074) //t + PluralCode = PLURALTYPE_OTHER; + else if(aPluralKeyWord[1] == 0x006E) //n + PluralCode = PLURALTYPE_ONE; + } + + return pEntry->m_apVersions[PluralCode]; +} + +/* LOCALIZATION *******************************************************/ + +/* BEGIN EDIT *********************************************************/ +CLocalization::CLocalization(class CStorage* pStorage) : + m_pStorage(pStorage), + m_pMainLanguage(NULL), + m_pUtf8Converter(NULL) +{ + +} +/* END EDIT ***********************************************************/ + +CLocalization::~CLocalization() +{ + for(int i=0; iOpenFile(pFilename, IOFLAG_READ, CStorage::TYPE_ALL); + if(!File) + { + dbg_msg("Localization", "can't open 'server_lang/index.json'"); + return true; //return true because it's not a critical error + } + + int FileSize = (int)io_length(File); + char *pFileData = new char[FileSize+1]; + io_read(File, pFileData, FileSize); + pFileData[FileSize] = 0; + io_close(File); + + // parse json data + json_settings JsonSettings; + mem_zero(&JsonSettings, sizeof(JsonSettings)); + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, pFileData, aError); + if(pJsonData == 0) + { + delete[] pFileData; + return true; //return true because it's not a critical error + } + + // extract data + m_pMainLanguage = 0; + const json_value &rStart = (*pJsonData)["language indices"]; + if(rStart.type == json_array) + { + for(unsigned i = 0; i < rStart.u.array.length; ++i) + { + CLanguage*& pLanguage = m_pLanguages.increment(); + pLanguage = new CLanguage((const char *)rStart[i]["name"], (const char *)rStart[i]["file"], (const char *)rStart[i]["parent"]); + + if((const char *)rStart[i]["direction"] && str_comp((const char *)rStart[i]["direction"], "rtl") == 0) + pLanguage->SetWritingDirection(DIRECTION_RTL); + + if(m_Cfg_MainLanguage == pLanguage->GetFilename()) + { + pLanguage->Load(this, Storage()); + + m_pMainLanguage = pLanguage; + } + } + } + + // clean up + json_value_free(pJsonData); + delete[] pFileData; + + return true; +} + +void CLocalization::AddListener(IListener* pListener) +{ + m_pListeners.increment() = pListener; +} + +void CLocalization::RemoveListener(IListener* pListener) +{ + for(int i=0; iGetFilename()) + { + CLanguage* pLanguage = 0; + + for(int i=0; iGetFilename()) + { + pLanguage = m_pLanguages[i]; + break; + } + } + + if(m_pMainLanguage != pLanguage) + { + m_pMainLanguage = pLanguage; + + for(int i=0; iOnLocalizationModified(); + } + } + + return true; +} + +const char *CLocalization::LanguageCodeByCountryCode(int CountryCode) +{ + // Constants from 'data/countryflags/index.txt' + switch(CountryCode) + { + /* ar - Arabic ************************************/ + case 12: //Algeria + case 48: //Bahrain + case 262: //Djibouti + case 818: //Egypt + case 368: //Iraq + case 400: //Jordan + case 414: //Kuwait + case 422: //Lebanon + case 434: //Libya + case 478: //Mauritania + case 504: //Morocco + case 512: //Oman + case 275: //Palestine + case 634: //Qatar + case 682: //Saudi Arabia + case 706: //Somalia + case 729: //Sudan + case 760: //Syria + case 788: //Tunisia + case 784: //United Arab Emirates + case 887: //Yemen + return "ar"; + /* bg - Bosnian *************************************/ + case 100: //Bulgaria + return "bg"; + /* bs - Bosnian *************************************/ + case 70: //Bosnia and Hercegovina + return "bs"; + /* cs - Czech *************************************/ + case 203: //Czechia + return "cs"; + /* de - German ************************************/ + case 40: //Austria + case 276: //Germany + case 438: //Liechtenstein + case 756: //Switzerland + return "de"; + /* el - Greek ***********************************/ + case 300: //Greece + case 196: //Cyprus + return "el"; + /* es - Spanish ***********************************/ + case 32: //Argentina + case 68: //Bolivia + case 152: //Chile + case 170: //Colombia + case 188: //Costa Rica + case 192: //Cuba + case 214: //Dominican Republic + case 218: //Ecuador + case 222: //El Salvador + case 226: //Equatorial Guinea + case 320: //Guatemala + case 340: //Honduras + case 484: //Mexico + case 558: //Nicaragua + case 591: //Panama + case 600: //Paraguay + case 604: //Peru + case 630: //Puerto Rico + case 724: //Spain + case 858: //Uruguay + case 862: //Venezuela + return "es"; + /* fa - Farsi ************************************/ + case 364: //Islamic Republic of Iran + case 4: //Afghanistan + return "fa"; + /* fr - French ************************************/ + case 204: //Benin + case 854: //Burkina Faso + case 178: //Republic of the Congo + case 384: //Cote d’Ivoire + case 266: //Gabon + case 324: //Ginea + case 466: //Mali + case 562: //Niger + case 686: //Senegal + case 768: //Togo + case 250: //France + case 492: //Monaco + return "fr"; + /* hr - Croatian **********************************/ + case 191: //Croatia + return "hr"; + /* hu - Hungarian *********************************/ + case 348: //Hungary + return "hu"; + /* it - Italian ***********************************/ + case 380: //Italy + return "it"; + /* ja - Japanese **********************************/ + case 392: //Japan + return "ja"; + /* la - Latin *************************************/ + case 336: //Vatican + return "la"; + /* nl - Dutch *************************************/ + case 533: //Aruba + case 531: //Curaçao + case 534: //Sint Maarten + case 528: //Netherland + case 740: //Suriname + case 56: //Belgique + return "nl"; + /* pl - Polish *************************************/ + case 616: //Poland + return "pl"; + /* pt - Portuguese ********************************/ + case 24: //Angola + case 76: //Brazil + case 132: //Cape Verde + //case 226: //Equatorial Guinea: official language, but not national language + //case 446: //Macao: official language, but spoken by less than 1% of the population + case 508: //Mozambique + case 626: //Timor-Leste + case 678: //São Tomé and Príncipe + return "pt"; + /* ru - Russian ***********************************/ + case 112: //Belarus + case 643: //Russia + case 398: //Kazakhstan + return "ru"; + /* sk - Slovak ************************************/ + case 703: //Slovakia + return "sk"; + /* sr - Serbian ************************************/ + case 688: //Serbia + return "sr"; + /* tl - Tagalog ************************************/ + case 608: //Philippines + return "tl"; + /* tr - Turkish ************************************/ + case 31: //Azerbaijan + case 792: //Turkey + return "tr"; + /* uk - Ukrainian **********************************/ + case 804: //Ukraine + return "uk"; + /* zh-Hans - Chinese (Simplified) **********************************/ + case 156: //People’s Republic of China + case 344: //Hong Kong + case 446: //Macau + return "zh-Hans"; + case 826: // United Kingdom of Great Britain and Northern Ireland + case 840: // United States of America + return "en"; + default: + return ""; + } +} + +const char *CLocalization::FallbackLanguageForIpCountryCode(int Country) +{ + switch (Country) { + case 364: //Islamic Republic of Iran + case 4: //Afghanistan + return "fa"; + case 112: //Belarus + case 643: //Russia + case 398: //Kazakhstan + return "ru"; + default: + return "en"; + } +} + +const char* CLocalization::LocalizeWithDepth(const char* pLanguageCode, const char* pText, int Depth) +{ + CLanguage* pLanguage = m_pMainLanguage; + if(pLanguageCode) + { + for(int i=0; iGetFilename(), pLanguageCode) == 0) + { + pLanguage = m_pLanguages[i]; + break; + } + } + } + + if(!pLanguage) + return pText; + + if(!pLanguage->IsLoaded()) + pLanguage->Load(this, Storage()); + + const char* pResult = pLanguage->Localize(pText); + if(pResult) + return pResult; + else if(pLanguage->GetParentFilename()[0] && Depth < 4) + return LocalizeWithDepth(pLanguage->GetParentFilename(), pText, Depth+1); + else + return pText; +} + +const char* CLocalization::Localize(const char* pLanguageCode, const char* pText) +{ + return LocalizeWithDepth(pLanguageCode, pText, 0); +} + +const char* CLocalization::LocalizeWithDepth_P(const char* pLanguageCode, int Number, const char* pText, int Depth) +{ + CLanguage* pLanguage = m_pMainLanguage; + if(pLanguageCode) + { + for(int i=0; iGetFilename(), pLanguageCode) == 0) + { + pLanguage = m_pLanguages[i]; + break; + } + } + } + + if(!pLanguage) + return pText; + + if(!pLanguage->IsLoaded()) + pLanguage->Load(this, Storage()); + + const char* pResult = pLanguage->Localize_P(Number, pText); + if(pResult) + return pResult; + else if(pLanguage->GetParentFilename()[0] && Depth < 4) + return LocalizeWithDepth_P(pLanguage->GetParentFilename(), Number, pText, Depth+1); + else + return pText; +} + +const char* CLocalization::Localize_P(const char* pLanguageCode, int Number, const char* pText) +{ + return LocalizeWithDepth_P(pLanguageCode, Number, pText, 0); +} + +void CLocalization::AppendNumber(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, int Number) +{ + UChar aBufUtf16[128]; + + UErrorCode Status = U_ZERO_ERROR; + unum_format(pLanguage->m_pNumberFormater, Number, aBufUtf16, sizeof(aBufUtf16), nullptr, &Status); + + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_NUMBER_"); + else + { + // update buffer size + const int SrcLength = u_strlen(aBufUtf16); + const int NeededSize = UCNV_GET_MAX_BYTES_FOR_STRING(SrcLength, ucnv_getMaxCharSize(m_pUtf8Converter)); + + while(Buffer.maxsize() - BufferIter <= NeededSize) + Buffer.resize_buffer(Buffer.maxsize()*2); + + const int Length = ucnv_fromUChars(m_pUtf8Converter, Buffer.buffer()+BufferIter, Buffer.maxsize() - BufferIter, aBufUtf16, SrcLength, &Status); + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_NUMBER_"); + else + BufferIter += Length; + } +} + +void CLocalization::AppendPercent(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, double Number) +{ + UChar aBufUtf16[128]; + + UErrorCode Status = U_ZERO_ERROR; + unum_formatDouble(pLanguage->m_pPercentFormater, Number, aBufUtf16, sizeof(aBufUtf16), NULL, &Status); + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_PERCENT_"); + else + { + //Update buffer size + int SrcLength = u_strlen(aBufUtf16); + int NeededSize = UCNV_GET_MAX_BYTES_FOR_STRING(SrcLength, ucnv_getMaxCharSize(m_pUtf8Converter)); + + while(Buffer.maxsize() - BufferIter <= NeededSize) + Buffer.resize_buffer(Buffer.maxsize()*2); + + int Length = ucnv_fromUChars(m_pUtf8Converter, Buffer.buffer()+BufferIter, Buffer.maxsize() - BufferIter, aBufUtf16, SrcLength, &Status); + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_PERCENT_"); + else + BufferIter += Length; + } +} + +void CLocalization::AppendDuration(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, int Number, icu::TimeUnit::UTimeUnitFields Type) +{ + UErrorCode Status = U_ZERO_ERROR; + icu::UnicodeString BufUTF16; + + icu::TimeUnitAmount* pAmount = new icu::TimeUnitAmount((double) Number, Type, Status); + icu::Formattable Formattable; + Formattable.adoptObject(pAmount); + pLanguage->m_pTimeUnitFormater->format(Formattable, BufUTF16, Status); + + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_DURATION_"); + else + { + int SrcLength = BufUTF16.length(); + + int NeededSize = UCNV_GET_MAX_BYTES_FOR_STRING(SrcLength, ucnv_getMaxCharSize(m_pUtf8Converter)); + + while(Buffer.maxsize() - BufferIter <= NeededSize) + Buffer.resize_buffer(Buffer.maxsize()*2); + + Status = U_ZERO_ERROR; + int Length = ucnv_fromUChars(m_pUtf8Converter, Buffer.buffer()+BufferIter, Buffer.maxsize() - BufferIter, BufUTF16.getBuffer(), SrcLength, &Status); + + if(U_FAILURE(Status)) + BufferIter = Buffer.append_at(BufferIter, "_DURATION_"); + else + BufferIter += Length; + } +} + +void CLocalization::Format_V(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, va_list VarArgs) +{ + CLanguage* pLanguage = m_pMainLanguage; + if(pLanguageCode) + { + for(int i=0; iGetFilename(), pLanguageCode) == 0) + { + pLanguage = m_pLanguages[i]; + break; + } + } + } + if(!pLanguage) + { + Buffer.append(pText); + return; + } + + const char* pVarArgName = NULL; + const void* pVarArgValue = NULL; + + int Iter = 0; + int Start = Iter; + int ParamTypeStart = -1; + int ParamTypeLength = 0; + int ParamNameStart = -1; + int ParamNameLength = 0; + + int BufferStart = Buffer.length(); + int BufferIter = BufferStart; + + while(pText[Iter]) + { + if(ParamNameStart >= 0) + { + if(pText[Iter] == '}') //End of the macro, try to apply it + { + //Try to find an argument with this name + va_list VarArgsIter; + + //windows + #if defined(CONF_FAMILY_WINDOWS) + #define va_copy(d,s) ((d) = (s)) + #endif + + va_copy(VarArgsIter, VarArgs); + pVarArgName = va_arg(VarArgsIter, const char*); + while(pVarArgName) + { + pVarArgValue = va_arg(VarArgsIter, const void*); + if(str_comp_num(pText+ParamNameStart, pVarArgName, ParamNameLength) == 0) + { + //Get argument type + if(str_comp_num("str:", pText+ParamTypeStart, 4) == 0) + { + BufferIter = Buffer.append_at(BufferIter, (const char*) pVarArgValue); + } + else if(str_comp_num("int:", pText+ParamTypeStart, 4) == 0) + { + int Number = *((int*) pVarArgValue); + AppendNumber(Buffer, BufferIter, pLanguage, Number); + } + else if(str_comp_num("ullint:", pText+ParamTypeStart, 4) == 0) + { + int Number = *((const unsigned long long int*) pVarArgValue); + AppendNumber(Buffer, BufferIter, pLanguage, Number); + } + else if(str_comp_num("uint:", pText+ParamTypeStart, 4) == 0) + { + int Number = *((const unsigned int*) pVarArgValue); + AppendNumber(Buffer, BufferIter, pLanguage, Number); + } + else if(str_comp_num("percent:", pText+ParamTypeStart, 4) == 0) + { + float Number = (*((const float*) pVarArgValue)); + AppendPercent(Buffer, BufferIter, pLanguage, Number); + } + else if(str_comp_num("sec:", pText+ParamTypeStart, 4) == 0) + { + int Duration = *((const int*) pVarArgValue); + int Minutes = Duration / 60; + int Seconds = Duration - Minutes*60; + if(Minutes > 0) + { + AppendDuration(Buffer, BufferIter, pLanguage, Minutes, icu::TimeUnit::UTIMEUNIT_MINUTE); + if(Seconds > 0) + { + BufferIter = Buffer.append_at(BufferIter, ", "); + AppendDuration(Buffer, BufferIter, pLanguage, Seconds, icu::TimeUnit::UTIMEUNIT_SECOND); + } + } + else + AppendDuration(Buffer, BufferIter, pLanguage, Seconds, icu::TimeUnit::UTIMEUNIT_SECOND); + } + break; + } + + pVarArgName = va_arg(VarArgsIter, const char*); + } + va_end(VarArgsIter); + + //Close the macro + Start = Iter+1; + ParamTypeStart = -1; + ParamNameStart = -1; + } + else + ParamNameLength++; + } + else if(ParamTypeStart >= 0) + { + if(pText[Iter] == ':') //End of the type, start of the name + { + ParamNameStart = Iter+1; + ParamNameLength = 0; + } + else if(pText[Iter] == '}') //Invalid: no name found + { + //Close the macro + Start = Iter+1; + ParamTypeStart = -1; + ParamNameStart = -1; + } + else + ParamTypeLength++; + } + else + { + if(pText[Iter] == '{') + { + //Flush the content of pText in the buffer + BufferIter = Buffer.append_at_num(BufferIter, pText+Start, Iter-Start); + Iter++; + ParamTypeStart = Iter; + } + } + + Iter = str_utf8_forward(pText, Iter); + } + + if(Iter > 0 && ParamTypeStart == -1 && ParamNameStart == -1) + { + BufferIter = Buffer.append_at_num(BufferIter, pText+Start, Iter-Start); + } + + if(pLanguage && pLanguage->GetWritingDirection() == DIRECTION_RTL) + ArabicShaping(Buffer, BufferStart); +} + +void CLocalization::Format(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, ...) +{ + va_list VarArgs; + va_start(VarArgs, pText); + + Format_V(Buffer, pLanguageCode, pText, VarArgs); + + va_end(VarArgs); +} + +void CLocalization::Format_VL(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, va_list VarArgs) +{ + const char* pLocalText = Localize(pLanguageCode, pText); + + Format_V(Buffer, pLanguageCode, pLocalText, VarArgs); +} + +void CLocalization::Format_L(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, ...) +{ + va_list VarArgs; + va_start(VarArgs, pText); + + Format_VL(Buffer, pLanguageCode, pText, VarArgs); + + va_end(VarArgs); +} + +void CLocalization::Format_VLP(dynamic_string& Buffer, const char* pLanguageCode, int Number, const char* pText, va_list VarArgs) +{ + const char* pLocalText = Localize_P(pLanguageCode, Number, pText); + + Format_V(Buffer, pLanguageCode, pLocalText, VarArgs); +} + +void CLocalization::Format_LP(dynamic_string& Buffer, const char* pLanguageCode, int Number, const char* pText, ...) +{ + va_list VarArgs; + va_start(VarArgs, pText); + + Format_VLP(Buffer, pLanguageCode, Number, pText, VarArgs); + + va_end(VarArgs); +} + +void CLocalization::ArabicShaping(dynamic_string& Buffer, int BufferStart) +{ + UErrorCode Status = U_ZERO_ERROR; + + int Length = (Buffer.length() - BufferStart + 1); + int LengthUTF16 = Length*2; + UChar* pBuf0 = new UChar[LengthUTF16]; + UChar* pBuf1 = new UChar[LengthUTF16]; + + ucnv_toUChars(m_pUtf8Converter, pBuf0, LengthUTF16, Buffer.buffer() + BufferStart, Length, &Status); + + UBiDi* pBiDi = ubidi_openSized(LengthUTF16, 0, &Status); + ubidi_setPara(pBiDi, pBuf0, -1, UBIDI_DEFAULT_LTR, 0, &Status); + ubidi_writeReordered(pBiDi, pBuf1, LengthUTF16, UBIDI_DO_MIRRORING, &Status); + ubidi_close(pBiDi); + + u_shapeArabic( + pBuf1, LengthUTF16, + pBuf0, LengthUTF16, + U_SHAPE_LETTERS_SHAPE | + U_SHAPE_PRESERVE_PRESENTATION | + U_SHAPE_TASHKEEL_RESIZE | + U_SHAPE_LENGTH_GROW_SHRINK | + U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | + U_SHAPE_LAMALEF_RESIZE, + &Status + ); + + int ShapedLength = u_strlen(pBuf0); + int NeededSize = UCNV_GET_MAX_BYTES_FOR_STRING(ShapedLength, ucnv_getMaxCharSize(m_pUtf8Converter)); + + while(Buffer.maxsize() - BufferStart <= NeededSize) + Buffer.resize_buffer(Buffer.maxsize()*2); + + ucnv_fromUChars(m_pUtf8Converter, Buffer.buffer() + BufferStart, Buffer.maxsize() - BufferStart, pBuf0, ShapedLength, &Status); + + delete[] pBuf0; + delete[] pBuf1; +} diff --git a/src/teeuniverses/components/localization.h b/src/teeuniverses/components/localization.h new file mode 100644 index 0000000000..062687ffd2 --- /dev/null +++ b/src/teeuniverses/components/localization.h @@ -0,0 +1,173 @@ +#ifndef __SHARED_LOCALIZATION__ +#define __SHARED_LOCALIZATION__ + +/* BEGIN EDIT *********************************************************/ +#include +#define CStorage IStorage +/* END EDIT ***********************************************************/ + +#include +#include +#include +#include + +#include + +struct CLocalizableString +{ + const char* m_pText; + + CLocalizableString(const char* pText) : + m_pText(pText) + { } +}; + +/* BEGIN EDIT *********************************************************/ +#define _(TEXT) TEXT +#define _P(TEXT_SINGULAR, TEXT_PLURAL) TEXT_PLURAL +/* END EDIT ***********************************************************/ + +/* BEGIN EDIT *********************************************************/ +class CLocalization +{ +private: + class CStorage* m_pStorage; + inline class CStorage* Storage() { return m_pStorage; } +/* END EDIT ***********************************************************/ +public: + enum + { + PLURALTYPE_NONE=0, + PLURALTYPE_ZERO, + PLURALTYPE_ONE, + PLURALTYPE_TWO, + PLURALTYPE_FEW, + PLURALTYPE_MANY, + PLURALTYPE_OTHER, + NUM_PLURALTYPES, + }; + + class IListener + { + public: + virtual void OnLocalizationModified() = 0; + }; + + static const char *LanguageCodeByCountryCode(int country); + static const char *FallbackLanguageForIpCountryCode(int Country); + + class CLanguage + { + protected: + class CEntry + { + public: + char* m_apVersions[NUM_PLURALTYPES]; + + CEntry() + { + for(int i=0; i m_Translations; + + public: + UPluralRules* m_pPluralRules; + UNumberFormat* m_pNumberFormater; + UNumberFormat* m_pPercentFormater; + icu::TimeUnitFormat* m_pTimeUnitFormater; + + public: + CLanguage(); + CLanguage(const char* pName, const char* pFilename, const char* pParentFilename); + ~CLanguage(); + + inline const char* GetParentFilename() const { return m_aParentFilename; } + inline const char* GetFilename() const { return m_aFilename; } + inline const char* GetName() const { return m_aName; } + inline int GetWritingDirection() const { return m_Direction; } + inline void SetWritingDirection(int Direction) { m_Direction = Direction; } + inline bool IsLoaded() const { return m_Loaded; } + bool Load(CLocalization* pLocalization, class CStorage* pStorage); + const char* Localize(const char* pKey) const; + const char* Localize_P(int Number, const char* pText) const; + }; + + enum + { + DIRECTION_LTR=0, + DIRECTION_RTL, + NUM_DIRECTIONS, + }; + +protected: + CLanguage* m_pMainLanguage; + array m_pListeners; + bool m_UpdateListeners; + + UConverter* m_pUtf8Converter; + +public: + array m_pLanguages; + fixed_string128 m_Cfg_MainLanguage; + +protected: + const char* LocalizeWithDepth(const char* pLanguageCode, const char* pText, int Depth); + const char* LocalizeWithDepth_P(const char* pLanguageCode, int Number, const char* pText, int Depth); + + void AppendNumber(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, int Number); + void AppendPercent(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, double Number); + void AppendDuration(dynamic_string& Buffer, int& BufferIter, CLanguage* pLanguage, int Number, icu::TimeUnit::UTimeUnitFields Type); + +public: +/* BEGIN EDIT *********************************************************/ + CLocalization(class CStorage* pStorage); +/* END EDIT ***********************************************************/ + virtual ~CLocalization(); + + virtual bool InitConfig(int argc, const char** argv); +/* BEGIN EDIT *********************************************************/ +/* END EDIT ***********************************************************/ + virtual bool Init(); + virtual bool PreUpdate(); + + void AddListener(IListener* pListener); + void RemoveListener(IListener* pListener); + + inline bool GetWritingDirection() const { return (!m_pMainLanguage ? DIRECTION_LTR : m_pMainLanguage->GetWritingDirection()); } + + //localize + const char* Localize(const char* pLanguageCode, const char* pText); + //localize and find the appropriate plural form based on Number + const char* Localize_P(const char* pLanguageCode, int Number, const char* pText); + + //format + void Format_V(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, va_list VarArgs); + void Format(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, ...); + //localize, format + void Format_VL(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, va_list VarArgs); + void Format_L(dynamic_string& Buffer, const char* pLanguageCode, const char* pText, ...); + //localize, find the appropriate plural form based on Number and format + void Format_VLP(dynamic_string& Buffer, const char* pLanguageCode, int Number, const char* pText, va_list VarArgs); + void Format_LP(dynamic_string& Buffer, const char* pLanguageCode, int Number, const char* pText, ...); + + void ArabicShaping(dynamic_string& Buffer, int BufferStart = 0); +}; + +#endif diff --git a/src/teeuniverses/system/string.h b/src/teeuniverses/system/string.h new file mode 100644 index 0000000000..f1a2f1550a --- /dev/null +++ b/src/teeuniverses/system/string.h @@ -0,0 +1,282 @@ +/* + * Some parts of this file comes from other projects. + * These parts are itendified in this file by the following block: + * + * FOREIGN CODE BEGIN: ProjectName ************************************* + * + * FOREIGN CODE END: ProjectName *************************************** + * + * If ProjectName is "TeeWorlds", then this part of the code follows the + * TeeWorlds licence: + * (c) Magnus Auvinen. See licence.txt in the root of the + * distribution for more information. If you are missing that file, + * acquire a complete release at teeworlds.com. + */ + +#ifndef __SHARED_SYSTEM_STRING__ +#define __SHARED_SYSTEM_STRING__ + +#include +/* BEGIN EDIT *********************************************************/ +#include +/* END EDIT ***********************************************************/ +#include + +//String contained in a fixed length array +template +class _fixed_string_core +{ +private: + char m_aBuffer[SIZE]; + + //throw a compilation error if the object is copied + _fixed_string_core(const _fixed_string_core&); + _fixed_string_core& operator=(const _fixed_string_core&); + +public: + _fixed_string_core() + { + m_aBuffer[0] = 0; + } + + inline char* buffer() { return m_aBuffer; } + inline const char* buffer() const { return m_aBuffer; } + inline int maxsize() const { return SIZE; } + + inline void copy(const char* pBuffer) + { + str_copy(m_aBuffer, pBuffer, SIZE); + } + + inline void transfert(_fixed_string_core& String) + { + copy((const char*) String.buffer()); + } + + inline void append_at(int Pos, const char* pBuffer) + { + str_append(m_aBuffer+Pos, pBuffer, SIZE-Pos); + } + + inline void append_at_num(int Pos, const char* pBuffer, int num) + { + str_append_num(m_aBuffer+Pos, pBuffer, SIZE-Pos, num); + } +}; + +//String contained in a rezisable array +template +class _dynamic_string_core +{ +private: + char* m_pBuffer; + int m_MaxSize; + +private: + //throw a compilation error if the object is copied + _dynamic_string_core(const _dynamic_string_core&); + _dynamic_string_core& operator=(const _dynamic_string_core&); + +public: + _dynamic_string_core() : + m_pBuffer(NULL), + m_MaxSize(0) + { + resize_buffer(INITIALSIZE); + } + + ~_dynamic_string_core() + { + if(m_pBuffer) + delete[] m_pBuffer; + } + + void resize_buffer(int Size) + { + if(m_pBuffer) + { + char* pBuffer = new char[Size]; + str_copy(pBuffer, m_pBuffer, Size); + delete[] m_pBuffer; + m_pBuffer = pBuffer; + m_MaxSize = Size; + } + else + { + m_MaxSize = Size; + m_pBuffer = new char[m_MaxSize]; + m_pBuffer[0] = 0; + } + } + + inline char* buffer() { return m_pBuffer; } + inline const char* buffer() const { return m_pBuffer; } + inline int maxsize() const { return m_MaxSize; } + + inline void copy(const char* pBuffer) + { + int Size = str_length(pBuffer)+1; + if(Size > m_MaxSize) + resize_buffer(Size); + + str_copy(m_pBuffer, pBuffer, m_MaxSize); + } + + inline void transfert(_dynamic_string_core& String) + { + if(m_pBuffer) + delete[] m_pBuffer; + + m_pBuffer = String.m_pBuffer; + m_MaxSize = String.m_MaxSize; + String.m_pBuffer = NULL; + String.m_MaxSize = 0; + } + + inline int append_at(int Pos, const char* pBuffer) + { + int BufferSize = str_length(pBuffer); + int Size = Pos+BufferSize+1; + if(Size > m_MaxSize) + { + int NewSize = m_MaxSize*2; + while(Size > NewSize) + NewSize *= 2; + + resize_buffer(NewSize); + } + + str_append(m_pBuffer+Pos, pBuffer, m_MaxSize-Pos); + + return minimum(Pos + BufferSize, m_MaxSize-1); + } + + inline int append_at_num(int Pos, const char* pBuffer, int Num) + { + int Size = Pos+Num+1; + if(Size > m_MaxSize) + { + int NewSize = m_MaxSize*2; + while(Size > NewSize) + NewSize *= 2; + + resize_buffer(NewSize); + } + + str_append_num(m_pBuffer+Pos, pBuffer, m_MaxSize-Pos, Num); + + return minimum(Pos + Num, m_MaxSize-1); + } +}; + +template +class string : public BASE +{ +public: + string() : + BASE() + { + + } + + string(const char* pBuffer) : + BASE() + { + BASE::copy(pBuffer); + } + + inline int length() const { return str_length(BASE::buffer()); } + inline void clear() { BASE::buffer()[0] = 0; } + inline bool empty() const { return (BASE::buffer()[0] == 0); } + + inline void copy(const char* pBuffer) { BASE::copy(pBuffer); } + + template + inline void copy(const STR& str) + { + BASE::copy(str.buffer()); + } + + inline void append(const char* pBuffer) { BASE::append_at(length(), pBuffer); } + + template + inline void append(const STR& str) + { + BASE::append_at(length(), str.buffer()); + } + + inline void append_num(const char* pBuffer, int num) { BASE::append_at_num(length(), pBuffer, num); } + + template + inline void append_num(const STR& str, int num) + { + BASE::append_at_num(length(), str.buffer(), num); + } + + bool operator<(const char* buffer) const + { + return (str_comp(BASE::buffer(), buffer) < 0); + } + + template + bool operator<(const STR& str) const + { + return (str_comp(BASE::buffer(), str.buffer()) < 0); + } + + bool operator>(const char* buffer) const + { + return (str_comp(BASE::buffer(), buffer) > 0); + } + + template + bool operator>(const STR& str) const + { + return (str_comp(BASE::buffer(), str.buffer()) > 0); + } + + bool operator==(const char* buffer) const + { + return (str_comp(BASE::buffer(), buffer) == 0); + } + + template + bool operator==(const STR& str) const + { + return (str_comp(BASE::buffer(), str.buffer()) == 0); + } + + bool operator!=(const char* buffer) const + { + return (str_comp(BASE::buffer(), buffer) != 0); + } + + template + bool operator!=(const STR& str) const + { + return (str_comp(BASE::buffer(), str.buffer()) != 0); + } + + int comp_num(const char* str, int num) const + { + return (str_comp_num(BASE::buffer(), str, num) != 0); + } + + template + int comp_num(const STR& str, int num) const + { + return (str_comp_num(BASE::buffer(), str.buffer(), num) != 0); + } +}; + +typedef string<_fixed_string_core<12> > fixed_string12; +typedef string<_fixed_string_core<16> > fixed_string16; +typedef string<_fixed_string_core<64> > fixed_string64; +typedef string<_fixed_string_core<128> > fixed_string128; +typedef string<_fixed_string_core<256> > fixed_string256; + +typedef string<_dynamic_string_core<128> > dynamic_string; + +//Operations on strings + +#endif diff --git a/src/teeuniverses/tl/allocator.h b/src/teeuniverses/tl/allocator.h new file mode 100644 index 0000000000..db9ab10d30 --- /dev/null +++ b/src/teeuniverses/tl/allocator.h @@ -0,0 +1,34 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef TEEUNIVERSES_TL_ALLOCATOR_H +#define TEEUNIVERSES_TL_ALLOCATOR_H + +template +class tu_allocator_default +{ +public: + static T *alloc() { return new T; } + static void free(T *p) { delete p; } + + static T *alloc_array(int size) { return new T [size]; } + static void free_array(T *p) { delete [] p; } + + static void copy(T& a, const T& b) { a = b; } + static void transfert(T& a, T& b) { a = b; } +}; + +template +class tu_allocator_copy +{ +public: + static T *alloc() { return new T; } + static void free(T *p) { delete p; } + + static T *alloc_array(int size) { return new T [size]; } + static void free_array(T *p) { delete [] p; } + + static void copy(T& a, const T& b) { a.copy(b); } + static void transfert(T& a, T& b) { a.transfert(b); } +}; + +#endif // TL_FILE_ALLOCATOR_HPP diff --git a/src/teeuniverses/tl/hashtable.h b/src/teeuniverses/tl/hashtable.h new file mode 100644 index 0000000000..d4f0589267 --- /dev/null +++ b/src/teeuniverses/tl/hashtable.h @@ -0,0 +1,210 @@ +#ifndef SHARED_TL_HASHTABLE_H +#define SHARED_TL_HASHTABLE_H + +/* BEGIN EDIT *********************************************************/ +#include +#include +#include +/* END EDIT ***********************************************************/ + +template > +class hashtable : private ALLOCATOR +{ +protected: + typedef unsigned int HASH; + + class entry + { + public: + dynamic_string m_Key; + T m_Data; + + entry& operator=(const entry& old) + { + m_Key.copy(old.m_Key); + ALLOCATOR::copy(m_Data, old.m_Data); + return *this; + } + }; + +protected: + array m_Table[TABLESIZE]; + +protected: + HASH hash(const char* pKey) const + { + HASH Hash = 5381; + for(; *pKey; pKey++) + Hash = ((Hash << 5) + Hash) + (*pKey); /* Hash * 33 + c */ + return Hash%TABLESIZE; + } + +public: + /* + Function: clear + Clear all entry in the hashtable + */ + void clear() + { + for(int i=0; i= 0 && Id < TABLESIZE && SubId >= 0 && SubId < m_Table[Id].size()) + return &m_Table[Id][SubId].m_Data; + else + return 0; + } + + const char* get_key(int Id, int SubId) const + { + if(Id >= 0 && Id < TABLESIZE && SubId >= 0 && SubId < m_Table[Id].size()) + return m_Table[Id][SubId].m_Key.buffer(); + else + return 0; + } + + T* get(const char* pKey) + { + HASH Hash = hash(pKey); + for(int i=0; iget_subtable_size(m_Id)) + break; + } + } + iterator(hashtable* pHashTable, int Id, int SubId) : m_pHashTable(pHashTable), m_Id(Id), m_SubId(SubId) {} + + iterator& operator++() + { + if(m_Id < TABLESIZE) + { + m_SubId++; + int TableSubSize = m_pHashTable->get_subtable_size(m_Id); + while(m_SubId >= TableSubSize && m_Id < TABLESIZE) + { + m_Id++; + m_SubId = 0; + TableSubSize = m_pHashTable->get_subtable_size(m_Id); + } + } + else + { + m_Id = TABLESIZE; + m_SubId = 0; + } + + return *this; + } + T* data() { return m_pHashTable->get(m_Id, m_SubId); } + const char* key() const { return m_pHashTable->get_key(m_Id, m_SubId); } + bool operator==(const iterator& Iter2) const { return (Iter2.m_pHashTable == m_pHashTable) && (Iter2.m_Id == m_Id) && (Iter2.m_SubId == m_SubId); } + bool operator!=(const iterator& Iter2) const { return (Iter2.m_pHashTable != m_pHashTable) || (Iter2.m_Id != m_Id) || (Iter2.m_SubId != m_SubId); } + }; + iterator begin() { return iterator(this); } + iterator end() { return iterator(this, TABLESIZE, 0); } +}; + +#endif // TL_FILE_HASHTABLE_HPP