18 changes: 18 additions & 0 deletions data/languages/ukrainian.txt
Expand Up @@ -1135,6 +1135,9 @@ Your nickname '%s' is already used (%d points). Do you still want to use it?
Checking for existing player with your name
==

Are you sure that you want to disconnect and switch to a different server?
==

Country / Region
==

Expand Down Expand Up @@ -1180,6 +1183,9 @@ Getting server list from master server
%d player
==

Refreshing...
==

Leak IP
==

Expand Down Expand Up @@ -1240,6 +1246,15 @@ Assets
Show client IDs
==

Kill Messages
==

Kill Message Normal Color
==

Kill Message Highlight Color
==

Laser Outline Color
==

Expand All @@ -1264,6 +1279,9 @@ Use old chat style
Preview
==

Show local player's key presses
==

Background
==

Expand Down
2 changes: 1 addition & 1 deletion ddnet-libs
Submodule ddnet-libs updated 166 files
79 changes: 48 additions & 31 deletions src/base/system.cpp
Expand Up @@ -314,39 +314,13 @@ IOHANDLE io_open(const char *filename, int flags)
dbg_assert(flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, write or append");
#if defined(CONF_FAMILY_WINDOWS)
WCHAR wBuffer[IO_MAX_PATH_LENGTH];
MultiByteToWideChar(CP_UTF8, 0, filename, IO_MAX_PATH_LENGTH, wBuffer, IO_MAX_PATH_LENGTH);
if(flags == IOFLAG_READ)
{
// check for filename case sensitive
WIN32_FIND_DATAW finddata;
HANDLE handle;
char buffer[IO_MAX_PATH_LENGTH];

int length = str_length(filename);
if(!filename || !length || filename[length - 1] == '\\')
return 0x0;
MultiByteToWideChar(CP_UTF8, 0, filename, IO_MAX_PATH_LENGTH, wBuffer, IO_MAX_PATH_LENGTH);
handle = FindFirstFileW(wBuffer, &finddata);
if(handle == INVALID_HANDLE_VALUE)
return 0x0;
WideCharToMultiByte(CP_UTF8, 0, finddata.cFileName, -1, buffer, IO_MAX_PATH_LENGTH, NULL, NULL);
if(str_comp(filename + length - str_length(buffer), buffer) != 0)
{
FindClose(handle);
return 0x0;
}
FindClose(handle);
return (IOHANDLE)_wfsopen(wBuffer, L"rb", _SH_DENYNO);
}
if(flags == IOFLAG_WRITE)
{
MultiByteToWideChar(CP_UTF8, 0, filename, IO_MAX_PATH_LENGTH, wBuffer, IO_MAX_PATH_LENGTH);
return (IOHANDLE)_wfsopen(wBuffer, L"wb", _SH_DENYNO);
}
if(flags == IOFLAG_APPEND)
{
MultiByteToWideChar(CP_UTF8, 0, filename, IO_MAX_PATH_LENGTH, wBuffer, IO_MAX_PATH_LENGTH);
return (IOHANDLE)_wfsopen(wBuffer, L"ab", _SH_DENYNO);
}
return 0x0;
#else
if(flags == IOFLAG_READ)
Expand Down Expand Up @@ -2195,10 +2169,12 @@ void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int t
int fs_storage_path(const char *appname, char *path, int max)
{
#if defined(CONF_FAMILY_WINDOWS)
char *home = getenv("APPDATA");
WCHAR *home = _wgetenv(L"APPDATA");
if(!home)
return -1;
_snprintf(path, max, "%s/%s", home, appname);
char buffer[IO_MAX_PATH_LENGTH];
WideCharToMultiByte(CP_UTF8, 0, home, -1, buffer, IO_MAX_PATH_LENGTH, NULL, NULL);
_snprintf(path, max, "%s/%s", buffer, appname);
return 0;
#elif defined(CONF_PLATFORM_ANDROID)
// just use the data directory
Expand Down Expand Up @@ -2249,7 +2225,9 @@ int fs_makedir_rec_for(const char *path)
int fs_makedir(const char *path)
{
#if defined(CONF_FAMILY_WINDOWS)
if(_mkdir(path) == 0)
WCHAR wBuffer[IO_MAX_PATH_LENGTH];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wBuffer, IO_MAX_PATH_LENGTH);
if(_wmkdir(wBuffer) == 0)
return 0;
if(errno == EEXIST)
return 0;
Expand Down Expand Up @@ -2319,10 +2297,19 @@ int fs_chdir(const char *path)
{
if(fs_is_dir(path))
{
#if defined(CONF_FAMILY_WINDOWS)
WCHAR wBuffer[IO_MAX_PATH_LENGTH];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wBuffer, IO_MAX_PATH_LENGTH);
if(_wchdir(wBuffer))
return 1;
else
return 0;
#else
if(chdir(path))
return 1;
else
return 0;
#endif
}
else
return 1;
Expand All @@ -2333,7 +2320,11 @@ char *fs_getcwd(char *buffer, int buffer_size)
if(buffer == 0)
return 0;
#if defined(CONF_FAMILY_WINDOWS)
return _getcwd(buffer, buffer_size);
WCHAR wBuffer[IO_MAX_PATH_LENGTH];
if(_wgetcwd(wBuffer, buffer_size) == 0)
return 0;
WideCharToMultiByte(CP_UTF8, 0, wBuffer, IO_MAX_PATH_LENGTH, buffer, buffer_size, NULL, NULL);
return buffer;
#else
return getcwd(buffer, buffer_size);
#endif
Expand Down Expand Up @@ -3530,6 +3521,32 @@ const char *str_next_token(const char *str, const char *delim, char *buffer, int
return tok + len;
}

int bytes_be_to_int(const unsigned char *bytes)
{
return ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff);
}

void int_to_bytes_be(unsigned char *bytes, int value)
{
bytes[0] = (value >> 24) & 0xff;
bytes[1] = (value >> 16) & 0xff;
bytes[2] = (value >> 8) & 0xff;
bytes[3] = value & 0xff;
}

unsigned bytes_be_to_uint(const unsigned char *bytes)
{
return ((bytes[0] & 0xffu) << 24u) | ((bytes[1] & 0xffu) << 16u) | ((bytes[2] & 0xffu) << 8u) | (bytes[3] & 0xffu);
}

void uint_to_bytes_be(unsigned char *bytes, unsigned value)
{
bytes[0] = (value >> 24u) & 0xffu;
bytes[1] = (value >> 16u) & 0xffu;
bytes[2] = (value >> 8u) & 0xffu;
bytes[3] = value & 0xffu;
}

int pid()
{
#if defined(CONF_FAMILY_WINDOWS)
Expand Down
54 changes: 50 additions & 4 deletions src/base/system.h
Expand Up @@ -1845,10 +1845,10 @@ void dbg_logger_file(const char *filename);

typedef struct
{
int sent_packets;
int sent_bytes;
int recv_packets;
int recv_bytes;
uint64_t sent_packets;
uint64_t sent_bytes;
uint64_t recv_packets;
uint64_t recv_bytes;
} NETSTATS;

void net_stats(NETSTATS *stats);
Expand Down Expand Up @@ -2125,6 +2125,52 @@ const char *str_next_token(const char *str, const char *delim, char *buffer, int
*/
int str_in_list(const char *list, const char *delim, const char *needle);

/*
Function: bytes_be_to_int
Packs 4 big endian bytes into an int
Returns:
The packed int
Remarks:
- Assumes the passed array is 4 bytes
- Assumes int is 4 bytes
*/
int bytes_be_to_int(const unsigned char *bytes);

/*
Function: int_to_bytes_be
Packs an int into 4 big endian bytes
Remarks:
- Assumes the passed array is 4 bytes
- Assumes int is 4 bytes
*/
void int_to_bytes_be(unsigned char *bytes, int value);

/*
Function: bytes_be_to_uint
Packs 4 big endian bytes into an unsigned
Returns:
The packed unsigned
Remarks:
- Assumes the passed array is 4 bytes
- Assumes unsigned is 4 bytes
*/
unsigned bytes_be_to_uint(const unsigned char *bytes);

/*
Function: uint_to_bytes_be
Packs an unsigned into 4 big endian bytes
Remarks:
- Assumes the passed array is 4 bytes
- Assumes unsigned is 4 bytes
*/
void uint_to_bytes_be(unsigned char *bytes, unsigned value);

/*
Function: pid
Returns the pid of the current process.
Expand Down
1 change: 1 addition & 0 deletions src/engine/client.h
Expand Up @@ -109,6 +109,7 @@ class IClient : public IInterface
virtual void DummyConnect() = 0;
virtual bool DummyConnected() = 0;
virtual bool DummyConnecting() = 0;
virtual bool DummyAllowed() = 0;

virtual void Restart() = 0;
virtual void Quit() = 0;
Expand Down
71 changes: 30 additions & 41 deletions src/engine/client/backend_sdl.cpp
Expand Up @@ -54,44 +54,26 @@ int putenv(const char *);
}
#endif

/*
sync_barrier - creates a full hardware fence
*/
#if defined(__GNUC__)
inline void sync_barrier()
{
__sync_synchronize();
}
#elif defined(_MSC_VER)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
inline void sync_barrier()
{
MemoryBarrier();
}
#else
#error missing atomic implementation for this compiler
#endif

// ------------ CGraphicsBackend_Threaded

void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)
void CGraphicsBackend_Threaded::ThreadFunc()
{
CGraphicsBackend_Threaded *pThis = (CGraphicsBackend_Threaded *)pUser;

while(!pThis->m_Shutdown)
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
// notify, that the thread started
m_BufferDoneCond.notify_all();
while(!m_Shutdown)
{
pThis->m_Activity.Wait();
if(pThis->m_pBuffer)
m_BufferSwapCond.wait(Lock);
if(m_pBuffer)
{
#ifdef CONF_PLATFORM_MACOS
CAutoreleasePool AutoreleasePool;
#endif
pThis->m_pProcessor->RunBuffer(pThis->m_pBuffer);
m_pProcessor->RunBuffer(m_pBuffer);

sync_barrier();
pThis->m_pBuffer = 0x0;
pThis->m_BufferDone.Signal();
m_pBuffer = nullptr;
m_BufferInProcess.store(false, std::memory_order_relaxed);
m_BufferDoneCond.notify_all();
}
#if defined(CONF_VIDEORECORDER)
if(IVideo::Current())
Expand All @@ -102,43 +84,50 @@ void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)

CGraphicsBackend_Threaded::CGraphicsBackend_Threaded()
{
m_pBuffer = 0x0;
m_pProcessor = 0x0;
m_pThread = 0x0;
m_pBuffer = nullptr;
m_pProcessor = nullptr;
m_BufferInProcess.store(false, std::memory_order_relaxed);
}

void CGraphicsBackend_Threaded::StartProcessor(ICommandProcessor *pProcessor)
{
m_Shutdown = false;
m_pProcessor = pProcessor;
m_pThread = thread_init(ThreadFunc, this, "CGraphicsBackend_Threaded");
m_BufferDone.Signal();
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_Thread = std::thread([&]() { ThreadFunc(); });
// wait for the thread to start
m_BufferDoneCond.wait(Lock);
}

void CGraphicsBackend_Threaded::StopProcessor()
{
m_Shutdown = true;
m_Activity.Signal();
if(m_pThread)
thread_wait(m_pThread);
{
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_BufferSwapCond.notify_all();
}
m_Thread.join();
}

void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
{
WaitForIdle();
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_pBuffer = pBuffer;
m_Activity.Signal();
m_BufferInProcess.store(true, std::memory_order_relaxed);
m_BufferSwapCond.notify_all();
}

bool CGraphicsBackend_Threaded::IsIdle() const
{
return m_pBuffer == 0x0;
return !m_BufferInProcess.load(std::memory_order_relaxed);
}

void CGraphicsBackend_Threaded::WaitForIdle()
{
while(m_pBuffer != 0x0)
m_BufferDone.Wait();
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
if(m_pBuffer != nullptr)
m_BufferDoneCond.wait(Lock);
}

// ------------ CCommandProcessorFragment_General
Expand Down
15 changes: 10 additions & 5 deletions src/engine/client/backend_sdl.h
Expand Up @@ -13,6 +13,9 @@
#include <base/tl/threading.h>

#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>

#if defined(CONF_PLATFORM_MACOS)
Expand Down Expand Up @@ -64,13 +67,15 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend

private:
ICommandProcessor *m_pProcessor;
CCommandBuffer *volatile m_pBuffer;
std::mutex m_BufferSwapMutex;
std::condition_variable m_BufferSwapCond;
std::condition_variable m_BufferDoneCond;
CCommandBuffer *m_pBuffer;
std::atomic_bool m_Shutdown;
CSemaphore m_Activity;
CSemaphore m_BufferDone;
void *m_pThread;
std::atomic_bool m_BufferInProcess;
std::thread m_Thread;

static void ThreadFunc(void *pUser);
void ThreadFunc();
};

// takes care of implementation independent operations
Expand Down
45 changes: 25 additions & 20 deletions src/engine/client/client.cpp
Expand Up @@ -80,6 +80,11 @@
#include "SDL_rwops.h"
#include "base/hash.h"

// for msvc
#ifndef PRIu64
#define PRIu64 "I64u"
#endif

static const ColorRGBA ClientNetworkPrintColor{0.7f, 1, 0.7f, 1.0f};
static const ColorRGBA ClientNetworkErrPrintColor{1.0f, 0.25f, 0.25f, 1.0f};

Expand Down Expand Up @@ -864,7 +869,7 @@ void CClient::DummyConnect()
if(m_NetClient[CLIENT_MAIN].State() != NET_CONNSTATE_ONLINE && m_NetClient[CLIENT_MAIN].State() != NET_CONNSTATE_PENDING)
return;

if(m_DummyConnected)
if(m_DummyConnected || !DummyAllowed())
return;

m_LastDummyConnectTime = GameTick(g_Config.m_ClDummy);
Expand Down Expand Up @@ -895,6 +900,11 @@ void CClient::DummyDisconnect(const char *pReason)
GameClient()->OnDummyDisconnect();
}

bool CClient::DummyAllowed()
{
return m_ServerCapabilities.m_AllowDummy;
}

int CClient::GetCurrentRaceTime()
{
if(GameClient()->GetLastRaceTick() < 0)
Expand Down Expand Up @@ -981,19 +991,10 @@ void CClient::SnapInvalidateItem(int SnapID, int Index)

void *CClient::SnapFindItem(int SnapID, int Type, int ID) const
{
// TODO: linear search. should be fixed.
int i;

if(!m_aSnapshots[g_Config.m_ClDummy][SnapID])
return 0x0;

for(i = 0; i < m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); i++)
{
CSnapshotItem *pItem = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(i);
if(m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(i) == Type && pItem->ID() == ID)
return (void *)pItem->Data();
}
return 0x0;
return m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->FindItem(Type, ID);
}

int CClient::SnapNumItems(int SnapID) const
Expand Down Expand Up @@ -1045,18 +1046,18 @@ void CClient::DebugRender()
Graphics()->QuadsText(2, 2, 16, aBuffer);

{
int SendPackets = (Current.sent_packets - Prev.sent_packets);
int SendBytes = (Current.sent_bytes - Prev.sent_bytes);
int SendTotal = SendBytes + SendPackets * 42;
int RecvPackets = (Current.recv_packets - Prev.recv_packets);
int RecvBytes = (Current.recv_bytes - Prev.recv_bytes);
int RecvTotal = RecvBytes + RecvPackets * 42;
uint64_t SendPackets = (Current.sent_packets - Prev.sent_packets);
uint64_t SendBytes = (Current.sent_bytes - Prev.sent_bytes);
uint64_t SendTotal = SendBytes + SendPackets * 42;
uint64_t RecvPackets = (Current.recv_packets - Prev.recv_packets);
uint64_t RecvBytes = (Current.recv_bytes - Prev.recv_bytes);
uint64_t RecvTotal = RecvBytes + RecvPackets * 42;

if(!SendPackets)
SendPackets++;
if(!RecvPackets)
RecvPackets++;
str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
str_format(aBuffer, sizeof(aBuffer), "send: %3" PRIu64 " %5" PRIu64 "+%4" PRIu64 "=%5" PRIu64 " (%3" PRIu64 " kbps) avg: %5" PRIu64 "\nrecv: %3" PRIu64 " %5" PRIu64 "+%4" PRIu64 "=%5" PRIu64 " (%3" PRIu64 " kbps) avg: %5" PRIu64,
SendPackets, SendBytes, SendPackets * 42, SendTotal, (SendTotal * 8) / 1024, SendBytes / SendPackets,
RecvPackets, RecvBytes, RecvPackets * 42, RecvTotal, (RecvTotal * 8) / 1024, RecvBytes / RecvPackets);
Graphics()->QuadsText(2, 14, 16, aBuffer);
Expand Down Expand Up @@ -1550,6 +1551,7 @@ static CServerCapabilities GetServerCapabilities(int Version, int Flags)
Result.m_ChatTimeoutCode = DDNet;
Result.m_AnyPlayerFlag = DDNet;
Result.m_PingEx = false;
Result.m_AllowDummy = true;
if(Version >= 1)
{
Result.m_ChatTimeoutCode = Flags & SERVERCAPFLAG_CHATTIMEOUTCODE;
Expand All @@ -1562,6 +1564,10 @@ static CServerCapabilities GetServerCapabilities(int Version, int Flags)
{
Result.m_PingEx = Flags & SERVERCAPFLAG_PINGEX;
}
if(Version >= 4)
{
Result.m_AllowDummy = Flags & SERVERCAPFLAG_ALLOWDUMMY;
}
return Result;
}

Expand Down Expand Up @@ -3771,7 +3777,6 @@ void CClient::DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void

const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
{
int Crc;
const char *pError;

IOHANDLE File = Storage()->OpenFile(pFilename, IOFLAG_READ, StorageType);
Expand All @@ -3790,7 +3795,7 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
return "error loading demo";

// load map
Crc = m_DemoPlayer.GetMapInfo()->m_Crc;
int Crc = m_DemoPlayer.GetMapInfo()->m_Crc;
SHA256_DIGEST Sha = m_DemoPlayer.GetMapInfo()->m_Sha256;
pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Sha != SHA256_ZEROED ? &Sha : nullptr, Crc);
if(pError)
Expand Down
2 changes: 2 additions & 0 deletions src/engine/client/client.h
Expand Up @@ -82,6 +82,7 @@ class CServerCapabilities
bool m_ChatTimeoutCode;
bool m_AnyPlayerFlag;
bool m_PingEx;
bool m_AllowDummy;
};

class CClient : public IClient, public CDemoPlayer::IListener
Expand Down Expand Up @@ -339,6 +340,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
virtual void DummyConnect();
virtual bool DummyConnected();
virtual bool DummyConnecting();
virtual bool DummyAllowed();
int m_DummyConnected;
int m_LastDummyConnectTime;

Expand Down
27 changes: 10 additions & 17 deletions src/engine/client/ghost.cpp
Expand Up @@ -141,23 +141,16 @@ int CGhostRecorder::Stop(int Ticks, int Time)

FlushChunk();

unsigned char aNumTicks[4];
unsigned char aTime[4];

aNumTicks[0] = (Ticks >> 24) & 0xff;
aNumTicks[1] = (Ticks >> 16) & 0xff;
aNumTicks[2] = (Ticks >> 8) & 0xff;
aNumTicks[3] = (Ticks)&0xff;

aTime[0] = (Time >> 24) & 0xff;
aTime[1] = (Time >> 16) & 0xff;
aTime[2] = (Time >> 8) & 0xff;
aTime[3] = (Time)&0xff;

// write down num shots and time
io_seek(m_File, gs_NumTicksOffset, IOSEEK_START);
io_write(m_File, &aNumTicks, sizeof(aNumTicks));
io_write(m_File, &aTime, sizeof(aTime));

unsigned char aNumTicks[4];
int_to_bytes_be(aNumTicks, Ticks);
io_write(m_File, aNumTicks, sizeof(aNumTicks));

unsigned char aTime[4];
int_to_bytes_be(aTime, Time);
io_write(m_File, aTime, sizeof(aTime));

io_close(m_File);
m_File = 0;
Expand Down Expand Up @@ -246,7 +239,7 @@ int CGhostLoader::Load(const char *pFilename, const char *pMap, SHA256_DIGEST Ma
else
{
io_skip(m_File, -(int)sizeof(SHA256_DIGEST));
unsigned GhostMapCrc = (m_Header.m_aZeroes[0] << 24) | (m_Header.m_aZeroes[1] << 16) | (m_Header.m_aZeroes[2] << 8) | (m_Header.m_aZeroes[3]);
unsigned GhostMapCrc = bytes_be_to_uint(m_Header.m_aZeroes);
if(str_comp(m_Header.m_aMap, pMap) != 0 || GhostMapCrc != MapCrc)
{
char aBuf[256];
Expand Down Expand Up @@ -396,7 +389,7 @@ bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostInfo *pInfo, const
}
else
{
unsigned GhostMapCrc = (Header.m_aZeroes[0] << 24) | (Header.m_aZeroes[1] << 16) | (Header.m_aZeroes[2] << 8) | (Header.m_aZeroes[3]);
unsigned GhostMapCrc = bytes_be_to_uint(Header.m_aZeroes);
if(GhostMapCrc != MapCrc)
{
return false;
Expand Down
4 changes: 2 additions & 2 deletions src/engine/client/ghost.h
Expand Up @@ -23,12 +23,12 @@ struct CGhostHeader

int GetTicks() const
{
return (m_aNumTicks[0] << 24) | (m_aNumTicks[1] << 16) | (m_aNumTicks[2] << 8) | (m_aNumTicks[3]);
return bytes_be_to_int(m_aNumTicks);
}

int GetTime() const
{
return (m_aTime[0] << 24) | (m_aTime[1] << 16) | (m_aTime[2] << 8) | (m_aTime[3]);
return bytes_be_to_int(m_aTime);
}

CGhostInfo ToGhostInfo() const
Expand Down
46 changes: 22 additions & 24 deletions src/engine/client/graphics_threaded.cpp
Expand Up @@ -503,45 +503,41 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename,
int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType)
{
char aCompleteFilename[IO_MAX_PATH_LENGTH];
unsigned char *pBuffer;
png_t Png; // ignore_convention

// open file for reading
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename));
if(File)
io_close(File);
else
if(!File)
{
dbg_msg("game/png", "failed to open file. filename='%s'", pFilename);
return 0;
}

int Error = png_open_file(&Png, aCompleteFilename); // ignore_convention
png_t Png; // ignore_convention
int Error = png_open_read(&Png, 0, File); // ignore_convention
if(Error != PNG_NO_ERROR)
{
dbg_msg("game/png", "failed to open file. filename='%s', pnglite: %s", aCompleteFilename, png_error_string(Error));
if(Error != PNG_FILE_ERROR)
png_close_file(&Png); // ignore_convention
io_close(File);
return 0;
}

if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention
{
dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename);
png_close_file(&Png); // ignore_convention
io_close(File);
return 0;
}

pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); // ignore_convention
unsigned char *pBuffer = (unsigned char *)malloc((size_t)Png.width * Png.height * Png.bpp); // ignore_convention
Error = png_get_data(&Png, pBuffer); // ignore_convention
if(Error != PNG_NO_ERROR)
{
dbg_msg("game/png", "failed to read image. filename='%s', pnglite: %s", aCompleteFilename, png_error_string(Error));
free(pBuffer);
png_close_file(&Png); // ignore_convention
io_close(File);
return 0;
}
png_close_file(&Png); // ignore_convention
io_close(File);

pImg->m_Width = Png.width; // ignore_convention
pImg->m_Height = Png.height; // ignore_convention
Expand Down Expand Up @@ -667,16 +663,20 @@ void CGraphics_Threaded::ScreenshotDirect()
png_t Png; // ignore_convention

IOHANDLE File = m_pStorage->OpenFile(m_aScreenshotName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
if(File)
io_close(File);

// save png
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f));
png_open_file_write(&Png, aWholePath); // ignore_convention
png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Image.m_pData); // ignore_convention
png_close_file(&Png); // ignore_convention
if(!File)
{
dbg_msg("game/screenshot", "failed to open file. filename='%s'", aWholePath);
}
else
{
// save png
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f));
png_open_write(&Png, 0, File); // ignore_convention
png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Image.m_pData); // ignore_convention
io_close(File); // ignore_convention
}

