97 changes: 59 additions & 38 deletions src/engine/client/serverbrowser.cpp
Expand Up @@ -218,7 +218,7 @@ void CServerBrowser::Filter()
{
Filtered = 1;
}
else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency)
else if(g_Config.m_BrFilterPing && g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency)
Filtered = 1;
else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0)
Filtered = 1;
Expand Down Expand Up @@ -596,7 +596,25 @@ void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServer
}

pEntry = Find(Addr);
if(m_ServerlistType != IServerBrowser::TYPE_LAN)

if(m_ServerlistType == IServerBrowser::TYPE_LAN)
{
NETADDR Broadcast;
mem_zero(&Broadcast, sizeof(Broadcast));
Broadcast.type = m_pNetClient->NetType()|NETTYPE_LINK_BROADCAST;
int Token = GenerateToken(Broadcast);
bool Drop = false;
Drop = Drop || BasicToken != GetBasicToken(Token);
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token));
if(Drop)
{
return;
}

if(!pEntry)
pEntry = Add(Addr);
}
else
{
if(!pEntry)
{
Expand All @@ -611,34 +629,16 @@ void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServer
return;
}
}
if(!pEntry)
pEntry = Add(Addr);
if(pEntry)

SetInfo(pEntry, *pInfo);
if (m_ServerlistType == IServerBrowser::TYPE_LAN)
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get()-m_BroadcastTime)*1000/time_freq()), 999);
else if (pEntry->m_RequestTime > 0)
{
if(m_ServerlistType == IServerBrowser::TYPE_LAN)
{
NETADDR Broadcast;
mem_zero(&Broadcast, sizeof(Broadcast));
Broadcast.type = m_pNetClient->NetType()|NETTYPE_LINK_BROADCAST;
int Token = GenerateToken(Broadcast);
bool Drop = false;
Drop = Drop || BasicToken != GetBasicToken(Token);
Drop = Drop || (pInfo->m_Type == SERVERINFO_EXTENDED && ExtraToken != GetExtraToken(Token));
if(Drop)
{
return;
}
}
SetInfo(pEntry, *pInfo);
if (m_ServerlistType == IServerBrowser::TYPE_LAN)
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get()-m_BroadcastTime)*1000/time_freq()), 999);
else if (pEntry->m_RequestTime > 0)
{
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get()-pEntry->m_RequestTime)*1000/time_freq()), 999);
pEntry->m_RequestTime = -1; // Request has been answered
}
RemoveRequest(pEntry);
pEntry->m_Info.m_Latency = minimum(static_cast<int>((time_get()-pEntry->m_RequestTime)*1000/time_freq()), 999);
pEntry->m_RequestTime = -1; // Request has been answered
}
RemoveRequest(pEntry);
}

Sort();
Expand All @@ -658,6 +658,7 @@ void CServerBrowser::Refresh(int Type)
m_RequestNumber++;

m_ServerlistType = Type;
secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed));

if(Type == IServerBrowser::TYPE_LAN)
{
Expand Down Expand Up @@ -705,16 +706,26 @@ void CServerBrowser::Refresh(int Type)
CountryFilterClean(NETWORK_DDNET);
TypeFilterClean(NETWORK_DDNET);

int MaxServers = 0;
for(int i = 0; i < m_aNetworks[NETWORK_DDNET].m_NumCountries; i++)
{
CNetworkCountry *pCntr = &m_aNetworks[NETWORK_DDNET].m_aCountries[i];
MaxServers = maximum(MaxServers, pCntr->m_NumServers);
}

// check for filter
if(DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pCntr->m_aName))
continue;

for(int g = 0; g < pCntr->m_NumServers; g++)
for(int g = 0; g < MaxServers; g++)
{
for(int i = 0; i < m_aNetworks[NETWORK_DDNET].m_NumCountries; i++)
{
CNetworkCountry *pCntr = &m_aNetworks[NETWORK_DDNET].m_aCountries[i];

// check for filter
if(DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pCntr->m_aName))
continue;

if(g >= pCntr->m_NumServers)
continue;

if(!DDNetFiltered(g_Config.m_BrFilterExcludeTypes, pCntr->m_aTypes[g]))
Set(pCntr->m_aServers[g], IServerBrowser::SET_DDNET_ADD, -1, 0);
}
Expand All @@ -726,16 +737,26 @@ void CServerBrowser::Refresh(int Type)
CountryFilterClean(NETWORK_KOG);
TypeFilterClean(NETWORK_KOG);

int MaxServers = 0;
for(int i = 0; i < m_aNetworks[NETWORK_KOG].m_NumCountries; i++)
{
CNetworkCountry *pCntr = &m_aNetworks[NETWORK_KOG].m_aCountries[i];
MaxServers = maximum(MaxServers, pCntr->m_NumServers);
}

// check for filter
if(DDNetFiltered(g_Config.m_BrFilterExcludeCountriesKoG, pCntr->m_aName))
continue;

for(int g = 0; g < pCntr->m_NumServers; g++)
for(int g = 0; g < MaxServers; g++)
{
for(int i = 0; i < m_aNetworks[NETWORK_KOG].m_NumCountries; i++)
{
CNetworkCountry *pCntr = &m_aNetworks[NETWORK_KOG].m_aCountries[i];

// check for filter
if(DDNetFiltered(g_Config.m_BrFilterExcludeCountriesKoG, pCntr->m_aName))
continue;

if(g >= pCntr->m_NumServers)
continue;

if(!DDNetFiltered(g_Config.m_BrFilterExcludeTypesKoG, pCntr->m_aTypes[g]))
Set(pCntr->m_aServers[g], IServerBrowser::SET_KOG_ADD, -1, 0);
}
Expand Down
35 changes: 28 additions & 7 deletions src/engine/client/steam.cpp
Expand Up @@ -4,32 +4,53 @@

class CSteam : public ISteam
{
ISteamApps *m_pSteamApps;
ISteamFriends *m_pSteamFriends;
char m_aPlayerName[16];

public:
CSteam()
{
ISteamFriends *pSteamFriends = SteamAPI_SteamFriends_v017();
str_copy(m_aPlayerName, SteamAPI_ISteamFriends_GetPersonaName(pSteamFriends), sizeof(m_aPlayerName));
m_pSteamApps = SteamAPI_SteamApps_v008();
m_pSteamFriends = SteamAPI_SteamFriends_v017();

char aCmdLine[128];
int CmdLineSize = SteamAPI_ISteamApps_GetLaunchCommandLine(m_pSteamApps, aCmdLine, sizeof(aCmdLine));
if(CmdLineSize >= 128)
{
CmdLineSize = 127;
}
aCmdLine[CmdLineSize] = 0;
dbg_msg("steam", "cmdline='%s' param_connect='%s'", aCmdLine, SteamAPI_ISteamApps_GetLaunchQueryParam(m_pSteamApps, "connect"));
str_copy(m_aPlayerName, SteamAPI_ISteamFriends_GetPersonaName(m_pSteamFriends), sizeof(m_aPlayerName));
}
~CSteam()
{
SteamAPI_Shutdown();
}


const char *GetPlayerName()
{
return m_aPlayerName;
}

void ClearGameInfo()
{
SteamAPI_ISteamFriends_ClearRichPresence(m_pSteamFriends);
}
void SetGameInfo(NETADDR ServerAddr, const char *pMapName)
{
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "status", pMapName);
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "steam_display", "#Status");
SteamAPI_ISteamFriends_SetRichPresence(m_pSteamFriends, "map", pMapName);
}
};

class CSteamStub : public ISteam
{
const char *GetPlayerName()
{
return 0;
}
const char *GetPlayerName() { return 0; }
void ClearGameInfo() { }
void SetGameInfo(NETADDR ServerAddr, const char *pMapName) { }
};

ISteam *CreateSteam()
Expand Down
94 changes: 63 additions & 31 deletions src/engine/client/text.cpp
Expand Up @@ -147,6 +147,7 @@ struct STextContainer

int m_Flags;
int m_LineCount;
int m_GlyphCount;
int m_CharCount;
int m_MaxLines;

Expand All @@ -167,7 +168,7 @@ struct STextContainer
m_StringInfo.m_CharacterQuads.clear();

m_AlignedStartX = m_AlignedStartY = m_X = m_Y = 0.f;
m_Flags = m_LineCount = m_CharCount = 0;
m_Flags = m_LineCount = m_CharCount = m_GlyphCount = 0;
m_MaxLines = -1;
m_StartX = m_StartY = 0.f;
m_LineWidth = -1.f;
Expand Down Expand Up @@ -237,15 +238,15 @@ class CTextRender : public IEngineTextRender

int WordLength(const char *pText)
{
int s = 1;
int Length = 0;
while(1)
{
if(*pText == 0)
return s-1;
if(*pText == '\n' || *pText == '\t' || *pText == ' ')
return s;
pText++;
s++;
const char *pCursor = (pText + Length);
if(*pCursor == 0)
return Length;
if(*pCursor == '\n' || *pCursor == '\t' || *pCursor == ' ')
return Length+1;
Length = str_utf8_forward(pText, Length);
}
}

Expand Down Expand Up @@ -675,10 +676,10 @@ class CTextRender : public IEngineTextRender

dbg_msg("textrender", "loaded pFont from '%s'", pFilename);

pFont->m_CurTextureDimensions[0] = 256;
pFont->m_CurTextureDimensions[0] = 1024;
pFont->m_TextureData[0] = new unsigned char[pFont->m_CurTextureDimensions[0] * pFont->m_CurTextureDimensions[0]];
mem_zero(pFont->m_TextureData[0], pFont->m_CurTextureDimensions[0] * pFont->m_CurTextureDimensions[0] * sizeof(unsigned char));
pFont->m_CurTextureDimensions[1] = 256;
pFont->m_CurTextureDimensions[1] = 1024;
pFont->m_TextureData[1] = new unsigned char[pFont->m_CurTextureDimensions[1] * pFont->m_CurTextureDimensions[1]];
mem_zero(pFont->m_TextureData[1], pFont->m_CurTextureDimensions[1] * pFont->m_CurTextureDimensions[1] * sizeof(unsigned char));

Expand Down Expand Up @@ -777,6 +778,7 @@ class CTextRender : public IEngineTextRender
pCursor->m_LineCount = 1;
pCursor->m_LineWidth = -1;
pCursor->m_Flags = Flags;
pCursor->m_GlyphCount = 0;
pCursor->m_CharCount = 0;
}

Expand Down Expand Up @@ -903,7 +905,7 @@ class CTextRender : public IEngineTextRender
// make sure there are no vertices
Graphics()->FlushVertices();

