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 000000000..c22df9960 Binary files /dev/null and b/data/deadtee.png differ 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) {