free(Image.m_pData);
}
Expand Down Expand Up @@ -2346,8 +2346,6 @@ void CGraphics_Threaded::Resize(int w, int h, int RefreshRate, bool SetWindowSiz
// adjust the viewport to only allow certain aspect ratios
if(m_ScreenHeight > 4 * m_ScreenWidth / 5)
m_ScreenHeight = 4 * m_ScreenWidth / 5;
if(m_ScreenWidth > 21 * m_ScreenHeight / 9)
m_ScreenWidth = 21 * m_ScreenHeight / 9;

m_ScreenRefreshRate = RefreshRate == -1 ? m_ScreenRefreshRate : RefreshRate;

Expand Down
6 changes: 3 additions & 3 deletions src/engine/client/http.cpp
Expand Up @@ -183,9 +183,9 @@ size_t CRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pU
int CRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
{
CGetFile *pTask = (CGetFile *)pUser;
pTask->m_Current = DlCurr;
pTask->m_Size = DlTotal;
pTask->m_Progress = (100 * DlCurr) / (DlTotal ? DlTotal : 1);
pTask->m_Current.store(DlCurr, std::memory_order_relaxed);
pTask->m_Size.store(DlTotal, std::memory_order_relaxed);
pTask->m_Progress.store((100 * DlCurr) / (DlTotal ? DlTotal : 1), std::memory_order_relaxed);
pTask->OnProgress();
return pTask->m_Abort ? -1 : 0;
}
Expand Down
13 changes: 7 additions & 6 deletions src/engine/client/http.h
@@ -1,6 +1,7 @@
#ifndef ENGINE_CLIENT_HTTP_H
#define ENGINE_CLIENT_HTTP_H

#include <atomic>
#include <engine/kernel.h>
#include <engine/shared/jobs.h>
#include <engine/storage.h>
Expand Down Expand Up @@ -46,9 +47,9 @@ class CRequest : public IJob

CTimeout m_Timeout;

double m_Size;
double m_Current;
int m_Progress;
std::atomic<double> m_Size;
std::atomic<double> m_Current;
std::atomic<int> m_Progress;
HTTPLOG m_LogProgress;

std::atomic<int> m_State;
Expand All @@ -66,9 +67,9 @@ class CRequest : public IJob
public:
CRequest(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL);

double Current() const { return m_Current; }
double Size() const { return m_Size; }
int Progress() const { return m_Progress; }
double Current() const { return m_Current.load(std::memory_order_relaxed); }
double Size() const { return m_Size.load(std::memory_order_relaxed); }
int Progress() const { return m_Progress.load(std::memory_order_relaxed); }
int State() const { return m_State; }
void Abort() { m_Abort = true; }
};
Expand Down
48 changes: 23 additions & 25 deletions src/engine/client/sound.cpp
Expand Up @@ -8,6 +8,7 @@
#include <engine/storage.h>

#include <engine/shared/config.h>
#include <mutex>

#include "SDL.h"

Expand Down Expand Up @@ -69,15 +70,15 @@ static CSample m_aSamples[NUM_SAMPLES] = {{0}};
static CVoice m_aVoices[NUM_VOICES] = {{0}};
static CChannel m_aChannels[NUM_CHANNELS] = {{255, 0}};