if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
Graphics()->TextureClear();
Graphics()->TextQuadsBegin();
Expand Down Expand Up @@ -938,17 +940,19 @@ class CTextRender : public IEngineTextRender
{
// word can't be fitted in one line, cut it
CTextCursor Cutter = *pCursor;
Cutter.m_GlyphCount = 0;
Cutter.m_CharCount = 0;
Cutter.m_X = DrawX;
Cutter.m_Y = DrawY;
Cutter.m_Flags &= ~TEXTFLAG_RENDER;
Cutter.m_Flags |= TEXTFLAG_STOP_AT_END;

TextEx(&Cutter, pCurrent, Wlen);
int WordGlyphs = Cutter.m_GlyphCount;
Wlen = Cutter.m_CharCount;
NewLine = 1;

if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next
if(WordGlyphs <= 3) // if we can't place 3 chars of the word on this line, take the next
Wlen = 0;
}
else if(Compare.m_X-pCursor->m_StartX > pCursor->m_LineWidth)
Expand All @@ -964,6 +968,7 @@ class CTextRender : public IEngineTextRender
int NextCharacter = str_utf8_decode(&pTmp);
while(pCurrent < pBatchEnd)
{
pCursor->m_CharCount += pTmp-pCurrent;
int Character = NextCharacter;
pCurrent = pTmp;
NextCharacter = str_utf8_decode(&pTmp);
Expand Down Expand Up @@ -1009,7 +1014,7 @@ class CTextRender : public IEngineTextRender

if(pCursor->m_Flags&TEXTFLAG_RENDER && m_Color.a != 0.f)
{
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
Graphics()->QuadsSetSubset(pChr->m_aUVs[0], pChr->m_aUVs[3], pChr->m_aUVs[2], pChr->m_aUVs[1]);
else
Graphics()->QuadsSetSubset(pChr->m_aUVs[0] * UVScale, pChr->m_aUVs[3] * UVScale, pChr->m_aUVs[2] * UVScale, pChr->m_aUVs[1] * UVScale);
Expand All @@ -1032,7 +1037,7 @@ class CTextRender : public IEngineTextRender
DrawX += BearingX + CharKerning + CharWidth;
else
DrawX += Advance*Size + CharKerning;
pCursor->m_CharCount++;
pCursor->m_GlyphCount++;

++CharacterCounter;
}
Expand All @@ -1054,7 +1059,7 @@ class CTextRender : public IEngineTextRender

if(pCursor->m_Flags&TEXTFLAG_RENDER)
{
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
float OutlineColor[4] = { m_OutlineColor.r, m_OutlineColor.g, m_OutlineColor.b, m_OutlineColor.a*m_Color.a };
Graphics()->TextQuadsEnd(pFont->m_CurTextureDimensions[0], pFont->m_aTextures[0], pFont->m_aTextures[1], OutlineColor);
Expand Down Expand Up @@ -1137,7 +1142,7 @@ class CTextRender : public IEngineTextRender
else
{
TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_CharacterQuads.size();
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
size_t DataSize = TextContainer.m_StringInfo.m_CharacterQuads.size() * sizeof(STextCharQuad);
void *pUploadData = &TextContainer.m_StringInfo.m_CharacterQuads[0];
Expand All @@ -1152,6 +1157,7 @@ class CTextRender : public IEngineTextRender
}

TextContainer.m_LineCount = pCursor->m_LineCount;
TextContainer.m_GlyphCount = pCursor->m_GlyphCount;
TextContainer.m_CharCount = pCursor->m_CharCount;
TextContainer.m_MaxLines = pCursor->m_MaxLines;
TextContainer.m_StartX = pCursor->m_StartX;
Expand Down Expand Up @@ -1243,17 +1249,19 @@ class CTextRender : public IEngineTextRender
{
// word can't be fitted in one line, cut it
CTextCursor Cutter = *pCursor;
Cutter.m_GlyphCount = 0;
Cutter.m_CharCount = 0;
Cutter.m_X = DrawX;
Cutter.m_Y = DrawY;
Cutter.m_Flags &= ~TEXTFLAG_RENDER;
Cutter.m_Flags |= TEXTFLAG_STOP_AT_END;

TextEx(&Cutter, pCurrent, Wlen);
int WordGlyphs = Cutter.m_GlyphCount;
Wlen = Cutter.m_CharCount;
NewLine = 1;

if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next
if(WordGlyphs <= 3) // if we can't place 3 chars of the word on this line, take the next
Wlen = 0;
}
else if(Compare.m_X - pCursor->m_StartX > pCursor->m_LineWidth)
Expand All @@ -1269,6 +1277,7 @@ class CTextRender : public IEngineTextRender
int NextCharacter = str_utf8_decode(&pTmp);
while(pCurrent < pBatchEnd)
{
TextContainer.m_CharCount += pTmp-pCurrent;
int Character = NextCharacter;
pCurrent = pTmp;
NextCharacter = str_utf8_decode(&pTmp);
Expand Down Expand Up @@ -1369,7 +1378,7 @@ class CTextRender : public IEngineTextRender
DrawX += BearingX + CharKerning + CharWidth;
else
DrawX += Advance * Size + CharKerning;
pCursor->m_CharCount++;
pCursor->m_GlyphCount++;
++CharacterCounter;
}
}
Expand All @@ -1392,7 +1401,7 @@ class CTextRender : public IEngineTextRender
{
TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_CharacterQuads.size();
// setup the buffers
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
size_t DataSize = TextContainer.m_StringInfo.m_CharacterQuads.size() * sizeof(STextCharQuad);
void *pUploadData = &TextContainer.m_StringInfo.m_CharacterQuads[0];
Expand Down Expand Up @@ -1505,6 +1514,7 @@ class CTextRender : public IEngineTextRender
CTextCursor FakeCursor;
SetCursor(&FakeCursor, DrawX, DrawY, TextContainer.m_UnscaledFontSize, TextContainer.m_Flags);
FakeCursor.m_LineCount = TextContainer.m_LineCount;
FakeCursor.m_GlyphCount = TextContainer.m_GlyphCount;
FakeCursor.m_CharCount = TextContainer.m_CharCount;
FakeCursor.m_MaxLines = TextContainer.m_MaxLines;
FakeCursor.m_StartX = TextContainer.m_StartX;
Expand All @@ -1524,17 +1534,19 @@ class CTextRender : public IEngineTextRender
{
// word can't be fitted in one line, cut it
CTextCursor Cutter = FakeCursor;
Cutter.m_GlyphCount = 0;
Cutter.m_CharCount = 0;
Cutter.m_X = DrawX;
Cutter.m_Y = DrawY;
Cutter.m_Flags &= ~TEXTFLAG_RENDER;
Cutter.m_Flags |= TEXTFLAG_STOP_AT_END;

TextEx(&Cutter, pCurrent, Wlen);
int WordGlyphs = Cutter.m_GlyphCount;
Wlen = Cutter.m_CharCount;
NewLine = 1;

if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next
if(WordGlyphs <= 3) // if we can't place 3 chars of the word on this line, take the next
Wlen = 0;
}
else if(Compare.m_X - TextContainer.m_StartX > TextContainer.m_LineWidth)
Expand All @@ -1551,6 +1563,7 @@ class CTextRender : public IEngineTextRender
int NextCharacter = str_utf8_decode(&pTmp);
while(pCurrent < pBatchEnd)
{
TextContainer.m_CharCount += pTmp-pCurrent;
int Character = NextCharacter;
pCurrent = pTmp;
NextCharacter = str_utf8_decode(&pTmp);
Expand Down Expand Up @@ -1611,7 +1624,7 @@ class CTextRender : public IEngineTextRender
else
DrawX += Advance * Size + CharKerning;

TextContainer.m_CharCount++;
TextContainer.m_GlyphCount++;
++CharacterCounter;
}
pCurrentLast = pCurrent;
Expand Down Expand Up @@ -1647,7 +1660,7 @@ class CTextRender : public IEngineTextRender
virtual void DeleteTextContainer(int TextContainerIndex)
{
STextContainer& TextContainer = GetTextContainer(TextContainerIndex);
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
if(TextContainer.m_StringInfo.m_QuadBufferContainerIndex != -1)
Graphics()->DeleteBufferContainer(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, true);
Expand Down Expand Up @@ -1675,7 +1688,7 @@ class CTextRender : public IEngineTextRender
s_CursorRenderTime = time_get_microseconds();
}

if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsTextBufferingEnabled())
{
Graphics()->TextureClear();
// render buffered text
Expand Down Expand Up @@ -1755,7 +1768,7 @@ class CTextRender : public IEngineTextRender
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
}

virtual void UploadEntityLayerText(IGraphics::CTextureHandle Texture, const char *pText, int Length, float x, float y, int FontSize)
virtual void UploadEntityLayerText(void* pTexBuff, int ImageColorChannelCount, int TexWidth, int TexHeight, const char *pText, int Length, float x, float y, int FontSize)
{
if (FontSize < 1)
return;
Expand Down Expand Up @@ -1794,27 +1807,46 @@ class CTextRender : public IEngineTextRender
mem_zero(ms_aGlyphData, SlotSize);

if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention
{
for(py = 0; py < (unsigned)SlotH; py++) // ignore_convention
for(px = 0; px < (unsigned)SlotW; px++)
{
ms_aGlyphData[(py)*SlotW + px] = pBitmap->buffer[py*pBitmap->width + px]; // ignore_convention
}
}

uint8_t* pImageBuff = (uint8_t*)pTexBuff;
for(int OffY = 0; OffY < SlotH; ++OffY)
{
for(int OffX = 0; OffX < SlotW; ++OffX)
{
for(py = 0; py < (unsigned)SlotH; py++) // ignore_convention
for(px = 0; px < (unsigned)SlotW; px++)
size_t ImageOffset = (y + OffY) * (TexWidth * ImageColorChannelCount) + ((x + OffX) + WidthLastChars) * ImageColorChannelCount;
size_t GlyphOffset = (OffY) * SlotW + OffX;
for(size_t i = 0; i < (size_t)ImageColorChannelCount; ++i)
{
if(i != (size_t)ImageColorChannelCount - 1)
{
*(pImageBuff + ImageOffset + i) = 255;
}
else
{
ms_aGlyphData[(py)*SlotW + px] = pBitmap->buffer[py*pBitmap->width + px]; // ignore_convention
*(pImageBuff + ImageOffset + i) = *(ms_aGlyphData + GlyphOffset);
}
}
}
}

Graphics()->LoadTextureRawSub(Texture, x + WidthLastChars, y, SlotW, SlotH, CImageInfo::FORMAT_ALPHA, ms_aGlyphData);
WidthLastChars += (SlotW + 1);

}
pCurrent = pTmp;
}
}

virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize = -1)
virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth)
{
int WidthOfText = CalculateTextWidth(pText, TextLength, 0, 100);

int FontSize = 100.f / ((float)WidthOfText / (float)MaxSize);
int FontSize = 100.f / ((float)WidthOfText / (float)MaxWidth);

if (MaxSize > 0 && FontSize > MaxSize)
FontSize = MaxSize;
Expand Down
46 changes: 44 additions & 2 deletions src/engine/graphics.h
Expand Up @@ -6,6 +6,7 @@
#include "kernel.h"

#include <base/color.h>
#include <stddef.h>

#include <vector>
#define GRAPHICS_TYPE_UNSIGNED_BYTE 0x1401
Expand Down Expand Up @@ -40,6 +41,22 @@ struct SQuadRenderInfo
float m_Rotation;
};

struct SGraphicTile
{
vec2 m_TopLeft;
vec2 m_TopRight;
vec2 m_BottomRight;
vec2 m_BottomLeft;
};

struct SGraphicTileTexureCoords
{
vec3 m_TexCoordTopLeft;
vec3 m_TexCoordTopRight;
vec3 m_TexCoordBottomRight;
vec3 m_TexCoordBottomLeft;
};

class CImageInfo
{
public:
Expand Down Expand Up @@ -80,6 +97,7 @@ class CVideoMode

struct GL_SPoint { float x, y; };
struct GL_STexCoord { float u, v; };
struct GL_STexCoord3D { float u, v, w; };
struct GL_SColorf { float r, g, b, a; };

//use normalized color values
Expand All @@ -92,6 +110,20 @@ struct GL_SVertex
GL_SColor m_Color;
};

struct GL_SVertexTex3D
{
GL_SPoint m_Pos;
GL_SColorf m_Color;
GL_STexCoord3D m_Tex;
};

struct SGraphicsWarning
{
SGraphicsWarning() : m_WasShown(false) {}
char m_aWarningMsg[128];
bool m_WasShown;
};

typedef void(*WINDOW_RESIZE_FUNC)(void *pUser);

class IGraphics : public IInterface
Expand All @@ -111,6 +143,11 @@ class IGraphics : public IInterface
TEXLOAD_NORESAMPLE = 1<<0,
TEXLOAD_NOMIPMAPS = 1<<1,
TEXLOAD_NO_COMPRESSION = 1<<2,
TEXLOAD_TO_3D_TEXTURE = (1 << 3),
TEXLOAD_TO_2D_ARRAY_TEXTURE = (1 << 4),
TEXLOAD_TO_3D_TEXTURE_SINGLE_LAYER = (1 << 5),
TEXLOAD_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER = (1 << 6),
TEXLOAD_NO_2D_TEXTURE = (1 << 7),
};


Expand Down Expand Up @@ -157,7 +194,7 @@ class IGraphics : public IInterface
virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) = 0;

virtual int UnloadTexture(CTextureHandle Index) = 0;
virtual CTextureHandle LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) = 0;
virtual CTextureHandle LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags, const char *pTexName = NULL) = 0;
virtual int LoadTextureRawSub(CTextureHandle TextureID, int x, int y, int Width, int Height, int Format, const void *pData) = 0;
virtual CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) = 0;
virtual void TextureSet(CTextureHandle Texture) = 0;
Expand Down Expand Up @@ -186,7 +223,11 @@ class IGraphics : public IInterface
virtual void UpdateBufferContainer(int ContainerIndex, struct SBufferContainerInfo *pContainerInfo) = 0;
virtual void IndicesNumRequiredNotify(unsigned int RequiredIndicesCount) = 0;

virtual bool IsBufferingEnabled() = 0;
virtual bool IsTileBufferingEnabled() = 0;
virtual bool IsQuadBufferingEnabled() = 0;
virtual bool IsTextBufferingEnabled() = 0;
virtual bool IsQuadContainerBufferingEnabled() = 0;
virtual bool HasTextureArrays() = 0;

struct CLineItem
{
Expand Down Expand Up @@ -276,6 +317,7 @@ class IGraphics : public IInterface
virtual void SetWindowGrab(bool Grab) = 0;
virtual void NotifyWindow() = 0;

virtual SGraphicsWarning *GetCurWarning() = 0;
protected:
inline CTextureHandle CreateTextureHandle(int Index)
{
Expand Down
1 change: 1 addition & 0 deletions src/engine/server.h
Expand Up @@ -41,6 +41,7 @@ class IServer : public IInterface
int Tick() const { return m_CurrentGameTick; }
int TickSpeed() const { return m_TickSpeed; }

virtual int Port() const = 0;
virtual int MaxClients() const = 0;
virtual int ClientCount() = 0;
virtual int DistinctClientCount() = 0;
Expand Down
6 changes: 4 additions & 2 deletions src/engine/server/databases/connection.cpp
Expand Up @@ -21,7 +21,8 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize)
"cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, "
"cp25 FLOAT DEFAULT 0, "
"GameID VARCHAR(64), "
"DDNet7 BOOL DEFAULT FALSE"
"DDNet7 BOOL DEFAULT FALSE, "
"PRIMARY KEY (Map, Name, Time, Timestamp, Server)"
");",
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
}
Expand All @@ -36,7 +37,8 @@ void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, co
"Time FLOAT DEFAULT 0, "
"ID %s NOT NULL, " // VARBINARY(16) for MySQL and BLOB for SQLite
"GameID VARCHAR(64), "
"DDNet7 BOOL DEFAULT FALSE"
"DDNet7 BOOL DEFAULT FALSE, "
"PRIMARY KEY (ID, Name)"
");",
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
}
Expand Down
2 changes: 2 additions & 0 deletions src/engine/server/databases/connection.h
Expand Up @@ -67,6 +67,8 @@ class IDbConnection
virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0;
// returns number of bytes read into the buffer
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0;
// syntax to insert a row into table or ignore if it already exists
virtual const char *GetInsertIgnore() const = 0;

