292 changes: 140 additions & 152 deletions src/base/system.cpp

Large diffs are not rendered by default.

67 changes: 48 additions & 19 deletions src/base/system.h
Expand Up @@ -18,6 +18,7 @@
#include <cstdarg>
#include <cstdint>
#include <ctime>
#include <string>

#ifdef __MINGW32__
#undef PRId64
Expand All @@ -41,6 +42,14 @@
#include <chrono>
#include <functional>

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

/**
* @defgroup Debug
*
Expand Down Expand Up @@ -99,6 +108,9 @@ bool dbg_assert_has_failed();
void
dbg_break();

typedef std::function<void(const char *message)> DBG_ASSERT_HANDLER;
void dbg_assert_set_handler(DBG_ASSERT_HANDLER handler);

/**
* Prints a debug message.
*
Expand Down Expand Up @@ -1154,12 +1166,9 @@ void net_unix_close(UNIXSOCKET sock);
*
* @param error The Windows error code.
*
* @return A new string representing the error code.
*
* @remark Guarantees that result will contain zero-termination.
* @remark The result must be freed after it has been used.
* @return A new std::string representing the error code.
*/
char *windows_format_system_message(unsigned long error);
std::string windows_format_system_message(unsigned long error);

#endif

Expand Down Expand Up @@ -2378,7 +2387,7 @@ int str_utf8_check(const char *str);
- The string is treated as zero-terminated utf8 string.
- It's the user's responsibility to make sure the bounds are aligned.
*/
void str_utf8_stats(const char *str, int max_size, int max_count, int *size, int *count);
void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count);

/*
Function: str_next_token
Expand Down Expand Up @@ -2580,19 +2589,15 @@ int secure_rand();
*/
int secure_rand_below(int below);

/*
Function: os_version_str
Returns a human-readable version string of the operating system
Parameters:
version - Buffer to use for the output.
length - Length of the output buffer.
Returns:
0 - Success in getting the version.
1 - Failure in getting the version.
*/
int os_version_str(char *version, int length);
/**
* Returns a human-readable version string of the operating system.
*
* @param version Buffer to use for the output.
* @param length Length of the output buffer.
*
* @return true on success, false on failure.
*/
bool os_version_str(char *version, size_t length);

/**
* Returns a string of the preferred locale of the user / operating system.
Expand Down Expand Up @@ -2647,6 +2652,30 @@ class CCmdlineFix
};

#if defined(CONF_FAMILY_WINDOWS)
/**
* Converts a utf8 encoded string to a wide character string
* for use with the Windows API.
*
* @param str The utf8 encoded string to convert.
*
* @return The argument as a wide character string.
*
* @remark The argument string must be zero-terminated.
*/
std::wstring windows_utf8_to_wide(const char *str);

/**
* Converts a wide character string obtained from the Windows API
* to a utf8 encoded string.
*
* @param wide_str The wide character string to convert.
*
* @return The argument as a utf8 encoded string.
*
* @remark The argument string must be zero-terminated.
*/
std::string windows_wide_to_utf8(const wchar_t *wide_str);

