4 changes: 2 additions & 2 deletions src/engine/client/discord.cpp
Expand Up @@ -64,7 +64,7 @@ class CDiscord : public IDiscord
{
m_pActivityManager->clear_activity(m_pActivityManager, 0, 0);
}
void SetGameInfo(NETADDR ServerAddr, const char *pMapName) override
void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) override
{
DiscordActivity Activity;
mem_zero(&Activity, sizeof(DiscordActivity));
Expand Down Expand Up @@ -102,7 +102,7 @@ class CDiscordStub : public IDiscord
{
void Update() override {}
void ClearGameInfo() override {}
void SetGameInfo(NETADDR ServerAddr, const char *pMapName) override {}
void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) override {}
};

IDiscord *CreateDiscord()
Expand Down
1 change: 1 addition & 0 deletions src/engine/client/favorites.cpp
Expand Up @@ -2,6 +2,7 @@
#include <engine/shared/config.h>
#include <engine/shared/protocol.h>

#include <algorithm>
#include <unordered_map>
#include <vector>

Expand Down
239 changes: 84 additions & 155 deletions src/engine/client/serverbrowser.cpp
Expand Up @@ -300,66 +300,84 @@ void CServerBrowser::Filter()
}
}

if(!Filtered && g_Config.m_BrFilterString[0] != 0)
if(!Filtered && g_Config.m_BrFilterString[0] != '\0')
{
int MatchFound = 0;

m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;

// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString))
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
}
if(aFilterStr[0] == '\0')
{
continue;
}

// match against players
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
{
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, g_Config.m_BrFilterString) ||
str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, g_Config.m_BrFilterString))
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
break;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
}
}

// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
// match against players
for(p = 0; p < minimum(m_ppServerlist[i]->m_Info.m_NumClients, (int)MAX_CLIENTS); p++)
{
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aName, aFilterStr) ||
str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aClients[p].m_aClan, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYER;
break;
}
}

// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aFilterStr))
{
MatchFound = 1;
m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
}
}

if(!MatchFound)
Filtered = 1;
}

if(!Filtered && g_Config.m_BrExcludeString[0] != 0)
if(!Filtered && g_Config.m_BrExcludeString[0] != '\0')
{
int MatchFound = 0;

// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrExcludeString))
const char *pStr = g_Config.m_BrExcludeString;
char aExcludeStr[sizeof(g_Config.m_BrExcludeString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aExcludeStr, sizeof(aExcludeStr))))
{
MatchFound = 1;
}
if(aExcludeStr[0] == '\0')
{
continue;
}

// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrExcludeString))
{
MatchFound = 1;
}
// match against server name
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aName, aExcludeStr))
{
Filtered = 1;
break;
}

// match against gametype
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrExcludeString))
{
MatchFound = 1;
}
// match against map
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, aExcludeStr))
{
Filtered = 1;
break;
}

if(MatchFound)
Filtered = 1;
// match against gametype
if(str_utf8_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, aExcludeStr))
{
Filtered = 1;
break;
}
}
}
}

Expand Down Expand Up @@ -702,12 +720,6 @@ void CServerBrowser::OnServerInfoUpdate(const NETADDR &Addr, int Token, const CS
{
SetInfo(pEntry, *pInfo);
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get() - m_BroadcastTime) * 1000 / time_freq()), 999);
if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo))
{
pEntry->m_Request64Legacy = true;
// Force a quick update.
RequestImpl64(Addr, pEntry);
}
}
else if(pEntry->m_RequestTime > 0)
{
Expand All @@ -729,16 +741,6 @@ void CServerBrowser::OnServerInfoUpdate(const NETADDR &Addr, int Token, const CS
SetLatency(Addr, Latency);
}
pEntry->m_RequestTime = -1; // Request has been answered

if(!pEntry->m_RequestIgnoreInfo)
{
if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo))
{
pEntry->m_Request64Legacy = true;
// Force a quick update.
RequestImpl64(Addr, pEntry);
}
}
}
RemoveRequest(pEntry);

Expand Down Expand Up @@ -854,35 +856,6 @@ void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int
pEntry->m_RequestTime = time_get();
}

void CServerBrowser::RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const
{
unsigned char aBuffer[sizeof(SERVERBROWSE_GETINFO_64_LEGACY) + 1];
CNetChunk Packet;

if(g_Config.m_Debug)
{
char aAddrStr[NETADDR_MAXSTRSIZE];
net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "requesting server info 64 from %s", aAddrStr);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
}

mem_copy(aBuffer, SERVERBROWSE_GETINFO_64_LEGACY, sizeof(SERVERBROWSE_GETINFO_64_LEGACY));
aBuffer[sizeof(SERVERBROWSE_GETINFO_64_LEGACY)] = GetBasicToken(GenerateToken(Addr));

Packet.m_ClientID = -1;
Packet.m_Address = Addr;
Packet.m_Flags = NETSENDFLAG_CONNLESS;
Packet.m_DataSize = sizeof(aBuffer);
Packet.m_pData = aBuffer;

m_pNetClient->Send(&Packet);

if(pEntry)
pEntry->m_RequestTime = time_get();
}

void CServerBrowser::RequestCurrentServer(const NETADDR &Addr) const
{
RequestImpl(Addr, nullptr, nullptr, nullptr, false);
Expand Down Expand Up @@ -1105,10 +1078,7 @@ void CServerBrowser::Update(bool ForceResort)

if(pEntry->m_RequestTime == 0)
{
if(pEntry->m_Request64Legacy)
RequestImpl64(pEntry->m_Info.m_aAddresses[0], pEntry);
else
RequestImpl(pEntry->m_Info.m_aAddresses[0], pEntry, nullptr, nullptr, false);
RequestImpl(pEntry->m_Info.m_aAddresses[0], pEntry, nullptr, nullptr, false);
}

Count++;
Expand Down Expand Up @@ -1477,6 +1447,33 @@ void CServerBrowser::TypeFilterClean(int Network)
str_copy(pExcludeTypes, aNewList, sizeof(g_Config.m_BrFilterExcludeTypes));
}

bool CServerBrowser::IsRegistered(const NETADDR &Addr)
{
const int NumServers = m_pHttp->NumServers();
for(int i = 0; i < NumServers; i++)
{
const CServerInfo Info = m_pHttp->Server(i);
for(int j = 0; j < Info.m_NumAddresses; j++)
{
if(net_addr_comp(&Info.m_aAddresses[j], &Addr) == 0)
{
return true;
}
}
}

const int NumLegacyServers = m_pHttp->NumLegacyServers();
for(int i = 0; i < NumLegacyServers; i++)
{
if(net_addr_comp(&m_pHttp->LegacyServer(i), &Addr) == 0)
{
return true;
}
}

return false;
}

int CServerInfo::EstimateLatency(int Loc1, int Loc2)
{
if(Loc1 == LOC_UNKNOWN || Loc2 == LOC_UNKNOWN)
Expand Down Expand Up @@ -1518,71 +1515,3 @@ bool CServerInfo::ParseLocation(int *pResult, const char *pString)
}
return true;
}

bool IsVanilla(const CServerInfo *pInfo)
{
return !str_comp(pInfo->m_aGameType, "DM") || !str_comp(pInfo->m_aGameType, "TDM") || !str_comp(pInfo->m_aGameType, "CTF");
}

bool IsCatch(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "catch");
}

bool IsInsta(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "idm") || str_find_nocase(pInfo->m_aGameType, "itdm") || str_find_nocase(pInfo->m_aGameType, "ictf");
}

bool IsFNG(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "fng");
}

bool IsRace(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "race") || str_find_nocase(pInfo->m_aGameType, "fastcap");
}

bool IsFastCap(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "fastcap");
}

bool IsBlockInfectionZ(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "blockz") ||
str_find_nocase(pInfo->m_aGameType, "infectionz");
}

bool IsBlockWorlds(const CServerInfo *pInfo)
{
return (str_startswith(pInfo->m_aGameType, "bw ")) || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0);
}

bool IsCity(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "city");
}

bool IsDDRace(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "ddrace") || str_find_nocase(pInfo->m_aGameType, "mkrace");
}

bool IsDDNet(const CServerInfo *pInfo)
{
return str_find_nocase(pInfo->m_aGameType, "ddracenet") || str_find_nocase(pInfo->m_aGameType, "ddnet");
}

// other

bool Is64Player(const CServerInfo *pInfo)
{
return str_find(pInfo->m_aGameType, "64") || str_find(pInfo->m_aName, "64") || IsDDNet(pInfo);
}

bool IsPlus(const CServerInfo *pInfo)
{
return str_find(pInfo->m_aGameType, "+");
}
3 changes: 1 addition & 2 deletions src/engine/client/serverbrowser.h
Expand Up @@ -32,7 +32,6 @@ class CServerBrowser : public IServerBrowser
int64_t m_RequestTime;
bool m_RequestIgnoreInfo;
int m_GotInfo;
bool m_Request64Legacy;
CServerInfo m_Info;

CServerEntry *m_pPrevReq; // request list
Expand Down Expand Up @@ -131,10 +130,10 @@ class CServerBrowser : public IServerBrowser
void SetBaseInfo(class CNetClient *pClient, const char *pNetVersion);
void OnInit();

void RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const;
void QueueRequest(CServerEntry *pEntry);
CServerEntry *Find(const NETADDR &Addr);
int GetCurrentType() override { return m_ServerlistType; }
bool IsRegistered(const NETADDR &Addr);

private:
CNetClient *m_pNetClient = nullptr;
Expand Down
3 changes: 1 addition & 2 deletions src/engine/client/sound.cpp
Expand Up @@ -342,7 +342,7 @@ int CSound::Update()
return 0;
}

int CSound::Shutdown()
void CSound::Shutdown()
{
for(unsigned SampleID = 0; SampleID < NUM_SAMPLES; SampleID++)
{
Expand All @@ -353,7 +353,6 @@ int CSound::Shutdown()
SDL_QuitSubSystem(SDL_INIT_AUDIO);
free(m_pMixBuffer);
m_pMixBuffer = 0;
return 0;
}

int CSound::AllocID()
Expand Down
2 changes: 1 addition & 1 deletion src/engine/client/sound.h
Expand Up @@ -30,7 +30,7 @@ class CSound : public IEngineSound
public:
int Init() override;
int Update() override;
int Shutdown() override;
void Shutdown() override;

bool IsSoundEnabled() override { return m_SoundEnabled; }

Expand Down
15 changes: 9 additions & 6 deletions src/engine/client/steam.cpp
Expand Up @@ -112,16 +112,19 @@ class CSteam : public ISteam
{
SteamAPI_ISteamFriends_ClearRichPresence(m_pSteamFriends);
}
void SetGameInfo(NETADDR ServerAddr, const char *pMapName) override
void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) override
{
char aServerAddr[NETADDR_MAXSTRSIZE];
net_addr_str(&ServerAddr, aServerAddr, sizeof(aServerAddr), true);
if(AnnounceAddr)
{
char aServerAddr[NETADDR_MAXSTRSIZE];
net_addr_str(&ServerAddr, aServerAddr, sizeof(aServerAddr), true);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "connect", aServerAddr);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "steam_player_group", aServerAddr);
}

SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "connect", aServerAddr);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "map", pMapName);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "status", pMapName);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "steam_display", "#Status");
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "steam_player_group", aServerAddr);
}
};

Expand All @@ -132,7 +135,7 @@ class CSteamStub : public ISteam
void ClearConnectAddress() override {}
void Update() override {}
void ClearGameInfo() override {}
void SetGameInfo(NETADDR ServerAddr, const char *pMapName) override {}
void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) override {}
};