// SQL statements, that can't be abstracted, has side effects to the result
virtual void AddPoints(const char *pPlayer, int Points) = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/engine/server/databases/mysql.cpp
Expand Up @@ -312,6 +312,11 @@ int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) c
#endif
}

const char *CMysqlConnection::GetInsertIgnore() const
{
return "INSERT IGNORE";
}

void CMysqlConnection::AddPoints(const char *pPlayer, int Points)
{
char aBuf[512];
Expand Down
1 change: 1 addition & 0 deletions src/engine/server/databases/mysql.h
Expand Up @@ -54,6 +54,7 @@ class CMysqlConnection : public IDbConnection
virtual int GetInt(int Col) const;
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual const char *GetInsertIgnore() const;

virtual void AddPoints(const char *pPlayer, int Points);

Expand Down
5 changes: 5 additions & 0 deletions src/engine/server/databases/sqlite.cpp
Expand Up @@ -205,6 +205,11 @@ int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
return Size;
}

const char *CSqliteConnection::GetInsertIgnore() const
{
return "INSERT OR IGNORE";
}

bool CSqliteConnection::Execute(const char *pQuery)
{
char *pErrorMsg;
Expand Down
1 change: 1 addition & 0 deletions src/engine/server/databases/sqlite.h
Expand Up @@ -42,6 +42,7 @@ class CSqliteConnection : public IDbConnection
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
// passing a negative buffer size is undefined behavior
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual const char *GetInsertIgnore() const;

virtual void AddPoints(const char *pPlayer, int Points);

Expand Down
4 changes: 2 additions & 2 deletions src/engine/server/register.cpp
Expand Up @@ -67,7 +67,7 @@ void CRegister::RegisterSendFwcheckresponse(NETADDR *pAddr, SECURITY_TOKEN Respo
void CRegister::RegisterSendHeartbeat(NETADDR Addr, SECURITY_TOKEN ResponseToken)
{
static unsigned char aData[sizeof(SERVERBROWSE_HEARTBEAT) + 2];
unsigned short Port = g_Config.m_SvPort;
unsigned short Port = m_pNetServer->Address().port;
CNetChunk Packet;

mem_copy(aData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT));
Expand Down Expand Up @@ -312,7 +312,7 @@ int CRegister::RegisterProcessPacket(CNetChunk *pPacket, SECURITY_TOKEN Response
{
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, "ERROR: the master server reports that clients can not connect to this server.");
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "ERROR: configure your firewall/nat to let through udp on port %d.", g_Config.m_SvPort);
str_format(aBuf, sizeof(aBuf), "ERROR: configure your firewall/nat to let through udp on port %d.", m_pNetServer->Address().port);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_pName, aBuf);
//RegisterNewState(REGISTERSTATE_ERROR);
return 1;
Expand Down
28 changes: 18 additions & 10 deletions src/engine/server/server.cpp
Expand Up @@ -618,6 +618,11 @@ bool CServer::ClientAuthed(int ClientID)
return ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_Authed;
}

int CServer::Port() const
{
return m_NetServer.Address().port;
}

int CServer::MaxClients() const
{
return m_NetServer.MaxClients();
Expand Down Expand Up @@ -1142,7 +1147,7 @@ void CServer::SendMap(int ClientID)
Msg.AddInt(m_aCurrentMapSize[Sixup]);
if(Sixup)
{
Msg.AddInt(1);
Msg.AddInt(g_Config.m_SvMapWindow);
Msg.AddInt(1024-128);
Msg.AddRaw(m_aCurrentMapSha256[Sixup].data, sizeof(m_aCurrentMapSha256[Sixup].data));
}
Expand Down Expand Up @@ -1419,8 +1424,10 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)

if(m_aClients[ClientID].m_Sixup)
{
SendMapData(ClientID, m_aClients[ClientID].m_NextMapChunk);
m_aClients[ClientID].m_NextMapChunk++;
for(int i = 0; i < g_Config.m_SvMapWindow; i++)
{
SendMapData(ClientID, m_aClients[ClientID].m_NextMapChunk++);
}
return;
}

Expand Down Expand Up @@ -2232,7 +2239,7 @@ int CServer::LoadMap(const char *pMapName)
if(i < MAX_CLIENTS)
{
char aPath[256];
str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, i);
str_format(aPath, sizeof(aPath), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, i);
Storage()->RemoveFile(aPath, IStorage::TYPE_SAVE);
}
}
Expand Down Expand Up @@ -2351,16 +2358,17 @@ int CServer::Run()

BindAddr.type = NetType;

for(BindAddr.port = g_Config.m_SvPort != 0 ? g_Config.m_SvPort : 8303; !m_NetServer.Open(BindAddr, &m_ServerBan, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0); BindAddr.port++)
int Port = g_Config.m_SvPort;
for(BindAddr.port = Port != 0 ? Port : 8303; !m_NetServer.Open(BindAddr, &m_ServerBan, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0); BindAddr.port++)
{
if(g_Config.m_SvPort != 0 || BindAddr.port >= 8310)
if(Port != 0 || BindAddr.port >= 8310)
{
dbg_msg("server", "couldn't open socket. port %d might already be in use", BindAddr.port);
return -1;
}
}

if(g_Config.m_SvPort == 0)
if(Port == 0)
dbg_msg("server", "using port %d", BindAddr.port);

#if defined(CONF_UPNP)
Expand Down Expand Up @@ -3020,7 +3028,7 @@ void CServer::SaveDemo(int ClientID, float Time)
// rename the demo
char aOldFilename[256];
char aNewFilename[256];
str_format(aOldFilename, sizeof(aOldFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID);
str_format(aOldFilename, sizeof(aOldFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientID);
str_format(aNewFilename, sizeof(aNewFilename), "demos/%s_%s_%5.2f.demo", m_aCurrentMap, m_aClients[ClientID].m_aName, Time);
Storage()->RenameFile(aOldFilename, aNewFilename, IStorage::TYPE_SAVE);
}
Expand All @@ -3031,7 +3039,7 @@ void CServer::StartRecord(int ClientID)
if(g_Config.m_SvPlayerDemoRecord)
{
char aFilename[128];
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID);
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientID);
m_aDemoRecorder[ClientID].Start(Storage(), Console(), aFilename, GameServer()->NetVersion(), m_aCurrentMap, &m_aCurrentMapSha256[SIX], m_aCurrentMapCrc[SIX], "server", m_aCurrentMapSize[SIX], m_apCurrentMapData[SIX]);
}
}
Expand All @@ -3043,7 +3051,7 @@ void CServer::StopRecord(int ClientID)
m_aDemoRecorder[ClientID].Stop();