/**
* This is a RAII wrapper to initialize/uninitialize the Windows COM library,
* which may be necessary for using the open_file and open_link functions.
Expand Down
8 changes: 8 additions & 0 deletions src/engine/client.h
Expand Up @@ -285,6 +285,14 @@ class IClient : public IInterface
virtual void ShellRegister() = 0;
virtual void ShellUnregister() = 0;
#endif

enum EMessageBoxType
{
MESSAGE_BOX_TYPE_ERROR,
MESSAGE_BOX_TYPE_WARNING,
MESSAGE_BOX_TYPE_INFO,
};
virtual void ShowMessageBox(const char *pTitle, const char *pMessage, EMessageBoxType Type = MESSAGE_BOX_TYPE_ERROR) = 0;
};

class IGameClient : public IInterface
Expand Down
32 changes: 22 additions & 10 deletions src/engine/client/backend_sdl.cpp
Expand Up @@ -161,7 +161,7 @@ void CGraphicsBackend_Threaded::ProcessError()
else
VerboseStr.append(ErrStr.m_Err + "\n");
}
const auto CreatedMsgBox = TryCreateMsgBox(true, "Graphics Assertion", VerboseStr.c_str());
const bool CreatedMsgBox = ShowMessageBox(SDL_MESSAGEBOX_ERROR, "Graphics Assertion", VerboseStr.c_str());
// check if error msg can be shown, then assert
dbg_assert(!CreatedMsgBox, VerboseStr.c_str());
}
Expand Down Expand Up @@ -774,12 +774,21 @@ void CGraphicsBackend_SDL_GL::ClampDriverVersion(EBackendType BackendType)
}
}

bool CGraphicsBackend_SDL_GL::TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg)
bool CGraphicsBackend_SDL_GL::ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg)
{
m_pProcessor->ErroneousCleanup();
SDL_DestroyWindow(m_pWindow);
SDL_ShowSimpleMessageBox(AsError ? SDL_MESSAGEBOX_ERROR : SDL_MESSAGEBOX_WARNING, pTitle, pMsg, nullptr);
return true;
if(m_pProcessor != nullptr)
{
m_pProcessor->ErroneousCleanup();
}
// TODO: Remove this workaround when https://github.com/libsdl-org/SDL/issues/3750 is
// fixed and pass the window to SDL_ShowSimpleMessageBox to make the popup modal instead
// of destroying the window before opening the popup.
if(m_pWindow != nullptr)
{
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;
}
return SDL_ShowSimpleMessageBox(Type, pTitle, pMsg, nullptr) == 0;
}

bool CGraphicsBackend_SDL_GL::IsModernAPI(EBackendType BackendType)
Expand Down Expand Up @@ -1171,9 +1180,6 @@ int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth,
}
}

if(g_Config.m_InpMouseOld)
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1");

m_pWindow = SDL_CreateWindow(
pName,
SDL_WINDOWPOS_CENTERED_DISPLAY(*pScreen),
Expand Down Expand Up @@ -1203,6 +1209,7 @@ int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth,
if(m_GLContext == NULL)
{
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;
dbg_msg("gfx", "unable to create graphic context: %s", SDL_GetError());
return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_CONTEXT_FAILED;
}
Expand All @@ -1211,6 +1218,7 @@ int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth,
{
SDL_GL_DeleteContext(m_GLContext);
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;
return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_UNKNOWN;
}
}
Expand All @@ -1237,6 +1245,7 @@ int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth,
if(m_GLContext)
SDL_GL_DeleteContext(m_GLContext);
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;

// try setting to glew supported version
g_Config.m_GfxGLMajor = GlewMajor;
Expand Down Expand Up @@ -1339,6 +1348,7 @@ int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth,
if(m_GLContext)
SDL_GL_DeleteContext(m_GLContext);
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;

// try setting to version string's supported version
if(InitError == -2)
Expand Down Expand Up @@ -1403,6 +1413,7 @@ int CGraphicsBackend_SDL_GL::Shutdown()
if(m_GLContext != nullptr)
SDL_GL_DeleteContext(m_GLContext);
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;

SDL_QuitSubSystem(SDL_INIT_VIDEO);
return 0;
Expand Down Expand Up @@ -1592,7 +1603,8 @@ void CGraphicsBackend_SDL_GL::GetViewportSize(int &w, int &h)

void CGraphicsBackend_SDL_GL::NotifyWindow()
{
#if SDL_MAJOR_VERSION > 2 || (SDL_MAJOR_VERSION == 2 && SDL_PATCHLEVEL >= 16)
// Minimum version 2.0.16, after version 2.0.22 the naming is changed to 2.24.0 etc.
#if SDL_MAJOR_VERSION > 2 || (SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION == 0 && SDL_PATCHLEVEL >= 16) || (SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION > 0)
if(SDL_FlashWindow(m_pWindow, SDL_FlashOperation::SDL_FLASH_UNTIL_FOCUSED) != 0)
{
// fails if SDL hasn't implemented it
Expand Down
8 changes: 2 additions & 6 deletions src/engine/client/backend_sdl.h
Expand Up @@ -92,9 +92,6 @@ class CGraphicsBackend_Threaded : public IGraphicsBackend
return m_Warning.m_WarningType != GFX_WARNING_TYPE_NONE;
}

// returns true if the error msg was shown
virtual bool TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg) = 0;

private:
ICommandProcessor *m_pProcessor;
std::mutex m_BufferSwapMutex;
Expand Down Expand Up @@ -243,9 +240,6 @@ class CGraphicsBackend_SDL_GL : public CGraphicsBackend_Threaded
static EBackendType DetectBackend();
static void ClampDriverVersion(EBackendType BackendType);

protected:
bool TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg) override;

public:
CGraphicsBackend_SDL_GL(TTranslateFunc &&TranslateFunc);
int Init(const char *pName, int *pScreen, int *pWidth, int *pHeight, int *pRefreshRate, int *pFsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, class IStorage *pStorage) override;
Expand Down Expand Up @@ -313,6 +307,8 @@ class CGraphicsBackend_SDL_GL : public CGraphicsBackend_Threaded

TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() override;

bool ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg) override;

static bool IsModernAPI(EBackendType BackendType);
};

Expand Down
257 changes: 177 additions & 80 deletions src/engine/client/client.cpp

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/engine/client/client.h
Expand Up @@ -367,7 +367,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
void GetServerInfo(CServerInfo *pServerInfo) const override;
void ServerInfoRequest();

int LoadData();
void LoadDebugFont();

// ---

Expand Down Expand Up @@ -425,6 +425,7 @@ class CClient : public IClient, public CDemoPlayer::IListener

void Run();

bool InitNetworkClient(char *pError, size_t ErrorSize);
bool CtrlShiftKey(int Key, bool &Last);

static void Con_Connect(IConsole::IResult *pResult, void *pUserData);
Expand All @@ -435,6 +436,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
static void Con_DummyResetInput(IConsole::IResult *pResult, void *pUserData);

static void Con_Quit(IConsole::IResult *pResult, void *pUserData);
static void Con_Restart(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoPlay(IConsole::IResult *pResult, void *pUserData);
static void Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData);
static void Con_Minimize(IConsole::IResult *pResult, void *pUserData);
Expand Down Expand Up @@ -549,6 +551,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
void ShellRegister() override;
void ShellUnregister() override;
#endif

void ShowMessageBox(const char *pTitle, const char *pMessage, EMessageBoxType Type = MESSAGE_BOX_TYPE_ERROR) override;
};

#endif
8 changes: 8 additions & 0 deletions src/engine/client/graphics_threaded.cpp
Expand Up @@ -3279,6 +3279,14 @@ SWarning *CGraphics_Threaded::GetCurWarning()
}
}

bool CGraphics_Threaded::ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg)
{
if(m_pBackend == nullptr)
return false;
m_pBackend->WaitForIdle();
return m_pBackend->ShowMessageBox(Type, pTitle, pMsg);
}

const char *CGraphics_Threaded::GetVendorString()
{
return m_pBackend->GetVendorString();
Expand Down
4 changes: 4 additions & 0 deletions src/engine/client/graphics_threaded.h
Expand Up @@ -773,6 +773,9 @@ class IGraphicsBackend
virtual TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() = 0;

virtual bool GetWarning(std::vector<std::string> &WarningStrings) = 0;

// returns true if the error msg was shown
virtual bool ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg) = 0;
};

class CGraphics_Threaded : public IEngineGraphics
Expand Down Expand Up @@ -1296,6 +1299,7 @@ class CGraphics_Threaded : public IEngineGraphics
void WaitForIdle() override;

SWarning *GetCurWarning() override;
bool ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg) override;

bool GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch, const char *&pName, EBackendType BackendType) override { return m_pBackend->GetDriverVersion(DriverAgeType, Major, Minor, Patch, pName, BackendType); }
bool IsConfigModernAPI() override { return m_pBackend->IsConfigModernAPI(); }
Expand Down
222 changes: 131 additions & 91 deletions src/engine/client/input.cpp

Large diffs are not rendered by default.

39 changes: 24 additions & 15 deletions src/engine/client/input.h
Expand Up @@ -9,6 +9,9 @@
#include <engine/input.h>
#include <engine/keys.h>

#include <string>
#include <vector>

class IEngineGraphics;

class CInput : public IEngineInput
Expand Down Expand Up @@ -75,9 +78,16 @@ class CInput : public IEngineInput
bool m_MouseFocus;
bool m_MouseDoubleClick;

// IME support
char m_aComposition[MAX_COMPOSITION_ARRAY_SIZE];
int m_CompositionCursor;
int m_CompositionLength;
std::vector<std::string> m_vCandidates;
int m_CandidateSelectedIndex;

void AddEvent(char *pText, int Key, int Flags);
void Clear() override;
bool IsEventValid(CEvent *pEvent) const override { return pEvent->m_InputCount == m_InputCounter; }
bool IsEventValid(const CEvent &Event) const override { return Event.m_InputCount == m_InputCounter; }

// quick access to input
unsigned short m_aInputCount[g_MaxKeys]; // tw-KEY
Expand All @@ -94,18 +104,15 @@ class CInput : public IEngineInput

char m_aDropFile[IO_MAX_PATH_LENGTH];

// IME support
int m_NumTextInputInstances;
char m_aEditingText[INPUT_TEXT_SIZE];
int m_EditingTextLen;
int m_EditingCursor;

bool KeyState(int Key) const;

void ProcessSystemMessage(SDL_SysWMmsg *pMsg);

public:
CInput();

void Init() override;
int Update() override;
void Shutdown() override;

bool ModifierIsPressed() const override { return KeyState(KEY_LCTRL) || KeyState(KEY_RCTRL) || KeyState(KEY_LGUI) || KeyState(KEY_RGUI); }
Expand All @@ -128,14 +135,16 @@ class CInput : public IEngineInput
const char *GetClipboardText() override;
void SetClipboardText(const char *pText) override;

int Update() override;

bool GetIMEState() override;
void SetIMEState(bool Activate) override;
int GetIMEEditingTextLength() const override { return m_EditingTextLen; }
const char *GetIMEEditingText() override;
int GetEditingCursor() override;
void SetEditingPosition(float X, float Y) override;
void StartTextInput() override;
void StopTextInput() override;
const char *GetComposition() const override { return m_aComposition; }
bool HasComposition() const override { return m_CompositionLength != COMP_LENGTH_INACTIVE; }
int GetCompositionCursor() const override { return m_CompositionCursor; }
int GetCompositionLength() const override { return m_CompositionLength; }
const char *GetCandidate(int Index) const override { return m_vCandidates[Index].c_str(); }
int GetCandidateCount() const override { return m_vCandidates.size(); }
int GetCandidateSelectedIndex() const override { return m_CandidateSelectedIndex; }
void SetCompositionWindowPosition(float X, float Y, float H) override;

bool GetDropFile(char *aBuf, int Len) override;
};
Expand Down
58 changes: 49 additions & 9 deletions src/engine/client/serverbrowser.cpp
Expand Up @@ -561,9 +561,28 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
pEntry->m_Info.m_NumAddresses = TmpInfo.m_NumAddresses;
ServerBrowserFormatAddresses(pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), pEntry->m_Info.m_aAddresses, pEntry->m_Info.m_NumAddresses);

if(pEntry->m_Info.m_ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_UNSPECIFIED)
{
if((str_find_nocase(pEntry->m_Info.m_aGameType, "race") || str_find_nocase(pEntry->m_Info.m_aGameType, "fastcap")) && g_Config.m_ClDDRaceScoreBoard)
{
pEntry->m_Info.m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT;
}
else
{
pEntry->m_Info.m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_POINTS;
}
}

class CPlayerScoreNameLess
{
int ScoreKind;

public:
CPlayerScoreNameLess(int ClientScoreKind) :
ScoreKind(ClientScoreKind)
{
}

bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1)
{
if(p0.m_Player && !p1.m_Player)
Expand All @@ -573,20 +592,41 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)

int Score0 = p0.m_Score;
int Score1 = p1.m_Score;
if(Score0 == -9999)
Score0 = INT_MIN;
if(Score1 == -9999)
Score1 = INT_MIN;

if(Score0 > Score1)
return true;
if(Score0 < Score1)
return false;
if(ScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME)
{
// time is sent as a positive value to the http master, counting 0, if there is a time (finished)
// only positive times are meant to represent an actual time.
if(Score0 != Score1)
{
if(Score0 < 0)
return false;
if(Score1 < 0)
return true;
return Score0 < Score1;
}
}
else
{
if(ScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT)
{
if(Score0 == -9999)
Score0 = INT_MIN;
if(Score1 == -9999)
Score1 = INT_MIN;
}

if(Score0 > Score1)
return true;
if(Score0 < Score1)
return false;
}

return str_comp_nocase(p0.m_aName, p1.m_aName) < 0;
}
};

std::sort(pEntry->m_Info.m_aClients, pEntry->m_Info.m_aClients + Info.m_NumReceivedClients, CPlayerScoreNameLess());
std::sort(pEntry->m_Info.m_aClients, pEntry->m_Info.m_aClients + Info.m_NumReceivedClients, CPlayerScoreNameLess(pEntry->m_Info.m_ClientScoreKind));

pEntry->m_GotInfo = 1;
}
Expand Down
309 changes: 180 additions & 129 deletions src/engine/client/text.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/engine/console.h
Expand Up @@ -109,7 +109,7 @@ class IConsole : public IInterface
virtual void ExecuteLine(const char *pStr, int ClientID = -1, bool InterpretSemicolons = true) = 0;
virtual void ExecuteLineFlag(const char *pStr, int FlasgMask, int ClientID = -1, bool InterpretSemicolons = true) = 0;
virtual void ExecuteLineStroked(int Stroke, const char *pStr, int ClientID = -1, bool InterpretSemicolons = true) = 0;
virtual void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) = 0;
virtual bool ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) = 0;

virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0;
virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/engine.h
Expand Up @@ -36,7 +36,7 @@ class IEngine : public IInterface

virtual void Init() = 0;
virtual void AddJob(std::shared_ptr<IJob> pJob) = 0;
virtual void SetAdditionalLogger(std::unique_ptr<ILogger> &&pLogger) = 0;
virtual void SetAdditionalLogger(std::shared_ptr<ILogger> &&pLogger) = 0;
static void RunJobBlocking(IJob *pJob);
};

Expand Down
3 changes: 3 additions & 0 deletions src/engine/graphics.h
Expand Up @@ -521,6 +521,9 @@ class IGraphics : public IInterface

virtual SWarning *GetCurWarning() = 0;

// returns true if the error msg was shown
virtual bool ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg) = 0;

protected:
inline CTextureHandle CreateTextureHandle(int Index)
{
Expand Down
49 changes: 26 additions & 23 deletions src/engine/input.h
Expand Up @@ -14,7 +14,7 @@ class IInput : public IInterface
public:
enum
{
INPUT_TEXT_SIZE = 128
INPUT_TEXT_SIZE = 32 * UTF8_BYTE_LENGTH + 1,
};

class CEvent
Expand All @@ -33,40 +33,39 @@ class IInput : public IInterface
};

// quick access to events
int m_NumEvents;
IInput::CEvent m_aInputEvents[INPUT_BUFFER_SIZE];
size_t m_NumEvents;
CEvent m_aInputEvents[INPUT_BUFFER_SIZE];
int64_t m_LastUpdate;
float m_UpdateTime;

public:
enum
{
FLAG_PRESS = 1,
FLAG_RELEASE = 2,
FLAG_REPEAT = 4,
FLAG_TEXT = 8,
FLAG_PRESS = 1 << 0,
FLAG_RELEASE = 1 << 1,
FLAG_TEXT = 1 << 2,
};
enum ECursorType
{
CURSOR_NONE,
CURSOR_MOUSE,
CURSOR_JOYSTICK,
};
enum
{
MAX_COMPOSITION_ARRAY_SIZE = 32, // SDL2 limitation

COMP_LENGTH_INACTIVE = -1,
};

// events
int NumEvents() const { return m_NumEvents; }
virtual bool IsEventValid(CEvent *pEvent) const = 0;
CEvent GetEvent(int Index) const
size_t NumEvents() const { return m_NumEvents; }
virtual bool IsEventValid(const CEvent &Event) const = 0;
const CEvent &GetEvent(size_t Index) const
{
if(Index < 0 || Index >= m_NumEvents)
{
IInput::CEvent e = {0, 0};
return e;
}
dbg_assert(Index < m_NumEvents, "Index invalid");
return m_aInputEvents[Index];
}
CEvent *GetEventsRaw() { return m_aInputEvents; }
int *GetEventCountRaw() { return &m_NumEvents; }

/**
* @return Rolling average of the time in seconds between
Expand Down Expand Up @@ -115,12 +114,16 @@ class IInput : public IInterface
virtual void SetClipboardText(const char *pText) = 0;

// text editing
virtual bool GetIMEState() = 0;
virtual void SetIMEState(bool Activate) = 0;
virtual int GetIMEEditingTextLength() const = 0;
virtual const char *GetIMEEditingText() = 0;
virtual int GetEditingCursor() = 0;
virtual void SetEditingPosition(float X, float Y) = 0;
virtual void StartTextInput() = 0;
virtual void StopTextInput() = 0;
virtual const char *GetComposition() const = 0;
virtual bool HasComposition() const = 0;
virtual int GetCompositionCursor() const = 0;
virtual int GetCompositionLength() const = 0;
virtual const char *GetCandidate(int Index) const = 0;
virtual int GetCandidateCount() const = 0;
virtual int GetCandidateSelectedIndex() const = 0;
virtual void SetCompositionWindowPosition(float X, float Y, float H) = 0;

virtual bool GetDropFile(char *aBuf, int Len) = 0;

Expand Down
19 changes: 11 additions & 8 deletions src/engine/map.h
Expand Up @@ -16,27 +16,30 @@ class IMap : public IInterface
MACRO_INTERFACE("map", 0)
public:
virtual void *GetData(int Index) = 0;
virtual int GetDataSize(int Index) = 0;
virtual int GetDataSize(int Index) const = 0;
virtual void *GetDataSwapped(int Index) = 0;
virtual void UnloadData(int Index) = 0;
virtual void *GetItem(int Index, int *pType, int *pID) = 0;
virtual int NumData() const = 0;

virtual void *GetItem(int Index, int *pType = nullptr, int *pID = nullptr) = 0;
virtual int GetItemSize(int Index) = 0;
virtual void GetType(int Type, int *pStart, int *pNum) = 0;
virtual void *FindItem(int Type, int ID) = 0;
virtual int NumItems() = 0;
virtual int NumItems() const = 0;
};

class IEngineMap : public IMap
{
MACRO_INTERFACE("enginemap", 0)
public:
virtual bool Load(const char *pMapName) = 0;
virtual bool IsLoaded() = 0;
virtual void Unload() = 0;
virtual SHA256_DIGEST Sha256() = 0;
virtual unsigned Crc() = 0;
virtual int MapSize() = 0;
virtual IOHANDLE File() = 0;
virtual bool IsLoaded() const = 0;
virtual IOHANDLE File() const = 0;

virtual SHA256_DIGEST Sha256() const = 0;
virtual unsigned Crc() const = 0;
virtual int MapSize() const = 0;
};

extern IEngineMap *CreateEngineMap();
Expand Down
5 changes: 3 additions & 2 deletions src/engine/server.h
Expand Up @@ -3,6 +3,7 @@
#ifndef ENGINE_SERVER_H
#define ENGINE_SERVER_H

#include <optional>
#include <type_traits>

#include <base/hash.h>
Expand Down Expand Up @@ -207,7 +208,7 @@ class IServer : public IInterface
virtual void SetClientName(int ClientID, char const *pName) = 0;
virtual void SetClientClan(int ClientID, char const *pClan) = 0;
virtual void SetClientCountry(int ClientID, int Country) = 0;
virtual void SetClientScore(int ClientID, int Score) = 0;
virtual void SetClientScore(int ClientID, std::optional<int> Score) = 0;
virtual void SetClientFlags(int ClientID, int Flags) = 0;

virtual int SnapNewID() = 0;
Expand Down Expand Up @@ -334,7 +335,7 @@ class IGameServer : public IInterface

/**
* Used to report custom player info to master servers.
*
*
* @param aBuf Should be the json key values to add, starting with a ',' beforehand, like: ',"skin": "default", "team": 1'
* @param i The client id.
*/
Expand Down
10 changes: 5 additions & 5 deletions src/engine/server/main.cpp
Expand Up @@ -119,7 +119,7 @@ int main(int argc, const char **argv)
char aBufName[IO_MAX_PATH_LENGTH];
char aDate[64];
str_timestamp(aDate, sizeof(aDate));
str_format(aBufName, sizeof(aBufName), "dumps/" GAME_NAME "-Server_crash_log_%d_%s.RTP", pid(), aDate);
str_format(aBufName, sizeof(aBufName), "dumps/" GAME_NAME "-Server_%s_crash_log_%s_%d_%s.RTP", CONF_PLATFORM_STRING, aDate, pid(), GIT_SHORTREV_HASH != nullptr ? GIT_SHORTREV_HASH : "");
pStorage->GetCompletePath(IStorage::TYPE_SAVE, aBufName, aBuf, sizeof(aBuf));
set_exception_handler_log_file(aBuf);
#endif
Expand Down Expand Up @@ -153,10 +153,8 @@ int main(int argc, const char **argv)
pServer->RegisterCommands();