ISteam *CreateSteam()
Expand Down
2 changes: 1 addition & 1 deletion src/engine/discord.h
Expand Up @@ -10,7 +10,7 @@ class IDiscord : public IInterface
virtual void Update() = 0;

virtual void ClearGameInfo() = 0;
virtual void SetGameInfo(NETADDR ServerAddr, const char *pMapName) = 0;
virtual void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) = 0;
};

IDiscord *CreateDiscord();
Expand Down
6 changes: 3 additions & 3 deletions src/engine/docs/snapshots.txt
Expand Up @@ -24,7 +24,7 @@ Items can be added when <mods_snap> is called using the <snap_new_item> function
Section: Client Side

Topic: Inspection
<modc_newsnapshot> is called when a new snapshot has arrived for processing. <snap_num_items>, <snap_get_item> and <snap_find_item> can be used to inspect the current and previous snapshot. This can be done anywhere while the client is running and not just in the <modc_newsnapshot> function. The client can also call <snap_invalidate_item> if an item contains improper information that could harm the operation of the client. This should however be done in <modc_newsnapshot> to assure that no bad data propagates into the rest of the client.
<modc_newsnapshot> is called when a new snapshot has arrived for processing. <snap_num_items>, <snap_get_item> and <snap_find_item> can be used to inspect the current and previous snapshot. This can be done anywhere while the client is running and not just in the <modc_newsnapshot> function.

Topic: Rendering
DOCTODO
Expand All @@ -50,8 +50,8 @@ Topic: Interval

The interval for how often a client receives a snapshot changes during the course of the connection. There are three different snapshot rates.

- *Init*. 5 snapshots per second. Used when a client is connecting and used until the client has acknowlaged a snapshot. This mechanism is used because the first snapshot because no delta compression can be done.
- *Init*. 5 snapshots per second. Used when a client is connecting and used until the client has acknowledged a snapshot. This mechanism is used because the first snapshot because no delta compression can be done.

- *Full*. Snapshot for every tick or every even tick depending on server configuration. This is used during normal gameplay and everything is running smooth.

- *Recovery*. 1 snapshot each second. A client enters recovery rate when it haven't acknowlaged a snapshot over 1 second. This is to let the client to beable to recover if it has a slow connection.
- *Recovery*. 1 snapshot each second. A client enters recovery rate when it hasn't acknowledged a snapshot over 1 second. This is to let the client be able to recover if it has a slow connection.
2 changes: 1 addition & 1 deletion src/engine/external/zlib/VERSION.txt
@@ -1 +1 @@
1.2.12
1.2.13
6 changes: 3 additions & 3 deletions src/engine/external/zlib/compress.c
Expand Up @@ -19,7 +19,7 @@
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
int ZEXPORT compress2(dest, destLen, source, sourceLen, level)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
Expand Down Expand Up @@ -65,7 +65,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)

/* ===========================================================================
*/
int ZEXPORT compress (dest, destLen, source, sourceLen)
int ZEXPORT compress(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
Expand All @@ -78,7 +78,7 @@ int ZEXPORT compress (dest, destLen, source, sourceLen)
If the default memLevel or windowBits for deflateInit() is changed, then
this function needs to be updated.
*/
uLong ZEXPORT compressBound (sourceLen)
uLong ZEXPORT compressBound(sourceLen)
uLong sourceLen;
{
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
Expand Down
33 changes: 21 additions & 12 deletions src/engine/external/zlib/crc32.c
Expand Up @@ -98,13 +98,22 @@
# endif
#endif

/* If available, use the ARM processor CRC32 instruction. */
#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8
# define ARMCRC32
#endif

/* Local functions. */
local z_crc_t multmodp OF((z_crc_t a, z_crc_t b));
local z_crc_t x2nmodp OF((z_off64_t n, unsigned k));

/* If available, use the ARM processor CRC32 instruction. */
#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8
# define ARMCRC32
#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE))
local z_word_t byte_swap OF((z_word_t word));
#endif

#if defined(W) && !defined(ARMCRC32)
local z_crc_t crc_word OF((z_word_t data));
local z_word_t crc_word_big OF((z_word_t data));
#endif

#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE))
Expand Down Expand Up @@ -630,7 +639,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
#endif /* DYNAMIC_CRC_TABLE */

/* Pre-condition the CRC */
crc ^= 0xffffffff;
crc = (~crc) & 0xffffffff;

/* Compute the CRC up to a word boundary. */
while (len && ((z_size_t)buf & 7) != 0) {
Expand All @@ -645,8 +654,8 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
len &= 7;

/* Do three interleaved CRCs to realize the throughput of one crc32x
instruction per cycle. Each CRC is calcuated on Z_BATCH words. The three
CRCs are combined into a single CRC after each set of batches. */
instruction per cycle. Each CRC is calculated on Z_BATCH words. The
three CRCs are combined into a single CRC after each set of batches. */
while (num >= 3 * Z_BATCH) {
crc1 = 0;
crc2 = 0;
Expand Down Expand Up @@ -749,7 +758,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len)
#endif /* DYNAMIC_CRC_TABLE */

/* Pre-condition the CRC */
crc ^= 0xffffffff;
crc = (~crc) & 0xffffffff;

#ifdef W

Expand Down Expand Up @@ -1077,7 +1086,7 @@ uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
#ifdef DYNAMIC_CRC_TABLE
once(&made, make_crc_table);
#endif /* DYNAMIC_CRC_TABLE */
return multmodp(x2nmodp(len2, 3), crc1) ^ crc2;
return multmodp(x2nmodp(len2, 3), crc1) ^ (crc2 & 0xffffffff);
}

/* ========================================================================= */
Expand All @@ -1086,7 +1095,7 @@ uLong ZEXPORT crc32_combine(crc1, crc2, len2)
uLong crc2;
z_off_t len2;
{
return crc32_combine64(crc1, crc2, len2);
return crc32_combine64(crc1, crc2, (z_off64_t)len2);
}

/* ========================================================================= */
Expand All @@ -1103,14 +1112,14 @@ uLong ZEXPORT crc32_combine_gen64(len2)
uLong ZEXPORT crc32_combine_gen(len2)
z_off_t len2;
{
return crc32_combine_gen64(len2);
return crc32_combine_gen64((z_off64_t)len2);
}

/* ========================================================================= */
uLong crc32_combine_op(crc1, crc2, op)
uLong ZEXPORT crc32_combine_op(crc1, crc2, op)
uLong crc1;
uLong crc2;
uLong op;
{
return multmodp(op, crc1) ^ crc2;
return multmodp(op, crc1) ^ (crc2 & 0xffffffff);
}
218 changes: 112 additions & 106 deletions src/engine/external/zlib/deflate.c

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/engine/external/zlib/deflate.h
Expand Up @@ -329,8 +329,8 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
# define _tr_tally_dist(s, distance, length, flush) \
{ uch len = (uch)(length); \
ush dist = (ush)(distance); \
s->sym_buf[s->sym_next++] = dist; \
s->sym_buf[s->sym_next++] = dist >> 8; \
s->sym_buf[s->sym_next++] = (uch)dist; \
s->sym_buf[s->sym_next++] = (uch)(dist >> 8); \
s->sym_buf[s->sym_next++] = len; \
dist--; \
s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
Expand Down
2 changes: 1 addition & 1 deletion src/engine/external/zlib/gzlib.c
Expand Up @@ -30,7 +30,7 @@ local gzFile gz_open OF((const void *, int, const char *));
The gz_strwinerror function does not change the current setting of
GetLastError. */
char ZLIB_INTERNAL *gz_strwinerror (error)
char ZLIB_INTERNAL *gz_strwinerror(error)
DWORD error;
{
static char buf[1024];
Expand Down
8 changes: 3 additions & 5 deletions src/engine/external/zlib/gzread.c
Expand Up @@ -157,11 +157,9 @@ local int gz_look(state)
the output buffer is larger than the input buffer, which also assures
space for gzungetc() */
state->x.next = state->out;
if (strm->avail_in) {
memcpy(state->x.next, strm->next_in, strm->avail_in);
state->x.have = strm->avail_in;
strm->avail_in = 0;
}
memcpy(state->x.next, strm->next_in, strm->avail_in);
state->x.have = strm->avail_in;
strm->avail_in = 0;
state->how = COPY;
state->direct = 1;
return 0;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/external/zlib/gzwrite.c
Expand Up @@ -474,7 +474,7 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, ...)
#else /* !STDC && !Z_HAVE_STDARG_H */

/* -- see zlib.h -- */
int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
int ZEXPORTVA gzprintf(file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
gzFile file;
const char *format;
Expand Down
17 changes: 10 additions & 7 deletions src/engine/external/zlib/infback.c
Expand Up @@ -66,6 +66,7 @@ int stream_size;
state->window = window;
state->wnext = 0;
state->whave = 0;
state->sane = 1;
return Z_OK;
}

Expand Down Expand Up @@ -605,25 +606,27 @@ void FAR *out_desc;
break;

case DONE:
/* inflate stream terminated properly -- write leftover output */
/* inflate stream terminated properly */
ret = Z_STREAM_END;
if (left < state->wsize) {
if (out(out_desc, state->window, state->wsize - left))
ret = Z_BUF_ERROR;
}
goto inf_leave;

case BAD:
ret = Z_DATA_ERROR;
goto inf_leave;

default: /* can't happen, but makes compilers happy */
default:
/* can't happen, but makes compilers happy */
ret = Z_STREAM_ERROR;
goto inf_leave;
}

/* Return unused input */
/* Write leftover output and return unused input */
inf_leave:
if (left < state->wsize) {
if (out(out_desc, state->window, state->wsize - left) &&
ret == Z_STREAM_END)
ret = Z_BUF_ERROR;
}
strm->next_in = next;
strm->avail_in = have;
return ret;
Expand Down
7 changes: 5 additions & 2 deletions src/engine/external/zlib/inflate.c
Expand Up @@ -168,6 +168,8 @@ int windowBits;

/* extract wrap request from windowBits parameter */
if (windowBits < 0) {
if (windowBits < -15)
return Z_STREAM_ERROR;
wrap = 0;
windowBits = -windowBits;
}
Expand Down Expand Up @@ -764,8 +766,9 @@ int flush;
if (copy > have) copy = have;
if (copy) {
if (state->head != Z_NULL &&
state->head->extra != Z_NULL) {
len = state->head->extra_len - state->length;
state->head->extra != Z_NULL &&
(len = state->head->extra_len - state->length) <
state->head->extra_max) {
zmemcpy(state->head->extra + len, next,
len + copy > state->head->extra_max ?
state->head->extra_max - len : copy);
Expand Down
4 changes: 2 additions & 2 deletions src/engine/external/zlib/inftrees.c
Expand Up @@ -9,7 +9,7 @@
#define MAXBITS 15

const char inflate_copyright[] =
" inflate 1.2.12 Copyright 1995-2022 Mark Adler ";
" inflate 1.2.13 Copyright 1995-2022 Mark Adler ";
/*
If you use the zlib library in a product, an acknowledgment is welcome
in the documentation of your product. If for some reason you cannot
Expand Down Expand Up @@ -62,7 +62,7 @@ unsigned short FAR *work;
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
static const unsigned short lext[31] = { /* Length codes 257..285 extra */
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 202};
19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 194, 65};
static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
Expand Down
2 changes: 1 addition & 1 deletion src/engine/external/zlib/inftrees.h
Expand Up @@ -38,7 +38,7 @@ typedef struct {
/* Maximum size of the dynamic table. The maximum number of code structures is
1444, which is the sum of 852 for literal/length codes and 592 for distance
codes. These values were found by exhaustive searches using the program
examples/enough.c found in the zlib distribtution. The arguments to that
examples/enough.c found in the zlib distribution. The arguments to that
program are the number of symbols, the initial root table size, and the
maximum bit length of a code. "enough 286 9 15" for literal/length codes
returns returns 852, and "enough 30 6 15" for distance codes returns 592.
Expand Down
123 changes: 61 additions & 62 deletions src/engine/external/zlib/trees.c

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/engine/external/zlib/uncompr.c
Expand Up @@ -24,7 +24,7 @@
Z_DATA_ERROR if the input data was corrupted, including if the input data is
an incomplete zlib stream.
*/
int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
int ZEXPORT uncompress2(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
Expand Down Expand Up @@ -83,7 +83,7 @@ int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
err;
}

int ZEXPORT uncompress (dest, destLen, source, sourceLen)
int ZEXPORT uncompress(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
Expand Down
19 changes: 16 additions & 3 deletions src/engine/external/zlib/zconf.h
Expand Up @@ -38,6 +38,9 @@
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
# define crc32_combine_gen z_crc32_combine_gen
# define crc32_combine_gen64 z_crc32_combine_gen64
# define crc32_combine_op z_crc32_combine_op
# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
Expand Down Expand Up @@ -349,6 +352,9 @@
# ifdef FAR
# undef FAR
# endif
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
Expand Down Expand Up @@ -467,11 +473,18 @@ typedef uLong FAR uLongf;
# undef _LARGEFILE64_SOURCE
#endif

#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
# define Z_HAVE_UNISTD_H
#ifndef Z_HAVE_UNISTD_H
# ifdef __WATCOMC__
# define Z_HAVE_UNISTD_H
# endif
#endif
#ifndef Z_HAVE_UNISTD_H
# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32)
# define Z_HAVE_UNISTD_H
# endif
#endif
#ifndef Z_SOLO
# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
# if defined(Z_HAVE_UNISTD_H)
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
# ifdef VMS
# include <unixio.h> /* for off_t */
Expand Down
20 changes: 10 additions & 10 deletions src/engine/external/zlib/zlib.h
@@ -1,5 +1,5 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
version 1.2.12, March 11th, 2022
version 1.2.13, October 13th, 2022
Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler
Expand Down Expand Up @@ -37,11 +37,11 @@
extern "C" {
#endif

#define ZLIB_VERSION "1.2.12"
#define ZLIB_VERNUM 0x12c0
#define ZLIB_VERSION "1.2.13"
#define ZLIB_VERNUM 0x12d0
#define ZLIB_VER_MAJOR 1
#define ZLIB_VER_MINOR 2
#define ZLIB_VER_REVISION 12
#define ZLIB_VER_REVISION 13
#define ZLIB_VER_SUBREVISION 0

/*
Expand Down Expand Up @@ -276,7 +276,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
== 0), or after each call of deflate(). If deflate returns Z_OK and with
zero avail_out, it must be called again after making room in the output
buffer because there might be more output pending. See deflatePending(),
which can be used if desired to determine whether or not there is more ouput
which can be used if desired to determine whether or not there is more output
in that case.
Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
Expand Down Expand Up @@ -660,7 +660,7 @@ ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm,
to dictionary. dictionary must have enough space, where 32768 bytes is
always enough. If deflateGetDictionary() is called with dictionary equal to
Z_NULL, then only the dictionary length is returned, and nothing is copied.
Similary, if dictLength is Z_NULL, then it is not set.
Similarly, if dictLength is Z_NULL, then it is not set.
deflateGetDictionary() may return a length less than the window size, even
when more than the window size in input has been provided. It may return up
Expand Down Expand Up @@ -915,7 +915,7 @@ ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm,
to dictionary. dictionary must have enough space, where 32768 bytes is
always enough. If inflateGetDictionary() is called with dictionary equal to
Z_NULL, then only the dictionary length is returned, and nothing is copied.
Similary, if dictLength is Z_NULL, then it is not set.
Similarly, if dictLength is Z_NULL, then it is not set.
inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
stream state is inconsistent.
Expand Down Expand Up @@ -1437,12 +1437,12 @@ ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems,
In the event that the end of file is reached and only a partial item is
available at the end, i.e. the remaining uncompressed data length is not a
multiple of size, then the final partial item is nevetheless read into buf
multiple of size, then the final partial item is nevertheless read into buf
and the end-of-file flag is set. The length of the partial item read is not
provided, but could be inferred from the result of gztell(). This behavior
is the same as the behavior of fread() implementations in common libraries,
but it prevents the direct use of gzfread() to read a concurrently written
file, reseting and retrying on end-of-file, when size is not 1.
file, resetting and retrying on end-of-file, when size is not 1.
*/

ZEXTERN int ZEXPORT gzwrite OF((gzFile file, voidpc buf, unsigned len));
Expand Down Expand Up @@ -1913,7 +1913,7 @@ ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp));
ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void));
ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int));
ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int));
ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp));
ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF((z_streamp));
ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp));
ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
#if defined(_WIN32) && !defined(Z_SOLO)
Expand Down
16 changes: 9 additions & 7 deletions src/engine/external/zlib/zutil.c
Expand Up @@ -61,9 +61,11 @@ uLong ZEXPORT zlibCompileFlags()
#ifdef ZLIB_DEBUG
flags += 1 << 8;
#endif
/*
#if defined(ASMV) || defined(ASMINF)
flags += 1 << 9;
#endif
*/
#ifdef ZLIB_WINAPI
flags += 1 << 10;
#endif
Expand Down Expand Up @@ -119,7 +121,7 @@ uLong ZEXPORT zlibCompileFlags()
# endif
int ZLIB_INTERNAL z_verbose = verbose;