static LOCK m_SoundLock = 0;
static std::mutex m_SoundLock;

static int m_CenterX = 0;
static int m_CenterY = 0;
static std::atomic<int> m_CenterX{0};
static std::atomic<int> m_CenterY{0};

static int m_MixingRate = 48000;
static std::atomic<int> m_SoundVolume{100};

static int m_NextVoice GUARDED_BY(m_SoundLock) = 0;
static int m_NextVoice = 0;
static int *m_pMixBuffer = 0; // buffer only used by the thread callback function
static unsigned m_MaxFrames = 0;

Expand Down Expand Up @@ -112,7 +113,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
Frames = minimum(Frames, m_MaxFrames);

// acquire lock while we are mixing
lock_wait(m_SoundLock);
m_SoundLock.lock();

MasterVol = m_SoundVolume;

Expand Down Expand Up @@ -144,8 +145,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan)
{
// TODO: we should respect the channel panning value
int dx = Voice.m_X - m_CenterX;
int dy = Voice.m_Y - m_CenterY;
int dx = Voice.m_X - m_CenterX.load(std::memory_order_relaxed);
int dy = Voice.m_Y - m_CenterY.load(std::memory_order_relaxed);
//
int p = IntAbs(dx);
float FalloffX = 0.0f;
Expand Down Expand Up @@ -255,7 +256,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
}

// release the lock
lock_unlock(m_SoundLock);
m_SoundLock.unlock();

{
// clamp accumulated values
Expand Down Expand Up @@ -299,8 +300,6 @@ int CSound::Init()

SDL_AudioSpec Format, FormatOut;

m_SoundLock = lock_create();

if(!g_Config.m_SndEnable)
return 0;

Expand Down Expand Up @@ -351,9 +350,8 @@ int CSound::Update()

if(WantedVolume != m_SoundVolume)
{
lock_wait(m_SoundLock);
std::unique_lock<std::mutex> Lock(m_SoundLock);
m_SoundVolume = WantedVolume;
lock_unlock(m_SoundLock);
}
//#if defined(CONF_VIDEORECORDER)
// if(IVideo::Current() && g_Config.m_ClVideoSndEnable)
Expand All @@ -371,7 +369,6 @@ int CSound::Shutdown()

SDL_CloseAudioDevice(m_Device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
lock_destroy(m_SoundLock);
free(m_pMixBuffer);
m_pMixBuffer = 0;
return 0;
Expand Down Expand Up @@ -761,8 +758,8 @@ float CSound::GetSampleDuration(int SampleID)

void CSound::SetListenerPos(float x, float y)
{
m_CenterX = (int)x;
m_CenterY = (int)y;
m_CenterX.store((int)x, std::memory_order_relaxed);
m_CenterY.store((int)y, std::memory_order_relaxed);
}

void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)
Expand All @@ -772,6 +769,7 @@ void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -786,6 +784,7 @@ void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -800,6 +799,7 @@ void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -814,10 +814,10 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

lock_wait(m_SoundLock);
{
if(m_aVoices[VoiceID].m_pSample)
{
Expand All @@ -841,7 +841,6 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)
}
}
}
lock_unlock(m_SoundLock);
}

void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)
Expand All @@ -851,6 +850,7 @@ void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -865,6 +865,7 @@ void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -885,7 +886,7 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float
int Age = -1;
int i;

lock_wait(m_SoundLock);
m_SoundLock.lock();

// search for voice
for(i = 0; i < NUM_VOICES; i++)
Expand Down Expand Up @@ -918,7 +919,7 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float
Age = m_aVoices[VoiceID].m_Age;
}

lock_unlock(m_SoundLock);
m_SoundLock.unlock();
return CreateVoiceHandle(VoiceID, Age);
}

Expand All @@ -935,7 +936,7 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags)
void CSound::Stop(int SampleID)
{
// TODO: a nice fade out
lock_wait(m_SoundLock);
std::unique_lock<std::mutex> Lock(m_SoundLock);
CSample *pSample = &m_aSamples[SampleID];
for(auto &Voice : m_aVoices)
{
Expand All @@ -948,13 +949,12 @@ void CSound::Stop(int SampleID)
Voice.m_pSample = 0;
}
}
lock_unlock(m_SoundLock);
}

void CSound::StopAll()
{
// TODO: a nice fade out
lock_wait(m_SoundLock);
std::unique_lock<std::mutex> Lock(m_SoundLock);
for(auto &Voice : m_aVoices)
{
if(Voice.m_pSample)
Expand All @@ -966,7 +966,6 @@ void CSound::StopAll()
}
Voice.m_pSample = 0;
}
lock_unlock(m_SoundLock);
}

void CSound::StopVoice(CVoiceHandle Voice)
Expand All @@ -976,15 +975,14 @@ void CSound::StopVoice(CVoiceHandle Voice)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

lock_wait(m_SoundLock);
{
m_aVoices[VoiceID].m_pSample = 0;
m_aVoices[VoiceID].m_Age++;
}
lock_unlock(m_SoundLock);
}

IEngineSound *CreateEngineSound() { return new CSound; }
2 changes: 1 addition & 1 deletion src/engine/client/steam.cpp
Expand Up @@ -22,7 +22,7 @@ class CSteam : public ISteam
m_pSteamFriends = SteamAPI_SteamFriends_v017();

ReadLaunchCommandLine();
str_copy(m_aPlayerName, SteamAPI_ISteamFriends_GetPersonaName(m_pSteamFriends), sizeof(m_aPlayerName));
str_utf8_copy(m_aPlayerName, SteamAPI_ISteamFriends_GetPersonaName(m_pSteamFriends), sizeof(m_aPlayerName));
}
~CSteam()
{
Expand Down
12 changes: 10 additions & 2 deletions src/engine/client/text.cpp
Expand Up @@ -1477,8 +1477,16 @@ class CTextRender : public IEngineTextRender
TextContainer.m_HasCursor = HasCursor;
TextContainer.m_HasSelection = HasSelection;

pCursor->m_SelectionStart = SelectionStartChar;
pCursor->m_SelectionEnd = SelectionEndChar;
if(HasSelection)
{
pCursor->m_SelectionStart = SelectionStartChar;
pCursor->m_SelectionEnd = SelectionEndChar;
}
else
{
pCursor->m_SelectionStart = -1;
pCursor->m_SelectionEnd = -1;
}
}

// even if no text is drawn the cursor position will be adjusted
Expand Down
6 changes: 3 additions & 3 deletions src/engine/demo.h
Expand Up @@ -33,14 +33,14 @@ struct CDemoHeader
unsigned char m_aMapSize[4];
unsigned char m_aMapCrc[4];
char m_aType[8];
char m_aLength[4];
unsigned char m_aLength[4];
char m_aTimestamp[20];
};

struct CTimelineMarkers
{
char m_aNumTimelineMarkers[4];
char m_aTimelineMarkers[MAX_TIMELINE_MARKERS][4];
unsigned char m_aNumTimelineMarkers[4];
unsigned char m_aTimelineMarkers[MAX_TIMELINE_MARKERS][4];
};

struct CMapInfo
Expand Down
6 changes: 0 additions & 6 deletions src/engine/external/json-parser/json.h
Expand Up @@ -226,12 +226,6 @@ typedef struct _json_value
};
}

/* DDNet additions */
inline operator int () const
{
return (json_int_t) *this;
}

inline operator bool () const
{
if (type != json_boolean)
Expand Down
1 change: 1 addition & 0 deletions src/engine/server/databases/connection.h
Expand Up @@ -72,6 +72,7 @@ class IDbConnection
virtual bool IsNull(int Col) = 0;
virtual float GetFloat(int Col) = 0;
virtual int GetInt(int Col) = 0;
virtual int64_t GetInt64(int Col) = 0;
// ensures that the string is null terminated
virtual void GetString(int Col, char *pBuffer, int BufferSize) = 0;
// returns number of bytes read into the buffer
Expand Down
29 changes: 29 additions & 0 deletions src/engine/server/databases/mysql.cpp
Expand Up @@ -91,6 +91,7 @@ class CMysqlConnection : public IDbConnection
virtual bool IsNull(int Col);
virtual float GetFloat(int Col);
virtual int GetInt(int Col);
virtual int64_t GetInt64(int Col);
virtual void GetString(int Col, char *pBuffer, int BufferSize);
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize);

Expand Down Expand Up @@ -550,6 +551,34 @@ int CMysqlConnection::GetInt(int Col)
return Value;
}

int64_t CMysqlConnection::GetInt64(int Col)
{
Col -= 1;

MYSQL_BIND Bind;
int64_t Value;
my_bool IsNull;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_LONGLONG;
Bind.buffer = &Value;
Bind.buffer_length = sizeof(Value);
Bind.length = nullptr;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = nullptr;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:int64");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetInt64");
}
if(IsNull)
{
dbg_assert(0, "error getting int: NULL");
}
return Value;
}

void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize)
{
Col -= 1;
Expand Down
6 changes: 6 additions & 0 deletions src/engine/server/databases/sqlite.cpp
Expand Up @@ -41,6 +41,7 @@ class CSqliteConnection : public IDbConnection
virtual bool IsNull(int Col);
virtual float GetFloat(int Col);
virtual int GetInt(int Col);
virtual int64_t GetInt64(int Col);
virtual void GetString(int Col, char *pBuffer, int BufferSize);
// passing a negative buffer size is undefined behavior
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize);
Expand Down Expand Up @@ -274,6 +275,11 @@ int CSqliteConnection::GetInt(int Col)
return sqlite3_column_int(m_pStmt, Col - 1);
}

int64_t CSqliteConnection::GetInt64(int Col)
{
return sqlite3_column_int64(m_pStmt, Col - 1);
}

void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize)
{
str_copy(pBuffer, (const char *)sqlite3_column_text(m_pStmt, Col - 1), BufferSize);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/server/server.cpp
Expand Up @@ -1147,7 +1147,7 @@ void CServer::SendCapabilities(int ClientID)
{
CMsgPacker Msg(NETMSG_CAPABILITIES, true);
Msg.AddInt(SERVERCAP_CURVERSION); // version
Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX); // flags
Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX | SERVERCAPFLAG_ALLOWDUMMY); // flags
SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
}

Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/compression.cpp
Expand Up @@ -55,7 +55,7 @@ const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut
if(!(*pSrc & 0x80))
break;
pSrc++;
*pInOut |= (*pSrc & (0x7F)) << (6 + 7 + 7 + 7);
*pInOut |= (*pSrc & (0x1F)) << (6 + 7 + 7 + 7);
} while(0);