char aFilename[128];
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID);
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, m_NetServer.Address().port, ClientID);
Storage()->RemoveFile(aFilename, IStorage::TYPE_SAVE);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/engine/server/server.h
Expand Up @@ -293,6 +293,7 @@ class CServer : public IServer
int ClientCountry(int ClientID);
bool ClientIngame(int ClientID);
bool ClientAuthed(int ClientID);
int Port() const;
int MaxClients() const;
int ClientCount();
int DistinctClientCount();
Expand Down
8 changes: 6 additions & 2 deletions src/engine/shared/config_variables.h
Expand Up @@ -116,6 +116,7 @@ MACRO_CONFIG_INT(GfxTextOverlay, gfx_text_overlay, 10, 1, 100, CFGFLAG_SAVE|CFGF
MACRO_CONFIG_INT(GfxAsyncRenderOld, gfx_asyncrender_old, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Do rendering async from the the update")
MACRO_CONFIG_INT(GfxTuneOverlay, gfx_tune_overlay, 20, 1, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Stop rendering text overlay in tuning zone in editor: high value = less details = more speed")
MACRO_CONFIG_INT(GfxQuadAsTriangle, gfx_quad_as_triangle, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Render quads as triangles (fixes quad coloring on some GPUs)")
MACRO_CONFIG_INT(GfxShowWarnings, gfx_show_warnings, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Render gfx warnings to screen")

MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 200, 1, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity")
MACRO_CONFIG_INT(InpMouseOld, inp_mouseold, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use old mouse mode (warp mouse instead of raw input)")
Expand Down Expand Up @@ -292,7 +293,7 @@ MACRO_CONFIG_INT(ClShowDecisecs, cl_show_decisecs, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG
MACRO_CONFIG_INT(SvResetPickups, sv_reset_pickups, 0, 0, 1, CFGFLAG_SERVER|CFGFLAG_GAME, "Whether the weapons are reset on passing the start tile or not")
MACRO_CONFIG_INT(ClShowOthers, cl_show_others, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show players in other teams")
MACRO_CONFIG_INT(ClShowOthersAlpha, cl_show_others_alpha, 40, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show players in other teams (alpha value, 0 invisible, 100 fully visible)")
MACRO_CONFIG_INT(ClOverlayEntities, cl_overlay_entities, 0, 0, 100, CFGFLAG_CLIENT, "Overlay game tiles with a percentage of opacity")
MACRO_CONFIG_INT(ClOverlayEntities, cl_overlay_entities, 0, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Overlay game tiles with a percentage of opacity")
MACRO_CONFIG_INT(ClShowQuads, cl_show_quads, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show quads")
MACRO_CONFIG_INT(ClZoomBackgroundLayers, cl_zoom_background_layers, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Zoom background layers")
MACRO_CONFIG_COL(ClBackgroundColor, cl_background_color, 128, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Background color") //0 0 128
Expand Down Expand Up @@ -374,7 +375,10 @@ MACRO_CONFIG_INT(ClDemoShowSpeed, cl_demo_show_speed, 0, 0, 1, CFGFLAG_SAVE|CFGF
MACRO_CONFIG_INT(ClDemoKeyboardShortcuts, cl_demo_keyboard_shortcuts, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Enable keyboard shortcuts in demo player")

//opengl
MACRO_CONFIG_INT(GfxOpenGL3, gfx_opengl3, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use new OpenGL3 with shaders")
MACRO_CONFIG_INT(GfxOpenGLMajor, gfx_opengl_major, 3, 1, 10, CFGFLAG_SAVE|CFGFLAG_CLIENT, "OpenGL major version")
MACRO_CONFIG_INT(GfxOpenGLMinor, gfx_opengl_minor, 0, 0, 10, CFGFLAG_SAVE|CFGFLAG_CLIENT, "OpenGL minor version")
MACRO_CONFIG_INT(GfxOpenGLPatch, gfx_opengl_patch, 0, 0, 10, CFGFLAG_SAVE|CFGFLAG_CLIENT, "OpenGL patch version")
MACRO_CONFIG_INT(Gfx3DTextureAnalysisDone, gfx_3d_texture_analysis_done, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Analyzed, if sampling 3D/2D array textures was correct")
#if !defined(CONF_PLATFORM_MACOSX)
MACRO_CONFIG_INT(GfxEnableTextureUnitOptimization, gfx_enable_texture_unit_optimization, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use multiple texture units, instead of only one.")
#else
Expand Down
2 changes: 2 additions & 0 deletions src/engine/shared/network.h
Expand Up @@ -309,6 +309,7 @@ class CNetServer
int m_Conns;
};

NETADDR m_Address;
NETSOCKET m_Socket;
MMSGS m_MMSGS;
class CNetBan *m_pNetBan;
Expand Down Expand Up @@ -367,6 +368,7 @@ class CNetServer
// status requests
const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
bool HasSecurityToken(int ClientID) const { return m_aSlots[ClientID].m_Connection.SecurityToken() != NET_SECURITY_TOKEN_UNSUPPORTED; }
NETADDR Address() const { return m_Address; }
NETSOCKET Socket() const { return m_Socket; }
class CNetBan *NetBan() const { return m_pNetBan; }
int NetType() const { return m_Socket.type; }
Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/network_server.cpp
Expand Up @@ -57,6 +57,7 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma
if(!m_Socket.type)
return false;

m_Address = BindAddr;
m_pNetBan = pNetBan;

// clamp clients
Expand Down
46 changes: 22 additions & 24 deletions src/engine/shared/packer.cpp
Expand Up @@ -33,36 +33,34 @@ void CPacker::AddString(const char *pStr, int Limit)
if(m_Error)
return;

//
if(Limit > 0)
if(Limit <= 0)
{
while(*pStr && Limit != 0)
{
*m_pCurrent++ = *pStr++;
Limit--;

if(m_pCurrent >= m_pEnd)
{
m_Error = 1;
break;
}
}
*m_pCurrent++ = 0;
Limit = PACKER_BUFFER_SIZE;
}
else
while(*pStr && Limit != 0)
{
while(*pStr)
int Codepoint = str_utf8_decode(&pStr);
if(Codepoint == -1)
{
*m_pCurrent++ = *pStr++;

if(m_pCurrent >= m_pEnd)
{
m_Error = 1;
break;
}
Codepoint = 0xfffd; // Unicode replacement character.
}
char aGarbage[4];
int Length = str_utf8_encode(aGarbage, Codepoint);
if(Limit < Length)
{
break;
}
// Ensure space for the null termination.
if(m_pEnd - m_pCurrent < Length + 1)
{
m_Error = 1;
break;
}
*m_pCurrent++ = 0;
Length = str_utf8_encode((char *)m_pCurrent, Codepoint);
m_pCurrent += Length;
Limit -= Length;
}
*m_pCurrent++ = 0;
}

void CPacker::AddRaw(const void *pData, int Size)
Expand Down
3 changes: 2 additions & 1 deletion src/engine/shared/packer.h
Expand Up @@ -7,11 +7,12 @@

class CPacker
{
public:
enum
{
PACKER_BUFFER_SIZE=1024*2
};

private:
unsigned char m_aBuffer[PACKER_BUFFER_SIZE];
unsigned char *m_pCurrent;
unsigned char *m_pEnd;
Expand Down
3 changes: 3 additions & 0 deletions src/engine/steam.h
Expand Up @@ -9,6 +9,9 @@ class ISteam : public IInterface
public:
// Returns NULL if the name cannot be determined.
virtual const char *GetPlayerName() = 0;

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

ISteam *CreateSteam();
Expand Down
6 changes: 4 additions & 2 deletions src/engine/textrender.h
Expand Up @@ -6,6 +6,7 @@

#include <base/color.h>
#include <engine/graphics.h>
#include <stdint.h>

enum
{
Expand Down Expand Up @@ -38,6 +39,7 @@ class CTextCursor
public:
int m_Flags;
int m_LineCount;
int m_GlyphCount;
int m_CharCount;
int m_MaxLines;

Expand Down Expand Up @@ -100,8 +102,8 @@ class ITextRender : public IInterface
virtual void RenderTextContainer(int TextContainerIndex, STextRenderColor *pTextColor, STextRenderColor *pTextOutlineColor) = 0;
virtual void RenderTextContainer(int TextContainerIndex, STextRenderColor *pTextColor, STextRenderColor *pTextOutlineColor, float X, float Y) = 0;

virtual void UploadEntityLayerText(IGraphics::CTextureHandle Texture, const char *pText, int Length, float x, float y, int FontHeight) = 0;
virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize = -1) = 0;
virtual void UploadEntityLayerText(void* pTexBuff, int ImageColorChannelCount, int TexWidth, int TexHeight, const char *pText, int Length, float x, float y, int FontHeight) = 0;
virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) = 0;
virtual int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontHeight) = 0;

// old foolish interface
Expand Down
10 changes: 8 additions & 2 deletions src/game/client/components/background.cpp
Expand Up @@ -50,14 +50,16 @@ void CBackground::LoadBackground()
m_pLayers = m_pBackgroundLayers;
m_pImages = m_pBackgroundImages;

bool NeedImageLoading = false;

str_copy(m_aMapName, g_Config.m_ClBackgroundEntities, sizeof(m_aMapName));
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "maps/%s", g_Config.m_ClBackgroundEntities);
if(m_pMap->Load(aBuf))
{
m_pLayers->InitBackground(m_pMap);
m_pImages->LoadBackground(m_pMap);
RenderTools()->RenderTilemapGenerateSkip(m_pLayers);
NeedImageLoading = true;
m_Loaded = true;
}
else if(str_comp(g_Config.m_ClBackgroundEntities, CURRENT) == 0)
Expand All @@ -71,8 +73,12 @@ void CBackground::LoadBackground()
}
}

if(m_Loaded)
if(m_Loaded)
{
CMapLayers::OnMapLoad();
if(NeedImageLoading)
m_pImages->LoadBackground(m_pLayers, m_pMap);
}

m_LastLoad = time_get();
}
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/camera.cpp
Expand Up @@ -38,7 +38,7 @@ void CCamera::ScaleZoom(float Factor)

void CCamera::ChangeZoom(float Target)
{
if(Target >= (Graphics()->IsBufferingEnabled()? 60 : 30))
if(Target >= (Graphics()->IsTileBufferingEnabled()? 60 : 30))
{
return;
}
Expand Down
6 changes: 3 additions & 3 deletions src/game/client/components/console.cpp
Expand Up @@ -710,7 +710,7 @@ void CGameConsole::OnRender()
TextRender()->Text(0, 10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f);

// render version
str_format(aBuf, sizeof(aBuf), "v%s", GAME_VERSION);
str_copy(aBuf, "v" GAME_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING, sizeof(aBuf));
float Width = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, Screen.w-Width-10.0f, FontSize / 2.f, FontSize, aBuf, -1.0f);
}
Expand Down Expand Up @@ -758,8 +758,8 @@ void CGameConsole::Toggle(int Type)

if (m_ConsoleState == CONSOLE_CLOSED || m_ConsoleState == CONSOLE_CLOSING)
{
/*Input()->MouseModeAbsolute();
m_pClient->m_pMenus->UseMouseButtons(false);*/
/*Input()->MouseModeAbsolute();*/
m_pClient->m_pMenus->UseMouseButtons(false);
m_ConsoleState = CONSOLE_OPENING;
/*// reset controls
m_pClient->m_pControls->OnReset();*/
Expand Down
127 changes: 82 additions & 45 deletions src/game/client/components/mapimages.cpp
Expand Up @@ -19,81 +19,98 @@ CMapImages::CMapImages(int TextureSize)
m_Count = 0;
m_TextureScale = TextureSize;
m_EntitiesIsLoaded = false;
m_SpeedupArrowIsLoaded = false;

mem_zero(m_aTextureUsedByTileOrQuadLayerFlag, sizeof(m_aTextureUsedByTileOrQuadLayerFlag));
}

void CMapImages::OnInit()
{
InitOverlayTextures();
}

void CMapImages::OnMapLoad()
void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
{
IMap *pMap = Kernel()->RequestInterface<IMap>();

// unload all textures
for(int i = 0; i < m_Count; i++)
{
Graphics()->UnloadTexture(m_aTextures[i]);
m_aTextures[i] = IGraphics::CTextureHandle();
m_aTextureUsedByTileOrQuadLayerFlag[i] = 0;
}
m_Count = 0;

int Start;
pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count);

// load new textures
for(int i = 0; i < m_Count; i++)
for(int g = 0; g < pLayers->NumGroups(); g++)
{
CMapItemImage *pImg = (CMapItemImage *)pMap->GetItem(Start+i, 0, 0);
if(pImg->m_External)
CMapItemGroup *pGroup = pLayers->GetGroup(g);
if(!pGroup)
{
char Buf[256];
char *pName = (char *)pMap->GetData(pImg->m_ImageName);
str_format(Buf, sizeof(Buf), "mapres/%s.png", pName);
m_aTextures[i] = Graphics()->LoadTexture(Buf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
continue;
}
else

for(int l = 0; l < pGroup->m_NumLayers; l++)
{
void *pData = pMap->GetData(pImg->m_ImageData);
m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, CImageInfo::FORMAT_RGBA, pData, CImageInfo::FORMAT_RGBA, 0);
pMap->UnloadData(pImg->m_ImageData);
CMapItemLayer *pLayer = pLayers->GetLayer(pGroup->m_StartLayer+l);
if(pLayer->m_Type == LAYERTYPE_TILES)
{
CMapItemLayerTilemap *pTLayer = (CMapItemLayerTilemap *)pLayer;
if(pTLayer->m_Image != -1 && pTLayer->m_Image < (int)(sizeof(m_aTextures) / sizeof(m_aTextures[0])))
{
m_aTextureUsedByTileOrQuadLayerFlag[(size_t)pTLayer->m_Image] |= 1;
}
}
else if(pLayer->m_Type == LAYERTYPE_QUADS)
{
CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer;
if(pQLayer->m_Image != -1 && pQLayer->m_Image < (int)(sizeof(m_aTextures) / sizeof(m_aTextures[0])))
{
m_aTextureUsedByTileOrQuadLayerFlag[(size_t)pQLayer->m_Image] |= 2;
}
}
}
}
}

void CMapImages::LoadBackground(class IMap *pMap)
{
// unload all textures
for(int i = 0; i < m_Count; i++)
{
Graphics()->UnloadTexture(m_aTextures[i]);
m_aTextures[i] = IGraphics::CTextureHandle();
}
m_Count = 0;

int Start;
pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count);
int TextureLoadFlag = Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;

// load new textures
for(int i = 0; i < m_Count; i++)
{
int LoadFlag = (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 1) != 0) ? TextureLoadFlag : 0) | (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 2) != 0) ? 0 : (Graphics()->IsTileBufferingEnabled() ? IGraphics::TEXLOAD_NO_2D_TEXTURE : 0));
CMapItemImage *pImg = (CMapItemImage *)pMap->GetItem(Start+i, 0, 0);
if(pImg->m_External)
{
char Buf[256];
char *pName = (char *)pMap->GetData(pImg->m_ImageName);
str_format(Buf, sizeof(Buf), "mapres/%s.png", pName);
m_aTextures[i] = Graphics()->LoadTexture(Buf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
m_aTextures[i] = Graphics()->LoadTexture(Buf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, LoadFlag);
}
else
{
void *pData = pMap->GetData(pImg->m_ImageData);
m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, CImageInfo::FORMAT_RGBA, pData, CImageInfo::FORMAT_RGBA, 0);
char *pName = (char *)pMap->GetData(pImg->m_ImageName);
char aTexName[128];
str_format(aTexName, sizeof(aTexName), "%s %s", "embedded:", pName);
m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, CImageInfo::FORMAT_RGBA, pData, CImageInfo::FORMAT_RGBA, LoadFlag, aTexName);
pMap->UnloadData(pImg->m_ImageData);
}
}
}

void CMapImages::OnMapLoad()
{
IMap *pMap = Kernel()->RequestInterface<IMap>();
CLayers *pLayers = m_pClient->Layers();
OnMapLoadImpl(pLayers, pMap);
}

void CMapImages::LoadBackground(class CLayers *pLayers, class IMap *pMap)
{
OnMapLoadImpl(pLayers, pMap);
}

IGraphics::CTextureHandle CMapImages::GetEntities()
{
// DDNet default to prevent delay in seeing entities
Expand All @@ -118,13 +135,27 @@ IGraphics::CTextureHandle CMapImages::GetEntities()

if(m_EntitiesTextures >= 0)
Graphics()->UnloadTexture(m_EntitiesTextures);
m_EntitiesTextures = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
int TextureLoadFlag = Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
m_EntitiesTextures = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, TextureLoadFlag);
m_EntitiesIsLoaded = true;
m_pEntitiesGameType = pEntities;
}
return m_EntitiesTextures;
}

IGraphics::CTextureHandle CMapImages::GetSpeedupArrow()
{
if(!m_SpeedupArrowIsLoaded)
{
int TextureLoadFlag = (Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER : IGraphics::TEXLOAD_TO_3D_TEXTURE_SINGLE_LAYER) | IGraphics::TEXLOAD_NO_2D_TEXTURE;
m_SpeedupArrowTexture = Graphics()->LoadTexture(g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, TextureLoadFlag);

m_SpeedupArrowIsLoaded = true;
}

return m_SpeedupArrowTexture;
}

IGraphics::CTextureHandle CMapImages::GetOverlayBottom()
{
return m_OverlayBottomTexture;
Expand Down Expand Up @@ -154,6 +185,10 @@ void CMapImages::SetTextureScale(int Scale)
Graphics()->UnloadTexture(m_OverlayTopTexture);
Graphics()->UnloadTexture(m_OverlayCenterTexture);

m_OverlayBottomTexture = IGraphics::CTextureHandle();
m_OverlayTopTexture = IGraphics::CTextureHandle();
m_OverlayCenterTexture = IGraphics::CTextureHandle();

InitOverlayTextures();
}
}
Expand All @@ -163,20 +198,22 @@ int CMapImages::GetTextureScale()
return m_TextureScale;
}

IGraphics::CTextureHandle CMapImages::UploadEntityLayerText(int TextureSize, int YOffset)
IGraphics::CTextureHandle CMapImages::UploadEntityLayerText(int TextureSize, int MaxWidth, int YOffset)
{
void *pMem = calloc(1024 * 1024, 1);
IGraphics::CTextureHandle Texture = Graphics()->LoadTextureRaw(1024, 1024, CImageInfo::FORMAT_ALPHA, pMem, CImageInfo::FORMAT_ALPHA, IGraphics::TEXLOAD_NOMIPMAPS);
free(pMem);
void *pMem = calloc(1024 * 1024 * 4, 1);

UpdateEntityLayerText(Texture, TextureSize, YOffset, 0);
UpdateEntityLayerText(Texture, TextureSize, YOffset, 1);
UpdateEntityLayerText(Texture, TextureSize, YOffset, 2, 255);
UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 0);
UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 1);
UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 2, 255);

int TextureLoadFlag = (Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE) | IGraphics::TEXLOAD_NO_2D_TEXTURE;
IGraphics::CTextureHandle Texture = Graphics()->LoadTextureRaw(1024, 1024, CImageInfo::FORMAT_RGBA, pMem, CImageInfo::FORMAT_RGBA, TextureLoadFlag);
free(pMem);

return Texture;
}

