From fcd26d0212746f654d63bbd06d9f2313aede7fab Mon Sep 17 00:00:00 2001 From: ST-Chara Date: Sat, 26 Mar 2022 22:12:35 +0800 Subject: [PATCH 1/2] First commit of Multi-Language Localization --- README.md | 11 +- bam.lua | 73 +- maps/ctf1.map | Bin 0 -> 4664 bytes maps/ctf2.map | Bin 0 -> 25626 bytes maps/ctf3.map | Bin 0 -> 5903 bytes maps/ctf4.map | Bin 0 -> 12030 bytes maps/ctf5.map | Bin 0 -> 12108 bytes maps/ctf6.map | Bin 0 -> 26927 bytes maps/ctf7.map | Bin 0 -> 5511 bytes maps/dd1.map | Bin 0 -> 5805 bytes maps/dm2.map | Bin 0 -> 8671 bytes maps/dm6.map | Bin 0 -> 7829 bytes maps/dm7.map | Bin 0 -> 10026 bytes maps/dm8.map | Bin 0 -> 40637 bytes maps/dm9.map | Bin 0 -> 8209 bytes server_lang/cn.json | 12 + server_lang/index.json | 14 + src/base/math.h | 36 +- src/base/system.c | 23 + src/base/system.h | 4 + src/base/tl/array.h | 8 +- src/engine/console.h | 10 +- src/engine/external/json-parser/VERSION | 1 + src/engine/external/json-parser/json.c | 841 ++++++++++++++++ src/engine/external/json-parser/json.h | 192 ++++ src/engine/server.h | 15 + src/engine/server/server.cpp | 44 +- src/engine/server/server.h | 6 + src/engine/shared/console.cpp | 23 +- src/engine/shared/console.h | 6 +- src/engine/shared/econ.cpp | 2 +- src/game/server/gamecontext.cpp | 130 ++- src/game/server/gamecontext.h | 9 +- src/game/server/player.cpp | 14 + src/game/server/player.h | 7 + src/teeuniverses/components/localization.cpp | 965 +++++++++++++++++++ src/teeuniverses/components/localization.h | 173 ++++ src/teeuniverses/system/string.h | 282 ++++++ src/teeuniverses/tl/allocator.h | 34 + src/teeuniverses/tl/hashtable.h | 210 ++++ 40 files changed, 3103 insertions(+), 42 deletions(-) create mode 100644 maps/ctf1.map create mode 100644 maps/ctf2.map create mode 100644 maps/ctf3.map create mode 100644 maps/ctf4.map create mode 100644 maps/ctf5.map create mode 100644 maps/ctf6.map create mode 100644 maps/ctf7.map create mode 100644 maps/dd1.map create mode 100644 maps/dm2.map create mode 100644 maps/dm6.map create mode 100644 maps/dm7.map create mode 100644 maps/dm8.map create mode 100644 maps/dm9.map create mode 100644 server_lang/cn.json create mode 100644 server_lang/index.json create mode 100644 src/engine/external/json-parser/VERSION create mode 100644 src/engine/external/json-parser/json.c create mode 100644 src/engine/external/json-parser/json.h create mode 100644 src/teeuniverses/components/localization.cpp create mode 100644 src/teeuniverses/components/localization.h create mode 100644 src/teeuniverses/system/string.h create mode 100644 src/teeuniverses/tl/allocator.h create mode 100644 src/teeuniverses/tl/hashtable.h diff --git a/README.md b/README.md index 674e01562f..646a12bb19 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 e0d1bdea69..628ef2d2dd 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,7 +221,8 @@ function build(settings) end -- build the small libraries - + json = Compile(settings, "src/engine/external/json-parser/json.c") + -- build game components engine_settings = settings:Copy() server_settings = engine_settings:Copy() @@ -188,10 +232,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) @@ -203,7 +270,7 @@ function build(settings) -- build server server_exe = Link(server_settings, "teeworlds_srv", engine, server, - game_shared, game_server, zlib, server_link_other) + game_shared, game_server, zlib, server_link_other, teeuniverses, json) serverlaunch = {} if platform == "macosx" then @@ -211,7 +278,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 0000000000000000000000000000000000000000..acf9835a56d25ae09257d5413839efe9a6fb70b5 GIT binary patch literal 4664 zcmd5v0(n6Sck`l5dF;Uj+ zOTy5C$U3seSYp2SCf}FGufD(M_4@wvJumOiz4x5+KIhzf&)nCGrn;#*EdqfM<3J#A z^aunUnDYW`1&{%Y#w}p-Ly%yk0e~aqpa9SSkOS9DKh{vk2EYgaZ5RN^fx-+R0bl^& z2oMQS1@H|(0XR7Vqyl^d-~`DG0D=KZ0KNk7fTSt_)&Ri(wE)Z@xiP?PfGz+iTmX9k zlmU(cz&upIKM4Fo0bGHfBPcE(?iuWn1=wNzaJw+XU4Q_v0XRbBD*j&p^><%TDi9h3 z5BPqc!vFwldXN_g@)vgK1MM`M?Vwg@1H*SaYNH(*0N?<&^4$(+GHdGZIxzjt4fqd& z05;?X9xNN?gE9XDEdUy6Hu2z{L+<~_Lv7%}JmC2M5s!8Q57r5L>z~w#-o#`6hxyx3;ZGAT|(~91&!bX1{fFC1!X-4!flxOC6ae)w4g&!CzlZ_j zti5x8uLJ5g)&X<lmEVz~hR)_Xe4)WSw z+H5$h1zHUCNrV?-zo`oarm_k(ykdJMq-(5R`}GmC+>*?ZZbCeBzJfu6nplj6e$}Oi zcaFC+VV~x529oP_8voplzRLdMKv-Jb7RG6BDmTk;Ny+|0Ny8;Zccc*WZiQc2My$Px z2X$%6&8lmQ77OFfZ>#u#p=sn=O&l(6G0P7dZlvn%A3ort8CD%={N)rEB43mhr&OLd6(PLeZ&cD`kFmvV4|{k?cI2bN?^h@Vt!1{lyQ5K1T+>PWQ!^ z7OL7FTx5chFx045)wU|AkC2WuRo;a~Wsgwg`(4AVwQd?!gaxtxuC<_8aqXj#>zu3s zH{&V3)nRY0^rLegS)$*tAKdBxeymLchldeU_j2nzr#1~5pMq0 z%E8avY8FOL{YBaFjbz@jmB^l+aqR8uK3y#%S)$zC@( w5VzsB%Dc0WX8U@OYi}I z>`8wknl%*c^nIP-c6H8xzLRCzT~Avj-O!AN-1Itk2|OI?<(ili49M2)&1GBIw9IAe zbKNzJAJHEkK;Gp?w9+H`kPEsa0~)*>>EvvhmSo>iHl6NU-}-0Grg!*DH1!SE zUp`rRa8<67t_G{`fzJ{vY`!JF^HP$@cqE9)iQlUfl&=&vTNH3@sdUm0Q>)4KG$tss zD0FPT`e~0OX9`2%qW@+3*J6Fc#bLNvA*sHn_c1Gfvbv7{_2Oz`ZGq}F5#6_jx;oEQ zgE_s`4~g&QHLnZ_^gi*im}06&eBsHIbCsd*Q4y^ac9>!DdtJ9;G>buwhWa( z(KJ7&8kocLn&(xY&$Z-ItD5Ph)y@(MM=R0WFD#y7Xg1i`H&OgwQ>5Em@lUol~OM-#hyHcvC_C_3NJ-ubff^?nCVIecOQS6rBaYN zXmrJ>!};_dPsZ=FK0d^D4W(WndNENmaJE1r;jRY5Hf)=W|ElGYlP~4EEq6(&|37Ye$?)eH11VAVVXC!#O=+{%*`5o7MIH<17c0H zip^&vP5H<9VsVxsV-GL0`LP|iv7#4}A_gTzUY!2T8*Vg91c6*!z z`M7FUkq+rr0}qa5-J&JrWaX~g`5?I8_VwdObTQ~A+POw|KXwZfYG%o_&fsrNQ9Y&H z5gZn0BPPkuaPE}$+1Hl*XWg#cb|u#LtAj`r5y@3{sq%YIxX`H<%{3cI4fK}}XtXI{ z+Z3FJ1x)fqCc!Z#)EQREFo(+97Ud_?U-+crsERT|Oj70AY4pqWcwH{Mk2ha(d6rkI z`oai=lq&6^teG$j$)k90}n+!bQnRFrUN;-{7nIo>T-+$s7qD*IOn z9YZvsi^9|LgK=JdQMyGcit`r@rTz$?^YG^NWFIX_vGVZ1SW%^)2i?DJ(d<%Zvi!Te z)o06NnF1Fg^R2RG3((x*0{XLQj|A@45Wa=Xst_2svk#Tgl6hST87^OOdM&+giw4L7 zBwy#OUaIP{3i-FUO!6!)QY63n#k6)FYpho zD4V38i2L@O#LE=N)qBJKcH;2loOSGbp+Op=)$AWK;jJF_D@o5?(_8fm&W)EJzd5f} z+w{;auaQe>C0=>#f^4ZShQ!+PP6HduFCTGwD!Ha0wx-+SVB#FdFm-2vzgZAdXSC?W zrbUzEZ%( zqxUr3^Wi|bQf9=9jKmj-*}6F+v%(;1xN#o;)KLew$qBb+TEsV@Om~tnjj6F#%cO5o dmR`n}#g~YigB|`U*}O@Gz+I#X<-^ZT^Dk%6LYx2q literal 0 HcmV?d00001 diff --git a/maps/ctf2.map b/maps/ctf2.map new file mode 100644 index 0000000000000000000000000000000000000000..1e3bd323c3a502f0bdfaf8b9538100b87bf705cc GIT binary patch literal 25626 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 literal 0 HcmV?d00001 diff --git a/maps/ctf4.map b/maps/ctf4.map new file mode 100644 index 0000000000000000000000000000000000000000..9e04fad5ae2fde090d7313abd47a5670b2d3ffb6 GIT binary patch literal 12030 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 A*#H0l literal 0 HcmV?d00001 diff --git a/maps/ctf5.map b/maps/ctf5.map new file mode 100644 index 0000000000000000000000000000000000000000..f2e64a9194d41ff56e5468e081ec5a09d6e0df50 GIT binary patch literal 12108 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

n%DM~vYEY}_aBHN z%mSDy;YTH~I%dZDE{0-tEz`{XV0T8o7>+cKw(95+4!s=m&)XAgw{^p4889yP1Tke7A0TlncA68aY|lHhM)dtIlN}~md|&Gpz#sB}by_^| zO1@jJXyWXm4>c<;=u1->rgx)J!@fg=r>@+0o=9VsbQ$y(Q5@eb$3c1@bd@;Baa?fp zwclac%3-p1 z$E7%flNuIeii_MKQxrP<1RY(Z*#A&I8`z) z*_U*SX=yZP?g!>s`kU&RPZ-*83Rov+?FUxQewyM`G|p@;-#Q<#h0hO$f*c~bMu%KL zBd3S&+PyF)u>7NQW?0$osj&^q$p5Uf&EFe7AXVPYwRArXc%}B@qvUY7qQaw@VHx{k z-ax<1<`TCda|P-nRV0*{U++h|AIa4+^dtyz{KW;y*n$nZ1J+?hJ7Hr2(+J0~lAWxv S4fEf2_$*{Gw70(t#r!W1Yy3R` literal 0 HcmV?d00001 diff --git a/maps/ctf6.map b/maps/ctf6.map new file mode 100644 index 0000000000000000000000000000000000000000..5bc1111293116e93a031ab7e936a8a083e0e0c0f GIT binary patch literal 26927 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 I5xDi^AGAjPG5`Po literal 0 HcmV?d00001 diff --git a/maps/ctf7.map b/maps/ctf7.map new file mode 100644 index 0000000000000000000000000000000000000000..e5211aeafe72f2e32be1e2ba991e8ecfb931d1ea GIT binary patch literal 5511 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 literal 0 HcmV?d00001 diff --git a/maps/dm2.map b/maps/dm2.map new file mode 100644 index 0000000000000000000000000000000000000000..c279f7ee74fe0a2c4bfe87cdd8b90ba185723c31 GIT binary patch literal 8671 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 literal 0 HcmV?d00001 diff --git a/maps/dm8.map b/maps/dm8.map new file mode 100644 index 0000000000000000000000000000000000000000..0aa29109aa8ad43d084dfa53843453bd2ee8fc98 GIT binary patch literal 40637 zcmeFZ2{e`M_dk3}s6?4E4^cE2QiMz?r6NNr^IS+|o~I%qV-Fe1kOpK-k(oq>Oi9LM z$Z&9Q$Z&8t=YL%X2T%+!3%~~gxDF5t@D*Se z2tp3P9Uuo_9AG~PMh74QpdMfw2uKpZ9v~fH5P+2&fw%$?1W*aE3?RH6_y>3a&<#Kb zDy9f<51<%e7JzRj@DC6R@D*SeNQxYQJ3tP=IKcj0z&}6)Ks~@VO5h*B9v~fH5P+2m z_y-6As03IB5Z(>^1H1s}2B6ym`~%zrCsz{sG(pasb8w_R|9Y z01*K70NX&LNCMacqyr2Bu+jtn06_qi0LuWvdx3v|7XaM=bPT{hz&(IsfLQ=OM&KVH z7T_zuE+*g~z#Sk5U>smSGw=@(0ZMK zAkG5>0el9aV@Dt~0Ad090QPer5M}^D0G|QqI6)@@1(-#g0FVPH1o^B6_zLg?U=e^4 z+c)1|iw_K6!u=ipa32h3!u_59a32h9!hM(p&^*8D z3_cR_a_7Gk1h}_l0}o7H1P;vUf1%xhH@9x$!8{VfBiY1*`GYzBH-FnU@xXLNXuE&o zk#6F_z7AvkHy+t09++kjurK`^k9-pkj(=kOZQsCyemIHb?byJB?RNkGUjHr5ott0}tjAOt(bw_H5w6 zIL`nO)32#F@Ss01Z6n~E@o)8`*~C)?95|Q!8;^DqPm4%@beniaMDpl2@L*od0f_0J zdpGec0gsq|#;}10{a67I(+-TAcn*L^On+h8#B(K*$GnN>1$Zz&|JHsin|MA%`eWU| zgMR#pp_bd_Mq)$*0f;9`rK^KurCPZQ_jp9yr$iYhI3T;6XpL0L0W!cmogW zV*rS$pU5WOB9Xikn|N5jgM0mdYj@ENJXk+6aEO8Njvz61J{7G)$!MyAxvVKyVcuYj{q&M(jURa5& zpUfs6JCVFon|PdnM@(L1H}QA?kC=7r^d{aBB6(*v@!+!tV#d4NCY~^n{?2aTK|kU| z^3HAG!S<5|Ja}F2`+s+ZH|00+WQpXR-^7yxJYvStg-yJ3MDi|f;K96{2RvfVRSKJU zmjREM{-U^n2lJ>-B=6D&9*k25@QCTxN*j34pAp~@v+q&f#4`sxV)i{Mn|O9a`cvJ+ zb0d;>c>@pT#Ru?+>7Qzwc=rL1n0|I;0}uM~2Rvfh;p!${2;dRZU#@N9g%inB-^7as zJeZ%q8?Wm!cvE8&FP2Dunj3h~Pa=^#tqnYw&m_Pjrv0=x@m>+h)7ivJ2RvfhU3U{N zlSrQ41|H1IYa)62n|N;kkC^^quz?5jm`5bfa03s6)mps6e0KBb# z!`ls%AYcsZ+>N}GfJd^4hX#t^^&kPTo|}0^z@H4@ZQI0yb~)ET$P@Ssx$6b;VEst{ zhQ|w(AmF^V*A zgAFNgZ#@q1kx*cpqyVhP0k7cIJ9z%^&vk+CxM4{I_yRzv2fTs;E+AJ31bm0K>%Z7o zkD~_Ytz&aO;5%eWB5Vu;y>)E10s9@*CL2IRXrJ|VfqnE>9o9du)dkiG{Dc7Xx&O=u z5dDV@`2C-3zRJYGy=iZ4SVsb zoH1=xP6=!e+OB2@+Nu1!npc{GJ9v zC~p%5zumFKniYl!eFl+{59M>sIBxYjK)2K>ZYwZH+g<+BjPX*s0$^hUR*0Sao+JGD! z01c4C@}E(x%HZ$VHp*Av3lUJh0p5E2@VhtsXRU11Uzbg6vcqwFwQV-rd5il!mNwd7 zx0B7pK=8{F{Qy`5@vqHA@blm4%YlQ;D_&Hb7nm z7QfB<@VWc7L;q6_x)fp`Sll=3%K^E=^s4*-@W%zf3UCg{t=8I+0emM1paOvQO}JO% zhjOwF@@L0Z{sjk8O90`5AX_PcBb#Q zK9oNMNCk3b@PK^X4wS?79j;gFe%I|B1~OMwB3Jb)M||t#6nA zTh0yqt}21`M)3EzegY0K>+;kSzxxg}2j!c`k=)vNhvTXV;4=W+M+*MU_(FpJlHfgY46hHpp6!Nqi~jDg07a2u^@!u;=WeyfXxe(A0OY3sP6l%ug$qC-_j_6xB(k|t#E*O4C>b(5k>sf z{#!pCG#r%gq&c;w`TGLpn~UID{DI%e5qTrO?HmFdYN&4&Mv1_#Eo+cV1(FTr*O}=N zHN(I4wZVEo@axTvh=H5iwUPlghfv;HA@_E1Re_3is)MW74Od@Qm20FlY*rl>Ni*@ zudDvLLiy&hoorQKSAY`H2nYOHdB4`Lm^7FXXM29z*9LdV>-3&ND9!w~FS|CrRIA`d<39`3--s13?yiEl zW__Ni12;p!cPKAdYZs_L4(1b(QvCV^v(d)-=MlhJ|Ge=EBY@vGmqIYl16PFNSn@mm zFJLpgu8%USB%HyZlJ4^A85DljlKS{d{&C;$d1>U+>X{Adv~5`T;dijy{3`4Ae+j^c zKl?Y31h8NFvw!=ue}m)V-}Z0o`;b5TpVj&5&;Dm^{{OT8A*^42_CKrr;m`i(_kIS> ztLx?e&HWGD@8Q9IZGLUf<_$JtdTXDj!F6%%v;JDJP=9k_TiZ8^8G_#oCjQ>Pb*;&Z zZzB+<;F;GtPj}5`r6oA?f@faq@=5UF@9bXyn=~k=Yyr>B3V!Q@ml*{4_EJ1vG5fb1 z+K2j4nRq;I!*fFD=kM(QT(fgt7@SY-f7`JE%Io^`M}N!L?Mwha@Vp9hhXJq!%D@!i zckZU(YO?luT_0XImdG`~HnDho=+58vF9Tc9FWlnmu>XEOwr(HV2+mpgl{YgeSSxWG z&ifzMh2T2x@Y@ap405e>4J`k%1OL8q{nj~c?0?AD^?9%UmOorq1~*kcE&kH)*&w%( zSokI15BkG;39kLEZ@ED(qxsu@<_7uGAHU;l+aUL`{ar75khgW$RN&0GvA}%(Z#~~W zFSdH_fX}z>L!|%p`L-?fsy=+aZ5ArMF-$`Io1bsfu08KURN(O1r@lfd(|!2iqxY%d&W&ld2Z5qypZ2GqcFk9B#|n%oog1^Tt~HPrtK|_$^ImCnp8WG(X?0xud9SpZhd=L? zR`c-Zz0&IW z&vfDMp5Yqw20$77fNHb+9FSA2{T>hMLItRw58xcDpxmIq|4A`K`NAc>+Jm+A7x=-u zu}cW@>%VUaHq_NLH7O~6x^M&B`b>VqIkv7=vFV^8_`4nTtxV3b6S|Ax5v}wM_cen8 zH22U%1~5h#KJl+es^L0+o!0yDdzBg^YVwE!TnbyxTHjDnt)%fLjl8#WzwceM*W1Hr zNNK5!w({Vc>ZDqcZBEGPiIq9~-R?5697$M9kH#Wo55|=V*&U@bFi>~(Sq}<*J0%i* zIbpKg?UjP`WZC7TnNF7;46dXdQhgyXH=NbBtX_v{m%NhJl$#{aj%%43f9~h^V*aMX z=%nlQA(;TD1k=9z5!+EuUex72mDj@!IOi36O7Z^43HY){>BmCbmxr0#(eiS;6!TRdzGdyniUr&W8?}pO*rOgFglkjfxy>FP zAp5Zp`lAQAEK%ts)jeMBrHA#sN&E7`U+g!Ho>L3^Ke#J~n~2A_sE3rvyZ2V?-j27i zv=Vt!*r9RZ3%9a`us4~ZkikmzPmYfzwwaRnu#pShG+R9MzG=r@i8^>GoxLsVOXWHB zn-AqlWXPT^>>kh)SNC+=;YurW@yF7x!y^1gd%?Uv>n&o@>j~M%8IocgB zji+_ZP77xX8O@!4K#5Y*eR)=uxr1>E<0Z&2?lz?7qF!o6yL{!Sigi&r+dU5p&Tl;? zv#PpRXoG3f{HlP5!RBYzviBg*UIX!d#aAq#t@71-9-(BtP09_*_ZX#+d$=F{Ms6vI-&HAum!CRc|(R*;@&fTIcrG7iKw#jf$2HlbF{UDvH z{5HAv&K@aOJ4;UsjfB*5fm#cy%#`W77lja6lMEkZ=BEsPipIum1Yx)QzD zX7SlU{x?gi8cOpbuccoIA)4;1=P=^>g)sS_dg9M3m7}kelX&0s9`$CbR6LE3Z#=R4 zbrW5P&_m;%3h^tUdovR|NwW<-4l7c~IGN5RYkl<2xb$53l(gJ?o;cugfa3jyC-T@E~>>x*DzCfDt(^0YyNccl*nhDU4p0h zeO9(7ME4!;5o(>1skW9$kQAijqv50GJHXC8i#}lSHq2v~<t z#dUIDFFs4AXk_rrJb0MrhyD2hc~|Mrh39AP29u<1`I%gWSawh9$Ey!ctCkztmMBi; zKh=Hh?d<)z>|3V^6P9n7kwPqKOeoyW+#-mg`Sif)8Q(Tl*V6MSWlj4U8VW;^)P@}? z<=8?_UMzD^KkB~v^R{xF`{%2Y+uZVkyZH|!OboidG{`jw?mJ|}pXilk_~g-eOQTSv zZT*Qxp(r~gBr}iACF9dMpS!xc&+vBadZtWg{tQ^8B5zoC-;bK|JiHZE>1O7 z*y`8U+mSrwuuw~5o2N6i--WaqwT)#P3+WU8hI_b@hn8*GP39rlvBm=ZtTSRr3xNS{3MknzrX#v?D~uXSC{pDg7@A9ZN{Cu!Zm!+Kwo zCST_z@MQHl2lnN8TY8%YAh)l$v3#>IOrT^nrmVI=vIdGFxpgXn_VAyxo@j9AKcSu? zb9QJvN9f@-KjG}Ba}VdZ8|qRTR2iP}$V%U1NMm?KHg$VJZ2pL(=c6=dO0i14-owu4 zri%N{%K91j^&Kd$t5k4L_pyvOO~_z1CaKo>C?09kNP3Q9E+s$*buiN-h2rPCIo^zf z)PR8E_y{_Rl$msna#Pjk&!y$QOMfKQyDqYjo3Yp3H5J%;hU z#j@kW#zh^&H|AX)8e0rSqr|FdV_VD<*+qPBvDM|~NyYJW3=}CM)_y)%l2~18)Rg|$z?7>h z?Si)SUMiG*Y*P;7y%U*llJ&Wh-D>AN%fER$Z6lW|u0YPeozR>22sXAvc4&)5nP&&x zifPJi7}Vmmo#0;7x}U!goznK?%gCO{1j}GLdWsa1432U?RnccAvZeRbD|p-7m<>28 zOy|Q|NCp?f`gVJBd&{_reLzxWPi&e0UXktSs+ErHK9V2ltMg=d=UlRoz)2p9i3lEj zhQu=(o^3PUZRZ~|2=5I;tN8RuZ!y)JU-0{y1RFvRALaq8Z0;z*v%{tKC-s+p?BMsHBDy!Q+HEZQsE;|DJp zigPLX@i|i9a(mn3}P6v%p2nE@7|Mwwv{`gul-g5ed(sHoF3Z` z$qO^b&8=enRGj*HG-tenNn^9VhN`r1CVZ&t=Ozy(^Rjug;`x_5UUw?7FWqnVV)xB= zQst$oA7w*I{Ol&I@5)F`oUFb#9B|W}a$w=HMQO;-B(2dsQ7jg5gKmEfo5o(vJ$E{I zyI_@vPt{g)cV|*_mcgseONPomWjh+fP@&t9+mNIQdytDA@)maYJ<|93iO*}t-r_~J zMQb$V@;k~-2v?9Uc42t5MX!c)e;FL%E09I)?{4VZC0wpMaUn%P<;ago@}8jMg7XK= zJ?6Lv)t#|6&$rAkYT8ov;14ZavA(+y5$f^bglVUIu2`ec?t=FTjy98~`H{)_@2b07 zdxY<1jS4V)z2ssi_tix}A>^Iu$m{N>NYjNw=9IAk)=I5f^fsYIDqjz!u-V|J9-vq$ zruKZ5o^oEOWBTbDuj7LW^j7fpqK;bVAbls^ByB2Ig=L-bN3qgoSyO7~Cq2@6-uC+K zA~R0`r+iNS6suuVo2H#ZEENtbWh2gvF?gppXGbzA%24phjii*fpM9&TfpcCk>7cf= zl>0F2;!04gm%u`vU(;f@sfAmJhq4_X3oTY0nKo%|0V{mzqZ~* zx6>vj^wnO54y192&0ogsL#$Rs@1LB^j||p%5`SRWZsMJ1a9!&K5jAGEWw)0v9*d-2 z`>^sNp2Z@hYH_>BzQ)YQEICT2*#e68p)QC~Jf+Ibwr(~JH)5^eO(iAC_8})F$)r`{K*>Ox)~IJGnS12vCdyQw zwzT}Ls?MFB!G$y!S}_*xybsEag%8@fcN-^EcnPpj;^b5IMs*I;V9nyi8iYb^LVRNF zMBAUvrL`JvX_xlNI(v(!grdDDiOanGnf+^#w4OtT6UT$8w2ov9-KxThiYea=xyEL3 z8{wC7_iOn3X?kUnw2$=9D@fYGAb&$t}s_9_>$BQolmWldMl4f+4HtIEa4+O!qETJH9+ zNhlI5^ZUZsd*%Y_lImjNvfeez(x^wgJfF~q_Tr^J@(HPWTIF@1Cx(M};3ZW(LrV(L z*`~oeusD^)BC3zL%rjR?LjnVSIw?KC$AwD@Q4cE*p>bCjC~g{aH75MXIp{@xq=d}H z_(K!Dp_W;xne7nzL#q~rcHPqAX;jKjCz`bOeT@lU%vEu>&8CGOzVK~ea4kLPC7*Ya z?TvXq^0t)zLUunY(es7~(_m(A?<1uF>P3kd&^q|Kt5E`B)yX zi#IlOdG~p~OXh)F0$x~1#ftKaInY;i?O;^B<)e8F3-^yke3iBcd7W$TgbV!4trWFC?#pbzlNqf6nKbsB z_O;KHX?kzElwDiMIv1W(cwIKK0M(%O-24lDiYQ00_g|Q*Dv>4(e#_O7A{B53ZD?vT z27gLKv8B*C_DEx$AoyV?ndp`p@I~MP-(hf!-Cn#^0DNnzKXQTpfpTq=@&(01KzvMs zwEdU3=~8_?u)F8k#q+?|yPt@a)UWTAAnMZk$gz=Uu=t zIXR(IEH#@?S?^mB+H1&;>lTlVdA>rkHCMXka=5mPGb8Wo#~<0@~p@1li{E?@lkk(P$5^ROfNU2M#--GL(B z`Pkssfi8|lSHj#LSQ{~Cx7>Egd~=TSE*HJe%aixD=>~^UHA-&>-u25}DA;NImJ089 z3!V0840Vj*r|W+A-9;)A>ZfN!8GfR!cbnNh$CcbeatlQ4j!wW{w-r&nLBC(4L9gn~ z&S*}j9#h)!efS$ViHQ2{-o$!J4({tKU*?x{xL!dcCFz}?kPXs`v;YLbky0=ar9W~(>kk) z)Rx1ipU~xqq}ANa(JbWsFsSWL)0L$lrx{kn7u#{V^!;)-ZxQ;tCtFLP{WG<&A~mt* zv*RuNO(JFHC%-0~FnY}CbXLqJsQ7fYq`H$I!;$2R+jZ+s8Na;u#z^om=fL>mUBw+d z#t->PT;F%$1w9?5=M4-v8yubz{V|>@1V4<$d;>WcTsAA`Hb0WR1^&afsMaJ|fENg>w>{#^O^0(syw( zSK`~78WBHyMuPIdkKEgjfBCM9FrHKkagGgtg2mvurIqggKzi@u)S{~K(&gMW>@!j- zuDGkqkCYL2{HV&k{yU_>;J>Z7i`c*I`P`j(Jayl7vlCZHS~#U|Wgm^+{R}BZYiuob z(aY(qpyy&)cwD$)#kVp8=E{VKL(o=BV9!cf&J*0^R)l1;qmFazlhS1rUf)A;S7K}I zE>72zVH|IK$?M6RNd@t^Ui*;nei;&yvy4<4pF32RIkulVeRS!_%yax!R1D(@ef-WO z+2!;jk_OWL(#WMTe1OxeQ zUnD;S-VN-=rNtSWXgDsifLo@4(>{xwU5_We;}wi%ofr@rQ}zqau^;2y>>qtB@Z;Ta z;r!{_I<`JxZg-y6T;`K?B{o~bTEixW=^5v(?98mZp)U=&iFSwNzud_U} zy+DnO9`epx;Gsv;vGV^)lb-0z(Ulb}f1X)+gE5A`qSocA?39iWyQ z`ipg$sxcx>*G@U}Cr10h?P&B6I*$v6L(ADx9C{wJQooM2o<8_fZvOU#D+0CUPJN1V(ixTEw;YZ0r>ct|fA9D!MdvS{QO=6x z%L9*cKZU26=0$#x>K&Tw%%F#v$M3TG!IXuLaXs14Hc19yhD$!HZ=~ z06%G>+elR-CTUL$LMuta#i{JJ&}c}@dN4P zk_G1~eMeSi{PR_e{Ci|r$n|#{cFLzsKZr-wwi@gE>oK^EQyjQI;?OMCAroQgvD_XQ zuahQ!?}1*;TUR+on1;~zF~ckG+2VE7Kb@I%8Mx?jP)II8G{{H&j7B4yc`@#K#u=6+ zGL&m!qSbaGjdrzxK7Aj}vDC-<^Wqr>x+%`vU3RwlQiGm#lWsD7F#yXH3EPRFx|C|k_w$qUk)&>bq@Nq3=CIvBRN z>h8jFg)u&DKReKuf9hRncZ`8@rfG7{XJn^o@-|xSk(qRxDKD+pvA3>fZfSEd!#w2d z4|K`Jo~MB3Ll7~;>LCv7J2jb}qOR3^I%RYxQ@g5MC4T9ptrSOp?W2;8`n2(nvU10> zf_$W9EwEX}&m{}y=c)GWPCeNx5eEAS`hAS;tFi<;wyqz^)uW&HdOb^E=N&LemHFn} ziOv|_J;R_IW{P_~Oz4-}8Y6Dax3n!Ub?IjXcIu1rE}=k2i0+NG^vG#CBOi65-2pf9 z1z}{TiwySJvW0I>4>Sn7*7SoI^&@&Wm4s(LiFG6`dWi2{99$UnC{YfPX;wy~f_;)# zH0Cjlm}`LoF)q0yml4hNM+OWG5#N^I^lCexjpOw((p%(amVDZ{fy3}@|op;PY1*oxRy^S3(R5cGPzBk*?W!F z#F!aHObRlQ+dbD@QfzeXRbk*~KNEkykP#-duwQYet~B{I${=TZdFie8a)OkKEM{JR zUds1C*2fV~^k3~=G{*L2?P83rkuT9{@y|UtI89w5wpWP_@rA-E%V1Bl(5 z6PQUmt8+VL24mcgKY-2jh*J&U<5~9I%RY$F+ojdZUK{e+eR};c->k04JwODtu+Xc* zMk*#VWfm+-pe+$O-R?J%@vw5cE=Q109Jl7!y~h|wndE&Um)-9+8J;nxgM&5iJ(G0k zn0VJp?Bnk3-}O^#gZG~s!Tn`3rMOKY;ewPrc4gkl>K!%z*Oe$yZWvzl)?-rDc;~Y3 zdtUXTEpPjCog2frRh8!)e21l-$4*>}sxP*3IP{mqi9U!!`LW%lJoo6U(QMQls)B2&1?;Q_eB`3vzsZFFtzzuCr!UWw@$`4tJS6`XBc6*^uexRg7h6D|?DGJu4d7LM*fXX%2F&=e({AqQqm{>M< zowGQ)HAJ)HLbqJJ5Jd|N-+6aqL|v)6+fV1z^B>)1T#8Ac)#^KnGdI#AZsB?9Wr5cHB=5c55-Z>(Tm+ zZn<;HNe{gCDX)aHL1zp5VqYnBAVU)C)MjxeBR{VgJ?QCWB=@BDJlMPh`&z? zyn&_1Su}<}%42dn&LMTgU$VSx@)TSihQTTcoITlk`7G~XC*9|}uvrxP6${4gs#AJu zpOOCz8jCx(G<-y+ukDoq(}d=HeM!d23^B%2g-b<&x!C5g=D0a9ix5 zoZ?s1Gn=E_0bqvd{4G_WDq&{;gV2wvt%W=P%C|vfi{)%t}d9GqtZ;b!81`I=SoV=qQ``szDpW|wR3kIul3B^h=o>N}=E;1jj+ zL*Al;QLwK@^%HF72@dt#(s{DU?v|~8=gCl;qsRRCfv1wtn^xen&j-nP)LVh7B@GFvQ+=@QEm{wi+et`1h%V4h^Auu2jK2K^NWM z#G2y=x%^=CSs!A?O1@*hXY(qwx3IZYQNM*BQQ=%(Hzq3g7-O_IHVg1V~sOxRJ_Zhx2Z6c z;rT=4+sRH?+wL83sX>3^kyh9x7k^@UfG`PZ!?=(+B}TE*%FmFiISIMZZg3LzB0B zXR6GGgCxDq01Ogrsi~joh}}SEe@x{rI7mDnm)m;U&wD6Sexf@RjZP-JL74cB#o$zS z26@7C#9_L7SkdS{Oiyi$1?qVTRq!-hTKh+#QfOIMm`Z@D?u|6C%uy2Ys_8BG_iX)F zq~?Hu#@@PuwoXjRf#Y(LKfC{mft5}t1#{eYOh&I^yzxrNu4gVGRppsX7FgDzGG=zM zv*?5hl4P3X5jdH3(nWgY%wV6X=Ut-Ktg&cRKLT9?(~svs-6EIo@Byj{ak%a|OD zY#MOZgw-v2G~b`Mn-vquNl=J=4mJq+HdJ6w-OIqQl)$eQYF`l8f+S3B)HqQbI&cyt zswTIusnqi=p(VF~d8DMQj9OSBv?cgyXQifVY>-c#X?9T5OwZ>uZPP&OK^F%UVR~^S zi?Qv*+<#VL^1w41-K@82U>7(-enW;lX-`JEy3V+g+7!ZbUMD@DL#xYVhrZgr_|x$# zBFCY#5%1%zZrE;Wr{vReMtUE=$H>A7$~?@_Qyfj{A!2ub``P#`lg9n9gD6`koit34 z(6dPYjD5nuV!G$ZEUro{TH5DE!a%X8eXe0)qg`&Ix0o(ch$`vo>!CMIY|nch?<3=E zO35=J%*6~f!srRRY8m$UeerVfUXqgGus>T>fJz`kTFQ#mmC@a~uNPkz=mrtgKp9 zm(}Ai_jSTQ&Eh&mUd3u8VVbJSXKuiLSCTl>Gvul(iI$4f$b<*5w?klV@UR-0el+wW zs%B>4(vMfZ?3_pmTW z>!&vblgudie_ZM|V_hzc&JZ+B@FN?H@Mo;YY!QD+7_}hL7?5Zc<7_c4{uTC7$tTo&8JB0b6$!7QrA)z z^5Ycfx`KhbRKtr>pADItC1nQs(19KC&sW%obuyTe?G{*>AKM(Gx)?ug+w*pbr{ol7 zqU?g;{=Flm;Ixx{C&SAMpLzb0#iL1uua%A5*G5b4*}N`&@KUC~I5cL_^AY2H$ryAK zHylUcag<$7ua)x7Xx=X4l6X3mOK{A+v`%vP22ObsbuNX<9TRotKP12N3R8t-nT&$n zq4AQ}Bz z-KUC^em^vN^%;MB@6hNBVTvJ%1xq^m7A^ZLI_jG6sq=(qSeW-QOQrK}%^Z61yl;fE zJe><3!ye3Ej-Ar8uM8>RVjd7Sl0N`DYLU0%zzxincXQeYc@*sm^5)VAH6x$0TEJqC zC<)LB(&u*{QEDUX>S&8=ddm{7>hfOWYDynF3nMKifrqeOjv3wkk=#u}y{0g#{B4b4 zb)98+Y|qK;tuDAyCu-=5gCr*Y`F9MZl?G?LZ2Z)HU&vP^0Rtry6I{3V0iQb+OH?bt z(VpvIslk?_C>Z>aLQ3F5I7HEaXO!cmS;d#RBlm?CUncCpE{BcIm|;BJ1TyFq9wsBd zEJCBZz$29sU3A%U@wMVRlKP&~39$N_oP@1bxI8jYvK|vI&p>$ac{2=5u^zo@y2D_K zO|9Gd!yR_5Z^*Z;SBrKZwY?F%Ao4RvN0Z=8CG4gr*sSd~+UvhdvJ6~cSk`Q@{o)|l z_NaOW@ftgi+O4p6FL{G)_#~E!!)&a5>Ta%_T3L_}xw*zz)LV4XySy0nTW(Ffk6pmn z@H6!UTc@AN=GhvpiwWj1ePXoN343t*wk)l7zt^53mFDpD&vE7L*Bz!DGbKyrCAeaB z{WIxpU~C+Hci)oj4c51U9YepnJ--`V2`%V0g`JWJB)NTpcC_YrUy>kffg`d>y$rc% z2G63mF(ZMJy_2)>p@xA|*r=1y1lG3q>*82%#!6CS=t!DZUYpp+{EfvGEu@EdSwRCh zA5K3(5wgGnH@&@4Y5r4fWyTuR{by#u)r{4>^~n%sR~&LeRl8&keLl2n4Bp_jWE~%` z`04hgamF!=TKb3T;Hbzm!W0RgFqU}qcn(E`j`&Ju#hMe&{}!-0=5S+dRx3r5>O!Nt zfUQCn@-z<(&))BgJ>~=Z1e<#VX3Tcozv>LNay~ zb8Diswo+QpbtR^w`pL*ka2V{Vl zhCmn_y#X!W1#3C}(S3Tkr*WR}sQZu@m|L2@;mF<3GRG(2x5&VFskX(8jXiIdDqvr_ zQmD=6RKT&6696q#lyla4D%o+AB5wyvPFCC|D3dbTilYw|tJpPpxcJtsoDO~hRRx@n zHi3sJ0s0O_NZgEq04A%jfN+X7b?(4>2_Tm=_M#m1E5#o5E8c|qbMT2wFSk{JbNdSY zPm2QQP$(tkJR^W~8yuiT7vjlXLX?Z;;nZgb)_O<0XH6lGRIgupk%Gk+u)kxpi@MPx zfyas`f9rNYSGvo5A7+QWQUC7odN-t(j4A3f4@TywlD1zD;TfADta(|rtI>omroG0_ zXhI6s=A4gWf;<-cOgaAO462saDZs>&iTt?paMVnh8ZGnJ7btbJ&$>q8nfg`bLznpG z>K1!nG3j<+vncDc4HdXeh|a1%+{LyN%@KvorS+1FAM(tGlZ87wn5-6Y*|*L~WRtiQ znp6_znr(2dS;~ERRy&*0$!2m-am@LbbzZJ`zYf5Xtexos-Ej~s?)fc1Rh&dxO zDKs!Q5br2=T(7EJz6);hRz8nBV4QZF6HhlIjGS8_J!5F34xZxEsgqB@3#M4Npomr` zza~8sM^jU$dC!>dF(pC@5eK(f6qek%D%*8i=wRgSo!QhA=4apJygBX`6F2niBNyrA zZ%Gli3nCP#3u?9+vB+{qF(1}43)|~e)c;$rOVh2g7IZI<;QqmBAs(D< z>07ioTAWv6Pe6)P;C*>Au8)cXkJ)r~sJmX5J2Z!1UO^}5^vcZiHf^O$sPd9HjXyHi zE<&Bl;DRemioU7y)+hJrDFM05s4se%D+A+V6CCOvat5xmbzkK>p;zWwUnhcM_-MY} z#bo8#J*b^P=38~EmP}B6sKCD~K=KjtK}?m1gcvy;bC#NuTLI3o_`Fo0q?ZJ{A#?k9 zAKTMYnN0{<8okUYWW;B&63OHU&C|fTq&n9LJty{`wo>r|*8m^Rms#SjY@O4_^|8lV$aC~1ltwXi7xtcuhQ|usb-XFQW<9a z*YCfI*CNOGogGW9b!ZW`DsE)kp5{?wH+gDbOTGy?@7>dLp7lC{?Joj+$5iXnF3<)> zgUT*Q>KJ&&Oq=85qrhp^_i(Tz#{|C-tS;}V#FNvLwu0^RM-01qEy^y}>}tVPos1Ts z>qJH;R8Q=7E%?B&^Puv=>1fSUF6lmThS@p=HiGI%>%=Z!$+(KMu<6FP_zru_?-f*c z)K7FJb9sY(KybQ6=U{t+U$IV69({^FSJR}WVUkS)Rmc`!Tec9Fg7{IYw3V6}MJGbw zcuI!H`puepnd(UTl!=;|vErk!KE1vat!{AxL*~JQYEqWL_o5fCC};Q_nE};b3<>S- z8%phrR>mS92k$eHo=C{>d48G;Yj#|(`9&0JY+|1lLspO8`(4e2W^9r)G};`3S+lm^ z?T)>0PGIGw=UA{fYNKJbGDEiIYv*5}j}GM4Y5K7{9||2C zB@rAtK9d4cZ&k5xV~ z(k`BMH8i<1?;}{h>ljH6M-El|PXBR=hGHjdVwTlTmEMK9OR^?+md>FJxDGlBAm`86 zwUM=6uE&Vqnt^H1*uAsCEstX?pXRAU_Q1t3rl$_gC!?tzWQ0zhv^Q}S$OtqwA&)M| z5zr#98mxcs2R<7r!ThWCo5eG~O~lZi>Ql%K;? z1z$EcL@61)&>VizcP~}w@<=To%unQ9?g}nK^)gEHVjH8b^=g;%CC?l{^>x}lwBzwA zL4SIs(zH)mUFiVqI2^td4T6}s(OI(ASlHAZN<&5gf(o+p=U= z%q6vwCiPBAesRu;g{o@hPnTVk3{rHYLeLD1sRnS)*rqZfQHYsLKpjz22t(R=3_R|{vytUK*}o^CxaFV9&9wG=DdAk6fLoxxFV@iW66ms59J zh433kUiNrRHOWW+iq@p32q~`n?P6=xv|UGTMi30a_|)A5c~Fa)*h8iYBkngpbzU~v z5htigj^?KqH$B$dT+-X)<`J+I;)1vJI0TcF;7#FCjW6h}l01fSi!-i^xnW;s%GRyP zGw=DKq!%}E#<2%Kop>xM4%1d%R#)#9UI$DRX5JfHZgY59Pm?;+R(}8TLszTeD7R4e z(pQ?69@m`*-CGXARNuaXS2N+5m{do1bf$x69ukfrgo#u`g)<7TYdLUTLMb?$R#8H! zt^CY$&_TavvAf=gSQ7uq>Uy9YX5{A6&&~yCb8-REwBVZK)-H4~;-XWD)3%uAmL`!` z$q!AJ3QVA@#x10_i%q;$ytKkf1L6Gj+k(R#rdIBCI2Ryk(HX2S|EleOh`GiC2QcH7 zUa+i8;&6d!Pp%05bhmLh4$a;=m8dg`E5((=#FQPSNU$RJ6KwlBf`v6sJV;-!K0xOY@i-IyZM|&}C|f z2m12dLYGQ=yH{1S`#ko^ryo2f+!I#mK55Kn=f!bOxw@K^5y?l=9(KazCXl7lw!ou$ z)H=NDo@8=G1EKp+F+9fH?wy``a4wh0g@wyeJe6w zTafDKbu3>U2LlAGu7lodz)1I4|F+E6!W2ErQ}JzQDWS zUbIqfEETUi@x#7_YMDgL`+z|re|Fv% zs8?g;Us*|(mPInXENwp7p>>3?mQ*{orwR{av%fpQT(YjNC9Wcu6;MCY9-cJe&j@TQ zf0S#4dR|0zDPI2!>c`-sstF~O28tLVZ6}MmJUG5ymMKE}0Oh&2ZRMpWAC|V^Gw0pq zGcN9HS`0Y}drc`R*dYL2ZTW9Y4yciXEp39RzDVYWOG94jMbFwA)97<^6(zFLeQrH7 z!U|$^CqB})nrFP4;WdW?#F^n>MBVbDSbL`Ek=B!gopUDiu!o?@VGmJPpS{w&^!8cu zW6iU0z0JR9!DfpM=}t0FO)j`3QV%X|<&>>l4y}04xsNQVW~F7$XGGpU7%p(Ri%%+DJ3is8Np5;2FenNi*Sh?FdO8zmD8Dz3 zlk8iehHRCh?EMsDCq;=6Qi(_zvWyagVGxofl!UUi43RZE!(e1&8-|c&#uD>m%wio3 zGyfU?bAIocbKW`kJkRsqx%1rb{k-SA&wU*;p8Fd|Iv%TjJ3uOn`RVwOKqKo9dDqu| z<195E_LuHwr*h`ZWnx-0bK>cFQ+l~<=DjTb3CLYuK+UTa(Z^k*(Q;EDI&oTNMzIRGHk$ z1ClnQ3a-&=g~fSCr2cwmHKegq4ufKi34ufFg`jti=V8DF0G0)=nd%ZO(TE_y`;$h% zZwT1M17`PSv0Njqwpc|fLkk>oCg^b#JAgOUWrse|tnt;+Xl*LuQ$D+m#kcCT*18LS zDmuD{v-%GD6wzR!&W!M5!eN`xzVD11QU^lB!M&KtYz@XDlJ%ku(iepfOkEmma|19+ zIH_eP?rj)F=yMvHgRd7el_odiNnWYOLYia1n@A!BNTtn3XR_V`By76P`2^+WM zYtCTND{!N3y@nr7rSi`xKKeLv;bzf1NzPNJ%y_iD(eN}ejoG@@y?16`MlL)5p&}bT zLoS@J!#t(<7%N*1!Khz4{5=!dVnHdq!V(Mm#OrGN5gq@W>3%Cayq9da!da~GF2!dI zvvNDKE&PFTbv(PrMKhPzain-{VYrve)Wwm0D>kg>464-`H4$xV46YL&))@S>!m=9o zV7DGcY_K}~VA6VjWv$k*&wm+I7PD!1I--o#HIr}7x#oFXpX3gbD27c*t#!M10KdzY zJ+6BKeyb7PK3eUs;m2^1g98)v1iaw$6Oa0P|&E0GN96$?g_7Jg- z6|--a+d+OnhWt>yVAoAH2YdFG(jG0f_d}D{{@u}ckAjDGJ!Gc_&(?`F(!w3%&CUBL zv)?T5$)7Q!y?UHXyMU?WBW!z#F;Pb*1t@S@M}CHtSWKLr=uOV5?6*a z9-chTl@-Nhb&We(;Ltt*b-aBKG?3j0tG~o5NoOqC8Wyy@Q(wA=_)@QH7(e=c`IPJm z*66jjb;TYmQ+;_p>*i8U-Nto1_G-iw*3hsdp5?_pY?pcOKI5upUrApe%0$Sdoe#>n z$$)HteJlk{?;JS_nlZhWafNLf@VL9nw`bA!*Qjsr7Jw3V+4*!+sRnI5lon)NOeF5@ zS0LFX@*Ermqnzj|e%3SiBn)dWRZXF`wgKeQ5c9CmGv1?V?0tHB2EH7^yar=l6CA^N z4qP}GL7Z6`9I`B*Y-f>$)o=CL_4Z_O^xHLkudS|p@1~?hr`&TGwYpA%b&z=mS1ZW2xK_L2i3s@r z1F`nlYve@JbeP1dq-eT(*DrsqX*wa6q`R3$q7ef98p6=zz`%{0OG03|ufXBie_cLS zaN>e?2rK)CCbPO&&jFLNb#10hBwrSpaCQvE2E@qlK~P7@p3wnuic@=sW&j+*gPg*C z1%EBQ8pRlLsj&*#$2^_nCi9b#7GVMv3)v9QON?O8*LH9Fr1IvuUFVK5x{O2A04mS? zsmCc)IeDrE17*>EKT#}JV2R@paGU#MY+$Fg&X$DaFwr&g9}J!eYP|NwE?K_nuokeZ z0X+@v-#+I2=<`)e;n9b(jLy*e0hG;Iot4al8N+WMh*-R1EOUASBo&C9vOm_ToV2pp$k|Lu@ZOD96X2+v%y=%hqSJ z^^03vb@d>Ch25%js_IgzJn-Sl(LAn}_pLbX`AOBkJNbQ)aw6_qSV8;aNZBfl9JxPBNKh z{OLhcZT)GJ{qf=Gxh!hry8o;;IdyTTFqmiK#(zyVQN)eAsOh3>lVSSY^MoRPI^w zy|9RQqxE@H;o5S`k;Rt0;~ldB4$6*scNP4CDmon-nmU2r-=PYlGm7|$Ge(J0N`66L zv~0t0y>{I2h54^#WUi1S&fz`}s6KrxL;AVk*$emy@SeEV&57mFxbKr9M73O%v9j;R zC21(VsV6g~haBH>zu5J-wemAUCD!X=!0qUVLA;yjuiYNfLG{wW?%8KAakAxD}6bjVe?uXxBa=Tc@}5Fi~{k0~@SFhzWO z&|j}BB-w^~-Uvl##rXEC>b>fT-~p|2*k2I4{G{n=SSJ z9-c#SmgIb@mU<=l4e@P#9$*o4+~v8Ok{th^bz!7EUUV-3i3keyeERLFnOSns9G*7J zBV2{;)GkHyOeRQr8f&{(gZn$RF6%y=llml6{1p@nl<7c?KhZyml0l1yV1rMKEoctM zFHlbqER!|R-TbxL4Anf;dI+b++*$9`Ws2yt)YP6?E||kB>6bEQL50XgU>pq#P3=wm5*x)T2G$4PZS-j&xk&@b!N^tI}goL6ZB zR3?^Mp1Z@GGn78D4pX|o^#%850A&GUSm8cEj)V~KV>4=$3)PUbc<PPpXcJS($AOdKQVKJ2vX%OwZ4q zSq{Df^v*)h=CL-gkYq7I3BV@@66Ww9x1`- zaNLhD4&rjt6etYIp|9KLL_^iYQ71yrYwq)Q<0BHdS(PW%cg{*F-_+FF1GyFOq5i9y zJ14Kq&4K@R63_ZMAAUU>)|5mqjAn1~)lfO6d)wep-Czw36M_EZ^ye?#NhVPDNRcfk zL1K2UN~E3cdgvRqKR;ZQ7@jZ%Crj0R-`;z-dOiD=?O#fISPvc|#iHFGTg$7pYw8z6 z#sy&J3y3DP+FQbCzm&;-Xcvb{q3b=!LT*Gj=YzYEq4}I3yD?oiz(&q$_vEBI4kBWc zYeZGoD~~1y_>F9|Of!Cc;WCO!oe|&mZ7nvB!GUBc&NJE#X4vK#xdshjU&+ub@QnU8 zC+x+88J%q|hH|)ZF-?IgX>e-Eo+@MjKn!Li@b9Y2RH0I*pI=?mZQ4Z_ePG8Qrtw(Ag59! z{sh-@Sx+>rEYmTidJ1AiEYqi4dr|5_)^{);K`55hqdL%|HOQqJ{vS^#+-H&WZ`&I^ zuoBdZG1VuoW-Yu|nLS}QVJ9w+8ux}%96zrO4C+6Eg$t#$aqaCKk>O7fzAb(1AMlrm z=|R1pkG>@%@0P|VK3{y3q?0NJg=B?OG)h(?yAh7%(Qs;W76tJ%-)Xh=a90~JizD4l zz$)G0wh@=-V_YH)c6>qW4wqHTc};%BHlU=!4uIsAtxEYZCle~t7sFl2_rC3)>yH;y z+%Cas$~{hlxW|=;kGCJn;tf2*Oq5kkUnnjY9&OVb=lJL*=*tS*$`$O0P$);MV`Xa~ z(!SBwm7JsPCw?KH@b*3my;}iqqQpz!@>*=g|1RZTR7K%*b*0w_s#-E23ofBfxTh;I zVQMfW25Hj(;$@=C`!2o`FH*DmSeSAECHN-F-(7sf9H9t4g*5|2Y}n&F#rGn literal 0 HcmV?d00001 diff --git a/maps/dm9.map b/maps/dm9.map new file mode 100644 index 0000000000000000000000000000000000000000..918c41e1ba49e4271ad117f38bc136af28feea1b GIT binary patch literal 8209 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 literal 0 HcmV?d00001 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 ca6400756a..ac3cd53f14 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -1870,6 +1870,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 ec208096bc..49adab72e6 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -733,6 +733,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 5036b6543e..315355019a 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -12,6 +12,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 @@ -22,6 +30,8 @@ class IServer : public IInterface int m_Latency; }; + inline class CLocalization* Localization() { return m_pLocalization; } + int Tick() const { return m_CurrentGameTick; } int TickSpeed() const { return m_TickSpeed; } @@ -66,6 +76,9 @@ 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; }; class IGameServer : public IInterface @@ -96,6 +109,8 @@ class IGameServer : public IInterface virtual const char *GameType() = 0; virtual const char *Version() = 0; virtual const char *NetVersion() = 0; + + virtual void OnSetAuthed(int ClientID, int Level) = 0; }; extern IGameServer *CreateGameServer(); diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 19745b3ae8..88b49f393c 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 @@ -266,6 +273,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) @@ -968,8 +986,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; @@ -995,6 +1023,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); @@ -1676,6 +1705,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); { @@ -1721,6 +1758,8 @@ int main(int argc, const char **argv) // ignore_convention pServer->Run(); // free + delete pServer->m_pLocalization; + delete pServer; delete pKernel; delete pEngineMap; @@ -1731,4 +1770,3 @@ int main(int argc, const char **argv) // ignore_convention delete pConfig; return 0; } - diff --git a/src/engine/server/server.h b/src/engine/server/server.h index c3c1794dc5..b4e72772be 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -125,6 +125,8 @@ class CServer : public IServer const IConsole::CCommandInfo *m_pRconCmdToSend; void Reset(); + + char m_aLanguage[16]; }; CClient m_aClients[MAX_CLIENTS]; @@ -237,6 +239,10 @@ class CServer : public IServer virtual void SnapFreeID(int ID); 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); }; #endif 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 acd43ec01f..aa3125b316 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); } @@ -470,7 +498,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"); @@ -643,14 +671,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 { @@ -1344,7 +1381,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; } @@ -1370,14 +1407,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) @@ -1392,7 +1429,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); } } @@ -1467,6 +1504,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; @@ -1504,6 +1605,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 91a36dbd93..356311b71c 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -47,6 +49,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); @@ -143,11 +146,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); + // @@ -178,6 +183,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 4b37385f36..fb1df82ea0 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; } CPlayer::~CPlayer() @@ -38,6 +41,7 @@ void CPlayer::Tick() return; Server()->SetClientScore(m_ClientID, m_Score); + Server()->SetClientLanguage(m_ClientID, m_aLanguage); // do latency stuff { @@ -290,3 +294,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 dd804a957f..a0e2e3060d 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -35,6 +35,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; @@ -96,6 +99,8 @@ class CPlayer int m_Max; } m_Latency; + int m_Authed; + private: CCharacter *m_pCharacter; CGameContext *m_pGameServer; @@ -107,6 +112,8 @@ class CPlayer bool m_Spawning; int m_ClientID; int m_Team; + + char m_aLanguage[16]; }; #endif 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 From 170a3e87b6b8a5b4328687346ad4cb84613c7311 Mon Sep 17 00:00:00 2001 From: ST-Chara Date: Sat, 26 Mar 2022 22:33:44 +0800 Subject: [PATCH 2/2] just for pr, will revert soon --- bam.lua | 5 +- src/engine/server.h | 95 +++++++++++++++++++++++++++++++++++++- src/engine/server/server.h | 11 ++++- src/game/server/player.cpp | 10 ++++ src/game/server/player.h | 9 ++++ 5 files changed, 124 insertions(+), 6 deletions(-) diff --git a/bam.lua b/bam.lua index 628ef2d2dd..56623e3dd1 100644 --- a/bam.lua +++ b/bam.lua @@ -222,7 +222,8 @@ function build(settings) -- 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 engine_settings = settings:Copy() server_settings = engine_settings:Copy() @@ -270,7 +271,7 @@ function build(settings) -- build server server_exe = Link(server_settings, "teeworlds_srv", engine, server, - game_shared, game_server, zlib, server_link_other, teeuniverses, json) + game_shared, game_server, zlib, server_link_other, teeuniverses, json, md5) serverlaunch = {} if platform == "macosx" then diff --git a/src/engine/server.h b/src/engine/server.h index 315355019a..990b548ca6 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -5,6 +5,9 @@ #include "kernel.h" #include "message.h" +#include +#include + class IServer : public IInterface { MACRO_INTERFACE("server", 0) @@ -28,6 +31,7 @@ class IServer : public IInterface { const char *m_pName; int m_Latency; + bool m_CustClt; }; inline class CLocalization* Localization() { return m_pLocalization; } @@ -48,12 +52,96 @@ class IServer : public IInterface template int SendPackMsg(T *pMsg, int Flags, int ClientID) { - CMsgPacker Packer(pMsg->MsgID()); + int result = 0; + T tmp; + if (ClientID == -1) + { + for(int i = 0; i < MAX_CLIENTS; i++) + if(ClientIngame(i)) + { + mem_copy(&tmp, pMsg, sizeof(T)); + result = SendPackMsgTranslate(&tmp, Flags, i); + } + } else { + mem_copy(&tmp, pMsg, sizeof(T)); + result = SendPackMsgTranslate(&tmp, Flags, ClientID); + } + return result; + } + + template + int SendPackMsgTranslate(T *pMsg, int Flags, int ClientID) + { + return SendPackMsgOne(pMsg, Flags, ClientID); + } + + int SendPackMsgTranslate(CNetMsg_Sv_Emoticon *pMsg, int Flags, int ClientID) + { + return Translate(pMsg->m_ClientID, ClientID) && SendPackMsgOne(pMsg, Flags, ClientID); + } + + char msgbuf[1000]; + + int SendPackMsgTranslate(CNetMsg_Sv_Chat *pMsg, int Flags, int ClientID) + { + if (pMsg->m_ClientID >= 0 && !Translate(pMsg->m_ClientID, ClientID)) + { + str_format(msgbuf, sizeof(msgbuf), "%s: %s", ClientName(pMsg->m_ClientID), pMsg->m_pMessage); + pMsg->m_pMessage = msgbuf; + pMsg->m_ClientID = VANILLA_MAX_CLIENTS - 1; + } + return SendPackMsgOne(pMsg, Flags, ClientID); + } + + int SendPackMsgTranslate(CNetMsg_Sv_KillMsg *pMsg, int Flags, int ClientID) + { + if (!Translate(pMsg->m_Victim, ClientID)) return 0; + if (!Translate(pMsg->m_Killer, ClientID)) pMsg->m_Killer = pMsg->m_Victim; + return SendPackMsgOne(pMsg, Flags, ClientID); + } + + template + int SendPackMsgOne(T *pMsg, int Flags, int ClientID) + { + CMsgPacker Packer(pMsg->MsgID()); if(pMsg->Pack(&Packer)) return -1; return SendMsg(&Packer, Flags, ClientID); } + bool Translate(int& target, int client) + { + CClientInfo info; + GetClientInfo(client, &info); + if (info.m_CustClt) + return true; + int* map = GetIdMap(client); + bool found = false; + for (int i = 0; i < VANILLA_MAX_CLIENTS; i++) + { + if (target == map[i]) + { + target = i; + found = true; + break; + } + } + return found; + } + + bool ReverseTranslate(int& target, int client) + { + CClientInfo info; + GetClientInfo(client, &info); + if (info.m_CustClt) + return true; + int* map = GetIdMap(client); + if (map[target] == -1) + return false; + target = map[target]; + return true; + } + virtual void SetClientName(int ClientID, char const *pName) = 0; virtual void SetClientClan(int ClientID, char const *pClan) = 0; virtual void SetClientCountry(int ClientID, int Country) = 0; @@ -79,6 +167,8 @@ class IServer : public IInterface 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; }; class IGameServer : public IInterface @@ -111,7 +201,8 @@ class IGameServer : public IInterface 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.h b/src/engine/server/server.h index b4e72772be..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; @@ -127,9 +128,12 @@ class CServer : public IServer void Reset(); char m_aLanguage[16]; + NETADDR m_Addr; + bool m_CustClt; }; CClient m_aClients[MAX_CLIENTS]; + int IdMap[MAX_CLIENTS * VANILLA_MAX_CLIENTS]; CSnapshotDelta m_SnapshotDelta; CSnapshotBuilder m_SnapshotBuilder; @@ -196,6 +200,7 @@ class CServer : public IServer void DoSnapshot(); static int NewClientCallback(int ClientID, void *pUser); + static int NewClientNoAuthCallback(int ClientID, void *pUser); static int DelClientCallback(int ClientID, const char *pReason, void *pUser); void SendMap(int ClientID); @@ -209,7 +214,7 @@ class CServer : public IServer void ProcessClientPacket(CNetChunk *pPacket); - void SendServerInfo(const NETADDR *pAddr, int Token); + void SendServerInfo(const NETADDR *pAddr, int Token, bool Extended=false, int Offset=0); void UpdateServerInfo(); void PumpNetwork(); @@ -243,6 +248,8 @@ class CServer : public IServer 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/game/server/player.cpp b/src/game/server/player.cpp index fb1df82ea0..fe612df19c 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -24,6 +24,16 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team) SetLanguage(Server()->GetClientLanguage(ClientID)); m_Authed = IServer::AUTHED_NO; + + m_PrevTuningParams = *pGameServer->Tuning(); + m_NextTuningParams = m_PrevTuningParams; + + int* idMap = Server()->GetIdMap(ClientID); + for (int i = 1;i < VANILLA_MAX_CLIENTS;i++) + { + idMap[i] = -1; + } + idMap[0] = ClientID; } CPlayer::~CPlayer() diff --git a/src/game/server/player.h b/src/game/server/player.h index a0e2e3060d..a51a230c7e 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -114,6 +114,15 @@ class CPlayer int m_Team; char m_aLanguage[16]; + + private: + CTuningParams m_PrevTuningParams; + CTuningParams m_NextTuningParams; + + void HandleTuningParams(); //This function will send the new parameters if needed + +public: + CTuningParams* GetNextTuningParams() { return &m_NextTuningParams; }; }; #endif