pSrc++;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/config_variables.h
Expand Up @@ -12,7 +12,7 @@ MACRO_CONFIG_INT(PlayerCountry, player_country, -1, -1, 1000, CFGFLAG_SAVE | CFG
MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Password to the server")
MACRO_CONFIG_STR(Logfile, logfile, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Filename to log all output to")
MACRO_CONFIG_INT(ConsoleOutputLevel, console_output_level, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Adjusts the amount of information in the console")
MACRO_CONFIG_INT(Events, events, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Enable triggering of events, like the happy eye emotes on some holidays.")
MACRO_CONFIG_INT(Events, events, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Enable triggering of events, (eye emotes on some holidays in server, christmas skins in client).")

MACRO_CONFIG_STR(SteamName, steam_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Last seen name of the Steam profile")

Expand Down
15 changes: 2 additions & 13 deletions src/engine/shared/datafile.cpp
Expand Up @@ -25,26 +25,15 @@ struct CItemEx
{
CItemEx Result;
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
{
Result.m_aUuid[i] =
(Uuid.m_aData[i * 4 + 0] << 24) |
(Uuid.m_aData[i * 4 + 1] << 16) |
(Uuid.m_aData[i * 4 + 2] << 8) |
(Uuid.m_aData[i * 4 + 3]);
}
Result.m_aUuid[i] = bytes_be_to_int(&Uuid.m_aData[i * 4]);
return Result;
}

CUuid ToUuid() const
{
CUuid Result;
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
{
Result.m_aData[i * 4 + 0] = m_aUuid[i] >> 24;
Result.m_aData[i * 4 + 1] = m_aUuid[i] >> 16;
Result.m_aData[i * 4 + 2] = m_aUuid[i] >> 8;
Result.m_aData[i * 4 + 3] = m_aUuid[i];
}
int_to_bytes_be(&Result.m_aData[i * 4], m_aUuid[i]);
return Result;
}
};
Expand Down
75 changes: 32 additions & 43 deletions src/engine/shared/demo.cpp
Expand Up @@ -42,7 +42,7 @@ CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapDat
}

// Record
int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, SHA256_DIGEST *pSha256, unsigned Crc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser)
int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, SHA256_DIGEST *pSha256, unsigned Crc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser)
{
m_pfnFilter = pfnFilter;
m_pUser = pUser;
Expand Down Expand Up @@ -130,14 +130,8 @@ int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, con
Header.m_Version = s_CurVersion;
str_copy(Header.m_aNetversion, pNetVersion, sizeof(Header.m_aNetversion));
str_copy(Header.m_aMapName, pMap, sizeof(Header.m_aMapName));
Header.m_aMapSize[0] = (MapSize >> 24) & 0xff;
Header.m_aMapSize[1] = (MapSize >> 16) & 0xff;
Header.m_aMapSize[2] = (MapSize >> 8) & 0xff;
Header.m_aMapSize[3] = (MapSize)&0xff;
Header.m_aMapCrc[0] = (Crc >> 24) & 0xff;
Header.m_aMapCrc[1] = (Crc >> 16) & 0xff;
Header.m_aMapCrc[2] = (Crc >> 8) & 0xff;
Header.m_aMapCrc[3] = (Crc)&0xff;
uint_to_bytes_be(Header.m_aMapSize, MapSize);
uint_to_bytes_be(Header.m_aMapCrc, Crc);
str_copy(Header.m_aType, pType, sizeof(Header.m_aType));
// Header.m_Length - add this on stop
str_timestamp(Header.m_aTimestamp, sizeof(Header.m_aTimestamp));
Expand Down Expand Up @@ -225,10 +219,7 @@ void CDemoRecorder::WriteTickMarker(int Tick, int Keyframe)
{
unsigned char aChunk[5];
aChunk[0] = CHUNKTYPEFLAG_TICKMARKER;
aChunk[1] = (Tick >> 24) & 0xff;
aChunk[2] = (Tick >> 16) & 0xff;
aChunk[3] = (Tick >> 8) & 0xff;
aChunk[4] = (Tick)&0xff;
uint_to_bytes_be(aChunk + 1, Tick);

if(Keyframe)
aChunk[0] |= CHUNKTICKFLAG_KEYFRAME;
Expand Down Expand Up @@ -349,30 +340,19 @@ int CDemoRecorder::Stop()

// add the demo length to the header
io_seek(m_File, s_LengthOffset, IOSEEK_START);
int DemoLength = Length();
char aLength[4];
aLength[0] = (DemoLength >> 24) & 0xff;
aLength[1] = (DemoLength >> 16) & 0xff;
aLength[2] = (DemoLength >> 8) & 0xff;
aLength[3] = (DemoLength)&0xff;
unsigned char aLength[4];
int_to_bytes_be(aLength, Length());
io_write(m_File, aLength, sizeof(aLength));

// add the timeline markers to the header
io_seek(m_File, s_NumMarkersOffset, IOSEEK_START);
char aNumMarkers[4];
aNumMarkers[0] = (m_NumTimelineMarkers >> 24) & 0xff;
aNumMarkers[1] = (m_NumTimelineMarkers >> 16) & 0xff;
aNumMarkers[2] = (m_NumTimelineMarkers >> 8) & 0xff;
aNumMarkers[3] = (m_NumTimelineMarkers)&0xff;
unsigned char aNumMarkers[4];
int_to_bytes_be(aNumMarkers, m_NumTimelineMarkers);
io_write(m_File, aNumMarkers, sizeof(aNumMarkers));
for(int i = 0; i < m_NumTimelineMarkers; i++)
{
int Marker = m_aTimelineMarkers[i];
char aMarker[4];
aMarker[0] = (Marker >> 24) & 0xff;
aMarker[1] = (Marker >> 16) & 0xff;
aMarker[2] = (Marker >> 8) & 0xff;
aMarker[3] = (Marker)&0xff;
unsigned char aMarker[4];
int_to_bytes_be(aMarker, m_aTimelineMarkers[i]);
io_write(m_File, aMarker, sizeof(aMarker));
}

Expand Down Expand Up @@ -466,7 +446,7 @@ int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
unsigned char aTickdata[4];
if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
return -1;
*pTick = (aTickdata[0] << 24) | (aTickdata[1] << 16) | (aTickdata[2] << 8) | aTickdata[3];
*pTick = bytes_be_to_int(aTickdata);
}
}
else
Expand Down Expand Up @@ -800,11 +780,11 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
m_DemoType = DEMOTYPE_INVALID;

// read map
unsigned MapSize = (m_Info.m_Header.m_aMapSize[0] << 24) | (m_Info.m_Header.m_aMapSize[1] << 16) | (m_Info.m_Header.m_aMapSize[2] << 8) | (m_Info.m_Header.m_aMapSize[3]);
unsigned MapSize = bytes_be_to_uint(m_Info.m_Header.m_aMapSize);

// check if we already have the map
// TODO: improve map checking (maps folder, check crc)
unsigned Crc = (m_Info.m_Header.m_aMapCrc[0] << 24) | (m_Info.m_Header.m_aMapCrc[1] << 16) | (m_Info.m_Header.m_aMapCrc[2] << 8) | (m_Info.m_Header.m_aMapCrc[3]);
unsigned Crc = bytes_be_to_uint(m_Info.m_Header.m_aMapCrc);

// save byte offset of map for later use
m_MapOffset = io_tell(m_File);
Expand All @@ -819,14 +799,11 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
if(m_Info.m_Header.m_Version > s_OldVersion)
{
// get timeline markers
int Num = ((m_Info.m_TimelineMarkers.m_aNumTimelineMarkers[0] << 24) & 0xFF000000) | ((m_Info.m_TimelineMarkers.m_aNumTimelineMarkers[1] << 16) & 0xFF0000) |
((m_Info.m_TimelineMarkers.m_aNumTimelineMarkers[2] << 8) & 0xFF00) | (m_Info.m_TimelineMarkers.m_aNumTimelineMarkers[3] & 0xFF);
int Num = bytes_be_to_int(m_Info.m_TimelineMarkers.m_aNumTimelineMarkers);
m_Info.m_Info.m_NumTimelineMarkers = minimum(Num, (int)MAX_TIMELINE_MARKERS);
for(int i = 0; i < m_Info.m_Info.m_NumTimelineMarkers; i++)
{
char *pTimelineMarker = m_Info.m_TimelineMarkers.m_aTimelineMarkers[i];
m_Info.m_Info.m_aTimelineMarkers[i] = ((pTimelineMarker[0] << 24) & 0xFF000000) | ((pTimelineMarker[1] << 16) & 0xFF0000) |
((pTimelineMarker[2] << 8) & 0xFF00) | (pTimelineMarker[3] & 0xFF);
m_Info.m_Info.m_aTimelineMarkers[i] = bytes_be_to_int(m_Info.m_TimelineMarkers.m_aTimelineMarkers[i]);
}
}

Expand All @@ -841,10 +818,10 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
return 0;
}

bool CDemoPlayer::ExtractMap(class IStorage *pStorage)
unsigned char *CDemoPlayer::GetMapData(class IStorage *pStorage)
{
if(!m_MapInfo.m_Size)
return false;
return 0;

long CurSeek = io_tell(m_File);

Expand All @@ -853,6 +830,14 @@ bool CDemoPlayer::ExtractMap(class IStorage *pStorage)
unsigned char *pMapData = (unsigned char *)malloc(m_MapInfo.m_Size);
io_read(m_File, pMapData, m_MapInfo.m_Size);
io_seek(m_File, CurSeek, IOSEEK_START);
return pMapData;
}

bool CDemoPlayer::ExtractMap(class IStorage *pStorage)
{
unsigned char *pMapData = GetMapData(pStorage);
if(!pMapData)
return false;

// handle sha256
SHA256_DIGEST Sha256 = SHA256_ZEROED;
Expand Down Expand Up @@ -1101,7 +1086,7 @@ bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, i
io_read(File, pTimelineMarkers, sizeof(CTimelineMarkers));

str_copy(pMapInfo->m_aName, pDemoHeader->m_aMapName, sizeof(pMapInfo->m_aName));
pMapInfo->m_Crc = (pDemoHeader->m_aMapCrc[0] << 24) | (pDemoHeader->m_aMapCrc[1] << 16) | (pDemoHeader->m_aMapCrc[2] << 8) | (pDemoHeader->m_aMapCrc[3]);
pMapInfo->m_Crc = bytes_be_to_int(pDemoHeader->m_aMapCrc);

SHA256_DIGEST Sha256 = SHA256_ZEROED;
if(pDemoHeader->m_Version >= s_Sha256Version)
Expand All @@ -1122,7 +1107,7 @@ bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, i
}
pMapInfo->m_Sha256 = Sha256;

pMapInfo->m_Size = (pDemoHeader->m_aMapSize[0] << 24) | (pDemoHeader->m_aMapSize[1] << 16) | (pDemoHeader->m_aMapSize[2] << 8) | (pDemoHeader->m_aMapSize[3]);
pMapInfo->m_Size = bytes_be_to_int(pDemoHeader->m_aMapSize);

io_close(File);
return !(mem_comp(pDemoHeader->m_aMarker, s_aHeaderMarker, sizeof(s_aHeaderMarker)) || pDemoHeader->m_Version < s_OldVersion);
Expand Down Expand Up @@ -1170,7 +1155,11 @@ void CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int
Sha256 = pMapInfo->m_Sha256;
}

if(m_pDemoRecorder->Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, &Sha256, pMapInfo->m_Crc, "client", pMapInfo->m_Size, NULL, NULL, pfnFilter, pUser) == -1)
unsigned char *pMapData = m_pDemoPlayer->GetMapData(m_pStorage);
const int Result = m_pDemoRecorder->Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, &Sha256, pMapInfo->m_Crc, "client", pMapInfo->m_Size, pMapData, NULL, pfnFilter, pUser) == -1;
if(pMapData)
free(pMapData);
if(Result != 0)
return;

m_pDemoPlayer->Play();
Expand Down
3 changes: 2 additions & 1 deletion src/engine/shared/demo.h
Expand Up @@ -38,7 +38,7 @@ class CDemoRecorder : public IDemoRecorder
CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData = false);
CDemoRecorder() {}

int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, SHA256_DIGEST *pSha256, unsigned MapCrc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile = 0, DEMOFUNC_FILTER pfnFilter = 0, void *pUser = 0);
int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, SHA256_DIGEST *pSha256, unsigned MapCrc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile = 0, DEMOFUNC_FILTER pfnFilter = 0, void *pUser = 0);
int Stop();
void AddDemoMarker();

Expand Down Expand Up @@ -133,6 +133,7 @@ class CDemoPlayer : public IDemoPlayer
void SetListener(IListener *pListener);

int Load(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType);
unsigned char *GetMapData(class IStorage *pStorage);
bool ExtractMap(class IStorage *pStorage);
int Play();
void Pause();
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/linereader.cpp
Expand Up @@ -4,7 +4,7 @@

void CLineReader::Init(IOHANDLE File)
{
m_BufferMaxSize = sizeof(m_aBuffer);
m_BufferMaxSize = sizeof(m_aBuffer) - 1;
m_BufferSize = 0;
m_BufferPos = 0;
m_File = File;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/linereader.h
Expand Up @@ -7,7 +7,7 @@
// buffered stream for reading lines, should perhaps be something smaller
class CLineReader
{
char m_aBuffer[4 * 8192];
char m_aBuffer[4 * 8192 + 1]; // 1 additional byte for null termination
unsigned m_BufferPos;
unsigned m_BufferSize;
unsigned m_BufferMaxSize;
Expand Down
3 changes: 2 additions & 1 deletion src/engine/shared/protocol_ex.h
Expand Up @@ -20,11 +20,12 @@ enum
UNPACKMESSAGE_OK,
UNPACKMESSAGE_ANSWER,

SERVERCAP_CURVERSION = 3,
SERVERCAP_CURVERSION = 4,
SERVERCAPFLAG_DDNET = 1 << 0,
SERVERCAPFLAG_CHATTIMEOUTCODE = 1 << 1,
SERVERCAPFLAG_ANYPLAYERFLAG = 1 << 2,
SERVERCAPFLAG_PINGEX = 1 << 3,
SERVERCAPFLAG_ALLOWDUMMY = 1 << 4,
};

void RegisterUuids(class CUuidManager *pManager);
Expand Down
8 changes: 4 additions & 4 deletions src/engine/shared/serverinfo.cpp
Expand Up @@ -80,8 +80,8 @@ bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
{
return true;
}
pOut->m_MaxClients = MaxClients;
pOut->m_MaxPlayers = MaxPlayers;
pOut->m_MaxClients = json_int_get(&MaxClients);
pOut->m_MaxPlayers = json_int_get(&MaxPlayers);
pOut->m_Passworded = Passworded;
str_copy(pOut->m_aGameType, GameType, sizeof(pOut->m_aGameType));
str_copy(pOut->m_aName, Name, sizeof(pOut->m_aName));
Expand Down Expand Up @@ -113,8 +113,8 @@ bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
CClient *pClient = &pOut->m_aClients[i];
str_copy(pClient->m_aName, Name, sizeof(pClient->m_aName));
str_copy(pClient->m_aClan, Clan, sizeof(pClient->m_aClan));
pClient->m_Country = Country;
pClient->m_Score = Score;
pClient->m_Country = json_int_get(&Country);
pClient->m_Score = json_int_get(&Score);
pClient->m_IsPlayer = IsPlayer;
}

Expand Down
48 changes: 35 additions & 13 deletions src/engine/shared/snapshot.cpp
Expand Up @@ -38,12 +38,7 @@ int CSnapshot::GetItemType(int Index) const
CSnapshotItem *pTypeItem = GetItem(TypeItemIndex);
CUuid Uuid;
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
{
Uuid.m_aData[i * 4 + 0] = pTypeItem->Data()[i] >> 24;
Uuid.m_aData[i * 4 + 1] = pTypeItem->Data()[i] >> 16;
Uuid.m_aData[i * 4 + 2] = pTypeItem->Data()[i] >> 8;
Uuid.m_aData[i * 4 + 3] = pTypeItem->Data()[i];
}
int_to_bytes_be(&Uuid.m_aData[i * 4], pTypeItem->Data()[i]);

return g_UuidManager.LookupUuid(Uuid);
}
Expand All @@ -59,6 +54,39 @@ int CSnapshot::GetItemIndex(int Key) const
return -1;
}

void *CSnapshot::FindItem(int Type, int ID) const
{
int InternalType = Type;
if(Type >= OFFSET_UUID)
{
CUuid TypeUuid = g_UuidManager.GetUuid(Type);
int aTypeUuidItem[sizeof(CUuid) / 4];
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
aTypeUuidItem[i] = bytes_be_to_int(&TypeUuid.m_aData[i * 4]);

bool Found = false;
for(int i = 0; i < m_NumItems; i++)
{
CSnapshotItem *pItem = GetItem(i);
if(pItem->Type() == 0 && pItem->ID() >= OFFSET_UUID_TYPE) // NETOBJTYPE_EX
{
if(mem_comp(pItem->Data(), aTypeUuidItem, sizeof(CUuid)) == 0)
{
InternalType = pItem->ID();
Found = true;
break;
}
}
}
if(!Found)
{
return nullptr;
}
}
int Index = GetItemIndex((InternalType << 16) | ID);
return Index < 0 ? nullptr : GetItem(Index)->Data();
}