void CMapImages::UpdateEntityLayerText(IGraphics::CTextureHandle Texture, int TextureSize, int YOffset, int NumbersPower, int MaxNumber)
void CMapImages::UpdateEntityLayerText(void* pTexBuffer, int ImageColorChannelCount, int TexWidth, int TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber)
{
char aBuf[4];
int DigitsCount = NumbersPower+1;
Expand All @@ -188,8 +225,8 @@ void CMapImages::UpdateEntityLayerText(IGraphics::CTextureHandle Texture, int Te

str_format(aBuf, 4, "%d", CurrentNumber);

int CurrentNumberSuitableFontSize = TextRender()->AdjustFontSize(aBuf, DigitsCount, TextureSize);
int UniversalSuitableFontSize = CurrentNumberSuitableFontSize*0.9; // should be smoothed enough to fit any digits combination
int CurrentNumberSuitableFontSize = TextRender()->AdjustFontSize(aBuf, DigitsCount, TextureSize, MaxWidth);
int UniversalSuitableFontSize = CurrentNumberSuitableFontSize*0.95f; // should be smoothed enough to fit any digits combination

int ApproximateTextWidth = TextRender()->CalculateTextWidth(aBuf, DigitsCount, 0, UniversalSuitableFontSize);
int XOffSet = (64-ApproximateTextWidth)/2;
Expand All @@ -202,7 +239,7 @@ void CMapImages::UpdateEntityLayerText(IGraphics::CTextureHandle Texture, int Te
float x = (CurrentNumber%16)*64;
float y = (CurrentNumber/16)*64;

TextRender()->UploadEntityLayerText(Texture, aBuf, DigitsCount, x+XOffSet, y+YOffset, UniversalSuitableFontSize);
TextRender()->UploadEntityLayerText(pTexBuffer, ImageColorChannelCount, TexWidth, TexHeight, aBuf, DigitsCount, x+XOffSet, y+YOffset, UniversalSuitableFontSize);
}
}

Expand All @@ -213,16 +250,16 @@ void CMapImages::InitOverlayTextures()

if(m_OverlayBottomTexture == -1)
{
m_OverlayBottomTexture = UploadEntityLayerText(TextureSize/2, 32+TextureToVerticalCenterOffset/2);
m_OverlayBottomTexture = UploadEntityLayerText(TextureSize/2, 64, 32+TextureToVerticalCenterOffset/2);
}

if(m_OverlayTopTexture == -1)
{
m_OverlayTopTexture = UploadEntityLayerText(TextureSize/2, TextureToVerticalCenterOffset/2);
m_OverlayTopTexture = UploadEntityLayerText(TextureSize/2, 64, TextureToVerticalCenterOffset/2);
}

if(m_OverlayCenterTexture == -1)
{
m_OverlayCenterTexture = UploadEntityLayerText(TextureSize, TextureToVerticalCenterOffset);
m_OverlayCenterTexture = UploadEntityLayerText(TextureSize, 64, TextureToVerticalCenterOffset);
}
}
12 changes: 8 additions & 4 deletions src/game/client/components/mapimages.h
Expand Up @@ -9,6 +9,7 @@ class CMapImages : public CComponent
friend class CBackground;

IGraphics::CTextureHandle m_aTextures[64];
int m_aTextureUsedByTileOrQuadLayerFlag[64]; // 0: nothing, 1(as flag): tile layer, 2(as flag): quad layer
int m_Count;

const char *m_pEntitiesGameType;
Expand All @@ -19,12 +20,14 @@ class CMapImages : public CComponent
IGraphics::CTextureHandle Get(int Index) const { return m_aTextures[Index]; }
int Num() const { return m_Count; }

void OnMapLoadImpl(class CLayers *pLayers, class IMap *pMap);
virtual void OnMapLoad();
virtual void OnInit();
void LoadBackground(class IMap *pMap);
void LoadBackground(class CLayers *pLayers, class IMap *pMap);

// DDRace
IGraphics::CTextureHandle GetEntities();
IGraphics::CTextureHandle GetSpeedupArrow();

IGraphics::CTextureHandle GetOverlayBottom();
IGraphics::CTextureHandle GetOverlayTop();
Expand All @@ -34,17 +37,18 @@ class CMapImages : public CComponent
int GetTextureScale();

private:

bool m_EntitiesIsLoaded;
bool m_SpeedupArrowIsLoaded;
IGraphics::CTextureHandle m_EntitiesTextures;
IGraphics::CTextureHandle m_SpeedupArrowTexture;
IGraphics::CTextureHandle m_OverlayBottomTexture;
IGraphics::CTextureHandle m_OverlayTopTexture;
IGraphics::CTextureHandle m_OverlayCenterTexture;
int m_TextureScale;

void InitOverlayTextures();
IGraphics::CTextureHandle UploadEntityLayerText(int TextureSize, int YOffset);
void UpdateEntityLayerText(IGraphics::CTextureHandle Texture, int TextureSize, int YOffset, int NumbersPower, int MaxNumber = -1);
IGraphics::CTextureHandle UploadEntityLayerText(int TextureSize, int MaxWidth, int YOffset);
void UpdateEntityLayerText(void* pTexBuffer, int ImageColorChannelCount, int TexWidth, int TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber = -1);
};

#endif
380 changes: 169 additions & 211 deletions src/game/client/components/maplayers.cpp

Large diffs are not rendered by default.

64 changes: 56 additions & 8 deletions src/game/client/components/menus.cpp
Expand Up @@ -977,9 +977,31 @@ void CMenus::PopupMessage(const char *pTopic, const char *pBody, const char *pBu
m_Popup = POPUP_MESSAGE;
}

void CMenus::PopupWarning(const char *pTopic, const char *pBody, const char *pButton, int64 Duration)
{
// reset active item
UI()->SetActiveItem(0);

str_copy(m_aMessageTopic, pTopic, sizeof(m_aMessageTopic));
str_copy(m_aMessageBody, pBody, sizeof(m_aMessageBody));
str_copy(m_aMessageButton, pButton, sizeof(m_aMessageButton));
m_Popup = POPUP_WARNING;
SetActive(true);

m_PopupWarningDuration = Duration;
m_PopupWarningLastTime = time_get_microseconds();
}

bool CMenus::CanDisplayWarning()
{
return m_Popup == POPUP_NONE && (Client()->State() == IClient::STATE_DEMOPLAYBACK || Client()->State() == IClient::STATE_ONLINE);
}

int CMenus::Render()
{
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_Popup == POPUP_NONE)
return 0;

CUIRect Screen = *UI()->Screen();
Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h);

Expand Down Expand Up @@ -1008,7 +1030,7 @@ int CMenus::Render()
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}

if(Client()->State() == IClient::STATE_ONLINE)
if(Client()->State() >= IClient::STATE_ONLINE)
{
ms_ColorTabbarInactive = ms_ColorTabbarInactiveIngame;
ms_ColorTabbarActive = ms_ColorTabbarActiveIngame;
Expand Down Expand Up @@ -1095,6 +1117,7 @@ int CMenus::Render()
const char *pButtonText = "";
int ExtraAlign = 0;

ColorRGBA BgColor = ColorRGBA{0.0f, 0.0f, 0.0f, 0.5f};
if(m_Popup == POPUP_MESSAGE)
{
pTitle = m_aMessageTopic;
Expand Down Expand Up @@ -1213,6 +1236,14 @@ int CMenus::Render()
pButtonText = Localize("Ok");
ExtraAlign = -1;
}
else if(m_Popup == POPUP_WARNING)
{
BgColor = ColorRGBA{0.5f, 0.0f, 0.0f, 0.7f};
pTitle = m_aMessageTopic;
pExtraText = m_aMessageBody;
pButtonText = m_aMessageButton;
ExtraAlign = -1;
}

CUIRect Box, Part;
Box = Screen;
Expand All @@ -1223,7 +1254,7 @@ int CMenus::Render()
}

// render the box
RenderTools()->DrawUIRect(&Box, ColorRGBA(0,0,0,0.5f), CUI::CORNER_ALL, 15.0f);
RenderTools()->DrawUIRect(&Box, BgColor, CUI::CORNER_ALL, 15.0f);

Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box);
Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box);
Expand Down Expand Up @@ -1318,6 +1349,7 @@ int CMenus::Render()
{
Client()->DummyDisconnect(0);
m_Popup = POPUP_NONE;
SetActive(false);
}
}
else if(m_Popup == POPUP_PASSWORD)
Expand Down Expand Up @@ -1454,7 +1486,7 @@ int CMenus::Render()
ActSelection = g_Config.m_BrFilterCountryIndex;
static float s_ScrollValue = 0.0f;
int OldSelected = -1;
UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue);
UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country / Region"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue);

for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i)
{
Expand Down Expand Up @@ -1807,6 +1839,19 @@ int CMenus::Render()
static float Offset = 0.0f;
DoEditBox(&g_Config.m_PlayerName, &TextBox, g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName), 12.0f, &Offset, false, CUI::CORNER_ALL, Client()->PlayerName());
}
else if(m_Popup == POPUP_WARNING)
{
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Part.VMargin(120.0f, &Part);

static int s_Button = 0;
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || m_EscapePressed || m_EnterPressed || (time_get_microseconds() - m_PopupWarningLastTime >= m_PopupWarningDuration))
{
m_Popup = POPUP_NONE;
SetActive(false);
}
}
else
{
Box.HSplitBottom(20.f, &Box, &Part);
Expand Down Expand Up @@ -1954,8 +1999,11 @@ void CMenus::OnStateChange(int NewState, int OldState)
m_Popup = POPUP_CONNECTING;
else if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK)
{
m_Popup = POPUP_NONE;
SetActive(false);
if(m_Popup != POPUP_WARNING)
{
m_Popup = POPUP_NONE;
SetActive(false);
}
}
}

Expand Down Expand Up @@ -2025,8 +2073,7 @@ void CMenus::OnRender()
UI()->Update(mx,my,mx*3.0f,my*3.0f,Buttons);

// render
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
Render();
Render();

// render cursor
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id);
Expand Down Expand Up @@ -2057,6 +2104,8 @@ void CMenus::OnRender()

void CMenus::RenderBackground()
{
Graphics()->BlendNormal();

float sw = 300*Graphics()->ScreenAspect();
float sh = 300;
Graphics()->MapScreen(0, 0, sw, sh);
Expand Down Expand Up @@ -2133,7 +2182,6 @@ void CMenus::RenderUpdating(const char *pCaption, int current, int total)
CUIRect Screen = *UI()->Screen();
Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h);

Graphics()->BlendNormal();
RenderBackground();

float w = 700;
Expand Down
8 changes: 8 additions & 0 deletions src/game/client/components/menus.h
Expand Up @@ -396,6 +396,13 @@ class CMenus : public CComponent
void DeleteGhostItem(int Index);

void setPopup(int Popup) { m_Popup = Popup; }
int GetCurPopup() { return m_Popup; }
bool CanDisplayWarning();

void PopupWarning(const char *pTopic, const char *pBody, const char *pButton, int64 Duration);

int64 m_PopupWarningLastTime;
int64 m_PopupWarningDuration;

int m_DemoPlayerState;
char m_aDemoPlayerPopupHint[256];
Expand All @@ -420,6 +427,7 @@ class CMenus : public CComponent
POPUP_QUIT,
POPUP_DISCONNECT,
POPUP_DISCONNECT_DUMMY,
POPUP_WARNING,

// demo player states
DEMOPLAYER_NONE=0,
Expand Down
12 changes: 8 additions & 4 deletions src/game/client/components/menus_browser.cpp
Expand Up @@ -614,11 +614,15 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)

UI()->DoLabelScaled(&Button, Localize("Maximum ping:"), FontSize, -1);

char aBuf[5];
str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_BrFilterPing);
char aBuf[5] = "";
if(g_Config.m_BrFilterPing != 0)
str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_BrFilterPing);
static float Offset = 0.0f;
DoEditBox(&g_Config.m_BrFilterPing, &EditBox, aBuf, sizeof(aBuf), FontSize, &Offset);
g_Config.m_BrFilterPing = clamp(str_toint(aBuf), 0, 999);
if (DoEditBox(&g_Config.m_BrFilterPing, &EditBox, aBuf, sizeof(aBuf), FontSize, &Offset))
{
g_Config.m_BrFilterPing = clamp(str_toint(aBuf), 0, 999);
Client()->ServerBrowserUpdate();
}
}

// server address
Expand Down
108 changes: 74 additions & 34 deletions src/game/client/components/menus_ingame.cpp
Expand Up @@ -32,7 +32,7 @@

void CMenus::RenderGame(CUIRect MainView)
{
CUIRect Button, ButtonBar;
CUIRect Button, ButtonBar, ButtonBar2;
MainView.HSplitTop(45.0f, &ButtonBar, &MainView);
RenderTools()->DrawUIRect(&ButtonBar, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);

Expand All @@ -41,7 +41,11 @@ void CMenus::RenderGame(CUIRect MainView)
ButtonBar.HSplitTop(25.0f, &ButtonBar, 0);
ButtonBar.VMargin(10.0f, &ButtonBar);

ButtonBar.HSplitTop(30.0f, 0, &ButtonBar2);
ButtonBar2.HSplitTop(25.0f, &ButtonBar2, 0);

ButtonBar.VSplitRight(120.0f, &ButtonBar, &Button);

static int s_DisconnectButton = 0;
if(DoButton_Menu(&s_DisconnectButton, Localize("Disconnect"), 0, &Button))
{
Expand All @@ -55,20 +59,64 @@ void CMenus::RenderGame(CUIRect MainView)
}
}

ButtonBar.VSplitRight(5.0f, &ButtonBar, 0);
ButtonBar.VSplitRight(170.0f, &ButtonBar, &Button);

