Add 0.7 client support

While keeping 0.6 fully working and untouched
This adds the option to connect via 0.7

0.7 servers are now mixed into the server browser list.
And users can also manually connect via 0.7
using a connection url like so:

    ./DDNet "connect tw-0.7+udp://127.0.0.1:8303"

This also adds a "Tee 0.7" section in the menu to pick a 0.7 skin.
The config variables are prefixed with player7_ like for example
player7_color_body
This commit is contained in:
ChillerDragon 2022-10-16 10:14:05 +02:00 committed by Dennis Felsing
parent 3a84cfeb8b
commit bae37ab7df
52 changed files with 4081 additions and 186 deletions

View file

@ -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

BIN
data/deadtee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -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)

View file

@ -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.

View file

@ -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,
};
/**

View file

@ -8,12 +8,17 @@
#include "message.h"
#include <base/hash.h>
#include <engine/shared/translation_context.h>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
#include <engine/friends.h>
#include <engine/shared/snapshot.h>
#include <functional>
#include <engine/client/enums.h>
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<void(ELoadingCallbackDetail Detail)> 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<class T>
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<class T>
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);

View file

@ -36,12 +36,18 @@
#include <engine/shared/network.h>
#include <engine/shared/packer.h>
#include <engine/shared/protocol.h>
#include <engine/shared/protocol7.h>
#include <engine/shared/protocol_ex.h>
#include <engine/shared/rust_version.h>
#include <engine/shared/snapshot.h>
#include <engine/shared/uuid_manager.h>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
#include <engine/shared/protocolglue.h>
#include <game/localization.h>
#include <game/version.h>
@ -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,10 +251,18 @@ void CClient::SendMapRequest()
Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
}
m_MapdownloadFileTemp = Storage()->OpenFile(m_aMapdownloadFilenameTemp, IOFLAG_WRITE, IStorage::TYPE_SAVE);
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++)
{
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_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,6 +755,10 @@ void CClient::DummyConnect()
g_Config.m_ClDummyHammer = 0;
m_DummyConnecting = true;
// 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);
}
@ -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++;
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,11 +2117,24 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
str_append(aBufMsg, aBuf);
}
}
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;

View file

@ -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; }

11
src/engine/client/enums.h Normal file
View file

@ -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

View file

@ -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;

View file

@ -0,0 +1,114 @@
#include <engine/client/client.h>
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;
}

View file

@ -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")

View file

@ -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)
{

View file

@ -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; }

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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)
@ -345,6 +390,21 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_
return 0;
}
else
{
if(CtrlMsg == NET_CTRLMSG_TOKEN)
{
if(State() == NET_CONNSTATE_TOKEN)
{
m_LastRecvTime = Now;
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_OFFLINE)
{
@ -392,14 +452,15 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_
if(g_Config.m_Debug)
dbg_msg("security", "got token %d", m_SecurityToken);
}
else
else if(!IsSixup())
{
m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED;
if(g_Config.m_Debug)
dbg_msg("security", "token not supported by server");
}
m_LastRecvTime = Now;
if(!IsSixup())
SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
m_LastRecvTime = Now;
m_State = NET_CONNSTATE_ONLINE;
if(g_Config.m_Debug)
dbg_msg("connection", "got connect+accept, sending accept. connection online");
@ -407,6 +468,7 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_
}
}
}
}
else
{
if(State() == NET_CONNSTATE_PENDING)

View file

@ -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);

View file

@ -4,6 +4,7 @@
#define ENGINE_SHARED_PROTOCOL_H
#include <bitset>
#include <engine/shared/protocol7.h>
/*
Connection diagram - How the initialization works.

View file

@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_PROTOCOL7_H
#define ENGINE_SHARED_PROTOCOL7_H
#include <base/types.h>
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

View file

@ -0,0 +1,75 @@
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
#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;
}

View file

@ -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

View file

@ -0,0 +1,516 @@
#include "compression.h"
#include "snapshot.h"
#include "uuid_manager.h"
#include <climits>
#include <cstdlib>
#include <base/math.h>
#include <base/system.h>
#include <engine/shared/protocolglue.h>
#include <engine/shared/translation_context.h>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
#include <game/gamecore.h>
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);
}

View file

@ -10,6 +10,7 @@
#include <base/math.h>
#include <base/system.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
// 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<int16_t>::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)

View file

@ -6,6 +6,9 @@
#include <cstddef>
#include <cstdint>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
// 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);
};

View file

@ -0,0 +1,120 @@
#ifndef ENGINE_SHARED_TRANSLATION_CONTEXT_H
#define ENGINE_SHARED_TRANSLATION_CONTEXT_H
#include <engine/shared/protocol.h>
#include <game/generated/protocol7.h>
#include <engine/client/enums.h>
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

View file

@ -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))))
{

View file

@ -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

View file

@ -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;

View file

@ -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]));

View file

@ -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

View file

@ -23,10 +23,13 @@
#include <game/client/component.h>
#include <game/client/components/mapimages.h>
#include <game/client/lineinput.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/voting.h>
#include <game/client/components/skins7.h>
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<protocol7::MAX_SKIN_ARRAY_SIZE, protocol7::MAX_SKIN_LENGTH> 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);

View file

@ -8,6 +8,7 @@
#include <engine/shared/config.h>
#include <engine/shared/linereader.h>
#include <engine/shared/localization.h>
#include <engine/shared/protocol7.h>
#include <engine/storage.h>
#include <engine/textrender.h>
#include <engine/updater.h>
@ -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,6 +2057,9 @@ void CMenus::RenderSettings(CUIRect MainView)
else if(g_Config.m_UiSettingsPage == SETTINGS_TEE)
{
GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_SETTINGS_TEE);
if(Client()->IsSixup())
RenderSettingsTee7(MainView);
else
RenderSettingsTee(MainView);
}
else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE)

View file

@ -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 <base/math.h>
#include <base/system.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/linereader.h>
#include <engine/shared/localization.h>
#include <engine/shared/protocol7.h>
#include <engine/storage.h>
#include <engine/textrender.h>
#include <engine/updater.h>
#include <game/generated/protocol.h>
#include <game/client/animstate.h>
#include <game/client/components/chat.h>
#include <game/client/components/menu_background.h>
#include <game/client/components/sounds.h>
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/client/skin.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h>
#include <game/localization.h>
#include "menus.h"
#include "skins7.h"
#include <vector>
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<const CSkins7::CSkin *> 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<const CSkins7::CSkinPart *> 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;
}

View file

@ -6,6 +6,7 @@
#include <engine/shared/config.h>
#include <game/gamecore.h>
#include <game/generated/client_data.h>
#include <game/generated/client_data7.h>
#include <game/generated/protocol.h>
#include <game/mapitems.h>
@ -26,6 +27,54 @@
#include <base/math.h>
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;

View file

@ -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,

View file

@ -16,6 +16,7 @@
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/generated/client_data7.h>
#include <game/localization.h>
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);

View file

@ -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 <base/color.h>
#include <base/log.h>
#include <base/math.h>
#include <base/system.h>
#include <engine/external/json-parser/json.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/shared/jsonwriter.h>
#include <engine/shared/protocol7.h>
#include <engine/storage.h>
#include <game/client/gameclient.h>
#include <game/localization.h>
#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<int>(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<const json_char *>(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<ColorRGBA>(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;
}

View file

@ -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 <base/color.h>
#include <base/vmath.h>
#include <engine/client/enums.h>
#include <engine/graphics.h>
#include <game/client/component.h>
#include <vector>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
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<CSkinPart> m_avSkinParts[protocol7::NUM_SKINPARTS];
std::vector<CSkin> 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

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -37,6 +37,9 @@
#include <game/mapitems.h>
#include <game/version.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
#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;
@ -725,6 +745,15 @@ void CGameClient::OnRender()
if(Client()->State() == IClient::STATE_ONLINE && !m_Menus.IsActive())
{
if(m_aCheckInfo[0] == 0)
{
if(m_pClient->IsSixup())
{
if(!GotWantedSkin7(false))
SendSkinChange7(false);
else
m_aCheckInfo[0] = -1;
}
else
{
if(
str_comp(m_aClients[m_aLocalIds[0]].m_aName, Client()->PlayerName()) ||
@ -738,6 +767,7 @@ void CGameClient::OnRender()
else
m_aCheckInfo[0] = -1;
}
}
if(m_aCheckInfo[0] > 0)
m_aCheckInfo[0]--;
@ -745,6 +775,15 @@ void CGameClient::OnRender()
if(Client()->DummyConnected())
{
if(m_aCheckInfo[1] == 0)
{
if(m_pClient->IsSixup())
{
if(!GotWantedSkin7(true))
SendSkinChange7(true);
else
m_aCheckInfo[1] = -1;
}
else
{
if(
str_comp(m_aClients[m_aLocalIds[1]].m_aName, Client()->DummyName()) ||
@ -758,6 +797,7 @@ void CGameClient::OnRender()
else
m_aCheckInfo[1] = -1;
}
}
if(m_aCheckInfo[1] > 0)
m_aCheckInfo[1]--;
@ -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)
{
// 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)
{
// 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,9 +1829,21 @@ 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));
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<bool>(p1);
if(!p1)
return false;
return (((p1->m_Score == -1) ? std::numeric_limits<int>::max() : p1->m_Score) <
((p2->m_Score == -1) ? std::numeric_limits<int>::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)
@ -2226,11 +2297,28 @@ void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay)
{
m_RenderInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(aTeamColors[m_Team]));
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(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<ColorRGBA>(ColorHSLA(12829350));
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(12829350));
for(auto &Color : m_RenderInfo.m_Sixup.m_aColors)
Color = color_cast<ColorRGBA>(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<CGameClient *>(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<CGameClient *>(pUserData);
@ -2434,9 +2603,11 @@ 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);

View file

@ -17,6 +17,9 @@
#include <game/client/prediction/gameworld.h>
#include <game/generated/protocol7.h>
#include <game/generated/protocolglue.h>
// 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<class CComponent *> m_vpAll;
std::vector<class CComponent *> 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);

View file

@ -13,6 +13,7 @@
#include <game/generated/client_data.h>
#include <game/generated/client_data7.h>
#include <game/generated/protocol.h>
#include <game/generated/protocol7.h>
#include <game/mapitems.h>
@ -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)

View file

@ -8,6 +8,7 @@
#include <game/client/skin.h>
#include <game/client/ui_rect.h>
#include <game/generated/protocol7.h>
class CAnimState;
class CSpeedupTile;
@ -25,6 +26,8 @@ struct CEnvPointBezier_upstream;
struct CMapItemGroup;
struct CQuad;
#include <game/generated/protocol.h>
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;

View file

@ -0,0 +1,689 @@
#include <game/localization.h>
#include <game/client/gameclient.h>
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;
}

View file

@ -18,6 +18,7 @@
#include <engine/shared/json.h>
#include <engine/shared/linereader.h>
#include <engine/shared/memheap.h>
#include <engine/shared/protocolglue.h>
#include <engine/storage.h>
#include <game/collision.h>
@ -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)
{

View file

@ -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"

View file

@ -2,18 +2,32 @@
#include <base/system.h>
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");
}

View file

@ -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)
{