unsigned CSnapshot::Crc()
{
unsigned int Crc = 0;
Expand Down Expand Up @@ -592,13 +620,7 @@ void CSnapshotBuilder::AddExtendedItemType(int Index)
if(pUuidItem)
{
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
{
pUuidItem[i] =
(Uuid.m_aData[i * 4 + 0] << 24) |
(Uuid.m_aData[i * 4 + 1] << 16) |
(Uuid.m_aData[i * 4 + 2] << 8) |
(Uuid.m_aData[i * 4 + 3]);
}
pUuidItem[i] = bytes_be_to_int(&Uuid.m_aData[i * 4]);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/snapshot.h
Expand Up @@ -46,6 +46,7 @@ class CSnapshot
int GetItemSize(int Index) const;
int GetItemIndex(int Key) const;
int GetItemType(int Index) const;
void *FindItem(int Type, int ID) const;

unsigned Crc();
void DebugDump();
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/chat.cpp
Expand Up @@ -898,7 +898,7 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
{
if(Now - m_aLastSoundPlayed[CHAT_CLIENT] >= time_freq() * 3 / 10)
{
bool PlaySound = m_aLines[m_CurrentLine].m_Team ? g_Config.m_SndChat : g_Config.m_SndTeamChat;
bool PlaySound = m_aLines[m_CurrentLine].m_Team ? g_Config.m_SndTeamChat : g_Config.m_SndChat;
#if defined(CONF_VIDEORECORDER)
if(IVideo::Current())
{
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/console.h
Expand Up @@ -27,7 +27,7 @@ class CGameConsole : public CComponent
ColorRGBA m_PrintColor;
char m_aText[1];
};
CStaticRingBuffer<CBacklogEntry, 64 * 1024, CRingBufferBase::FLAG_RECYCLE> m_Backlog;
CStaticRingBuffer<CBacklogEntry, 1024 * 1024, CRingBufferBase::FLAG_RECYCLE> m_Backlog;
CStaticRingBuffer<char, 64 * 1024, CRingBufferBase::FLAG_RECYCLE> m_History;
char *m_pHistoryEntry;

Expand Down
48 changes: 30 additions & 18 deletions src/game/client/components/items.cpp
Expand Up @@ -310,31 +310,32 @@ void CItems::OnRender()
bool BlinkingProj = (Ticks % 20) < 2;
bool BlinkingProjEx = (Ticks % 6) < 2;
bool BlinkingLight = (Ticks % 6) < 2;
int OwnTeam = m_pClient->OwnTeam();
int SwitcherTeam = m_pClient->SwitchStateTeam();
int DraggerStartTick = maximum((Client()->GameTick(g_Config.m_ClDummy) / 7) * 7, Client()->GameTick(g_Config.m_ClDummy) - 4);
int GunStartTick = (Client()->GameTick(g_Config.m_ClDummy) / 7) * 7;

bool UsePredicted = GameClient()->Predict() && GameClient()->AntiPingGunfire();
if(UsePredicted)
{
for(auto *pProj = (CProjectile *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->NextEntity())
{
if(!IsSuper && pProj->m_Number > 0 && pProj->m_Number < Collision()->m_NumSwitchers && !Collision()->m_pSwitchers[pProj->m_Number].m_Status[OwnTeam] && (pProj->m_Explosive ? BlinkingProjEx : BlinkingProj))
if(!IsSuper && pProj->m_Number > 0 && pProj->m_Number < Collision()->m_NumSwitchers + 1 && !Collision()->m_pSwitchers[pProj->m_Number].m_Status[SwitcherTeam] && (pProj->m_Explosive ? BlinkingProjEx : BlinkingProj))
continue;

CProjectileData Data = pProj->GetData();
RenderProjectile(&Data, pProj->ID());
}
for(auto *pLaser = (CLaser *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser *)pLaser->NextEntity())
{
if(pLaser->GetOwner() < 0 || (pLaser->GetOwner() >= 0 && !GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal))
if(pLaser->GetOwner() < 0 || !GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal)
continue;
CNetObj_Laser Data;
pLaser->FillInfo(&Data);
RenderLaser(&Data, true);
}
for(auto *pPickup = (CPickup *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup *)pPickup->NextEntity())
{
if(!IsSuper && pPickup->m_Layer == LAYER_SWITCH && pPickup->m_Number > 0 && pPickup->m_Number < Collision()->m_NumSwitchers && !Collision()->m_pSwitchers[pPickup->m_Number].m_Status[OwnTeam] && BlinkingPickup)
if(!IsSuper && pPickup->m_Layer == LAYER_SWITCH && pPickup->m_Number > 0 && pPickup->m_Number < Collision()->m_NumSwitchers + 1 && !Collision()->m_pSwitchers[pPickup->m_Number].m_Status[SwitcherTeam] && BlinkingPickup)
continue;

if(pPickup->InDDNetTile())
Expand All @@ -358,7 +359,7 @@ void CItems::OnRender()

bool Inactive = false;
if(pEntEx)
Inactive = !IsSuper && pEntEx->m_SwitchNumber > 0 && pEntEx->m_SwitchNumber < Collision()->m_NumSwitchers && !Collision()->m_pSwitchers[pEntEx->m_SwitchNumber].m_Status[OwnTeam];
Inactive = !IsSuper && pEntEx->m_SwitchNumber > 0 && pEntEx->m_SwitchNumber < Collision()->m_NumSwitchers + 1 && !Collision()->m_pSwitchers[pEntEx->m_SwitchNumber].m_Status[SwitcherTeam];

if(Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE)
{
Expand All @@ -375,17 +376,20 @@ void CItems::OnRender()
{
if(auto *pProj = (CProjectile *)GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData))
{
bool IsOtherTeam = m_pClient->IsOtherTeam(pProj->GetOwner());
if(pProj->m_LastRenderTick <= 0 && (pProj->m_Type != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets
&& (pProj->m_Type == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod
&& (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal || IsOtherTeam) // skip locally predicted projectiles
&& !Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID))
if(pProj->GetOwner() >= 0)
{
ReconstructSmokeTrail(&Data, pProj->m_DestroyTick);
bool IsOtherTeam = m_pClient->IsOtherTeam(pProj->GetOwner());
if(pProj->m_LastRenderTick <= 0 && (pProj->m_Type != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets
&& (pProj->m_Type == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod
&& (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal || IsOtherTeam) // skip locally predicted projectiles
&& !Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID))
{
ReconstructSmokeTrail(&Data, pProj->m_DestroyTick);
}
pProj->m_LastRenderTick = Client()->GameTick(g_Config.m_ClDummy);
if(!IsOtherTeam)
continue;
}
pProj->m_LastRenderTick = Client()->GameTick(g_Config.m_ClDummy);
if(!IsOtherTeam)
continue;
}
}
if(Inactive && (Data.m_Explosive ? BlinkingProjEx : BlinkingProj))
Expand Down Expand Up @@ -418,10 +422,18 @@ void CItems::OnRender()

if(pEntEx)
{
if(pEntEx->m_EntityClass == ENTITYCLASS_LIGHT && Inactive && BlinkingLight)
continue;
if(pEntEx->m_EntityClass >= ENTITYCLASS_GUN_NORMAL && pEntEx->m_EntityClass <= ENTITYCLASS_GUN_UNFREEZE && Inactive && BlinkingGun)
continue;
if(pEntEx->m_EntityClass == ENTITYCLASS_LIGHT)
{
if(Inactive && BlinkingLight)
continue;
Laser.m_StartTick = DraggerStartTick;
}
if(pEntEx->m_EntityClass >= ENTITYCLASS_GUN_NORMAL && pEntEx->m_EntityClass <= ENTITYCLASS_GUN_UNFREEZE)
{
if(Inactive && BlinkingGun)
continue;
Laser.m_StartTick = GunStartTick;
}
if(pEntEx->m_EntityClass >= ENTITYCLASS_DRAGGER_WEAK && pEntEx->m_EntityClass <= ENTITYCLASS_DRAGGER_STRONG)
{
if(Inactive && BlinkingDragger)
Expand Down
1 change: 1 addition & 0 deletions src/game/client/components/killmessages.cpp
Expand Up @@ -103,6 +103,7 @@ void CKillMessages::CreateKillmessageNamesIfNotCreated(CKillMsg &Kill)

Kill.m_KillerTextContainerIndex = TextRender()->CreateTextContainer(&Cursor, Kill.m_aKillerName);
}
TextRender()->TextColor(TextRender()->DefaultTextColor());
}

void CKillMessages::OnMessage(int MsgType, void *pRawMsg)
Expand Down
11 changes: 7 additions & 4 deletions src/game/client/components/menus.h
Expand Up @@ -385,14 +385,17 @@ class CMenus : public CComponent

int NumMarkers() const
{
return ((m_TimelineMarkers.m_aNumTimelineMarkers[0] << 24) & 0xFF000000) | ((m_TimelineMarkers.m_aNumTimelineMarkers[1] << 16) & 0xFF0000) |
((m_TimelineMarkers.m_aNumTimelineMarkers[2] << 8) & 0xFF00) | (m_TimelineMarkers.m_aNumTimelineMarkers[3] & 0xFF);
return bytes_be_to_int(m_TimelineMarkers.m_aNumTimelineMarkers);
}

int Length() const
{
return ((m_Info.m_aLength[0] << 24) & 0xFF000000) | ((m_Info.m_aLength[1] << 16) & 0xFF0000) |
((m_Info.m_aLength[2] << 8) & 0xFF00) | (m_Info.m_aLength[3] & 0xFF);
return bytes_be_to_int(m_Info.m_aLength);
}

unsigned Size() const
{
return bytes_be_to_uint(m_Info.m_aMapSize);
}

bool operator<(const CDemoItem &Other) const
Expand Down
9 changes: 4 additions & 5 deletions src/game/client/components/menus_demo.cpp
Expand Up @@ -945,12 +945,11 @@ void CMenus::RenderDemoList(CUIRect MainView)
Labels.HSplitTop(20.0f, &Left, &Labels);
Left.VSplitLeft(150.0f, &Left, &Right);
UI()->DoLabelScaled(&Left, Localize("Size:"), 14.0f, -1);
unsigned Size = (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[0] << 24) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[1] << 16) |
(m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[2] << 8) | (m_lDemos[m_DemolistSelectedIndex].m_Info.m_aMapSize[3]);
if(Size > 1024 * 1024)
str_format(aBuf, sizeof(aBuf), Localize("%.2f MiB"), float(Size) / (1024 * 1024));
const float Size = m_lDemos[m_DemolistSelectedIndex].Size() / 1024.0f;
if(Size > 1024)
str_format(aBuf, sizeof(aBuf), Localize("%.2f MiB"), Size / 1024.0f);
else
str_format(aBuf, sizeof(aBuf), Localize("%.2f KiB"), float(Size) / 1024);
str_format(aBuf, sizeof(aBuf), Localize("%.2f KiB"), Size);
UI()->DoLabelScaled(&Right, aBuf, 14.0f, -1);
Labels.HSplitTop(5.0f, 0, &Labels);
Labels.HSplitTop(20.0f, &Left, &Labels);
Expand Down
6 changes: 5 additions & 1 deletion src/game/client/components/menus_ingame.cpp
Expand Up @@ -64,7 +64,11 @@ void CMenus::RenderGame(CUIRect MainView)

bool DummyConnecting = Client()->DummyConnecting();
static int s_DummyButton = 0;
if(DummyConnecting)
if(!Client()->DummyAllowed())
{
DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button, 0, 15, 5.0f, 0.0f, vec4(1.0f, 0.5f, 0.5f, 0.75f), vec4(1, 0.5f, 0.5f, 0.5f));
}
else if(DummyConnecting)
{
DoButton_Menu(&s_DummyButton, Localize("Connecting dummy"), 1, &Button);
}
Expand Down
14 changes: 7 additions & 7 deletions src/game/client/components/menus_settings.cpp
Expand Up @@ -2118,17 +2118,17 @@ void CMenus::RenderSettingsHUD(CUIRect MainView)
MainView.HSplitTop(5.0f, 0x0, &MainView);

MainView.HSplitTop(50.0f, &Section, &MainView);
Section.VSplitLeft(300.0f, &Section, 0x0);
Section.VSplitLeft(260.0f, &Section, 0x0);
MainView.HSplitTop(25.0f, &SectionTwo, &MainView);

static int LasterOutResetID, LaserInResetID;

ColorHSLA LaserOutlineColor = DoLine_ColorPicker(&LasterOutResetID, 25.0f, 235.0f, 13.0f, 5.0f, &SectionTwo, Localize("Laser Outline Color"), &g_Config.m_ClLaserOutlineColor, ColorRGBA(0.074402f, 0.074402f, 0.247166f, 1.0f), false);
ColorHSLA LaserOutlineColor = DoLine_ColorPicker(&LasterOutResetID, 25.0f, 180.0f, 13.0f, 5.0f, &SectionTwo, Localize("Laser Outline Color"), &g_Config.m_ClLaserOutlineColor, ColorRGBA(0.074402f, 0.074402f, 0.247166f, 1.0f), false);

MainView.HSplitTop(5.0f, 0x0, &MainView);
MainView.HSplitTop(25.0f, &SectionTwo, &MainView);

ColorHSLA LaserInnerColor = DoLine_ColorPicker(&LaserInResetID, 25.0f, 235.0f, 13.0f, 5.0f, &SectionTwo, Localize("Laser Inner Color"), &g_Config.m_ClLaserInnerColor, ColorRGBA(0.498039f, 0.498039f, 1.0f, 1.0f), false);
ColorHSLA LaserInnerColor = DoLine_ColorPicker(&LaserInResetID, 25.0f, 180.0f, 13.0f, 5.0f, &SectionTwo, Localize("Laser Inner Color"), &g_Config.m_ClLaserInnerColor, ColorRGBA(0.498039f, 0.498039f, 1.0f, 1.0f), false);

Section.VSplitLeft(30.0f, 0, &Section);

Expand All @@ -2143,17 +2143,17 @@ void CMenus::RenderSettingsHUD(CUIRect MainView)
MainView.HSplitTop(25.0f, &SectionTwo, &MainView);

static int HookCollNoCollResetID, HookCollHookableCollResetID, HookCollTeeCollResetID;
DoLine_ColorPicker(&HookCollNoCollResetID, 25.0f, 235.0f, 13.0f, 5.0f, &SectionTwo, Localize("No hit"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false);
DoLine_ColorPicker(&HookCollNoCollResetID, 25.0f, 180.0f, 13.0f, 5.0f, &SectionTwo, Localize("No hit"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false);

MainView.HSplitTop(5.0f, 0x0, &MainView);
MainView.HSplitTop(25.0f, &SectionTwo, &MainView);

DoLine_ColorPicker(&HookCollHookableCollResetID, 25.0f, 235.0f, 13.0f, 5.0f, &SectionTwo, Localize("Hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false);
DoLine_ColorPicker(&HookCollHookableCollResetID, 25.0f, 180.0f, 13.0f, 5.0f, &SectionTwo, Localize("Hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false);

MainView.HSplitTop(5.0f, 0x0, &MainView);
MainView.HSplitTop(25.0f, &SectionTwo, &MainView);

DoLine_ColorPicker(&HookCollTeeCollResetID, 25.0f, 235.0f, 13.0f, 5.0f, &SectionTwo, Localize("Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false);
DoLine_ColorPicker(&HookCollTeeCollResetID, 25.0f, 180.0f, 13.0f, 5.0f, &SectionTwo, Localize("Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false);
}
else if(s_CurTab == 1)
{ // ***** CHAT TAB ***** //
Expand Down Expand Up @@ -2370,7 +2370,7 @@ void CMenus::RenderSettingsHUD(CUIRect MainView)
TextRender()->TextEx(&Cursor, "*** Echo command executed", -1);
TextRender()->SetCursorPosition(&Cursor, X, Y);

TextRender()->TextColor(1, 1, 1, 1);
TextRender()->TextColor(TextRender()->DefaultTextColor());
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/game/client/components/skins.cpp
Expand Up @@ -422,7 +422,9 @@ int CSkins::FindImpl(const char *pName)
str_copy(Skin.m_aName, pName, sizeof(Skin.m_aName));

char aUrl[256];
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClSkinDownloadUrl, pName);
char aEscapedName[256];
EscapeUrl(aEscapedName, sizeof(aEscapedName), pName);
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClSkinDownloadUrl, aEscapedName);
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s.%d.tmp", pName, pid());
Skin.m_pTask = std::make_shared<CGetPngFile>(this, Storage(), aUrl, Skin.m_aPath, IStorage::TYPE_SAVE, CTimeout{0, 0, 0}, HTTPLOG::NONE);
m_pClient->Engine()->AddJob(Skin.m_pTask);
Expand Down
64 changes: 40 additions & 24 deletions src/game/client/gameclient.cpp
Expand Up @@ -277,6 +277,8 @@ void CGameClient::OnInit()
m_DDRaceMsgSent[1] = false;
m_ShowOthers[0] = -1;
m_ShowOthers[1] = -1;
m_SwitchStateTeam[0] = -1;
m_SwitchStateTeam[1] = -1;

m_LastZoom = .0;
m_LastScreenAspect = .0;
Expand Down Expand Up @@ -1146,6 +1148,8 @@ void CGameClient::OnNewSnapshot()
#endif

bool FoundGameInfoEx = false;
bool GotSwitchStateTeam = false;
m_SwitchStateTeam[g_Config.m_ClDummy] = -1;

for(auto &Client : m_aClients)
{
Expand Down Expand Up @@ -1409,7 +1413,7 @@ void CGameClient::OnNewSnapshot()
int NumSwitchers = clamp(pSwitchStateData->m_NumSwitchers, 0, 255);
if(!Collision()->m_pSwitchers || NumSwitchers != Collision()->m_NumSwitchers)
{
delete Collision()->m_pSwitchers;
delete[] Collision()->m_pSwitchers;
Collision()->m_pSwitchers = new CCollision::SSwitchers[NumSwitchers + 1];
Collision()->m_NumSwitchers = NumSwitchers;
}
Expand Down Expand Up @@ -1440,6 +1444,12 @@ void CGameClient::OnNewSnapshot()
Collision()->m_pSwitchers[i].m_Type[Team] = TILE_SWITCHCLOSE;
Collision()->m_pSwitchers[i].m_EndTick[Team] = 0;
}

if(!GotSwitchStateTeam)
m_SwitchStateTeam[g_Config.m_ClDummy] = Team;
else
m_SwitchStateTeam[g_Config.m_ClDummy] = -1;
GotSwitchStateTeam = true;
}
}
}
Expand Down Expand Up @@ -2591,11 +2601,11 @@ bool CGameClient::IsOtherTeam(int ClientID)
return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_LocalClientID);
}

int CGameClient::OwnTeam()
int CGameClient::SwitchStateTeam()
{
if(m_Snap.m_LocalClientID < 0)
return 0;
else if(m_aClients[m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW)
if(m_SwitchStateTeam[g_Config.m_ClDummy] >= 0)
return m_SwitchStateTeam[g_Config.m_ClDummy];
else if(m_Snap.m_LocalClientID < 0)
return 0;
else if(m_Snap.m_SpecInfo.m_Active && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
return m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID);
Expand Down Expand Up @@ -3085,37 +3095,43 @@ void CGameClient::SnapCollectEntities()
int NumSnapItems = Client()->SnapNumItems(IClient::SNAP_CURRENT);

std::vector<CSnapEntities> aItemData;
std::vector<CSnapEntities> aItemEx;

for(int Index = 0; Index < NumSnapItems; Index++)
{
IClient::CSnapItem Item;
const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item);
if(Item.m_Type == NETOBJTYPE_ENTITYEX || Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_PICKUP || Item.m_Type == NETOBJTYPE_LASER || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE)
if(Item.m_Type == NETOBJTYPE_ENTITYEX)
aItemEx.push_back({Item, pData, 0});
else if(Item.m_Type == NETOBJTYPE_PICKUP || Item.m_Type == NETOBJTYPE_LASER || Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE)
aItemData.push_back({Item, pData, 0});
}

// sort by id, with non-extended items before extended items of the same id
std::sort(aItemData.begin(), aItemData.end(), [](const CSnapEntities &lhs, const CSnapEntities &rhs) {
if(lhs.m_Item.m_ID == rhs.m_Item.m_ID)
return lhs.m_Item.m_Type != NETOBJTYPE_ENTITYEX;
return lhs.m_Item.m_ID < rhs.m_Item.m_ID;
});
// sort by id
class CEntComparer
{
public:
bool operator()(const CSnapEntities &lhs, const CSnapEntities &rhs) const
{
return lhs.m_Item.m_ID < rhs.m_Item.m_ID;
}
};

std::sort(aItemData.begin(), aItemData.end(), CEntComparer());
std::sort(aItemEx.begin(), aItemEx.end(), CEntComparer());

// merge extended items with items they belong to
m_aSnapEntities.clear();
for(size_t Index = 0; Index < aItemData.size(); Index++)
{
if(aItemData[Index].m_Item.m_Type == NETOBJTYPE_ENTITYEX)
continue;

const IClient::CSnapItem Item = aItemData[Index].m_Item;
const void *pData = (const void *)aItemData[Index].m_pData;
size_t IndexEx = 0;
for(const CSnapEntities &Ent : aItemData)
{
const CNetObj_EntityEx *pDataEx = 0;
while(IndexEx < aItemEx.size() && aItemEx[IndexEx].m_Item.m_ID < Ent.m_Item.m_ID)
IndexEx++;
if(IndexEx < aItemEx.size() && aItemEx[IndexEx].m_Item.m_ID == Ent.m_Item.m_ID)
pDataEx = (const CNetObj_EntityEx *)aItemEx[IndexEx].m_pData;

if(Index + 1 < aItemData.size() && aItemData[Index + 1].m_Item.m_ID == Item.m_ID && aItemData[Index + 1].m_Item.m_Type == NETOBJTYPE_ENTITYEX)
{
pDataEx = (const CNetObj_EntityEx *)aItemData[Index + 1].m_pData;
Index++;
}
m_aSnapEntities.push_back({Item, pData, pDataEx});
m_aSnapEntities.push_back({Ent.m_Item, Ent.m_pData, pDataEx});
}
}
3 changes: 2 additions & 1 deletion src/game/client/gameclient.h
Expand Up @@ -520,7 +520,7 @@ class CGameClient : public IGameClient
void DummyResetInput();
void Echo(const char *pString);
bool IsOtherTeam(int ClientID);
int OwnTeam();
int SwitchStateTeam();
bool IsLocalCharSuper();
bool CanDisplayWarning();
bool IsDisplayingWarning();
Expand Down Expand Up @@ -657,6 +657,7 @@ class CGameClient : public IGameClient
int m_IsDummySwapping;
CCharOrder m_CharOrder;
class CCharacter m_aLastWorldCharacters[MAX_CLIENTS];
int m_SwitchStateTeam[NUM_DUMMIES];

enum
{
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/prediction/entities/pickup.cpp
Expand Up @@ -17,7 +17,7 @@ void CPickup::Tick()
{
if(GameWorld()->m_WorldConfig.m_IsVanilla && distance(m_Pos, pChr->m_Pos) >= 20.0f * 2) // pickup distance is shorter on vanilla due to using ClosestEntity
continue;
if(m_Layer == LAYER_SWITCH && m_Number > 0 && m_Number < Collision()->m_NumSwitchers && !GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[pChr->Team()])
if(m_Layer == LAYER_SWITCH && m_Number > 0 && m_Number < Collision()->m_NumSwitchers + 1 && !GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[pChr->Team()])
continue;
bool sound = false;
// player picked us up, is someone was hooking us, let them go
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/prediction/entities/projectile.cpp
Expand Up @@ -110,7 +110,7 @@ void CProjectile::Tick()
CCharacter *apEnts[MAX_CLIENTS];
int Num = GameWorld()->FindEntities(CurPos, 1.0f, (CEntity **)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
for(int i = 0; i < Num; ++i)
if(apEnts[i] && (m_Layer != LAYER_SWITCH || (m_Layer == LAYER_SWITCH && m_Number > 0 && m_Number < Collision()->m_NumSwitchers && GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[apEnts[i]->Team()])))
if(apEnts[i] && (m_Layer != LAYER_SWITCH || (m_Layer == LAYER_SWITCH && m_Number > 0 && m_Number < Collision()->m_NumSwitchers + 1 && GameWorld()->Collision()->m_pSwitchers[m_Number].m_Status[apEnts[i]->Team()])))
apEnts[i]->Freeze();
}
if(Collide && m_Bouncing != 0)
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/projectile_data.cpp
Expand Up @@ -49,7 +49,7 @@ CProjectileData ExtractProjectileInfoDDNet(const CNetObj_DDNetProjectile *pProj,

Result.m_ExtraInfo = true;
Result.m_Owner = pProj->m_Data & 255;
if(pProj->m_Data & PROJECTILEFLAG_NO_OWNER)
if(pProj->m_Data & PROJECTILEFLAG_NO_OWNER || Result.m_Owner < 0 || Result.m_Owner >= MAX_CLIENTS)
{
Result.m_Owner = -1;
}
Expand Down
30 changes: 21 additions & 9 deletions src/game/client/ui_ex.cpp
Expand Up @@ -39,8 +39,18 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi

FontSize *= UI()->Scale();

auto &&SetHasSelection = [&](bool HasSelection) {
m_HasSelection = HasSelection;
m_pSelItem = m_HasSelection ? pID : nullptr;
};

if(UI()->LastActiveItem() == pID)
{
if(m_HasSelection && m_pSelItem != pID)
{
SetHasSelection(false);
}

m_CurCursor = minimum(str_length(pStr), m_CurCursor);

bool IsShiftPressed = Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT);
Expand All @@ -64,7 +74,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
{
OffsetL = UTF8SelLeft;
OffsetR = UTF8SelRight;
m_HasSelection = false;
SetHasSelection(false);
}
}

Expand Down Expand Up @@ -119,7 +129,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
{
NewStr = std::string(pStr, UTF8SelLeft) + std::string(pStr + UTF8SelRight);
str_copy(pStr, NewStr.c_str(), StrSize);
m_HasSelection = false;
SetHasSelection(false);
if(m_CurCursor > UTF8SelLeft)
m_CurCursor = maximum(0, m_CurCursor - (UTF8SelRight - UTF8SelLeft));
else
Expand All @@ -136,7 +146,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
m_CurSelStart = 0;
int StrLen = str_length(pStr);
TextRender()->UTF8OffToDecodedOff(pStr, StrLen, m_CurSelEnd);
m_HasSelection = true;
SetHasSelection(true);
m_CurCursor = StrLen;
}

Expand Down Expand Up @@ -183,7 +193,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
{
OffsetL = UTF8SelLeft;
OffsetR = UTF8SelRight;
m_HasSelection = false;
SetHasSelection(false);
}

std::string NewStr(pStr, OffsetL);
Expand Down Expand Up @@ -220,9 +230,9 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
}
}
if(m_CurSelStart == m_CurSelEnd)
m_HasSelection = false;
SetHasSelection(false);
else
m_HasSelection = true;
SetHasSelection(true);
}
else
{
Expand All @@ -243,7 +253,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
TextRender()->DecodedOffToUTF8Off(pStr, m_CurSelStart, m_CurCursor);
}
}
m_HasSelection = false;
SetHasSelection(false);
}
}
}
Expand Down Expand Up @@ -302,9 +312,11 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
UpdateOffset = true;
}

bool IsEmptyText = false;
if(pDisplayStr[0] == '\0')
{
pDisplayStr = pEmptyText;
IsEmptyText = true;
TextRender()->TextColor(1, 1, 1, 0.75f);
}

Expand Down Expand Up @@ -372,7 +384,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
m_MouseCurX = UI()->MouseX();
m_MouseCurY = UI()->MouseY();
}
HasMouseSel = m_MouseIsPress;
HasMouseSel = m_MouseIsPress && !IsEmptyText;
if(m_MouseIsPress && UI()->MouseButtonReleased(0))
{
m_MouseIsPress = false;
Expand Down Expand Up @@ -402,7 +414,7 @@ int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSi
{
m_CurSelStart = SelCursor.m_SelectionStart;
m_CurSelEnd = SelCursor.m_SelectionEnd;
m_HasSelection = m_CurSelStart != m_CurSelEnd;
SetHasSelection(m_CurSelStart != m_CurSelEnd);
}
if(SelCursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE)
{
Expand Down
1 change: 1 addition & 0 deletions src/game/client/ui_ex.h
Expand Up @@ -34,6 +34,7 @@ class CUIEx
int m_MouseCurY = 0;
int m_CurSelStart = 0;
int m_CurSelEnd = 0;
void *m_pSelItem = nullptr;

int m_CurCursor = 0;

Expand Down
19 changes: 17 additions & 2 deletions src/game/editor/editor.cpp
Expand Up @@ -2668,6 +2668,20 @@ void CEditor::DoMapEditor(CUIRect View)
UI()->SetActiveItem(0);
}
}
if(!Input()->KeyIsPressed(KEY_LSHIFT) && !Input()->KeyIsPressed(KEY_RSHIFT) &&
!Input()->KeyIsPressed(KEY_LCTRL) && !Input()->KeyIsPressed(KEY_RCTRL) &&
m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)
{
float PanSpeed = 64.0f;
if(Input()->KeyPress(KEY_A))
m_WorldOffsetX -= PanSpeed * m_WorldZoom;
else if(Input()->KeyPress(KEY_D))
m_WorldOffsetX += PanSpeed * m_WorldZoom;
if(Input()->KeyPress(KEY_W))
m_WorldOffsetY -= PanSpeed * m_WorldZoom;
else if(Input()->KeyPress(KEY_S))
m_WorldOffsetY += PanSpeed * m_WorldZoom;
}
}
else if(UI()->ActiveItem() == s_pEditorID)
{
Expand Down Expand Up @@ -5939,13 +5953,14 @@ void CEditor::Render()
if(Zoom != 0)
{
float OldLevel = m_ZoomLevel;
m_ZoomLevel = clamp(m_ZoomLevel + Zoom * 20, 10, 2000);
m_ZoomLevel = maximum(m_ZoomLevel + Zoom * 20, 10);
if(g_Config.m_ClLimitMaxZoomLevel)
m_ZoomLevel = minimum(m_ZoomLevel, 2000);
if(g_Config.m_EdZoomTarget)
ZoomMouseTarget((float)m_ZoomLevel / OldLevel);
}
}

m_ZoomLevel = clamp(m_ZoomLevel, 10, 2000);
m_WorldZoom = m_ZoomLevel / 100.0f;

if(m_GuiActive)
Expand Down
39 changes: 34 additions & 5 deletions src/game/server/alloc.h
Expand Up @@ -6,6 +6,25 @@
#include <new>

#include <base/system.h>
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#if __has_feature(address_sanitizer)
#include <sanitizer/asan_interface.h>
#else
#define ASAN_POISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
#endif

#if __cplusplus >= 201703L
#define MAYBE_UNUSED [[maybe_unused]]
#elif defined(__GNUC__)
#define MAYBE_UNUSED __attribute__((unused))
#else
#define MAYBE_UNUSED
#endif

#define MACRO_ALLOC_HEAP() \
public: \
Expand All @@ -30,16 +49,24 @@ public: \
\
private:

#if __has_feature(address_sanitizer)
#define MACRO_ALLOC_GET_SIZE(POOLTYPE) ((sizeof(POOLTYPE) + 7) & ~7)
#else
#define MACRO_ALLOC_GET_SIZE(POOLTYPE) (sizeof(POOLTYPE))
#endif

#define MACRO_ALLOC_POOL_ID_IMPL(POOLTYPE, PoolSize) \
static char ms_PoolData##POOLTYPE[PoolSize][sizeof(POOLTYPE)] = {{0}}; \
static char ms_PoolData##POOLTYPE[PoolSize][MACRO_ALLOC_GET_SIZE(POOLTYPE)] = {{0}}; \
static int ms_PoolUsed##POOLTYPE[PoolSize] = {0}; \
MAYBE_UNUSED static int ms_PoolDummy##POOLTYPE = (ASAN_POISON_MEMORY_REGION(ms_PoolData##POOLTYPE, sizeof(ms_PoolData##POOLTYPE)), 0); \
void *POOLTYPE::operator new(size_t Size, int id) \
{ \
dbg_assert(sizeof(POOLTYPE) == Size, "size error"); \
dbg_assert(sizeof(POOLTYPE) >= Size, "size error"); \
dbg_assert(!ms_PoolUsed##POOLTYPE[id], "already used"); \
/*dbg_msg("pool", "++ %s %d", #POOLTYPE, id);*/ \
ASAN_UNPOISON_MEMORY_REGION(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
ms_PoolUsed##POOLTYPE[id] = 1; \
mem_zero(ms_PoolData##POOLTYPE[id], Size); \
mem_zero(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
return ms_PoolData##POOLTYPE[id]; \
} \
void POOLTYPE::operator delete(void *p, int id) \
Expand All @@ -48,15 +75,17 @@ public: \
dbg_assert(id == (POOLTYPE *)p - (POOLTYPE *)ms_PoolData##POOLTYPE, "invalid id"); \
/*dbg_msg("pool", "-- %s %d", #POOLTYPE, id);*/ \
ms_PoolUsed##POOLTYPE[id] = 0; \
mem_zero(ms_PoolData##POOLTYPE[id], sizeof(POOLTYPE)); \
mem_zero(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
ASAN_POISON_MEMORY_REGION(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
} \
void POOLTYPE::operator delete(void *p) /* NOLINT(misc-new-delete-overloads) */ \
{ \
int id = (POOLTYPE *)p - (POOLTYPE *)ms_PoolData##POOLTYPE; \
dbg_assert(ms_PoolUsed##POOLTYPE[id], "not used"); \
/*dbg_msg("pool", "-- %s %d", #POOLTYPE, id);*/ \
ms_PoolUsed##POOLTYPE[id] = 0; \
mem_zero(ms_PoolData##POOLTYPE[id], sizeof(POOLTYPE)); \
mem_zero(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
ASAN_POISON_MEMORY_REGION(ms_PoolData##POOLTYPE[id], sizeof(ms_PoolData##POOLTYPE[id])); \
}

#endif
4 changes: 3 additions & 1 deletion src/game/server/ddracechat.cpp
Expand Up @@ -39,7 +39,9 @@ void CGameContext::ConCredits(IConsole::IResult *pResult, void *pUserData)
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "credits",
"trafilaw, Zwelf, Patiga, Konsti, ElXreno, MikiGamer,");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "credits",
"Fireball, Banana090, axblk, yangfl & others.");
"Fireball, Banana090, axblk, yangfl, nobody-mb, Kaffeine,");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "credits",
"Zodiac & others.");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "credits",
"Based on DDRace by the DDRace developers,");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "credits",
Expand Down
16 changes: 8 additions & 8 deletions src/game/server/entities/door.cpp
Expand Up @@ -45,14 +45,6 @@ void CDoor::Snap(int SnappingClient)
if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, m_To))
return;

CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(!pEntData)
return;

pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = ENTITYCLASS_DOOR;

CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));

Expand All @@ -64,8 +56,16 @@ void CDoor::Snap(int SnappingClient)

int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;

CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));

if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = ENTITYCLASS_DOOR;

pObj->m_FromX = (int)m_To.x;
pObj->m_FromY = (int)m_To.y;
pObj->m_StartTick = 0;
Expand Down
160 changes: 87 additions & 73 deletions src/game/server/entities/dragger.cpp
Expand Up @@ -16,7 +16,7 @@ CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
int CaughtTeam, int Layer, int Number) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_Target = 0;
m_TargetID = -1;
m_Layer = Layer;
m_Number = Number;
m_Pos = Pos;
Expand All @@ -30,34 +30,50 @@ CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
{
SoloID = -1;
}

for(int &SoloEntID : m_SoloEntIDs)
{
SoloEntID = -1;
}
}

void CDragger::Move()
{
if(m_Target && (!m_Target->IsAlive() || (m_Target->IsAlive() && (m_Target->m_Super || m_Target->IsPaused() || (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[m_Target->Team()])))))
m_Target = 0;
if(m_TargetID >= 0)
{
CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);
if(!pTarget || pTarget->m_Super || pTarget->IsPaused() || (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pTarget->Team()]))
{
m_TargetID = -1;
}
}