// execute autoexec file
IOHANDLE File = pStorage->OpenFile(AUTOEXEC_SERVER_FILE, IOFLAG_READ, IStorage::TYPE_ALL);
if(File)
if(pStorage->FileExists(AUTOEXEC_SERVER_FILE, IStorage::TYPE_ALL))
{
io_close(File);
pConsole->ExecuteFile(AUTOEXEC_SERVER_FILE);
}
else // fallback
Expand Down Expand Up @@ -184,7 +182,8 @@ int main(int argc, const char **argv)
dbg_msg("server", "failed to open '%s' for logging", g_Config.m_Logfile);
}
}
pEngine->SetAdditionalLogger(std::make_unique<CServerLogger>(pServer));
auto pServerLogger = std::make_shared<CServerLogger>(pServer);
pEngine->SetAdditionalLogger(pServerLogger);

// run the server
dbg_msg("server", "starting...");
Expand All @@ -193,6 +192,7 @@ int main(int argc, const char **argv)
MysqlUninit();
secure_random_uninit();

pServerLogger->OnServerDeletion();
// free
delete pKernel;

Expand Down
42 changes: 32 additions & 10 deletions src/engine/server/server.cpp
Expand Up @@ -299,7 +299,7 @@ void CServer::CClient::Reset()
m_LastAckedSnapshot = -1;
m_LastInputTick = -1;
m_SnapRate = CClient::SNAPRATE_INIT;
m_Score = 0;
m_Score = -1;
m_NextMapChunk = 0;
m_Flags = 0;
}
Expand Down Expand Up @@ -473,7 +473,7 @@ void CServer::SetClientCountry(int ClientID, int Country)
m_aClients[ClientID].m_Country = Country;
}

void CServer::SetClientScore(int ClientID, int Score)
void CServer::SetClientScore(int ClientID, std::optional<int> Score)
{
if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY)
return;
Expand Down Expand Up @@ -2017,7 +2017,24 @@ void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients)
q.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan

ADD_INT(q, m_aClients[i].m_Country); // client country
ADD_INT(q, m_aClients[i].m_Score); // client score

int Score;
if(m_aClients[i].m_Score.has_value())
{
Score = m_aClients[i].m_Score.value();
if(Score == 9999)
Score = -10000;
else if(Score == 0) // 0 time isn't displayed otherwise.
Score = -1;
else
Score = -Score;
}
else
{
Score = -9999;
}

ADD_INT(q, Score); // client score
ADD_INT(q, GameServer()->IsClientPlayer(i) ? 1 : 0); // is player?
if(Type == SERVERINFO_EXTENDED)
q.AddString("", 0); // extra info, reserved
Expand Down Expand Up @@ -2099,7 +2116,7 @@ void CServer::CacheServerInfoSixup(CCache *pCache, bool SendClients)
Packer.AddString(ClientName(i), MAX_NAME_LENGTH); // client name
Packer.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan
Packer.AddInt(m_aClients[i].m_Country); // client country
Packer.AddInt(m_aClients[i].m_Score == -9999 ? -1 : -m_aClients[i].m_Score); // client score
Packer.AddInt(m_aClients[i].m_Score.value_or(-1)); // client score
Packer.AddInt(GameServer()->IsClientPlayer(i) ? 0 : 1); // flag spectator=1, bot=2 (player=0)
}
}
Expand Down Expand Up @@ -2230,6 +2247,7 @@ void CServer::UpdateRegisterServerInfo()
"\"size\":%d"
"},"
"\"version\":\"%s\","
"\"client_score_kind\":\"time\","
"\"clients\":[",
MaxClients,
MaxPlayers,
Expand Down Expand Up @@ -2266,7 +2284,7 @@ void CServer::UpdateRegisterServerInfo()
EscapeJson(aCName, sizeof(aCName), ClientName(i)),
EscapeJson(aCClan, sizeof(aCClan), ClientClan(i)),
m_aClients[i].m_Country,
m_aClients[i].m_Score,
m_aClients[i].m_Score.value_or(-9999),
JsonBool(GameServer()->IsClientPlayer(i)),
aExtraPlayerInfo);
str_append(aInfo, aClientInfo, sizeof(aInfo));
Expand Down Expand Up @@ -2568,12 +2586,16 @@ int CServer::Run()

// start server
NETADDR BindAddr;
int NetType = Config()->m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL;

if(!Config()->m_Bindaddr[0] || net_host_lookup(Config()->m_Bindaddr, &BindAddr, NetType) != 0)
if(g_Config.m_Bindaddr[0] == '\0')
{
mem_zero(&BindAddr, sizeof(BindAddr));

BindAddr.type = NetType;
}
else if(net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) != 0)
{
dbg_msg("server", "The configured bindaddr '%s' cannot be resolved", g_Config.m_Bindaddr);
return -1;
}
BindAddr.type = Config()->m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL;

int Port = Config()->m_SvPort;
for(BindAddr.port = Port != 0 ? Port : 8303; !m_NetServer.Open(BindAddr, &m_ServerBan, Config()->m_SvMaxClients, Config()->m_SvMaxClientsPerIP); BindAddr.port++)
Expand Down
5 changes: 3 additions & 2 deletions src/engine/server/server.h
Expand Up @@ -19,6 +19,7 @@

#include <list>
#include <memory>
#include <optional>
#include <vector>

#include "antibot.h"
Expand Down Expand Up @@ -182,7 +183,7 @@ class CServer : public IServer
char m_aName[MAX_NAME_LENGTH];
char m_aClan[MAX_CLAN_LENGTH];
int m_Country;
int m_Score;
std::optional<int> m_Score;
int m_Authed;
int m_AuthKey;
int m_AuthTries;
Expand Down Expand Up @@ -282,7 +283,7 @@ class CServer : public IServer
void SetClientName(int ClientID, const char *pName) override;
void SetClientClan(int ClientID, char const *pClan) override;
void SetClientCountry(int ClientID, int Country) override;
void SetClientScore(int ClientID, int Score) override;
void SetClientScore(int ClientID, std::optional<int> Score) override;
void SetClientFlags(int ClientID, int Flags) override;

void Kick(int ClientID, const char *pReason) override;
Expand Down
9 changes: 9 additions & 0 deletions src/engine/serverbrowser.h
Expand Up @@ -33,6 +33,14 @@ class CServerInfo
NUM_LOCS,
};

enum
{
CLIENT_SCORE_KIND_UNSPECIFIED,
CLIENT_SCORE_KIND_POINTS,
CLIENT_SCORE_KIND_TIME,
CLIENT_SCORE_KIND_TIME_BACKCOMPAT,
};