bool DummyConnecting = Client()->DummyConnecting();
static int s_DummyButton = 0;
if(DummyConnecting)
{
DoButton_Menu(&s_DummyButton, Localize("Connecting dummy"), 1, &Button);
}
else if(DoButton_Menu(&s_DummyButton, Client()->DummyConnected() ? Localize("Disconnect Dummy") : Localize("Connect Dummy"), 0, &Button))
{
if(!Client()->DummyConnected())
{
Client()->DummyConnect();
}
else
{
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
{
m_Popup = POPUP_DISCONNECT_DUMMY;
}
else
{
Client()->DummyDisconnect(0);
SetActive(false);
}
}
}

ButtonBar.VSplitRight(5.0f, &ButtonBar, 0);
ButtonBar.VSplitRight(140.0f, &ButtonBar, &Button);

static int s_DemoButton = 0;
bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording();
if(DoButton_Menu(&s_DemoButton, Recording ? Localize("Stop record") : Localize("Record demo"), 0, &Button))
{
if(!Recording)
Client()->DemoRecorder_Start(Client()->GetCurrentMap(), true, RECORDER_MANUAL);
else
Client()->DemoRecorder_Stop(RECORDER_MANUAL);
}

static int s_SpectateButton = 0;
static int s_JoinRedButton = 0;
static int s_JoinBlueButton = 0;
bool DummyConnecting = m_pClient->Client()->DummyConnecting();

if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj)
bool Paused = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Paused;
bool Spec = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Spec;

if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj && !Paused && !Spec)
{
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar);
if(!DummyConnecting && DoButton_Menu(&s_SpectateButton, Localize("Spectate"), 0, &Button))
{
if(g_Config.m_ClDummy == 0 || m_pClient->Client()->DummyConnected())
if(g_Config.m_ClDummy == 0 || Client()->DummyConnected())
{
m_pClient->SendSwitchTeam(TEAM_SPECTATORS);
SetActive(false);
Expand Down Expand Up @@ -115,42 +163,34 @@ void CMenus::RenderGame(CUIRect MainView)
}
}

ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(150.0f, &Button, &ButtonBar);

static int s_DemoButton = 0;
bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording();
if(DoButton_Menu(&s_DemoButton, Recording ? Localize("Stop record") : Localize("Record demo"), 0, &Button))
if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj)
{
if(!Recording)
Client()->DemoRecorder_Start(Client()->GetCurrentMap(), true, RECORDER_MANUAL);
else
Client()->DemoRecorder_Stop(RECORDER_MANUAL);
}

ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(170.0f, &Button, &ButtonBar);
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(65.0f, &Button, &ButtonBar);

static int s_DummyButton = 0;
if(DummyConnecting)
{
DoButton_Menu(&s_DummyButton, Localize("Connecting dummy"), 1, &Button);
static int s_KillButton = 0;
if(DoButton_Menu(&s_KillButton, Localize("Kill"), 0, &Button))
{
m_pClient->SendKill(-1);
SetActive(false);
}
}
}
else if(DoButton_Menu(&s_DummyButton, Client()->DummyConnected() ? Localize("Disconnect Dummy") : Localize("Connect Dummy"), 0, &Button))

if(m_pClient->m_ReceivedDDNetPlayer && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj)
{
if(!Client()->DummyConnected())
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS || Paused || Spec)
{
Client()->DummyConnect();
}
else
{
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
{
m_Popup = POPUP_DISCONNECT_DUMMY;
}
else
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft((!Paused && !Spec) ? 65.0f : 120.0f, &Button, &ButtonBar);

static int s_PauseButton = 0;
if(DoButton_Menu(&s_PauseButton, (!Paused && !Spec) ? Localize("Pause") : Localize("Join game"), 0, &Button))
{
Client()->DummyDisconnect(0);
m_pClient->Console()->ExecuteLine("say /pause");
SetActive(false);
}
}
}
Expand Down
40 changes: 20 additions & 20 deletions src/game/client/components/menus_settings.cpp
Expand Up @@ -349,7 +349,7 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
MainView.HSplitTop(20.0f, 0, &MainView);
static float s_ScrollValue = 0.0f;
int OldSelected = -1;
UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue);
UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Country / Region"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue);

for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i)
{
Expand Down Expand Up @@ -907,7 +907,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples;
static int s_GfxTextureQuality = g_Config.m_GfxTextureQuality;
static int s_GfxTextureCompression = g_Config.m_GfxTextureCompression;
static int s_GfxOpenGLVersion = g_Config.m_GfxOpenGL3;
static int s_GfxOpenGLVersion = (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4;
static int s_GfxEnableTextureUnitOptimization = g_Config.m_GfxEnableTextureUnitOptimization;
static int s_GfxUsePreinitBuffer = g_Config.m_GfxUsePreinitBuffer;
static int s_GfxHighdpi = g_Config.m_GfxHighdpi;
Expand Down Expand Up @@ -1014,32 +1014,32 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
CheckSettings = true;
}

MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxTextureQuality, Localize("Quality Textures"), g_Config.m_GfxTextureQuality, &Button))
{
g_Config.m_GfxTextureQuality ^= 1;
CheckSettings = true;
}

MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxTextureCompression, Localize("Texture Compression"), g_Config.m_GfxTextureCompression, &Button))
{
g_Config.m_GfxTextureCompression ^= 1;
CheckSettings = true;
}

MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button))
g_Config.m_GfxHighDetail ^= 1;

MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxOpenGL3, Localize("Use OpenGL 3.3 (experimental)"), g_Config.m_GfxOpenGL3, &Button))
bool IsNewOpenGL = (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4;
if(DoButton_CheckBox(&g_Config.m_GfxOpenGLMajor, Localize("Use OpenGL 3.3 (experimental)"), IsNewOpenGL, &Button))
{
CheckSettings = true;
g_Config.m_GfxOpenGL3 ^= 1;
if(IsNewOpenGL)
{
g_Config.m_GfxOpenGLMajor = 3;
g_Config.m_GfxOpenGLMinor = 0;
g_Config.m_GfxOpenGLPatch = 0;
IsNewOpenGL = false;
}
else
{
g_Config.m_GfxOpenGLMajor = 3;
g_Config.m_GfxOpenGLMinor = 3;
g_Config.m_GfxOpenGLPatch = 0;
IsNewOpenGL = true;
}
}

if(g_Config.m_GfxOpenGL3)
if(IsNewOpenGL)
{
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxUsePreinitBuffer, Localize("Preinit VBO (iGPUs only)"), g_Config.m_GfxUsePreinitBuffer, &Button))
Expand Down Expand Up @@ -1074,7 +1074,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples &&
s_GfxTextureQuality == g_Config.m_GfxTextureQuality &&
s_GfxTextureCompression == g_Config.m_GfxTextureCompression &&
s_GfxOpenGLVersion == g_Config.m_GfxOpenGL3 &&
s_GfxOpenGLVersion == (int)IsNewOpenGL &&
s_GfxUsePreinitBuffer == g_Config.m_GfxUsePreinitBuffer &&
s_GfxEnableTextureUnitOptimization == g_Config.m_GfxEnableTextureUnitOptimization &&
s_GfxHighdpi == g_Config.m_GfxHighdpi)
Expand Down
46 changes: 27 additions & 19 deletions src/game/client/components/nameplates.cpp
Expand Up @@ -135,31 +135,47 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
TOutlineColor.m_A *= Alpha;
TColor.m_A *= Alpha;

float YOffset = Position.y-38;

if(m_aNamePlates[ClientID].m_NameTextContainerIndex != -1)
TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_NameTextContainerIndex, &TColor, &TOutlineColor, Position.x - tw / 2.0f, Position.y - FontSize - 38.0f);
{
YOffset -= FontSize;
TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_NameTextContainerIndex, &TColor, &TOutlineColor, Position.x - tw / 2.0f, YOffset);
}

if(g_Config.m_ClNameplatesClan)
{
YOffset -= FontSizeClan;
if(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex != -1)
TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex, &TColor, &TOutlineColor, Position.x - m_aNamePlates[ClientID].m_ClanNameTextWidth / 2.0f, Position.y - FontSize - FontSizeClan - 38.0f);
TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex, &TColor, &TOutlineColor, Position.x - m_aNamePlates[ClientID].m_ClanNameTextWidth / 2.0f, YOffset);
}

if (g_Config.m_ClNameplatesFriendMark && m_pClient->m_aClients[ClientID].m_Friend)
{
YOffset -= FontSize;
char aFriendMark[] = "";
TextRender()->TextColor(ColorRGBA(1.0f, 0.0f, 0.0f));
float XOffSet = TextRender()->TextWidth(0, FontSize, aFriendMark, -1, -1.0f)/2.0f;
TextRender()->Text(0, Position.x-XOffSet, YOffset, FontSize, aFriendMark, -1.0f);
}

if(g_Config.m_Debug || g_Config.m_ClNameplatesIDs) // render client id when in debug as well
{
YOffset -= FontSize;
char aBuf[128];
str_format(aBuf, sizeof(aBuf),"%d", pPlayerInfo->m_ClientID);
float Offset = g_Config.m_ClNameplatesClan ? (FontSize * 2 + FontSizeClan) : (FontSize * 2);
float tw_id = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
float XOffset = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f)/2.0f;
TextRender()->TextColor(rgb);
TextRender()->Text(0, Position.x-tw_id/2.0f, Position.y-Offset-38.0f, 28.0f, aBuf, -1.0f);
TextRender()->Text(0, Position.x-XOffset, YOffset, FontSize, aBuf, -1.0f);
}

if(g_Config.m_ClNameplatesHA) // render health and armor in nameplate
{
int Health = m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Health;
if(Health > 0)
int Armor = m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Armor;

if(Health > 0 || Armor > 0)
{
int Armor = m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Armor;
float HFontSize = 5.0f + 20.0f * g_Config.m_ClNameplatesHASize / 100.0f;
float AFontSize = 6.0f + 24.0f * g_Config.m_ClNameplatesHASize / 100.0f;
char aHealth[40] = "\0";
Expand All @@ -175,24 +191,16 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
str_append(aArmor, "", sizeof(aArmor));
str_append(aArmor, "\0", sizeof(aArmor));

float Offset;

if(g_Config.m_ClNameplatesClan && (g_Config.m_Debug || g_Config.m_ClNameplatesIDs))
Offset = (FontSize * 3 + FontSizeClan);
else if (g_Config.m_ClNameplatesClan)
Offset = (FontSize * 2 + FontSizeClan);
else if (g_Config.m_Debug || g_Config.m_ClNameplatesIDs)
Offset = (FontSize * 3);
else
Offset = (FontSize * 2);

YOffset -= HFontSize+AFontSize;
float PosHealth = TextRender()->TextWidth(0, HFontSize, aHealth, -1, -1.0f);
TextRender()->TextColor(ColorRGBA(1.0f, 0.0f, 0.0f));
TextRender()->Text(0, Position.x-PosHealth/2.0f, Position.y-Offset-HFontSize-AFontSize, HFontSize, aHealth, -1);
TextRender()->Text(0, Position.x-PosHealth/2.0f, YOffset, HFontSize, aHealth, -1);

YOffset -= -AFontSize+3.0f;
float PosArmor = TextRender()->TextWidth(0, AFontSize, aArmor, -1, -1.0f);
TextRender()->TextColor(ColorRGBA(1.0f, 1.0f, 0.0f));
TextRender()->Text(0, Position.x-PosArmor/2.0f, Position.y-Offset-AFontSize-3.0f, AFontSize, aArmor, -1);
TextRender()->Text(0, Position.x-PosArmor/2.0f, YOffset, AFontSize, aArmor, -1);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/particles.cpp
Expand Up @@ -178,7 +178,7 @@ void CParticles::RenderGroup(int Group)
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_PARTICLES].m_Id);

// don't use the buffer methods here, else the old renderer gets many draw calls
if(Graphics()->IsBufferingEnabled())
if(Graphics()->IsQuadContainerBufferingEnabled())
{
int i = m_aFirstPart[Group];

Expand Down
20 changes: 19 additions & 1 deletion src/game/client/gameclient.cpp
Expand Up @@ -221,7 +221,7 @@ void CGameClient::OnConsoleInit()

// add the some console commands
Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team");
Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself");
Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself to restart");

// register server dummy commands for tab completion
Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value");
Expand Down Expand Up @@ -535,6 +535,8 @@ void CGameClient::OnReset()
m_DDRaceMsgSent[1] = false;
m_ShowOthers[0] = -1;
m_ShowOthers[1] = -1;

m_ReceivedDDNetPlayer = false;
}


Expand Down Expand Up @@ -623,6 +625,21 @@ void CGameClient::OnRender()
// update the local character and spectate position
UpdatePositions();

// display gfx warnings
if(g_Config.m_GfxShowWarnings == 1)
{
SGraphicsWarning* pWarning = Graphics()->GetCurWarning();
if(pWarning != NULL)
{
if(m_pMenus->CanDisplayWarning())
{
m_pMenus->PopupWarning("Warning!", pWarning->m_aWarningMsg, "Ok", 10000000);

pWarning->m_WasShown = true;
}
}
}