mem_zero(m_SoloEnts, sizeof(m_SoloEnts));
CCharacter *TempEnts[MAX_CLIENTS];
mem_zero(TempEnts, sizeof(TempEnts));

for(int &SoloEntID : m_SoloEntIDs)
{
SoloEntID = -1;
}

int Num = GameServer()->m_World.FindEntities(m_Pos, g_Config.m_SvDraggerRange,
(CEntity **)m_SoloEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
mem_copy(TempEnts, m_SoloEnts, sizeof(TempEnts));
(CEntity **)TempEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);

int Id = -1;
int MinLen = 0;
CCharacter *Temp;
for(int i = 0; i < Num; i++)
{
Temp = m_SoloEnts[i];
Temp = TempEnts[i];
m_SoloEntIDs[i] = Temp->GetPlayer()->GetCID();
if(Temp->Team() != m_CaughtTeam)
{
m_SoloEnts[i] = 0;
m_SoloEntIDs[i] = -1;
continue;
}
if(m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Temp->Team()])
{
m_SoloEnts[i] = 0;
m_SoloEntIDs[i] = -1;
continue;
}
int Res =
Expand All @@ -75,61 +91,62 @@ void CDragger::Move()
}

if(!Temp->Teams()->m_Core.GetSolo(Temp->GetPlayer()->GetCID()))
m_SoloEnts[i] = 0;
m_SoloEntIDs[i] = -1;
}
else
{
m_SoloEnts[i] = 0;
m_SoloEntIDs[i] = -1;
}
}