class CClient
{
public:
Expand Down Expand Up @@ -68,6 +76,7 @@ class CServerInfo
int m_MaxPlayers;
int m_NumPlayers;
int m_Flags;
int m_ClientScoreKind;
TRISTATE m_Favorite;
TRISTATE m_FavoriteAllowPing;
bool m_Official;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/assertion_logger.cpp
Expand Up @@ -48,7 +48,7 @@ void CAssertionLogger::Dump()
char aAssertLogFile[IO_MAX_PATH_LENGTH];
char aDate[64];
str_timestamp(aDate, sizeof(aDate));
str_format(aAssertLogFile, std::size(aAssertLogFile), "%s%s_assert_log_%d_%s.txt", m_aAssertLogPath, m_aGameName, pid(), aDate);
str_format(aAssertLogFile, std::size(aAssertLogFile), "%s%s_assert_log_%s_%d.txt", m_aAssertLogPath, m_aGameName, aDate, pid());
std::unique_lock<std::mutex> Lock(m_DbgMessageMutex);
IOHANDLE FileHandle = io_open(aAssertLogFile, IOFLAG_WRITE);
if(FileHandle)
Expand Down
4 changes: 3 additions & 1 deletion src/engine/shared/config_variables.h
Expand Up @@ -129,9 +129,11 @@ MACRO_CONFIG_INT(GfxTuneOverlay, gfx_tune_overlay, 20, 1, 100, CFGFLAG_SAVE | CF
MACRO_CONFIG_INT(GfxQuadAsTriangle, gfx_quad_as_triangle, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Render quads as triangles (fixes quad coloring on some GPUs)")

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)")
MACRO_CONFIG_INT(InpTranslatedKeys, inp_translated_keys, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Translate keys before interpreting them, respects keyboard layouts")
MACRO_CONFIG_INT(InpIgnoredModifiers, inp_ignored_modifiers, 0, 0, 65536, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Ignored keyboard modifier mask")
#if defined(CONF_FAMILY_WINDOWS)
MACRO_CONFIG_INT(InpImeNativeUi, inp_ime_native_ui, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Use native UI for IME (may cause IME to not work in fullscreen mode) (changing requires restart)")
#endif

MACRO_CONFIG_INT(InpControllerEnable, inp_controller_enable, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable controller")
MACRO_CONFIG_STR(InpControllerGUID, inp_controller_guid, 34, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Controller GUID which uniquely identifies the active controller")
Expand Down
17 changes: 10 additions & 7 deletions src/engine/shared/console.cpp
Expand Up @@ -622,15 +622,15 @@ void CConsole::ExecuteLineFlag(const char *pStr, int FlagMask, int ClientID, boo
m_FlagMask = Temp;
}

void CConsole::ExecuteFile(const char *pFilename, int ClientID, bool LogFailure, int StorageType)
bool CConsole::ExecuteFile(const char *pFilename, int ClientID, bool LogFailure, int StorageType)
{
// make sure that this isn't being executed already
for(CExecFile *pCur = m_pFirstExec; pCur; pCur = pCur->m_pPrev)
if(str_comp(pFilename, pCur->m_pFilename) == 0)
return;
return false;

if(!m_pStorage)
return;
return false;

// push this one to the stack
CExecFile ThisFile;
Expand All @@ -642,20 +642,22 @@ void CConsole::ExecuteFile(const char *pFilename, int ClientID, bool LogFailure,
// exec the file
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ | IOFLAG_SKIP_BOM, StorageType);

char aBuf[128];
bool Success = false;
char aBuf[32 + IO_MAX_PATH_LENGTH];
if(File)
{
char *pLine;
CLineReader Reader;

str_format(aBuf, sizeof(aBuf), "executing '%s'", pFilename);
Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf);

CLineReader Reader;
Reader.Init(File);

char *pLine;
while((pLine = Reader.Get()))
ExecuteLine(pLine, ClientID);

io_close(File);
Success = true;
}
else if(LogFailure)
{
Expand All @@ -664,6 +666,7 @@ void CConsole::ExecuteFile(const char *pFilename, int ClientID, bool LogFailure,
}

m_pFirstExec = pPrev;
return Success;
}

void CConsole::Con_Echo(IResult *pResult, void *pUserData)
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/console.h
Expand Up @@ -214,7 +214,7 @@ class CConsole : public IConsole
bool LineIsValid(const char *pStr) override;
void ExecuteLine(const char *pStr, int ClientID = -1, bool InterpretSemicolons = true) override;
void ExecuteLineFlag(const char *pStr, int FlagMask, int ClientID = -1, bool InterpretSemicolons = true) override;
void ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) override;
bool ExecuteFile(const char *pFilename, int ClientID = -1, bool LogFailure = false, int StorageType = IStorage::TYPE_ALL) override;

char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) override;
void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) const override;
Expand Down
99 changes: 47 additions & 52 deletions src/engine/shared/datafile.cpp
Expand Up @@ -217,6 +217,28 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
return true;
}

bool CDataFileReader::Close()
{
if(!m_pDataFile)
return true;

// free the data that is loaded
for(int i = 0; i < m_pDataFile->m_Header.m_NumRawData; i++)
free(m_pDataFile->m_ppDataPtrs[i]);

io_close(m_pDataFile->m_File);
free(m_pDataFile);
m_pDataFile = nullptr;
return true;
}

IOHANDLE CDataFileReader::File() const
{
if(!m_pDataFile)
return 0;
return m_pDataFile->m_File;
}

int CDataFileReader::NumData() const
{
if(!m_pDataFile)
Expand All @@ -227,7 +249,7 @@ int CDataFileReader::NumData() const
}

// returns the size in the file
int CDataFileReader::GetFileDataSize(int Index)
int CDataFileReader::GetFileDataSize(int Index) const
{
if(!m_pDataFile)
{
Expand All @@ -240,7 +262,7 @@ int CDataFileReader::GetFileDataSize(int Index)
}

// returns the size of the resulting data
int CDataFileReader::GetDataSize(int Index)
int CDataFileReader::GetDataSize(int Index) const
{
if(!m_pDataFile)
{
Expand All @@ -257,11 +279,11 @@ void *CDataFileReader::GetDataImpl(int Index, int Swap)
{
if(!m_pDataFile)
{
return 0;
return nullptr;
}

if(Index < 0 || Index >= m_pDataFile->m_Header.m_NumRawData)
return 0;
return nullptr;

// load it if needed
if(!m_pDataFile->m_ppDataPtrs[Index])
Expand Down Expand Up @@ -331,7 +353,7 @@ void CDataFileReader::UnloadData(int Index)

//
free(m_pDataFile->m_ppDataPtrs[Index]);
m_pDataFile->m_ppDataPtrs[Index] = 0x0;
m_pDataFile->m_ppDataPtrs[Index] = nullptr;
}

int CDataFileReader::GetItemSize(int Index) const
Expand All @@ -354,7 +376,7 @@ int CDataFileReader::GetExternalItemType(int InternalType)
{
return InternalType;
}
const CItemEx *pItemEx = (const CItemEx *)GetItem(TypeIndex, 0, 0);
const CItemEx *pItemEx = (const CItemEx *)GetItem(TypeIndex);
// Propagate UUID_UNKNOWN, it doesn't hurt.
return g_UuidManager.LookupUuid(pItemEx->ToUuid());
}
Expand All @@ -375,7 +397,7 @@ int CDataFileReader::GetInternalItemType(int ExternalType)
continue;
}
int ID;
if(Uuid == ((const CItemEx *)GetItem(i, 0, &ID))->ToUuid())
if(Uuid == ((const CItemEx *)GetItem(i, nullptr, &ID))->ToUuid())
{
return ID;
}
Expand All @@ -391,7 +413,7 @@ void *CDataFileReader::GetItem(int Index, int *pType, int *pID)
*pType = 0;
if(pID)
*pID = 0;
return 0;
return nullptr;
}

CDatafileItem *pItem = (CDatafileItem *)(m_pDataFile->m_Info.m_pItemStart + m_pDataFile->m_Info.m_pItemOffsets[Index]);
Expand Down Expand Up @@ -439,7 +461,7 @@ int CDataFileReader::FindItemIndex(int Type, int ID)
for(int i = 0; i < Num; i++)
{
int ItemID;
GetItem(Start + i, 0, &ItemID);
GetItem(Start + i, nullptr, &ItemID);
if(ID == ItemID)
{
return Start + i;
Expand All @@ -453,9 +475,9 @@ void *CDataFileReader::FindItem(int Type, int ID)
int Index = FindItemIndex(Type, ID);
if(Index < 0)
{
return 0;
return nullptr;
}
return GetItem(Index, 0, 0);
return GetItem(Index);
}

int CDataFileReader::NumItems() const
Expand All @@ -465,22 +487,6 @@ int CDataFileReader::NumItems() const
return m_pDataFile->m_Header.m_NumItems;
}

bool CDataFileReader::Close()
{
if(!m_pDataFile)
return true;

// free the data that is loaded
int i;
for(i = 0; i < m_pDataFile->m_Header.m_NumRawData; i++)
free(m_pDataFile->m_ppDataPtrs[i]);

io_close(m_pDataFile->m_File);
free(m_pDataFile);
m_pDataFile = 0;
return true;
}

SHA256_DIGEST CDataFileReader::Sha256() const
{
if(!m_pDataFile)
Expand Down Expand Up @@ -509,13 +515,6 @@ int CDataFileReader::MapSize() const
return m_pDataFile->m_Header.m_Size + 16;
}

IOHANDLE CDataFileReader::File()
{
if(!m_pDataFile)
return 0;
return m_pDataFile->m_File;
}

CDataFileWriter::CDataFileWriter()
{
m_File = 0;
Expand All @@ -527,15 +526,15 @@ CDataFileWriter::CDataFileWriter()
CDataFileWriter::~CDataFileWriter()
{
free(m_pItemTypes);
m_pItemTypes = 0;
m_pItemTypes = nullptr;
for(int i = 0; i < m_NumItems; i++)
free(m_pItems[i].m_pData);
for(int i = 0; i < m_NumDatas; ++i)
free(m_pDatas[i].m_pCompressedData);
free(m_pItems);
m_pItems = 0;
m_pItems = nullptr;
free(m_pDatas);
m_pDatas = 0;
m_pDatas = nullptr;
}

bool CDataFileWriter::OpenFile(class IStorage *pStorage, const char *pFilename, int StorageType)
Expand Down Expand Up @@ -568,7 +567,7 @@ bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename, int
return OpenFile(pStorage, pFilename, StorageType);
}

int CDataFileWriter::GetTypeFromIndex(int Index)
int CDataFileWriter::GetTypeFromIndex(int Index) const
{
return ITEMTYPE_EX - Index - 1;
}
Expand Down Expand Up @@ -678,40 +677,36 @@ int CDataFileWriter::Finish()
if(!m_File)
return 1;

int ItemSize = 0;
int TypesSize, HeaderSize, OffsetSize, FileSize, SwapSize;
int DataSize = 0;
CDatafileHeader Header;

// we should now write this file!
if(DEBUG)
dbg_msg("datafile", "writing");

// calculate sizes
int ItemSize = 0;
for(int i = 0; i < m_NumItems; i++)
{
if(DEBUG)
dbg_msg("datafile", "item=%d size=%d (%d)", i, m_pItems[i].m_Size, m_pItems[i].m_Size + (int)sizeof(CDatafileItem));
ItemSize += m_pItems[i].m_Size + sizeof(CDatafileItem);
}

int DataSize = 0;
for(int i = 0; i < m_NumDatas; i++)
DataSize += m_pDatas[i].m_CompressedSize;

// calculate the complete size
TypesSize = m_NumItemTypes * sizeof(CDatafileItemType);
HeaderSize = sizeof(CDatafileHeader);
OffsetSize = (m_NumItems + m_NumDatas + m_NumDatas) * sizeof(int); // ItemOffsets, DataOffsets, DataUncompressedSizes
FileSize = HeaderSize + TypesSize + OffsetSize + ItemSize + DataSize;
SwapSize = FileSize - DataSize;

(void)SwapSize;
const int TypesSize = m_NumItemTypes * sizeof(CDatafileItemType);
const int HeaderSize = sizeof(CDatafileHeader);
const int OffsetSize = (m_NumItems + m_NumDatas + m_NumDatas) * sizeof(int); // ItemOffsets, DataOffsets, DataUncompressedSizes
const int FileSize = HeaderSize + TypesSize + OffsetSize + ItemSize + DataSize;
const int SwapSize = FileSize - DataSize;

if(DEBUG)
dbg_msg("datafile", "num_m_aItemTypes=%d TypesSize=%d m_aItemsize=%d DataSize=%d", m_NumItemTypes, TypesSize, ItemSize, DataSize);

// construct Header
{
CDatafileHeader Header;
Header.m_aID[0] = 'D';
Header.m_aID[1] = 'A';
Header.m_aID[2] = 'T';
Expand Down Expand Up @@ -843,12 +838,12 @@ int CDataFileWriter::Finish()
for(int i = 0; i < m_NumItems; i++)
{
free(m_pItems[i].m_pData);
m_pItems[i].m_pData = 0;
m_pItems[i].m_pData = nullptr;
}
for(int i = 0; i < m_NumDatas; ++i)
{
free(m_pDatas[i].m_pCompressedData);
m_pDatas[i].m_pCompressedData = 0;
m_pDatas[i].m_pCompressedData = nullptr;
}

io_close(m_File);
Expand Down
17 changes: 8 additions & 9 deletions src/engine/shared/datafile.h
Expand Up @@ -20,7 +20,7 @@ class CDataFileReader
{
struct CDatafile *m_pDataFile;
void *GetDataImpl(int Index, int Swap);
int GetFileDataSize(int Index);
int GetFileDataSize(int Index) const;

int GetExternalItemType(int InternalType);
int GetInternalItemType(int ExternalType);
Expand All @@ -30,28 +30,27 @@ class CDataFileReader
m_pDataFile(nullptr) {}
~CDataFileReader() { Close(); }

bool IsOpen() const { return m_pDataFile != nullptr; }

bool Open(class IStorage *pStorage, const char *pFilename, int StorageType);
bool Close();
bool IsOpen() const { return m_pDataFile != nullptr; }
IOHANDLE File() const;

void *GetData(int Index);
void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved
int GetDataSize(int Index);
int GetDataSize(int Index) const;
void UnloadData(int Index);
void *GetItem(int Index, int *pType, int *pID);
int NumData() const;

void *GetItem(int Index, int *pType = nullptr, int *pID = nullptr);
int GetItemSize(int Index) const;
void GetType(int Type, int *pStart, int *pNum);
int FindItemIndex(int Type, int ID);
void *FindItem(int Type, int ID);
int NumItems() const;
int NumData() const;
void Unload();

SHA256_DIGEST Sha256() const;
unsigned Crc() const;
int MapSize() const;
IOHANDLE File();
};

// write access
Expand Down Expand Up @@ -99,8 +98,8 @@ class CDataFileWriter
CDataInfo *m_pDatas;
int m_aExtendedItemTypes[MAX_EXTENDED_ITEM_TYPES];

int GetTypeFromIndex(int Index) const;
int GetExtendedItemTypeIndex(int Type);
int GetTypeFromIndex(int Index);

public:
CDataFileWriter();
Expand Down
16 changes: 8 additions & 8 deletions src/engine/shared/econ.cpp
Expand Up @@ -64,18 +64,18 @@ void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan)
return;

NETADDR BindAddr;
if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0)
if(g_Config.m_EcBindaddr[0] == '\0')
{
// got bindaddr
BindAddr.type = NETTYPE_ALL;
BindAddr.port = g_Config.m_EcPort;
mem_zero(&BindAddr, sizeof(BindAddr));
}
else
else if(net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) != 0)
{
mem_zero(&BindAddr, sizeof(BindAddr));
BindAddr.type = NETTYPE_ALL;
BindAddr.port = g_Config.m_EcPort;
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "The configured bindaddr '%s' cannot be resolved.", g_Config.m_Bindaddr);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf);
}
BindAddr.type = NETTYPE_ALL;
BindAddr.port = g_Config.m_EcPort;

if(m_NetConsole.Open(BindAddr, pNetBan))
{
Expand Down
8 changes: 4 additions & 4 deletions src/engine/shared/engine.cpp
Expand Up @@ -73,9 +73,9 @@ class CEngine : public IEngine
#endif

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

// init the network
Expand Down Expand Up @@ -113,9 +113,9 @@ class CEngine : public IEngine
m_JobPool.Add(std::move(pJob));
}

void SetAdditionalLogger(std::unique_ptr<ILogger> &&pLogger) override
void SetAdditionalLogger(std::shared_ptr<ILogger> &&pLogger) override
{
m_pFutureLogger->Set(std::move(pLogger));
m_pFutureLogger->Set(pLogger);
}
};

Expand Down
28 changes: 10 additions & 18 deletions src/engine/shared/fifo.cpp
Expand Up @@ -95,25 +95,20 @@ void CFifo::Init(IConsole *pConsole, char *pFifoFile, int Flag)
str_append(m_aFilename, pFifoFile, sizeof(m_aFilename));
m_Flag = Flag;

const int WLen = MultiByteToWideChar(CP_UTF8, 0, m_aFilename, -1, NULL, 0);
dbg_assert(WLen > 0, "MultiByteToWideChar failure");
wchar_t *pWide = static_cast<wchar_t *>(malloc(WLen * sizeof(*pWide)));
dbg_assert(MultiByteToWideChar(CP_UTF8, 0, m_aFilename, -1, pWide, WLen) == WLen, "MultiByteToWideChar failure");
m_pPipe = CreateNamedPipeW(pWide,
const std::wstring WideFilename = windows_utf8_to_wide(m_aFilename);
m_pPipe = CreateNamedPipeW(WideFilename.c_str(),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES,
8192,
8192,
NMPWAIT_USE_DEFAULT_WAIT,
NULL);
free(pWide);
if(m_pPipe == INVALID_HANDLE_VALUE)
{
const DWORD LastError = GetLastError();
char *pErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to create named pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
free(pErrorMsg);
const std::string ErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to create named pipe '%s' (%ld %s)", m_aFilename, LastError, ErrorMsg.c_str());
}
else
dbg_msg("fifo", "created named pipe '%s'", m_aFilename);
Expand Down Expand Up @@ -147,9 +142,8 @@ void CFifo::Update()
}
if(LastError != ERROR_PIPE_CONNECTED) // pipe already connected, not an error
{
char *pErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to connect named pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
free(pErrorMsg);
const std::string ErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to connect named pipe '%s' (%ld %s)", m_aFilename, LastError, ErrorMsg.c_str());
return;
}
}
Expand All @@ -162,9 +156,8 @@ void CFifo::Update()
const DWORD LastError = GetLastError();
if(LastError != ERROR_BAD_PIPE) // pipe not connected, not an error
{
char *pErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to peek at pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
free(pErrorMsg);
const std::string ErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to peek at pipe '%s' (%ld %s)", m_aFilename, LastError, ErrorMsg.c_str());
}
return;
}
Expand All @@ -176,9 +169,8 @@ void CFifo::Update()
if(!ReadFile(m_pPipe, pBuf, BytesAvailable, &Length, NULL))
{
const DWORD LastError = GetLastError();
char *pErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to read from pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
free(pErrorMsg);
const std::string ErrorMsg = windows_format_system_message(LastError);
dbg_msg("fifo", "failed to read from pipe '%s' (%ld %s)", m_aFilename, LastError, ErrorMsg.c_str());
free(pBuf);
return;
}
Expand Down
45 changes: 29 additions & 16 deletions src/engine/shared/map.cpp
Expand Up @@ -9,42 +9,50 @@ void *CMap::GetData(int Index)
{
return m_DataFile.GetData(Index);
}
int CMap::GetDataSize(int Index)

