From bae37ab7df8421722bc6ef0262263883ec341805 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Sun, 16 Oct 2022 10:14:05 +0200 Subject: [PATCH] Add 0.7 client support While keeping 0.6 fully working and untouched This adds the option to connect via 0.7 0.7 servers are now mixed into the server browser list. And users can also manually connect via 0.7 using a connection url like so: ./DDNet "connect tw-0.7+udp://127.0.0.1:8303" This also adds a "Tee 0.7" section in the menu to pick a 0.7 skin. The config variables are prefixed with player7_ like for example player7_color_body --- CMakeLists.txt | 11 + data/deadtee.png | Bin 0 -> 4087 bytes src/base/system.cpp | 50 +- src/base/system.h | 21 +- src/base/types.h | 7 +- src/engine/client.h | 28 +- src/engine/client/client.cpp | 255 ++++++- src/engine/client/client.h | 6 + src/engine/client/enums.h | 11 + src/engine/client/serverbrowser.cpp | 2 +- src/engine/client/sixup_translate_system.cpp | 114 +++ src/engine/shared/config_variables.h | 42 ++ src/engine/shared/demo.cpp | 4 +- src/engine/shared/demo.h | 3 + src/engine/shared/network.cpp | 25 + src/engine/shared/network.h | 36 +- src/engine/shared/network_client.cpp | 17 +- src/engine/shared/network_conn.cpp | 152 ++-- src/engine/shared/network_server.cpp | 3 +- src/engine/shared/protocol.h | 1 + src/engine/shared/protocol7.h | 14 +- src/engine/shared/protocolglue.cpp | 75 ++ src/engine/shared/protocolglue.h | 9 + .../shared/sixup_translate_snapshot.cpp | 516 +++++++++++++ src/engine/shared/snapshot.cpp | 22 +- src/engine/shared/snapshot.h | 18 +- src/engine/shared/translation_context.h | 120 +++ src/game/client/components/broadcast.cpp | 8 +- src/game/client/components/broadcast.h | 2 + src/game/client/components/chat.cpp | 10 + src/game/client/components/hud.cpp | 4 +- src/game/client/components/mapimages.cpp | 11 +- src/game/client/components/menus.h | 13 + src/game/client/components/menus_settings.cpp | 8 +- .../client/components/menus_settings7.cpp | 548 ++++++++++++++ src/game/client/components/players.cpp | 51 ++ src/game/client/components/players.h | 3 + src/game/client/components/scoreboard.cpp | 50 +- src/game/client/components/skins7.cpp | 560 ++++++++++++++ src/game/client/components/skins7.h | 97 +++ src/game/client/components/spectator.cpp | 16 + src/game/client/components/voting.cpp | 10 + src/game/client/components/voting.h | 2 +- src/game/client/gameclient.cpp | 250 ++++++- src/game/client/gameclient.h | 17 + src/game/client/render.cpp | 251 ++++++- src/game/client/render.h | 34 + src/game/client/sixup_translate_game.cpp | 689 ++++++++++++++++++ src/game/server/gamecontext.cpp | 21 +- src/game/version.h | 6 + src/test/netaddr.cpp | 41 +- src/tools/twping.cpp | 3 +- 52 files changed, 4081 insertions(+), 186 deletions(-) create mode 100644 data/deadtee.png create mode 100644 src/engine/client/enums.h create mode 100644 src/engine/client/sixup_translate_system.cpp create mode 100644 src/engine/shared/protocolglue.cpp create mode 100644 src/engine/shared/protocolglue.h create mode 100644 src/engine/shared/sixup_translate_snapshot.cpp create mode 100644 src/engine/shared/translation_context.h create mode 100644 src/game/client/components/menus_settings7.cpp create mode 100644 src/game/client/components/skins7.cpp create mode 100644 src/game/client/components/skins7.h create mode 100644 src/game/client/sixup_translate_game.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 89cc81f6d..3c15aa4b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1386,6 +1386,7 @@ set(EXPECTED_DATA countryflags/ZW.png countryflags/default.png countryflags/index.txt + deadtee.png debug_font.png editor/audio_source.png editor/automap/basic_freeze.rules @@ -1986,10 +1987,13 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared protocol_ex.cpp protocol_ex.h protocol_ex_msgs.h + protocolglue.cpp + protocolglue.h ringbuffer.cpp ringbuffer.h serverinfo.cpp serverinfo.h + sixup_translate_snapshot.cpp snapshot.cpp snapshot.h storage.cpp @@ -1998,6 +2002,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared teehistorian_ex.cpp teehistorian_ex.h teehistorian_ex_chunks.h + translation_context.h uuid_manager.cpp uuid_manager.h video.cpp @@ -2148,6 +2153,7 @@ if(CLIENT) demoedit.cpp demoedit.h discord.cpp + enums.h favorites.cpp friends.cpp friends.h @@ -2169,6 +2175,7 @@ if(CLIENT) serverbrowser_http.h serverbrowser_ping_cache.cpp serverbrowser_ping_cache.h + sixup_translate_system.cpp smooth_time.cpp smooth_time.h sound.cpp @@ -2237,6 +2244,7 @@ if(CLIENT) components/menus_demo.cpp components/menus_ingame.cpp components/menus_settings.cpp + components/menus_settings7.cpp components/menus_settings_assets.cpp components/menus_start.cpp components/motd.cpp @@ -2253,6 +2261,8 @@ if(CLIENT) components/scoreboard.h components/skins.cpp components/skins.h + components/skins7.cpp + components/skins7.h components/sounds.cpp components/sounds.h components/spectator.cpp @@ -2292,6 +2302,7 @@ if(CLIENT) render.cpp render.h render_map.cpp + sixup_translate_game.cpp skin.h ui.cpp ui.h diff --git a/data/deadtee.png b/data/deadtee.png new file mode 100644 index 0000000000000000000000000000000000000000..c22df996083b28b913717f7c04d18307dab0a4a2 GIT binary patch literal 4087 zcmVq~ zfo-b~M4X3KmngQr_KDgyy7a-)?zY?UiBE0kq0uOyAPO>rj0qtLB%v~sxrRHuKW?g^ zQdJ?TX!m<-@mnh^_ujMbK4Vf$M-bfZf15!1RCVhzw)_ zDS+^Ec47k70eXV(Ah7Zo7rL}{i(kcOP z0N?bc<31n;SSJXA>5hM&!+pzs#r#2FZ+cfNtpNr5HVtK7Bi=eMi2xfQDolCiR2IL4-g5=1+E631Wo`G zLot)P>1ZVIH4qP=>pGQ=dNTU?ghmj2>rQukerhNHnyv>lq$I}T@u*xrV?3fLuzt%v z_8ls<0foSDprY$L-3|OtK>YYo16lHKmteJ+scmTTBZ##}c`wt$5+yu*52%NWPfE?L z?bvORzJ|Fo#*>wvj4T^7`=Ww;o_PN6?A(7W2DlM;v@5evQy>SJY>SHE{+q8LJvD)Z zxM+stX7~}MB*l3z#{-sL2w}zeA{PKNFidm^VD7=YEkLcRY1B8i_%-Ba_45f2Ac_L> zXZfsuUNB~%EFcaHld_miAxDyxoptfHElx~32$25wk51Cyck z+c1=HHj;rSfEq#I16h`r0a>Cb2+2ta49v}D%*cEO=4O+a5QiWj0eL1_z61CiV|RH7 zW91iszXFSFQ4zw5+pju%4GIcfhfmjU_ulRdM$;XKcY&)?lVX`Oy?}%`J83ELWTqz} ziT--~VQ&$C{L}lWng*~6co<`^{!dw!tcs$Note(O%PwJJ!6@m9}g9= zb^9JZUbg{9buE%4qN-YRrvTF}W)qLyca7h4bhG@0xNF6it!@{`snH#n|W;YJCs$_gkseM zTnTIl*4UjR6p$d}~B@MlwkWcDC;;MwTU8Tx<-_ zJn z+zf03nt@}$%fK?=$B_N>c3i+TjD;wpUveD(arf1vrNjfUbN?}(`t$ot7(0?xE0+@+ zWA7#V(5SAd9$Wnmm(85U%7=f8B=ywG?m3(;H@|&gCA;<>AUe`Yb$t^UuI@CTJ{+EX2_d5~ zwyG%+5f;#4%7q0y^TdjPMe?VF+wEcT&G&Hl*h$=Oj|&)pF+IaVUp|u?cnesJj=g_I zKVDq@IA&95=dy79X1?6;Ex7|SiHNWUv{zI)c;l@Pa5~*&Wd?UHB}roJ@N5b6xh)5W4CcXRCc2>`4XGw-~%n!Ev7KAInP z?&YStA3;$Jzh~U2p}hL%r;(&UYwBoi;>*Hy{O;*D&~+3HukvU(0`zsFz9C6c_Tt|x zV9up*5SLaYdB5$i3(=^VsZdKQIs+1 ztvKqMI91&ss;U+bd=QQReVMFdfoZZVvGlsDLK3b2<~#Q6KjOQ$MOn#APokxznbx*; z0Cw!&M_v7Cz}Ws$lVa)LFM*=sA{>su{aun}B+10pbEkMMW)X&a-D~tk0ZU|AX2IN9 z#Mq-l5rmOF|M=Ichjj;V@nh1d`(Y{esF3>sXDEM^nAS^dB;ps_t1{~12LeW4SB%_bAsncb%X zs~oie$g&jFKZv&30I=JQ|DqUNnU$5N0-lND8Gh-h3Et>(a8CyB$EUY1bRtDnRbrw- zCrbcaE*Ah+OK`gMv{V2F=j9j=LKstXOIyG*@hn$6DZ%dbK2y3Mb|Au4fK5>~P~E6X z&_oMxCUI#BV%nl8keibYz?2Io81!1OWD+Pr7D4^2q-ccI;=K>O9fd%+3UG^pfXCyZ zu`VFd>VtP~2lM9ga&vs9KVjTREEY2!j~YM+5EU62@XX_Jo^`Ew*CJOhWB|ffK%*>6 zw6;5Gsyl_^3EppVq7f#GK^DsCQ_pyKf*^1~!ARVm;MSDZ&p)4_YZ|WhGYQewwhlVG zXzZOB;VYm<5CrNQThMil#+s8si6#~p$)v71{P?(7F1gs>#l8Qon-IXer~6@e{!qXB zrUr-K)T=+;>Q!ECZv_cg0ed@~E{>HOt8??|Iy5cNe3xFg5LuRd0ge~TmRy6?>Yqi= z$y_E%1!uZ*PCqxfbb< z0{eR_L|>x8x7;4ZvvYqjr6;Qao$8ZCs7l}>))(kHR96(!-g-KqZQr3%ijG&JX<94r zQ8@hjVgao{lErMAc%rJ738M!giO_Po22qqSS*!tpO*m+9l+)bk2zcJq(#D$CKBKkG ziK=NUf%W0=?`ujd6(}*AO_5fMi5nNqWZDJ84aS0qCBjCOEtaU*M9h}JVCY}Adk#;{HzB@EGR8v>wFTAR0y!d7zZ-4SF9z{h_RWQyd-VaxRz9>Kd)&RGP zqQKoZ%wxi+L9G4g8ycJ1C>WXNN9=KR(9&3gB%3i?qX-p6Ynn>ksWKX>{r7rZhyQx{ z6SnL)#C=-A>y+*i|9RlzIox2JqsXtYQ*&2x?n-E0_QIgPg4aM!Iqpc~ZQ}ya1cBS|8! zy|?7~ zk)#AWjZLjqjKYj!LL88Gw+lQ3JRXcDr+*4JT{GKTl?6bBqn-!;@VYm*{|{h_$t2rN zvc#O}1$!8u90q2m`L$Iz>M7i`o5D?d&;hsGQ;!ktQ(ehvdNQ&1F*HLre( zqH07~&D?#%Togs6xU7nOhsrp9!hywX@+qC7aok5e;QC$Qtn{g*Fj5v0NA?wh*$U;AZXGe)Gp8rP+46cbQSl) z>GH7gdtdx<72OvoGy|V>{&mF--MkkJwHP%>AMQDDoCUv}#ZDhlDzWKkkr=$J}u>;_ntv}MWZu;Bpz9YGVoC7@# zhsR^=zt-f$7?R>+C^}w=$FPWPz+`%$?VlJa7^QHw85fP>irE){An?hTKTx=N4;Ur3 zxj^|()!}rEG8FGGGZc&(p}!9RyhCw002ovPDHLkV1fk7tvCPx literal 0 HcmV?d00001 diff --git a/src/base/system.cpp b/src/base/system.cpp index a3dd1c7f5..b7320fff9 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -958,7 +958,7 @@ const NETADDR NETADDR_ZEROED = {NETTYPE_INVALID, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest) { mem_zero(dest, sizeof(struct sockaddr_in)); - if(src->type != NETTYPE_IPV4 && src->type != NETTYPE_WEBSOCKET_IPV4) + if(!(src->type & NETTYPE_IPV4) && !(src->type & NETTYPE_WEBSOCKET_IPV4)) { dbg_msg("system", "couldn't convert NETADDR of type %d to ipv4", src->type); return; @@ -972,9 +972,9 @@ static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest) static void netaddr_to_sockaddr_in6(const NETADDR *src, struct sockaddr_in6 *dest) { mem_zero(dest, sizeof(struct sockaddr_in6)); - if(src->type != NETTYPE_IPV6) + if(!(src->type & NETTYPE_IPV6)) { - dbg_msg("system", "couldn't not convert NETADDR of type %d to ipv6", src->type); + dbg_msg("system", "couldn't convert NETADDR of type %d to ipv6", src->type); return; } @@ -1098,16 +1098,16 @@ void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buf } } -void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port) +bool net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port) { - if(addr->type == NETTYPE_IPV4 || addr->type == NETTYPE_WEBSOCKET_IPV4) + if(addr->type & NETTYPE_IPV4 || addr->type & NETTYPE_WEBSOCKET_IPV4) { if(add_port != 0) str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port); else str_format(string, max_length, "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]); } - else if(addr->type == NETTYPE_IPV6) + else if(addr->type & NETTYPE_IPV6) { int port = -1; unsigned short ip[8]; @@ -1123,7 +1123,27 @@ void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_por net_addr_str_v6(ip, port, string, max_length); } else + { str_format(string, max_length, "unknown type %d", addr->type); + return false; + } + return true; +} + +void net_addr_url_str(const NETADDR *addr, char *string, int max_length, int add_port) +{ + char ipaddr[512]; + if(!net_addr_str(addr, ipaddr, sizeof(ipaddr), add_port)) + { + str_copy(string, ipaddr, max_length); + return; + } + str_format( + string, + max_length, + "tw-%s+udp://%s", + addr->type & NETTYPE_TW7 ? "0.7" : "0.6", + ipaddr); } static int priv_net_extract(const char *hostname, char *host, int max_host, int *port) @@ -1275,12 +1295,17 @@ static int parse_uint16(unsigned short *out, const char **str) int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t host_buf_size) { + bool sixup = false; + mem_zero(addr, sizeof(*addr)); const char *str = str_startswith(string, "tw-0.6+udp://"); + if(!str && (str = str_startswith(string, "tw-0.7+udp://"))) + { + addr->type |= NETTYPE_TW7; + sixup = true; + } if(!str) return 1; - mem_zero(addr, sizeof(*addr)); - int length = str_length(str); int start = 0; int end = length; @@ -1307,7 +1332,14 @@ int net_addr_from_url(NETADDR *addr, const char *string, char *host_buf, size_t if(host_buf) str_copy(host_buf, host, host_buf_size); - return net_addr_from_str(addr, host); + int failure = net_addr_from_str(addr, host); + if(failure) + return failure; + + if(sixup) + addr->type |= NETTYPE_TW7; + + return failure; } int net_addr_from_str(NETADDR *addr, const char *string) diff --git a/src/base/system.h b/src/base/system.h index 400986a24..260b6abf7 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -812,9 +812,28 @@ int net_addr_comp_noport(const NETADDR *a, const NETADDR *b); * @param max_length Maximum size of the string. * @param add_port add port to string or not * + * @return true on success + * * @remark The string will always be zero terminated */ -void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port); +bool net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port); + +/** + * Turns a network address into a url string. + * Examples: + * tw-0.6+udp://127.0.0.1:8303 + * tw-0.7+udp://127.0.0.1 + * + * @ingroup Network-General + * + * @param addr Address to turn into a string. + * @param string Buffer to fill with the url string. + * @param max_length Maximum size of the url string. + * @param add_port add port to url string or not + * + * @remark The string will always be zero terminated + */ +void net_addr_url_str(const NETADDR *addr, char *string, int max_length, int add_port); /** * Turns url string into a network address struct. diff --git a/src/base/types.h b/src/base/types.h index ffa1b47ed..13abeb0e8 100644 --- a/src/base/types.h +++ b/src/base/types.h @@ -45,9 +45,14 @@ enum NETTYPE_IPV4 = 1, NETTYPE_IPV6 = 2, NETTYPE_WEBSOCKET_IPV4 = 8, + /** + * 0.7 address. This is a flag in NETADDR to avoid introducing a parameter to every networking function + * to differenciate between 0.6 and 0.7 connections. + */ + NETTYPE_TW7 = 16, NETTYPE_ALL = NETTYPE_IPV4 | NETTYPE_IPV6 | NETTYPE_WEBSOCKET_IPV4, - NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST, + NETTYPE_MASK = NETTYPE_ALL | NETTYPE_LINK_BROADCAST | NETTYPE_TW7, }; /** diff --git a/src/engine/client.h b/src/engine/client.h index b60793192..69eafbdef 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -8,12 +8,17 @@ #include "message.h" #include +#include + #include +#include #include #include #include +#include + struct SWarning; enum @@ -23,8 +28,6 @@ enum RECORDER_RACE = 2, RECORDER_REPLAYS = 3, RECORDER_MAX = 4, - - NUM_DUMMIES = 2, }; typedef bool (*CLIENTFUNC_FILTER)(const void *pData, int DataSize, void *pUser); @@ -73,6 +76,7 @@ public: LOADING_CALLBACK_DETAIL_DEMO, }; typedef std::function TLoadingCallback; + CTranslationContext m_TranslationContext; protected: // quick access to state of the client @@ -232,19 +236,29 @@ public: virtual CSnapItem SnapGetItem(int SnapId, int Index) const = 0; virtual void SnapSetStaticsize(int ItemType, int Size) = 0; + virtual void SnapSetStaticsize7(int ItemType, int Size) = 0; virtual int SendMsg(int Conn, CMsgPacker *pMsg, int Flags) = 0; virtual int SendMsgActive(CMsgPacker *pMsg, int Flags) = 0; template - int SendPackMsgActive(T *pMsg, int Flags) + int SendPackMsgActive(T *pMsg, int Flags, bool NoTranslate = false) { - CMsgPacker Packer(T::ms_MsgId, false); + CMsgPacker Packer(T::ms_MsgId, false, NoTranslate); if(pMsg->Pack(&Packer)) return -1; return SendMsgActive(&Packer, Flags); } + template + int SendPackMsg(int Conn, T *pMsg, int Flags, bool NoTranslate = false) + { + CMsgPacker Packer(T::ms_MsgId, false, NoTranslate); + if(pMsg->Pack(&Packer)) + return -1; + return SendMsg(Conn, &Packer, Flags); + } + // virtual const char *PlayerName() const = 0; virtual const char *DummyName() = 0; @@ -265,6 +279,9 @@ public: int Points() const { return m_Points; } int64_t ReconnectTime() const { return m_ReconnectTime; } void SetReconnectTime(int64_t ReconnectTime) { m_ReconnectTime = ReconnectTime; } + + virtual bool IsSixup() const = 0; + virtual int GetCurrentRaceTime() = 0; virtual void RaceRecord_Start(const char *pFilename) = 0; @@ -368,6 +385,9 @@ public: virtual void RenderShutdownMessage() = 0; virtual CNetObjHandler *GetNetObjHandler() = 0; + virtual protocol7::CNetObjHandler *GetNetObjHandler7() = 0; + + virtual int ClientVersion7() const = 0; }; void SnapshotRemoveExtraProjectileInfo(CSnapshot *pSnap); diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index bebd80962..b637fa2b1 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -36,12 +36,18 @@ #include #include #include +#include #include #include #include #include #include +#include +#include + +#include + #include #include @@ -96,15 +102,59 @@ CClient::CClient() : for(auto &GameTime : m_aGameTime) GameTime.Init(0); m_PredictedTime.Init(0); + + m_Sixup = false; } // ----- send functions ----- -static inline bool RepackMsg(const CMsgPacker *pMsg, CPacker &Packer) +static inline bool RepackMsg(const CMsgPacker *pMsg, CPacker &Packer, bool Sixup) { + int MsgId = pMsg->m_MsgId; Packer.Reset(); + + if(Sixup && !pMsg->m_NoTranslate) + { + if(pMsg->m_System) + { + if(MsgId >= OFFSET_UUID) + ; + else if(MsgId == NETMSG_INFO || MsgId == NETMSG_REQUEST_MAP_DATA) + ; + else if(MsgId == NETMSG_READY) + MsgId = protocol7::NETMSG_READY; + else if(MsgId == NETMSG_RCON_CMD) + MsgId = protocol7::NETMSG_RCON_CMD; + else if(MsgId == NETMSG_ENTERGAME) + MsgId = protocol7::NETMSG_ENTERGAME; + else if(MsgId == NETMSG_INPUT) + MsgId = protocol7::NETMSG_INPUT; + else if(MsgId == NETMSG_RCON_AUTH) + MsgId = protocol7::NETMSG_RCON_AUTH; + else if(MsgId == NETMSGTYPE_CL_SETTEAM) + MsgId = protocol7::NETMSGTYPE_CL_SETTEAM; + else if(MsgId == NETMSGTYPE_CL_VOTE) + MsgId = protocol7::NETMSGTYPE_CL_VOTE; + else if(MsgId == NETMSG_PING) + MsgId = protocol7::NETMSG_PING; + else + { + dbg_msg("net", "0.7 DROP send sys %d", MsgId); + return true; + } + } + else + { + if(MsgId >= 0 && MsgId < OFFSET_UUID) + MsgId = Msg_SixToSeven(MsgId); + + if(MsgId < 0) + return true; + } + } + if(pMsg->m_MsgId < OFFSET_UUID) { - Packer.AddInt((pMsg->m_MsgId << 1) | (pMsg->m_System ? 1 : 0)); + Packer.AddInt((MsgId << 1) | (pMsg->m_System ? 1 : 0)); } else { @@ -125,7 +175,7 @@ int CClient::SendMsg(int Conn, CMsgPacker *pMsg, int Flags) // repack message (inefficient) CPacker Pack; - if(RepackMsg(pMsg, Pack)) + if(RepackMsg(pMsg, Pack, IsSixup())) return 0; mem_zero(&Packet, sizeof(CNetChunk)); @@ -160,6 +210,15 @@ int CClient::SendMsgActive(CMsgPacker *pMsg, int Flags) void CClient::SendInfo(int Conn) { + if(IsSixup()) + { + CMsgPacker Msg(NETMSG_INFO, true); + Msg.AddString(GAME_NETVERSION7, 128); + Msg.AddString(Config()->m_Password); + Msg.AddInt(GameClient()->ClientVersion7()); + SendMsg(Conn, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH); + return; + } CMsgPacker MsgVer(NETMSG_CLIENTVER, true); MsgVer.AddRaw(&m_ConnectionId, sizeof(m_ConnectionId)); MsgVer.AddInt(GameClient()->DDNetVersion()); @@ -192,9 +251,17 @@ void CClient::SendMapRequest() Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); } m_MapdownloadFileTemp = Storage()->OpenFile(m_aMapdownloadFilenameTemp, IOFLAG_WRITE, IStorage::TYPE_SAVE); - CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true); - Msg.AddInt(m_MapdownloadChunk); - SendMsg(CONN_MAIN, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH); + if(IsSixup()) + { + CMsgPacker MsgP(protocol7::NETMSG_REQUEST_MAP_DATA, true, true); + SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH); + } + else + { + CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA, true); + Msg.AddInt(m_MapdownloadChunk); + SendMsg(CONN_MAIN, &Msg, MSGFLAG_VITAL | MSGFLAG_FLUSH); + } } void CClient::RconAuth(const char *pName, const char *pPassword) @@ -207,6 +274,14 @@ void CClient::RconAuth(const char *pName, const char *pPassword) if(pPassword != m_aRconPassword) str_copy(m_aRconPassword, pPassword); + if(IsSixup()) + { + CMsgPacker Msg7(protocol7::NETMSG_RCON_AUTH, true, true); + Msg7.AddString(pPassword); + SendMsgActive(&Msg7, MSGFLAG_VITAL); + return; + } + CMsgPacker Msg(NETMSG_RCON_AUTH, true); Msg.AddString(pName); Msg.AddString(pPassword); @@ -269,7 +344,18 @@ void CClient::SendInput() // pack it for(int k = 0; k < Size / 4; k++) - Msg.AddInt(m_aInputs[i][m_aCurrentInput[i]].m_aData[k]); + { + static const int FlagsOffset = offsetof(CNetObj_PlayerInput, m_PlayerFlags) / sizeof(int); + if(k == FlagsOffset && IsSixup()) + { + int PlayerFlags = m_aInputs[i][m_aCurrentInput[i]].m_aData[k]; + Msg.AddInt(PlayerFlags_SixToSeven(PlayerFlags)); + } + else + { + Msg.AddInt(m_aInputs[i][m_aCurrentInput[i]].m_aData[k]); + } + } m_aCurrentInput[i]++; m_aCurrentInput[i] %= 200; @@ -468,11 +554,13 @@ void CClient::Connect(const char *pAddress, const char *pPassword) mem_zero(aConnectAddrs, sizeof(aConnectAddrs)); const char *pNextAddr = pAddress; char aBuffer[128]; + bool OnlySixup = true; while((pNextAddr = str_next_token(pNextAddr, ",", aBuffer, sizeof(aBuffer)))) { NETADDR NextAddr; char aHost[128]; int url = net_addr_from_url(&NextAddr, aBuffer, aHost, sizeof(aHost)); + bool Sixup = NextAddr.type & NETTYPE_TW7; if(url > 0) str_copy(aHost, aBuffer); @@ -491,6 +579,10 @@ void CClient::Connect(const char *pAddress, const char *pPassword) NextAddr.port = 8303; } char aNextAddr[NETADDR_MAXSTRSIZE]; + if(Sixup) + NextAddr.type |= NETTYPE_TW7; + else + OnlySixup = false; net_addr_str(&NextAddr, aNextAddr, sizeof(aNextAddr), true); log_debug("client", "resolved connect address '%s' to %s", aBuffer, aNextAddr); aConnectAddrs[NumConnectAddrs] = NextAddr; @@ -523,7 +615,14 @@ void CClient::Connect(const char *pAddress, const char *pPassword) m_CanReceiveServerCapabilities = true; - m_aNetClient[CONN_MAIN].Connect(aConnectAddrs, NumConnectAddrs); + m_Sixup = OnlySixup; + if(m_Sixup) + { + m_aNetClient[CONN_MAIN].Connect7(aConnectAddrs, NumConnectAddrs); + } + else + m_aNetClient[CONN_MAIN].Connect(aConnectAddrs, NumConnectAddrs); + m_aNetClient[CONN_MAIN].RefreshStun(); SetState(IClient::STATE_CONNECTING); @@ -595,6 +694,10 @@ void CClient::DisconnectWithReason(const char *pReason) m_aapSnapshots[0][SNAP_PREV] = 0; m_aReceivedSnapshots[0] = 0; m_LastDummy = false; + + // 0.7 + m_TranslationContext.Reset(); + m_Sixup = false; } void CClient::Disconnect() @@ -652,7 +755,11 @@ void CClient::DummyConnect() g_Config.m_ClDummyHammer = 0; m_DummyConnecting = true; - m_aNetClient[CONN_DUMMY].Connect(m_aNetClient[CONN_MAIN].ServerAddress(), 1); + // connect to the server + if(IsSixup()) + m_aNetClient[CONN_DUMMY].Connect7(m_aNetClient[CONN_MAIN].ServerAddress(), 1); + else + m_aNetClient[CONN_DUMMY].Connect(m_aNetClient[CONN_MAIN].ServerAddress(), 1); } void CClient::DummyDisconnect(const char *pReason) @@ -738,6 +845,11 @@ void CClient::SnapSetStaticsize(int ItemType, int Size) m_SnapshotDelta.SetStaticsize(ItemType, Size); } +void CClient::SnapSetStaticsize7(int ItemType, int Size) +{ + m_SnapshotDelta.SetStaticsize7(ItemType, Size); +} + void CClient::DebugRender() { if(!g_Config.m_Debug) @@ -1335,6 +1447,20 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) SendMsg(Conn, &Packer, MSGFLAG_VITAL); } + // allocates the memory for the translated data + CPacker Packer6; + if(IsSixup()) + { + bool IsExMsg = false; + int Success = !TranslateSysMsg(&Msg, Sys, &Unpacker, &Packer6, pPacket, &IsExMsg); + if(Msg < 0) + return; + if(Success && !IsExMsg) + { + Unpacker.Reset(Packer6.Data(), Packer6.Size()); + } + } + if(Sys) { // system message @@ -1482,11 +1608,25 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) { return; } + int Last = -1; + int MapCRC = -1; + int Chunk = -1; + int Size = -1; + + if(IsSixup()) + { + MapCRC = m_MapdownloadCrc; + Chunk = m_MapdownloadChunk; + Size = minimum(m_TranslationContext.m_MapDownloadChunkSize, m_TranslationContext.m_MapdownloadTotalsize - m_MapdownloadAmount); + } + else + { + Last = Unpacker.GetInt(); + MapCRC = Unpacker.GetInt(); + Chunk = Unpacker.GetInt(); + Size = Unpacker.GetInt(); + } - int Last = Unpacker.GetInt(); - int MapCRC = Unpacker.GetInt(); - int Chunk = Unpacker.GetInt(); - int Size = Unpacker.GetInt(); const unsigned char *pData = Unpacker.GetRaw(Size); if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk) { @@ -1497,6 +1637,9 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) m_MapdownloadAmount += Size; + if(IsSixup()) + Last = m_MapdownloadAmount == m_TranslationContext.m_MapdownloadTotalsize; + if(Last) { if(m_MapdownloadFileTemp) @@ -1511,9 +1654,17 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) // request new chunk m_MapdownloadChunk++; - CMsgPacker MsgP(NETMSG_REQUEST_MAP_DATA, true); - MsgP.AddInt(m_MapdownloadChunk); - SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH); + if(IsSixup() && (m_MapdownloadChunk % m_TranslationContext.m_MapDownloadChunksPerRequest == 0)) + { + CMsgPacker MsgP(protocol7::NETMSG_REQUEST_MAP_DATA, true, true); + SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH); + } + else + { + CMsgPacker MsgP(NETMSG_REQUEST_MAP_DATA, true); + MsgP.AddInt(m_MapdownloadChunk); + SendMsg(CONN_MAIN, &MsgP, MSGFLAG_VITAL | MSGFLAG_FLUSH); + } if(g_Config.m_Debug) { @@ -1781,7 +1932,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) } // unpack delta - const int SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); + const int SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize, IsSixup()); if(SnapSize < 0) { dbg_msg("client", "delta unpack failed. error=%d", SnapSize); @@ -1828,9 +1979,30 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) m_aSnapshotStorage[Conn].PurgeUntil(PurgeTick); // create a verified and unpacked snapshot + int AltSnapSize = -1; unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE]; CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer; - const int AltSnapSize = UnpackAndValidateSnapshot(pTmpBuffer3, pAltSnapBuffer); + + if(IsSixup()) + { + unsigned char aTmpTransSnapBuffer[CSnapshot::MAX_SIZE]; + CSnapshot *pTmpTransSnapBuffer = (CSnapshot *)aTmpTransSnapBuffer; + mem_copy(pTmpTransSnapBuffer, pTmpBuffer3, CSnapshot::MAX_SIZE); + AltSnapSize = pTmpTransSnapBuffer->TranslateSevenToSix( + pAltSnapBuffer, + m_TranslationContext, + LocalTime(), + CClient::GameTick(g_Config.m_ClDummy), + Conn, + Dummy, + GameClient()->GetNetObjHandler7(), + GameClient()->GetNetObjHandler()); + } + else + { + AltSnapSize = UnpackAndValidateSnapshot(pTmpBuffer3, pAltSnapBuffer); + } + if(AltSnapSize < 0) { dbg_msg("client", "unpack snapshot and validate failed. error=%d", AltSnapSize); @@ -1902,8 +2074,6 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) { if(m_ServerCapabilities.m_ChatTimeoutCode) { - CNetMsg_Cl_Say MsgP; - MsgP.m_Team = 0; char aBuf[128]; char aBufMsg[256]; if(!g_Config.m_ClRunOnJoin[0] && !g_Config.m_ClDummyDefaultEyes && !g_Config.m_ClPlayerDefaultEyes) @@ -1947,10 +2117,23 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) str_append(aBufMsg, aBuf); } } - MsgP.m_pMessage = aBufMsg; - CMsgPacker PackerTimeout(&MsgP); - MsgP.Pack(&PackerTimeout); - SendMsg(Conn, &PackerTimeout, MSGFLAG_VITAL); + if(IsSixup()) + { + protocol7::CNetMsg_Cl_Say Msg7; + Msg7.m_Mode = protocol7::CHAT_ALL; + Msg7.m_Target = -1; + Msg7.m_pMessage = aBufMsg; + SendPackMsg(Conn, &Msg7, MSGFLAG_VITAL, true); + } + else + { + CNetMsg_Cl_Say MsgP; + MsgP.m_Team = 0; + MsgP.m_pMessage = aBufMsg; + CMsgPacker PackerTimeout(&MsgP); + MsgP.Pack(&PackerTimeout); + SendMsg(Conn, &PackerTimeout, MSGFLAG_VITAL); + } } m_aCodeRunAfterJoin[Conn] = true; } @@ -2313,9 +2496,10 @@ void CClient::PumpNetwork() // process packets CNetChunk Packet; + SECURITY_TOKEN ResponseToken; for(int Conn = 0; Conn < NUM_CONNS; Conn++) { - while(m_aNetClient[Conn].Recv(&Packet)) + while(m_aNetClient[Conn].Recv(&Packet, &ResponseToken, IsSixup())) { if(Packet.m_ClientId == -1) { @@ -2347,11 +2531,29 @@ void CClient::OnDemoPlayerSnapshot(void *pData, int Size) return; } + unsigned char aTmpTranslateBuffer[CSnapshot::MAX_SIZE]; + CSnapshot *pTmpTranslateBuffer = nullptr; + int TranslatedSize = 0; + if(IsSixup()) + { + pTmpTranslateBuffer = (CSnapshot *)aTmpTranslateBuffer; + TranslatedSize = ((CSnapshot *)pData)->TranslateSevenToSix(pTmpTranslateBuffer, m_TranslationContext, LocalTime(), GameTick(g_Config.m_ClDummy), CONN_MAIN, false, GameClient()->GetNetObjHandler7(), GameClient()->GetNetObjHandler()); + if(TranslatedSize < 0) + { + dbg_msg("sixup", "failed to translate snapshot. error=%d", TranslatedSize); + pTmpTranslateBuffer = nullptr; + TranslatedSize = 0; + } + } + // handle snapshots after validation std::swap(m_aapSnapshots[0][SNAP_PREV], m_aapSnapshots[0][SNAP_CURRENT]); mem_copy(m_aapSnapshots[0][SNAP_CURRENT]->m_pSnap, pData, Size); mem_copy(m_aapSnapshots[0][SNAP_CURRENT]->m_pAltSnap, pAltSnapBuffer, AltSnapSize); + if(pTmpTranslateBuffer && TranslatedSize > 0) + mem_copy(m_aapSnapshots[0][SNAP_CURRENT]->m_pAltSnap, pTmpTranslateBuffer, TranslatedSize); + GameClient()->OnNewSnapshot(); } @@ -3591,6 +3793,9 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType) return m_DemoPlayer.ErrorMessage(); } + m_Sixup = str_startswith(m_DemoPlayer.Info()->m_Header.m_aNetversion, "0.7"); + m_DemoPlayer.SetSixup(m_Sixup); + // load map const CMapInfo *pMapInfo = m_DemoPlayer.GetMapInfo(); int Crc = pMapInfo->m_Crc; diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 7ee46e270..a72d95173 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -93,6 +93,7 @@ class CClient : public IClient, public CDemoPlayer::IListener char m_aConnectAddressStr[MAX_SERVER_ADDRESSES * NETADDR_MAXSTRSIZE] = ""; CUuid m_ConnectionId = UUID_ZEROED; + bool m_Sixup; bool m_HaveGlobalTcpAddr = false; NETADDR m_GlobalTcpAddr = NETADDR_ZEROED; @@ -334,6 +335,7 @@ public: const void *SnapFindItem(int SnapId, int Type, int Id) const override; int SnapNumItems(int SnapId) const override; void SnapSetStaticsize(int ItemType, int Size) override; + void SnapSetStaticsize7(int ItemType, int Size) override; void Render(); void DebugRender(); @@ -348,6 +350,8 @@ public: const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc); const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc); + int TranslateSysMsg(int *pMsgId, bool System, CUnpacker *pUnpacker, CPacker *pPacker, CNetChunk *pPacket, bool *pIsExMsg); + void ProcessConnlessPacket(CNetChunk *pPacket); void ProcessServerInfo(int Type, NETADDR *pFrom, const void *pData, int DataSize); void ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy); @@ -362,6 +366,8 @@ public: void FinishDDNetInfo(); void LoadDDNetInfo(); + bool IsSixup() const override { return m_Sixup; } + const NETADDR &ServerAddress() const override { return *m_aNetClient[CONN_MAIN].ServerAddress(); } int ConnectNetTypes() const override; const char *ConnectAddressString() const override { return m_aConnectAddressStr; } diff --git a/src/engine/client/enums.h b/src/engine/client/enums.h new file mode 100644 index 000000000..45a475fa1 --- /dev/null +++ b/src/engine/client/enums.h @@ -0,0 +1,11 @@ +/* (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 ENGINE_CLIENT_ENUMS_H +#define ENGINE_CLIENT_ENUMS_H + +enum +{ + NUM_DUMMIES = 2, +}; + +#endif diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 393105c3e..4da2676df 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -704,7 +704,7 @@ void ServerBrowserFormatAddresses(char *pBuffer, int BufferSize, NETADDR *pAddrs { return; } - net_addr_str(&pAddrs[i], pBuffer, BufferSize, true); + net_addr_url_str(&pAddrs[i], pBuffer, BufferSize, true); int Length = str_length(pBuffer); pBuffer += Length; BufferSize -= Length; diff --git a/src/engine/client/sixup_translate_system.cpp b/src/engine/client/sixup_translate_system.cpp new file mode 100644 index 000000000..f6398cfcb --- /dev/null +++ b/src/engine/client/sixup_translate_system.cpp @@ -0,0 +1,114 @@ +#include + +int CClient::TranslateSysMsg(int *pMsgId, bool System, CUnpacker *pUnpacker, CPacker *pPacker, CNetChunk *pPacket, bool *pIsExMsg) +{ + *pIsExMsg = false; + if(!System) + return -1; + + // ddnet ex + if(*pMsgId > NETMSG_WHATIS && *pMsgId < NETMSG_RCON_CMD_GROUP_END) + { + *pIsExMsg = true; + return 0; + } + + pPacker->Reset(); + + if(*pMsgId == protocol7::NETMSG_MAP_CHANGE) + { + *pMsgId = NETMSG_MAP_CHANGE; + const char *pMapName = pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES); + int MapCrc = pUnpacker->GetInt(); + int Size = pUnpacker->GetInt(); + m_TranslationContext.m_MapDownloadChunksPerRequest = pUnpacker->GetInt(); + int ChunkSize = pUnpacker->GetInt(); + // void *pSha256 = pUnpacker->GetRaw(); // probably safe to ignore + pPacker->AddString(pMapName, 0); + pPacker->AddInt(MapCrc); + pPacker->AddInt(Size); + m_TranslationContext.m_MapdownloadTotalsize = Size; + m_TranslationContext.m_MapDownloadChunkSize = ChunkSize; + return 0; + } + else if(*pMsgId == protocol7::NETMSG_SERVERINFO) + { + // side effect only + // this is a 0.7 only message and not handled in 0.6 code + *pMsgId = -1; + net_addr_str(&pPacket->m_Address, m_CurrentServerInfo.m_aAddress, sizeof(m_CurrentServerInfo.m_aAddress), true); + str_copy(m_CurrentServerInfo.m_aVersion, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES)); + str_copy(m_CurrentServerInfo.m_aName, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES)); + str_clean_whitespaces(m_CurrentServerInfo.m_aName); + pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES); // Hostname + str_copy(m_CurrentServerInfo.m_aMap, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES)); + str_copy(m_CurrentServerInfo.m_aGameType, pUnpacker->GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES)); + int Flags = pUnpacker->GetInt(); + if(Flags & SERVER_FLAG_PASSWORD) + m_CurrentServerInfo.m_Flags |= SERVER_FLAG_PASSWORD; + // ddnets http master server handles timescore for us already + // if(Flags&SERVER_FLAG_TIMESCORE) + // m_CurrentServerInfo.m_Flags |= SERVER_FLAG_TIMESCORE; + pUnpacker->GetInt(); // Server level + m_CurrentServerInfo.m_NumPlayers = pUnpacker->GetInt(); + m_CurrentServerInfo.m_MaxPlayers = pUnpacker->GetInt(); + m_CurrentServerInfo.m_NumClients = pUnpacker->GetInt(); + m_CurrentServerInfo.m_MaxClients = pUnpacker->GetInt(); + return 0; + } + else if(*pMsgId == protocol7::NETMSG_RCON_AUTH_ON) + { + *pMsgId = NETMSG_RCON_AUTH_STATUS; + pPacker->AddInt(1); // authed + pPacker->AddInt(1); // cmdlist + return 0; + } + else if(*pMsgId == protocol7::NETMSG_RCON_AUTH_OFF) + { + *pMsgId = NETMSG_RCON_AUTH_STATUS; + pPacker->AddInt(0); // authed + pPacker->AddInt(0); // cmdlist + return 0; + } + else if(*pMsgId == protocol7::NETMSG_MAP_DATA) + { + // not binary compatible but translation happens on unpack + *pMsgId = NETMSG_MAP_DATA; + } + else if(*pMsgId >= protocol7::NETMSG_CON_READY && *pMsgId <= protocol7::NETMSG_INPUTTIMING) + { + *pMsgId = *pMsgId - 1; + } + else if(*pMsgId == protocol7::NETMSG_RCON_LINE) + { + *pMsgId = NETMSG_RCON_LINE; + } + else if(*pMsgId == protocol7::NETMSG_RCON_CMD_ADD) + { + *pMsgId = NETMSG_RCON_CMD_ADD; + } + else if(*pMsgId == protocol7::NETMSG_RCON_CMD_REM) + { + *pMsgId = NETMSG_RCON_CMD_REM; + } + else if(*pMsgId == protocol7::NETMSG_PING_REPLY) + { + *pMsgId = NETMSG_PING_REPLY; + } + else if(*pMsgId == protocol7::NETMSG_MAPLIST_ENTRY_ADD || *pMsgId == protocol7::NETMSG_MAPLIST_ENTRY_REM) + { + // This is just a nice to have so silently dropping that is fine + return -1; + } + else if(*pMsgId >= NETMSG_INFO && *pMsgId <= NETMSG_MAP_DATA) + { + return -1; // same in 0.6 and 0.7 + } + else if(*pMsgId < OFFSET_UUID) + { + dbg_msg("sixup", "drop unknown sys msg=%d", *pMsgId); + return -1; + } + + return -1; +} diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index f9df0aad1..417807e84 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -136,6 +136,48 @@ MACRO_CONFIG_INT(ClPlayerDefaultEyes, player_default_eyes, 0, 0, 5, CFGFLAG_CLIE MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)") MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins") +MACRO_CONFIG_COL(ClPlayer7ColorBody, player7_color_body, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player body color") +MACRO_CONFIG_COL(ClPlayer7ColorFeet, player7_color_feet, 0x1C873E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player feet color") + +MACRO_CONFIG_INT(ClPlayer7ColorMarking, player7_color_marking, 0xFF0000FF, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player marking color") +MACRO_CONFIG_COL(ClPlayer7ColorDecoration, player7_color_decoration, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player decoration color") +MACRO_CONFIG_COL(ClPlayer7ColorHands, player7_color_hands, 0x1B759E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player hands color") +MACRO_CONFIG_COL(ClPlayer7ColorEyes, player7_color_eyes, 0x0000FF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Player eyes color") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorBody, player7_use_custom_color_body, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for body") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorMarking, player7_use_custom_color_marking, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for marking") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorDecoration, player7_use_custom_color_decoration, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for decoration") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorHands, player7_use_custom_color_hands, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for hands") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorFeet, player7_use_custom_color_feet, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for feet") +MACRO_CONFIG_INT(ClPlayer7UseCustomColorEyes, player7_use_custom_color_eyes, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for eyes") +MACRO_CONFIG_STR(ClPlayer7Skin, player7_skin, protocol7::MAX_SKIN_ARRAY_SIZE, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin") +MACRO_CONFIG_STR(ClPlayer7SkinBody, player7_skin_body, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin body") +MACRO_CONFIG_STR(ClPlayer7SkinMarking, player7_skin_marking, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin marking") +MACRO_CONFIG_STR(ClPlayer7SkinDecoration, player7_skin_decoration, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin decoration") +MACRO_CONFIG_STR(ClPlayer7SkinHands, player7_skin_hands, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin hands") +MACRO_CONFIG_STR(ClPlayer7SkinFeet, player7_skin_feet, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin feet") +MACRO_CONFIG_STR(ClPlayer7SkinEyes, player7_skin_eyes, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Player skin eyes") + +MACRO_CONFIG_COL(ClDummy7ColorBody, dummy7_color_body, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy body color") +MACRO_CONFIG_COL(ClDummy7ColorFeet, dummy7_color_feet, 0x1C873E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy feet color") + +MACRO_CONFIG_COL(ClDummy7ColorMarking, dummy7_color_marking, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy marking color") +MACRO_CONFIG_COL(ClDummy7ColorDecoration, dummy7_color_decoration, 0x1B6F74, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy decoration color") +MACRO_CONFIG_COL(ClDummy7ColorHands, dummy7_color_hands, 0x1B759E, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy hands color") +MACRO_CONFIG_COL(ClDummy7ColorEyes, dummy7_color_eyes, 0x0000FF, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLLIGHT | CFGFLAG_INSENSITIVE, "Dummy eyes color") +MACRO_CONFIG_INT(ClDummy7UseCustomColorBody, dummy7_use_custom_color_body, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for body") +MACRO_CONFIG_INT(ClDummy7UseCustomColorMarking, dummy7_use_custom_color_marking, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for marking") +MACRO_CONFIG_INT(ClDummy7UseCustomColorDecoration, dummy7_use_custom_color_decoration, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for decoration") +MACRO_CONFIG_INT(ClDummy7UseCustomColorHands, dummy7_use_custom_color_hands, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for hands") +MACRO_CONFIG_INT(ClDummy7UseCustomColorFeet, dummy7_use_custom_color_feet, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for feet") +MACRO_CONFIG_INT(ClDummy7UseCustomColorEyes, dummy7_use_custom_color_eyes, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Toggles usage of custom colors for eyes") +MACRO_CONFIG_STR(ClDummy7Skin, dummy7_skin, protocol7::MAX_SKIN_ARRAY_SIZE, "default", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin") +MACRO_CONFIG_STR(ClDummy7SkinBody, dummy7_skin_body, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin body") +MACRO_CONFIG_STR(ClDummy7SkinMarking, dummy7_skin_marking, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin marking") +MACRO_CONFIG_STR(ClDummy7SkinDecoration, dummy7_skin_decoration, protocol7::MAX_SKIN_ARRAY_SIZE, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin decoration") +MACRO_CONFIG_STR(ClDummy7SkinHands, dummy7_skin_hands, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin hands") +MACRO_CONFIG_STR(ClDummy7SkinFeet, dummy7_skin_feet, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin feet") +MACRO_CONFIG_STR(ClDummy7SkinEyes, dummy7_skin_eyes, protocol7::MAX_SKIN_ARRAY_SIZE, "standard", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Dummy skin eyes") + MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 13, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page") MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page") MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page") diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp index 5e7566b24..21678de73 100644 --- a/src/engine/shared/demo.cpp +++ b/src/engine/shared/demo.cpp @@ -677,7 +677,7 @@ void CDemoPlayer::DoTick() { // process delta snapshot CSnapshot *pNewsnap = (CSnapshot *)m_aSnapshot; - DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, pNewsnap, m_aChunkData, DataSize); + DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, pNewsnap, m_aChunkData, DataSize, IsSixup()); if(DataSize < 0) { @@ -1243,7 +1243,7 @@ bool CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int CDemoRecorder DemoRecorder(m_pSnapshotDelta); unsigned char *pMapData = DemoPlayer.GetMapData(m_pStorage); - const int Result = DemoRecorder.Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, Sha256, pMapInfo->m_Crc, pInfo->m_Header.m_aType, pMapInfo->m_Size, pMapData, nullptr, pfnFilter, pUser) == -1; + const int Result = DemoRecorder.Start(m_pStorage, m_pConsole, pDst, pInfo->m_Header.m_aNetversion, pMapInfo->m_aName, Sha256, pMapInfo->m_Crc, pInfo->m_Header.m_aType, pMapInfo->m_Size, pMapData, nullptr, pfnFilter, pUser) == -1; free(pMapData); if(Result != 0) { diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h index f1ecc21d4..5792429f1 100644 --- a/src/engine/shared/demo.h +++ b/src/engine/shared/demo.h @@ -145,6 +145,7 @@ private: bool ScanFile(); int64_t Time(); + bool m_Sixup; public: CDemoPlayer(class CSnapshotDelta *pSnapshotDelta, bool UseVideo); @@ -176,6 +177,8 @@ public: const char *ErrorMessage() const override { return m_aErrorMessage; } int Update(bool RealTime = true); + bool IsSixup() const { return m_Sixup; } + void SetSixup(bool Sixup) { m_Sixup = Sixup; } const CPlaybackInfo *Info() const { return &m_Info; } bool IsPlaying() const override { return m_File != nullptr; } diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp index 40e2b5dff..d427c153d 100644 --- a/src/engine/shared/network.cpp +++ b/src/engine/shared/network.cpp @@ -298,6 +298,18 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct return -1; } + // set the response token (a bit hacky because this function shouldn't know about control packets) + if(pPacket->m_Flags & NET_PACKETFLAG_CONTROL) + { + if(pPacket->m_DataSize >= 5) // control byte + token + { + if(pPacket->m_aChunkData[0] == NET_CTRLMSG_CONNECT || pPacket->m_aChunkData[0] == NET_CTRLMSG_TOKEN) + { + *pResponseToken = ToSecurityToken(&pPacket->m_aChunkData[1]); + } + } + } + // log the data if(ms_DataLogRecv) { @@ -327,6 +339,19 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken, Sixup, true); } +void CNetBase::SendControlMsgWithToken7(NETSOCKET Socket, NETADDR *pAddr, TOKEN Token, int Ack, int ControlMsg, TOKEN MyToken, bool Extended) +{ + dbg_assert((Token & ~NET_TOKEN_MASK) == 0, "token out of range"); + dbg_assert((MyToken & ~NET_TOKEN_MASK) == 0, "resp token out of range"); + + unsigned char s_aRequestTokenBuf[NET_TOKENREQUEST_DATASIZE]; + s_aRequestTokenBuf[0] = (MyToken >> 24) & 0xff; + s_aRequestTokenBuf[1] = (MyToken >> 16) & 0xff; + s_aRequestTokenBuf[2] = (MyToken >> 8) & 0xff; + s_aRequestTokenBuf[3] = (MyToken)&0xff; + CNetBase::SendControlMsg(Socket, pAddr, 0, ControlMsg, s_aRequestTokenBuf, Extended ? sizeof(s_aRequestTokenBuf) : 4, Token, true); +} + unsigned char *CNetChunkHeader::Pack(unsigned char *pData, int Split) const { pData[0] = ((m_Flags & 3) << 6) | ((m_Size >> Split) & 0x3f); diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 2e2ec65c6..7cea700c1 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -65,10 +65,11 @@ enum NET_SEQUENCE_MASK = NET_MAX_SEQUENCE - 1, NET_CONNSTATE_OFFLINE = 0, - NET_CONNSTATE_CONNECT = 1, - NET_CONNSTATE_PENDING = 2, - NET_CONNSTATE_ONLINE = 3, - NET_CONNSTATE_ERROR = 4, + NET_CONNSTATE_TOKEN = 1, + NET_CONNSTATE_CONNECT = 2, + NET_CONNSTATE_PENDING = 3, + NET_CONNSTATE_ONLINE = 4, + NET_CONNSTATE_ERROR = 5, NET_PACKETFLAG_UNUSED = 1 << 0, NET_PACKETFLAG_TOKEN = 1 << 1, @@ -87,6 +88,7 @@ enum NET_CTRLMSG_CONNECTACCEPT = 2, NET_CTRLMSG_ACCEPT = 3, NET_CTRLMSG_CLOSE = 4, + NET_CTRLMSG_TOKEN = 5, NET_CONN_BUFFERSIZE = 1024 * 32, @@ -94,8 +96,17 @@ enum NET_ENUM_TERMINATOR }; +enum +{ + NET_TOKEN_MAX = 0xffffffff, + NET_TOKEN_NONE = NET_TOKEN_MAX, + NET_TOKEN_MASK = NET_TOKEN_MAX, + + NET_TOKENREQUEST_DATASIZE = 512, +}; typedef int SECURITY_TOKEN; +typedef unsigned int TOKEN; SECURITY_TOKEN ToSecurityToken(const unsigned char *pData); void WriteSecurityToken(unsigned char *pData, SECURITY_TOKEN Token); @@ -217,7 +228,10 @@ private: unsigned short m_PeerAck; unsigned m_State; +public: SECURITY_TOKEN m_SecurityToken; + +private: int m_RemoteClosed; bool m_BlockCloseMsg; bool m_UnknownSeq; @@ -239,6 +253,10 @@ private: NETSTATS m_Stats; char m_aPeerAddrStr[NETADDR_MAXSTRSIZE]; + // client 0.7 + static TOKEN GenerateToken7(const NETADDR *pPeerAddr); + class CNetBase *m_pNetBase; + bool IsSixup() { return m_Sixup; } // void ResetStats(); @@ -248,6 +266,7 @@ private: int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence); void SendConnect(); void SendControl(int ControlMsg, const void *pExtra, int ExtraSize); + void SendControlWithToken7(int ControlMsg, SECURITY_TOKEN ResponseToken); void ResendChunk(CNetChunkResend *pResend); void Resend(); @@ -255,15 +274,18 @@ public: bool m_TimeoutProtected; bool m_TimeoutSituation; + void SetToken7(TOKEN Token); + void Reset(bool Rejoin = false); void Init(NETSOCKET Socket, bool BlockCloseMsg); int Connect(const NETADDR *pAddr, int NumAddrs); + int Connect7(const NETADDR *pAddr, int NumAddrs); void Disconnect(const char *pReason); int Update(); int Flush(); - int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED); + int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED, SECURITY_TOKEN ResponseToken = NET_SECURITY_TOKEN_UNSUPPORTED); int QueueChunk(int Flags, int DataSize, const void *pData); const char *ErrorString(); @@ -501,9 +523,10 @@ public: // connection state int Disconnect(const char *pReason); int Connect(const NETADDR *pAddr, int NumAddrs); + int Connect7(const NETADDR *pAddr, int NumAddrs); // communication - int Recv(CNetChunk *pChunk); + int Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken, bool Sixup); int Send(CNetChunk *pChunk); // pumping @@ -541,6 +564,7 @@ public: static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize); static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken, bool Sixup = false); + static void SendControlMsgWithToken7(NETSOCKET Socket, NETADDR *pAddr, TOKEN Token, int Ack, int ControlMsg, TOKEN MyToken, bool Extended); static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize, bool Extended, unsigned char aExtra[4]); static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken, bool Sixup = false, bool NoCompress = false); diff --git a/src/engine/shared/network_client.cpp b/src/engine/shared/network_client.cpp index 934305908..7d8027215 100644 --- a/src/engine/shared/network_client.cpp +++ b/src/engine/shared/network_client.cpp @@ -56,13 +56,19 @@ int CNetClient::Connect(const NETADDR *pAddr, int NumAddrs) return 0; } +int CNetClient::Connect7(const NETADDR *pAddr, int NumAddrs) +{ + m_Connection.Connect7(pAddr, NumAddrs); + return 0; +} + int CNetClient::ResetErrorString() { m_Connection.ResetErrorString(); return 0; } -int CNetClient::Recv(CNetChunk *pChunk) +int CNetClient::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken, bool Sixup) { while(true) { @@ -83,9 +89,12 @@ int CNetClient::Recv(CNetChunk *pChunk) { continue; } + if(Sixup) + Addr.type |= NETTYPE_TW7; - bool Sixup = false; - if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup) == 0) + SECURITY_TOKEN Token; + *pResponseToken = NET_SECURITY_TOKEN_UNKNOWN; + if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token, pResponseToken) == 0) { if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS) { @@ -103,7 +112,7 @@ int CNetClient::Recv(CNetChunk *pChunk) } else { - if(m_Connection.State() != NET_CONNSTATE_OFFLINE && m_Connection.State() != NET_CONNSTATE_ERROR && m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + if(m_Connection.State() != NET_CONNSTATE_OFFLINE && m_Connection.State() != NET_CONNSTATE_ERROR && m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token, *pResponseToken)) m_RecvUnpacker.Start(&Addr, &m_Connection, 0); } } diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp index 594152353..2826b1a6e 100644 --- a/src/engine/shared/network_conn.cpp +++ b/src/engine/shared/network_conn.cpp @@ -210,6 +210,51 @@ int CNetConnection::Connect(const NETADDR *pAddr, int NumAddrs) return 0; } +void CNetConnection::SendControlWithToken7(int ControlMsg, SECURITY_TOKEN ResponseToken) +{ + m_LastSendTime = time_get(); + + CNetBase::SendControlMsgWithToken7(m_Socket, &m_PeerAddr, ResponseToken, 0, ControlMsg, m_Token, true); +} + +int CNetConnection::Connect7(const NETADDR *pAddr, int NumAddrs) +{ + if(State() != NET_CONNSTATE_OFFLINE) + return -1; + + // init connection + Reset(); + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + for(int i = 0; i < NumAddrs; i++) + { + m_aConnectAddrs[i] = pAddr[i]; + } + m_LastRecvTime = time_get(); + m_NumConnectAddrs = NumAddrs; + m_PeerAddr = *pAddr; + SetToken7(GenerateToken7(pAddr)); + mem_zero(m_aErrorString, sizeof(m_aErrorString)); + m_State = NET_CONNSTATE_TOKEN; + SendControlWithToken7(NET_CTRLMSG_TOKEN, NET_TOKEN_NONE); + m_Sixup = true; + return 0; +} + +void CNetConnection::SetToken7(TOKEN Token) +{ + if(State() != NET_CONNSTATE_OFFLINE) + return; + + m_Token = Token; +} + +TOKEN CNetConnection::GenerateToken7(const NETADDR *pPeerAddr) +{ + TOKEN Token; + secure_random_fill(&Token, sizeof(Token)); + return Token; +} + void CNetConnection::Disconnect(const char *pReason) { if(State() == NET_CONNSTATE_OFFLINE) @@ -256,7 +301,7 @@ void CNetConnection::DirectInit(const NETADDR &Addr, SECURITY_TOKEN SecurityToke m_Sixup = Sixup; } -int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken) +int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken, SECURITY_TOKEN ResponseToken) { // Disregard packets from the wrong address, unless we don't know our peer yet. if(State() != NET_CONNSTATE_OFFLINE && State() != NET_CONNSTATE_CONNECT && *pAddr != m_PeerAddr) @@ -346,63 +391,80 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_ } else { - if(State() == NET_CONNSTATE_OFFLINE) + if(CtrlMsg == NET_CTRLMSG_TOKEN) { - if(CtrlMsg == NET_CTRLMSG_CONNECT) + if(State() == NET_CONNSTATE_TOKEN) { - if(net_addr_comp_noport(&m_PeerAddr, pAddr) == 0 && time_get() - m_LastUpdateTime < time_freq() * 3) - return 0; - - // send response and init connection - Reset(); - m_State = NET_CONNSTATE_PENDING; - m_PeerAddr = *pAddr; - net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true); - mem_zero(m_aErrorString, sizeof(m_aErrorString)); - m_LastSendTime = Now; m_LastRecvTime = Now; - m_LastUpdateTime = Now; - if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) - { - m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; - if(g_Config.m_Debug) - dbg_msg("security", "generated token %d", m_SecurityToken); - } - else - { - if(g_Config.m_Debug) - dbg_msg("security", "token not supported by client (packet size %d)", pPacket->m_DataSize); - m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; - } - SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); - if(g_Config.m_Debug) - dbg_msg("connection", "got connection, sending connect+accept"); + m_State = NET_CONNSTATE_CONNECT; + m_SecurityToken = ResponseToken; + SendControlWithToken7(NET_CTRLMSG_CONNECT, m_SecurityToken); + dbg_msg("connection", "got token, replying, token=%x mytoken=%x", m_SecurityToken, m_Token); } + else if(g_Config.m_Debug) + dbg_msg("connection", "got token, token=%x", ResponseToken); } - else if(State() == NET_CONNSTATE_CONNECT) + else { - // connection made - if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) + if(State() == NET_CONNSTATE_OFFLINE) { - m_PeerAddr = *pAddr; - net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true); - if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) + if(CtrlMsg == NET_CTRLMSG_CONNECT) { - m_SecurityToken = ToSecurityToken(&pPacket->m_aChunkData[1 + sizeof(SECURITY_TOKEN_MAGIC)]); + if(net_addr_comp_noport(&m_PeerAddr, pAddr) == 0 && time_get() - m_LastUpdateTime < time_freq() * 3) + return 0; + + // send response and init connection + Reset(); + m_State = NET_CONNSTATE_PENDING; + m_PeerAddr = *pAddr; + net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true); + mem_zero(m_aErrorString, sizeof(m_aErrorString)); + m_LastSendTime = Now; + m_LastRecvTime = Now; + m_LastUpdateTime = Now; + if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) + { + m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + if(g_Config.m_Debug) + dbg_msg("security", "generated token %d", m_SecurityToken); + } + else + { + if(g_Config.m_Debug) + dbg_msg("security", "token not supported by client (packet size %d)", pPacket->m_DataSize); + m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + } + SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); if(g_Config.m_Debug) - dbg_msg("security", "got token %d", m_SecurityToken); + dbg_msg("connection", "got connection, sending connect+accept"); } - else + } + else if(State() == NET_CONNSTATE_CONNECT) + { + // connection made + if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) { - m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + m_PeerAddr = *pAddr; + net_addr_str(pAddr, m_aPeerAddrStr, sizeof(m_aPeerAddrStr), true); + if(m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken)) && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) + { + m_SecurityToken = ToSecurityToken(&pPacket->m_aChunkData[1 + sizeof(SECURITY_TOKEN_MAGIC)]); + if(g_Config.m_Debug) + dbg_msg("security", "got token %d", m_SecurityToken); + } + else if(!IsSixup()) + { + m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + if(g_Config.m_Debug) + dbg_msg("security", "token not supported by server"); + } + if(!IsSixup()) + SendControl(NET_CTRLMSG_ACCEPT, 0, 0); + m_LastRecvTime = Now; + m_State = NET_CONNSTATE_ONLINE; if(g_Config.m_Debug) - dbg_msg("security", "token not supported by server"); + dbg_msg("connection", "got connect+accept, sending accept. connection online"); } - m_LastRecvTime = Now; - SendControl(NET_CTRLMSG_ACCEPT, 0, 0); - m_State = NET_CONNSTATE_ONLINE; - if(g_Config.m_Debug) - dbg_msg("connection", "got connect+accept, sending accept. connection online"); } } } diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index 682d2351d..e72bc2000 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -640,7 +640,6 @@ int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken) SECURITY_TOKEN Token; bool Sixup = false; - *pResponseToken = NET_SECURITY_TOKEN_UNKNOWN; if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token, pResponseToken) == 0) { if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS) @@ -685,7 +684,7 @@ int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken) if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONTROL) OnConnCtrlMsg(Addr, Slot, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data); - if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token)) + if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token, *pResponseToken)) { if(m_RecvUnpacker.m_Data.m_DataSize) m_RecvUnpacker.Start(&Addr, &m_aSlots[Slot].m_Connection, Slot); diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h index 6c00a487b..e0f807b10 100644 --- a/src/engine/shared/protocol.h +++ b/src/engine/shared/protocol.h @@ -4,6 +4,7 @@ #define ENGINE_SHARED_PROTOCOL_H #include +#include /* Connection diagram - How the initialization works. diff --git a/src/engine/shared/protocol7.h b/src/engine/shared/protocol7.h index c8524ef78..73bbe92fd 100644 --- a/src/engine/shared/protocol7.h +++ b/src/engine/shared/protocol7.h @@ -3,6 +3,8 @@ #ifndef ENGINE_SHARED_PROTOCOL7_H #define ENGINE_SHARED_PROTOCOL7_H +#include + namespace protocol7 { enum @@ -52,6 +54,16 @@ enum NETMSG_MAPLIST_ENTRY_REM, }; -} +enum +{ + MAX_NAME_LENGTH = 16, + MAX_NAME_ARRAY_SIZE = MAX_NAME_LENGTH * UTF8_BYTE_LENGTH + 1, + MAX_CLAN_LENGTH = 12, + MAX_CLAN_ARRAY_SIZE = MAX_CLAN_LENGTH * UTF8_BYTE_LENGTH + 1, + MAX_SKIN_LENGTH = 24, + MAX_SKIN_ARRAY_SIZE = MAX_SKIN_LENGTH * UTF8_BYTE_LENGTH + 1, +}; + +} // namespace protocol7 #endif diff --git a/src/engine/shared/protocolglue.cpp b/src/engine/shared/protocolglue.cpp new file mode 100644 index 000000000..e6bc23f95 --- /dev/null +++ b/src/engine/shared/protocolglue.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "protocolglue.h" + +int PlayerFlags_SevenToSix(int Flags) +{ + int Six = 0; + if(Flags & protocol7::PLAYERFLAG_CHATTING) + Six |= PLAYERFLAG_CHATTING; + if(Flags & protocol7::PLAYERFLAG_SCOREBOARD) + Six |= PLAYERFLAG_SCOREBOARD; + if(Flags & protocol7::PLAYERFLAG_AIM) + Six |= PLAYERFLAG_AIM; + return Six; +} + +int PlayerFlags_SixToSeven(int Flags) +{ + int Seven = 0; + if(Flags & PLAYERFLAG_CHATTING) + Seven |= protocol7::PLAYERFLAG_CHATTING; + if(Flags & PLAYERFLAG_SCOREBOARD) + Seven |= protocol7::PLAYERFLAG_SCOREBOARD; + if(Flags & PLAYERFLAG_AIM) + Seven |= protocol7::PLAYERFLAG_AIM; + return Seven; +} + +void PickupType_SevenToSix(int Type7, int &Type6, int &SubType6) +{ + SubType6 = 0; + Type6 = POWERUP_WEAPON; + switch(Type7) + { + case protocol7::PICKUP_HEALTH: + case protocol7::PICKUP_ARMOR: + Type6 = Type7; + break; + case protocol7::PICKUP_GRENADE: + SubType6 = WEAPON_GRENADE; + break; + case protocol7::PICKUP_SHOTGUN: + SubType6 = WEAPON_SHOTGUN; + break; + case protocol7::PICKUP_LASER: + SubType6 = WEAPON_LASER; + break; + case protocol7::PICKUP_GUN: + SubType6 = WEAPON_GUN; + break; + case protocol7::PICKUP_HAMMER: + SubType6 = WEAPON_HAMMER; + break; + case protocol7::PICKUP_NINJA: + SubType6 = WEAPON_NINJA; + Type6 = POWERUP_NINJA; + break; + default: + // dbg_msg("sixup", "ERROR: failed to translate weapon=%d to 0.6", Type7); + break; + } +} + +int PickupType_SixToSeven(int Type6, int SubType6) +{ + if(Type6 == POWERUP_WEAPON) + return SubType6 == WEAPON_SHOTGUN ? protocol7::PICKUP_SHOTGUN : SubType6 == WEAPON_GRENADE ? protocol7::PICKUP_GRENADE : protocol7::PICKUP_LASER; + else if(Type6 == POWERUP_NINJA) + return protocol7::PICKUP_NINJA; + else if(Type6 == POWERUP_ARMOR) + return protocol7::PICKUP_ARMOR; + return 0; +} diff --git a/src/engine/shared/protocolglue.h b/src/engine/shared/protocolglue.h new file mode 100644 index 000000000..8fa2f13dd --- /dev/null +++ b/src/engine/shared/protocolglue.h @@ -0,0 +1,9 @@ +#ifndef ENGINE_SHARED_PROTOCOLGLUE_H +#define ENGINE_SHARED_PROTOCOLGLUE_H + +int PlayerFlags_SevenToSix(int Flags); +int PlayerFlags_SixToSeven(int Flags); +void PickupType_SevenToSix(int Type7, int &Type6, int &SubType6); +int PickupType_SixToSeven(int Type6, int SubType6); + +#endif diff --git a/src/engine/shared/sixup_translate_snapshot.cpp b/src/engine/shared/sixup_translate_snapshot.cpp new file mode 100644 index 000000000..d90925d1c --- /dev/null +++ b/src/engine/shared/sixup_translate_snapshot.cpp @@ -0,0 +1,516 @@ +#include "compression.h" +#include "snapshot.h" +#include "uuid_manager.h" + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +int CSnapshot::TranslateSevenToSix( + CSnapshot *pSixSnapDest, + CTranslationContext &TranslationContext, + float LocalTime, + int GameTick, + int Conn, + bool Dummy, + protocol7::CNetObjHandler *pNetObjHandler, + CNetObjHandler *pNetObjHandler6) +{ + CSnapshotBuilder Builder; + Builder.Init(); + + for(auto &PlayerInfosRace : TranslationContext.m_apPlayerInfosRace) + PlayerInfosRace = nullptr; + + int SpectatorId = -3; + + for(int i = 0; i < m_NumItems; i++) + { + const CSnapshotItem *pItem7 = (CSnapshotItem *)GetItem(i); + const int Size = GetItemSize(i); + const int ItemType = GetItemType(i); + + // ddnet ex snap items + if((ItemType > __NETOBJTYPE_UUID_HELPER && ItemType < OFFSET_NETMSGTYPE_UUID) || pItem7->Type() == NETOBJTYPE_EX) + { + CUnpacker Unpacker; + Unpacker.Reset(pItem7->Data(), Size); + + void *pRawObj = pNetObjHandler6->SecureUnpackObj(ItemType, &Unpacker); + if(!pRawObj) + { + if(ItemType != UUID_UNKNOWN) + dbg_msg("sixup", "dropped weird ddnet ex object '%s' (%d), failed on '%s'", pNetObjHandler6->GetObjName(ItemType), ItemType, pNetObjHandler6->FailedObjOn()); + continue; + } + const int ItemSize = pNetObjHandler6->GetUnpackedObjSize(ItemType); + + void *pObj = Builder.NewItem(pItem7->Type(), pItem7->Id(), ItemSize); + if(!pObj) + return -17; + + mem_copy(pObj, pRawObj, ItemSize); + continue; + } + + if(pNetObjHandler->ValidateObj(pItem7->Type(), pItem7->Data(), Size) != 0) + { + if(pItem7->Type() > 0 && pItem7->Type() < OFFSET_UUID_TYPE) + { + dbg_msg( + "sixup", + "invalidated index=%d type=%d (%s) size=%d id=%d", + i, + pItem7->Type(), + pNetObjHandler->GetObjName(pItem7->Type()), + Size, + pItem7->Id()); + } + InvalidateItem(i); + } + + if(pItem7->Type() == protocol7::NETOBJTYPE_PLAYERINFORACE) + { + const protocol7::CNetObj_PlayerInfoRace *pInfo = (const protocol7::CNetObj_PlayerInfoRace *)pItem7->Data(); + int ClientId = pItem7->Id(); + if(ClientId < MAX_CLIENTS && TranslationContext.m_aClients[ClientId].m_Active) + { + TranslationContext.m_apPlayerInfosRace[ClientId] = pInfo; + } + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_SPECTATORINFO) + { + const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data(); + SpectatorId = pSpec7->m_SpectatorId; + if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW) + SpectatorId = SPEC_FREEVIEW; + } + } + + // hack to put game info in the snap + // even though in 0.7 we get it as a game message + // this message has to be on the top + // the client looks at the items in order and needs the gameinfo at the top + // otherwise it will not render skins with team colors + if(TranslationContext.m_ShouldSendGameInfo) + { + void *pObj = Builder.NewItem(NETOBJTYPE_GAMEINFO, 0, sizeof(CNetObj_GameInfo)); + if(!pObj) + return -1; + + int GameStateFlagsSix = 0; + if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_GAMEOVER) + GameStateFlagsSix |= GAMESTATEFLAG_GAMEOVER; + if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_SUDDENDEATH) + GameStateFlagsSix |= GAMESTATEFLAG_SUDDENDEATH; + if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_PAUSED) + GameStateFlagsSix |= GAMESTATEFLAG_PAUSED; + + /* + This is a 0.7 only flag that we just ignore for now + + GAMESTATEFLAG_ROUNDOVER + */ + + CNetObj_GameInfo Info6 = {}; + Info6.m_GameFlags = TranslationContext.m_GameFlags; + Info6.m_GameStateFlags = GameStateFlagsSix; + Info6.m_RoundStartTick = TranslationContext.m_GameStartTick7; + Info6.m_WarmupTimer = 0; // removed in 0.7 + if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_WARMUP) + Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick; + if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_STARTCOUNTDOWN) + Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick; + + // hack to port 0.7 race timer to ddnet warmup gametimer hack + int TimerClientId = clamp(TranslationContext.m_aLocalClientId[Conn], 0, (int)MAX_CLIENTS); + if(SpectatorId >= 0) + TimerClientId = SpectatorId; + const protocol7::CNetObj_PlayerInfoRace *pRaceInfo = TranslationContext.m_apPlayerInfosRace[TimerClientId]; + if(pRaceInfo) + { + Info6.m_WarmupTimer = -pRaceInfo->m_RaceStartTick; + Info6.m_GameStateFlags |= GAMESTATEFLAG_RACETIME; + } + + Info6.m_ScoreLimit = TranslationContext.m_ScoreLimit; + Info6.m_TimeLimit = TranslationContext.m_TimeLimit; + Info6.m_RoundNum = TranslationContext.m_MatchNum; + Info6.m_RoundCurrent = TranslationContext.m_MatchCurrent; + + mem_copy(pObj, &Info6, sizeof(CNetObj_GameInfo)); + } + + for(int i = 0; i < MAX_CLIENTS; i++) + { + const CTranslationContext::CClientData &Client = TranslationContext.m_aClients[i]; + if(!Client.m_Active) + continue; + + void *pObj = Builder.NewItem(NETOBJTYPE_CLIENTINFO, i, sizeof(CNetObj_ClientInfo)); + if(!pObj) + return -2; + + CNetObj_ClientInfo Info6 = {}; + StrToInts(&Info6.m_Name0, 4, Client.m_aName); + StrToInts(&Info6.m_Clan0, 3, Client.m_aClan); + Info6.m_Country = Client.m_Country; + StrToInts(&Info6.m_Skin0, 6, Client.m_aSkinName); + Info6.m_UseCustomColor = Client.m_UseCustomColor; + Info6.m_ColorBody = Client.m_ColorBody; + Info6.m_ColorFeet = Client.m_ColorFeet; + mem_copy(pObj, &Info6, sizeof(CNetObj_ClientInfo)); + } + + bool NewGameData = false; + + for(int i = 0; i < m_NumItems; i++) + { + const CSnapshotItem *pItem7 = GetItem(i); + const int Size = GetItemSize(i); + // the first few items are a full match + // no translation needed + if(pItem7->Type() == protocol7::NETOBJTYPE_PROJECTILE || + pItem7->Type() == protocol7::NETOBJTYPE_LASER || + pItem7->Type() == protocol7::NETOBJTYPE_FLAG) + { + void *pObj = Builder.NewItem(pItem7->Type(), pItem7->Id(), Size); + if(!pObj) + return -4; + + mem_copy(pObj, pItem7->Data(), Size); + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_PICKUP) + { + void *pObj = Builder.NewItem(NETOBJTYPE_PICKUP, pItem7->Id(), sizeof(CNetObj_Pickup)); + if(!pObj) + return -5; + + const protocol7::CNetObj_Pickup *pPickup7 = (const protocol7::CNetObj_Pickup *)pItem7->Data(); + CNetObj_Pickup Pickup6 = {}; + Pickup6.m_X = pPickup7->m_X; + Pickup6.m_Y = pPickup7->m_Y; + PickupType_SevenToSix(pPickup7->m_Type, Pickup6.m_Type, Pickup6.m_Subtype); + + mem_copy(pObj, &Pickup6, sizeof(CNetObj_Pickup)); + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATA) + { + const protocol7::CNetObj_GameData *pGameData = (const protocol7::CNetObj_GameData *)pItem7->Data(); + TranslationContext.m_GameStateFlags7 = pGameData->m_GameStateFlags; + TranslationContext.m_GameStartTick7 = pGameData->m_GameStartTick; + TranslationContext.m_GameStateEndTick7 = pGameData->m_GameStateEndTick; + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATATEAM) + { + // 0.7 added GameDataTeam and GameDataFlag + // both items merged together have all fields of the 0.6 GameData + // so if we get either one of them we store the details in the translation context + // and build one GameData snap item after this loop + const protocol7::CNetObj_GameDataTeam *pTeam7 = (const protocol7::CNetObj_GameDataTeam *)pItem7->Data(); + + TranslationContext.m_TeamscoreRed = pTeam7->m_TeamscoreRed; + TranslationContext.m_TeamscoreBlue = pTeam7->m_TeamscoreBlue; + NewGameData = true; + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_GAMEDATAFLAG) + { + const protocol7::CNetObj_GameDataFlag *pFlag7 = (const protocol7::CNetObj_GameDataFlag *)pItem7->Data(); + + TranslationContext.m_FlagCarrierRed = pFlag7->m_FlagCarrierRed; + TranslationContext.m_FlagCarrierBlue = pFlag7->m_FlagCarrierBlue; + NewGameData = true; + + // used for blinking the flags in the hud + // but that already works the 0.6 way + // and is covered by the NETOBJTYPE_GAMEDATA translation + // pFlag7->m_FlagDropTickRed; + // pFlag7->m_FlagDropTickBlue; + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_CHARACTER) + { + void *pObj = Builder.NewItem(NETOBJTYPE_CHARACTER, pItem7->Id(), sizeof(CNetObj_Character)); + if(!pObj) + return -6; + + const protocol7::CNetObj_Character *pChar7 = (const protocol7::CNetObj_Character *)pItem7->Data(); + + CNetObj_Character Char6 = {}; + // character core is unchanged + mem_copy(&Char6, pItem7->Data(), sizeof(CNetObj_CharacterCore)); + + Char6.m_PlayerFlags = 0; + if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS) + Char6.m_PlayerFlags = PlayerFlags_SevenToSix(TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7); + Char6.m_Health = pChar7->m_Health; + Char6.m_Armor = pChar7->m_Armor; + Char6.m_AmmoCount = pChar7->m_AmmoCount; + Char6.m_Weapon = pChar7->m_Weapon; + Char6.m_Emote = pChar7->m_Emote; + Char6.m_AttackTick = pChar7->m_AttackTick; + + if(TranslationContext.m_aLocalClientId[Conn] != pItem7->Id()) + { + if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_GROUND_JUMP) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld)); + if(!pEvent) + return -7; + + CNetEvent_SoundWorld Sound = {}; + Sound.m_X = pChar7->m_X; + Sound.m_Y = pChar7->m_Y; + Sound.m_SoundId = SOUND_PLAYER_JUMP; + mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld)); + } + if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_PLAYER) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld)); + if(!pEvent) + return -7; + + CNetEvent_SoundWorld Sound = {}; + Sound.m_X = pChar7->m_X; + Sound.m_Y = pChar7->m_Y; + Sound.m_SoundId = SOUND_HOOK_ATTACH_PLAYER; + mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld)); + } + if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_GROUND) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld)); + if(!pEvent) + return -7; + + CNetEvent_SoundWorld Sound = {}; + Sound.m_X = pChar7->m_X; + Sound.m_Y = pChar7->m_Y; + Sound.m_SoundId = SOUND_HOOK_ATTACH_GROUND; + mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld)); + } + if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_HIT_NOHOOK) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld)); + if(!pEvent) + return -7; + + CNetEvent_SoundWorld Sound = {}; + Sound.m_X = pChar7->m_X; + Sound.m_Y = pChar7->m_Y; + Sound.m_SoundId = SOUND_HOOK_NOATTACH; + mem_copy(pEvent, &Sound, sizeof(CNetEvent_SoundWorld)); + } + } + + mem_copy(pObj, &Char6, sizeof(CNetObj_Character)); + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_PLAYERINFO) + { + void *pObj = Builder.NewItem(NETOBJTYPE_PLAYERINFO, pItem7->Id(), sizeof(CNetObj_PlayerInfo)); + if(!pObj) + return -8; + + const protocol7::CNetObj_PlayerInfo *pInfo7 = (const protocol7::CNetObj_PlayerInfo *)pItem7->Data(); + CNetObj_PlayerInfo Info6 = {}; + Info6.m_Local = TranslationContext.m_aLocalClientId[Conn] == pItem7->Id(); + Info6.m_ClientId = pItem7->Id(); + Info6.m_Team = 0; + if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS) + { + Info6.m_Team = TranslationContext.m_aClients[pItem7->Id()].m_Team; + TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7 = pInfo7->m_PlayerFlags; + } + Info6.m_Score = pInfo7->m_Score; + Info6.m_Latency = pInfo7->m_Latency; + mem_copy(pObj, &Info6, sizeof(CNetObj_PlayerInfo)); + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_SPECTATORINFO) + { + void *pObj = Builder.NewItem(NETOBJTYPE_SPECTATORINFO, pItem7->Id(), sizeof(CNetObj_SpectatorInfo)); + if(!pObj) + return -9; + + const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data(); + CNetObj_SpectatorInfo Spec6 = {}; + Spec6.m_SpectatorId = pSpec7->m_SpectatorId; + if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW) + Spec6.m_SpectatorId = SPEC_FREEVIEW; + Spec6.m_X = pSpec7->m_X; + Spec6.m_Y = pSpec7->m_Y; + mem_copy(pObj, &Spec6, sizeof(CNetObj_SpectatorInfo)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_EXPLOSION) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_EXPLOSION, pItem7->Id(), sizeof(CNetEvent_Explosion)); + if(!pEvent) + return -10; + + const protocol7::CNetEvent_Explosion *pExplosion7 = (const protocol7::CNetEvent_Explosion *)pItem7->Data(); + CNetEvent_Explosion Explosion6 = {}; + Explosion6.m_X = pExplosion7->m_X; + Explosion6.m_Y = pExplosion7->m_Y; + mem_copy(pEvent, &Explosion6, sizeof(CNetEvent_Explosion)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_SPAWN) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SPAWN, pItem7->Id(), sizeof(CNetEvent_Spawn)); + if(!pEvent) + return -11; + + const protocol7::CNetEvent_Spawn *pSpawn7 = (const protocol7::CNetEvent_Spawn *)pItem7->Data(); + CNetEvent_Spawn Spawn6 = {}; + Spawn6.m_X = pSpawn7->m_X; + Spawn6.m_Y = pSpawn7->m_Y; + mem_copy(pEvent, &Spawn6, sizeof(CNetEvent_Spawn)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_HAMMERHIT) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_HAMMERHIT, pItem7->Id(), sizeof(CNetEvent_HammerHit)); + if(!pEvent) + return -12; + + const protocol7::CNetEvent_HammerHit *pHammerHit7 = (const protocol7::CNetEvent_HammerHit *)pItem7->Data(); + CNetEvent_HammerHit HammerHit6 = {}; + HammerHit6.m_X = pHammerHit7->m_X; + HammerHit6.m_Y = pHammerHit7->m_Y; + mem_copy(pEvent, &HammerHit6, sizeof(CNetEvent_HammerHit)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_DEATH) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_DEATH, pItem7->Id(), sizeof(CNetEvent_Death)); + if(!pEvent) + return -13; + + const protocol7::CNetEvent_Death *pDeath7 = (const protocol7::CNetEvent_Death *)pItem7->Data(); + CNetEvent_Death Death6 = {}; + Death6.m_X = pDeath7->m_X; + Death6.m_Y = pDeath7->m_Y; + Death6.m_ClientId = pDeath7->m_ClientId; + mem_copy(pEvent, &Death6, sizeof(CNetEvent_Death)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_SOUNDWORLD) + { + void *pEvent = Builder.NewItem(NETEVENTTYPE_SOUNDWORLD, pItem7->Id(), sizeof(CNetEvent_SoundWorld)); + if(!pEvent) + return -14; + + const protocol7::CNetEvent_SoundWorld *pSoundWorld7 = (const protocol7::CNetEvent_SoundWorld *)pItem7->Data(); + CNetEvent_SoundWorld SoundWorld6 = {}; + SoundWorld6.m_X = pSoundWorld7->m_X; + SoundWorld6.m_Y = pSoundWorld7->m_Y; + SoundWorld6.m_SoundId = pSoundWorld7->m_SoundId; + mem_copy(pEvent, &SoundWorld6, sizeof(CNetEvent_SoundWorld)); + } + else if(pItem7->Type() == protocol7::NETEVENTTYPE_DAMAGE) + { + // 0.7 introduced amount for damage indicators + // so for one 0.7 item we might create multiple 0.6 ones + const protocol7::CNetEvent_Damage *pDmg7 = (const protocol7::CNetEvent_Damage *)pItem7->Data(); + + int Amount = pDmg7->m_HealthAmount + pDmg7->m_ArmorAmount; + if(Amount < 1) + continue; + + int ClientId = pDmg7->m_ClientId; + TranslationContext.m_aDamageTaken[ClientId]++; + + float Angle; + // create healthmod indicator + if(LocalTime < TranslationContext.m_aDamageTakenTick[ClientId] + 0.5f) + { + // make sure that the damage indicators don't group together + Angle = TranslationContext.m_aDamageTaken[ClientId] * 0.25f; + } + else + { + TranslationContext.m_aDamageTaken[ClientId] = 0; + Angle = 0; + } + + TranslationContext.m_aDamageTakenTick[ClientId] = LocalTime; + + float a = 3 * pi / 2 + Angle; + float s = a - pi / 3; + float e = a + pi / 3; + for(int k = 0; k < Amount; k++) + { + // pItem7->Id() is reused that is technically wrong + // but the client implementation does not look at the ids + // and renders the damage indicators just fine + void *pEvent = Builder.NewItem(NETEVENTTYPE_DAMAGEIND, pItem7->Id(), sizeof(CNetEvent_DamageInd)); + if(!pEvent) + return -16; + + CNetEvent_DamageInd Dmg6 = {}; + Dmg6.m_X = pDmg7->m_X; + Dmg6.m_Y = pDmg7->m_Y; + float f = mix(s, e, float(k + 1) / float(Amount + 2)); + Dmg6.m_Angle = (int)(f * 256.0f); + mem_copy(pEvent, &Dmg6, sizeof(CNetEvent_DamageInd)); + } + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_DE_CLIENTINFO) + { + const protocol7::CNetObj_De_ClientInfo *pInfo = (const protocol7::CNetObj_De_ClientInfo *)pItem7->Data(); + + const int ClientId = pItem7->Id(); + + if(ClientId < 0 || ClientId >= MAX_CLIENTS) + { + dbg_msg("sixup", "De_ClientInfo got invalid ClientId: %d", ClientId); + return -17; + } + + if(pInfo->m_Local) + { + TranslationContext.m_aLocalClientId[Conn] = ClientId; + } + CTranslationContext::CClientData &Client = TranslationContext.m_aClients[ClientId]; + Client.m_Active = true; + Client.m_Team = pInfo->m_Team; + IntsToStr(pInfo->m_aName, 4, Client.m_aName, std::size(Client.m_aName)); + IntsToStr(pInfo->m_aClan, 3, Client.m_aClan, std::size(Client.m_aClan)); + Client.m_Country = pInfo->m_Country; + + // TODO: get this method into scope + // ApplySkin7InfoFromGameMsg(pInfo, ClientId); + } + else if(pItem7->Type() == protocol7::NETOBJTYPE_DE_GAMEINFO) + { + const protocol7::CNetObj_De_GameInfo *pInfo = (const protocol7::CNetObj_De_GameInfo *)pItem7->Data(); + + TranslationContext.m_GameFlags = pInfo->m_GameFlags; + TranslationContext.m_ScoreLimit = pInfo->m_ScoreLimit; + TranslationContext.m_TimeLimit = pInfo->m_TimeLimit; + TranslationContext.m_MatchNum = pInfo->m_MatchNum; + TranslationContext.m_MatchCurrent = pInfo->m_MatchCurrent; + TranslationContext.m_ShouldSendGameInfo = true; + } + } + + if(NewGameData) + { + void *pObj = Builder.NewItem(NETOBJTYPE_GAMEDATA, 0, sizeof(CNetObj_GameData)); + if(!pObj) + return -17; + + CNetObj_GameData GameData = {}; + GameData.m_TeamscoreRed = TranslationContext.m_TeamscoreRed; + GameData.m_TeamscoreBlue = TranslationContext.m_TeamscoreBlue; + GameData.m_FlagCarrierRed = TranslationContext.m_FlagCarrierRed; + GameData.m_FlagCarrierBlue = TranslationContext.m_FlagCarrierBlue; + mem_copy(pObj, &GameData, sizeof(CNetObj_GameData)); + } + + return Builder.Finish(pSixSnapDest); +} diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 1201d94d5..76e920d6a 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -10,6 +10,7 @@ #include #include +#include #include // CSnapshot @@ -65,6 +66,11 @@ int CSnapshot::GetItemIndex(int Key) const return -1; } +void CSnapshot::InvalidateItem(int Index) +{ + ((CSnapshotItem *)(DataStart() + Offsets()[Index]))->Invalidate(); +} + const void *CSnapshot::FindItem(int Type, int Id) const { int InternalType = Type; @@ -243,6 +249,7 @@ void CSnapshotDelta::UndiffItem(const int *pPast, const int *pDiff, int *pOut, i CSnapshotDelta::CSnapshotDelta() { mem_zero(m_aItemSizes, sizeof(m_aItemSizes)); + mem_zero(m_aItemSizes7, sizeof(m_aItemSizes7)); mem_zero(m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate)); mem_zero(m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates)); mem_zero(&m_Empty, sizeof(m_Empty)); @@ -251,6 +258,7 @@ CSnapshotDelta::CSnapshotDelta() CSnapshotDelta::CSnapshotDelta(const CSnapshotDelta &Old) { mem_copy(m_aItemSizes, Old.m_aItemSizes, sizeof(m_aItemSizes)); + mem_copy(m_aItemSizes7, Old.m_aItemSizes7, sizeof(m_aItemSizes7)); mem_copy(m_aSnapshotDataRate, Old.m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate)); mem_copy(m_aSnapshotDataUpdates, Old.m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates)); mem_zero(&m_Empty, sizeof(m_Empty)); @@ -263,6 +271,13 @@ void CSnapshotDelta::SetStaticsize(int ItemType, size_t Size) m_aItemSizes[ItemType] = Size; } +void CSnapshotDelta::SetStaticsize7(int ItemType, size_t Size) +{ + dbg_assert(ItemType >= 0 && ItemType < MAX_NETOBJSIZES, "ItemType invalid"); + dbg_assert(Size <= (size_t)std::numeric_limits::max(), "Size invalid"); + m_aItemSizes7[ItemType] = Size; +} + const CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta() const { return &m_Empty; @@ -484,7 +499,7 @@ int CSnapshotDelta::DebugDumpDelta(const void *pSrcData, int DataSize) return 0; } -int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize) +int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize, bool Sixup) { CData *pDelta = (CData *)pSrcData; int *pData = (int *)pDelta->m_aData; @@ -542,8 +557,9 @@ int CSnapshotDelta::UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const vo return -203; int ItemSize; - if(Type < MAX_NETOBJSIZES && m_aItemSizes[Type]) - ItemSize = m_aItemSizes[Type]; + const short *pItemSizes = Sixup ? m_aItemSizes7 : m_aItemSizes; + if(Type < MAX_NETOBJSIZES && pItemSizes[Type]) + ItemSize = pItemSizes[Type]; else { if(pData + 1 > pEnd) diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index c745dc089..4060b9938 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -6,6 +6,9 @@ #include #include +#include +#include + // CSnapshot class CSnapshotItem @@ -21,6 +24,7 @@ public: int Type() const { return m_TypeAndId >> 16; } int Id() const { return m_TypeAndId & 0xffff; } int Key() const { return m_TypeAndId; } + void Invalidate() { m_TypeAndId = -1; } }; class CSnapshot @@ -52,12 +56,22 @@ public: const CSnapshotItem *GetItem(int Index) const; int GetItemSize(int Index) const; int GetItemIndex(int Key) const; + void InvalidateItem(int Index); int GetItemType(int Index) const; int GetExternalItemType(int InternalType) const; const void *FindItem(int Type, int Id) const; unsigned Crc() const; void DebugDump() const; + int TranslateSevenToSix( + CSnapshot *pSixSnapDest, + class CTranslationContext &TranslationContext, + float LocalTime, + int GameTick, + int Conn, + bool Dummy, + protocol7::CNetObjHandler *pNetObjHandler, + CNetObjHandler *pNetObjHandler6); bool IsValid(size_t ActualSize) const; static const CSnapshot *EmptySnapshot() { return &ms_EmptySnapshot; } @@ -83,6 +97,7 @@ private: MAX_NETOBJSIZES = 64 }; short m_aItemSizes[MAX_NETOBJSIZES]; + short m_aItemSizes7[MAX_NETOBJSIZES]; int m_aSnapshotDataRate[CSnapshot::MAX_TYPE + 1]; int m_aSnapshotDataUpdates[CSnapshot::MAX_TYPE + 1]; CData m_Empty; @@ -96,9 +111,10 @@ public: int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; } int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; } void SetStaticsize(int ItemType, size_t Size); + void SetStaticsize7(int ItemType, size_t Size); const CData *EmptyDelta() const; int CreateDelta(const CSnapshot *pFrom, const CSnapshot *pTo, void *pDstData); - int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize); + int UnpackDelta(const CSnapshot *pFrom, CSnapshot *pTo, const void *pSrcData, int DataSize, bool Sixup); int DebugDumpDelta(const void *pSrcData, int DataSize); }; diff --git a/src/engine/shared/translation_context.h b/src/engine/shared/translation_context.h new file mode 100644 index 000000000..60cae63c9 --- /dev/null +++ b/src/engine/shared/translation_context.h @@ -0,0 +1,120 @@ +#ifndef ENGINE_SHARED_TRANSLATION_CONTEXT_H +#define ENGINE_SHARED_TRANSLATION_CONTEXT_H + +#include +#include + +#include + +class CTranslationContext +{ +public: + CTranslationContext() + { + Reset(); + } + + void Reset() + { + for(int &LocalClientId : m_aLocalClientId) + LocalClientId = -1; + m_ShouldSendGameInfo = false; + m_GameFlags = 0; + m_ScoreLimit = 0; + m_TimeLimit = 0; + m_MatchNum = 0; + m_MatchCurrent = 0; + mem_zero(m_aDamageTaken, sizeof(m_aDamageTaken)); + mem_zero(m_aDamageTakenTick, sizeof(m_aDamageTakenTick)); + m_FlagCarrierBlue = 0; + m_FlagCarrierRed = 0; + m_TeamscoreRed = 0; + m_TeamscoreBlue = 0; + } + + // this class is not used + // it could be used in the in game menu + // to grey out buttons and similar + // + // but that can not be done without mixing it + // into the 0.6 code so it is out of scope for ddnet + class CServerSettings + { + public: + bool m_KickVote = false; + int m_KickMin = 0; + bool m_SpecVote = false; + bool m_TeamLock = false; + bool m_TeamBalance = false; + int m_PlayerSlots = 0; + } m_ServerSettings; + + class CClientData + { + public: + CClientData() + { + Reset(); + } + + void Reset() + { + m_Active = false; + + m_UseCustomColor = 0; + m_ColorBody = 0; + m_ColorFeet = 0; + + m_aName[0] = '\0'; + m_aClan[0] = '\0'; + m_Country = 0; + m_aSkinName[0] = '\0'; + m_SkinColor = 0; + m_Team = 0; + m_PlayerFlags7 = 0; + } + + bool m_Active; + + int m_UseCustomColor; + int m_ColorBody; + int m_ColorFeet; + + char m_aName[MAX_NAME_LENGTH]; + char m_aClan[MAX_CLAN_LENGTH]; + int m_Country; + char m_aSkinName[protocol7::MAX_SKIN_LENGTH]; + int m_SkinColor; + int m_Team; + int m_PlayerFlags7; + }; + + const protocol7::CNetObj_PlayerInfoRace *m_apPlayerInfosRace[MAX_CLIENTS]; + CClientData m_aClients[MAX_CLIENTS]; + int m_aDamageTaken[MAX_CLIENTS]; + float m_aDamageTakenTick[MAX_CLIENTS]; + + int m_aLocalClientId[NUM_DUMMIES]; + + bool m_ShouldSendGameInfo; + int m_GameStateFlags7; + int m_GameFlags; + int m_ScoreLimit; + int m_TimeLimit; + int m_MatchNum; + int m_MatchCurrent; + + int m_MapdownloadTotalsize; + int m_MapDownloadChunkSize; + int m_MapDownloadChunksPerRequest; + + int m_FlagCarrierBlue; + int m_FlagCarrierRed; + int m_TeamscoreRed; + int m_TeamscoreBlue; + + int m_GameStartTick7; + int m_GameStateEndTick7; +}; + +#endif diff --git a/src/game/client/components/broadcast.cpp b/src/game/client/components/broadcast.cpp index 5683705da..87a1c0f60 100644 --- a/src/game/client/components/broadcast.cpp +++ b/src/game/client/components/broadcast.cpp @@ -74,20 +74,20 @@ void CBroadcast::OnMessage(int MsgType, void *pRawMsg) { if(MsgType == NETMSGTYPE_SV_BROADCAST) { - OnBroadcastMessage((CNetMsg_Sv_Broadcast *)pRawMsg); + const CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg; + DoBroadcast(pMsg->m_pMessage); } } -void CBroadcast::OnBroadcastMessage(const CNetMsg_Sv_Broadcast *pMsg) +void CBroadcast::DoBroadcast(const char *pText) { - str_copy(m_aBroadcastText, pMsg->m_pMessage); + str_copy(m_aBroadcastText, pText); m_BroadcastTick = Client()->GameTick(g_Config.m_ClDummy) + Client()->GameTickSpeed() * 10; m_BroadcastRenderOffset = -1.0f; TextRender()->DeleteTextContainer(m_TextContainerIndex); if(g_Config.m_ClPrintBroadcasts) { - const char *pText = m_aBroadcastText; char aLine[sizeof(m_aBroadcastText)]; while((pText = str_next_token(pText, "\n", aLine, sizeof(aLine)))) { diff --git a/src/game/client/components/broadcast.h b/src/game/client/components/broadcast.h index e8bfaa41f..11942764d 100644 --- a/src/game/client/components/broadcast.h +++ b/src/game/client/components/broadcast.h @@ -25,6 +25,8 @@ public: virtual void OnWindowResize() override; virtual void OnRender() override; virtual void OnMessage(int MsgType, void *pRawMsg) override; + + void DoBroadcast(const char *pText); }; #endif diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 35a7257ad..d4a25f2c4 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -1318,6 +1318,16 @@ void CChat::SendChat(int Team, const char *pLine) m_LastChatSend = time(); + if(m_pClient->Client()->IsSixup()) + { + protocol7::CNetMsg_Cl_Say Msg7; + Msg7.m_Mode = Team == 1 ? protocol7::CHAT_TEAM : protocol7::CHAT_ALL; + Msg7.m_Target = -1; + Msg7.m_pMessage = pLine; + Client()->SendPackMsgActive(&Msg7, MSGFLAG_VITAL, true); + return; + } + // send chat message CNetMsg_Cl_Say Msg; Msg.m_Team = Team; diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 517482188..9ebb7afca 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -329,7 +329,9 @@ void CHud::RenderScoreHud() { if(apPlayerInfo[t]) { - if(m_pClient->m_GameInfo.m_TimeScore) + if(Client()->IsSixup() && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE) + str_time((int64_t)absolute(apPlayerInfo[t]->m_Score) / 10, TIME_MINS_CENTISECS, aScore[t], sizeof(aScore[t])); + else if(m_pClient->m_GameInfo.m_TimeScore) { if(apPlayerInfo[t]->m_Score != -9999) str_time((int64_t)absolute(apPlayerInfo[t]->m_Score) * 100, TIME_HOURS, aScore[t], sizeof(aScore[t])); diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp index 84568e793..10e703f3d 100644 --- a/src/game/client/components/mapimages.cpp +++ b/src/game/client/components/mapimages.cpp @@ -132,7 +132,16 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap) if(pImg->m_External) { char aPath[IO_MAX_PATH_LENGTH]; - str_format(aPath, sizeof(aPath), "mapres/%s.png", pName); + bool Translated = false; + if(Client()->IsSixup()) + { + Translated = + !str_comp(pName, "grass_doodads") || + !str_comp(pName, "grass_main") || + !str_comp(pName, "winter_main") || + !str_comp(pName, "generic_unhookable"); + } + str_format(aPath, sizeof(aPath), "mapres/%s%s.png", pName, Translated ? "_0.7" : ""); m_aTextures[i] = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, LoadFlag); } else diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 3e0b8cf73..1064aca7c 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -23,10 +23,13 @@ #include #include +#include #include #include #include +#include + struct CServerProcess { PROCESS m_Process; @@ -244,6 +247,11 @@ protected: bool m_NeedSendDummyinfo; int m_SettingPlayerPage; + // 0.7 skins + int m_TeePartSelected = protocol7::SKINPART_BODY; + const CSkins7::CSkin *m_pSelectedSkin = nullptr; + CLineInputBuffered m_SkinNameInput; + // for map download popup int64_t m_DownloadLastCheckTime; int m_DownloadLastCheckSize; @@ -586,6 +594,11 @@ protected: void RenderSettingsPlayer(CUIRect MainView); void RenderSettingsDummyPlayer(CUIRect MainView); void RenderSettingsTee(CUIRect MainView); + void RenderSettingsTee7(CUIRect MainView); + void RenderSettingsTeeCustom7(CUIRect MainView); + void RenderSettingsTeeBasic7(CUIRect MainView); + void RenderSkinSelection7(CUIRect MainView); + void RenderSkinPartSelection7(CUIRect MainView); void RenderSettingsControls(CUIRect MainView); void ResetSettingsControls(); void RenderSettingsGraphics(CUIRect MainView); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 8172b46a5..b22d50ec2 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2021,7 +2022,7 @@ void CMenus::RenderSettings(CUIRect MainView) Localize("Language"), Localize("General"), Localize("Player"), - Localize("Tee"), + Client()->IsSixup() ? "Tee 0.7" : Localize("Tee"), Localize("Appearance"), Localize("Controls"), Localize("Graphics"), @@ -2056,7 +2057,10 @@ void CMenus::RenderSettings(CUIRect MainView) else if(g_Config.m_UiSettingsPage == SETTINGS_TEE) { GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_SETTINGS_TEE); - RenderSettingsTee(MainView); + if(Client()->IsSixup()) + RenderSettingsTee7(MainView); + else + RenderSettingsTee(MainView); } else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE) { diff --git a/src/game/client/components/menus_settings7.cpp b/src/game/client/components/menus_settings7.cpp new file mode 100644 index 000000000..d077e3aff --- /dev/null +++ b/src/game/client/components/menus_settings7.cpp @@ -0,0 +1,548 @@ +/* (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. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "menus.h" +#include "skins7.h" + +#include + +void CMenus::RenderSettingsTee7(CUIRect MainView) +{ + static bool s_CustomSkinMenu = false; + // static int s_PlayerCountry = 0; + // static char s_aPlayerName[64] = {0}; + // static char s_aPlayerClan[64] = {0}; + + // if(m_pClient->m_IdentityState < 0) + // { + // s_PlayerCountry = Config()->m_PlayerCountry; + // str_copy(s_aPlayerName, Config()->m_PlayerName, sizeof(s_aPlayerName)); + // str_copy(s_aPlayerClan, Config()->m_PlayerClan, sizeof(s_aPlayerClan)); + // m_pClient->m_IdentityState = 0; + // } + + CUIRect Label, TopView, BottomView, Left, Right; + + // cut view + MainView.HSplitBottom(40.0f, &MainView, &BottomView); + BottomView.HSplitTop(20.f, 0, &BottomView); + + CUIRect QuickSearch, Buttons; + CUIRect ButtonLeft, ButtonMiddle, ButtonRight; + + BottomView.VSplitMid(&QuickSearch, &Buttons); + + const float ButtonSize = Buttons.w / 3; + Buttons.VSplitLeft(ButtonSize, &ButtonLeft, &Buttons); + Buttons.VSplitLeft(ButtonSize, &ButtonMiddle, &Buttons); + Buttons.VSplitLeft(ButtonSize, &ButtonRight, &Buttons); + + // HotCuiRects(MainView, BottomView, QuickSearch, ButtonLeft, ButtonMiddle, ButtonRight); + + // render skin preview background + const float SpacingH = 2.0f; + const float SpacingW = 3.0f; + const float ButtonHeight = 20.0f; + const float SkinHeight = 50.0f; + const float BackgroundHeight = (ButtonHeight + SpacingH) + SkinHeight * 2; + const vec2 MousePosition = vec2(Ui()->MouseX(), Ui()->MouseY()); + + MainView.HSplitTop(20.0f, 0, &MainView); + MainView.HSplitTop(BackgroundHeight, &TopView, &MainView); + TopView.VSplitMid(&Left, &Right, 3.0f); + Left.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + Right.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + Left.HSplitTop(ButtonHeight, &Label, &Left); + Ui()->DoLabel(&Label, Localize("Tee"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC); + + // Preview + { + CUIRect Top, Bottom, TeeLeft, TeeRight; + + Left.HSplitTop(SpacingH, 0, &Left); + Left.HSplitTop(SkinHeight * 2, &Top, &Left); + + // split the menu in 2 parts + Top.HSplitMid(&Top, &Bottom, SpacingH); + + // handle left + + // validate skin parts for solo mode + CTeeRenderInfo OwnSkinInfo; + OwnSkinInfo.m_Size = 50.0f; + + char aSkinParts[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE]; + char *apSkinPartsPtr[protocol7::NUM_SKINPARTS]; + int aUCCVars[protocol7::NUM_SKINPARTS]; + int aColorVars[protocol7::NUM_SKINPARTS]; + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE); + apSkinPartsPtr[Part] = aSkinParts[Part]; + aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part]; + aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part]; + } + + m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, 0); + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart); + if(aUCCVars[Part]) + { + OwnSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture; + OwnSkinInfo.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING); + } + else + { + OwnSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture; + OwnSkinInfo.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + } + } + + // draw preview + Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + Top.VSplitLeft(Top.w / 3.0f + SpacingW / 2.0f, &Label, &Top); + Label.y += 17.0f; + Ui()->DoLabel(&Label, Localize("Normal:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER); + + Top.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + { + // interactive tee: tee looking towards cursor, and it is happy when you touch it + vec2 TeePosition = vec2(Top.x + Top.w / 2.0f, Top.y + Top.h / 2.0f + 6.0f); + vec2 DeltaPosition = MousePosition - TeePosition; + float Distance = length(DeltaPosition); + vec2 TeeDirection = Distance < 20.0f ? normalize(vec2(DeltaPosition.x, maximum(DeltaPosition.y, 0.5f))) : normalize(DeltaPosition); + int TeeEmote = Distance < 20.0f ? EMOTE_HAPPY : EMOTE_NORMAL; + RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, TeeEmote, TeeDirection, TeePosition); + if(Distance < 20.0f && Ui()->MouseButtonClicked(0)) + m_pClient->m_Sounds.Play(CSounds::CHN_GUI, SOUND_PLAYER_SPAWN, 0); + } + + // handle right (team skins) + + // validate skin parts for team game mode + CTeeRenderInfo TeamSkinInfo = OwnSkinInfo; + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + str_copy(aSkinParts[Part], CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], protocol7::MAX_SKIN_ARRAY_SIZE); + apSkinPartsPtr[Part] = aSkinParts[Part]; + aUCCVars[Part] = *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part]; + aColorVars[Part] = *CSkins7::ms_apColorVariables[(int)m_Dummy][Part]; + } + + m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, GAMEFLAG_TEAMS); + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart); + if(aUCCVars[Part]) + { + TeamSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_ColorTexture; + TeamSkinInfo.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING); + } + else + { + TeamSkinInfo.m_Sixup.m_aTextures[Part] = pSkinPart->m_OrgTexture; + TeamSkinInfo.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + } + } + + // draw preview + Bottom.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + Bottom.VSplitLeft(Bottom.w / 3.0f + SpacingW / 2.0f, &Label, &Bottom); + Label.y += 17.0f; + Ui()->DoLabel(&Label, Localize("Team:"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER); + + Bottom.VSplitMid(&TeeLeft, &TeeRight, SpacingW); + + TeeLeft.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_RED, Part); + TeamSkinInfo.m_Sixup.m_aColors[Part] = TeamColor; + } + RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(1, 0), vec2(TeeLeft.x + TeeLeft.w / 2.0f, TeeLeft.y + TeeLeft.h / 2.0f + 6.0f)); + + TeeRight.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + ColorRGBA TeamColor = m_pClient->m_Skins7.GetTeamColor(aUCCVars[Part], aColorVars[Part], TEAM_BLUE, Part); + TeamSkinInfo.m_Sixup.m_aColors[Part] = TeamColor; + } + RenderTools()->RenderTee(CAnimState::GetIdle(), &TeamSkinInfo, 0, vec2(-1, 0), vec2(TeeRight.x + TeeRight.w / 2.0f, TeeRight.y + TeeRight.h / 2.0f + 6.0f)); + } + + Right.HSplitTop(ButtonHeight, &Label, &Right); + Ui()->DoLabel(&Label, Localize("Settings"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC); + + // Settings + { + CUIRect Top, Bottom, Dummy, DummyLabel; + Right.HSplitTop(SpacingH, 0, &Right); + Right.HSplitMid(&Top, &Bottom, SpacingH); + + Right.HSplitTop(20.0f, &Dummy, &Right); + Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); + + if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &DummyLabel)) + { + m_Dummy ^= 1; + } + GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings")); + } + + MainView.HSplitTop(10.0f, 0, &MainView); + + if(s_CustomSkinMenu) + RenderSettingsTeeCustom7(MainView); + else + RenderSettingsTeeBasic7(MainView); + + // bottom buttons + if(s_CustomSkinMenu) + { + static CButtonContainer s_RandomizeSkinButton; + if(DoButton_Menu(&s_RandomizeSkinButton, Localize("Randomize"), 0, &ButtonMiddle)) + { + m_pClient->m_Skins7.RandomizeSkin(m_Dummy); + Config()->m_ClPlayer7Skin[0] = 0; + SetNeedSendInfo(); + } + } + else if(m_pSelectedSkin && (m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD) == 0) + { + static CButtonContainer s_CustomSkinDeleteButton; + if(DoButton_Menu(&s_CustomSkinDeleteButton, Localize("Delete"), 0, &ButtonMiddle)) + { + // char aBuf[128]; + // str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the skin '%s'?"), m_pSelectedSkin->m_aName); + // PopupConfirm(Localize("Delete skin"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteSkin); + } + } + + static CButtonContainer s_CustomSwitchButton; + if(DoButton_Menu(&s_CustomSwitchButton, s_CustomSkinMenu ? Localize("Basic") : Localize("Custom"), 0, &ButtonRight)) + { + s_CustomSkinMenu = !s_CustomSkinMenu; + if(s_CustomSkinMenu && m_pSelectedSkin) + { + if(m_pSelectedSkin->m_Flags & CSkins7::SKINFLAG_STANDARD) + { + m_SkinNameInput.Set("copy_"); + m_SkinNameInput.Append(m_pSelectedSkin->m_aName); + } + else + m_SkinNameInput.Set(m_pSelectedSkin->m_aName); + } + } + + // Quick search + { + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); + Ui()->DoLabel(&QuickSearch, FontIcons::FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); + float SearchWidth = TextRender()->TextWidth(14.0f, FontIcons::FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + QuickSearch.VSplitLeft(SearchWidth + 5.0f, nullptr, &QuickSearch); + static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); + if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) + { + Ui()->SetActiveItem(&s_SkinFilterInput); + s_SkinFilterInput.SelectAll(); + } + s_SkinFilterInput.SetEmptyText(Localize("Search")); + if(Ui()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f)) + m_SkinListNeedsUpdate = true; + } +} + +void CMenus::RenderSettingsTeeBasic7(CUIRect MainView) +{ + RenderSkinSelection7(MainView); // yes thats all here ^^ +} + +void CMenus::RenderSettingsTeeCustom7(CUIRect MainView) +{ + CUIRect Label, Patterns, Button, Left, Right, Picker, Palette; + + // render skin preview background + float SpacingH = 2.0f; + float SpacingW = 3.0f; + float ButtonHeight = 20.0f; + + MainView.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + MainView.HSplitTop(ButtonHeight, &Label, &MainView); + Ui()->DoLabel(&Label, Localize("Customize"), ButtonHeight * CUi::ms_FontmodHeight * 0.8f, TEXTALIGN_MC); + + // skin part selection + MainView.HSplitTop(SpacingH, 0, &MainView); + MainView.HSplitTop(ButtonHeight, &Patterns, &MainView); + Patterns.Draw(vec4(0.0f, 0.0f, 0.0f, 0.25f), 15, 5.0F); + + float ButtonWidth = (Patterns.w / 6.0f) - (SpacingW * 5.0) / 6.0f; + + static CButtonContainer s_aPatternButtons[protocol7::NUM_SKINPARTS]; + for(int i = 0; i < protocol7::NUM_SKINPARTS; i++) + { + Patterns.VSplitLeft(ButtonWidth, &Button, &Patterns); + if(DoButton_MenuTab(&s_aPatternButtons[i], Localize(CSkins7::ms_apSkinPartNames[i], "skins"), m_TeePartSelected == i, &Button, IGraphics::CORNER_ALL)) + { + m_TeePartSelected = i; + } + Patterns.VSplitLeft(SpacingW, 0, &Patterns); + } + + MainView.HSplitTop(SpacingH, 0, &MainView); + MainView.VSplitMid(&Left, &Right, SpacingW); + + // part selection + RenderSkinPartSelection7(Left); + + // use custom color checkbox + Right.HSplitTop(ButtonHeight, &Button, &Right); + Right.HSplitBottom(45.0f, &Picker, &Palette); + static CButtonContainer s_ColorPicker; + DoLine_ColorPicker( + &s_ColorPicker, + 25.0f, // LineSize + 13.0f, // LabelSize + 5.0f, // BottomMargin + &Right, + Localize("Custom colors"), + (unsigned int *)CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected], + ColorRGBA(1.0f, 1.0f, 0.5f), // DefaultColor + true, // CheckBoxSpacing + CSkins7::ms_apUCCVariables[(int)m_Dummy][m_TeePartSelected], // CheckBoxValue + m_TeePartSelected == protocol7::SKINPART_MARKING); // use alpha + static int s_OldColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected]; + int NewColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected]; + if(s_OldColor != NewColor) + { + s_OldColor = NewColor; + SetNeedSendInfo(); + } +} + +void CMenus::RenderSkinSelection7(CUIRect MainView) +{ + static float s_LastSelectionTime = -10.0f; + static std::vector s_vpSkinList; + static CListBox s_ListBox; + static int s_SkinCount = 0; + if(m_SkinListNeedsUpdate || m_pClient->m_Skins7.Num() != s_SkinCount) + { + s_vpSkinList.clear(); + s_SkinCount = m_pClient->m_Skins7.Num(); + for(int i = 0; i < s_SkinCount; ++i) + { + const CSkins7::CSkin *s = m_pClient->m_Skins7.Get(i); + + if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(s->m_aName, g_Config.m_ClSkinFilterString)) + continue; + + // no special skins + if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0) + { + s_vpSkinList.emplace_back(s); + } + } + m_SkinListNeedsUpdate = false; + } + + m_pSelectedSkin = 0; + int s_OldSelected = -1; + s_ListBox.DoStart(60.0f, s_vpSkinList.size(), 10, 1, s_OldSelected, &MainView); + + for(int i = 0; i < (int)s_vpSkinList.size(); ++i) + { + const CSkins7::CSkin *s = s_vpSkinList[i]; + if(s == 0) + continue; + if(!str_comp(s->m_aName, Config()->m_ClPlayer7Skin)) + { + m_pSelectedSkin = s; + s_OldSelected = i; + } + + CListboxItem Item = s_ListBox.DoNextItem(&s_vpSkinList[i], s_OldSelected == i); + if(Item.m_Visible) + { + CTeeRenderInfo Info; + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + if(s->m_aUseCustomColors[Part]) + { + Info.m_Sixup.m_aTextures[Part] = s->m_apParts[Part]->m_ColorTexture; + Info.m_Sixup.m_aColors[Part] = m_pClient->m_Skins7.GetColor(s->m_aPartColors[Part], Part == protocol7::SKINPART_MARKING); + } + else + { + Info.m_Sixup.m_aTextures[Part] = s->m_apParts[Part]->m_OrgTexture; + Info.m_Sixup.m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + } + } + + Info.m_Size = 50.0f; + Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top + + { + // interactive tee: tee is happy to be selected + int TeeEmote = (Item.m_Selected && s_LastSelectionTime + 0.75f > Client()->LocalTime()) ? EMOTE_HAPPY : EMOTE_NORMAL; + RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeeEmote, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2)); + } + + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); + + Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC); + } + } + + const int NewSelected = s_ListBox.DoEnd(); + if(NewSelected != -1 && NewSelected != s_OldSelected) + { + s_LastSelectionTime = Client()->LocalTime(); + m_pSelectedSkin = s_vpSkinList[NewSelected]; + str_copy(Config()->m_ClPlayer7Skin, m_pSelectedSkin->m_aName); + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], m_pSelectedSkin->m_apParts[Part]->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); + *CSkins7::ms_apUCCVariables[(int)m_Dummy][Part] = m_pSelectedSkin->m_aUseCustomColors[Part]; + *CSkins7::ms_apColorVariables[(int)m_Dummy][Part] = m_pSelectedSkin->m_aPartColors[Part]; + } + SetNeedSendInfo(); + } +} + +void CMenus::RenderSkinPartSelection7(CUIRect MainView) +{ + static bool s_InitSkinPartList = true; + static std::vector s_paList[protocol7::NUM_SKINPARTS]; + static CListBox s_ListBox; + if(s_InitSkinPartList) + { + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + s_paList[Part].clear(); + for(int i = 0; i < m_pClient->m_Skins7.NumSkinPart(Part); ++i) + { + const CSkins7::CSkinPart *s = m_pClient->m_Skins7.GetSkinPart(Part, i); + // no special skins + if((s->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0) + { + s_paList[Part].emplace_back(s); + } + } + } + s_InitSkinPartList = false; + } + + static int s_OldSelected = -1; + s_ListBox.DoBegin(&MainView); + s_ListBox.DoStart(60.0f, s_paList[m_TeePartSelected].size(), 5, 1, s_OldSelected); + + for(int i = 0; i < (int)s_paList[m_TeePartSelected].size(); ++i) + { + const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][i]; + if(s == 0) + continue; + if(!str_comp(s->m_aName, CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected])) + s_OldSelected = i; + + CListboxItem Item = s_ListBox.DoNextItem(&s_paList[m_TeePartSelected][i], s_OldSelected == i); + if(Item.m_Visible) + { + CTeeRenderInfo Info; + for(int j = 0; j < protocol7::NUM_SKINPARTS; j++) + { + int SkinPart = m_pClient->m_Skins7.FindSkinPart(j, CSkins7::ms_apSkinVariables[(int)m_Dummy][j], false); + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart); + if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j]) + { + if(m_TeePartSelected == j) + Info.m_Sixup.m_aTextures[j] = s->m_ColorTexture; + else + Info.m_Sixup.m_aTextures[j] = pSkinPart->m_ColorTexture; + Info.m_Sixup.m_aColors[j] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][j], j == protocol7::SKINPART_MARKING); + } + else + { + if(m_TeePartSelected == j) + Info.m_Sixup.m_aTextures[j] = s->m_OrgTexture; + else + Info.m_Sixup.m_aTextures[j] = pSkinPart->m_OrgTexture; + Info.m_Sixup.m_aColors[j] = vec4(1.0f, 1.0f, 1.0f, 1.0f); + } + } + Info.m_Size = 50.0f; + Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top + const vec2 TeePos(Item.m_Rect.x + Item.m_Rect.w / 2, Item.m_Rect.y + Item.m_Rect.h / 2); + + if(m_TeePartSelected == protocol7::SKINPART_HANDS) + { + // RenderTools()->RenderTeeHand(&Info, TeePos, vec2(1.0f, 0.0f), -pi*0.5f, vec2(18, 0)); + } + int TeePartEmote = EMOTE_NORMAL; + if(m_TeePartSelected == protocol7::SKINPART_EYES) + { + float LocalTime = Client()->LocalTime(); + TeePartEmote = (int)(LocalTime * 0.5f) % NUM_EMOTES; + } + RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, TeePartEmote, vec2(1.0f, 0.0f), TeePos); + + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); + + Ui()->DoLabel(&Label, s->m_aName, 10.0f, TEXTALIGN_MC); + } + } + + const int NewSelected = s_ListBox.DoEnd(); + if(NewSelected != -1 && NewSelected != s_OldSelected) + { + const CSkins7::CSkinPart *s = s_paList[m_TeePartSelected][NewSelected]; + str_copy(CSkins7::ms_apSkinVariables[(int)m_Dummy][m_TeePartSelected], s->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); + Config()->m_ClPlayer7Skin[0] = 0; + SetNeedSendInfo(); + } + s_OldSelected = NewSelected; +} diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index be642f967..b8487b259 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,54 @@ #include void CPlayers::RenderHand(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha) +{ + if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY].IsValid()) + RenderHand7(pInfo, CenterPos, Dir, AngleOffset, PostRotOffset, Alpha); + else + RenderHand6(pInfo, CenterPos, Dir, AngleOffset, PostRotOffset, Alpha); +} + +void CPlayers::RenderHand7(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha) +{ + // in-game hand size is 15 when tee size is 64 + float BaseSize = 15.0f * (pInfo->m_Size / 64.0f); + + vec2 HandPos = CenterPos + Dir; + float Angle = angle(Dir); + if(Dir.x < 0) + Angle -= AngleOffset; + else + Angle += AngleOffset; + + vec2 DirX = Dir; + vec2 DirY(-Dir.y, Dir.x); + + if(Dir.x < 0) + DirY = -DirY; + + HandPos += DirX * PostRotOffset.x; + HandPos += DirY * PostRotOffset.y; + + ColorRGBA Color = pInfo->m_Sixup.m_aColors[protocol7::SKINPART_HANDS]; + Color.a = Alpha; + IGraphics::CQuadItem QuadOutline(HandPos.x, HandPos.y, 2 * BaseSize, 2 * BaseSize); + IGraphics::CQuadItem QuadHand = QuadOutline; + + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_HANDS]); + Graphics()->QuadsBegin(); + Graphics()->SetColor(Color); + Graphics()->QuadsSetRotation(Angle); + + RenderTools()->SelectSprite7(client_data7::SPRITE_TEE_HAND_OUTLINE, 0, 0, 0); + Graphics()->QuadsDraw(&QuadOutline, 1); + RenderTools()->SelectSprite7(client_data7::SPRITE_TEE_HAND, 0, 0, 0); + Graphics()->QuadsDraw(&QuadHand, 1); + + Graphics()->QuadsSetRotation(0); + Graphics()->QuadsEnd(); +} + +void CPlayers::RenderHand6(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha) { vec2 HandPos = CenterPos + Dir; float Angle = angle(Dir); @@ -779,6 +828,8 @@ void CPlayers::OnRender() const auto *pSkin = m_pClient->m_Skins.FindOrNullptr("x_ninja"); if(pSkin != nullptr) { + aRenderInfo[i].m_Sixup.Reset(); + aRenderInfo[i].m_OriginalRenderSkin = pSkin->m_OriginalSkin; aRenderInfo[i].m_ColorableRenderSkin = pSkin->m_ColorableSkin; aRenderInfo[i].m_BloodColor = pSkin->m_BloodColor; diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h index 5c8d668b6..b20df3518 100644 --- a/src/game/client/components/players.h +++ b/src/game/client/components/players.h @@ -11,6 +11,9 @@ class CPlayers : public CComponent { friend class CGhost; + void RenderHand6(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f); + void RenderHand7(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f); + void RenderHand(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha = 1.0f); void RenderPlayer( const CNetObj_Character *pPrevChar, diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index a51d56caf..de08db6a8 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include CScoreboard::CScoreboard() @@ -227,6 +228,8 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, const int NumPlayers = CountEnd - CountStart; const bool LowScoreboardWidth = Scoreboard.w < 700.0f; + bool Race7 = Client()->IsSixup() && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE; + // calculate measurements float LineHeight; float TeeSizeMod; @@ -336,6 +339,11 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, int DDTeam = GameClient()->m_Teams.Team(pInfo->m_ClientId); int NextDDTeam = 0; + bool RenderDead = Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_DEAD; + + ColorRGBA TextColor = TextRender()->DefaultTextColor(); + TextColor.a = RenderDead ? 0.5f : 1.0f; + TextRender()->TextColor(TextColor); for(int j = i + 1; j < MAX_CLIENTS; j++) { @@ -420,7 +428,21 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, } // score - if(TimeScore) + if(Race7) + { + if(pInfo->m_Score == -1) + { + aBuf[0] = '\0'; + } + else + { + // 0.7 uses milliseconds and ddnets str_time wants centiseconds + // 0.7 servers can also send the amount of precision the client should use + // we ignore that and always show 3 digit precision + str_time((int64_t)absolute(pInfo->m_Score / 10), TIME_MINS_CENTISECS, aBuf, sizeof(aBuf)); + } + } + else if(TimeScore) { if(pInfo->m_Score == -9999) { @@ -453,6 +475,23 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, const CGameClient::CClientData &ClientData = GameClient()->m_aClients[pInfo->m_ClientId]; // skin + if(RenderDead) + { + Graphics()->BlendNormal(); + Graphics()->TextureSet(client_data7::g_pData->m_aImages[client_data7::IMAGE_DEADTEE].m_Id); + Graphics()->QuadsBegin(); + if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) + { + ColorRGBA Color = m_pClient->m_Skins7.GetTeamColor(true, 0, m_pClient->m_aClients[pInfo->m_ClientId].m_Team, protocol7::SKINPART_BODY); + Graphics()->SetColor(Color.r, Color.g, Color.b, Color.a); + } + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientId].m_RenderInfo; + TeeInfo.m_Size *= TeeSizeMod; + IGraphics::CQuadItem QuadItem(TeeOffset, Row.y, TeeInfo.m_Size, TeeInfo.m_Size); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + else { CTeeRenderInfo TeeInfo = ClientData.m_RenderInfo; TeeInfo.m_Size *= TeeSizeMod; @@ -480,6 +519,13 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, { TextRender()->TextEx(&Cursor, ClientData.m_aName); } + + // ready / watching + if(Client()->IsSixup() && Client()->m_TranslationContext.m_aClients[pInfo->m_ClientId].m_PlayerFlags7 & protocol7::PLAYERFLAG_READY) + { + TextRender()->TextColor(0.1f, 1.0f, 0.1f, TextColor.a); + TextRender()->TextEx(&Cursor, "✓"); + } } // clan @@ -490,7 +536,7 @@ void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, } else { - TextRender()->TextColor(TextRender()->DefaultTextColor()); + TextRender()->TextColor(TextColor); } CTextCursor Cursor; TextRender()->SetCursor(&Cursor, ClanOffset + (ClanLength - minimum(TextRender()->TextWidth(FontSize, ClientData.m_aClan), ClanLength)) / 2.0f, Row.y + (Row.h - FontSize) / 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_ELLIPSIS_AT_END); diff --git a/src/game/client/components/skins7.cpp b/src/game/client/components/skins7.cpp new file mode 100644 index 000000000..183ce886a --- /dev/null +++ b/src/game/client/components/skins7.cpp @@ -0,0 +1,560 @@ +/* (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. */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "menus.h" +#include "skins7.h" + +const char *const CSkins7::ms_apSkinPartNames[protocol7::NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"}; /* Localize("body","skins");Localize("marking","skins");Localize("decoration","skins");Localize("hands","skins");Localize("feet","skins");Localize("eyes","skins"); */ +const char *const CSkins7::ms_apColorComponents[NUM_COLOR_COMPONENTS] = {"hue", "sat", "lgt", "alp"}; + +char *CSkins7::ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}}; +int *CSkins7::ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}}; +int *CSkins7::ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS] = {{0}}; + +// TODO: uncomment +// const float MIN_EYE_BODY_COLOR_DIST = 80.f; // between body and eyes (LAB color space) + +int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser) +{ + CSkins7 *pSelf = (CSkins7 *)pUser; + if(IsDir || !str_endswith(pName, ".png")) + return 0; + + size_t PartNameSize, PartNameCount; + str_utf8_stats(pName, str_length(pName) - str_length(".png") + 1, IO_MAX_PATH_LENGTH, &PartNameSize, &PartNameCount); + if(PartNameSize >= protocol7::MAX_SKIN_ARRAY_SIZE || PartNameCount > protocol7::MAX_SKIN_LENGTH) + { + log_error("skins7", "Failed to load skin part '%s': name too long", pName); + return 0; + } + + CSkinPart Part; + str_copy(Part.m_aName, pName, minimum(PartNameSize + 1, sizeof(Part.m_aName))); + if(pSelf->FindSkinPart(pSelf->m_ScanningPart, Part.m_aName, true) != -1) + return 0; + + char aFilename[IO_MAX_PATH_LENGTH]; + str_format(aFilename, sizeof(aFilename), "skins/%s/%s", CSkins7::ms_apSkinPartNames[pSelf->m_ScanningPart], pName); + CImageInfo Info; + if(!pSelf->Graphics()->LoadPng(Info, aFilename, DirType)) + { + log_error("skins7", "Failed to load skin part '%s'", pName); + return 0; + } + if(Info.m_Format != CImageInfo::FORMAT_RGBA) + { + log_error("skins7", "Failed to load skin part '%s': must be RGBA format", pName); + return 0; + } + + Part.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aFilename); + Part.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f); + + int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3; + unsigned char *pData = (unsigned char *)Info.m_pData; + + // dig out blood color + if(pSelf->m_ScanningPart == protocol7::SKINPART_BODY) + { + int Pitch = Info.m_Width * Step; + int PartX = Info.m_Width / 2; + int PartY = 0; + int PartWidth = Info.m_Width / 2; + int PartHeight = Info.m_Height / 2; + + int aColors[3] = {0}; + for(int y = PartY; y < PartY + PartHeight; y++) + for(int x = PartX; x < PartX + PartWidth; x++) + if(pData[y * Pitch + x * Step + 3] > 128) + for(int c = 0; c < 3; c++) + aColors[c] += pData[y * Pitch + x * Step + c]; + + Part.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2]))); + } + + // create colorless version + for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++) + { + const int Average = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3; + pData[i * Step] = Average; + pData[i * Step + 1] = Average; + pData[i * Step + 2] = Average; + } + + Part.m_ColorTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aFilename); + + // set skin part data + Part.m_Flags = 0; + if(pName[0] == 'x' && pName[1] == '_') + Part.m_Flags |= SKINFLAG_SPECIAL; + if(DirType != IStorage::TYPE_SAVE) + Part.m_Flags |= SKINFLAG_STANDARD; + log_debug("skins7", "load skin part %s", Part.m_aName); + pSelf->m_avSkinParts[pSelf->m_ScanningPart].emplace_back(Part); + + return 0; +} + +int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) +{ + if(IsDir || !str_endswith(pName, ".json")) + return 0; + + CSkins7 *pSelf = (CSkins7 *)pUser; + + // read file data into buffer + char aFilename[IO_MAX_PATH_LENGTH]; + str_format(aFilename, sizeof(aFilename), "skins/%s", pName); + void *pFileData; + unsigned JsonFileSize; + if(!pSelf->Storage()->ReadFile(aFilename, IStorage::TYPE_ALL, &pFileData, &JsonFileSize)) + { + return 0; + } + + // init + CSkin Skin = pSelf->m_DummySkin; + int NameLength = str_length(pName); + str_copy(Skin.m_aName, pName, 1 + NameLength - str_length(".json")); + bool SpecialSkin = pName[0] == 'x' && pName[1] == '_'; + + // parse json data + json_settings JsonSettings; + mem_zero(&JsonSettings, sizeof(JsonSettings)); + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast(pFileData), JsonFileSize, aError); + free(pFileData); + + if(pJsonData == nullptr) + { + log_error("skins7", "Failed to parse skin json file '%s': %s", aFilename, aError); + return 0; + } + + // extract data + const json_value &Start = (*pJsonData)["skin"]; + if(Start.type == json_object) + { + for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex) + { + const json_value &Part = Start[(const char *)ms_apSkinPartNames[PartIndex]]; + if(Part.type != json_object) + continue; + + // filename + const json_value &Filename = Part["filename"]; + if(Filename.type == json_string) + { + int SkinPart = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin); + if(SkinPart > -1) + Skin.m_apParts[PartIndex] = pSelf->GetSkinPart(PartIndex, SkinPart); + } + + // use custom colors + bool UseCustomColors = false; + const json_value &Color = Part["custom_colors"]; + if(Color.type == json_string) + UseCustomColors = str_comp((const char *)Color, "true") == 0; + else if(Color.type == json_boolean) + UseCustomColors = Color.u.boolean; + Skin.m_aUseCustomColors[PartIndex] = UseCustomColors; + + // color components + if(!UseCustomColors) + continue; + + for(int i = 0; i < NUM_COLOR_COMPONENTS; i++) + { + if(PartIndex != protocol7::SKINPART_MARKING && i == 3) + continue; + + const json_value &Component = Part[(const char *)ms_apColorComponents[i]]; + if(Component.type == json_integer) + { + switch(i) + { + case 0: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFF00FFFF) | (Component.u.integer << 16); break; + case 1: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFF00FF) | (Component.u.integer << 8); break; + case 2: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFFFF00) | Component.u.integer; break; + case 3: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0x00FFFFFF) | (Component.u.integer << 24); break; + } + } + } + } + } + + // clean up + json_value_free(pJsonData); + + // set skin data + Skin.m_Flags = SpecialSkin ? SKINFLAG_SPECIAL : 0; + if(DirType != IStorage::TYPE_SAVE) + Skin.m_Flags |= SKINFLAG_STANDARD; + if(pSelf->Config()->m_Debug) + { + log_debug("skins7", "load skin %s", Skin.m_aName); + } + pSelf->m_vSkins.insert(std::lower_bound(pSelf->m_vSkins.begin(), pSelf->m_vSkins.end(), Skin), Skin); + + return 0; +} + +int CSkins7::GetInitAmount() const +{ + return protocol7::NUM_SKINPARTS * 5 + 8; +} + +void CSkins7::OnInit() +{ + int Dummy = 0; + ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClPlayer7SkinBody; + ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClPlayer7SkinMarking; + ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClPlayer7SkinDecoration; + ms_apSkinVariables[Dummy][protocol7::SKINPART_HANDS] = Config()->m_ClPlayer7SkinHands; + ms_apSkinVariables[Dummy][protocol7::SKINPART_FEET] = Config()->m_ClPlayer7SkinFeet; + ms_apSkinVariables[Dummy][protocol7::SKINPART_EYES] = Config()->m_ClPlayer7SkinEyes; + ms_apUCCVariables[Dummy][protocol7::SKINPART_BODY] = &Config()->m_ClPlayer7UseCustomColorBody; + ms_apUCCVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClPlayer7UseCustomColorMarking; + ms_apUCCVariables[Dummy][protocol7::SKINPART_DECORATION] = &Config()->m_ClPlayer7UseCustomColorDecoration; + ms_apUCCVariables[Dummy][protocol7::SKINPART_HANDS] = &Config()->m_ClPlayer7UseCustomColorHands; + ms_apUCCVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClPlayer7UseCustomColorFeet; + ms_apUCCVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClPlayer7UseCustomColorEyes; + ms_apColorVariables[Dummy][protocol7::SKINPART_BODY] = (int *)&Config()->m_ClPlayer7ColorBody; + ms_apColorVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClPlayer7ColorMarking; + ms_apColorVariables[Dummy][protocol7::SKINPART_DECORATION] = (int *)&Config()->m_ClPlayer7ColorDecoration; + ms_apColorVariables[Dummy][protocol7::SKINPART_HANDS] = (int *)&Config()->m_ClPlayer7ColorHands; + ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = (int *)&Config()->m_ClPlayer7ColorFeet; + ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = (int *)&Config()->m_ClPlayer7ColorEyes; + + Dummy = 1; + ms_apSkinVariables[Dummy][protocol7::SKINPART_BODY] = Config()->m_ClDummy7SkinBody; + ms_apSkinVariables[Dummy][protocol7::SKINPART_MARKING] = Config()->m_ClDummy7SkinMarking; + ms_apSkinVariables[Dummy][protocol7::SKINPART_DECORATION] = Config()->m_ClDummy7SkinDecoration; + ms_apSkinVariables[Dummy][protocol7::SKINPART_HANDS] = Config()->m_ClDummy7SkinHands; + ms_apSkinVariables[Dummy][protocol7::SKINPART_FEET] = Config()->m_ClDummy7SkinFeet; + ms_apSkinVariables[Dummy][protocol7::SKINPART_EYES] = Config()->m_ClDummy7SkinEyes; + ms_apUCCVariables[Dummy][protocol7::SKINPART_BODY] = &Config()->m_ClDummy7UseCustomColorBody; + ms_apUCCVariables[Dummy][protocol7::SKINPART_MARKING] = &Config()->m_ClDummy7UseCustomColorMarking; + ms_apUCCVariables[Dummy][protocol7::SKINPART_DECORATION] = &Config()->m_ClDummy7UseCustomColorDecoration; + ms_apUCCVariables[Dummy][protocol7::SKINPART_HANDS] = &Config()->m_ClDummy7UseCustomColorHands; + ms_apUCCVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClDummy7UseCustomColorFeet; + ms_apUCCVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClDummy7UseCustomColorEyes; + ms_apColorVariables[Dummy][protocol7::SKINPART_BODY] = (int *)&Config()->m_ClDummy7ColorBody; + ms_apColorVariables[Dummy][protocol7::SKINPART_MARKING] = (int *)&Config()->m_ClDummy7ColorMarking; + ms_apColorVariables[Dummy][protocol7::SKINPART_DECORATION] = (int *)&Config()->m_ClDummy7ColorDecoration; + ms_apColorVariables[Dummy][protocol7::SKINPART_HANDS] = (int *)&Config()->m_ClDummy7ColorHands; + ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = (int *)&Config()->m_ClDummy7ColorFeet; + ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = (int *)&Config()->m_ClDummy7ColorEyes; + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + m_avSkinParts[Part].clear(); + + // add none part + if(Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION) + { + CSkinPart NoneSkinPart; + NoneSkinPart.m_Flags = SKINFLAG_STANDARD; + NoneSkinPart.m_aName[0] = '\0'; + NoneSkinPart.m_BloodColor = vec3(1.0f, 1.0f, 1.0f); + m_avSkinParts[Part].emplace_back(NoneSkinPart); + } + + // load skin parts + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "skins/%s", ms_apSkinPartNames[Part]); + m_ScanningPart = Part; + Storage()->ListDirectory(IStorage::TYPE_ALL, aBuf, SkinPartScan, this); + + // add dummy skin part + if(m_avSkinParts[Part].empty()) + { + CSkinPart DummySkinPart; + DummySkinPart.m_Flags = SKINFLAG_STANDARD; + str_copy(DummySkinPart.m_aName, "dummy"); + DummySkinPart.m_BloodColor = vec3(1.0f, 1.0f, 1.0f); + m_avSkinParts[Part].emplace_back(DummySkinPart); + } + + GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); + } + + // create dummy skin + m_DummySkin.m_Flags = SKINFLAG_STANDARD; + str_copy(m_DummySkin.m_aName, "dummy"); + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + int Default; + if(Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION) + Default = FindSkinPart(Part, "", false); + else + Default = FindSkinPart(Part, "standard", false); + if(Default < 0) + Default = 0; + m_DummySkin.m_apParts[Part] = GetSkinPart(Part, Default); + m_DummySkin.m_aPartColors[Part] = Part == protocol7::SKINPART_MARKING ? (255 << 24) + 65408 : 65408; + m_DummySkin.m_aUseCustomColors[Part] = 0; + } + GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); + + // load skins + m_vSkins.clear(); + Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, this); + GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); + + // add dummy skin + if(m_vSkins.empty()) + m_vSkins.emplace_back(m_DummySkin); + + { + // add xmas hat + const char *pFileName = "skins/xmas_hat.png"; + CImageInfo Info; + if(!Graphics()->LoadPng(Info, pFileName, IStorage::TYPE_ALL) || Info.m_Width != 128 || Info.m_Height != 512) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "failed to load xmas hat '%s'", pFileName); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf); + } + else + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "loaded xmas hat '%s'", pFileName); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf); + m_XmasHatTexture = Graphics()->LoadTextureRawMove(Info, 0, pFileName); + } + } + GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); + + { + // add bot decoration + const char *pFileName = "skins/bot.png"; + CImageInfo Info; + if(!Graphics()->LoadPng(Info, pFileName, IStorage::TYPE_ALL) || Info.m_Width != 384 || Info.m_Height != 160) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "failed to load bot '%s'", pFileName); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf); + } + else + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "loaded bot '%s'", pFileName); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "skins7", aBuf); + m_BotTexture = Graphics()->LoadTextureRawMove(Info, 0, pFileName); + } + } + GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); +} + +void CSkins7::AddSkin(const char *pSkinName, int Dummy) +{ + CSkin Skin = m_DummySkin; + Skin.m_Flags = 0; + str_copy(Skin.m_aName, pSkinName, sizeof(Skin.m_aName)); + for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex) + { + int SkinPart = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false); + if(SkinPart > -1) + Skin.m_apParts[PartIndex] = GetSkinPart(PartIndex, SkinPart); + Skin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex]; + Skin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex]; + } + int SkinIndex = Find(Skin.m_aName, false); + if(SkinIndex != -1) + m_vSkins[SkinIndex] = Skin; + else + m_vSkins.emplace_back(Skin); +} + +void CSkins7::RemoveSkin(const CSkin *pSkin) +{ + auto Position = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin); + m_vSkins.erase(Position); +} + +int CSkins7::Num() +{ + return m_vSkins.size(); +} + +int CSkins7::NumSkinPart(int Part) +{ + return m_avSkinParts[Part].size(); +} + +const CSkins7::CSkin *CSkins7::Get(int Index) +{ + return &m_vSkins[maximum((std::size_t)0, Index % m_vSkins.size())]; +} + +int CSkins7::Find(const char *pName, bool AllowSpecialSkin) +{ + for(unsigned int i = 0; i < m_vSkins.size(); i++) + { + if(str_comp(m_vSkins[i].m_aName, pName) == 0 && ((m_vSkins[i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialSkin)) + return i; + } + return -1; +} + +const CSkins7::CSkinPart *CSkins7::GetSkinPart(int Part, int Index) +{ + int Size = m_avSkinParts[Part].size(); + return &m_avSkinParts[Part][maximum(0, Index % Size)]; +} + +int CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) +{ + for(unsigned int i = 0; i < m_avSkinParts[Part].size(); i++) + { + if(str_comp(m_avSkinParts[Part][i].m_aName, pName) == 0 && ((m_avSkinParts[Part][i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialPart)) + return i; + } + return -1; +} + +void CSkins7::RandomizeSkin(int Dummy) +{ + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + int Hue = rand() % 255; + int Sat = rand() % 255; + int Lgt = rand() % 255; + int Alp = 0; + if(Part == protocol7::SKINPART_MARKING) + Alp = rand() % 255; + int ColorVariable = (Alp << 24) | (Hue << 16) | (Sat << 8) | Lgt; + *CSkins7::ms_apUCCVariables[Dummy][Part] = true; + *CSkins7::ms_apColorVariables[Dummy][Part] = ColorVariable; + } + + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) + { + const CSkins7::CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); + while(pSkinPart->m_Flags & CSkins7::SKINFLAG_SPECIAL) + pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); + mem_copy(CSkins7::ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); + } +} + +ColorRGBA CSkins7::GetColor(int Value, bool UseAlpha) const +{ + float Dark = DARKEST_COLOR_LGT / 255.0f; + ColorRGBA Color = color_cast(ColorHSLA(Value).UnclampLighting(Dark)); + float Alpha = UseAlpha ? ((Value >> 24) & 0xff) / 255.0f : 1.0f; + Color.a = Alpha; + return Color; +} + +ColorRGBA CSkins7::GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const +{ + static const int s_aTeamColors[3] = {0xC4C34E, 0x00FF6B, 0x9BFF6B}; + + int TeamHue = (s_aTeamColors[Team + 1] >> 16) & 0xff; + int TeamSat = (s_aTeamColors[Team + 1] >> 8) & 0xff; + int TeamLgt = s_aTeamColors[Team + 1] & 0xff; + int PartSat = (PartColor >> 8) & 0xff; + int PartLgt = PartColor & 0xff; + + if(!UseCustomColors) + { + PartSat = 255; + PartLgt = 255; + } + + int MinSat = 160; + int MaxSat = 255; + + int h = TeamHue; + int s = clamp(mix(TeamSat, PartSat, 0.2), MinSat, MaxSat); + int l = clamp(mix(TeamLgt, PartLgt, 0.2), (int)DARKEST_COLOR_LGT, 200); + + int ColorVal = (h << 16) + (s << 8) + l; + + return GetColor(ColorVal, Part == protocol7::SKINPART_MARKING); +} + +bool CSkins7::ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const +{ + // force standard (black) eyes on team skins + if(GameFlags & GAMEFLAG_TEAMS) + { + // TODO: adjust eye color here as well? + if(str_comp(apPartNames[protocol7::SKINPART_EYES], "colorable") == 0 || str_comp(apPartNames[protocol7::SKINPART_EYES], "negative") == 0) + { + str_copy(apPartNames[protocol7::SKINPART_EYES], "standard", protocol7::MAX_SKIN_ARRAY_SIZE); + return false; + } + } + return true; +} + +bool CSkins7::SaveSkinfile(const char *pSaveSkinName, int Dummy) +{ + char aBuf[IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), "skins/%s.json", pSaveSkinName); + IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE); + if(!File) + return false; + + CJsonFileWriter Writer(File); + + Writer.BeginObject(); + Writer.WriteAttribute("skin"); + Writer.BeginObject(); + for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; PartIndex++) + { + if(!ms_apSkinVariables[Dummy][PartIndex][0]) + continue; + + // part start + Writer.WriteAttribute(ms_apSkinPartNames[PartIndex]); + Writer.BeginObject(); + { + Writer.WriteAttribute("filename"); + Writer.WriteStrValue(ms_apSkinVariables[Dummy][PartIndex]); + + const bool CustomColors = *ms_apUCCVariables[PartIndex]; + Writer.WriteAttribute("custom_colors"); + Writer.WriteBoolValue(CustomColors); + + if(CustomColors) + { + for(int ColorComponent = 0; ColorComponent < NUM_COLOR_COMPONENTS - 1; ColorComponent++) + { + int Val = (*ms_apColorVariables[Dummy][PartIndex] >> (2 - ColorComponent) * 8) & 0xff; + Writer.WriteAttribute(ms_apColorComponents[ColorComponent]); + Writer.WriteIntValue(Val); + } + if(PartIndex == protocol7::SKINPART_MARKING) + { + int Val = (*ms_apColorVariables[Dummy][PartIndex] >> 24) & 0xff; + Writer.WriteAttribute(ms_apColorComponents[3]); + Writer.WriteIntValue(Val); + } + } + } + Writer.EndObject(); + } + Writer.EndObject(); + Writer.EndObject(); + + // add new skin to the skin list + AddSkin(pSaveSkinName, Dummy); + return true; +} diff --git a/src/game/client/components/skins7.h b/src/game/client/components/skins7.h new file mode 100644 index 000000000..4809a76e4 --- /dev/null +++ b/src/game/client/components/skins7.h @@ -0,0 +1,97 @@ +/* (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 GAME_CLIENT_COMPONENTS_SKINS7_H +#define GAME_CLIENT_COMPONENTS_SKINS7_H +#include +#include +#include +#include +#include +#include + +#include +#include + +class CSkins7 : public CComponent +{ +public: + enum + { + SKINFLAG_SPECIAL = 1 << 0, + SKINFLAG_STANDARD = 1 << 1, + + DARKEST_COLOR_LGT = 61, + + NUM_COLOR_COMPONENTS = 4, + + HAT_NUM = 2, + HAT_OFFSET_SIDE = 2, + }; + + struct CSkinPart + { + int m_Flags; + char m_aName[24]; + IGraphics::CTextureHandle m_OrgTexture; + IGraphics::CTextureHandle m_ColorTexture; + ColorRGBA m_BloodColor; + + bool operator<(const CSkinPart &Other) { return str_comp_nocase(m_aName, Other.m_aName) < 0; } + }; + + struct CSkin + { + int m_Flags; + char m_aName[24]; + const CSkinPart *m_apParts[protocol7::NUM_SKINPARTS]; + int m_aPartColors[protocol7::NUM_SKINPARTS]; + int m_aUseCustomColors[protocol7::NUM_SKINPARTS]; + + bool operator<(const CSkin &Other) const { return str_comp_nocase(m_aName, Other.m_aName) < 0; } + bool operator==(const CSkin &Other) const { return !str_comp(m_aName, Other.m_aName); } + }; + + static const char *const ms_apSkinPartNames[protocol7::NUM_SKINPARTS]; + static const char *const ms_apColorComponents[NUM_COLOR_COMPONENTS]; + + static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; + static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color + static int *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; + IGraphics::CTextureHandle m_XmasHatTexture; + IGraphics::CTextureHandle m_BotTexture; + + int GetInitAmount() const; + void OnInit() override; + + void AddSkin(const char *pSkinName, int Dummy); + void RemoveSkin(const CSkin *pSkin); + + int Num(); + int NumSkinPart(int Part); + const CSkin *Get(int Index); + int Find(const char *pName, bool AllowSpecialSkin); + const CSkinPart *GetSkinPart(int Part, int Index); + int FindSkinPart(int Part, const char *pName, bool AllowSpecialPart); + void RandomizeSkin(int Dummy); + + ColorRGBA GetColor(int Value, bool UseAlpha) const; + ColorRGBA GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const; + + // returns true if everything was valid and nothing changed + bool ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const; + + bool SaveSkinfile(const char *pSaveSkinName, int Dummy); + + virtual int Sizeof() const override { return sizeof(*this); } + +private: + int m_ScanningPart; + std::vector m_avSkinParts[protocol7::NUM_SKINPARTS]; + std::vector m_vSkins; + CSkin m_DummySkin; + + static int SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser); + static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser); +}; + +#endif diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 345d43cbe..11b4fd383 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -558,6 +558,22 @@ void CSpectator::Spectate(int SpectatorId) if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == SpectatorId) return; + if(Client()->IsSixup()) + { + protocol7::CNetMsg_Cl_SetSpectatorMode Msg; + if(SpectatorId == SPEC_FREEVIEW) + { + Msg.m_SpecMode = protocol7::SPEC_FREEVIEW; + Msg.m_SpectatorId = -1; + } + else + { + Msg.m_SpecMode = protocol7::SPEC_PLAYER; + Msg.m_SpectatorId = SpectatorId; + } + Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true); + return; + } CNetMsg_Cl_SetSpectatorMode Msg; Msg.m_SpectatorId = SpectatorId; Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp index 0c2fbb57e..8a0282fdb 100644 --- a/src/game/client/components/voting.cpp +++ b/src/game/client/components/voting.cpp @@ -31,6 +31,16 @@ void CVoting::ConVote(IConsole::IResult *pResult, void *pUserData) void CVoting::Callvote(const char *pType, const char *pValue, const char *pReason) { + if(Client()->IsSixup()) + { + protocol7::CNetMsg_Cl_CallVote Msg; + Msg.m_pType = pType; + Msg.m_pValue = pValue; + Msg.m_pReason = pReason; + Msg.m_Force = false; + Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true); + return; + } CNetMsg_Cl_CallVote Msg = {0}; Msg.m_pType = pType; Msg.m_pValue = pValue; diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h index 2d3d36d15..87abcb1eb 100644 --- a/src/game/client/components/voting.h +++ b/src/game/client/components/voting.h @@ -26,7 +26,6 @@ class CVoting : public CComponent int m_Yes, m_No, m_Pass, m_Total; bool m_ReceivingOptions; - void AddOption(const char *pDescription); void RemoveOption(const char *pDescription); void ClearOptions(); void Callvote(const char *pType, const char *pValue, const char *pReason); @@ -54,6 +53,7 @@ public: void CallvoteOption(int OptionId, const char *pReason, bool ForceVote = false); void RemovevoteOption(int OptionId); void AddvoteOption(const char *pDescription, const char *pCommand); + void AddOption(const char *pDescription); void Vote(int v); // -1 = no, 1 = yes diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index fdd6b5161..5cc75cb63 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -37,6 +37,9 @@ #include #include +#include +#include + #include "components/background.h" #include "components/binds.h" #include "components/broadcast.h" @@ -66,6 +69,7 @@ #include "components/race_demo.h" #include "components/scoreboard.h" #include "components/skins.h" +#include "components/skins7.h" #include "components/sounds.h" #include "components/spectator.h" #include "components/statboard.h" @@ -79,6 +83,7 @@ const char *CGameClient::Version() const { return GAME_VERSION; } const char *CGameClient::NetVersion() const { return GAME_NETVERSION; } int CGameClient::DDNetVersion() const { return DDNET_VERSION_NUMBER; } const char *CGameClient::DDNetVersionStr() const { return m_aDDNetVersionStr; } +int CGameClient::ClientVersion7() const { return CLIENT_VERSION7; } const char *CGameClient::GetItemName(int Type) const { return m_NetObjHandler.GetObjName(Type); } void CGameClient::OnConsoleInit() @@ -105,6 +110,7 @@ void CGameClient::OnConsoleInit() // make a list of all the systems, make sure to add them in the correct render order m_vpAll.insert(m_vpAll.end(), {&m_Skins, + &m_Skins7, &m_CountryFlags, &m_MapImages, &m_Effects, // doesn't render anything, just updates effects @@ -161,6 +167,7 @@ void CGameClient::OnConsoleInit() // add basic console commands Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team"); Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself to restart"); + Console()->Register("ready_change", "", CFGFLAG_CLIENT, ConReadyChange7, this, "Change ready state (0.7 only)"); // register tune zone command to allow the client prediction to load tunezones from the map Console()->Register("tune_zone", "i[zone] s[tuning] f[value]", CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value"); @@ -272,6 +279,11 @@ void CGameClient::OnInit() // setup item sizes for(int i = 0; i < NUM_NETOBJTYPES; i++) Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i)); + // HACK: only set static size for items, which were available in the first 0.7 release + // so new items don't break the snapshot delta + static const int OLD_NUM_NETOBJTYPES = 23; + for(int i = 0; i < OLD_NUM_NETOBJTYPES; i++) + Client()->SnapSetStaticsize7(i, m_NetObjHandler7.GetObjSize(i)); TextRender()->LoadFonts(); TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile); @@ -330,6 +342,14 @@ void CGameClient::OnInit() g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL); m_Menus.RenderLoading(pLoadingDDNetCaption, pLoadingMessageAssets, 1); } + for(int i = 0; i < client_data7::g_pData->m_NumImages; i++) + { + if(client_data7::g_pData->m_aImages[i].m_pFilename[0] == '\0') // handle special null image without filename + client_data7::g_pData->m_aImages[i].m_Id = IGraphics::CTextureHandle(); + else if(i == client_data7::IMAGE_DEADTEE) + client_data7::g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(client_data7::g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, 0); + m_Menus.RenderLoading(pLoadingDDNetCaption, Localize("Initializing assets"), 1); + } m_GameWorld.m_pCollision = Collision(); m_GameWorld.m_pTuningList = m_aTuningList; @@ -726,17 +746,27 @@ void CGameClient::OnRender() { if(m_aCheckInfo[0] == 0) { - if( - str_comp(m_aClients[m_aLocalIds[0]].m_aName, Client()->PlayerName()) || - str_comp(m_aClients[m_aLocalIds[0]].m_aClan, g_Config.m_PlayerClan) || - m_aClients[m_aLocalIds[0]].m_Country != g_Config.m_PlayerCountry || - str_comp(m_aClients[m_aLocalIds[0]].m_aSkinName, g_Config.m_ClPlayerSkin) || - m_aClients[m_aLocalIds[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor || - m_aClients[m_aLocalIds[0]].m_ColorBody != (int)g_Config.m_ClPlayerColorBody || - m_aClients[m_aLocalIds[0]].m_ColorFeet != (int)g_Config.m_ClPlayerColorFeet) - SendInfo(false); + if(m_pClient->IsSixup()) + { + if(!GotWantedSkin7(false)) + SendSkinChange7(false); + else + m_aCheckInfo[0] = -1; + } else - m_aCheckInfo[0] = -1; + { + if( + str_comp(m_aClients[m_aLocalIds[0]].m_aName, Client()->PlayerName()) || + str_comp(m_aClients[m_aLocalIds[0]].m_aClan, g_Config.m_PlayerClan) || + m_aClients[m_aLocalIds[0]].m_Country != g_Config.m_PlayerCountry || + str_comp(m_aClients[m_aLocalIds[0]].m_aSkinName, g_Config.m_ClPlayerSkin) || + m_aClients[m_aLocalIds[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor || + m_aClients[m_aLocalIds[0]].m_ColorBody != (int)g_Config.m_ClPlayerColorBody || + m_aClients[m_aLocalIds[0]].m_ColorFeet != (int)g_Config.m_ClPlayerColorFeet) + SendInfo(false); + else + m_aCheckInfo[0] = -1; + } } if(m_aCheckInfo[0] > 0) @@ -746,17 +776,27 @@ void CGameClient::OnRender() { if(m_aCheckInfo[1] == 0) { - if( - str_comp(m_aClients[m_aLocalIds[1]].m_aName, Client()->DummyName()) || - str_comp(m_aClients[m_aLocalIds[1]].m_aClan, g_Config.m_ClDummyClan) || - m_aClients[m_aLocalIds[1]].m_Country != g_Config.m_ClDummyCountry || - str_comp(m_aClients[m_aLocalIds[1]].m_aSkinName, g_Config.m_ClDummySkin) || - m_aClients[m_aLocalIds[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor || - m_aClients[m_aLocalIds[1]].m_ColorBody != (int)g_Config.m_ClDummyColorBody || - m_aClients[m_aLocalIds[1]].m_ColorFeet != (int)g_Config.m_ClDummyColorFeet) - SendDummyInfo(false); + if(m_pClient->IsSixup()) + { + if(!GotWantedSkin7(true)) + SendSkinChange7(true); + else + m_aCheckInfo[1] = -1; + } else - m_aCheckInfo[1] = -1; + { + if( + str_comp(m_aClients[m_aLocalIds[1]].m_aName, Client()->DummyName()) || + str_comp(m_aClients[m_aLocalIds[1]].m_aClan, g_Config.m_ClDummyClan) || + m_aClients[m_aLocalIds[1]].m_Country != g_Config.m_ClDummyCountry || + str_comp(m_aClients[m_aLocalIds[1]].m_aSkinName, g_Config.m_ClDummySkin) || + m_aClients[m_aLocalIds[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor || + m_aClients[m_aLocalIds[1]].m_ColorBody != (int)g_Config.m_ClDummyColorBody || + m_aClients[m_aLocalIds[1]].m_ColorFeet != (int)g_Config.m_ClDummyColorFeet) + SendDummyInfo(false); + else + m_aCheckInfo[1] = -1; + } } if(m_aCheckInfo[1] > 0) @@ -815,16 +855,18 @@ void CGameClient::OnRelease() void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dummy) { // special messages + static_assert((int)NETMSGTYPE_SV_TUNEPARAMS == (int)protocol7::NETMSGTYPE_SV_TUNEPARAMS, "0.6 and 0.7 tune message id do not match"); if(MsgId == NETMSGTYPE_SV_TUNEPARAMS) { // unpack the new tuning CTuningParams NewTuning; int *pParams = (int *)&NewTuning; - // No jetpack on DDNet incompatible servers: - NewTuning.m_JetpackStrength = 0; for(unsigned i = 0; i < sizeof(CTuningParams) / sizeof(int); i++) { - int value = pUnpacker->GetInt(); + // 31 is the magic number index of laser_damage + // which was removed in 0.7 + // also in 0.6 it is unsed so we just set it to 0 + int value = (Client()->IsSixup() && i == 30) ? 0 : pUnpacker->GetInt(); // check for unpacking errors if(pUnpacker->Error()) @@ -833,6 +875,9 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm pParams[i] = value; } + // No jetpack on DDNet incompatible servers: + NewTuning.m_JetpackStrength = 0; + m_ServerMode = SERVERMODE_PURE; m_aReceivedTuning[Conn] = true; @@ -841,12 +886,18 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm return; } - void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker); + void *pRawMsg = TranslateGameMsg(&MsgId, pUnpacker, Conn); + if(!pRawMsg) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn()); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + // the 0.7 version of this error message is printed on translation + // in sixup/translate_game.cpp + if(!Client()->IsSixup()) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn()); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + } return; } @@ -1591,6 +1642,10 @@ void CGameClient::OnNewSnapshot() m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)Item.m_pData; m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_Id); + // needed for 0.7 survival + // to auto spec players when dead + if(Client()->IsSixup()) + m_Snap.m_SpecInfo.m_Active = true; m_Snap.m_SpecInfo.m_SpectatorId = m_Snap.m_pSpectatorInfo->m_SpectatorId; } else if(Item.m_Type == NETOBJTYPE_GAMEINFO) @@ -1728,7 +1783,11 @@ void CGameClient::OnNewSnapshot() if(Client()->State() == IClient::STATE_DEMOPLAYBACK) { if(m_Snap.m_LocalClientId == -1 && m_DemoSpecId == SPEC_FOLLOW) - m_DemoSpecId = SPEC_FREEVIEW; + { + // TODO: can this be done in the translation layer? + if(!Client()->IsSixup()) + m_DemoSpecId = SPEC_FREEVIEW; + } if(m_DemoSpecId != SPEC_FOLLOW) { m_Snap.m_SpecInfo.m_Active = true; @@ -1770,18 +1829,30 @@ void CGameClient::OnNewSnapshot() }); bool TimeScore = m_GameInfo.m_TimeScore; + bool Race7 = Client()->IsSixup() && m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameFlags & protocol7::GAMEFLAG_RACE; // sort player infos by score mem_copy(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByName, sizeof(m_Snap.m_apInfoByScore)); - std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS, - [TimeScore](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool { - if(!p2) - return static_cast(p1); - if(!p1) - return false; - return (((TimeScore && p1->m_Score == -9999) ? std::numeric_limits::min() : p1->m_Score) > - ((TimeScore && p2->m_Score == -9999) ? std::numeric_limits::min() : p2->m_Score)); - }); + if(Race7) + std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS, + [](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool { + if(!p2) + return static_cast(p1); + if(!p1) + return false; + return (((p1->m_Score == -1) ? std::numeric_limits::max() : p1->m_Score) < + ((p2->m_Score == -1) ? std::numeric_limits::max() : p2->m_Score)); + }); + else + std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS, + [TimeScore](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool { + if(!p2) + return static_cast(p1); + if(!p1) + return false; + return (((TimeScore && p1->m_Score == -9999) ? std::numeric_limits::min() : p1->m_Score) > + ((TimeScore && p2->m_Score == -9999) ? std::numeric_limits::min() : p2->m_Score)); + }); // sort player infos by DDRace Team (and score between) int Index = 0; @@ -2226,11 +2297,28 @@ void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay) { m_RenderInfo.m_ColorBody = color_cast(ColorHSLA(aTeamColors[m_Team])); m_RenderInfo.m_ColorFeet = color_cast(ColorHSLA(aTeamColors[m_Team])); + + // 0.7 + { + const ColorRGBA aTeamColorsSixup[2] = { + ColorRGBA(0.753f, 0.318f, 0.318f, 1.0f), + ColorRGBA(0.318f, 0.471f, 0.753f, 1.0f)}; + const ColorRGBA aMarkingColorsSixup[2] = { + ColorRGBA(0.824f, 0.345f, 0.345f, 1.0f), + ColorRGBA(0.345f, 0.514f, 0.824f, 1.0f)}; + float MarkingAlpha = m_RenderInfo.m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a; + for(auto &Color : m_RenderInfo.m_Sixup.m_aColors) + Color = aTeamColorsSixup[m_Team]; + if(MarkingAlpha > 0.1f) + m_RenderInfo.m_Sixup.m_aColors[protocol7::SKINPART_MARKING] = aMarkingColorsSixup[m_Team]; + } } else { m_RenderInfo.m_ColorBody = color_cast(ColorHSLA(12829350)); m_RenderInfo.m_ColorFeet = color_cast(ColorHSLA(12829350)); + for(auto &Color : m_RenderInfo.m_Sixup.m_aColors) + Color = color_cast(ColorHSLA(12829350)); } } } @@ -2315,8 +2403,63 @@ void CGameClient::SendSwitchTeam(int Team) m_Camera.OnReset(); } +void CGameClient::SendStartInfo7(bool Dummy) const +{ + protocol7::CNetMsg_Cl_StartInfo Msg; + Msg.m_pName = Dummy ? Client()->DummyName() : Config()->m_PlayerName; + Msg.m_pClan = Dummy ? Config()->m_ClDummyClan : Config()->m_PlayerClan; + Msg.m_Country = Dummy ? Config()->m_ClDummyCountry : Config()->m_PlayerCountry; + for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) + { + Msg.m_apSkinPartNames[p] = CSkins7::ms_apSkinVariables[(int)Dummy][p]; + Msg.m_aUseCustomColors[p] = *CSkins7::ms_apUCCVariables[(int)Dummy][p]; + Msg.m_aSkinPartColors[p] = *CSkins7::ms_apColorVariables[(int)Dummy][p]; + } + CMsgPacker Packer(&Msg, false, true); + if(Msg.Pack(&Packer)) + return; + Client()->SendMsg((int)Dummy, &Packer, MSGFLAG_VITAL | MSGFLAG_FLUSH); +} + +void CGameClient::SendSkinChange7(bool Dummy) +{ + protocol7::CNetMsg_Cl_SkinChange Msg; + for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) + { + Msg.m_apSkinPartNames[p] = CSkins7::ms_apSkinVariables[(int)Dummy][p]; + Msg.m_aUseCustomColors[p] = *CSkins7::ms_apUCCVariables[(int)Dummy][p]; + Msg.m_aSkinPartColors[p] = *CSkins7::ms_apColorVariables[(int)Dummy][p]; + } + CMsgPacker Packer(&Msg, false, true); + if(Msg.Pack(&Packer)) + return; + Client()->SendMsg((int)Dummy, &Packer, MSGFLAG_VITAL | MSGFLAG_FLUSH); + m_aCheckInfo[(int)Dummy] = Client()->GameTickSpeed(); +} + +bool CGameClient::GotWantedSkin7(bool Dummy) +{ + // TODO: add name change ddnet extension to 0.7 protocol + // if(str_comp(m_aClients[m_aLocalIds[(int)Dummy]].m_aName, Dummy ? Client()->DummyName() : Client()->PlayerName())) + // return false; + // if(str_comp(m_aClients[m_aLocalIds[(int)Dummy]].m_aClan, Dummy ? g_Config.m_ClDummyClan : g_Config.m_PlayerClan)) + // return false; + // if(m_aClients[m_aLocalIds[(int)Dummy]].m_Country != (Dummy ? g_Config.m_ClDummyCountry : g_Config.m_PlayerCountry)) + // return false; + + return true; +} + void CGameClient::SendInfo(bool Start) { + if(m_pClient->IsSixup()) + { + if(Start) + SendStartInfo7(false); + else + SendSkinChange7(false); + return; + } if(Start) { CNetMsg_Cl_StartInfo Msg; @@ -2351,6 +2494,14 @@ void CGameClient::SendInfo(bool Start) void CGameClient::SendDummyInfo(bool Start) { + if(m_pClient->IsSixup()) + { + if(Start) + SendStartInfo7(true); + else + SendSkinChange7(true); + return; + } if(Start) { CNetMsg_Cl_StartInfo Msg; @@ -2395,6 +2546,17 @@ void CGameClient::SendKill(int ClientId) const } } +void CGameClient::SendReadyChange7() +{ + if(!Client()->IsSixup()) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "Error you have to be connected to a 0.7 server to use ready_change"); + return; + } + protocol7::CNetMsg_Cl_ReadyChange Msg; + Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL, true); +} + void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData) { ((CGameClient *)pUserData)->SendSwitchTeam(pResult->GetInteger(0)); @@ -2405,6 +2567,13 @@ void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData) ((CGameClient *)pUserData)->SendKill(-1); } +void CGameClient::ConReadyChange7(IConsole::IResult *pResult, void *pUserData) +{ + CGameClient *pClient = static_cast(pUserData); + if(pClient->Client()->State() == IClient::STATE_ONLINE) + pClient->SendReadyChange7(); +} + void CGameClient::ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { CGameClient *pThis = static_cast(pUserData); @@ -2434,8 +2603,10 @@ void CGameClient::ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserDa { pfnCallback(pResult, pCallbackUserData); if(pResult->NumArguments()) + { if(g_Config.m_ClDummy && !((CGameClient *)pUserData)->Client()->DummyConnected()) g_Config.m_ClDummy = 0; + } } void CGameClient::ConchainClTextEntitiesSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) @@ -3565,6 +3736,11 @@ CNetObjHandler *CGameClient::GetNetObjHandler() return &m_NetObjHandler; } +protocol7::CNetObjHandler *CGameClient::GetNetObjHandler7() +{ + return &m_NetObjHandler7; +} + void CGameClient::SnapCollectEntities() { int NumSnapItems = Client()->SnapNumItems(IClient::SNAP_CURRENT); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 0d4eae079..6a2732bb9 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -17,6 +17,9 @@ #include +#include +#include + // components #include "components/background.h" #include "components/binds.h" @@ -48,6 +51,7 @@ #include "components/race_demo.h" #include "components/scoreboard.h" #include "components/skins.h" +#include "components/skins7.h" #include "components/sounds.h" #include "components/spectator.h" #include "components/statboard.h" @@ -121,6 +125,7 @@ public: CParticles m_Particles; CMenus m_Menus; CSkins m_Skins; + CSkins7 m_Skins7; CCountryFlags m_CountryFlags; CFlow m_Flow; CHud m_Hud; @@ -157,6 +162,7 @@ private: std::vector m_vpAll; std::vector m_vpInput; CNetObjHandler m_NetObjHandler; + protocol7::CNetObjHandler m_NetObjHandler7; class IEngine *m_pEngine; class IInput *m_pInput; @@ -203,6 +209,7 @@ private: static void ConTeam(IConsole::IResult *pResult, void *pUserData); static void ConKill(IConsole::IResult *pResult, void *pUserData); + static void ConReadyChange7(IConsole::IResult *pResult, void *pUserData); static void ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); @@ -478,6 +485,7 @@ public: void OnInit() override; void OnConsoleInit() override; void OnStateChange(int NewState, int OldState) override; + void *TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn); void OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dummy) override; void InvalidateSnapshot() override; void OnNewSnapshot() override; @@ -508,13 +516,21 @@ public: const char *NetVersion() const override; int DDNetVersion() const override; const char *DDNetVersionStr() const override; + virtual int ClientVersion7() const override; + + void DoTeamChangeMessage7(const char *pName, int ClientId, int Team, const char *pPrefix = ""); // actions // TODO: move these void SendSwitchTeam(int Team); + void SendStartInfo7(bool Dummy) const; + void SendSkinChange7(bool Dummy); + // Returns true if the requested skin change got applied by the server + bool GotWantedSkin7(bool Dummy); void SendInfo(bool Start); void SendDummyInfo(bool Start) override; void SendKill(int ClientId) const; + void SendReadyChange7(); int m_NextChangeInfo; @@ -557,6 +573,7 @@ public: bool IsLocalCharSuper() const; bool CanDisplayWarning() const override; CNetObjHandler *GetNetObjHandler() override; + protocol7::CNetObjHandler *GetNetObjHandler7() override; void LoadGameSkin(const char *pPath, bool AsDir = false); void LoadEmoticonsSkin(const char *pPath, bool AsDir = false); diff --git a/src/game/client/render.cpp b/src/game/client/render.cpp index 998cfec4e..ed2974a7a 100644 --- a/src/game/client/render.cpp +++ b/src/game/client/render.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -89,6 +90,13 @@ void CRenderTools::SelectSprite(int Id, int Flags, int sx, int sy) const SelectSprite(&g_pData->m_aSprites[Id], Flags, sx, sy); } +void CRenderTools::SelectSprite7(int Id, int Flags, int sx, int sy) const +{ + if(Id < 0 || Id >= client_data7::g_pData->m_NumSprites) + return; + SelectSprite(&client_data7::g_pData->m_aSprites[Id], Flags, sx, sy); +} + void CRenderTools::GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const { int w = pSprite->m_W; @@ -263,6 +271,234 @@ void CRenderTools::GetRenderTeeOffsetToRenderedTee(const CAnimState *pAnim, cons } void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const +{ + if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY].IsValid()) + RenderTee7(pAnim, pInfo, Emote, Dir, Pos); + else + RenderTee6(pAnim, pInfo, Emote, Dir, Pos, Alpha); + + Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); + Graphics()->QuadsSetRotation(0); +} + +void CRenderTools::RenderTee7(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos) const +{ + vec2 Direction = Dir; + vec2 Position = Pos; + bool IsBot = false; + + // first pass we draw the outline + // second pass we draw the filling + for(int Pass = 0; Pass < 2; Pass++) + { + bool OutLine = Pass == 0; + + for(int Filling = 0; Filling < 2; Filling++) + { + float AnimScale = pInfo->m_Size * 1.0f / 64.0f; + float BaseSize = pInfo->m_Size; + if(Filling == 1) + { + vec2 BodyPos = Position + vec2(pAnim->GetBody()->m_X, pAnim->GetBody()->m_Y) * AnimScale; + IGraphics::CQuadItem BodyItem(BodyPos.x, BodyPos.y, BaseSize, BaseSize); + IGraphics::CQuadItem BotItem(BodyPos.x + (2.f / 3.f) * AnimScale, BodyPos.y + (-16 + 2.f / 3.f) * AnimScale, BaseSize, BaseSize); // x+0.66, y+0.66 to correct some rendering bug + IGraphics::CQuadItem Item; + + // draw bot visuals (background) + if(IsBot && !OutLine) + { + Graphics()->TextureSet(pInfo->m_Sixup.m_BotTexture); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + SelectSprite7(client_data7::SPRITE_TEE_BOT_BACKGROUND, 0, 0, 0); + Item = BotItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->QuadsEnd(); + } + + // draw bot visuals (foreground) + if(IsBot && !OutLine) + { + Graphics()->TextureSet(pInfo->m_Sixup.m_BotTexture); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + SelectSprite7(client_data7::SPRITE_TEE_BOT_FOREGROUND, 0, 0, 0); + Item = BotItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->SetColor(pInfo->m_Sixup.m_BotColor.r, pInfo->m_Sixup.m_BotColor.g, pInfo->m_Sixup.m_BotColor.b, pInfo->m_Sixup.m_BotColor.a); + SelectSprite7(client_data7::SPRITE_TEE_BOT_GLOW, 0, 0, 0); + Item = BotItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->QuadsEnd(); + } + + // draw decoration + if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_DECORATION].IsValid()) + { + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_DECORATION]); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); + Graphics()->SetColor(pInfo->m_Sixup.m_aColors[protocol7::SKINPART_DECORATION].r, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_DECORATION].g, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_DECORATION].b, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_DECORATION].a); + SelectSprite7(OutLine ? client_data7::SPRITE_TEE_DECORATION_OUTLINE : client_data7::SPRITE_TEE_DECORATION, 0, 0, 0); + Item = BodyItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->QuadsEnd(); + } + + // draw body (behind marking) + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY]); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); + if(OutLine) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + SelectSprite7(client_data7::SPRITE_TEE_BODY_OUTLINE, 0, 0, 0); + } + else + { + Graphics()->SetColor(pInfo->m_Sixup.m_aColors[protocol7::SKINPART_BODY].r, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_BODY].g, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_BODY].b, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_BODY].a); + SelectSprite7(client_data7::SPRITE_TEE_BODY, 0, 0, 0); + } + Item = BodyItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->QuadsEnd(); + + // draw marking + if(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_MARKING].IsValid() && !OutLine) + { + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_MARKING]); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); + Graphics()->SetColor(pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].r * pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].g * pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a, + pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].b * pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_MARKING].a); + SelectSprite7(client_data7::SPRITE_TEE_MARKING, 0, 0, 0); + Item = BodyItem; + Graphics()->QuadsDraw(&Item, 1); + Graphics()->QuadsEnd(); + } + + // draw body (in front of marking) + if(!OutLine) + { + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_BODY]); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + for(int t = 0; t < 2; t++) + { + SelectSprite7(t == 0 ? client_data7::SPRITE_TEE_BODY_SHADOW : client_data7::SPRITE_TEE_BODY_UPPER_OUTLINE, 0, 0, 0); + Item = BodyItem; + Graphics()->QuadsDraw(&Item, 1); + } + Graphics()->QuadsEnd(); + } + + // draw eyes + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_EYES]); + Graphics()->QuadsBegin(); + Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); + if(IsBot) + { + Graphics()->SetColor(pInfo->m_Sixup.m_BotColor.r, pInfo->m_Sixup.m_BotColor.g, pInfo->m_Sixup.m_BotColor.b, pInfo->m_Sixup.m_BotColor.a); + Emote = EMOTE_SURPRISE; + } + else + Graphics()->SetColor(pInfo->m_Sixup.m_aColors[protocol7::SKINPART_EYES].r, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_EYES].g, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_EYES].b, pInfo->m_Sixup.m_aColors[protocol7::SKINPART_EYES].a); + if(Pass == 1) + { + switch(Emote) + { + case EMOTE_PAIN: + SelectSprite7(client_data7::SPRITE_TEE_EYES_PAIN, 0, 0, 0); + break; + case EMOTE_HAPPY: + SelectSprite7(client_data7::SPRITE_TEE_EYES_HAPPY, 0, 0, 0); + break; + case EMOTE_SURPRISE: + SelectSprite7(client_data7::SPRITE_TEE_EYES_SURPRISE, 0, 0, 0); + break; + case EMOTE_ANGRY: + SelectSprite7(client_data7::SPRITE_TEE_EYES_ANGRY, 0, 0, 0); + break; + default: + SelectSprite7(client_data7::SPRITE_TEE_EYES_NORMAL, 0, 0, 0); + break; + } + + float EyeScale = BaseSize * 0.60f; + float h = Emote == EMOTE_BLINK ? BaseSize * 0.15f / 2.0f : EyeScale / 2.0f; + vec2 Offset = vec2(Direction.x * 0.125f, -0.05f + Direction.y * 0.10f) * BaseSize; + IGraphics::CQuadItem QuadItem(BodyPos.x + Offset.x, BodyPos.y + Offset.y, EyeScale, h); + Graphics()->QuadsDraw(&QuadItem, 1); + } + Graphics()->QuadsEnd(); + + // TODO: ddnet has own hats + // draw xmas hat + // if(!OutLine && pInfo->m_HatTexture.IsValid()) + // { + // Graphics()->TextureSet(pInfo->m_HatTexture); + // Graphics()->QuadsBegin(); + // Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle*pi * 2); + // Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + // int Flag = Direction.x < 0.0f ? SPRITE_FLAG_FLIP_X : 0; + // switch(pInfo->m_HatSpriteIndex) + // { + // case 0: + // SelectSprite7(SPRITE_TEE_HATS_TOP1, Flag, 0, 0); + // break; + // case 1: + // SelectSprite7(SPRITE_TEE_HATS_TOP2, Flag, 0, 0); + // break; + // case 2: + // SelectSprite7(SPRITE_TEE_HATS_SIDE1, Flag, 0, 0); + // break; + // case 3: + // SelectSprite7(SPRITE_TEE_HATS_SIDE2, Flag, 0, 0); + // } + // Item = BodyItem; + // Graphics()->QuadsDraw(&Item, 1); + // Graphics()->QuadsEnd(); + // } + } + + // draw feet + Graphics()->TextureSet(pInfo->m_Sixup.m_aTextures[protocol7::SKINPART_FEET]); + Graphics()->QuadsBegin(); + const CAnimKeyframe *pFoot = Filling ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); + + float w = BaseSize / 2.1f; + float h = w; + + Graphics()->QuadsSetRotation(pFoot->m_Angle * pi * 2); + + if(OutLine) + { + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + SelectSprite7(client_data7::SPRITE_TEE_FOOT_OUTLINE, 0, 0, 0); + } + else + { + bool Indicate = !pInfo->m_GotAirJump && g_Config.m_ClAirjumpindicator; + float ColorScale = 1.0f; + if(Indicate) + ColorScale = 0.5f; + Graphics()->SetColor( + pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].r * ColorScale, + pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].g * ColorScale, + pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].b * ColorScale, + pInfo->m_Sixup.m_aColors[protocol7::SKINPART_FEET].a); + SelectSprite7(client_data7::SPRITE_TEE_FOOT, 0, 0, 0); + } + + IGraphics::CQuadItem QuadItem(Position.x + pFoot->m_X * AnimScale, Position.y + pFoot->m_Y * AnimScale, w, h); + Graphics()->QuadsDraw(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + } +} + +void CRenderTools::RenderTee6(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha) const { vec2 Direction = Dir; vec2 Position = Pos; @@ -271,15 +507,15 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf // first pass we draw the outline // second pass we draw the filling - for(int p = 0; p < 2; p++) + for(int Pass = 0; Pass < 2; Pass++) { - int OutLine = p == 0 ? 1 : 0; + int OutLine = Pass == 0 ? 1 : 0; - for(int f = 0; f < 2; f++) + for(int Filling = 0; Filling < 2; Filling++) { float AnimScale, BaseSize; GetRenderTeeAnimScaleAndBaseSize(pInfo, AnimScale, BaseSize); - if(f == 1) + if(Filling == 1) { Graphics()->QuadsSetRotation(pAnim->GetBody()->m_Angle * pi * 2); @@ -292,7 +528,7 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, OutLine, BodyPos.x, BodyPos.y, BodyScale, BodyScale); // draw eyes - if(p == 1) + if(Pass == 1) { int QuadOffset = 2; int EyeQuadOffset = 0; @@ -333,7 +569,7 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf } // draw feet - const CAnimKeyframe *pFoot = f ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); + const CAnimKeyframe *pFoot = Filling ? pAnim->GetFrontFoot() : pAnim->GetBackFoot(); float w = BaseSize; float h = BaseSize / 2; @@ -362,9 +598,6 @@ void CRenderTools::RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInf Graphics()->RenderQuadContainerAsSprite(m_TeeQuadContainerIndex, QuadOffset, Position.x + pFoot->m_X * AnimScale, Position.y + pFoot->m_Y * AnimScale, w / 64.f, h / 32.f); } } - - Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); - Graphics()->QuadsSetRotation(0); } void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight) diff --git a/src/game/client/render.h b/src/game/client/render.h index 0bbbbbacb..ed5a31a9f 100644 --- a/src/game/client/render.h +++ b/src/game/client/render.h @@ -8,6 +8,7 @@ #include #include +#include class CAnimState; class CSpeedupTile; @@ -25,6 +26,8 @@ struct CEnvPointBezier_upstream; struct CMapItemGroup; struct CQuad; +#include + class CTeeRenderInfo { public: @@ -46,6 +49,8 @@ public: m_GotAirJump = true; m_TeeRenderFlags = 0; m_FeetFlipped = false; + + m_Sixup.Reset(); } CSkin::SSkinTextures m_OriginalRenderSkin; @@ -67,6 +72,31 @@ public: { return m_CustomColoredSkin ? m_ColorableRenderSkin.m_Body.IsValid() : m_OriginalRenderSkin.m_Body.IsValid(); } + + class CSixup + { + public: + void Reset() + { + for(auto &Texture : m_aTextures) + Texture = IGraphics::CTextureHandle(); + m_BotTexture = IGraphics::CTextureHandle(); + } + bool Valid() const + { + for(const auto &Texture : m_aTextures) + if(!Texture.IsValid()) + return false; + return true; + } + + IGraphics::CTextureHandle m_aTextures[protocol7::NUM_SKINPARTS]; + vec4 m_aColors[protocol7::NUM_SKINPARTS]; + IGraphics::CTextureHandle m_BotTexture; + vec4 m_BotColor; + }; + + CSixup m_Sixup; }; // Tee Render Flags @@ -129,6 +159,9 @@ class CRenderTools static void GetRenderTeeBodyScale(float BaseSize, float &BodyScale); static void GetRenderTeeFeetScale(float BaseSize, float &FeetScaleWidth, float &FeetScaleHeight); + void RenderTee6(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f) const; + void RenderTee7(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos) const; + public: class IGraphics *Graphics() const { return m_pGraphics; } class ITextRender *TextRender() const { return m_pTextRender; } @@ -137,6 +170,7 @@ public: void SelectSprite(CDataSprite *pSprite, int Flags = 0, int sx = 0, int sy = 0) const; void SelectSprite(int Id, int Flags = 0, int sx = 0, int sy = 0) const; + void SelectSprite7(int Id, int Flags = 0, int sx = 0, int sy = 0) const; void GetSpriteScale(const CDataSprite *pSprite, float &ScaleX, float &ScaleY) const; void GetSpriteScale(int Id, float &ScaleX, float &ScaleY) const; diff --git a/src/game/client/sixup_translate_game.cpp b/src/game/client/sixup_translate_game.cpp new file mode 100644 index 000000000..bd918b33d --- /dev/null +++ b/src/game/client/sixup_translate_game.cpp @@ -0,0 +1,689 @@ +#include + +#include + +enum +{ + STR_TEAM_GAME, + STR_TEAM_RED, + STR_TEAM_BLUE, + STR_TEAM_SPECTATORS, +}; + +static int GetStrTeam7(int Team, bool Teamplay) +{ + if(Teamplay) + { + if(Team == TEAM_RED) + return STR_TEAM_RED; + else if(Team == TEAM_BLUE) + return STR_TEAM_BLUE; + } + else if(Team == 0) + return STR_TEAM_GAME; + + return STR_TEAM_SPECTATORS; +} + +enum +{ + DO_CHAT = 0, + DO_BROADCAST, + DO_SPECIAL, + + PARA_NONE = 0, + PARA_I, + PARA_II, + PARA_III, +}; + +struct CGameMsg7 +{ + int m_Action; + int m_ParaType; + const char *m_pText; +}; + +static CGameMsg7 gs_GameMsgList7[protocol7::NUM_GAMEMSGS] = { + {/*GAMEMSG_TEAM_SWAP*/ DO_CHAT, PARA_NONE, "Teams were swapped"}, // Localize("Teams were swapped") + {/*GAMEMSG_SPEC_INVALIDID*/ DO_CHAT, PARA_NONE, "Invalid spectator id used"}, //! + {/*GAMEMSG_TEAM_SHUFFLE*/ DO_CHAT, PARA_NONE, "Teams were shuffled"}, // Localize("Teams were shuffled") + {/*GAMEMSG_TEAM_BALANCE*/ DO_CHAT, PARA_NONE, "Teams have been balanced"}, // Localize("Teams have been balanced") + {/*GAMEMSG_CTF_DROP*/ DO_SPECIAL, PARA_NONE, ""}, // special - play ctf drop sound + {/*GAMEMSG_CTF_RETURN*/ DO_SPECIAL, PARA_NONE, ""}, // special - play ctf return sound + + {/*GAMEMSG_TEAM_ALL*/ DO_SPECIAL, PARA_I, ""}, // special - add team name + {/*GAMEMSG_TEAM_BALANCE_VICTIM*/ DO_SPECIAL, PARA_I, ""}, // special - add team name + {/*GAMEMSG_CTF_GRAB*/ DO_SPECIAL, PARA_I, ""}, // special - play ctf grab sound based on team + + {/*GAMEMSG_CTF_CAPTURE*/ DO_SPECIAL, PARA_III, ""}, // special - play ctf capture sound + capture chat message + + {/*GAMEMSG_GAME_PAUSED*/ DO_SPECIAL, PARA_I, ""}, // special - add player name +}; + +void CGameClient::DoTeamChangeMessage7(const char *pName, int ClientId, int Team, const char *pPrefix) +{ + char aBuf[128]; + switch(GetStrTeam7(Team, m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS)) + { + case STR_TEAM_GAME: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the game"), pName, pPrefix); break; + case STR_TEAM_RED: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the red team"), pName, pPrefix); break; + case STR_TEAM_BLUE: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the blue team"), pName, pPrefix); break; + case STR_TEAM_SPECTATORS: str_format(aBuf, sizeof(aBuf), Localize("'%s' %sjoined the spectators"), pName, pPrefix); break; + } + m_Chat.AddLine(-1, 0, aBuf); +} + +void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn) +{ + if(!m_pClient->IsSixup()) + { + return m_NetObjHandler.SecureUnpackMsg(*pMsgId, pUnpacker); + } + + void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(*pMsgId, pUnpacker); + if(!pRawMsg) + { + if(*pMsgId > __NETMSGTYPE_UUID_HELPER && *pMsgId < OFFSET_MAPITEMTYPE_UUID) + { + void *pDDNetExMsg = m_NetObjHandler.SecureUnpackMsg(*pMsgId, pUnpacker); + if(pDDNetExMsg) + return pDDNetExMsg; + } + + dbg_msg("sixup", "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(*pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn()); + return nullptr; + } + static char s_aRawMsg[1024]; + + if(*pMsgId == protocol7::NETMSGTYPE_SV_MOTD) + { + protocol7::CNetMsg_Sv_Motd *pMsg7 = (protocol7::CNetMsg_Sv_Motd *)pRawMsg; + ::CNetMsg_Sv_Motd *pMsg = (::CNetMsg_Sv_Motd *)s_aRawMsg; + + pMsg->m_pMessage = pMsg7->m_pMessage; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_BROADCAST) + { + protocol7::CNetMsg_Sv_Broadcast *pMsg7 = (protocol7::CNetMsg_Sv_Broadcast *)pRawMsg; + ::CNetMsg_Sv_Broadcast *pMsg = (::CNetMsg_Sv_Broadcast *)s_aRawMsg; + + pMsg->m_pMessage = pMsg7->m_pMessage; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETTEAM) + { + protocol7::CNetMsg_Cl_SetTeam *pMsg7 = (protocol7::CNetMsg_Cl_SetTeam *)pRawMsg; + ::CNetMsg_Cl_SetTeam *pMsg = (::CNetMsg_Cl_SetTeam *)s_aRawMsg; + + pMsg->m_Team = pMsg7->m_Team; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_TEAM) + { + protocol7::CNetMsg_Sv_Team *pMsg7 = (protocol7::CNetMsg_Sv_Team *)pRawMsg; + + if(Client()->State() != IClient::STATE_DEMOPLAYBACK) + { + m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team; + m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team; + m_aClients[pMsg7->m_ClientId].UpdateRenderInfo(IsTeamPlay()); + + // if(pMsg7->m_ClientId == m_LocalClientId) + // { + // m_TeamCooldownTick = pMsg7->m_CooldownTick; + // m_TeamChangeTime = Client()->LocalTime(); + // } + } + + if(Conn != g_Config.m_ClDummy) + return nullptr; + + if(pMsg7->m_Silent == 0) + { + DoTeamChangeMessage7(m_aClients[pMsg7->m_ClientId].m_aName, pMsg7->m_ClientId, pMsg7->m_Team); + } + + // we drop the message and add the new team + // info to the playerinfo snap item + // using translation context + return nullptr; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_WEAPONPICKUP) + { + protocol7::CNetMsg_Sv_WeaponPickup *pMsg7 = (protocol7::CNetMsg_Sv_WeaponPickup *)pRawMsg; + ::CNetMsg_Sv_WeaponPickup *pMsg = (::CNetMsg_Sv_WeaponPickup *)s_aRawMsg; + + pMsg->m_Weapon = pMsg7->m_Weapon; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_SERVERSETTINGS) + { + // 0.7 only message for ui enrichment like locked teams + protocol7::CNetMsg_Sv_ServerSettings *pMsg = (protocol7::CNetMsg_Sv_ServerSettings *)pRawMsg; + + if(!m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && pMsg->m_TeamLock) + m_Chat.AddLine(-1, 0, Localize("Teams were locked")); + else if(m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && !pMsg->m_TeamLock) + m_Chat.AddLine(-1, 0, Localize("Teams were unlocked")); + + m_pClient->m_TranslationContext.m_ServerSettings.m_KickVote = pMsg->m_KickVote; + m_pClient->m_TranslationContext.m_ServerSettings.m_KickMin = pMsg->m_KickMin; + m_pClient->m_TranslationContext.m_ServerSettings.m_SpecVote = pMsg->m_SpecVote; + m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock = pMsg->m_TeamLock; + m_pClient->m_TranslationContext.m_ServerSettings.m_TeamBalance = pMsg->m_TeamBalance; + m_pClient->m_TranslationContext.m_ServerSettings.m_PlayerSlots = pMsg->m_PlayerSlots; + return nullptr; // There is no 0.6 equivalent + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_RACEFINISH) + { + *pMsgId = NETMSGTYPE_SV_RACEFINISH; + protocol7::CNetMsg_Sv_RaceFinish *pMsg7 = (protocol7::CNetMsg_Sv_RaceFinish *)pRawMsg; + ::CNetMsg_Sv_RaceFinish *pMsg = (::CNetMsg_Sv_RaceFinish *)s_aRawMsg; + + pMsg->m_ClientId = pMsg7->m_ClientId; + pMsg->m_Time = pMsg7->m_Time; + pMsg->m_Diff = pMsg7->m_Diff; + pMsg->m_RecordPersonal = pMsg7->m_RecordPersonal; + pMsg->m_RecordServer = pMsg7->m_RecordServer; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFOREMOVE) + { + *pMsgId = NETMSGTYPE_SV_COMMANDINFOREMOVE; + protocol7::CNetMsg_Sv_CommandInfoRemove *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfoRemove *)pRawMsg; + ::CNetMsg_Sv_CommandInfoRemove *pMsg = (::CNetMsg_Sv_CommandInfoRemove *)s_aRawMsg; + + pMsg->m_pName = pMsg7->m_pName; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFO) + { + *pMsgId = NETMSGTYPE_SV_COMMANDINFO; + protocol7::CNetMsg_Sv_CommandInfo *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfo *)pRawMsg; + ::CNetMsg_Sv_CommandInfo *pMsg = (::CNetMsg_Sv_CommandInfo *)s_aRawMsg; + + pMsg->m_pName = pMsg7->m_pName; + pMsg->m_pArgsFormat = pMsg7->m_pArgsFormat; + pMsg->m_pHelpText = pMsg7->m_pHelpText; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_SKINCHANGE) + { + protocol7::CNetMsg_Sv_SkinChange *pMsg7 = (protocol7::CNetMsg_Sv_SkinChange *)pRawMsg; + + if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS) + { + dbg_msg("sixup", "Sv_SkinChange got invalid ClientId: %d", pMsg7->m_ClientId); + return nullptr; + } + + CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId]; + Client.m_Active = true; + CClientData *pClient = &m_aClients[pMsg7->m_ClientId]; + for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) + { + int Id = m_Skins7.FindSkinPart(p, pMsg7->m_apSkinPartNames[p], false); + const CSkins7::CSkinPart *pSkinPart = m_Skins7.GetSkinPart(p, Id); + if(pMsg7->m_aUseCustomColors[p]) + { + pClient->m_SkinInfo.m_Sixup.m_aTextures[p] = pSkinPart->m_ColorTexture; + pClient->m_SkinInfo.m_Sixup.m_aColors[p] = m_Skins7.GetColor(pMsg7->m_aSkinPartColors[p], p == protocol7::SKINPART_MARKING); + } + else + { + pClient->m_SkinInfo.m_Sixup.m_aTextures[p] = pSkinPart->m_OrgTexture; + pClient->m_SkinInfo.m_Sixup.m_aColors[p] = vec4(1.0f, 1.0f, 1.0f, 1.0f); + } + } + // skin will be moved to the 0.6 snap by the translation context + // and we drop the game message + return nullptr; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTECLEAROPTIONS) + { + *pMsgId = NETMSGTYPE_SV_VOTECLEAROPTIONS; + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONADD) + { + *pMsgId = NETMSGTYPE_SV_VOTEOPTIONADD; + protocol7::CNetMsg_Sv_VoteOptionAdd *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionAdd *)pRawMsg; + ::CNetMsg_Sv_VoteOptionAdd *pMsg = (::CNetMsg_Sv_VoteOptionAdd *)s_aRawMsg; + pMsg->m_pDescription = pMsg7->m_pDescription; + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONREMOVE) + { + *pMsgId = NETMSGTYPE_SV_VOTEOPTIONREMOVE; + protocol7::CNetMsg_Sv_VoteOptionRemove *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionRemove *)pRawMsg; + ::CNetMsg_Sv_VoteOptionRemove *pMsg = (::CNetMsg_Sv_VoteOptionRemove *)s_aRawMsg; + pMsg->m_pDescription = pMsg7->m_pDescription; + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONLISTADD) + { + ::CNetMsg_Sv_VoteOptionListAdd *pMsg = (::CNetMsg_Sv_VoteOptionListAdd *)s_aRawMsg; + int NumOptions = pUnpacker->GetInt(); + if(NumOptions > 14) + { + for(int i = 0; i < NumOptions; i++) + { + const char *pDescription = pUnpacker->GetString(CUnpacker::SANITIZE_CC); + if(pUnpacker->Error()) + continue; + + m_Voting.AddOption(pDescription); + } + // 0.7 can send more vote options than + // the 0.6 protocol fit + // in that case we do not translate it but just + // reimplement what 0.6 would do + return nullptr; + } + pMsg->m_NumOptions = 0; + for(int i = 0; i < NumOptions; i++) + { + const char *pDescription = pUnpacker->GetString(CUnpacker::SANITIZE_CC); + if(pUnpacker->Error()) + continue; + + pMsg->m_NumOptions++; + switch(i) + { + case 0: (pMsg->m_pDescription0 = pDescription); break; + case 1: (pMsg->m_pDescription1 = pDescription); break; + case 2: (pMsg->m_pDescription2 = pDescription); break; + case 3: (pMsg->m_pDescription3 = pDescription); break; + case 4: (pMsg->m_pDescription4 = pDescription); break; + case 5: (pMsg->m_pDescription5 = pDescription); break; + case 6: (pMsg->m_pDescription6 = pDescription); break; + case 7: (pMsg->m_pDescription7 = pDescription); break; + case 8: (pMsg->m_pDescription8 = pDescription); break; + case 9: (pMsg->m_pDescription9 = pDescription); break; + case 10: (pMsg->m_pDescription10 = pDescription); break; + case 11: (pMsg->m_pDescription11 = pDescription); break; + case 12: (pMsg->m_pDescription12 = pDescription); break; + case 13: (pMsg->m_pDescription13 = pDescription); break; + case 14: (pMsg->m_pDescription14 = pDescription); + } + } + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESET) + { + *pMsgId = NETMSGTYPE_SV_VOTESET; + protocol7::CNetMsg_Sv_VoteSet *pMsg7 = (protocol7::CNetMsg_Sv_VoteSet *)pRawMsg; + ::CNetMsg_Sv_VoteSet *pMsg = (::CNetMsg_Sv_VoteSet *)s_aRawMsg; + + pMsg->m_Timeout = pMsg7->m_Timeout; + pMsg->m_pDescription = pMsg7->m_pDescription; + pMsg->m_pReason = pMsg7->m_pReason; + + char aBuf[128]; + if(pMsg7->m_Timeout) + { + if(pMsg7->m_ClientId != -1) + { + const char *pName = m_aClients[pMsg7->m_ClientId].m_aName; + switch(pMsg7->m_Type) + { + case protocol7::VOTE_START_OP: + str_format(aBuf, sizeof(aBuf), Localize("'%s' called vote to change server option '%s' (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason); + m_Chat.AddLine(-1, 0, aBuf); + break; + case protocol7::VOTE_START_KICK: + { + str_format(aBuf, sizeof(aBuf), Localize("'%s' called for vote to kick '%s' (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason); + m_Chat.AddLine(-1, 0, aBuf); + break; + } + case protocol7::VOTE_START_SPEC: + { + str_format(aBuf, sizeof(aBuf), Localize("'%s' called for vote to move '%s' to spectators (%s)"), pName, pMsg7->m_pDescription, pMsg7->m_pReason); + m_Chat.AddLine(-1, 0, aBuf); + } + } + } + } + else + { + switch(pMsg7->m_Type) + { + case protocol7::VOTE_START_OP: + str_format(aBuf, sizeof(aBuf), Localize("Admin forced server option '%s' (%s)"), pMsg7->m_pDescription, pMsg7->m_pReason); + m_Chat.AddLine(-1, 0, aBuf); + break; + case protocol7::VOTE_START_SPEC: + str_format(aBuf, sizeof(aBuf), Localize("Admin moved '%s' to spectator (%s)"), pMsg7->m_pDescription, pMsg7->m_pReason); + m_Chat.AddLine(-1, 0, aBuf); + break; + case protocol7::VOTE_END_ABORT: + m_Voting.OnReset(); + m_Chat.AddLine(-1, 0, Localize("Vote aborted")); + break; + case protocol7::VOTE_END_PASS: + m_Voting.OnReset(); + m_Chat.AddLine(-1, 0, pMsg7->m_ClientId == -1 ? Localize("Admin forced vote yes") : Localize("Vote passed")); + break; + case protocol7::VOTE_END_FAIL: + m_Voting.OnReset(); + m_Chat.AddLine(-1, 0, pMsg7->m_ClientId == -1 ? Localize("Admin forced vote no") : Localize("Vote failed")); + } + } + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESTATUS) + { + *pMsgId = NETMSGTYPE_SV_VOTESTATUS; + protocol7::CNetMsg_Sv_VoteStatus *pMsg7 = (protocol7::CNetMsg_Sv_VoteStatus *)pRawMsg; + ::CNetMsg_Sv_VoteStatus *pMsg = (::CNetMsg_Sv_VoteStatus *)s_aRawMsg; + + pMsg->m_Yes = pMsg7->m_Yes; + pMsg->m_No = pMsg7->m_No; + pMsg->m_Pass = pMsg7->m_Pass; + pMsg->m_Total = pMsg7->m_Total; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_READYTOENTER) + { + *pMsgId = NETMSGTYPE_SV_READYTOENTER; + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTDROP) + { + protocol7::CNetMsg_Sv_ClientDrop *pMsg7 = (protocol7::CNetMsg_Sv_ClientDrop *)pRawMsg; + if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS) + { + dbg_msg("sixup", "Sv_ClientDrop got invalid ClientId: %d", pMsg7->m_ClientId); + return nullptr; + } + CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId]; + Client.Reset(); + + if(pMsg7->m_Silent) + return nullptr; + + if(Conn != g_Config.m_ClDummy) + return nullptr; + + static char s_aBuf[128]; + if(pMsg7->m_pReason[0]) + str_format(s_aBuf, sizeof(s_aBuf), "'%s' has left the game (%s)", m_aClients[pMsg7->m_ClientId].m_aName, pMsg7->m_pReason); + else + str_format(s_aBuf, sizeof(s_aBuf), "'%s' has left the game", m_aClients[pMsg7->m_ClientId].m_aName); + m_Chat.AddLine(-1, 0, s_aBuf); + + return nullptr; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTINFO) + { + protocol7::CNetMsg_Sv_ClientInfo *pMsg7 = (protocol7::CNetMsg_Sv_ClientInfo *)pRawMsg; + if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS) + { + dbg_msg("sixup", "Sv_ClientInfo got invalid ClientId: %d", pMsg7->m_ClientId); + return nullptr; + } + + if(pMsg7->m_Local) + { + m_pClient->m_TranslationContext.m_aLocalClientId[Conn] = pMsg7->m_ClientId; + } + CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId]; + Client.m_Active = true; + Client.m_Team = pMsg7->m_Team; + str_copy(Client.m_aName, pMsg7->m_pName); + str_copy(Client.m_aClan, pMsg7->m_pClan); + Client.m_Country = pMsg7->m_Country; + CClientData *pClient = &m_aClients[pMsg7->m_ClientId]; + for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) + { + int Id = m_Skins7.FindSkinPart(p, pMsg7->m_apSkinPartNames[p], false); + const CSkins7::CSkinPart *pSkinPart = m_Skins7.GetSkinPart(p, Id); + if(pMsg7->m_aUseCustomColors[p]) + { + pClient->m_SkinInfo.m_Sixup.m_aTextures[p] = pSkinPart->m_ColorTexture; + pClient->m_SkinInfo.m_Sixup.m_aColors[p] = m_Skins7.GetColor(pMsg7->m_aSkinPartColors[p], p == protocol7::SKINPART_MARKING); + } + else + { + pClient->m_SkinInfo.m_Sixup.m_aTextures[p] = pSkinPart->m_OrgTexture; + pClient->m_SkinInfo.m_Sixup.m_aColors[p] = vec4(1.0f, 1.0f, 1.0f, 1.0f); + } + } + + if(m_pClient->m_TranslationContext.m_aLocalClientId[Conn] == -1) + return nullptr; + if(pMsg7->m_Silent || pMsg7->m_Local) + return nullptr; + + if(Conn != g_Config.m_ClDummy) + return nullptr; + + DoTeamChangeMessage7( + pMsg7->m_pName, + pMsg7->m_ClientId, + pMsg7->m_Team, + "entered and "); + + return nullptr; // we only do side effects and add stuff to the snap here + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEINFO) + { + protocol7::CNetMsg_Sv_GameInfo *pMsg7 = (protocol7::CNetMsg_Sv_GameInfo *)pRawMsg; + m_pClient->m_TranslationContext.m_GameFlags = pMsg7->m_GameFlags; + m_pClient->m_TranslationContext.m_ScoreLimit = pMsg7->m_ScoreLimit; + m_pClient->m_TranslationContext.m_TimeLimit = pMsg7->m_TimeLimit; + m_pClient->m_TranslationContext.m_MatchNum = pMsg7->m_MatchNum; + m_pClient->m_TranslationContext.m_MatchCurrent = pMsg7->m_MatchCurrent; + m_pClient->m_TranslationContext.m_ShouldSendGameInfo = true; + return nullptr; // Added to snap by translation context + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_EMOTICON) + { + *pMsgId = NETMSGTYPE_SV_EMOTICON; + protocol7::CNetMsg_Sv_Emoticon *pMsg7 = (protocol7::CNetMsg_Sv_Emoticon *)pRawMsg; + ::CNetMsg_Sv_Emoticon *pMsg = (::CNetMsg_Sv_Emoticon *)s_aRawMsg; + + pMsg->m_ClientId = pMsg7->m_ClientId; + pMsg->m_Emoticon = pMsg7->m_Emoticon; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_KILLMSG) + { + *pMsgId = NETMSGTYPE_SV_KILLMSG; + + protocol7::CNetMsg_Sv_KillMsg *pMsg7 = (protocol7::CNetMsg_Sv_KillMsg *)pRawMsg; + ::CNetMsg_Sv_KillMsg *pMsg = (::CNetMsg_Sv_KillMsg *)s_aRawMsg; + + pMsg->m_Killer = pMsg7->m_Killer; + pMsg->m_Victim = pMsg7->m_Victim; + pMsg->m_Weapon = pMsg7->m_Weapon; + pMsg->m_ModeSpecial = pMsg7->m_ModeSpecial; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHAT) + { + *pMsgId = NETMSGTYPE_SV_CHAT; + + protocol7::CNetMsg_Sv_Chat *pMsg7 = (protocol7::CNetMsg_Sv_Chat *)pRawMsg; + ::CNetMsg_Sv_Chat *pMsg = (::CNetMsg_Sv_Chat *)s_aRawMsg; + + if(pMsg7->m_Mode == protocol7::CHAT_WHISPER) + { + bool Receive = pMsg7->m_TargetId == m_pClient->m_TranslationContext.m_aLocalClientId[Conn]; + + pMsg->m_Team = Receive ? 3 : 2; + pMsg->m_ClientId = Receive ? pMsg7->m_ClientId : pMsg7->m_TargetId; + } + else + { + pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM ? 1 : 0; + pMsg->m_ClientId = pMsg7->m_ClientId; + } + + pMsg->m_pMessage = pMsg7->m_pMessage; + + return s_aRawMsg; + } + else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEMSG) + { + int GameMsgId = pUnpacker->GetInt(); + + /** + * Prints chat message only once + * even if it is being sent to main tee and dummy + */ + auto SendChat = [Conn, GameMsgId, this](const char *pText) -> void { + if(GameMsgId != protocol7::GAMEMSG_TEAM_BALANCE_VICTIM && GameMsgId != protocol7::GAMEMSG_SPEC_INVALIDID) + { + if(Conn != g_Config.m_ClDummy) + return; + } + m_Chat.AddLine(-1, 0, pText); + }; + + // check for valid gamemsgid + if(GameMsgId < 0 || GameMsgId >= protocol7::NUM_GAMEMSGS) + return nullptr; + + int aParaI[3] = {0}; + int NumParaI = 0; + + // get paras + switch(gs_GameMsgList7[GameMsgId].m_ParaType) + { + case PARA_I: NumParaI = 1; break; + case PARA_II: NumParaI = 2; break; + case PARA_III: NumParaI = 3; break; + } + for(int i = 0; i < NumParaI; i++) + { + aParaI[i] = pUnpacker->GetInt(); + } + + // check for unpacking errors + if(pUnpacker->Error()) + return nullptr; + + // handle special messages + char aBuf[256]; + bool TeamPlay = m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS; + if(gs_GameMsgList7[GameMsgId].m_Action == DO_SPECIAL) + { + switch(GameMsgId) + { + case protocol7::GAMEMSG_CTF_DROP: + m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_DROP); + break; + case protocol7::GAMEMSG_CTF_RETURN: + m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_RETURN); + break; + case protocol7::GAMEMSG_TEAM_ALL: + { + const char *pMsg = ""; + switch(GetStrTeam7(aParaI[0], TeamPlay)) + { + case STR_TEAM_GAME: pMsg = Localize("All players were moved to the game"); break; + case STR_TEAM_RED: pMsg = Localize("All players were moved to the red team"); break; + case STR_TEAM_BLUE: pMsg = Localize("All players were moved to the blue team"); break; + case STR_TEAM_SPECTATORS: pMsg = Localize("All players were moved to the spectators"); break; + } + m_Broadcast.DoBroadcast(pMsg); // client side broadcast + } + break; + case protocol7::GAMEMSG_TEAM_BALANCE_VICTIM: + { + const char *pMsg = ""; + switch(GetStrTeam7(aParaI[0], TeamPlay)) + { + case STR_TEAM_RED: pMsg = Localize("You were moved to the red team due to team balancing"); break; + case STR_TEAM_BLUE: pMsg = Localize("You were moved to the blue team due to team balancing"); break; + } + m_Broadcast.DoBroadcast(pMsg); // client side broadcast + } + break; + case protocol7::GAMEMSG_CTF_GRAB: + m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_EN); + break; + case protocol7::GAMEMSG_GAME_PAUSED: + { + int ClientId = clamp(aParaI[0], 0, MAX_CLIENTS - 1); + str_format(aBuf, sizeof(aBuf), Localize("'%s' initiated a pause"), m_aClients[ClientId].m_aName); + SendChat(aBuf); + } + break; + case protocol7::GAMEMSG_CTF_CAPTURE: + m_Sounds.Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_CAPTURE); + int ClientId = clamp(aParaI[1], 0, MAX_CLIENTS - 1); + m_aStats[ClientId].m_FlagCaptures++; + + float Time = aParaI[2] / (float)Client()->GameTickSpeed(); + if(Time <= 60) + { + if(aParaI[0]) + { + str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s' (%.2f seconds)"), m_aClients[ClientId].m_aName, Time); + } + else + { + str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s' (%.2f seconds)"), m_aClients[ClientId].m_aName, Time); + } + } + else + { + if(aParaI[0]) + { + str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s'"), m_aClients[ClientId].m_aName); + } + else + { + str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s'"), m_aClients[ClientId].m_aName); + } + } + SendChat(aBuf); + } + return nullptr; + } + + // build message + const char *pText = ""; + if(NumParaI == 0) + { + pText = Localize(gs_GameMsgList7[GameMsgId].m_pText); + } + + // handle message + switch(gs_GameMsgList7[GameMsgId].m_Action) + { + case DO_CHAT: + SendChat(pText); + break; + case DO_BROADCAST: + m_Broadcast.DoBroadcast(pText); // client side broadcast + break; + } + + // no need to handle it in 0.6 since we printed + // the message already + return nullptr; + } + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(*pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn()); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + + return nullptr; +} diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index ec8b0fb26..f98d6e387 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -490,13 +491,7 @@ bool CGameContext::SnapPickup(const CSnapContext &Context, int SnapId, const vec pPickup->m_X = (int)Pos.x; pPickup->m_Y = (int)Pos.y; - - if(Type == POWERUP_WEAPON) - pPickup->m_Type = SubType == WEAPON_SHOTGUN ? protocol7::PICKUP_SHOTGUN : SubType == WEAPON_GRENADE ? protocol7::PICKUP_GRENADE : protocol7::PICKUP_LASER; - else if(Type == POWERUP_NINJA) - pPickup->m_Type = protocol7::PICKUP_NINJA; - else if(Type == POWERUP_ARMOR) - pPickup->m_Type = protocol7::PICKUP_ARMOR; + pPickup->m_Type = PickupType_SixToSeven(Type, SubType); } else if(Context.GetClientVersion() >= VERSION_DDNET_ENTITY_NETOBJS) { @@ -1291,18 +1286,6 @@ void CGameContext::OnTick() // Warning: do not put code in this function directly above or below this comment } -static int PlayerFlags_SevenToSix(int Flags) -{ - int Six = 0; - if(Flags & protocol7::PLAYERFLAG_CHATTING) - Six |= PLAYERFLAG_CHATTING; - if(Flags & protocol7::PLAYERFLAG_SCOREBOARD) - Six |= PLAYERFLAG_SCOREBOARD; - if(Flags & protocol7::PLAYERFLAG_AIM) - Six |= PLAYERFLAG_AIM; - return Six; -} - // Server hooks void CGameContext::OnClientPrepareInput(int ClientId, void *pInput) { diff --git a/src/game/version.h b/src/game/version.h index d53d9bd1e..793ff5b2f 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -5,8 +5,14 @@ #ifndef GAME_RELEASE_VERSION #define GAME_RELEASE_VERSION "18.4" #endif + +// teeworlds +#define CLIENT_VERSION7 0x0705 #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" +#define GAME_NETVERSION7 "0.7 802f1be60a05665f" + +// ddnet #define DDNET_VERSION_NUMBER 18040 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" diff --git a/src/test/netaddr.cpp b/src/test/netaddr.cpp index ccbf09648..ecc6450b5 100644 --- a/src/test/netaddr.cpp +++ b/src/test/netaddr.cpp @@ -2,18 +2,32 @@ #include -TEST(NetAddr, FromUrlString) +TEST(NetAddr, FromUrlStringInvalid) { NETADDR Addr; - char aBuf1[NETADDR_MAXSTRSIZE]; - char aBuf2[NETADDR_MAXSTRSIZE]; EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://127.0", nullptr, 0), -1); // invalid ip EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://ddnet.org", nullptr, 0), -1); // invalid ip EXPECT_EQ(net_addr_from_url(&Addr, "127.0.0.1", nullptr, 0), 1); // not a URL EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.9+udp://127.0.0.1", nullptr, 0), 1); // invalid tw protocol - EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.7+udp://127.0.0.1", nullptr, 0), 1); // unsupported tw protocol EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+tcp://127.0.0.1", nullptr, 0), 1); // invalid internet protocol +} + +TEST(NetAddr, FromUrlStringValid) +{ + NETADDR Addr; + char aBuf1[NETADDR_MAXSTRSIZE]; + char aBuf2[NETADDR_MAXSTRSIZE]; + + EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.7+udp://127.0.0.1", nullptr, 0), 0); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "127.0.0.1:0"); + EXPECT_STREQ(aBuf2, "127.0.0.1"); + net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "tw-0.7+udp://127.0.0.1:0"); + EXPECT_STREQ(aBuf2, "tw-0.7+udp://127.0.0.1"); EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://127.0.0.1", nullptr, 0), 0); net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); @@ -26,12 +40,20 @@ TEST(NetAddr, FromUrlString) net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); EXPECT_STREQ(aBuf1, "127.0.0.1:0"); EXPECT_STREQ(aBuf2, "127.0.0.1"); + net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "tw-0.6+udp://127.0.0.1:0"); + EXPECT_STREQ(aBuf2, "tw-0.6+udp://127.0.0.1"); EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://[0123:4567:89ab:cdef:1:2:3:4]:5678", nullptr, 0), 0); net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); EXPECT_STREQ(aBuf1, "[123:4567:89ab:cdef:1:2:3:4]:5678"); EXPECT_STREQ(aBuf2, "[123:4567:89ab:cdef:1:2:3:4]"); + net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "tw-0.6+udp://[123:4567:89ab:cdef:1:2:3:4]:5678"); + EXPECT_STREQ(aBuf2, "tw-0.6+udp://[123:4567:89ab:cdef:1:2:3:4]"); char aHost[128]; EXPECT_EQ(net_addr_from_url(&Addr, "tw-0.6+udp://ger10.ddnet.org:5678", aHost, sizeof(aHost)), -1); @@ -134,3 +156,14 @@ TEST(NetAddr, StrInvalid) net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); EXPECT_STREQ(aBuf2, "unknown type 0"); } + +TEST(NetAddr, UrlStrInvalid) +{ + NETADDR Addr = {0}; + char aBuf1[NETADDR_MAXSTRSIZE]; + char aBuf2[NETADDR_MAXSTRSIZE]; + net_addr_url_str(&Addr, aBuf1, sizeof(aBuf1), true); + EXPECT_STREQ(aBuf1, "unknown type 0"); + net_addr_url_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf2, "unknown type 0"); +} diff --git a/src/tools/twping.cpp b/src/tools/twping.cpp index b48f8d4a6..f24541a74 100644 --- a/src/tools/twping.cpp +++ b/src/tools/twping.cpp @@ -64,7 +64,8 @@ int main(int argc, const char **argv) NetClient.Update(); - while(NetClient.Recv(&Packet)) + SECURITY_TOKEN ResponseToken; + while(NetClient.Recv(&Packet, &ResponseToken, false)) { if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(Packet.m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) {