void ZLIB_INTERNAL z_error (m)
void ZLIB_INTERNAL z_error(m)
char *m;
{
fprintf(stderr, "%s\n", m);
Expand Down Expand Up @@ -214,7 +216,7 @@ local ptr_table table[MAX_PTR];
* a protected system like OS/2. Use Microsoft C instead.
*/

voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size)
{
voidpf buf;
ulg bsize = (ulg)items*size;
Expand All @@ -240,7 +242,7 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
return buf;
}

void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr)
{
int n;

Expand Down Expand Up @@ -277,13 +279,13 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
# define _hfree hfree
#endif

voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size)
{
(void)opaque;
return _halloc((long)items, size);
}

void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr)
{
(void)opaque;
_hfree(ptr);
Expand All @@ -302,7 +304,7 @@ extern voidp calloc OF((uInt items, uInt size));
extern void free OF((voidpf ptr));
#endif

voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
voidpf ZLIB_INTERNAL zcalloc(opaque, items, size)
voidpf opaque;
unsigned items;
unsigned size;
Expand All @@ -312,7 +314,7 @@ voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
(voidpf)calloc(items, size);
}

void ZLIB_INTERNAL zcfree (opaque, ptr)
void ZLIB_INTERNAL zcfree(opaque, ptr)
voidpf opaque;
voidpf ptr;
{
Expand Down
1 change: 1 addition & 0 deletions src/engine/external/zlib/zutil.h
Expand Up @@ -193,6 +193,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
(!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0)
ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine_gen64 OF((z_off_t));
#endif

/* common defaults */
Expand Down
2 changes: 1 addition & 1 deletion src/engine/graphics.h
Expand Up @@ -524,7 +524,7 @@ class IEngineGraphics : public IGraphics
MACRO_INTERFACE("enginegraphics", 0)
public:
virtual int Init() = 0;
virtual void Shutdown() = 0;
virtual void Shutdown() override = 0;

virtual void Minimize() = 0;
virtual void Maximize() = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/input.h
Expand Up @@ -128,7 +128,7 @@ class IEngineInput : public IInput
MACRO_INTERFACE("engineinput", 0)
public:
virtual void Init() = 0;
virtual void Shutdown() = 0;
virtual void Shutdown() override = 0;
virtual int Update() = 0;
virtual int VideoRestartNeeded() = 0;
};
Expand Down
2 changes: 2 additions & 0 deletions src/engine/kernel.h
Expand Up @@ -20,6 +20,7 @@ class IInterface
public:
IInterface() :
m_pKernel(nullptr) {}
virtual void Shutdown() {}
virtual ~IInterface() {}
};

Expand All @@ -40,6 +41,7 @@ class IKernel

public:
static IKernel *Create();
virtual void Shutdown() = 0;
virtual ~IKernel() {}

// templated access to handle pointer conversions and interface names
Expand Down
4 changes: 4 additions & 0 deletions src/engine/server/main.cpp
Expand Up @@ -62,6 +62,10 @@ int main(int argc, const char **argv)
}
}

#if defined(CONF_FAMILY_WINDOWS)
CWindowsComLifecycle WindowsComLifecycle(false);
#endif

std::vector<std::shared_ptr<ILogger>> vpLoggers;
#if defined(CONF_PLATFORM_ANDROID)
vpLoggers.push_back(std::shared_ptr<ILogger>(log_logger_android()));
Expand Down
24 changes: 24 additions & 0 deletions src/engine/server/register.cpp
Expand Up @@ -118,6 +118,9 @@ class CRegister : public IRegister
CConfig *m_pConfig;
IConsole *m_pConsole;
IEngine *m_pEngine;
// Don't start sending registers before the server has initialized
// completely.
bool m_GotFirstUpdateCall = false;
int m_ServerPort;
char m_aConnlessTokenHex[16];

Expand Down Expand Up @@ -505,6 +508,16 @@ CRegister::CRegister(CConfig *pConfig, IConsole *pConsole, IEngine *pEngine, int

void CRegister::Update()
{
if(!m_GotFirstUpdateCall)
{
bool Ipv6 = m_aProtocolEnabled[PROTOCOL_TW6_IPV6] || m_aProtocolEnabled[PROTOCOL_TW7_IPV6];
bool Ipv4 = m_aProtocolEnabled[PROTOCOL_TW6_IPV4] || m_aProtocolEnabled[PROTOCOL_TW7_IPV4];
if(Ipv6 && Ipv4)
{
dbg_assert(!HttpHasIpresolveBug(), "curl version < 7.77.0 does not support registering via both IPv4 and IPv6, set `sv_register ipv6` or `sv_register ipv4`");
}
m_GotFirstUpdateCall = true;
}
if(!m_GotServerInfo)
{
return;
Expand Down Expand Up @@ -605,6 +618,11 @@ void CRegister::OnConfigChange()
str_copy(m_aaExtraHeaders[m_NumExtraHeaders], aHeader);
m_NumExtraHeaders += 1;
}
// Don't start registering before the first `CRegister::Update` call.
if(!m_GotFirstUpdateCall)
{
return;
}
for(int i = 0; i < NUM_PROTOCOLS; i++)
{
if(aOldProtocolEnabled[i] == m_aProtocolEnabled[i])
Expand Down Expand Up @@ -670,6 +688,12 @@ void CRegister::OnNewInfo(const char *pInfo)
m_pGlobal->m_InfoSerial += 1;
}

// Don't start registering before the first `CRegister::Update` call.
if(!m_GotFirstUpdateCall)
{
return;
}

// Immediately send new info if it changes, but at most once per second.
int64_t Now = time_get();
int64_t Freq = time_freq();
Expand Down
9 changes: 7 additions & 2 deletions src/engine/server/server.cpp
Expand Up @@ -1538,7 +1538,11 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
str_format(aBuf, sizeof(aBuf), "player has entered the game. ClientID=%d addr=<{%s}> sixup=%d", ClientID, aAddrStr, IsSixup(ClientID));
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
m_aClients[ClientID].m_State = CClient::STATE_INGAME;
if(IsSixup(ClientID))
if(!IsSixup(ClientID))
{
SendServerInfo(m_NetServer.ClientAddr(ClientID), -1, SERVERINFO_EXTENDED, false);
}
else
{
CMsgPacker Msgp(4, true, true); //NETMSG_SERVERINFO //TODO: Import the shared protocol from 7 aswell
GetServerInfoSixup(&Msgp, -1, false);
Expand Down Expand Up @@ -1886,7 +1890,8 @@ void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients)
}
else
{
str_format(aBuf, sizeof(aBuf), "%s [%d/%d]", Config()->m_SvName, ClientCount, m_NetServer.MaxClients());
const int MaxClients = maximum(ClientCount, m_NetServer.MaxClients() - Config()->m_SvReservedSlots);
str_format(aBuf, sizeof(aBuf), "%s [%d/%d]", Config()->m_SvName, ClientCount, MaxClients);
p.AddString(aBuf, 64);
}
}
Expand Down
16 changes: 2 additions & 14 deletions src/engine/serverbrowser.h
Expand Up @@ -85,20 +85,6 @@ class CServerInfo
static bool ParseLocation(int *pResult, const char *pString);
};