if(!m_Target)
m_Target = Id != -1 ? TempEnts[Id] : 0;
if(m_TargetID < 0)
m_TargetID = Id != -1 ? TempEnts[Id]->GetPlayer()->GetCID() : -1;

if(m_Target)
if(m_TargetID >= 0)
{
for(auto &SoloEnt : m_SoloEnts)
const CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);
for(auto &SoloEntID : m_SoloEntIDs)
{
if(SoloEnt == m_Target)
SoloEnt = 0;
if(GameServer()->GetPlayerChar(SoloEntID) == pTarget)
SoloEntID = -1;
}
}
}

void CDragger::Drag()
{
if(!m_Target)
if(m_TargetID < 0)
return;

CCharacter *Target = m_Target;
CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);

for(int i = -1; i < MAX_CLIENTS; i++)
{
if(i >= 0)
Target = m_SoloEnts[i];
pTarget = GameServer()->GetPlayerChar(m_SoloEntIDs[i]);

if(!Target)
if(!pTarget)
continue;

int Res = 0;
if(!m_NW)
Res = GameServer()->Collision()->IntersectNoLaser(m_Pos,
Target->m_Pos, 0, 0);
pTarget->m_Pos, 0, 0);
else
Res = GameServer()->Collision()->IntersectNoLaserNW(m_Pos,
Target->m_Pos, 0, 0);
if(Res || length(m_Pos - Target->m_Pos) > g_Config.m_SvDraggerRange)
pTarget->m_Pos, 0, 0);
if(Res || length(m_Pos - pTarget->m_Pos) > g_Config.m_SvDraggerRange)
{
Target = 0;
pTarget = 0;
if(i == -1)
m_Target = 0;
m_TargetID = -1;
else
m_SoloEnts[i] = 0;
m_SoloEntIDs[i] = -1;
}
else if(length(m_Pos - Target->m_Pos) > 28)
else if(length(m_Pos - pTarget->m_Pos) > 28)
{
vec2 Temp = Target->Core()->m_Vel + (normalize(m_Pos - Target->m_Pos) * m_Strength);
Target->Core()->m_Vel = ClampVel(Target->m_MoveRestrictions, Temp);
vec2 Temp = pTarget->Core()->m_Vel + (normalize(m_Pos - pTarget->m_Pos) * m_Strength);
pTarget->Core()->m_Vel = ClampVel(pTarget->m_MoveRestrictions, Temp);
}
}
}
Expand Down Expand Up @@ -165,9 +182,32 @@ void CDragger::Snap(int SnappingClient)
if(((CGameControllerDDRace *)GameServer()->m_pController)->m_Teams.GetTeamState(m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
return;

if(NetworkClipped(SnappingClient, m_Pos))
return;

CCharacter *pChar = GameServer()->GetPlayerChar(SnappingClient);

if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
pChar = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);

if(pChar && pChar->Team() != m_CaughtTeam)
return;

int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;

CCharacter *Target = m_Target;
CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
{
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = clamp(ENTITYCLASS_DRAGGER_WEAK + round_to_int(m_Strength) - 1, (int)ENTITYCLASS_DRAGGER_WEAK, (int)ENTITYCLASS_DRAGGER_STRONG);
}
}

CCharacter *pTarget = m_TargetID < 0 ? 0 : GameServer()->GetPlayerChar(m_TargetID);