int CMap::GetDataSize(int Index) const
{
return m_DataFile.GetDataSize(Index);
}

void *CMap::GetDataSwapped(int Index)
{
return m_DataFile.GetDataSwapped(Index);
}

void CMap::UnloadData(int Index)
{
m_DataFile.UnloadData(Index);
}

int CMap::NumData() const
{
return m_DataFile.NumData();
}

void *CMap::GetItem(int Index, int *pType, int *pID)
{
return m_DataFile.GetItem(Index, pType, pID);
}

int CMap::GetItemSize(int Index)
{
return m_DataFile.GetItemSize(Index);
}

void CMap::GetType(int Type, int *pStart, int *pNum)
{
m_DataFile.GetType(Type, pStart, pNum);
}

void *CMap::FindItem(int Type, int ID)
{
return m_DataFile.FindItem(Type, ID);
}
int CMap::NumItems()
{
return m_DataFile.NumItems();
}

void CMap::Unload()
int CMap::NumItems() const
{
m_DataFile.Close();
return m_DataFile.NumItems();
}

bool CMap::Load(const char *pMapName)
Expand All @@ -55,29 +63,34 @@ bool CMap::Load(const char *pMapName)
return m_DataFile.Open(pStorage, pMapName, IStorage::TYPE_ALL);
}

bool CMap::IsLoaded()
void CMap::Unload()
{
m_DataFile.Close();
}

bool CMap::IsLoaded() const
{
return m_DataFile.IsOpen();
}

SHA256_DIGEST CMap::Sha256()
IOHANDLE CMap::File() const
{
return m_DataFile.Sha256();
return m_DataFile.File();
}

unsigned CMap::Crc()
SHA256_DIGEST CMap::Sha256() const
{
return m_DataFile.Crc();
return m_DataFile.Sha256();
}

int CMap::MapSize()
unsigned CMap::Crc() const
{
return m_DataFile.MapSize();
return m_DataFile.Crc();
}

IOHANDLE CMap::File()
int CMap::MapSize() const
{
return m_DataFile.File();
return m_DataFile.MapSize();
}

extern IEngineMap *CreateEngineMap() { return new CMap; }
25 changes: 11 additions & 14 deletions src/engine/shared/map.h
Expand Up @@ -16,28 +16,25 @@ class CMap : public IEngineMap
CMap();

void *GetData(int Index) override;
int GetDataSize(int Index) override;
int GetDataSize(int Index) const override;
void *GetDataSwapped(int Index) override;
void UnloadData(int Index) override;
void *GetItem(int Index, int *pType, int *pID) override;
int NumData() const override;

void *GetItem(int Index, int *pType = nullptr, int *pID = nullptr) override;
int GetItemSize(int Index) override;
void GetType(int Type, int *pStart, int *pNum) override;
void *FindItem(int Type, int ID) override;
int NumItems() override;

void Unload() override;
int NumItems() const override;

bool Load(const char *pMapName) override;
void Unload() override;
bool IsLoaded() const override;
IOHANDLE File() const override;

bool IsLoaded() override;

SHA256_DIGEST Sha256() override;

unsigned Crc() override;

int MapSize() override;

IOHANDLE File() override;
SHA256_DIGEST Sha256() const override;
unsigned Crc() const override;
int MapSize() const override;
};

#endif
2 changes: 1 addition & 1 deletion src/engine/shared/network.h
Expand Up @@ -57,7 +57,7 @@ enum

NET_MAX_PACKETSIZE = 1400,
NET_MAX_PAYLOAD = NET_MAX_PACKETSIZE - 6,
NET_MAX_CHUNKHEADERSIZE = 5,
NET_MAX_CHUNKHEADERSIZE = 3,
NET_PACKETHEADERSIZE = 3,
NET_MAX_CLIENTS = 64,
NET_MAX_CONSOLE_CLIENTS = 4,
Expand Down
28 changes: 14 additions & 14 deletions src/engine/shared/packer.cpp
Expand Up @@ -7,7 +7,7 @@

void CPacker::Reset()
{
m_Error = 0;
m_Error = false;
m_pCurrent = m_aBuffer;
m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE;
}
Expand All @@ -20,7 +20,7 @@ void CPacker::AddInt(int i)
unsigned char *pNext = CVariableInt::Pack(m_pCurrent, i, m_pEnd - m_pCurrent);
if(!pNext)
{
m_Error = 1;
m_Error = true;
return;
}
m_pCurrent = pNext;
Expand Down Expand Up @@ -51,7 +51,7 @@ void CPacker::AddString(const char *pStr, int Limit)
// Ensure space for the null termination.
if(m_pEnd - m_pCurrent < Length + 1)
{
m_Error = 1;
m_Error = true;
break;
}
Length = str_utf8_encode((char *)m_pCurrent, Codepoint);
Expand All @@ -66,9 +66,9 @@ void CPacker::AddRaw(const void *pData, int Size)
if(m_Error)
return;

if(m_pCurrent + Size >= m_pEnd)
if(m_pCurrent + Size > m_pEnd)
{
m_Error = 1;
m_Error = true;
return;
}

Expand All @@ -82,7 +82,7 @@ void CPacker::AddRaw(const void *pData, int Size)

void CUnpacker::Reset(const void *pData, int Size)
{
m_Error = 0;
m_Error = false;
m_pStart = (const unsigned char *)pData;
m_pEnd = m_pStart + Size;
m_pCurrent = m_pStart;
Expand All @@ -95,15 +95,15 @@ int CUnpacker::GetInt()

if(m_pCurrent >= m_pEnd)
{
m_Error = 1;
m_Error = true;
return 0;
}

int i;
const unsigned char *pNext = CVariableInt::Unpack(m_pCurrent, &i, m_pEnd - m_pCurrent);
if(!pNext)
{
m_Error = 1;
m_Error = true;
return 0;
}
m_pCurrent = pNext;
Expand All @@ -128,14 +128,14 @@ int CUnpacker::GetUncompressedInt()
if(m_Error)
return 0;

if(m_pCurrent + 4 > m_pEnd)
if(m_pCurrent + sizeof(int) > m_pEnd)
{
m_Error = 1;
m_Error = true;
return 0;
}

int i = *(int *)m_pCurrent;
m_pCurrent += 4;
m_pCurrent += sizeof(int);
return i;
}

Expand All @@ -159,7 +159,7 @@ const char *CUnpacker::GetString(int SanitizeType)

if(m_pCurrent >= m_pEnd)
{
m_Error = 1;
m_Error = true;
return "";
}

Expand All @@ -169,7 +169,7 @@ const char *CUnpacker::GetString(int SanitizeType)
m_pCurrent++;
if(m_pCurrent == m_pEnd)
{
m_Error = 1;
m_Error = true;
return "";
}
}
Expand All @@ -192,7 +192,7 @@ const unsigned char *CUnpacker::GetRaw(int Size)
// check for nasty sizes
if(Size < 0 || m_pCurrent + Size > m_pEnd)
{
m_Error = 1;
m_Error = true;
return 0;
}

Expand Down
6 changes: 3 additions & 3 deletions src/engine/shared/packer.h
Expand Up @@ -15,12 +15,12 @@ class CPacker
unsigned char m_aBuffer[PACKER_BUFFER_SIZE];
unsigned char *m_pCurrent;
unsigned char *m_pEnd;
int m_Error;
bool m_Error;

public:
void Reset();
void AddInt(int i);
void AddString(const char *pStr, int Limit);
void AddString(const char *pStr, int Limit = PACKER_BUFFER_SIZE);
void AddRaw(const void *pData, int Size);

int Size() const { return (int)(m_pCurrent - m_aBuffer); }
Expand All @@ -33,7 +33,7 @@ class CUnpacker
const unsigned char *m_pStart;
const unsigned char *m_pCurrent;
const unsigned char *m_pEnd;
int m_Error;
bool m_Error;

public:
enum
Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/protocol.h
Expand Up @@ -123,6 +123,7 @@ enum
VERSION_DDNET_WEAPON_SHIELDS = 16010,
VERSION_DDNET_NEW_HUD = 16020,
VERSION_DDNET_MULTI_LASER = 16040,
VERSION_DDNET_ENTITY_NETOBJS = 16200,
};

typedef std::bitset<MAX_CLIENTS> CClientMask;
Expand Down
13 changes: 13 additions & 0 deletions src/engine/shared/serverinfo.cpp
Expand Up @@ -64,6 +64,7 @@ bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
const json_value &ServerInfo = *pJson;
const json_value &MaxClients = ServerInfo["max_clients"];
const json_value &MaxPlayers = ServerInfo["max_players"];
const json_value &ClientScoreKind = ServerInfo["client_score_kind"];
const json_value &Passworded = ServerInfo["passworded"];
const json_value &GameType = ServerInfo["game_type"];
const json_value &Name = ServerInfo["name"];
Expand All @@ -74,6 +75,7 @@ bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
Error = Error || MaxClients.type != json_integer;
Error = Error || MaxPlayers.type != json_integer;
Error = Error || Passworded.type != json_boolean;
Error = Error || (ClientScoreKind.type != json_none && ClientScoreKind.type != json_string);
Error = Error || GameType.type != json_string || str_has_cc(GameType);
Error = Error || Name.type != json_string || str_has_cc(Name);
Error = Error || MapName.type != json_string || str_has_cc(MapName);
Expand All @@ -85,6 +87,15 @@ bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
}
pOut->m_MaxClients = json_int_get(&MaxClients);
pOut->m_MaxPlayers = json_int_get(&MaxPlayers);
pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_UNSPECIFIED;
if(ClientScoreKind.type == json_string && str_startswith(ClientScoreKind, "points"))
{
pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_POINTS;
}
else if(ClientScoreKind.type == json_string && str_startswith(ClientScoreKind, "time"))
{
pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_TIME;
}
pOut->m_Passworded = Passworded;
str_copy(pOut->m_aGameType, GameType);
str_copy(pOut->m_aName, Name);
Expand Down Expand Up @@ -174,6 +185,7 @@ bool CServerInfo2::operator==(const CServerInfo2 &Other) const
Unequal = Unequal || m_NumClients != Other.m_NumClients;
Unequal = Unequal || m_MaxPlayers != Other.m_MaxPlayers;
Unequal = Unequal || m_NumPlayers != Other.m_NumPlayers;
Unequal = Unequal || m_ClientScoreKind != Other.m_ClientScoreKind;
Unequal = Unequal || m_Passworded != Other.m_Passworded;
Unequal = Unequal || str_comp(m_aGameType, Other.m_aGameType) != 0;
Unequal = Unequal || str_comp(m_aName, Other.m_aName) != 0;
Expand Down Expand Up @@ -206,6 +218,7 @@ CServerInfo2::operator CServerInfo() const
Result.m_NumClients = m_NumClients;
Result.m_MaxPlayers = m_MaxPlayers;
Result.m_NumPlayers = m_NumPlayers;
Result.m_ClientScoreKind = m_ClientScoreKind;
Result.m_Flags = m_Passworded ? SERVER_FLAG_PASSWORD : 0;
str_copy(Result.m_aGameType, m_aGameType);
str_copy(Result.m_aName, m_aName);
Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/serverinfo.h
Expand Up @@ -29,6 +29,7 @@ class CServerInfo2
int m_NumClients; // Indirectly serialized.
int m_MaxPlayers;
int m_NumPlayers; // Not serialized.
int m_ClientScoreKind;
bool m_Passworded;
char m_aGameType[16];
char m_aName[64];
Expand Down
8 changes: 2 additions & 6 deletions src/engine/shared/storage.cpp
Expand Up @@ -289,19 +289,15 @@ class CStorage : public IStorage
char aBuf[IO_MAX_PATH_LENGTH];
str_copy(m_aBinarydir, pArgv0, Pos + 1);
str_format(aBuf, sizeof(aBuf), "%s/" PLAT_SERVER_EXEC, m_aBinarydir);
IOHANDLE File = io_open(aBuf, IOFLAG_READ);
if(File)
if(fs_is_file(aBuf))
{
io_close(File);
return;
}
#if defined(CONF_PLATFORM_MACOS)
str_append(m_aBinarydir, "/../../../DDNet-Server.app/Contents/MacOS", sizeof(m_aBinarydir));
str_format(aBuf, sizeof(aBuf), "%s/" PLAT_SERVER_EXEC, m_aBinarydir);
IOHANDLE FileBis = io_open(aBuf, IOFLAG_READ);
if(FileBis)
if(fs_is_file(aBuf))
{
io_close(FileBis);
return;
}
#endif
Expand Down
172 changes: 151 additions & 21 deletions src/engine/textrender.h
Expand Up @@ -9,6 +9,7 @@
#include <engine/graphics.h>

#include <cstdint>
#include <memory>