bool IsVanilla(const CServerInfo *pInfo);
bool IsCatch(const CServerInfo *pInfo);
bool IsInsta(const CServerInfo *pInfo);
bool IsFNG(const CServerInfo *pInfo);
bool IsRace(const CServerInfo *pInfo);
bool IsFastCap(const CServerInfo *pInfo);
bool IsDDRace(const CServerInfo *pInfo);
bool IsDDNet(const CServerInfo *pInfo);
bool IsBlockWorlds(const CServerInfo *pInfo);
bool IsCity(const CServerInfo *pInfo);

bool Is64Player(const CServerInfo *pInfo);
bool IsPlus(const CServerInfo *pInfo);

class IServerBrowser : public IInterface
{
MACRO_INTERFACE("serverbrowser", 0)
Expand Down Expand Up @@ -141,6 +127,8 @@ class IServerBrowser : public IInterface
NUM_NETWORKS,
};

static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";";

virtual void Refresh(int Type) = 0;
virtual bool IsGettingServerlist() const = 0;
virtual bool IsRefreshing() const = 0;
Expand Down
19 changes: 9 additions & 10 deletions src/engine/shared/config_variables.h
Expand Up @@ -44,8 +44,8 @@ MACRO_CONFIG_STR(ClAssetParticles, cl_asset_particles, 50, "default", CFGFLAG_SA
MACRO_CONFIG_STR(ClAssetHud, cl_asset_hud, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for HUD")
MACRO_CONFIG_STR(ClAssetExtras, cl_asset_extras, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for the game graphics that do not come from Teeworlds")

MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "Novice", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser filtering string")
MACRO_CONFIG_STR(BrExcludeString, br_exclude_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser exclusion string")
MACRO_CONFIG_STR(BrFilterString, br_filter_string, 128, "Novice", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser filtering string")
MACRO_CONFIG_STR(BrExcludeString, br_exclude_string, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server browser exclusion string")
MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out full server in browser")
MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out empty server in browser")
MACRO_CONFIG_INT(BrFilterSpectators, br_filter_spectators, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out spectators from player numbers")
Expand Down Expand Up @@ -166,7 +166,7 @@ MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "Th
MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos")
MACRO_CONFIG_INT(SvAutoDemoMax, sv_auto_demo_max, 10, 0, 1000, CFGFLAG_SERVER, "Maximum number of automatically recorded demos (0 = no limit)")
MACRO_CONFIG_INT(SvTeeHistorian, sv_tee_historian, 0, 0, 1, CFGFLAG_SERVER, "Activate the tee historian that writes complete gameplay data to disk (WARNING: This will use a lot of disk space)")
MACRO_CONFIG_INT(SvVanillaAntiSpoof, sv_vanilla_antispoof, 0, 0, 1, CFGFLAG_SERVER, "Enable vanilla Antispoof")
MACRO_CONFIG_INT(SvVanillaAntiSpoof, sv_vanilla_antispoof, 1, 0, 1, CFGFLAG_SERVER, "Enable vanilla Antispoof")
MACRO_CONFIG_INT(SvDnsbl, sv_dnsbl, 0, 0, 1, CFGFLAG_SERVER, "Enable DNSBL (DNS-based Blackhole List)")
MACRO_CONFIG_STR(SvDnsblHost, sv_dnsbl_host, 128, "", CFGFLAG_SERVER, "Hostname of DNSBL provider to use for IP Verification")
MACRO_CONFIG_STR(SvDnsblKey, sv_dnsbl_key, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Optional Authentication Key for the specified DNSBL provider")
Expand Down Expand Up @@ -300,12 +300,12 @@ MACRO_CONFIG_COL(ClMessageTeamColor, cl_message_team_color, 5636050, CFGFLAG_CLI
MACRO_CONFIG_COL(ClMessageColor, cl_message_color, 255, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Message color")
MACRO_CONFIG_COL(ClLaserRifleInnerColor, cl_laser_rifle_inner_color, 11206591, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for Rifle")
MACRO_CONFIG_COL(ClLaserRifleOutlineColor, cl_laser_rifle_outline_color, 11176233, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser outline color for Rifle")
MACRO_CONFIG_COL(ClLaserShotgunInnerColor, cl_laser_sg_inner_color, 1900385, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for Shotgun")
MACRO_CONFIG_COL(ClLaserShotgunInnerColor, cl_laser_sg_inner_color, 1467241, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for Shotgun")
MACRO_CONFIG_COL(ClLaserShotgunOutlineColor, cl_laser_sg_outline_color, 1866773, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser outline color for Shotgun")
MACRO_CONFIG_COL(ClLaserDoorInnerColor, cl_laser_door_inner_color, 7701379, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for doors")
MACRO_CONFIG_COL(ClLaserDoorOutlineColor, cl_laser_door_outline_color, 7667473, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser outline color for doors")
MACRO_CONFIG_COL(ClLaserFreezeInnerColor, cl_laser_freeze_inner_color, 15958915, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for freezes")
MACRO_CONFIG_COL(ClLaserFreezeOutlineColor, cl_laser_freeze_outline_color, 15972381, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser outline color for freezes")
MACRO_CONFIG_COL(ClLaserFreezeInnerColor, cl_laser_freeze_inner_color, 12001153, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser inner color for freezes")
MACRO_CONFIG_COL(ClLaserFreezeOutlineColor, cl_laser_freeze_outline_color, 11613223, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Laser outline color for freezes")
MACRO_CONFIG_COL(ClKillMessageNormalColor, cl_kill_message_normal_color, 255, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLALPHA, "Kill message normal color")
MACRO_CONFIG_COL(ClKillMessageHighlightColor, cl_kill_message_highlight_color, 255, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_COLALPHA, "Kill message highlight color")

Expand Down Expand Up @@ -373,9 +373,9 @@ MACRO_CONFIG_INT(SvShutdownWhenEmpty, sv_shutdown_when_empty, 0, 0, 1, CFGFLAG_S
MACRO_CONFIG_INT(SvReloadWhenEmpty, sv_reload_when_empty, 0, 0, 2, CFGFLAG_SERVER, "Reload map when server is empty (1 = reload once, 2 = reload every time server gets empty)")
MACRO_CONFIG_INT(SvKillProtection, sv_kill_protection, 20, 0, 9999, CFGFLAG_SERVER, "0 - Disable, 1-9999 minutes")
MACRO_CONFIG_INT(SvSoloServer, sv_solo_server, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Set server to solo mode (no player interactions, has to be set before loading the map)")
MACRO_CONFIG_STR(SvClientSuggestion, sv_client_suggestion, 128, "Get DDNet client from DDNet.tw to use all features on DDNet!", CFGFLAG_SERVER, "Broadcast to display to players without DDNet client")
MACRO_CONFIG_STR(SvClientSuggestionOld, sv_client_suggestion_old, 128, "Your DDNet client is old, update it on DDNet.tw!", CFGFLAG_SERVER, "Broadcast to display to players with an old version of DDNet client")
MACRO_CONFIG_STR(SvClientSuggestionBot, sv_client_suggestion_bot, 128, "Your client has bots and can be remotely controlled!\nPlease use another client like DDNet client from DDNet.tw", CFGFLAG_SERVER, "Broadcast to display to players with a known botting client")
MACRO_CONFIG_STR(SvClientSuggestion, sv_client_suggestion, 128, "Get DDNet client from DDNet.org to use all features on DDNet!", CFGFLAG_SERVER, "Broadcast to display to players without DDNet client")
MACRO_CONFIG_STR(SvClientSuggestionOld, sv_client_suggestion_old, 128, "Your DDNet client is old, update it on DDNet.org!", CFGFLAG_SERVER, "Broadcast to display to players with an old version of DDNet client")
MACRO_CONFIG_STR(SvClientSuggestionBot, sv_client_suggestion_bot, 128, "Your client has bots and can be remotely controlled!\nPlease use another client like DDNet client from DDNet.org", CFGFLAG_SERVER, "Broadcast to display to players with a known botting client")
MACRO_CONFIG_STR(SvBannedVersions, sv_banned_versions, 128, "", CFGFLAG_SERVER, "Comma separated list of banned clients to be kicked on join")

// netlimit
Expand Down Expand Up @@ -406,7 +406,6 @@ MACRO_CONFIG_INT(ClChatReset, cl_chat_reset, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_S
MACRO_CONFIG_INT(ClChatOld, cl_chat_old, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Old chat style: No tee, no background");

MACRO_CONFIG_INT(ClShowDirection, cl_show_direction, 1, 0, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show key presses (1 = other players', 2 = also your own)")
MACRO_CONFIG_INT(ClHttpMapDownload, cl_http_map_download, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Try fast HTTP map download first")
MACRO_CONFIG_INT(ClOldGunPosition, cl_old_gun_position, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Tees hold gun a bit higher like in TW 0.6.1 and older")
MACRO_CONFIG_INT(ClConfirmDisconnectTime, cl_confirm_disconnect_time, 20, -1, 1440, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Confirmation popup before disconnecting after game time (in minutes, -1 to turn off, 0 to always turn on)")
MACRO_CONFIG_INT(ClConfirmQuitTime, cl_confirm_quit_time, 20, -1, 1440, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Confirmation popup before quitting after game time (in minutes, -1 to turn off, 0 to always turn on)")
Expand Down
4 changes: 3 additions & 1 deletion src/engine/shared/console.cpp
Expand Up @@ -548,7 +548,9 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo
}
else if(Stroke)
{
if(!m_pfnUnknownCommandCallback(Result.m_pCommand, m_pUnknownCommandUserdata))
// Pass the original string to the unknown command callback instead of the parsed command, as the latter
// ends at the first whitespace, which breaks for unknown commands (filenames) containing spaces.
if(!m_pfnUnknownCommandCallback(pStr, m_pUnknownCommandUserdata))
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand);
Expand Down
9 changes: 5 additions & 4 deletions src/engine/shared/datafile.cpp
Expand Up @@ -4,6 +4,7 @@
#include "datafile.h"

#include <base/hash_ctxt.h>
#include <base/log.h>
#include <base/system.h>
#include <engine/storage.h>

Expand Down Expand Up @@ -100,7 +101,7 @@ struct CDatafile

bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int StorageType)
{
dbg_msg("datafile", "loading. filename='%s'", pFilename);
log_trace("datafile", "loading. filename='%s'", pFilename);

IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
Expand Down Expand Up @@ -221,7 +222,7 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData];
m_pDataFile->m_Info.m_pDataStart = m_pDataFile->m_Info.m_pItemStart + m_pDataFile->m_Header.m_ItemSize;

dbg_msg("datafile", "loading done. datafile='%s'", pFilename);
log_trace("datafile", "loading done. datafile='%s'", pFilename);

return true;
}
Expand Down Expand Up @@ -288,7 +289,7 @@ void *CDataFileReader::GetDataImpl(int Index, int Swap)
unsigned long UncompressedSize = m_pDataFile->m_Info.m_pDataSizes[Index];
unsigned long s;

dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%lu", Index, DataSize, UncompressedSize);
log_trace("datafile", "loading data index=%d size=%d uncompressed=%lu", Index, DataSize, UncompressedSize);
m_pDataFile->m_ppDataPtrs[Index] = (char *)malloc(UncompressedSize);

// read the compressed data
Expand All @@ -308,7 +309,7 @@ void *CDataFileReader::GetDataImpl(int Index, int Swap)
else
{
// load the data
dbg_msg("datafile", "loading data index=%d size=%d", Index, DataSize);
log_trace("datafile", "loading data index=%d size=%d", Index, DataSize);
m_pDataFile->m_ppDataPtrs[Index] = (char *)malloc(DataSize);
io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset + m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START);
io_read(m_pDataFile->m_File, m_pDataFile->m_ppDataPtrs[Index], DataSize);
Expand Down
6 changes: 6 additions & 0 deletions src/engine/shared/engine.cpp
Expand Up @@ -72,6 +72,12 @@ class CEngine : public IEngine
dbg_msg("engine", "unknown endian");
#endif

char aVersionStr[128];
if(!os_version_str(aVersionStr, sizeof(aVersionStr)))
{
dbg_msg("engine", "operation system version: %s", aVersionStr);
}

// init the network
net_init();
CNetBase::Init();
Expand Down
9 changes: 8 additions & 1 deletion src/engine/shared/http.cpp
Expand Up @@ -116,6 +116,13 @@ void EscapeUrl(char *pBuf, int Size, const char *pStr)
curl_free(pEsc);
}

bool HttpHasIpresolveBug()
{
// curl < 7.77.0 doesn't use CURLOPT_IPRESOLVE correctly wrt.
// connection caches.
return curl_version_info(CURLVERSION_NOW)->version_num < 0x074d00;
}

CHttpRequest::CHttpRequest(const char *pUrl)
{
str_copy(m_aUrl, pUrl);
Expand Down Expand Up @@ -271,7 +278,7 @@ int CHttpRequest::RunImpl(CURL *pUser)
if(Ret != CURLE_OK)
{
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
dbg_msg("http", "%s failed. libcurl error: %s", m_aUrl, aErr);
dbg_msg("http", "%s failed. libcurl error (%d): %s", m_aUrl, (int)Ret, aErr);
return (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
}
else
Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/http.h
Expand Up @@ -194,4 +194,5 @@ inline std::unique_ptr<CHttpRequest> HttpPostJson(const char *pUrl, const char *

bool HttpInit(IStorage *pStorage);
void EscapeUrl(char *pBuf, int Size, const char *pStr);
bool HttpHasIpresolveBug();
#endif // ENGINE_SHARED_HTTP_H
9 changes: 9 additions & 0 deletions src/engine/shared/kernel.cpp
Expand Up @@ -44,6 +44,15 @@ class CKernel : public IKernel
m_NumInterfaces = 0;
}

void Shutdown() override
{
for(int i = m_NumInterfaces - 1; i >= 0; i--)
{
if(m_aInterfaces[i].m_AutoDestroy)
m_aInterfaces[i].m_pInterface->Shutdown();
}
}

virtual ~CKernel()
{
// delete interfaces in reverse order just the way it would happen to objects on the stack
Expand Down
24 changes: 12 additions & 12 deletions src/engine/shared/snapshot.cpp
Expand Up @@ -12,9 +12,9 @@

// CSnapshot

CSnapshotItem *CSnapshot::GetItem(int Index) const
const CSnapshotItem *CSnapshot::GetItem(int Index) const
{
return (CSnapshotItem *)(DataStart() + Offsets()[Index]);
return (const CSnapshotItem *)(DataStart() + Offsets()[Index]);
}

int CSnapshot::GetItemSize(int Index) const
Expand Down Expand Up @@ -42,7 +42,7 @@ int CSnapshot::GetExternalItemType(int InternalType) const
{
return InternalType;
}
CSnapshotItem *pTypeItem = GetItem(TypeItemIndex);
const CSnapshotItem *pTypeItem = GetItem(TypeItemIndex);
CUuid Uuid;
for(int i = 0; i < (int)sizeof(CUuid) / 4; i++)
int_to_bytes_be(&Uuid.m_aData[i * 4], pTypeItem->Data()[i]);
Expand All @@ -61,7 +61,7 @@ int CSnapshot::GetItemIndex(int Key) const
return -1;
}

void *CSnapshot::FindItem(int Type, int ID) const
const void *CSnapshot::FindItem(int Type, int ID) const
{
int InternalType = Type;
if(Type >= OFFSET_UUID)
Expand All @@ -74,7 +74,7 @@ void *CSnapshot::FindItem(int Type, int ID) const
bool Found = false;
for(int i = 0; i < m_NumItems; i++)
{
CSnapshotItem *pItem = GetItem(i);
const CSnapshotItem *pItem = GetItem(i);
if(pItem->Type() == 0 && pItem->ID() >= OFFSET_UUID_TYPE) // NETOBJTYPE_EX
{
if(mem_comp(pItem->Data(), aTypeUuidItem, sizeof(CUuid)) == 0)
Expand All @@ -100,7 +100,7 @@ unsigned CSnapshot::Crc()

for(int i = 0; i < m_NumItems; i++)
{
CSnapshotItem *pItem = GetItem(i);
const CSnapshotItem *pItem = GetItem(i);
int Size = GetItemSize(i);

for(int b = 0; b < Size / 4; b++)
Expand All @@ -114,7 +114,7 @@ void CSnapshot::DebugDump()
dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems);
for(int i = 0; i < m_NumItems; i++)
{
CSnapshotItem *pItem = GetItem(i);
const CSnapshotItem *pItem = GetItem(i);
int Size = GetItemSize(i);
dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID());
for(int b = 0; b < Size / 4; b++)
Expand Down Expand Up @@ -196,7 +196,7 @@ static int GetItemIndexHashed(int Key, const CItemList *pHashlist)
return -1;
}

int CSnapshotDelta::DiffItem(int *pPast, int *pCurrent, int *pOut, int Size)
int CSnapshotDelta::DiffItem(const int *pPast, const int *pCurrent, int *pOut, int Size)
{
int Needed = 0;
while(Size)
Expand All @@ -212,7 +212,7 @@ int CSnapshotDelta::DiffItem(int *pPast, int *pCurrent, int *pOut, int Size)
return Needed;
}

void CSnapshotDelta::UndiffItem(int *pPast, int *pDiff, int *pOut, int Size, int *pDataRate)
void CSnapshotDelta::UndiffItem(const int *pPast, int *pDiff, int *pOut, int Size, int *pDataRate)
{
while(Size)
{
Expand Down Expand Up @@ -304,15 +304,15 @@ int CSnapshotDelta::CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pDstData
{
// do delta
const int ItemSize = pTo->GetItemSize(i); // O(1) .. O(n)
CSnapshotItem *pCurItem = pTo->GetItem(i); // O(1) .. O(n)
const CSnapshotItem *pCurItem = pTo->GetItem(i); // O(1) .. O(n)
const int PastIndex = aPastIndices[i];
const bool IncludeSize = pCurItem->Type() >= MAX_NETOBJSIZES || !m_aItemSizes[pCurItem->Type()];

if(PastIndex != -1)
{
int *pItemDataDst = pData + 3;

CSnapshotItem *pPastItem = pFrom->GetItem(PastIndex);
const CSnapshotItem *pPastItem = pFrom->GetItem(PastIndex);

if(!IncludeSize)
pItemDataDst = pData + 2;
Expand Down Expand Up @@ -373,7 +373,7 @@ int CSnapshotDelta::UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, const void *pS
// copy all non deleted stuff
for(int i = 0; i < pFrom->NumItems(); i++)
{
CSnapshotItem *pFromItem = pFrom->GetItem(i);
const CSnapshotItem *pFromItem = pFrom->GetItem(i);
const int ItemSize = pFrom->GetItemSize(i);
bool Keep = true;
for(int d = 0; d < pDelta->m_NumDeletedItems; d++)
Expand Down
14 changes: 9 additions & 5 deletions src/engine/shared/snapshot.h
Expand Up @@ -10,10 +10,14 @@

class CSnapshotItem
{
friend class CSnapshotBuilder;

int *Data() { return (int *)(this + 1); }

public:
int m_TypeAndID;

int *Data() { return (int *)(this + 1); }
const int *Data() const { return (int *)(this + 1); }
int Type() const { return m_TypeAndID >> 16; }
int ID() const { return m_TypeAndID & 0xffff; }
int Key() const { return m_TypeAndID; }
Expand Down Expand Up @@ -48,12 +52,12 @@ class CSnapshot
m_NumItems = 0;
}
int NumItems() const { return m_NumItems; }
CSnapshotItem *GetItem(int Index) const;
const CSnapshotItem *GetItem(int Index) const;
int GetItemSize(int Index) const;
int GetItemIndex(int Key) const;
int GetItemType(int Index) const;
int GetExternalItemType(int InternalType) const;
void *FindItem(int Type, int ID) const;
const void *FindItem(int Type, int ID) const;

unsigned Crc();
void DebugDump();
Expand Down Expand Up @@ -84,10 +88,10 @@ class CSnapshotDelta
int m_aSnapshotDataUpdates[CSnapshot::MAX_TYPE + 1];
CData m_Empty;

static void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size, int *pDataRate);
static void UndiffItem(const int *pPast, int *pDiff, int *pOut, int Size, int *pDataRate);

public:
static int DiffItem(int *pPast, int *pCurrent, int *pOut, int Size);
static int DiffItem(const int *pPast, const int *pCurrent, int *pOut, int Size);
CSnapshotDelta();
CSnapshotDelta(const CSnapshotDelta &Old);
int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; }
Expand Down
151 changes: 84 additions & 67 deletions src/engine/shared/storage.cpp
Expand Up @@ -62,37 +62,36 @@ class CStorage : public IStorage
// add save directories
if(StorageType != STORAGETYPE_BASIC && m_NumPaths && (!m_aaStoragePaths[TYPE_SAVE][0] || fs_makedir_rec_for(m_aaStoragePaths[TYPE_SAVE]) || !fs_makedir(m_aaStoragePaths[TYPE_SAVE])))
{
char aPath[IO_MAX_PATH_LENGTH];
if(StorageType == STORAGETYPE_CLIENT)
{
fs_makedir(GetPath(TYPE_SAVE, "screenshots", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto/stats", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "maps", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "mapres", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "skins", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "downloadedskins", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "themes", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/emoticons", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/entities", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/game", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/particles", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/hud", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "assets/extras", aPath, sizeof(aPath)));
CreateFolder("screenshots", TYPE_SAVE);
CreateFolder("screenshots/auto", TYPE_SAVE);
CreateFolder("screenshots/auto/stats", TYPE_SAVE);
CreateFolder("maps", TYPE_SAVE);
CreateFolder("mapres", TYPE_SAVE);
CreateFolder("downloadedmaps", TYPE_SAVE);
CreateFolder("skins", TYPE_SAVE);
CreateFolder("downloadedskins", TYPE_SAVE);
CreateFolder("themes", TYPE_SAVE);
CreateFolder("assets", TYPE_SAVE);
CreateFolder("assets/emoticons", TYPE_SAVE);
CreateFolder("assets/entities", TYPE_SAVE);
CreateFolder("assets/game", TYPE_SAVE);
CreateFolder("assets/particles", TYPE_SAVE);
CreateFolder("assets/hud", TYPE_SAVE);
CreateFolder("assets/extras", TYPE_SAVE);
#if defined(CONF_VIDEORECORDER)
fs_makedir(GetPath(TYPE_SAVE, "videos", aPath, sizeof(aPath)));
CreateFolder("videos", TYPE_SAVE);
#endif
}
fs_makedir(GetPath(TYPE_SAVE, "dumps", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos/auto", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos/auto/race", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "demos/replays", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "editor", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "ghosts", aPath, sizeof(aPath)));
fs_makedir(GetPath(TYPE_SAVE, "teehistorian", aPath, sizeof(aPath)));
CreateFolder("dumps", TYPE_SAVE);
CreateFolder("demos", TYPE_SAVE);
CreateFolder("demos/auto", TYPE_SAVE);
CreateFolder("demos/auto/race", TYPE_SAVE);
CreateFolder("demos/replays", TYPE_SAVE);
CreateFolder("editor", TYPE_SAVE);
CreateFolder("ghosts", TYPE_SAVE);
CreateFolder("teehistorian", TYPE_SAVE);
}

return m_NumPaths ? 0 : 1;
Expand Down Expand Up @@ -124,10 +123,10 @@ class CStorage : public IStorage
}
}

char *pLine;
CLineReader LineReader;
LineReader.Init(File);

char *pLine;
while((pLine = LineReader.Get()))
{
const char *pLineWithoutPrefix = str_startswith(pLine, "add_path ");
Expand All @@ -152,8 +151,16 @@ class CStorage : public IStorage

void AddPath(const char *pPath)
{
if(m_NumPaths >= MAX_PATHS || !pPath[0])
if(!pPath[0])
{
dbg_msg("storage", "cannot add empty path");
return;
}
if(m_NumPaths >= MAX_PATHS)
{
dbg_msg("storage", "cannot add path '%s', the maximum number of paths is %d", pPath, MAX_PATHS);
return;
}

if(!str_comp(pPath, "$USERDIR"))
{
Expand Down Expand Up @@ -183,6 +190,10 @@ class CStorage : public IStorage
str_copy(m_aaStoragePaths[m_NumPaths++], pPath);
dbg_msg("storage", "added path '%s'", pPath);
}
else
{
dbg_msg("storage", "cannot add path '%s', which is not a directory", pPath);
}
}
}

Expand Down Expand Up @@ -242,16 +253,14 @@ class CStorage : public IStorage
"/usr/pkg/share/ddnet",
"/usr/pkg/share/games/ddnet",
"/opt/ddnet"};
const int DirsCount = std::size(apDirs);

int i;
for(i = 0; i < DirsCount; i++)
for(const char *pDir : apDirs)
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "%s/data/mapres", apDirs[i]);
str_format(aBuf, sizeof(aBuf), "%s/data/mapres", pDir);
if(fs_is_dir(aBuf))
{
str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", apDirs[i]);
str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", pDir);
return;
}
}
Expand Down Expand Up @@ -309,14 +318,18 @@ class CStorage : public IStorage
if(Type == TYPE_ALL)
{
// list all available directories
for(int i = 0; i < m_NumPaths; ++i)
for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser);
}
else if(Type >= 0 && Type < m_NumPaths)
else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{
// list wanted directory
fs_listdir_fileinfo(GetPath(Type, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, Type, pUser);
}
else
{
dbg_assert(false, "Type invalid");
}
}

void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override
Expand All @@ -325,17 +338,21 @@ class CStorage : public IStorage
if(Type == TYPE_ALL)
{
// list all available directories
for(int i = 0; i < m_NumPaths; ++i)
for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser);
}
else if(Type >= 0 && Type < m_NumPaths)
else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{
// list wanted directory
fs_listdir(GetPath(Type, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, Type, pUser);
}
else
{
dbg_assert(false, "Type invalid");
}
}

virtual const char *GetPath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize)
const char *GetPath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize)
{
if(Type == TYPE_ABSOLUTE)
{
Expand Down Expand Up @@ -379,25 +396,27 @@ class CStorage : public IStorage
}
else
{
IOHANDLE Handle = 0;

if(Type <= TYPE_ALL)
{
// check all available directories
for(int i = 0; i < m_NumPaths; ++i)
for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
{
Handle = io_open(GetPath(i, pFilename, pBuffer, BufferSize), Flags);
IOHANDLE Handle = io_open(GetPath(i, pFilename, pBuffer, BufferSize), Flags);
if(Handle)
return Handle;
}
}
else if(Type >= 0 && Type < m_NumPaths)
else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{
// check wanted directory
Handle = io_open(GetPath(Type, pFilename, pBuffer, BufferSize), Flags);
IOHANDLE Handle = io_open(GetPath(Type, pFilename, pBuffer, BufferSize), Flags);
if(Handle)
return Handle;
}
else
{
dbg_assert(false, "Type invalid");
}
}

pBuffer[0] = 0;
Expand Down Expand Up @@ -466,41 +485,44 @@ class CStorage : public IStorage

bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) override
{
if(BufferSize < 1)
return false;
dbg_assert(BufferSize >= 1, "BufferSize invalid");

pBuffer[0] = 0;
char aBuf[IO_MAX_PATH_LENGTH];

CFindCBData Data;
Data.m_pStorage = this;
Data.m_pFilename = pFilename;
Data.m_pPath = pPath;
Data.m_pBuffer = pBuffer;
Data.m_BufferSize = BufferSize;

char aBuf[IO_MAX_PATH_LENGTH];
if(Type == TYPE_ALL)
{
// search within all available directories
for(int i = 0; i < m_NumPaths; ++i)
for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
{
fs_listdir(GetPath(i, pPath, aBuf, sizeof(aBuf)), FindFileCallback, i, &Data);
if(pBuffer[0])
return true;
}
}
else if(Type >= 0 && Type < m_NumPaths)
else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{
// search within wanted directory
fs_listdir(GetPath(Type, pPath, aBuf, sizeof(aBuf)), FindFileCallback, Type, &Data);
}
else
{
dbg_assert(false, "Type invalid");
}

return pBuffer[0] != 0;
}

bool RemoveFile(const char *pFilename, int Type) override
{
if(Type < TYPE_ABSOLUTE || Type == TYPE_ALL || Type >= m_NumPaths)
return false;
dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");

char aBuffer[IO_MAX_PATH_LENGTH];
GetPath(Type, pFilename, aBuffer, sizeof(aBuffer));
Expand All @@ -524,8 +546,7 @@ class CStorage : public IStorage

bool RenameFile(const char *pOldFilename, const char *pNewFilename, int Type) override
{
if(Type < 0 || Type >= m_NumPaths)
return false;
dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");

char aOldBuffer[IO_MAX_PATH_LENGTH];
char aNewBuffer[IO_MAX_PATH_LENGTH];
Expand All @@ -546,7 +567,10 @@ class CStorage : public IStorage
GetBinaryPath(pNewFilename, aNewBuffer, sizeof(aNewBuffer));

if(fs_makedir_rec_for(aNewBuffer) < 0)
{
dbg_msg("storage", "cannot create folder for: %s", aNewBuffer);
return false;
}

bool Success = !fs_rename(aOldBuffer, aNewBuffer);
if(!Success)
Expand All @@ -556,8 +580,7 @@ class CStorage : public IStorage

bool CreateFolder(const char *pFoldername, int Type) override
{
if(Type < 0 || Type >= m_NumPaths)
return false;
dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");

char aBuffer[IO_MAX_PATH_LENGTH];
GetPath(Type, pFoldername, aBuffer, sizeof(aBuffer));
Expand All @@ -570,13 +593,7 @@ class CStorage : public IStorage

void GetCompletePath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) override
{
if(Type < 0 || Type >= m_NumPaths)
{
if(BufferSize > 0)
pBuffer[0] = 0;
return;
}

dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
GetPath(Type, pDir, pBuffer, BufferSize);
}

Expand All @@ -588,14 +605,14 @@ class CStorage : public IStorage

static IStorage *Create(int StorageType, int NumArgs, const char **ppArguments)
{
CStorage *p = new CStorage();
if(p && p->Init(StorageType, NumArgs, ppArguments))
CStorage *pStorage = new CStorage();
if(pStorage && pStorage->Init(StorageType, NumArgs, ppArguments))
{
dbg_msg("storage", "initialisation failed");
delete p;
p = 0;
delete pStorage;
pStorage = nullptr;
}
return p;
return pStorage;
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/engine/sound.h
Expand Up @@ -110,7 +110,7 @@ class IEngineSound : public ISound
public:
virtual int Init() = 0;
virtual int Update() = 0;
virtual int Shutdown() = 0;
virtual void Shutdown() override = 0;
};

extern IEngineSound *CreateEngineSound();
Expand Down
2 changes: 1 addition & 1 deletion src/engine/steam.h
Expand Up @@ -18,7 +18,7 @@ class ISteam : public IInterface
virtual void Update() = 0;

virtual void ClearGameInfo() = 0;
virtual void SetGameInfo(NETADDR ServerAddr, const char *pMapName) = 0;
virtual void SetGameInfo(const NETADDR &ServerAddr, const char *pMapName, bool AnnounceAddr) = 0;
};

ISteam *CreateSteam();
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/camera.h
Expand Up @@ -2,12 +2,12 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_CAMERA_H
#define GAME_CLIENT_COMPONENTS_CAMERA_H
#include <base/bezier.h>
#include <base/vmath.h>

#include <engine/client.h>
#include <engine/console.h>

#include <game/bezier.h>
#include <game/client/component.h>

class CCamera : public CComponent
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/chat.cpp
Expand Up @@ -928,7 +928,7 @@ void CChat::RefindSkins()
{
if(Line.m_HasRenderTee)
{
const CSkin *pSkin = m_pClient->m_Skins.Get(m_pClient->m_Skins.Find(Line.m_aSkinName));
const CSkin *pSkin = m_pClient->m_Skins.Find(Line.m_aSkinName);
if(Line.m_CustomColoredSkin)
Line.m_RenderSkin = pSkin->m_ColorableSkin;
else
Expand Down
14 changes: 5 additions & 9 deletions src/game/client/components/ghost.cpp
Expand Up @@ -352,15 +352,14 @@ void CGhost::OnRender()
if(Player.m_Weapon == WEAPON_NINJA && g_Config.m_ClShowNinja)
{
// change the skin for the ghost to the ninja
int Skin = m_pClient->m_Skins.Find("x_ninja");
if(Skin != -1)
const auto *pSkin = m_pClient->m_Skins.FindOrNullptr("x_ninja");
if(pSkin != nullptr)
{
bool IsTeamplay = false;
if(m_pClient->m_Snap.m_pGameInfoObj)
IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) != 0;

GhostNinjaRenderInfo = Ghost.m_RenderInfo;
const CSkin *pSkin = m_pClient->m_Skins.Get(Skin);
GhostNinjaRenderInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
GhostNinjaRenderInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
GhostNinjaRenderInfo.m_BloodColor = pSkin->m_BloodColor;
Expand All @@ -387,8 +386,7 @@ void CGhost::InitRenderInfos(CGhostItem *pGhost)
IntsToStr(&pGhost->m_Skin.m_Skin0, 6, aSkinName);
CTeeRenderInfo *pRenderInfo = &pGhost->m_RenderInfo;

int SkinId = m_pClient->m_Skins.Find(aSkinName);
const CSkin *pSkin = m_pClient->m_Skins.Get(SkinId);
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin;
pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin;
pRenderInfo->m_BloodColor = pSkin->m_BloodColor;
Expand Down Expand Up @@ -679,8 +677,7 @@ void CGhost::RefindSkin()
{
CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo;

int SkinId = m_pClient->m_Skins.Find(aSkinName);
const CSkin *pSkin = m_pClient->m_Skins.Get(SkinId);
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin;
pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin;
pRenderInfo->m_SkinMetrics = pSkin->m_Metrics;
Expand All @@ -691,8 +688,7 @@ void CGhost::RefindSkin()
{
CTeeRenderInfo *pRenderInfo = &m_CurGhost.m_RenderInfo;

int SkinId = m_pClient->m_Skins.Find(aSkinName);
const CSkin *pSkin = m_pClient->m_Skins.Get(SkinId);
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin;
pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin;
pRenderInfo->m_SkinMetrics = pSkin->m_Metrics;
Expand Down
144 changes: 93 additions & 51 deletions src/game/client/components/menus.cpp
Expand Up @@ -11,6 +11,7 @@
#include <base/vmath.h>

#include <engine/client.h>
#include <engine/config.h>
#include <engine/editor.h>
#include <engine/friends.h>
#include <engine/graphics.h>
Expand Down Expand Up @@ -1037,6 +1038,15 @@ void CMenus::OnInit()
Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this);
}

void CMenus::OnConsoleInit()
{
auto *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
if(pConfigManager != nullptr)
pConfigManager->RegisterCallback(CMenus::ConfigSaveCallback, this);
Console()->Register("add_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_AddFavoriteSkin, this, "Add a skin as a favorite");
Console()->Register("remove_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_RemFavoriteSkin, this, "Remove a skin from the favorites");
}

void CMenus::ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
Expand Down Expand Up @@ -1114,11 +1124,15 @@ void CMenus::RenderColorPicker()
return;
}

// Prevent activation of UI elements outside of active color picker
if(UI()->MouseInside(&PickerRect))
UI()->SetHotItem(&ms_ColorPicker);

// Render
ColorRGBA BackgroundColor(0.1f, 0.1f, 0.1f, 1.0f);
PickerRect.Draw(BackgroundColor, 0, 0);

CUIRect ColorsArea, HueArea, ValuesHitbox, BottomArea, HSVHRect, HSVSRect, HSVVRect, HEXRect, ALPHARect;
CUIRect ColorsArea, HueArea, ValuesHitbox, BottomArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
PickerRect.Margin(3, &ColorsArea);

ColorsArea.HSplitBottom(ms_ColorPicker.ms_Height - 140.0f, &ColorsArea, &ValuesHitbox);
Expand All @@ -1128,21 +1142,21 @@ void CMenus::RenderColorPicker()
BottomArea.HSplitTop(3, 0x0, &BottomArea);
HueArea.VSplitLeft(3, 0x0, &HueArea);

BottomArea.HSplitTop(20, &HSVHRect, &BottomArea);
BottomArea.HSplitTop(20, &HueRect, &BottomArea);
BottomArea.HSplitTop(3, 0x0, &BottomArea);

constexpr float ValuePadding = 5.0f;
const float HSVValueWidth = (HSVHRect.w - ValuePadding * 2) / 3.0f;
const float HEXValueWidth = HSVValueWidth * 2 + ValuePadding;
const float HsvValueWidth = (HueRect.w - ValuePadding * 2) / 3.0f;
const float HexValueWidth = HsvValueWidth * 2 + ValuePadding;

HSVHRect.VSplitLeft(HSVValueWidth, &HSVHRect, &HSVSRect);
HSVSRect.VSplitLeft(ValuePadding, 0x0, &HSVSRect);
HSVSRect.VSplitLeft(HSVValueWidth, &HSVSRect, &HSVVRect);
HSVVRect.VSplitLeft(ValuePadding, 0x0, &HSVVRect);
HueRect.VSplitLeft(HsvValueWidth, &HueRect, &SatRect);
SatRect.VSplitLeft(ValuePadding, 0x0, &SatRect);
SatRect.VSplitLeft(HsvValueWidth, &SatRect, &ValueRect);
ValueRect.VSplitLeft(ValuePadding, 0x0, &ValueRect);

BottomArea.HSplitTop(20, &HEXRect, &BottomArea);
HEXRect.VSplitLeft(HEXValueWidth, &HEXRect, &ALPHARect);
ALPHARect.VSplitLeft(ValuePadding, 0x0, &ALPHARect);
BottomArea.HSplitTop(20, &HexRect, &BottomArea);
HexRect.VSplitLeft(HexValueWidth, &HexRect, &AlphaRect);
AlphaRect.VSplitLeft(ValuePadding, 0x0, &AlphaRect);

if(UI()->MouseButtonReleased(1) && !UI()->MouseInside(&ValuesHitbox))
{
Expand All @@ -1160,22 +1174,16 @@ void CMenus::RenderColorPicker()
ColorsArea.Draw(BlackColor, 0, 0);
ColorsArea.Margin(1, &ColorsArea);

unsigned int H = ms_ColorPicker.m_HSVColor / (1 << 16);
unsigned int S = (ms_ColorPicker.m_HSVColor - (H << 16)) / (1 << 8);
unsigned int V = ms_ColorPicker.m_HSVColor % (1 << 8);

ColorHSVA PickerColorHSV(ms_ColorPicker.m_HSVColor);
unsigned H = (unsigned)(PickerColorHSV.x * 255.0f);
unsigned S = (unsigned)(PickerColorHSV.y * 255.0f);
unsigned V = (unsigned)(PickerColorHSV.z * 255.0f);

// Color Area
ColorRGBA rgb;
rgb = color_cast<ColorRGBA, ColorHSVA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
vec4 TL(rgb.r, rgb.g, rgb.b, 1.0f);
rgb = color_cast<ColorRGBA, ColorHSVA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
vec4 TR(rgb.r, rgb.g, rgb.b, 1.0f);
rgb = color_cast<ColorRGBA, ColorHSVA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
vec4 BL(rgb.r, rgb.g, rgb.b, 1.0f);
rgb = color_cast<ColorRGBA, ColorHSVA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
vec4 BR(rgb.r, rgb.g, rgb.b, 1.0f);
vec4 TL = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
vec4 TR = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
vec4 BL = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
vec4 BR = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));

ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);

Expand Down Expand Up @@ -1210,59 +1218,54 @@ void CMenus::RenderColorPicker()
HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f);
}

//Editboxes Area
// Editboxes Area
ColorRGBA EditboxBackground(0, 0, 0, 0.4f);

static int RGBRID = 0;
static int RGBGID = 0;
static int RGBBID = 0;

H = DoValueSelector(&RGBRID, &HSVHRect, "H:", true, H, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
S = DoValueSelector(&RGBGID, &HSVSRect, "S:", true, S, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
V = DoValueSelector(&RGBBID, &HSVVRect, "V:", true, V, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
static int s_aValueSelectorIds[4];

PickerColorHSV = ColorHSVA((H << 16) + (S << 8) + V);
H = DoValueSelector(&s_aValueSelectorIds[0], &HueRect, "H:", true, H, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
S = DoValueSelector(&s_aValueSelectorIds[1], &SatRect, "S:", true, S, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
V = DoValueSelector(&s_aValueSelectorIds[2], &ValueRect, "V:", true, V, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);

unsigned int HEX = color_cast<ColorRGBA, ColorHSVA>(PickerColorHSV).Pack(false);
static int HEXID = 0;
PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f);

unsigned int NEWHEX = DoValueSelector(&HEXID, &HEXRect, "HEX:", false, HEX, 0, 0xFFFFFF, 1, 1, true, 5.0f, &EditboxBackground);
unsigned int Hex = color_cast<ColorRGBA>(PickerColorHSV).Pack(false);
unsigned int NewHex = DoValueSelector(&s_aValueSelectorIds[3], &HexRect, "HEX:", false, Hex, 0, 0xFFFFFF, 1, 1, true, 5.0f, &EditboxBackground);

if(HEX != NEWHEX)
PickerColorHSV = color_cast<ColorHSVA, ColorRGBA>(NEWHEX);
if(Hex != NewHex)
PickerColorHSV = color_cast<ColorHSVA>(ColorRGBA(NewHex));

// TODO : ALPHA SUPPORT
//static int ALPHAID = 0;
UI()->DoLabel(&ALPHARect, "A: 255", 10, TEXTALIGN_CENTER);
ALPHARect.Draw(ColorRGBA(0, 0, 0, 0.65f), IGraphics::CORNER_ALL, 5.0f);
UI()->DoLabel(&AlphaRect, "A: 255", 10, TEXTALIGN_CENTER);
AlphaRect.Draw(ColorRGBA(0, 0, 0, 0.65f), IGraphics::CORNER_ALL, 5.0f);

// Logic
float PickerX, PickerY;

static int ColorPickerID = 0;
static int HuePickerID = 0;
static int s_ColorPickerId = 0;
static int s_HuePickerId = 0;

if(UI()->DoPickerLogic(&ColorPickerID, &ColorsArea, &PickerX, &PickerY))
if(UI()->DoPickerLogic(&s_ColorPickerId, &ColorsArea, &PickerX, &PickerY))
{
PickerColorHSV.y = PickerX / ColorsArea.w;
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
}

if(UI()->DoPickerLogic(&HuePickerID, &HueArea, &PickerX, &PickerY))
if(UI()->DoPickerLogic(&s_HuePickerId, &HueArea, &PickerX, &PickerY))
PickerColorHSV.x = 1.0f - PickerY / HueArea.h;

// Marker Color Area
float MarkerX = ColorsArea.x + ColorsArea.w * PickerColorHSV.y;
float MarkerY = ColorsArea.y + ColorsArea.h * (1.0f - PickerColorHSV.z);

int MarkerOutlineInd = PickerColorHSV.z > 0.5f ? 0.0f : 1.0f;
const float MarkerOutlineInd = PickerColorHSV.z > 0.5f ? 0.0f : 1.0f;
ColorRGBA MarkerOutline(MarkerOutlineInd, MarkerOutlineInd, MarkerOutlineInd, 1.0f);

Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(MarkerOutline);
Graphics()->DrawCircle(MarkerX, MarkerY, 4.5f, 32);
Graphics()->SetColor(color_cast<ColorRGBA, ColorHSVA>(PickerColorHSV));
Graphics()->SetColor(color_cast<ColorRGBA>(PickerColorHSV));
Graphics()->DrawCircle(MarkerX, MarkerY, 3.5f, 32);
Graphics()->QuadsEnd();

Expand All @@ -1272,16 +1275,16 @@ void CMenus::RenderColorPicker()
HueMarker.h = 6.5f;
HueMarker.y = (HueArea.y + HueArea.h * (1.0f - PickerColorHSV.x)) - HueMarker.h / 2.0f;

ColorRGBA HueMarkerColor = color_cast<ColorRGBA, ColorHSVA>(ColorHSVA(PickerColorHSV.x, 1, 1, 1));
const float HMOColor = PickerColorHSV.x > 0.75f ? 1.0f : 0.0f;
ColorRGBA HueMarkerOutline(HMOColor, HMOColor, HMOColor, 1);
ColorRGBA HueMarkerColor = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1, 1, 1));
const float HueMarkerOutlineColor = PickerColorHSV.x > 0.75f ? 1.0f : 0.0f;
ColorRGBA HueMarkerOutline(HueMarkerOutlineColor, HueMarkerOutlineColor, HueMarkerOutlineColor, 1);

HueMarker.Draw(HueMarkerOutline, IGraphics::CORNER_ALL, 1.2f);
HueMarker.Margin(1.2f, &HueMarker);
HueMarker.Draw(HueMarkerColor, IGraphics::CORNER_ALL, 1.2f);

ms_ColorPicker.m_HSVColor = PickerColorHSV.Pack(false);
*ms_ColorPicker.m_pColor = color_cast<ColorHSLA, ColorHSVA>(PickerColorHSV).Pack(false);
*ms_ColorPicker.m_pColor = color_cast<ColorHSLA>(PickerColorHSV).Pack(false);
}

int CMenus::Render()
Expand All @@ -1305,6 +1308,7 @@ int CMenus::Render()
s_Frame++;
m_DoubleClickIndex = -1;

RefreshBrowserTab(g_Config.m_UiPage);
if(g_Config.m_UiPage == PAGE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
else if(g_Config.m_UiPage == PAGE_LAN)
Expand Down Expand Up @@ -1383,19 +1387,34 @@ int CMenus::Render()
if(Client()->State() != IClient::STATE_OFFLINE)
{
if(m_GamePage == PAGE_GAME)
{
RenderGame(MainView);
RenderIngameHint();
}
else if(m_GamePage == PAGE_PLAYERS)
{
RenderPlayers(MainView);
}
else if(m_GamePage == PAGE_SERVER_INFO)
{
RenderServerInfo(MainView);
}
else if(m_GamePage == PAGE_NETWORK)
{
RenderInGameNetwork(MainView);
}
else if(m_GamePage == PAGE_GHOST)
{
RenderGhost(MainView);
}
else if(m_GamePage == PAGE_CALLVOTE)
{
RenderServerControl(MainView);
}
else if(m_GamePage == PAGE_SETTINGS)
{
RenderSettings(MainView);
}
}
else if(m_MenuPage == PAGE_NEWS)
{
Expand Down Expand Up @@ -1790,6 +1809,7 @@ int CMenus::Render()
{
Client()->Disconnect();
m_Popup = POPUP_NONE;
RefreshBrowserTab(g_Config.m_UiPage);
}

if(Client()->MapDownloadTotalsize() > 0)
Expand Down Expand Up @@ -2734,6 +2754,28 @@ void CMenus::SetMenuPage(int NewPage)
g_Config.m_UiPage = NewPage;
}

void CMenus::RefreshBrowserTab(int UiPage)
{
if(UiPage == PAGE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
else if(UiPage == PAGE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
else if(UiPage == PAGE_FAVORITES)
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
else if(UiPage == PAGE_DDNET)
{
// start a new server list request
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
}
else if(UiPage == PAGE_KOG)
{
// start a new server list request
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}
}

bool CMenus::HandleListInputs(const CUIRect &View, float &ScrollValue, const float ScrollAmount, int *pScrollOffset, const float ElemHeight, int &SelectedIndex, const int NumElems)
{
if(NumElems == 0)
Expand Down
14 changes: 13 additions & 1 deletion src/game/client/components/menus.h
Expand Up @@ -7,6 +7,7 @@
#include <base/vmath.h>

#include <chrono>
#include <unordered_set>
#include <vector>

#include <engine/console.h>
Expand Down Expand Up @@ -414,7 +415,7 @@ class CMenus : public CComponent
const CDemoItem &Right = g_Config.m_BrDemoSortOrder ? *this : Other;

if(g_Config.m_BrDemoSort == SORT_DEMONAME)
return str_comp_nocase(Left.m_aFilename, Right.m_aFilename) < 0;
return str_comp_filenames(Left.m_aFilename, Right.m_aFilename) < 0;
if(g_Config.m_BrDemoSort == SORT_DATE)
return Left.m_Date < Right.m_Date;

Expand Down Expand Up @@ -507,6 +508,7 @@ class CMenus : public CComponent
void RenderServerControl(CUIRect MainView);
bool RenderServerControlKick(CUIRect MainView, bool FilterSpectators);
bool RenderServerControlServer(CUIRect MainView);
void RenderIngameHint();

// found in menus_browser.cpp
int m_SelectedIndex;
Expand All @@ -520,6 +522,14 @@ class CMenus : public CComponent
static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);

// skin favorite list
bool m_SkinFavoritesChanged = false;
std::unordered_set<std::string> m_SkinFavorites;
static void Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData);
static void Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData);
static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData);
void OnConfigSave(IConfigManager *pConfigManager);

// found in menus_settings.cpp
void RenderLanguageSelection(CUIRect MainView);
void RenderThemeSelection(CUIRect MainView, bool Header = true);
Expand Down Expand Up @@ -560,6 +570,7 @@ class CMenus : public CComponent
void KillServer();

virtual void OnInit() override;
void OnConsoleInit() override;

virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnReset() override;
Expand Down Expand Up @@ -705,6 +716,7 @@ class CMenus : public CComponent
private:
static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
void SetMenuPage(int NewPage);
void RefreshBrowserTab(int UiPage);
bool HandleListInputs(const CUIRect &View, float &ScrollValue, float ScrollAmount, int *pScrollOffset, float ElemHeight, int &SelectedIndex, int NumElems);

// found in menus_ingame.cpp
Expand Down
153 changes: 82 additions & 71 deletions src/game/client/components/menus_browser.cpp
Expand Up @@ -347,23 +347,34 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
else if(ID == COL_NAME)
{
float FontSize = 12.0f;
bool Printed = false;

if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME))
{
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, g_Config.m_BrFilterString);
if(pStr)
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
if(aFilterStr[0] == '\0')
{
continue;
}

// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
Printed = true;
break;
}
}
else
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_MAP)
Expand All @@ -382,23 +393,29 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
}

float FontSize = 12.0f;
bool Printed = false;

if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME))
{
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString);
if(pStr)
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(g_Config.m_BrFilterString), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(g_Config.m_BrFilterString), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
Printed = true;
break;
}
}
else
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_PLAYERS)
Expand Down Expand Up @@ -447,19 +464,19 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
{
ColorHSLA hsl = ColorHSLA(1.0f, 1.0f, 1.0f);

if(IsVanilla(pItem))
if(str_comp(pItem->m_aGameType, "DM") == 0 || str_comp(pItem->m_aGameType, "TDM") == 0 || str_comp(pItem->m_aGameType, "CTF") == 0)
hsl = ColorHSLA(0.33f, 1.0f, 0.75f);
else if(IsCatch(pItem))
else if(str_find_nocase(pItem->m_aGameType, "catch"))
hsl = ColorHSLA(0.17f, 1.0f, 0.75f);
else if(IsInsta(pItem))
else if(str_find_nocase(pItem->m_aGameType, "idm") || str_find_nocase(pItem->m_aGameType, "itdm") || str_find_nocase(pItem->m_aGameType, "ictf"))
hsl = ColorHSLA(0.00f, 1.0f, 0.75f);
else if(IsFNG(pItem))
else if(str_find_nocase(pItem->m_aGameType, "fng"))
hsl = ColorHSLA(0.83f, 1.0f, 0.75f);
else if(IsDDNet(pItem))
else if(str_find_nocase(pItem->m_aGameType, "ddracenet") || str_find_nocase(pItem->m_aGameType, "ddnet"))
hsl = ColorHSLA(0.58f, 1.0f, 0.75f);
else if(IsDDRace(pItem))
else if(str_find_nocase(pItem->m_aGameType, "ddrace") || str_find_nocase(pItem->m_aGameType, "mkrace"))
hsl = ColorHSLA(0.75f, 1.0f, 0.75f);
else if(IsRace(pItem))
else if(str_find_nocase(pItem->m_aGameType, "race") || str_find_nocase(pItem->m_aGameType, "fastcap"))
hsl = ColorHSLA(0.46f, 1.0f, 0.75f);

ColorRGBA rgb = color_cast<ColorRGBA>(hsl);
Expand Down Expand Up @@ -631,25 +648,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)

if(DoButtonMenu(m_RefreshButton, &s_RefreshButton, Func, 0, &ButtonRefresh, true, false, IGraphics::CORNER_ALL) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
{
if(g_Config.m_UiPage == PAGE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
else if(g_Config.m_UiPage == PAGE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
else if(g_Config.m_UiPage == PAGE_FAVORITES)
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
else if(g_Config.m_UiPage == PAGE_DDNET)
{
// start a new server list request
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
}
else if(g_Config.m_UiPage == PAGE_KOG)
{
// start a new server list request
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}
m_DoubleClickIndex = -1;
RefreshBrowserTab(g_Config.m_UiPage);
}

static int s_JoinButton = 0;
Expand Down Expand Up @@ -1168,7 +1167,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)

if(!pSelectedServer->m_aClients[i].m_Player)
str_copy(aTemp, "SPEC");
else if(IsRace(pSelectedServer) && g_Config.m_ClDDRaceScoreBoard)
else if((str_find_nocase(pSelectedServer->m_aGameType, "race") || str_find_nocase(pSelectedServer->m_aGameType, "fastcap")) && g_Config.m_ClDDRaceScoreBoard)
{
if(pSelectedServer->m_aClients[i].m_Score == -9999 || pSelectedServer->m_aClients[i].m_Score == 0)
aTemp[0] = 0;
Expand All @@ -1190,44 +1189,56 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Name.w;
const char *pName = pSelectedServer->m_aClients[i].m_aName;
bool Printed = false;
if(g_Config.m_BrFilterString[0])
{
// highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, g_Config.m_BrFilterString);
if(pFilteredStr)
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(g_Config.m_BrFilterString));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(g_Config.m_BrFilterString), -1);
// highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, aFilterStr);
if(pFilteredStr)
{
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
else
TextRender()->TextEx(&Cursor, pName, -1);
}
else
if(!Printed)
TextRender()->TextEx(&Cursor, pName, -1);

// clan
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Clan.w;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan;
Printed = false;
if(g_Config.m_BrFilterString[0])
{
// highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, g_Config.m_BrFilterString);
if(pFilteredString)
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(g_Config.m_BrFilterString));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(g_Config.m_BrFilterString), -1);
// highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, aFilterStr);
if(pFilteredString)
{
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
else
TextRender()->TextEx(&Cursor, pClan, -1);
}
else
if(!Printed)
TextRender()->TextEx(&Cursor, pClan, -1);

// flag
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/menus_demo.cpp
Expand Up @@ -513,7 +513,7 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
m_aDemoPlayerPopupHint[0] = '\0';
m_DemoPlayerState = DEMOPLAYER_SLICE_SAVE;
}
GameClient()->m_Tooltips.DoToolTip(&s_SliceSaveButton, &Button, Localize("Export cut as a seperate demo"));
GameClient()->m_Tooltips.DoToolTip(&s_SliceSaveButton, &Button, Localize("Export cut as a separate demo"));

// close button
ButtonBar.VSplitRight(ButtonbarHeight * 3, &ButtonBar, &Button);
Expand Down
10 changes: 10 additions & 0 deletions src/game/client/components/menus_ingame.cpp
Expand Up @@ -61,6 +61,7 @@ void CMenus::RenderGame(CUIRect MainView)
else
{
Client()->Disconnect();
RefreshBrowserTab(g_Config.m_UiPage);
}
}

Expand Down Expand Up @@ -1158,3 +1159,12 @@ void CMenus::RenderGhost(CUIRect MainView)
m_pClient->m_Ghost.SaveGhost(pGhost);
}
}

void CMenus::RenderIngameHint()
{
float Width = 300 * Graphics()->ScreenAspect();
Graphics()->MapScreen(0, 0, Width, 300);
TextRender()->TextColor(1, 1, 1, 1);
TextRender()->Text(0x0, 5, 280, 5, Localize("Menu opened. Press Esc key again to close menu."), -1.0f);
UI()->MapScreen();
}