// render all systems
for(int i = 0; i < m_All.m_Num; i++)
m_All.m_paComponents[i]->OnRender();
Expand Down Expand Up @@ -1233,6 +1250,7 @@ void CGameClient::OnNewSnapshot()
}
else if(Item.m_Type == NETOBJTYPE_DDNETPLAYER)
{
m_ReceivedDDNetPlayer = true;
const CNetObj_DDNetPlayer *pInfo = (const CNetObj_DDNetPlayer *)pData;
if(Item.m_ID < MAX_CLIENTS)
{
Expand Down
1 change: 1 addition & 0 deletions src/game/client/gameclient.h
Expand Up @@ -409,6 +409,7 @@ class CGameClient : public IGameClient
CNetObj_PlayerInput m_DummyInput;
CNetObj_PlayerInput m_HammerInput;
int m_DummyFire;
bool m_ReceivedDDNetPlayer;

class CRaceDemo *m_pRaceDemo;
class CGhost *m_pGhost;
Expand Down
59 changes: 42 additions & 17 deletions src/game/editor/editor.cpp
Expand Up @@ -917,10 +917,13 @@ CSoundSource *CEditor::GetSelectedSource()
return 0;
}

void CEditor::SelectLayer(int Index)
void CEditor::SelectLayer(int LayerIndex, int GroupIndex)
{
if (GroupIndex != -1)
m_SelectedGroup = GroupIndex;

m_lSelectedLayers.clear();
m_lSelectedLayers.add(Index);
m_lSelectedLayers.add(LayerIndex);
}

void CEditor::SelectQuad(int Index)
Expand Down Expand Up @@ -3350,8 +3353,9 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View)
if(int Result = DoButton_Ex(&m_Map.m_lGroups[g], aBuf, g==m_SelectedGroup, &Slot,
BUTTON_CONTEXT, m_Map.m_lGroups[g]->m_Collapse ? "Select group. Shift click to select all layers. Double click to expand." : "Select group. Shift click to select all layers. Double click to collapse.", CUI::CORNER_R, FontSize))
{
m_SelectedGroup = g;
SelectLayer(0);
if (g != m_SelectedGroup)
SelectLayer(0, g);

if ((Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) && m_SelectedGroup == g)
{
for(int i = 1; i < m_Map.m_lGroups[g]->m_lLayers.size(); i++)
Expand Down Expand Up @@ -3453,12 +3457,30 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View)
}
else if(!(Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)))
{
m_SelectedGroup = g;
SelectLayer(i);
SelectLayer(i, g);
}
}
else if(Result == 2)
{
bool IsLayerSelected = false;

if (m_SelectedGroup == g)
{
for (int x = 0; x < m_lSelectedLayers.size(); x++)
{
if (m_lSelectedLayers[x] == i)
{
IsLayerSelected = true;
break;
}
}
}

if (!IsLayerSelected)
{
SelectLayer(i, g);
}

if(m_lSelectedLayers.size() > 1)
{
bool AllTile = true;
Expand Down Expand Up @@ -3514,8 +3536,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View)
{
if(m_Map.m_lGroups[Group]->m_lLayers.size() > 0)
{
m_SelectedGroup = Group;
SelectLayer(0);
SelectLayer(0, Group);
break;
}
}
Expand Down Expand Up @@ -3546,8 +3567,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View)
{
if(m_Map.m_lGroups[Group]->m_lLayers.size() > 0)
{
m_SelectedGroup = Group;
SelectLayer(m_Map.m_lGroups[Group]->m_lLayers.size() - 1);
SelectLayer(m_Map.m_lGroups[Group]->m_lLayers.size() - 1, Group);
break;
}
}
Expand Down Expand Up @@ -4351,7 +4371,7 @@ void CEditor::AddFileDialogEntry(int Index, CUIRect *pView)
m_PreviewImageIsLoaded = false;

if(Input()->MouseDoubleClick())
m_aFileDialogActivate = true;
m_FileDialogActivate = true;
}
}

Expand Down Expand Up @@ -4422,6 +4442,8 @@ void CEditor::RenderFileDialog()
UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, -1, -1);
str_copy(m_aFileDialogPrevSearchText, m_aFileDialogSearchText, sizeof(m_aFileDialogPrevSearchText));
DoEditBox(&s_SearchBoxID, &FileBox, m_aFileDialogSearchText, sizeof(m_aFileDialogSearchText), 10.0f, &s_SearchBoxID,false,CUI::CORNER_L);
if(m_FileDialogOpening)
UI()->SetActiveItem(&s_SearchBoxID);

// clearSearchbox button
{
Expand All @@ -4439,6 +4461,8 @@ void CEditor::RenderFileDialog()
m_FileDialogScrollValue = 0.0f;
}

m_FileDialogOpening = false;

int Num = (int)(View.h/17.0f)+1;
static int ScrollBar = 0;
Scroll.HMargin(5.0f, &Scroll);
Expand Down Expand Up @@ -4562,7 +4586,7 @@ void CEditor::RenderFileDialog()
if(Input()->GetEvent(i).m_Flags&IInput::FLAG_PRESS)
{
if(Input()->GetEvent(i).m_Key == KEY_RETURN || Input()->GetEvent(i).m_Key == KEY_KP_ENTER)
m_aFileDialogActivate = true;
m_FileDialogActivate = true;
}
}

Expand Down Expand Up @@ -4592,9 +4616,9 @@ void CEditor::RenderFileDialog()
CUIRect Button;
ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button);
bool IsDir = m_FilesSelectedIndex >= 0 && m_FileList[m_FilesSelectedIndex].m_IsDir;
if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, 0) || m_aFileDialogActivate)
if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, 0) || m_FileDialogActivate)
{
m_aFileDialogActivate = false;
m_FileDialogActivate = false;
if(IsDir) // folder
{
if(str_comp(m_FileList[m_FilesSelectedIndex].m_aFilename, "..") == 0) // parent folder
Expand Down Expand Up @@ -4696,7 +4720,7 @@ void CEditor::FilelistPopulate(int StorageType)
Storage()->ListDirectory(StorageType, m_pFileDialogPath, EditorListdirCallback, this);
m_FilesSelectedIndex = m_FileList.size() ? 0 : -1;
m_PreviewImageIsLoaded = false;
m_aFileDialogActivate = false;
m_FileDialogActivate = false;

if(m_FilesSelectedIndex >= 0 && !m_FileList[m_FilesSelectedIndex].m_IsDir)
str_copy(m_aFileDialogFileName, m_FileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName));
Expand All @@ -4721,6 +4745,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_FileDialogFileType = FileType;
m_FileDialogScrollValue = 0.0f;
m_PreviewImageIsLoaded = false;
m_FileDialogOpening = true;

if(pDefaultName)
str_copy(m_aFileDialogFileName, pDefaultName, sizeof(m_aFileDialogFileName));
Expand All @@ -4729,6 +4754,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle

FilelistPopulate(m_FileDialogStorageType);

m_FileDialogOpening = true;
m_Dialog = DIALOG_FILE;
}

Expand Down Expand Up @@ -6150,9 +6176,8 @@ void CEditor::Reset(bool CreateDefault)
if(CreateDefault)
m_Map.CreateDefault(m_EntitiesTexture);

SelectLayer(0);
SelectLayer(0, 0);
m_lSelectedQuads.clear();
m_SelectedGroup = 0;
m_SelectedPoints = 0;
m_SelectedEnvelope = 0;
m_SelectedImage = 0;
Expand Down
9 changes: 5 additions & 4 deletions src/game/editor/editor.h
Expand Up @@ -671,7 +671,8 @@ class CEditor : public IEditor
m_aFileDialogCurrentFolder[0] = 0;
m_aFileDialogCurrentLink[0] = 0;
m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_aFileDialogActivate = false;
m_FileDialogActivate = false;
m_FileDialogOpening = false;
m_FileDialogScrollValue = 0.0f;
m_FilesSelectedIndex = -1;
m_FilesStartAt = 0;
Expand Down Expand Up @@ -778,8 +779,7 @@ class CEditor : public IEditor
CLayer *GetSelectedLayer(int Index);
CLayerGroup *GetSelectedGroup();
CSoundSource *GetSelectedSource();
void SelectLayer(int Index);

void SelectLayer(int LayerIndex, int GroupIndex = -1);
void SelectQuad(int Index);
void DeleteSelectedQuads();
bool IsQuadSelected(int Index);
Expand Down Expand Up @@ -841,7 +841,7 @@ class CEditor : public IEditor
char m_aFileDialogSearchText[64];
char m_aFileDialogPrevSearchText[64];
char *m_pFileDialogPath;
bool m_aFileDialogActivate;
bool m_FileDialogActivate;
int m_FileDialogFileType;
float m_FileDialogScrollValue;
int m_FilesSelectedIndex;
Expand All @@ -850,6 +850,7 @@ class CEditor : public IEditor
IGraphics::CTextureHandle m_FilePreviewImage;
bool m_PreviewImageIsLoaded;
CImageInfo m_FilePreviewImageInfo;
bool m_FileDialogOpening;


struct CFilelistItem
Expand Down
2 changes: 1 addition & 1 deletion src/game/editor/layer_tiles.cpp
Expand Up @@ -932,7 +932,7 @@ int CLayerTiles::RenderProperties(CUIRect *pToolBox)
m_Image = NewVal%m_pEditor->m_Map.m_lImages.size();
m_AutoMapperConfig = -1;

if(m_pEditor->m_Map.m_lImages[m_Image]->m_Width % 16 != 0)
if(m_pEditor->m_Map.m_lImages[m_Image]->m_Width % 16 != 0 || m_pEditor->m_Map.m_lImages[m_Image]->m_Height % 16 != 0)
{
m_pEditor->m_PopupEventType = m_pEditor->POPEVENT_IMAGEDIV16;
m_pEditor->m_PopupEventActivated = true;
Expand Down
20 changes: 10 additions & 10 deletions src/game/editor/popups.cpp
Expand Up @@ -1021,32 +1021,32 @@ int CEditor::PopupMapInfo(CEditor *pEditor, CUIRect View, void *pContext)
// author box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Author:", 10.0f, -1);
Label.VSplitLeft(40.0f, 0, &Button);
Button.HSplitTop(12.0f, &Button, 0);
Label.VSplitLeft(45.0f, 0, &Button);
Button.HMargin(3, &Button);
static float s_AuthorBox = 0;
pEditor->DoEditBox(&s_AuthorBox, &Button, pEditor->m_Map.m_MapInfo.m_aAuthorTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aAuthorTmp), 10.0f, &s_AuthorBox);

// version box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Version:", 10.0f, -1);
Label.VSplitLeft(40.0f, 0, &Button);
Button.HSplitTop(12.0f, &Button, 0);
Label.VSplitLeft(45.0f, 0, &Button);
Button.HMargin(3, &Button);
static float s_VersionBox = 0;
pEditor->DoEditBox(&s_VersionBox, &Button, pEditor->m_Map.m_MapInfo.m_aVersionTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aVersionTmp), 10.0f, &s_VersionBox);

// credits box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "Credits:", 10.0f, -1);
Label.VSplitLeft(40.0f, 0, &Button);
Button.HSplitTop(12.0f, &Button, 0);
Label.VSplitLeft(45.0f, 0, &Button);
Button.HMargin(3, &Button);
static float s_CreditsBox = 0;
pEditor->DoEditBox(&s_CreditsBox, &Button, pEditor->m_Map.m_MapInfo.m_aCreditsTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aCreditsTmp), 10.0f, &s_CreditsBox);

// license box
View.HSplitTop(20.0f, &Label, &View);
pEditor->UI()->DoLabel(&Label, "License:", 10.0f, -1);
Label.VSplitLeft(40.0f, 0, &Button);
Button.HSplitTop(12.0f, &Button, 0);
Label.VSplitLeft(45.0f, 0, &Button);
Button.HMargin(3, &Button);
static float s_LicenseBox = 0;
pEditor->DoEditBox(&s_LicenseBox, &Button, pEditor->m_Map.m_MapInfo.m_aLicenseTmp, sizeof(pEditor->m_Map.m_MapInfo.m_aLicenseTmp), 10.0f, &s_LicenseBox);

Expand Down Expand Up @@ -1092,7 +1092,7 @@ int CEditor::PopupEvent(CEditor *pEditor, CUIRect View, void *pContext)
else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES)
pEditor->UI()->DoLabel(&Label, "Unused tiles disabled", 20.0f, 0);
else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16)
pEditor->UI()->DoLabel(&Label, "Image width", 20.0f, 0);
pEditor->UI()->DoLabel(&Label, "Image width/height", 20.0f, 0);

View.HSplitBottom(10.0f, &View, 0);
View.HSplitBottom(20.0f, &View, &ButtonBar);
Expand All @@ -1115,7 +1115,7 @@ int CEditor::PopupEvent(CEditor *pEditor, CUIRect View, void *pContext)
else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES)
pEditor->UI()->DoLabel(&Label, "Unused tiles can't be placed by default because they could get a use later and then destroy your map.\nActivate the 'Unused' switch to be able to place every tile.", 10.0f, -1, Label.w-10.0f);
else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16)
pEditor->UI()->DoLabel(&Label, "The width of this image is not divisible by 16. This is required for images used in tile layers for Teeworlds 0.7 compatibility.", 10.0f, -1, Label.w-10.0f);
pEditor->UI()->DoLabel(&Label, "The width or height of this image is not divisible by 16. This is required for images used in tile layers for Teeworlds 0.7 compatibility.", 10.0f, -1, Label.w-10.0f);