enum
{
Expand All @@ -20,9 +21,25 @@ enum

enum ETextAlignment
{
TEXTALIGN_LEFT = 1 << 0,
TEXTALIGN_LEFT = 0,
TEXTALIGN_CENTER = 1 << 1,
TEXTALIGN_RIGHT = 1 << 2,
TEXTALIGN_TOP = 0, // this is also 0, so the default alignment is top-left
TEXTALIGN_MIDDLE = 1 << 3,
TEXTALIGN_BOTTOM = 1 << 4,

TEXTALIGN_TL = TEXTALIGN_TOP | TEXTALIGN_LEFT,
TEXTALIGN_TC = TEXTALIGN_TOP | TEXTALIGN_CENTER,
TEXTALIGN_TR = TEXTALIGN_TOP | TEXTALIGN_RIGHT,
TEXTALIGN_ML = TEXTALIGN_MIDDLE | TEXTALIGN_LEFT,
TEXTALIGN_MC = TEXTALIGN_MIDDLE | TEXTALIGN_CENTER,
TEXTALIGN_MR = TEXTALIGN_MIDDLE | TEXTALIGN_RIGHT,
TEXTALIGN_BL = TEXTALIGN_BOTTOM | TEXTALIGN_LEFT,
TEXTALIGN_BC = TEXTALIGN_BOTTOM | TEXTALIGN_CENTER,
TEXTALIGN_BR = TEXTALIGN_BOTTOM | TEXTALIGN_RIGHT,

TEXTALIGN_MASK_HORIZONTAL = TEXTALIGN_LEFT | TEXTALIGN_CENTER | TEXTALIGN_RIGHT,
TEXTALIGN_MASK_VERTICAL = TEXTALIGN_TOP | TEXTALIGN_MIDDLE | TEXTALIGN_BOTTOM,
};

enum ETextRenderFlags
Expand All @@ -45,6 +62,69 @@ enum
TEXT_FONT_ICON_FONT = 0,
};

namespace FontIcons {
// Each font icon is named according to its official name in Font Awesome
MAYBE_UNUSED static const char *FONT_ICON_LOCK = "\xEF\x80\xA3";
MAYBE_UNUSED static const char *FONT_ICON_MAGNIFYING_GLASS = "\xEF\x80\x82";
MAYBE_UNUSED static const char *FONT_ICON_HEART = "\xEF\x80\x84";
MAYBE_UNUSED static const char *FONT_ICON_STAR = "\xEF\x80\x85";
MAYBE_UNUSED static const char *FONT_ICON_CHECK = "\xEF\x80\x8C";
MAYBE_UNUSED static const char *FONT_ICON_XMARK = "\xEF\x80\x8D";
MAYBE_UNUSED static const char *FONT_ICON_ARROW_ROTATE_LEFT = "\xEF\x83\xA2";
MAYBE_UNUSED static const char *FONT_ICON_ARROW_ROTATE_RIGHT = "\xEF\x80\x9E";
MAYBE_UNUSED static const char *FONT_ICON_CERTIFICATE = "\xEF\x82\xA3";
MAYBE_UNUSED static const char *FONT_ICON_FLAG_CHECKERED = "\xEF\x84\x9E";
MAYBE_UNUSED static const char *FONT_ICON_BAN = "\xEF\x81\x9E";
MAYBE_UNUSED static const char *FONT_ICON_CIRCLE_CHEVRON_DOWN = "\xEF\x84\xBA";
MAYBE_UNUSED static const char *FONT_ICON_SQUARE_MINUS = "\xEF\x85\x86";
MAYBE_UNUSED static const char *FONT_ICON_SQUARE_PLUS = "\xEF\x83\xBE";

MAYBE_UNUSED static const char *FONT_ICON_HOUSE = "\xEF\x80\x95";
MAYBE_UNUSED static const char *FONT_ICON_NEWSPAPER = "\xEF\x87\xAA";
MAYBE_UNUSED static const char *FONT_ICON_POWER_OFF = "\xEF\x80\x91";
MAYBE_UNUSED static const char *FONT_ICON_GEAR = "\xEF\x80\x93";
MAYBE_UNUSED static const char *FONT_ICON_PEN_TO_SQUARE = "\xEF\x81\x84";
MAYBE_UNUSED static const char *FONT_ICON_CLAPPERBOARD = "\xEE\x84\xB1";
MAYBE_UNUSED static const char *FONT_ICON_EARTH_AMERICAS = "\xEF\x95\xBD";

MAYBE_UNUSED static const char *FONT_ICON_SLASH = "\xEF\x9C\x95";
MAYBE_UNUSED static const char *FONT_ICON_PLAY = "\xEF\x81\x8B";
MAYBE_UNUSED static const char *FONT_ICON_PAUSE = "\xEF\x81\x8C";
MAYBE_UNUSED static const char *FONT_ICON_STOP = "\xEF\x81\x8D";
MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_LEFT = "\xEF\x81\x93";
MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_RIGHT = "\xEF\x81\x94";
MAYBE_UNUSED static const char *FONT_ICON_BACKWARD = "\xEF\x81\x8A";
MAYBE_UNUSED static const char *FONT_ICON_FORWARD = "\xEF\x81\x8E";
MAYBE_UNUSED static const char *FONT_ICON_RIGHT_FROM_BRACKET = "\xEF\x8B\xB5";
MAYBE_UNUSED static const char *FONT_ICON_RIGHT_TO_BRACKET = "\xEF\x8B\xB6";
MAYBE_UNUSED static const char *FONT_ICON_ARROW_UP_RIGHT_FROM_SQUARE = "\xEF\x82\x8E";
MAYBE_UNUSED static const char *FONT_ICON_BACKWARD_STEP = "\xEF\x81\x88";
MAYBE_UNUSED static const char *FONT_ICON_FORWARD_STEP = "\xEF\x81\x91";
MAYBE_UNUSED static const char *FONT_ICON_KEYBOARD = "\xE2\x8C\xA8";

MAYBE_UNUSED static const char *FONT_ICON_FOLDER = "\xEF\x81\xBB";
MAYBE_UNUSED static const char *FONT_ICON_FOLDER_TREE = "\xEF\xA0\x82";
MAYBE_UNUSED static const char *FONT_ICON_FILM = "\xEF\x80\x88";
MAYBE_UNUSED static const char *FONT_ICON_MAP = "\xEF\x89\xB9";
MAYBE_UNUSED static const char *FONT_ICON_IMAGE = "\xEF\x80\xBE";
MAYBE_UNUSED static const char *FONT_ICON_MUSIC = "\xEF\x80\x81";
MAYBE_UNUSED static const char *FONT_ICON_FILE = "\xEF\x85\x9B";

MAYBE_UNUSED static const char *FONT_ICON_ARROWS_LEFT_RIGHT = "\xEF\x8C\xB7";
MAYBE_UNUSED static const char *FONT_ICON_ARROWS_UP_DOWN = "\xEF\x81\xBD";
MAYBE_UNUSED static const char *FONT_ICON_CIRCLE_PLAY = "\xEF\x85\x84";
MAYBE_UNUSED static const char *FONT_ICON_BORDER_ALL = "\xEF\xA1\x8C";
MAYBE_UNUSED static const char *FONT_ICON_EYE = "\xEF\x81\xAE";
MAYBE_UNUSED static const char *FONT_ICON_EYE_SLASH = "\xEF\x81\xB0";

MAYBE_UNUSED static const char *FONT_ICON_DICE_ONE = "\xEF\x94\xA5";
MAYBE_UNUSED static const char *FONT_ICON_DICE_TWO = "\xEF\x94\xA8";
MAYBE_UNUSED static const char *FONT_ICON_DICE_THREE = "\xEF\x94\xA7";
MAYBE_UNUSED static const char *FONT_ICON_DICE_FOUR = "\xEF\x94\xA4";
MAYBE_UNUSED static const char *FONT_ICON_DICE_FIVE = "\xEF\x94\xA3";
MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6";
} // end namespace FontIcons

class CFont;

enum ETextCursorSelectionMode
Expand All @@ -67,6 +147,23 @@ enum ETextCursorCursorMode
TEXT_CURSOR_CURSOR_MODE_SET,
};

struct STextBoundingBox
{
float m_X;
float m_Y;
float m_W;
float m_H;

float Right() const { return m_X + m_W; }
float Bottom() const { return m_Y + m_H; }
vec2 Size() const { return vec2(m_W, m_H); }
void MoveBy(vec2 Offset)
{
m_X += Offset.x;
m_Y += Offset.y;
}
};

class CTextCursor
{
public:
Expand All @@ -89,27 +186,57 @@ class CTextCursor
float m_AlignedFontSize;

ETextCursorSelectionMode m_CalculateSelectionMode;
float m_SelectionHeightFactor;

// these coordinates are repsected if selection mode is set to calculate @see ETextCursorSelectionMode
int m_PressMouseX;
int m_PressMouseY;
// these coordinates are repsected if selection/cursor mode is set to calculate @see ETextCursorSelectionMode / @see ETextCursorCursorMode
int m_ReleaseMouseX;
int m_ReleaseMouseY;
// these coordinates are respected if selection mode is set to calculate @see ETextCursorSelectionMode
vec2 m_PressMouse;
// these coordinates are respected if selection/cursor mode is set to calculate @see ETextCursorSelectionMode / @see ETextCursorCursorMode
vec2 m_ReleaseMouse;

// note m_SelectionStart can be bigger than m_SelectionEnd, depending on how the mouse cursor was dragged
// also note, that these are the character offsets decoded
int m_SelectionStart;
int m_SelectionEnd;

ETextCursorCursorMode m_CursorMode;
bool m_ForceCursorRendering;
// note this is the decoded character offset
int m_CursorCharacter;
vec2 m_CursorRenderedPosition;

float Height() const
{
return m_LineCount * m_AlignedFontSize;
}

STextBoundingBox BoundingBox() const
{
return {m_StartX, m_StartY, m_LongestLineWidth, Height()};
}
};

struct STextContainerUsages
{
int m_Dummy = 0;
};

struct STextContainerIndex
{
int m_Index;
std::shared_ptr<STextContainerUsages> m_UseCount =
std::make_shared<STextContainerUsages>(STextContainerUsages());

STextContainerIndex() { Reset(); }
bool Valid() const { return m_Index >= 0; }
void Reset() { m_Index = -1; }
};

struct STextSizeProperties
{
float *m_pHeight = nullptr;
float *m_pAlignedFontSize = nullptr;
float *m_pMaxCharacterHeightInLine = nullptr;
int *m_pLineCount = nullptr;
};

class ITextRender : public IInterface
Expand All @@ -133,23 +260,25 @@ class ITextRender : public IInterface

ColorRGBA DefaultTextColor() const { return ColorRGBA(1, 1, 1, 1); }
ColorRGBA DefaultTextOutlineColor() const { return ColorRGBA(0, 0, 0, 0.3f); }
ColorRGBA DefaultSelectionColor() const { return ColorRGBA(0, 0, 1.0f, 1.0f); }
ColorRGBA DefaultTextSelectionColor() const { return ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); }