for(int &SoloID : m_SoloIDs)
{
Expand All @@ -184,45 +224,27 @@ void CDragger::Snap(int SnappingClient)
{
if(i >= 0)
{
Target = m_SoloEnts[i];
pTarget = GameServer()->GetPlayerChar(m_SoloEntIDs[i]);

if(!Target)
if(!pTarget)
continue;
}

if(Target)
{
if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, Target->m_Pos))
continue;
}
else if(NetworkClipped(SnappingClient, m_Pos))
if(pTarget && NetworkClipped(SnappingClient, pTarget->m_Pos))
continue;

CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);

if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);

if(i != -1 || SnappingClientVersion < VERSION_DDNET_SWITCH)
{
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick)))
if(pChar && m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pChar->Team()] && (!Tick))
continue;
}

if(Char && Char->IsAlive())
{
if(Char->Team() != m_CaughtTeam)
continue;
}
else
{
// send to spectators only active draggers and some inactive from team 0
if(!((Target && Target->IsAlive()) || m_CaughtTeam == 0))
continue;
}
// send to spectators only active draggers and some inactive from team 0
if(!pChar && !pTarget && m_CaughtTeam != 0)
continue;

if(Char && Char->IsAlive() && Target && Target->IsAlive() && Target->GetPlayer()->GetCID() != Char->GetPlayer()->GetCID() && ((Char->GetPlayer()->m_ShowOthers == 0 && (Char->Teams()->m_Core.GetSolo(SnappingClient) || Char->Teams()->m_Core.GetSolo(Target->GetPlayer()->GetCID()))) || (Char->GetPlayer()->m_ShowOthers == 2 && !Target->SameTeam(SnappingClient))))
if(pChar && pTarget && pTarget->GetPlayer()->GetCID() != pChar->GetPlayer()->GetCID() && ((pChar->GetPlayer()->m_ShowOthers == 0 && (pChar->Teams()->m_Core.GetSolo(SnappingClient) || pChar->Teams()->m_Core.GetSolo(pTarget->GetPlayer()->GetCID()))) || (pChar->GetPlayer()->m_ShowOthers == 2 && !pTarget->SameTeam(SnappingClient))))
{
continue;
}
Expand All @@ -233,14 +255,6 @@ void CDragger::Snap(int SnappingClient)
{
obj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));

CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(!pEntData)
return;

pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = clamp(ENTITYCLASS_DRAGGER_WEAK + round_to_int(m_Strength) - 1, (int)ENTITYCLASS_DRAGGER_WEAK, (int)ENTITYCLASS_DRAGGER_STRONG);
}
else
{
Expand All @@ -254,18 +268,22 @@ void CDragger::Snap(int SnappingClient)
continue;
obj->m_X = (int)m_Pos.x;
obj->m_Y = (int)m_Pos.y;
if(Target)
if(pTarget)
{
obj->m_FromX = (int)Target->m_Pos.x;
obj->m_FromY = (int)Target->m_Pos.y;
obj->m_FromX = (int)pTarget->m_Pos.x;
obj->m_FromY = (int)pTarget->m_Pos.y;
}
else
{
obj->m_FromX = (int)m_Pos.x;
obj->m_FromY = (int)m_Pos.y;
}

if(i != -1 || SnappingClientVersion < VERSION_DDNET_SWITCH)
if(pEntData && i == -1)
{
obj->m_StartTick = 0;
}
else
{
int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
Expand All @@ -274,10 +292,6 @@ void CDragger::Snap(int SnappingClient)
StartTick = Server()->Tick();
obj->m_StartTick = StartTick;
}
else
{
obj->m_StartTick = 0;
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/game/server/entities/dragger.h
Expand Up @@ -12,11 +12,11 @@ class CDragger : public CEntity
int m_EvalTick;
void Move();
void Drag();
CCharacter *m_Target;
int m_TargetID;
bool m_NW;
int m_CaughtTeam;

CCharacter *m_SoloEnts[MAX_CLIENTS];
int m_SoloEntIDs[MAX_CLIENTS];
int m_SoloIDs[MAX_CLIENTS];

public:
Expand Down
45 changes: 26 additions & 19 deletions src/game/server/entities/gun.cpp
Expand Up @@ -106,7 +106,7 @@ void CGun::Tick()
}
m_Pos += m_Core;
}
if(m_LastFire + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec <= Server()->Tick())
if(g_Config.m_SvPlasmaPerSec > 0 && m_LastFire + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec <= Server()->Tick())
Fire();
}

Expand All @@ -115,30 +115,33 @@ void CGun::Snap(int SnappingClient)
if(NetworkClipped(SnappingClient))
return;

CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(!pEntData)
return;

pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;

if(m_Explosive && !m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_NORMAL;
else if(m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_EXPLOSIVE;
else if(!m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_FREEZE;
else
pEntData->m_EntityClass = ENTITYCLASS_GUN_UNFREEZE;

CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);

if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);

int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
if(SnappingClientVersion < VERSION_DDNET_SWITCH)

CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));

if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;

if(m_Explosive && !m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_NORMAL;
else if(m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_EXPLOSIVE;
else if(!m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_FREEZE;
else
pEntData->m_EntityClass = ENTITYCLASS_GUN_UNFREEZE;
}
else
{
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) && (!Tick))
Expand All @@ -154,5 +157,9 @@ void CGun::Snap(int SnappingClient)
pObj->m_Y = (int)m_Pos.y;
pObj->m_FromX = (int)m_Pos.x;
pObj->m_FromY = (int)m_Pos.y;
pObj->m_StartTick = m_EvalTick;

if(pEntData)
pObj->m_StartTick = 0;
else
pObj->m_StartTick = m_EvalTick;
}
38 changes: 24 additions & 14 deletions src/game/server/entities/light.cpp
Expand Up @@ -105,21 +105,24 @@ void CLight::Snap(int SnappingClient)
if(NetworkClipped(SnappingClient, m_Pos) && NetworkClipped(SnappingClient, m_To))
return;

CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(!pEntData)
return;
int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;

pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = ENTITYCLASS_LIGHT;
CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH && (m_Layer == LAYER_SWITCH || length(m_Core) > 0))
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));

CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);

if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);

int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
if(SnappingClientVersion < VERSION_DDNET_SWITCH)
if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = ENTITYCLASS_LIGHT;
}
else
{
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 6;
if(Char && Char->IsAlive() && m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && Tick)
Expand Down Expand Up @@ -156,10 +159,17 @@ void CLight::Snap(int SnappingClient)
pObj->m_FromY = (int)m_Pos.y;
}

int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
StartTick = Server()->Tick() - 4;
else if(StartTick > Server()->Tick())
StartTick = Server()->Tick();
pObj->m_StartTick = StartTick;
if(pEntData)
{
pObj->m_StartTick = 0;
}
else
{
int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
StartTick = Server()->Tick() - 4;
else if(StartTick > Server()->Tick())
StartTick = Server()->Tick();
pObj->m_StartTick = StartTick;
}
}
20 changes: 10 additions & 10 deletions src/game/server/entities/pickup.cpp
Expand Up @@ -152,27 +152,27 @@ void CPickup::TickPaused()

void CPickup::Snap(int SnappingClient)
{
/*if(m_SpawnTick != -1 || NetworkClipped(SnappingClient))
return;*/
if(NetworkClipped(SnappingClient))
return;

CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);

if(SnappingClient > -1 && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == -1 || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);

if(!NetworkClipped(SnappingClient))
{
CNetObj_EntityEx *pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(!pEntData)
return;
int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;

CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH && (m_Layer == LAYER_SWITCH || length(m_Core) > 0))
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));

if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = ENTITYCLASS_PICKUP;
}

int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
if(SnappingClientVersion < VERSION_DDNET_SWITCH)
else
{
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(Char && Char->IsAlive() && m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && !Tick)
Expand Down
5 changes: 5 additions & 0 deletions src/game/server/gamecontext.cpp
Expand Up @@ -2423,6 +2423,11 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)

// set start infos
Server()->SetClientName(ClientID, pMsg->m_pName);
// trying to set client name can delete the player object, check if it still exists
if(!m_apPlayers[ClientID])
{
return;
}
Server()->SetClientClan(ClientID, pMsg->m_pClan);
Server()->SetClientCountry(ClientID, pMsg->m_Country);
str_copy(pPlayer->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_SkinName));
Expand Down
4 changes: 4 additions & 0 deletions src/game/server/gamecontroller.cpp
Expand Up @@ -632,6 +632,10 @@ void IGameController::Snap(int SnappingClient)
if(GameServer()->Collision()->m_pSwitchers)
{
int Team = pPlayer && pPlayer->GetCharacter() ? pPlayer->GetCharacter()->Team() : 0;

if(pPlayer && (pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[pPlayer->m_SpectatorID] && GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter())
Team = GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()->Team();

CNetObj_SwitchState *pSwitchState = static_cast<CNetObj_SwitchState *>(Server()->SnapNewItem(NETOBJTYPE_SWITCHSTATE, Team, sizeof(CNetObj_SwitchState)));
if(!pSwitchState)
return;
Expand Down
16 changes: 14 additions & 2 deletions src/game/server/gameworld.cpp
Expand Up @@ -106,13 +106,25 @@ void CGameWorld::RemoveEntity(CEntity *pEnt)
//
void CGameWorld::Snap(int SnappingClient)
{
for(auto *pEnt : m_apFirstEntityTypes)
for(; pEnt;)
for(CEntity *pEnt = m_apFirstEntityTypes[ENTTYPE_CHARACTER]; pEnt;)
{
m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
pEnt->Snap(SnappingClient);
pEnt = m_pNextTraverseEntity;
}

for(int i = 0; i < NUM_ENTTYPES; i++)
{
if(i == ENTTYPE_CHARACTER)
continue;

for(CEntity *pEnt = m_apFirstEntityTypes[i]; pEnt;)
{
m_pNextTraverseEntity = pEnt->m_pNextTypeEntity;
pEnt->Snap(SnappingClient);
pEnt = m_pNextTraverseEntity;
}
}
}

void CGameWorld::Reset()
Expand Down
22 changes: 13 additions & 9 deletions src/game/server/player.cpp
Expand Up @@ -165,21 +165,25 @@ static int PlayerFlags_SixToSeven(int Flags)

void CPlayer::Tick()
{
#ifdef CONF_DEBUG
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS - g_Config.m_DbgDummies)
#endif
if(m_ScoreQueryResult != nullptr && m_ScoreQueryResult->m_Completed)
{
ProcessScoreResult(*m_ScoreQueryResult);
m_ScoreQueryResult = nullptr;
}
if(m_ScoreQueryResult != nullptr && m_ScoreQueryResult->m_Completed)
{
ProcessScoreResult(*m_ScoreQueryResult);
m_ScoreQueryResult = nullptr;
}
if(m_ScoreFinishResult != nullptr && m_ScoreFinishResult->m_Completed)
{
ProcessScoreResult(*m_ScoreFinishResult);
m_ScoreFinishResult = nullptr;
}

if(!Server()->ClientIngame(m_ClientID))
bool ClientIngame = Server()->ClientIngame(m_ClientID);
#ifdef CONF_DEBUG
if(g_Config.m_DbgDummies && m_ClientID >= MAX_CLIENTS - g_Config.m_DbgDummies)
{
ClientIngame = true;
}
#endif
if(!ClientIngame)
return;

if(m_ChatScore > 0)
Expand Down
13 changes: 8 additions & 5 deletions src/game/server/teams.cpp
Expand Up @@ -442,17 +442,20 @@ void CGameTeams::ChangeTeamState(int Team, int State)
m_TeamState[Team] = State;
}

void CGameTeams::KillTeam(int Team, int NewStrongID)
void CGameTeams::KillTeam(int Team, int NewStrongID, int ExceptID)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
if(NewStrongID != -1 && i != NewStrongID)
if(i != ExceptID)
{
GameServer()->m_apPlayers[i]->Respawn(true); // spawn the rest of team with weak hook on the killer
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
if(NewStrongID != -1 && i != NewStrongID)
{
GameServer()->m_apPlayers[i]->Respawn(true); // spawn the rest of team with weak hook on the killer
}
}
}
}
Expand Down Expand Up @@ -1006,7 +1009,7 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon)

m_Practice[Team] = false;

KillTeam(Team, Weapon == WEAPON_SELF ? ClientID : -1);
KillTeam(Team, Weapon == WEAPON_SELF ? ClientID : -1, ClientID);
if(Count(Team) > 1)
{
GameServer()->SendChatTeam(Team, aBuf);
Expand Down
11 changes: 7 additions & 4 deletions src/game/server/teams.h
Expand Up @@ -35,10 +35,13 @@ class CGameTeams

class CGameContext *m_pGameContext;

// Kill the whole team, making the player `NewStrongID` have strong
// hook on everyone else. `NewStrongID` can be -1 to get the normal
// spawning order.
void KillTeam(int Team, int NewStrongID);
/**
* Kill the whole team.
* @param Team The team id to kill
* @param NewStrongID The player with that id will get strong hook on everyone else, -1 will set the normal spawning order
* @param ExceptID The player that should not get killed
*/
void KillTeam(int Team, int NewStrongID, int ExceptID = -1);
bool TeamFinished(int Team);
void OnTeamFinish(CPlayer **Players, unsigned int Size, float Time, const char *pTimestamp);
void OnFinish(CPlayer *Player, float Time, const char *pTimestamp);
Expand Down
4 changes: 2 additions & 2 deletions src/game/version.h
Expand Up @@ -3,11 +3,11 @@
#ifndef GAME_VERSION_H
#define GAME_VERSION_H
#ifndef GAME_RELEASE_VERSION
#define GAME_RELEASE_VERSION "15.5.4"
#define GAME_RELEASE_VERSION "15.7"
#endif
#define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
#define CLIENT_VERSIONNR 15054
#define CLIENT_VERSIONNR 15070
extern const char *GIT_SHORTREV_HASH;
#define GAME_NAME "DDNet"
#endif