// button bar
ButtonBar.VSplitLeft(30.0f, 0, &ButtonBar);
Expand Down
2 changes: 1 addition & 1 deletion src/game/server/ddracechat.h
Expand Up @@ -56,6 +56,6 @@ CHAT_COMMAND("timer", "?s['gametimer'|'broadcast'|'both'|'none'|'cycle']", CFGFL
CHAT_COMMAND("r", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConRescue, this, "Teleport yourself out of freeze (use sv_rescue 1 to enable this feature)")
CHAT_COMMAND("rescue", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConRescue, this, "Teleport yourself out of freeze (use sv_rescue 1 to enable this feature)")

CHAT_COMMAND("kill", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConProtectedKill, this, "Kill yourself")
CHAT_COMMAND("kill", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConProtectedKill, this, "Kill yourself when kill-protected during a long game (use f1, kill for regular kill)")

#undef CHAT_COMMAND
20 changes: 15 additions & 5 deletions src/game/server/gamecontext.cpp
Expand Up @@ -1996,7 +1996,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
}

if(aCmd[0] && str_comp(aCmd, "info") != 0)
CallVote(ClientID, aDesc, aCmd, aReason, aChatmsg, aSixupDesc);
CallVote(ClientID, aDesc, aCmd, aReason, aChatmsg, aSixupDesc[0] ? aSixupDesc : 0);
}
else if(MsgID == NETMSGTYPE_CL_VOTE)
{
Expand Down Expand Up @@ -2304,12 +2304,22 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)

CNetMsg_Cl_StartInfo *pMsg = (CNetMsg_Cl_StartInfo *)pRawMsg;

if(!str_utf8_check(pMsg->m_pName)
|| !str_utf8_check(pMsg->m_pClan)
|| !str_utf8_check(pMsg->m_pSkin))
if(!str_utf8_check(pMsg->m_pName))
{
Server()->Kick(ClientID, "name is not valid utf8");
return;
}
if(!str_utf8_check(pMsg->m_pClan))
{
Server()->Kick(ClientID, "clan is not valid utf8");
return;
}
if(!str_utf8_check(pMsg->m_pSkin))
{
Server()->Kick(ClientID, "skin is not valid utf8");
return;
}

pPlayer->m_LastChangeInfo = Server()->Tick();

// set start infos
Expand Down Expand Up @@ -3096,7 +3106,7 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
GameInfo.m_pPrngDescription = m_Prng.Description();

GameInfo.m_pServerName = g_Config.m_SvName;
GameInfo.m_ServerPort = g_Config.m_SvPort;
GameInfo.m_ServerPort = Server()->Port();
GameInfo.m_pGameType = m_pController->m_pGameType;

GameInfo.m_pConfig = &g_Config;
Expand Down
30 changes: 12 additions & 18 deletions src/game/server/score.cpp
Expand Up @@ -508,7 +508,7 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat

// save score. Can't fail, because no UNIQUE/PRIMARY KEY constrain is defined.
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_race("
"%s INTO %s_race("
"Map, Name, Timestamp, Time, Server, "
"cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
"cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
Expand All @@ -518,7 +518,8 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat
"%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
"%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
"?, false);",
pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc(), pData->m_Time,
pSqlServer->GetInsertIgnore(), pSqlServer->GetPrefix(),
pSqlServer->InsertTimestampAsUtc(), pData->m_Time,
pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2],
pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5],
pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8],
Expand Down Expand Up @@ -631,9 +632,10 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam
{
// if no entry found... create a new one
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) "
"%s INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) "
"VALUES (?, ?, %s, %.2f, ?, ?, false);",
pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc(), pData->m_Time);
pSqlServer->GetInsertIgnore(), pSqlServer->GetPrefix(),
pSqlServer->InsertTimestampAsUtc(), pData->m_Time);
pSqlServer->PrepareStatement(aBuf);
pSqlServer->BindString(1, pData->m_Map);
pSqlServer->BindString(2, pData->m_aNames[i]);
Expand Down Expand Up @@ -1321,9 +1323,9 @@ bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData
if(UseCode)
{
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"%s INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, false)",
pSqlServer->GetPrefix());
pSqlServer->GetInsertIgnore(), pSqlServer->GetPrefix());
pSqlServer->PrepareStatement(aBuf);
pSqlServer->BindString(1, pSaveState);
pSqlServer->BindString(2, pData->m_Map);
Expand Down Expand Up @@ -1442,7 +1444,7 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData

char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"SELECT Savegame, Server, %s-%s AS Ago, SaveID "
"SELECT Savegame, %s-%s AS Ago, SaveID "
"FROM %s_saves "
"where Code = ? AND Map = ? AND DDNet7 = false AND Savegame LIKE ?;",
aCurrentTimestamp, aTimestamp,
Expand All @@ -1457,16 +1459,8 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData
strcpy(pData->m_pResult->m_aMessage, "No such savegame for this map");
goto end;
}
char aServerName[32];
pSqlServer->GetString(2, aServerName, sizeof(aServerName));
if(str_comp(aServerName, g_Config.m_SvSqlServerName) != 0)
{
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
"You have to be on the '%s' server to load this savegame", aServerName);
goto end;
}

int Since = pSqlServer->GetInt(3);
int Since = pSqlServer->GetInt(2);
if(Since < g_Config.m_SvSaveGamesDelay)
{
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
Expand All @@ -1477,9 +1471,9 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData

char aSaveID[UUID_MAXSTRSIZE];
memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData));
if(!pSqlServer->IsNull(4))
if(!pSqlServer->IsNull(3))
{
pSqlServer->GetString(4, aSaveID, sizeof(aSaveID));
pSqlServer->GetString(3, aSaveID, sizeof(aSaveID));
if(str_length(aSaveID) + 1 != UUID_MAXSTRSIZE)
{
strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: SaveID corrupted");
Expand Down
1 change: 1 addition & 0 deletions src/game/variables.h
Expand Up @@ -26,6 +26,7 @@ MACRO_CONFIG_INT(ClNameplatesIDs, cl_nameplates_ids, 0, 0, 1, CFGFLAG_CLIENT|CFG
MACRO_CONFIG_INT(ClNameplatesHA, cl_nameplates_ha, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show Health and Armor in name plates")
MACRO_CONFIG_INT(ClNameplatesHASize, cl_nameplates_ha_size, 50, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Size of the health and armor nameplates from 0 to 100%")
MACRO_CONFIG_INT(ClNameplatesOwn, cl_nameplates_own, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show own name plate (useful for demo recording)")
MACRO_CONFIG_INT(ClNameplatesFriendMark, cl_nameplates_friendmark, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show friend mark (♥) in name plates")
MACRO_CONFIG_INT(ClTextEntities, cl_text_entities, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Render textual entity data")
MACRO_CONFIG_INT(ClTextEntitiesSize, cl_text_entities_size, 100, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Size of textual entity data from 0 to 100%")

Expand Down
6 changes: 3 additions & 3 deletions src/game/version.h
Expand Up @@ -2,10 +2,10 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_VERSION_H
#define GAME_VERSION_H
#define GAME_VERSION "0.6.4, 14.5.1"
#define GAME_VERSION "0.6.4, 14.6.2"
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
#define GAME_NAME "DDNet"
#define GAME_RELEASE_VERSION "14.5.1"
#define CLIENT_VERSIONNR 14051
#define GAME_RELEASE_VERSION "14.6.2"
#define CLIENT_VERSIONNR 14062
extern const char *GIT_SHORTREV_HASH;
#endif
8 changes: 8 additions & 0 deletions src/steam/steam_api_flat.h
Expand Up @@ -10,12 +10,20 @@
extern "C"
{

struct ISteamApps;
struct ISteamFriends;

STEAMAPI bool SteamAPI_Init(); // Returns true on success.
STEAMAPI void SteamAPI_Shutdown();

STEAMAPI ISteamApps *SteamAPI_SteamApps_v008();
STEAMAPI int SteamAPI_ISteamApps_GetLaunchCommandLine(ISteamApps *pSelf, char *pBuffer, int BufferSize);
STEAMAPI const char *SteamAPI_ISteamApps_GetLaunchQueryParam(ISteamApps *pSelf, const char *pKey);

STEAMAPI ISteamFriends *SteamAPI_SteamFriends_v017();
STEAMAPI void SteamAPI_ISteamFriends_ClearRichPresence(ISteamFriends *pSelf);
STEAMAPI const char *SteamAPI_ISteamFriends_GetPersonaName(ISteamFriends *pSelf);
STEAMAPI bool SteamAPI_ISteamFriends_SetRichPresence(ISteamFriends *pSelf, const char *pKey, const char *pValue);

}

Expand Down
5 changes: 5 additions & 0 deletions src/steam/steam_api_stub.cpp
Expand Up @@ -9,7 +9,12 @@ extern "C"

bool SteamAPI_Init() { return false; }
void SteamAPI_Shutdown() { abort(); }
ISteamApps *SteamAPI_SteamApps_v008() { abort(); }
int SteamAPI_ISteamApps_GetLaunchCommandLine(ISteamApps *pSelf, char *pBuffer, int BufferSize) { abort(); }
const char *SteamAPI_ISteamApps_GetLaunchQueryParam(ISteamApps *pSelf, const char *pKey) { abort(); }
ISteamFriends *SteamAPI_SteamFriends_v017() { abort(); }
const char *SteamAPI_ISteamFriends_GetPersonaName(ISteamFriends *pSelf) { abort(); }
bool SteamAPI_ISteamFriends_SetRichPresence(ISteamFriends *pSelf, const char *pKey, const char *pValue) { abort(); }
void SteamAPI_ISteamFriends_ClearRichPresence(ISteamFriends *pSelf) { abort(); }

}
64 changes: 64 additions & 0 deletions src/test/packer.cpp
@@ -0,0 +1,64 @@
#include "test.h"
#include <gtest/gtest.h>

#include <base/system.h>
#include <engine/shared/packer.h>

// pExpected is NULL if an error is expected
static void ExpectAddString5(const char *pString, int Limit, const char *pExpected)
{
static char ZEROS[CPacker::PACKER_BUFFER_SIZE] = {0};
static const int OFFSET = CPacker::PACKER_BUFFER_SIZE - 5;
CPacker Packer;
Packer.Reset();
Packer.AddRaw(ZEROS, OFFSET);
Packer.AddString(pString, Limit);

EXPECT_EQ(pExpected == 0, Packer.Error());
if(pExpected)
{
// Include null termination.
int ExpectedLength = str_length(pExpected) + 1;
EXPECT_EQ(ExpectedLength, Packer.Size() - OFFSET);
if(ExpectedLength == Packer.Size() - OFFSET)
{
EXPECT_STREQ(pExpected, (const char *)Packer.Data() + OFFSET);
}
}
}

TEST(Packer, AddString)
{
ExpectAddString5("", 0, "");
ExpectAddString5("a", 0, "a");
ExpectAddString5("abcd", 0, "abcd");
ExpectAddString5("abcde", 0, 0);
}

TEST(Packer, AddStringLimit)
{
ExpectAddString5("", 1, "");
ExpectAddString5("a", 1, "a");
ExpectAddString5("aa", 1, "a");
ExpectAddString5("ä", 1, "");

ExpectAddString5("", 10, "");
ExpectAddString5("a", 10, "a");
ExpectAddString5("abcd", 10, "abcd");
ExpectAddString5("abcde", 10, 0);

ExpectAddString5("äöü", 5, "äö");
ExpectAddString5("äöü", 6, 0);
}

TEST(Packer, AddStringBroken)
{
ExpectAddString5("\x80", 0, "�");
ExpectAddString5("\x80\x80", 0, 0);
ExpectAddString5("a\x80", 0, "a�");
ExpectAddString5("\x80""a", 0, "�a");
ExpectAddString5("\x80", 1, "");
ExpectAddString5("\x80\x80", 3, "�");
ExpectAddString5("\x80\x80", 5, "�");
ExpectAddString5("\x80\x80", 6, 0);
}
13 changes: 10 additions & 3 deletions src/tools/map_convert_07.cpp
Expand Up @@ -97,13 +97,14 @@ bool CheckImageDimensions(void *pItem, int Type, const char *pFilename)
return true;

CMapItemImage *pImgItem = (CMapItemImage *)pItem2;
if(pImgItem->m_Width % 16 == 0)

if(pImgItem->m_Width % 16 == 0 && pImgItem->m_Height % 16 == 0)
return true;

char aTileLayerName[12];
IntsToStr(pTMap->m_aName, sizeof(pTMap->m_aName)/sizeof(int), aTileLayerName);
char *pName = (char *)g_DataReader.GetData(pImgItem->m_ImageName);
dbg_msg("map_convert_07", "%s: Tile layer \"%s\" uses image \"%s\" with width %d, which is not divisible by 16. This is not supported in Teeworlds 0.7. Please scale the image and replace it manually.", pFilename, aTileLayerName, pName, pImgItem->m_Width);
dbg_msg("map_convert_07", "%s: Tile layer \"%s\" uses image \"%s\" with width %d, height %d, which is not divisible by 16. This is not supported in Teeworlds 0.7. Please scale the image and replace it manually.", pFilename, aTileLayerName, pName, pImgItem->m_Width, pImgItem->m_Height);
return false;
}

Expand Down Expand Up @@ -176,9 +177,15 @@ int main(int argc, const char **argv)
IStorage::StripPathAndExtension(pSourceFileName, aBuf, sizeof(aBuf));
str_format(aDestFileName, sizeof(aDestFileName), "data/maps7/%s.map", aBuf);
pDestFileName = aDestFileName;
if(fs_makedir("data") != 0)
{
dbg_msg("map_convert_07", "failed to create data directory");
return -1;
}

if(fs_makedir("data/maps7") != 0)
{
dbg_msg("map_convert_07", "failed to create maps7 directory");
dbg_msg("map_convert_07", "failed to create data/maps7 directory");
return -1;
}
}
Expand Down