//
virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length) = 0;
virtual bool CreateTextContainer(int &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void AppendTextContainer(int TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual bool CreateTextContainer(STextContainerIndex &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void AppendTextContainer(STextContainerIndex TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
// either creates a new text container or appends to a existing one
virtual bool CreateOrAppendTextContainer(int &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual bool CreateOrAppendTextContainer(STextContainerIndex &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
// just deletes and creates text container
virtual void RecreateTextContainer(int &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void RecreateTextContainerSoft(int &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void DeleteTextContainer(int &TextContainerIndex) = 0;
virtual void RecreateTextContainer(STextContainerIndex &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void RecreateTextContainerSoft(STextContainerIndex &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) = 0;
virtual void DeleteTextContainer(STextContainerIndex &TextContainerIndex) = 0;

virtual void UploadTextContainer(STextContainerIndex TextContainerIndex) = 0;

virtual void UploadTextContainer(int TextContainerIndex) = 0;
virtual void RenderTextContainer(STextContainerIndex TextContainerIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor) = 0;
virtual void RenderTextContainer(STextContainerIndex TextContainerIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, float X, float Y) = 0;

virtual void RenderTextContainer(int TextContainerIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor) = 0;
virtual void RenderTextContainer(int TextContainerIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, float X, float Y) = 0;
virtual STextBoundingBox GetBoundingBoxTextContainer(STextContainerIndex TextContainerIndex) = 0;

virtual void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontHeight) = 0;
virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) const = 0;
Expand All @@ -167,14 +296,15 @@ class ITextRender : public IInterface
virtual void TextOutlineColor(ColorRGBA rgb) = 0;
virtual void TextSelectionColor(float r, float g, float b, float a) = 0;
virtual void TextSelectionColor(ColorRGBA rgb) = 0;
virtual void Text(float x, float y, float Size, const char *pText, float LineWidth) = 0;
virtual float TextWidth(float Size, const char *pText, int StrLength, float LineWidth, float *pAlignedHeight = nullptr, float *pMaxCharacterHeightInLine = nullptr) = 0;
virtual int TextLineCount(float Size, const char *pText, float LineWidth) = 0;
virtual void Text(float x, float y, float Size, const char *pText, float LineWidth = -1.0f) = 0;
virtual float TextWidth(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0, const STextSizeProperties &TextSizeProps = {}) = 0;
virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) = 0;

virtual ColorRGBA GetTextColor() const = 0;
virtual ColorRGBA GetTextOutlineColor() const = 0;
virtual ColorRGBA GetTextSelectionColor() const = 0;

virtual void OnPreWindowResize() = 0;
virtual void OnWindowResize() = 0;
};

Expand Down
8 changes: 6 additions & 2 deletions src/game/client/component.h
Expand Up @@ -179,6 +179,10 @@ class CComponent
* The render order depends on the component insertion order.
*/
virtual void OnRender(){};
/**
* Called when a new snapshot is received.
*/
virtual void OnNewSnapshot(){};
/**
* Called when the input gets released, for example when a text box loses focus.
*/
Expand All @@ -205,9 +209,9 @@ class CComponent
virtual bool OnCursorMove(float x, float y, IInput::ECursorType CursorType) { return false; }
/**
* Called on a input event.
* @param e The input event.
* @param Event The input event.
*/
virtual bool OnInput(IInput::CEvent e) { return false; }
virtual bool OnInput(const IInput::CEvent &Event) { return false; }
};

#endif
28 changes: 14 additions & 14 deletions src/game/client/components/binds.cpp
Expand Up @@ -8,7 +8,7 @@

static const ColorRGBA gs_BindPrintColor{1.0f, 1.0f, 0.8f, 1.0f};

bool CBinds::CBindsSpecial::OnInput(IInput::CEvent Event)
bool CBinds::CBindsSpecial::OnInput(const IInput::CEvent &Event)
{
// only handle F and composed F binds
if(((Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F12) || (Event.m_Key >= KEY_F13 && Event.m_Key <= KEY_F24)) && (Event.m_Key != KEY_F5 || !m_pClient->m_Menus.IsActive()))
Expand Down Expand Up @@ -123,33 +123,33 @@ int CBinds::GetModifierMaskOfKey(int Key)
}
}

bool CBinds::OnInput(IInput::CEvent e)
bool CBinds::OnInput(const IInput::CEvent &Event)
{
// don't handle invalid events
if(e.m_Key <= 0 || e.m_Key >= KEY_LAST)
if(Event.m_Key <= KEY_FIRST || Event.m_Key >= KEY_LAST)
return false;

int Mask = GetModifierMask(Input());
int KeyModifierMask = GetModifierMaskOfKey(e.m_Key);
int KeyModifierMask = GetModifierMaskOfKey(Event.m_Key);
Mask &= ~KeyModifierMask;

bool ret = false;
if(m_aapKeyBindings[Mask][e.m_Key])
if(m_aapKeyBindings[Mask][Event.m_Key])
{
if(e.m_Flags & IInput::FLAG_PRESS)
Console()->ExecuteLineStroked(1, m_aapKeyBindings[Mask][e.m_Key]);
if(e.m_Flags & IInput::FLAG_RELEASE)
Console()->ExecuteLineStroked(0, m_aapKeyBindings[Mask][e.m_Key]);
if(Event.m_Flags & IInput::FLAG_PRESS)
Console()->ExecuteLineStroked(1, m_aapKeyBindings[Mask][Event.m_Key]);
if(Event.m_Flags & IInput::FLAG_RELEASE)
Console()->ExecuteLineStroked(0, m_aapKeyBindings[Mask][Event.m_Key]);
ret = true;
}

if(m_aapKeyBindings[0][e.m_Key] && !ret)
if(m_aapKeyBindings[0][Event.m_Key] && !ret)
{
// When ctrl+shift are pressed (ctrl+shift binds and also the hard-coded ctrl+shift+d, ctrl+shift+g, ctrl+shift+e), ignore other +xxx binds
if(e.m_Flags & IInput::FLAG_PRESS && Mask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) && Mask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
Console()->ExecuteLineStroked(1, m_aapKeyBindings[0][e.m_Key]);
if(e.m_Flags & IInput::FLAG_RELEASE)
Console()->ExecuteLineStroked(0, m_aapKeyBindings[0][e.m_Key]);
if(Event.m_Flags & IInput::FLAG_PRESS && Mask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) && Mask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
Console()->ExecuteLineStroked(1, m_aapKeyBindings[0][Event.m_Key]);
if(Event.m_Flags & IInput::FLAG_RELEASE)
Console()->ExecuteLineStroked(0, m_aapKeyBindings[0][Event.m_Key]);
ret = true;
}

Expand Down
4 changes: 2 additions & 2 deletions src/game/client/components/binds.h
Expand Up @@ -32,7 +32,7 @@ class CBinds : public CComponent
public:
CBinds *m_pBinds;
virtual int Sizeof() const override { return sizeof(*this); }
virtual bool OnInput(IInput::CEvent Event) override;
virtual bool OnInput(const IInput::CEvent &Event) override;
};

enum
Expand Down Expand Up @@ -60,7 +60,7 @@ class CBinds : public CComponent
static const char *GetKeyBindModifiersName(int ModifierCombination);

virtual void OnConsoleInit() override;
virtual bool OnInput(IInput::CEvent Event) override;
virtual bool OnInput(const IInput::CEvent &Event) override;

// DDRace

Expand Down
91 changes: 59 additions & 32 deletions src/game/client/components/broadcast.cpp
Expand Up @@ -16,58 +16,85 @@
void CBroadcast::OnReset()
{
m_BroadcastTick = 0;
m_BroadcastRenderOffset = -1.0f;
TextRender()->DeleteTextContainer(m_TextContainerIndex);
}

void CBroadcast::OnWindowResize()
{
m_BroadcastRenderOffset = -1.0f;
TextRender()->DeleteTextContainer(m_TextContainerIndex);
}

void CBroadcast::OnRender()
{
if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
return;

RenderServerBroadcast();
}

void CBroadcast::RenderServerBroadcast()
{
if(m_pClient->m_Scoreboard.Active() || m_pClient->m_Motd.IsActive() || !g_Config.m_ClShowBroadcasts)
return;
const float SecondsRemaining = (m_BroadcastTick - Client()->GameTick(g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed();
if(SecondsRemaining <= 0.0f)
{
TextRender()->DeleteTextContainer(m_TextContainerIndex);
return;
}

Graphics()->MapScreen(0, 0, 300 * Graphics()->ScreenAspect(), 300);
const float Height = 300.0f;
const float Width = Height * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, Height);

if(Client()->GameTick(g_Config.m_ClDummy) < m_BroadcastTick)
if(m_BroadcastRenderOffset < 0.0f)
m_BroadcastRenderOffset = Width / 2.0f - TextRender()->TextWidth(12.0f, m_aBroadcastText, -1, Width) / 2.0f;

if(!m_TextContainerIndex.Valid())
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, m_BroadcastRenderOffset, 40.0f, 12.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = 300 * Graphics()->ScreenAspect() - m_BroadcastRenderOffset;
TextRender()->TextEx(&Cursor, m_aBroadcastText, -1);
TextRender()->SetCursor(&Cursor, m_BroadcastRenderOffset, 40.0f, 12.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = Width;
TextRender()->CreateTextContainer(m_TextContainerIndex, &Cursor, m_aBroadcastText);
}
if(m_TextContainerIndex.Valid())
{
const float Alpha = SecondsRemaining >= 1.0f ? 1.0f : SecondsRemaining;
ColorRGBA TextColor = TextRender()->DefaultTextColor();
TextColor.a *= Alpha;
ColorRGBA OutlineColor = TextRender()->DefaultTextOutlineColor();
OutlineColor.a *= Alpha;
TextRender()->RenderTextContainer(m_TextContainerIndex, TextColor, OutlineColor);
}
}

void CBroadcast::OnMessage(int MsgType, void *pRawMsg)
{
if(MsgType == NETMSGTYPE_SV_BROADCAST)
{
CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg;
str_copy(m_aBroadcastText, pMsg->m_pMessage);
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, 0, 0, 12.0f, TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = 300 * Graphics()->ScreenAspect();
TextRender()->TextEx(&Cursor, m_aBroadcastText, -1);
m_BroadcastRenderOffset = 150 * Graphics()->ScreenAspect() - Cursor.m_X / 2;
m_BroadcastTick = Client()->GameTick(g_Config.m_ClDummy) + Client()->GameTickSpeed() * 10;
if(g_Config.m_ClPrintBroadcasts)
OnBroadcastMessage((CNetMsg_Sv_Broadcast *)pRawMsg);
}
}

void CBroadcast::OnBroadcastMessage(const CNetMsg_Sv_Broadcast *pMsg)
{
str_copy(m_aBroadcastText, pMsg->m_pMessage);
m_BroadcastTick = Client()->GameTick(g_Config.m_ClDummy) + Client()->GameTickSpeed() * 10;
m_BroadcastRenderOffset = -1.0f;
TextRender()->DeleteTextContainer(m_TextContainerIndex);

if(g_Config.m_ClPrintBroadcasts)
{
const char *pText = m_aBroadcastText;
char aLine[sizeof(m_aBroadcastText)];
while((pText = str_next_token(pText, "\n", aLine, sizeof(aLine))))
{
char aBuf[1024];
int i, ii;
for(i = 0, ii = 0; i < str_length(m_aBroadcastText); i++)
if(aLine[0] != '\0')
{
if(m_aBroadcastText[i] == '\n')
{
aBuf[ii] = '\0';
ii = 0;
if(aBuf[0])
m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageHighlightColor)));
}
else
{
aBuf[ii] = m_aBroadcastText[i];
ii++;
}
m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aLine, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageHighlightColor)));
}
aBuf[ii] = '\0';
if(aBuf[0])
m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageHighlightColor)));
}
}
}
8 changes: 8 additions & 0 deletions src/game/client/components/broadcast.h
Expand Up @@ -2,6 +2,9 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_BROADCAST_H
#define GAME_CLIENT_COMPONENTS_BROADCAST_H

#include <engine/textrender.h>

#include <game/client/component.h>

class CBroadcast : public CComponent
Expand All @@ -10,10 +13,15 @@ class CBroadcast : public CComponent
char m_aBroadcastText[1024];
int m_BroadcastTick;
float m_BroadcastRenderOffset;
STextContainerIndex m_TextContainerIndex;

void RenderServerBroadcast();
void OnBroadcastMessage(const CNetMsg_Sv_Broadcast *pMsg);

public:
virtual int Sizeof() const override { return sizeof(*this); }
virtual void OnReset() override;
virtual void OnWindowResize() override;
virtual void OnRender() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override;
};
Expand Down
207 changes: 46 additions & 161 deletions src/game/client/components/chat.cpp
Expand Up @@ -26,7 +26,7 @@ CChat::CChat()
for(auto &Line : m_aLines)
{
// reset the container indices, so the text containers can be deleted on reset
Line.m_TextContainerIndex = -1;
Line.m_TextContainerIndex.Reset();
Line.m_QuadContainerIndex = -1;
}

Expand All @@ -36,6 +36,8 @@ CChat::CChat()
std::sort(m_vCommands.begin(), m_vCommands.end());

m_Mode = MODE_NONE;

m_Input.SetClipboardLineCallback([this](const char *pStr) { SayChat(pStr); });
}

void CChat::RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp)
Expand Down Expand Up @@ -77,8 +79,6 @@ void CChat::Reset()
m_PrevShowChat = false;

m_Show = false;
m_InputUpdate = false;
m_ChatStringOffset = 0;
m_CompletionUsed = false;
m_CompletionChosen = -1;
m_aCompletionBuffer[0] = 0;
Expand Down Expand Up @@ -165,104 +165,11 @@ void CChat::OnInit()
Console()->Chain("cl_chat_old", ConchainChatOld, this);
}

bool CChat::OnInput(IInput::CEvent Event)
bool CChat::OnInput(const IInput::CEvent &Event)
{
if(m_Mode == MODE_NONE)
return false;

if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_V))
{
const char *pText = Input()->GetClipboardText();
if(pText)
{
// if the text has more than one line, we send all lines except the last one
// the last one is set as in the text field
char aLine[256];
int i, Begin = 0;
for(i = 0; i < str_length(pText); i++)
{
if(pText[i] == '\n')
{
int max = minimum(i - Begin + 1, (int)sizeof(aLine));
str_copy(aLine, pText + Begin, max);
Begin = i + 1;
SayChat(aLine);
while(pText[i] == '\n')
i++;
}
}
int max = minimum(i - Begin + 1, (int)sizeof(aLine));
str_copy(aLine, pText + Begin, max);
m_Input.Append(aLine);
}
}

if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_C))
{
Input()->SetClipboardText(m_Input.GetString());
}

if(Input()->ModifierIsPressed()) // jump to spaces and special ASCII characters
{
int SearchDirection = 0;
if(Input()->KeyPress(KEY_LEFT) || Input()->KeyPress(KEY_BACKSPACE))
SearchDirection = -1;
else if(Input()->KeyPress(KEY_RIGHT) || Input()->KeyPress(KEY_DELETE))
SearchDirection = 1;

if(SearchDirection != 0)
{
int OldOffset = m_Input.GetCursorOffset();

int FoundAt = SearchDirection > 0 ? m_Input.GetLength() - 1 : 0;
for(int i = m_Input.GetCursorOffset() + SearchDirection; SearchDirection > 0 ? i < m_Input.GetLength() - 1 : i > 0; i += SearchDirection)
{
int Next = i + SearchDirection;
if((m_Input.GetString()[Next] == ' ') ||
(m_Input.GetString()[Next] >= 32 && m_Input.GetString()[Next] <= 47) ||
(m_Input.GetString()[Next] >= 58 && m_Input.GetString()[Next] <= 64) ||
(m_Input.GetString()[Next] >= 91 && m_Input.GetString()[Next] <= 96))
{
FoundAt = i;
if(SearchDirection < 0)
FoundAt++;
break;
}
}

if(Input()->KeyPress(KEY_BACKSPACE))
{
if(m_Input.GetCursorOffset() != 0)
{
char aText[512];
str_copy(aText, m_Input.GetString(), FoundAt + 1);

if(m_Input.GetCursorOffset() != str_length(m_Input.GetString()))
str_append(aText, m_Input.GetString() + m_Input.GetCursorOffset(), str_length(m_Input.GetString()));

m_Input.Set(aText);
}
}
else if(Input()->KeyPress(KEY_DELETE))
{
if(m_Input.GetCursorOffset() != m_Input.GetLength())
{
char aText[512];
aText[0] = '\0';

str_copy(aText, m_Input.GetString(), m_Input.GetCursorOffset() + 1);

if(FoundAt != m_Input.GetLength())
str_append(aText, m_Input.GetString() + FoundAt, sizeof(aText));

m_Input.Set(aText);
FoundAt = OldOffset;
}
}
m_Input.SetCursorOffset(FoundAt);
}
}

if(Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
{
DisableMode();
Expand Down Expand Up @@ -307,7 +214,7 @@ bool CChat::OnInput(IInput::CEvent Event)
if(!m_CompletionUsed)
{
const char *pCursor = m_Input.GetString() + m_Input.GetCursorOffset();
for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor - 1) != ' '; --pCursor, ++Count)
for(size_t Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor - 1) != ' '; --pCursor, ++Count)
;
m_PlaceholderOffset = pCursor - m_Input.GetString();

Expand Down Expand Up @@ -405,10 +312,8 @@ bool CChat::OnInput(IInput::CEvent Event)
str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength, sizeof(aBuf));

m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_pName) + 1;
m_OldChatStringLength = m_Input.GetLength();
m_Input.Set(aBuf); // TODO: Use Add instead
m_Input.Set(aBuf);
m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength);
m_InputUpdate = true;
}
}
else
Expand Down Expand Up @@ -470,10 +375,8 @@ bool CChat::OnInput(IInput::CEvent Event)
str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength, sizeof(aBuf));

m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionString);
m_OldChatStringLength = m_Input.GetLength();
m_Input.Set(aBuf); // TODO: Use Add instead
m_Input.Set(aBuf);
m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength);
m_InputUpdate = true;
}
}
}
Expand All @@ -486,9 +389,7 @@ bool CChat::OnInput(IInput::CEvent Event)
m_CompletionUsed = false;
}

m_OldChatStringLength = m_Input.GetLength();
m_Input.ProcessInput(Event);
m_InputUpdate = true;
}

if(Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
Expand Down Expand Up @@ -532,19 +433,19 @@ void CChat::EnableMode(int Team)
else
m_Mode = MODE_ALL;

Input()->SetIMEState(true);
Input()->Clear();
m_CompletionChosen = -1;
m_CompletionUsed = false;
m_Input.Activate(EInputPriority::CHAT);
}
}

void CChat::DisableMode()
{
if(m_Mode != MODE_NONE)
{
Input()->SetIMEState(false);
m_Mode = MODE_NONE;
m_Input.Deactivate();
}
}

Expand Down Expand Up @@ -966,7 +867,7 @@ void CChat::OnPrepareLines()
if(Now > m_aLines[r].m_Time + 16 * time_freq() && !m_PrevShowChat)
break;

if(m_aLines[r].m_TextContainerIndex != -1 && !ForceRecreate)
if(m_aLines[r].m_TextContainerIndex.Valid() && !ForceRecreate)
continue;

TextRender()->DeleteTextContainer(m_aLines[r].m_TextContainerIndex);
Expand Down Expand Up @@ -1021,11 +922,11 @@ void CChat::OnPrepareLines()
}

CTextCursor AppendCursor = Cursor;

AppendCursor.m_LongestLineWidth = 0.0f;
if(!IsScoreBoardOpen && !g_Config.m_ClChatOld)
{
AppendCursor.m_StartX = Cursor.m_X;
AppendCursor.m_LineWidth -= (Cursor.m_LongestLineWidth - Cursor.m_StartX);
AppendCursor.m_LineWidth -= Cursor.m_LongestLineWidth;
}

TextRender()->TextEx(&AppendCursor, m_aLines[r].m_aText, -1);
Expand Down Expand Up @@ -1113,10 +1014,13 @@ void CChat::OnPrepareLines()
TextRender()->TextColor(Color);

CTextCursor AppendCursor = Cursor;
AppendCursor.m_LongestLineWidth = 0.0f;
float OriginalWidth = 0.0f;
if(!IsScoreBoardOpen && !g_Config.m_ClChatOld)
{
AppendCursor.m_LineWidth -= (Cursor.m_LongestLineWidth - Cursor.m_StartX);
AppendCursor.m_StartX = Cursor.m_X;
AppendCursor.m_LineWidth -= Cursor.m_LongestLineWidth;
OriginalWidth = Cursor.m_LongestLineWidth;
}

TextRender()->CreateOrAppendTextContainer(m_aLines[r].m_TextContainerIndex, &AppendCursor, m_aLines[r].m_aText);
Expand All @@ -1125,11 +1029,11 @@ void CChat::OnPrepareLines()
{
float Height = m_aLines[r].m_aYOffset[OffsetType];
Graphics()->SetColor(1, 1, 1, 1);
m_aLines[r].m_QuadContainerIndex = Graphics()->CreateRectQuadContainer(Begin, y, (AppendCursor.m_LongestLineWidth - TextBegin) + RealMsgPaddingX * 1.5f, Height, MESSAGE_ROUNDING, IGraphics::CORNER_ALL);
m_aLines[r].m_QuadContainerIndex = Graphics()->CreateRectQuadContainer(Begin, y, OriginalWidth + AppendCursor.m_LongestLineWidth + RealMsgPaddingX * 1.5f, Height, MESSAGE_ROUNDING, IGraphics::CORNER_ALL);
}

TextRender()->SetRenderFlags(CurRenderFlags);
if(m_aLines[r].m_TextContainerIndex != -1)
if(m_aLines[r].m_TextContainerIndex.Valid())
TextRender()->UploadTextContainer(m_aLines[r].m_TextContainerIndex);
}

Expand All @@ -1153,8 +1057,10 @@ void CChat::OnRender()
--m_PendingChatCounter;
}

float Width = 300.0f * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f);
const float Height = 300.0f;
const float Width = Height * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, Height);

float x = 5.0f;
float y = 300.0f - 20.0f;
if(m_Mode != MODE_NONE)
Expand All @@ -1163,7 +1069,6 @@ void CChat::OnRender()
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = Width - 190.0f;
Cursor.m_MaxLines = 2;

if(m_Mode == MODE_ALL)
TextRender()->TextEx(&Cursor, Localize("All"), -1);
Expand All @@ -1174,52 +1079,32 @@ void CChat::OnRender()

TextRender()->TextEx(&Cursor, ": ", -1);

// IME candidate editing
bool Editing = false;
int EditingCursor = Input()->GetEditingCursor();
if(Input()->GetIMEState())
{
if(str_length(Input()->GetIMEEditingText()))
{
m_Input.Editing(Input()->GetIMEEditingText(), EditingCursor);
Editing = true;
}
}
const float MessageMaxWidth = Cursor.m_LineWidth - (Cursor.m_X - Cursor.m_StartX);
const CUIRect ClippingRect = {Cursor.m_X, Cursor.m_Y, MessageMaxWidth, 2.25f * Cursor.m_FontSize};
const float XScale = Graphics()->ScreenWidth() / Width;
const float YScale = Graphics()->ScreenHeight() / Height;
Graphics()->ClipEnable((int)(ClippingRect.x * XScale), (int)(ClippingRect.y * YScale), (int)(ClippingRect.w * XScale), (int)(ClippingRect.h * YScale));

// check if the visible text has to be moved
if(m_InputUpdate)
{
if(m_ChatStringOffset > 0 && m_Input.GetLength(Editing) < m_OldChatStringLength)
m_ChatStringOffset = maximum(0, m_ChatStringOffset - (m_OldChatStringLength - m_Input.GetLength(Editing)));
float ScrollOffset = m_Input.GetScrollOffset();
float ScrollOffsetChange = m_Input.GetScrollOffsetChange();

if(m_ChatStringOffset > m_Input.GetCursorOffset(Editing))
m_ChatStringOffset -= m_ChatStringOffset - m_Input.GetCursorOffset(Editing);
else
{
CTextCursor Temp = Cursor;
Temp.m_Flags = 0;
TextRender()->TextEx(&Temp, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset);
TextRender()->TextEx(&Temp, "|", -1);
while(Temp.m_LineCount > 2)
{
++m_ChatStringOffset;
Temp = Cursor;
Temp.m_Flags = 0;
TextRender()->TextEx(&Temp, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset);
TextRender()->TextEx(&Temp, "|", -1);
}
}
m_InputUpdate = false;
}
m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active
const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f};
const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth);

Graphics()->ClipDisable();

// Scroll up or down to keep the caret inside the clipping rect
const float CaretPositionY = m_Input.GetCaretPosition().y - ScrollOffsetChange;
if(CaretPositionY < ClippingRect.y)
ScrollOffsetChange -= ClippingRect.y - CaretPositionY;
else if(CaretPositionY + Cursor.m_FontSize > ClippingRect.y + ClippingRect.h)
ScrollOffsetChange += CaretPositionY + Cursor.m_FontSize - (ClippingRect.y + ClippingRect.h);

UI()->DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, ClippingRect.h, BoundingBox.m_H);

TextRender()->TextEx(&Cursor, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset);
static float MarkerOffset = TextRender()->TextWidth(8.0f, "|", -1, -1.0f) / 3;
CTextCursor Marker = Cursor;
Marker.m_X -= MarkerOffset;
TextRender()->TextEx(&Marker, "|", -1);
TextRender()->TextEx(&Cursor, m_Input.GetString(Editing) + m_Input.GetCursorOffset(Editing), -1);
if(m_pClient->m_GameConsole.IsClosed())
Input()->SetEditingPosition(Marker.m_X, Marker.m_Y + Marker.m_FontSize);
m_Input.SetScrollOffset(ScrollOffset);
m_Input.SetScrollOffsetChange(ScrollOffsetChange);
}

#if defined(CONF_VIDEORECORDER)
Expand Down Expand Up @@ -1274,7 +1159,7 @@ void CChat::OnRender()
}
}

if(m_aLines[r].m_TextContainerIndex != -1)
if(m_aLines[r].m_TextContainerIndex.Valid())
{
if(!g_Config.m_ClChatOld && m_aLines[r].m_HasRenderTee)
{
Expand Down
9 changes: 3 additions & 6 deletions src/game/client/components/chat.h
Expand Up @@ -15,7 +15,7 @@

class CChat : public CComponent
{
CLineInput m_Input;
CLineInputBuffered<512> m_Input;

static constexpr float CHAT_WIDTH = 200.0f;
static constexpr float CHAT_HEIGHT_FULL = 200.0f;
Expand All @@ -40,7 +40,7 @@ class CChat : public CComponent
bool m_Friend;
bool m_Highlighted;

int m_TextContainerIndex;
STextContainerIndex m_TextContainerIndex;
int m_QuadContainerIndex;

char m_aSkinName[std::size(g_Config.m_ClPlayerSkin)];
Expand Down Expand Up @@ -80,9 +80,6 @@ class CChat : public CComponent

int m_Mode;
bool m_Show;
bool m_InputUpdate;
int m_ChatStringOffset;
int m_OldChatStringLength;
bool m_CompletionUsed;
int m_CompletionChosen;
char m_aCompletionBuffer[256];
Expand Down Expand Up @@ -166,7 +163,7 @@ class CChat : public CComponent
void Reset();
void OnRelease() override;
void OnMessage(int MsgType, void *pRawMsg) override;
bool OnInput(IInput::CEvent Event) override;
bool OnInput(const IInput::CEvent &Event) override;
void OnInit() override;

void RebuildChat();
Expand Down