Better map settings input (autocomplete, validation)

This commit is contained in:
Corantin H 2023-12-18 02:08:12 +01:00
parent a2c8869025
commit 9cc8a28305
19 changed files with 2367 additions and 562 deletions

View file

@ -2299,8 +2299,11 @@ if(CLIENT)
editor_object.cpp
editor_object.h
editor_props.cpp
editor_server_settings.cpp
editor_server_settings.h
editor_trackers.cpp
editor_trackers.h
editor_ui.h
explanations.cpp
map_grid.cpp
map_grid.h

View file

@ -3590,6 +3590,18 @@ int str_toint(const char *str)
return str_toint_base(str, 10);
}
bool str_toint(const char *str, int *out)
{
// returns true if conversion was successful
char *end;
int value = strtol(str, &end, 10);
if(*end != '\0')
return false;
if(out != nullptr)
*out = value;
return true;
}
int str_toint_base(const char *str, int base)
{
return strtol(str, nullptr, base);
@ -3610,6 +3622,18 @@ float str_tofloat(const char *str)
return strtod(str, nullptr);
}
bool str_tofloat(const char *str, float *out)
{
// returns true if conversion was successful
char *end;
float value = strtod(str, &end);
if(*end != '\0')
return false;
if(out != nullptr)
*out = value;
return true;
}
void str_from_int(int value, char *buffer, size_t buffer_size)
{
buffer[0] = '\0'; // Fix false positive clang-analyzer-core.UndefinedBinaryOperatorResult when using result

View file

@ -2120,10 +2120,12 @@ typedef struct
void net_stats(NETSTATS *stats);
int str_toint(const char *str);
bool str_toint(const char *str, int *out);
int str_toint_base(const char *str, int base);
unsigned long str_toulong_base(const char *str, int base);
int64_t str_toint64_base(const char *str, int base = 10);
float str_tofloat(const char *str);
bool str_tofloat(const char *str, float *out);
void str_from_int(int value, char *buffer, size_t buffer_size);

View file

@ -1668,7 +1668,7 @@ public:
while(pCurrent < pBatchEnd && pCurrent != pEllipsis)
{
const int PrevCharCount = pCursor->m_CharCount;
const int PrevCharCount = pCursor->m_GlyphCount;
pCursor->m_CharCount += pTmp - pCurrent;
pCurrent = pTmp;
int Character = NextCharacter;
@ -1754,9 +1754,9 @@ public:
if(ColorOption < (int)pCursor->m_vColorSplits.size())
{
STextColorSplit &Split = pCursor->m_vColorSplits.at(ColorOption);
if(PrevCharCount >= Split.m_CharIndex && PrevCharCount < Split.m_CharIndex + Split.m_Length)
if(PrevCharCount >= Split.m_CharIndex && (Split.m_Length == -1 || PrevCharCount < Split.m_CharIndex + Split.m_Length))
Color = Split.m_Color;
if(PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1))
if(Split.m_Length != -1 && PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1))
ColorOption++;
}

View file

@ -10,6 +10,7 @@ class IConfigManager : public IInterface
MACRO_INTERFACE("config")
public:
typedef void (*SAVECALLBACKFUNC)(IConfigManager *pConfig, void *pUserData);
typedef void (*POSSIBLECFGFUNC)(const struct SConfigVariable *, void *pUserData);
virtual void Init() = 0;
virtual void Reset(const char *pScriptName) = 0;
@ -23,6 +24,8 @@ public:
virtual void WriteLine(const char *pLine) = 0;
virtual void StoreUnknownCommand(const char *pCommand) = 0;
virtual void PossibleConfigVariables(const char *pStr, int FlagMask, POSSIBLECFGFUNC pfnCallback, void *pUserData) = 0;
};
extern IConfigManager *CreateConfigManager();

View file

@ -11,361 +11,6 @@
CConfig g_Config;
static void EscapeParam(char *pDst, const char *pSrc, int Size)
{
str_escape(&pDst, pSrc, pDst + Size);
}
struct SConfigVariable
{
enum EVariableType
{
VAR_INT,
VAR_COLOR,
VAR_STRING,
};
IConsole *m_pConsole;
const char *m_pScriptName;
EVariableType m_Type;
int m_Flags;
const char *m_pHelp;
// Note that this only applies to the console command and the SetValue function,
// but the underlying config variable can still be modified programatically.
bool m_ReadOnly = false;
SConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp) :
m_pConsole(pConsole),
m_pScriptName(pScriptName),
m_Type(Type),
m_Flags(Flags),
m_pHelp(pHelp)
{
}
virtual ~SConfigVariable() = default;
virtual void Register() = 0;
virtual bool IsDefault() const = 0;
virtual void Serialize(char *pOut, size_t Size) const = 0;
virtual void ResetToDefault() = 0;
virtual void ResetToOld() = 0;
protected:
void ExecuteLine(const char *pLine) const
{
m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1);
}
bool CheckReadOnly() const
{
if(!m_ReadOnly)
return false;
char aBuf[IConsole::CMDLINE_LENGTH + 64];
str_format(aBuf, sizeof(aBuf), "The config variable '%s' cannot be changed right now.", m_pScriptName);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
return true;
}
};
struct SIntConfigVariable : public SConfigVariable
{
int *m_pVariable;
int m_Default;
int m_Min;
int m_Max;
int m_OldValue;
SIntConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, int *pVariable, int Default, int Min, int Max) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pVariable(pVariable),
m_Default(Default),
m_Min(Min),
m_Max(Max),
m_OldValue(Default)
{
*m_pVariable = m_Default;
}
~SIntConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SIntConfigVariable *pData = static_cast<SIntConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
int Value = pResult->GetInteger(0);
// do clamping
if(pData->m_Min != pData->m_Max)
{
if(Value < pData->m_Min)
Value = pData->m_Min;
if(pData->m_Max != 0 && Value > pData->m_Max)
Value = pData->m_Max;
}
*pData->m_pVariable = Value;
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
pData->m_OldValue = Value;
}
else
{
char aBuf[32];
str_format(aBuf, sizeof(aBuf), "Value: %d", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return *m_pVariable == m_Default;
}
void Serialize(char *pOut, size_t Size, int Value) const
{
str_format(pOut, Size, "%s %i", m_pScriptName, Value);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, *m_pVariable);
}
void SetValue(int Value)
{
if(CheckReadOnly())
return;
char aBuf[IConsole::CMDLINE_LENGTH];
Serialize(aBuf, sizeof(aBuf), Value);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_Default);
}
void ResetToOld() override
{
*m_pVariable = m_OldValue;
}
};
struct SColorConfigVariable : public SConfigVariable
{
unsigned *m_pVariable;
unsigned m_Default;
bool m_Light;
bool m_Alpha;
unsigned m_OldValue;
SColorConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, unsigned *pVariable, unsigned Default) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pVariable(pVariable),
m_Default(Default),
m_Light(Flags & CFGFLAG_COLLIGHT),
m_Alpha(Flags & CFGFLAG_COLALPHA),
m_OldValue(Default)
{
*m_pVariable = m_Default;
}
~SColorConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
const ColorHSLA Color = pResult->GetColor(0, pData->m_Light);
const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
*pData->m_pVariable = Value;
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
pData->m_OldValue = Value;
}
else
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
ColorHSLA Hsla = ColorHSLA(*pData->m_pVariable, true);
if(pData->m_Light)
Hsla = Hsla.UnclampLighting();
str_format(aBuf, sizeof(aBuf), "H: %d°, S: %d%%, L: %d%%", round_truncate(Hsla.h * 360), round_truncate(Hsla.s * 100), round_truncate(Hsla.l * 100));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
const ColorRGBA Rgba = color_cast<ColorRGBA>(Hsla);
str_format(aBuf, sizeof(aBuf), "R: %d, G: %d, B: %d, #%06X", round_truncate(Rgba.r * 255), round_truncate(Rgba.g * 255), round_truncate(Rgba.b * 255), Rgba.Pack(false));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
if(pData->m_Alpha)
{
str_format(aBuf, sizeof(aBuf), "A: %d%%", round_truncate(Hsla.a * 100));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return *m_pVariable == m_Default;
}
void Serialize(char *pOut, size_t Size, unsigned Value) const
{
str_format(pOut, Size, "%s %u", m_pScriptName, Value);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, *m_pVariable);
}
void SetValue(unsigned Value)
{
if(CheckReadOnly())
return;
char aBuf[IConsole::CMDLINE_LENGTH];
Serialize(aBuf, sizeof(aBuf), Value);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_Default);
}
void ResetToOld() override
{
*m_pVariable = m_OldValue;
}
};
struct SStringConfigVariable : public SConfigVariable
{
char *m_pStr;
const char *m_pDefault;
size_t m_MaxSize;
char *m_pOldValue;
SStringConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, char *pStr, const char *pDefault, size_t MaxSize, char *pOldValue) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pStr(pStr),
m_pDefault(pDefault),
m_MaxSize(MaxSize),
m_pOldValue(pOldValue)
{
str_copy(m_pStr, m_pDefault, m_MaxSize);
str_copy(m_pOldValue, m_pDefault, m_MaxSize);
}
~SStringConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SStringConfigVariable *pData = static_cast<SStringConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
const char *pString = pResult->GetString(0);
if(!str_utf8_check(pString))
{
char aTemp[4];
size_t Length = 0;
while(*pString)
{
size_t Size = str_utf8_encode(aTemp, static_cast<unsigned char>(*pString++));
if(Length + Size < pData->m_MaxSize)
{
mem_copy(pData->m_pStr + Length, aTemp, Size);
Length += Size;
}
else
break;
}
pData->m_pStr[Length] = '\0';
}
else
str_copy(pData->m_pStr, pString, pData->m_MaxSize);
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
str_copy(pData->m_pOldValue, pData->m_pStr, pData->m_MaxSize);
}
else
{
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?r", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return str_comp(m_pStr, m_pDefault) == 0;
}
void Serialize(char *pOut, size_t Size, const char *pValue) const
{
str_copy(pOut, m_pScriptName, Size);
str_append(pOut, " \"", Size);
const int OutLen = str_length(pOut);
EscapeParam(pOut + OutLen, pValue, Size - OutLen - 1); // -1 to ensure space for final quote
str_append(pOut, "\"", Size);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, m_pStr);
}
void SetValue(const char *pValue)
{
if(CheckReadOnly())
return;
char aBuf[2048];
Serialize(aBuf, sizeof(aBuf), pValue);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_pDefault);
}
void ResetToOld() override
{
str_copy(m_pStr, m_pOldValue, m_MaxSize);
}
};
CConfigManager::CConfigManager()
{
m_pConsole = nullptr;
@ -388,7 +33,7 @@ void CConfigManager::Init()
#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \
{ \
const char *pHelp = Min == Max ? Desc " (default: " #Def ")" : Max == 0 ? Desc " (default: " #Def ", min: " #Min ")" : Desc " (default: " #Def ", min: " #Min ", max: " #Max ")"; \
const char *pHelp = Min == Max ? Desc " (default: " #Def ")" : (Max == 0 ? Desc " (default: " #Def ", min: " #Min ")" : Desc " (default: " #Def ", min: " #Min ", max: " #Max ")"); \
AddVariable(m_ConfigHeap.Allocate<SIntConfigVariable>(m_pConsole, #ScriptName, SConfigVariable::VAR_INT, Flags, pHelp, &g_Config.m_##Name, Def, Min, Max)); \
}
@ -549,6 +194,20 @@ void CConfigManager::StoreUnknownCommand(const char *pCommand)
m_vpUnknownCommands.push_back(m_ConfigHeap.StoreString(pCommand));
}
void CConfigManager::PossibleConfigVariables(const char *pStr, int FlagMask, POSSIBLECFGFUNC pfnCallback, void *pUserData)
{
for(const SConfigVariable *pVariable : m_vpAllVariables)
{
if(pVariable->m_Flags & FlagMask)
{
if(str_find_nocase(pVariable->m_pScriptName, pStr))
{
pfnCallback(pVariable, pUserData);
}
}
}
}
void CConfigManager::Con_Reset(IConsole::IResult *pResult, void *pUserData)
{
static_cast<CConfigManager *>(pUserData)->Reset(pResult->GetString(0));

View file

@ -4,6 +4,7 @@
#define ENGINE_SHARED_CONFIG_H
#include <base/detect.h>
#include <base/system.h>
#include <engine/config.h>
#include <engine/console.h>
@ -58,6 +59,361 @@ enum
CFGFLAG_INSENSITIVE = 1 << 12,
};
static void EscapeParam(char *pDst, const char *pSrc, int Size)
{
str_escape(&pDst, pSrc, pDst + Size);
}
struct SConfigVariable
{
enum EVariableType
{
VAR_INT,
VAR_COLOR,
VAR_STRING,
};
IConsole *m_pConsole;
const char *m_pScriptName;
EVariableType m_Type;
int m_Flags;
const char *m_pHelp;
// Note that this only applies to the console command and the SetValue function,
// but the underlying config variable can still be modified programatically.
bool m_ReadOnly = false;
SConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp) :
m_pConsole(pConsole),
m_pScriptName(pScriptName),
m_Type(Type),
m_Flags(Flags),
m_pHelp(pHelp)
{
}
virtual ~SConfigVariable() = default;
virtual void Register() = 0;
virtual bool IsDefault() const = 0;
virtual void Serialize(char *pOut, size_t Size) const = 0;
virtual void ResetToDefault() = 0;
virtual void ResetToOld() = 0;
protected:
void ExecuteLine(const char *pLine) const
{
m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1);
}
bool CheckReadOnly() const
{
if(!m_ReadOnly)
return false;
char aBuf[IConsole::CMDLINE_LENGTH + 64];
str_format(aBuf, sizeof(aBuf), "The config variable '%s' cannot be changed right now.", m_pScriptName);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
return true;
}
};
struct SIntConfigVariable : public SConfigVariable
{
int *m_pVariable;
int m_Default;
int m_Min;
int m_Max;
int m_OldValue;
SIntConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, int *pVariable, int Default, int Min, int Max) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pVariable(pVariable),
m_Default(Default),
m_Min(Min),
m_Max(Max),
m_OldValue(Default)
{
*m_pVariable = m_Default;
}
~SIntConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SIntConfigVariable *pData = static_cast<SIntConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
int Value = pResult->GetInteger(0);
// do clamping
if(pData->m_Min != pData->m_Max)
{
if(Value < pData->m_Min)
Value = pData->m_Min;
if(pData->m_Max != 0 && Value > pData->m_Max)
Value = pData->m_Max;
}
*pData->m_pVariable = Value;
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
pData->m_OldValue = Value;
}
else
{
char aBuf[32];
str_format(aBuf, sizeof(aBuf), "Value: %d", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return *m_pVariable == m_Default;
}
void Serialize(char *pOut, size_t Size, int Value) const
{
str_format(pOut, Size, "%s %i", m_pScriptName, Value);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, *m_pVariable);
}
void SetValue(int Value)
{
if(CheckReadOnly())
return;
char aBuf[IConsole::CMDLINE_LENGTH];
Serialize(aBuf, sizeof(aBuf), Value);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_Default);
}
void ResetToOld() override
{
*m_pVariable = m_OldValue;
}
};
struct SColorConfigVariable : public SConfigVariable
{
unsigned *m_pVariable;
unsigned m_Default;
bool m_Light;
bool m_Alpha;
unsigned m_OldValue;
SColorConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, unsigned *pVariable, unsigned Default) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pVariable(pVariable),
m_Default(Default),
m_Light(Flags & CFGFLAG_COLLIGHT),
m_Alpha(Flags & CFGFLAG_COLALPHA),
m_OldValue(Default)
{
*m_pVariable = m_Default;
}
~SColorConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
const ColorHSLA Color = pResult->GetColor(0, pData->m_Light);
const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
*pData->m_pVariable = Value;
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
pData->m_OldValue = Value;
}
else
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
ColorHSLA Hsla = ColorHSLA(*pData->m_pVariable, true);
if(pData->m_Light)
Hsla = Hsla.UnclampLighting();
str_format(aBuf, sizeof(aBuf), "H: %d°, S: %d%%, L: %d%%", round_truncate(Hsla.h * 360), round_truncate(Hsla.s * 100), round_truncate(Hsla.l * 100));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
const ColorRGBA Rgba = color_cast<ColorRGBA>(Hsla);
str_format(aBuf, sizeof(aBuf), "R: %d, G: %d, B: %d, #%06X", round_truncate(Rgba.r * 255), round_truncate(Rgba.g * 255), round_truncate(Rgba.b * 255), Rgba.Pack(false));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
if(pData->m_Alpha)
{
str_format(aBuf, sizeof(aBuf), "A: %d%%", round_truncate(Hsla.a * 100));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return *m_pVariable == m_Default;
}
void Serialize(char *pOut, size_t Size, unsigned Value) const
{
str_format(pOut, Size, "%s %u", m_pScriptName, Value);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, *m_pVariable);
}
void SetValue(unsigned Value)
{
if(CheckReadOnly())
return;
char aBuf[IConsole::CMDLINE_LENGTH];
Serialize(aBuf, sizeof(aBuf), Value);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_Default);
}
void ResetToOld() override
{
*m_pVariable = m_OldValue;
}
};
struct SStringConfigVariable : public SConfigVariable
{
char *m_pStr;
const char *m_pDefault;
size_t m_MaxSize;
char *m_pOldValue;
SStringConfigVariable(IConsole *pConsole, const char *pScriptName, EVariableType Type, int Flags, const char *pHelp, char *pStr, const char *pDefault, size_t MaxSize, char *pOldValue) :
SConfigVariable(pConsole, pScriptName, Type, Flags, pHelp),
m_pStr(pStr),
m_pDefault(pDefault),
m_MaxSize(MaxSize),
m_pOldValue(pOldValue)
{
str_copy(m_pStr, m_pDefault, m_MaxSize);
str_copy(m_pOldValue, m_pDefault, m_MaxSize);
}
~SStringConfigVariable() override = default;
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
{
SStringConfigVariable *pData = static_cast<SStringConfigVariable *>(pUserData);
if(pResult->NumArguments())
{
if(pData->CheckReadOnly())
return;
const char *pString = pResult->GetString(0);
if(!str_utf8_check(pString))
{
char aTemp[4];
size_t Length = 0;
while(*pString)
{
size_t Size = str_utf8_encode(aTemp, static_cast<unsigned char>(*pString++));
if(Length + Size < pData->m_MaxSize)
{
mem_copy(pData->m_pStr + Length, aTemp, Size);
Length += Size;
}
else
break;
}
pData->m_pStr[Length] = '\0';
}
else
str_copy(pData->m_pStr, pString, pData->m_MaxSize);
if(pResult->m_ClientID != IConsole::CLIENT_ID_GAME)
str_copy(pData->m_pOldValue, pData->m_pStr, pData->m_MaxSize);
}
else
{
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
}
void Register() override
{
m_pConsole->Register(m_pScriptName, "?r", m_Flags, CommandCallback, this, m_pHelp);
}
bool IsDefault() const override
{
return str_comp(m_pStr, m_pDefault) == 0;
}
void Serialize(char *pOut, size_t Size, const char *pValue) const
{
str_copy(pOut, m_pScriptName, Size);
str_append(pOut, " \"", Size);
const int OutLen = str_length(pOut);
EscapeParam(pOut + OutLen, pValue, Size - OutLen - 1); // -1 to ensure space for final quote
str_append(pOut, "\"", Size);
}
void Serialize(char *pOut, size_t Size) const override
{
Serialize(pOut, Size, m_pStr);
}
void SetValue(const char *pValue)
{
if(CheckReadOnly())
return;
char aBuf[2048];
Serialize(aBuf, sizeof(aBuf), pValue);
ExecuteLine(aBuf);
}
void ResetToDefault() override
{
SetValue(m_pDefault);
}
void ResetToOld() override
{
str_copy(m_pStr, m_pOldValue, m_MaxSize);
}
};
class CConfigManager : public IConfigManager
{
IConsole *m_pConsole;
@ -79,8 +435,8 @@ class CConfigManager : public IConfigManager
};
std::vector<SCallback> m_vCallbacks;
std::vector<struct SConfigVariable *> m_vpAllVariables;
std::vector<struct SConfigVariable *> m_vpGameVariables;
std::vector<SConfigVariable *> m_vpAllVariables;
std::vector<SConfigVariable *> m_vpGameVariables;
std::vector<const char *> m_vpUnknownCommands;
CHeap m_ConfigHeap;
@ -103,6 +459,8 @@ public:
void WriteLine(const char *pLine) override;
void StoreUnknownCommand(const char *pCommand) override;
void PossibleConfigVariables(const char *pStr, int FlagMask, POSSIBLECFGFUNC pfnCallback, void *pUserData) override;
};
#endif

View file

@ -145,6 +145,9 @@ MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6";
MAYBE_UNUSED static const char *FONT_ICON_LAYER_GROUP = "\xEF\x97\xBD";
MAYBE_UNUSED static const char *FONT_ICON_UNDO = "\xEF\x8B\xAA";
MAYBE_UNUSED static const char *FONT_ICON_REDO = "\xEF\x8B\xB9";
MAYBE_UNUSED static const char *FONT_ICON_ARROWS_ROTATE = "\xEF\x80\xA1";
MAYBE_UNUSED static const char *FONT_ICON_QUESTION = "\x3F";
} // end namespace FontIcons
enum ETextCursorSelectionMode

View file

@ -166,7 +166,7 @@ void CGameClient::OnConsoleInit()
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 or show current value");
Console()->Register("tune", "s[tuning] ?f[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value or show current value");
Console()->Register("tune_reset", "?s[tuning]", CFGFLAG_SERVER, 0, 0, "Reset all or one tuning variable to default");
Console()->Register("tunes", "", CFGFLAG_SERVER, 0, 0, "List all tuning variables and their values");
Console()->Register("change_map", "?r[map]", CFGFLAG_SERVER, 0, 0, "Change map");
@ -185,7 +185,7 @@ void CGameClient::OnConsoleInit()
Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, 0, 0, "Shuffle the current teams");
// register tune zone command to allow the client prediction to load tunezones from the map
Console()->Register("tune_zone", "i[zone] s[tuning] i[value]", CFGFLAG_CLIENT | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value");
Console()->Register("tune_zone", "i[zone] s[tuning] f[value]", CFGFLAG_CLIENT | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value");
for(auto &pComponent : m_vpAll)
pComponent->m_pClient = this;

View file

@ -389,7 +389,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event)
return m_WasChanged || m_WasCursorChanged || KeyHandled;
}
STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing)
STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing, const std::vector<STextColorSplit> &vColorSplits)
{
// update derived attributes to handle external changes to the buffer
UpdateStrData();
@ -432,6 +432,7 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al
Cursor.m_LineSpacing = LineSpacing;
Cursor.m_PressMouse.x = m_MouseSelection.m_PressMouse.x;
Cursor.m_ReleaseMouse.x = m_MouseSelection.m_ReleaseMouse.x;
Cursor.m_vColorSplits = vColorSplits;
if(LineWidth < 0.0f)
{
// Using a Y position that's always inside the line input makes it so the selection does not reset when
@ -512,6 +513,7 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = LineWidth;
Cursor.m_LineSpacing = LineSpacing;
Cursor.m_vColorSplits = vColorSplits;
TextRender()->TextEx(&Cursor, pDisplayStr);
}

View file

@ -187,7 +187,7 @@ public:
return Changed;
}
STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing);
STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth, float LineSpacing, const std::vector<STextColorSplit> &vColorSplits = {});
SMouseSelection *GetMouseSelection() { return &m_MouseSelection; }
const void *GetClearButtonId() const { return &m_ClearButtonId; }

View file

@ -674,6 +674,7 @@ void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, Size, TEXTFLAG_RENDER | Flags);
Cursor.m_vColorSplits = LabelProps.m_vColorSplits;
Cursor.m_LineWidth = (float)LabelProps.m_MaxWidth;
TextRender()->TextEx(&Cursor, pText, -1);
}
@ -761,7 +762,7 @@ void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRe
}
}
bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners)
bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const std::vector<STextColorSplit> &vColorSplits)
{
const bool Inside = MouseHovered(pRect);
const bool Active = m_pLastActiveItem == pLineInput;
@ -843,7 +844,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f);
ClipEnable(pRect);
Textbox.x -= ScrollOffset;
const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed || CursorChanged, -1.0f, 0.0f);
const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed || CursorChanged, -1.0f, 0.0f, vColorSplits);
ClipDisable();
// Scroll left or right if necessary
@ -864,12 +865,12 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
return Changed;
}
bool CUI::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners)
bool CUI::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const std::vector<STextColorSplit> &vColorSplits)
{
CUIRect EditBox, ClearButton;
pRect->VSplitRight(pRect->h, &EditBox, &ClearButton);
bool ReturnValue = DoEditBox(pLineInput, &EditBox, FontSize, Corners & ~IGraphics::CORNER_R);
bool ReturnValue = DoEditBox(pLineInput, &EditBox, FontSize, Corners & ~IGraphics::CORNER_R, vColorSplits);
ClearButton.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f * ButtonColorMul(pLineInput->GetClearButtonId())), Corners & ~IGraphics::CORNER_L, 3.0f);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);

View file

@ -212,6 +212,7 @@ struct SLabelProperties
bool m_StopAtEnd = false;
bool m_EllipsisAtEnd = false;
bool m_EnableWidthCheck = true;
std::vector<STextColorSplit> m_vColorSplits = {};
};
struct SMenuButtonProperties
@ -513,8 +514,8 @@ public:
void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const;
void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr) const;
bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL);
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL);
bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const std::vector<STextColorSplit> &vColorSplits = {});
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const std::vector<STextColorSplit> &vColorSplits = {});
int DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props = {});
// only used for popup menus

View file

@ -41,6 +41,7 @@
#include "editor_actions.h"
#include <chrono>
#include <iterator>
#include <limits>
#include <type_traits>
@ -113,16 +114,16 @@ void CEditor::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, v
OTHER
*********************************************************/
bool CEditor::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip)
bool CEditor::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip, const std::vector<STextColorSplit> &vColorSplits)
{
UpdateTooltip(pLineInput, pRect, pToolTip);
return UI()->DoEditBox(pLineInput, pRect, FontSize, Corners);
return UI()->DoEditBox(pLineInput, pRect, FontSize, Corners, vColorSplits);
}
bool CEditor::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip)
bool CEditor::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip, const std::vector<STextColorSplit> &vColorSplits)
{
UpdateTooltip(pLineInput, pRect, pToolTip);
return UI()->DoClearableEditBox(pLineInput, pRect, FontSize, Corners);
return UI()->DoClearableEditBox(pLineInput, pRect, FontSize, Corners, vColorSplits);
}
ColorRGBA CEditor::GetButtonColor(const void *pID, int Checked)
@ -7515,189 +7516,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
}
}
void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast)
{
// TODO: improve validation (https://github.com/ddnet/ddnet/issues/1406)
// Returns true if the argument is a valid server setting
const auto &&ValidateServerSetting = [](const char *pStr) {
return str_find(pStr, " ") != nullptr;
};
static int s_CommandSelectedIndex = -1;
static CListBox s_ListBox;
s_ListBox.SetActive(m_Dialog == DIALOG_NONE && !UI()->IsPopupOpen());
bool GotSelection = s_ListBox.Active() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size();
const bool CurrentInputValid = ValidateServerSetting(m_SettingsCommandInput.GetString());
CUIRect ToolBar, Button, Label, List, DragBar;
View.HSplitTop(22.0f, &DragBar, nullptr);
DragBar.y -= 2.0f;
DragBar.w += 2.0f;
DragBar.h += 4.0f;
DoEditorDragBar(View, &DragBar, EDragSide::SIDE_TOP, &m_aExtraEditorSplits[EXTRAEDITOR_SERVER_SETTINGS]);
View.HSplitTop(20.0f, &ToolBar, &View);
View.HSplitTop(2.0f, nullptr, &List);
ToolBar.HMargin(2.0f, &ToolBar);
// delete button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
ToolBar.VSplitRight(5.0f, &ToolBar, nullptr);
static int s_DeleteButton = 0;
if(DoButton_FontIcon(&s_DeleteButton, FONT_ICON_TRASH, GotSelection ? 0 : -1, &Button, 0, "[Delete] Delete the selected command from the command list.", IGraphics::CORNER_ALL, 9.0f) == 1 || (GotSelection && CLineInput::GetActiveInput() == nullptr && m_Dialog == DIALOG_NONE && UI()->ConsumeHotkey(CUI::HOTKEY_DELETE)))
{
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::DELETE, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand));
m_Map.m_vSettings.erase(m_Map.m_vSettings.begin() + s_CommandSelectedIndex);
if(s_CommandSelectedIndex >= (int)m_Map.m_vSettings.size())
s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1;
if(s_CommandSelectedIndex >= 0)
m_SettingsCommandInput.Set(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand);
m_Map.OnModify();
s_ListBox.ScrollToSelected();
}
// move down button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
const bool CanMoveDown = GotSelection && s_CommandSelectedIndex < (int)m_Map.m_vSettings.size() - 1;
static int s_DownButton = 0;
if(DoButton_FontIcon(&s_DownButton, FONT_ICON_SORT_DOWN, CanMoveDown ? 0 : -1, &Button, 0, "[Alt+Down] Move the selected command down.", IGraphics::CORNER_R, 11.0f) == 1 || (CanMoveDown && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_DOWN)))
{
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::MOVE_DOWN, &s_CommandSelectedIndex, s_CommandSelectedIndex));
std::swap(m_Map.m_vSettings[s_CommandSelectedIndex], m_Map.m_vSettings[s_CommandSelectedIndex + 1]);
s_CommandSelectedIndex++;
m_Map.OnModify();
s_ListBox.ScrollToSelected();
}
// move up button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
ToolBar.VSplitRight(5.0f, &ToolBar, nullptr);
const bool CanMoveUp = GotSelection && s_CommandSelectedIndex > 0;
static int s_UpButton = 0;
if(DoButton_FontIcon(&s_UpButton, FONT_ICON_SORT_UP, CanMoveUp ? 0 : -1, &Button, 0, "[Alt+Up] Move the selected command up.", IGraphics::CORNER_L, 11.0f) == 1 || (CanMoveUp && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_UP)))
{
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::MOVE_UP, &s_CommandSelectedIndex, s_CommandSelectedIndex));
std::swap(m_Map.m_vSettings[s_CommandSelectedIndex], m_Map.m_vSettings[s_CommandSelectedIndex - 1]);
s_CommandSelectedIndex--;
m_Map.OnModify();
s_ListBox.ScrollToSelected();
}
// redo button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
static int s_RedoButton = 0;
if(DoButton_FontIcon(&s_RedoButton, FONT_ICON_REDO, m_ServerSettingsHistory.CanRedo() ? 0 : -1, &Button, 0, "[Ctrl+Y] Redo command edit", IGraphics::CORNER_R, 11.0f) == 1 || (CanMoveDown && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_DOWN)))
{
m_ServerSettingsHistory.Redo();
}
// undo button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
ToolBar.VSplitRight(5.0f, &ToolBar, nullptr);
static int s_UndoButton = 0;
if(DoButton_FontIcon(&s_UndoButton, FONT_ICON_UNDO, m_ServerSettingsHistory.CanUndo() ? 0 : -1, &Button, 0, "[Ctrl+Z] Undo command edit", IGraphics::CORNER_L, 11.0f) == 1 || (CanMoveUp && Input()->AltIsPressed() && UI()->ConsumeHotkey(CUI::HOTKEY_UP)))
{
m_ServerSettingsHistory.Undo();
}
GotSelection = s_ListBox.Active() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size();
// update button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
const bool CanUpdate = GotSelection && CurrentInputValid && str_comp(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_SettingsCommandInput.GetString()) != 0;
static int s_UpdateButton = 0;
if(DoButton_FontIcon(&s_UpdateButton, FONT_ICON_PENCIL, CanUpdate ? 0 : -1, &Button, 0, "[Alt+Enter] Update the selected command based on the entered value.", IGraphics::CORNER_R, 9.0f) == 1 || (CanUpdate && Input()->AltIsPressed() && m_Dialog == DIALOG_NONE && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
{
bool Found = false;
int i;
for(i = 0; i < (int)m_Map.m_vSettings.size(); ++i)
{
if(i != s_CommandSelectedIndex && !str_comp(m_Map.m_vSettings[i].m_aCommand, m_SettingsCommandInput.GetString()))
{
Found = true;
break;
}
}
if(Found)
{
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::DELETE, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand));
m_Map.m_vSettings.erase(m_Map.m_vSettings.begin() + s_CommandSelectedIndex);
s_CommandSelectedIndex = i > s_CommandSelectedIndex ? i - 1 : i;
}
else
{
const char *pStr = m_SettingsCommandInput.GetString();
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::EDIT, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, pStr));
str_copy(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, pStr);
}
m_Map.OnModify();
s_ListBox.ScrollToSelected();
UI()->SetActiveItem(&m_SettingsCommandInput);
}
// add button
ToolBar.VSplitRight(25.0f, &ToolBar, &Button);
ToolBar.VSplitRight(100.0f, &ToolBar, nullptr);
const bool CanAdd = s_ListBox.Active() && CurrentInputValid;
static int s_AddButton = 0;
if(DoButton_FontIcon(&s_AddButton, FONT_ICON_PLUS, CanAdd ? 0 : -1, &Button, 0, "[Enter] Add a command to the command list.", IGraphics::CORNER_L) == 1 || (CanAdd && !Input()->AltIsPressed() && m_Dialog == DIALOG_NONE && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
{
bool Found = false;
for(size_t i = 0; i < m_Map.m_vSettings.size(); ++i)
{
if(!str_comp(m_Map.m_vSettings[i].m_aCommand, m_SettingsCommandInput.GetString()))
{
s_CommandSelectedIndex = i;
Found = true;
break;
}
}
if(!Found)
{
m_Map.m_vSettings.emplace_back(m_SettingsCommandInput.GetString());
s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1;
m_ServerSettingsHistory.RecordAction(std::make_shared<CEditorCommandAction>(this, CEditorCommandAction::EType::ADD, &s_CommandSelectedIndex, s_CommandSelectedIndex, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand));
m_Map.OnModify();
}
s_ListBox.ScrollToSelected();
UI()->SetActiveItem(&m_SettingsCommandInput);
}
// command input (use remaining toolbar width)
if(!ShowServerSettingsEditorLast) // Just activated
UI()->SetActiveItem(&m_SettingsCommandInput);
m_SettingsCommandInput.SetEmptyText("Command");
DoClearableEditBox(&m_SettingsCommandInput, &ToolBar, 12.0f, IGraphics::CORNER_ALL, "Enter a server setting.");
// command list
s_ListBox.DoStart(15.0f, m_Map.m_vSettings.size(), 1, 3, s_CommandSelectedIndex, &List);
for(size_t i = 0; i < m_Map.m_vSettings.size(); i++)
{
const CListboxItem Item = s_ListBox.DoNextItem(&m_Map.m_vSettings[i], s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex == i);
if(!Item.m_Visible)
continue;
Item.m_Rect.VMargin(5.0f, &Label);
SLabelProperties Props;
Props.m_MaxWidth = Label.w;
Props.m_EllipsisAtEnd = true;
UI()->DoLabel(&Label, m_Map.m_vSettings[i].m_aCommand, 10.0f, TEXTALIGN_ML, Props);
}
const int NewSelected = s_ListBox.DoEnd();
if(s_CommandSelectedIndex != NewSelected)
{
s_CommandSelectedIndex = NewSelected;
m_SettingsCommandInput.Set(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand);
}
}
void CEditor::RenderEditorHistory(CUIRect View)
{
enum EHistoryType
@ -8507,6 +8325,8 @@ void CEditor::Reset(bool CreateDefault)
m_EnvOpTracker.m_pEditor = this;
m_EnvOpTracker.Reset();
m_MapSettingsCommandContext.Reset();
}
int CEditor::GetTextureUsageFlag()
@ -8560,7 +8380,8 @@ void CEditor::Init()
{
m_pInput = Kernel()->RequestInterface<IInput>();
m_pClient = Kernel()->RequestInterface<IClient>();
m_pConfig = Kernel()->RequestInterface<IConfigManager>()->Values();
m_pConfigManager = Kernel()->RequestInterface<IConfigManager>();
m_pConfig = m_pConfigManager->Values();
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pEngine = Kernel()->RequestInterface<IEngine>();
m_pGraphics = Kernel()->RequestInterface<IGraphics>();
@ -8577,6 +8398,7 @@ void CEditor::Init()
m_Map.m_pEditor = this;
m_vComponents.emplace_back(m_MapView);
m_vComponents.emplace_back(m_MapSettingsBackend);
for(CEditorComponent &Component : m_vComponents)
Component.Init(this);

View file

@ -8,6 +8,7 @@
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/mapitems.h>
#include <game/editor/mapitems/envelope.h>
@ -23,6 +24,7 @@
#include <game/editor/mapitems/layer_tiles.h>
#include <game/editor/mapitems/layer_tune.h>
#include <engine/console.h>
#include <engine/editor.h>
#include <engine/engine.h>
#include <engine/graphics.h>
@ -31,7 +33,9 @@
#include "auto_map.h"
#include "editor_history.h"
#include "editor_server_settings.h"
#include "editor_trackers.h"
#include "editor_ui.h"
#include "map_view.h"
#include "smooth_value.h"
@ -43,6 +47,8 @@
#include <vector>
typedef std::function<void(int *pIndex)> FIndexModifyFunction;
template<typename T>
using FDropdownRenderCallback = std::function<void(const T &, char (&aOutput)[128], std::vector<STextColorSplit> &)>;
// CEditor SPECIFIC
enum
@ -267,6 +273,7 @@ class CEditor : public IEditor
{
class IInput *m_pInput = nullptr;
class IClient *m_pClient = nullptr;
class IConfigManager *m_pConfigManager = nullptr;
class CConfig *m_pConfig = nullptr;
class IConsole *m_pConsole = nullptr;
class IEngine *m_pEngine = nullptr;
@ -305,6 +312,7 @@ class CEditor : public IEditor
public:
class IInput *Input() { return m_pInput; }
class IClient *Client() { return m_pClient; }
class IConfigManager *ConfigManager() { return m_pConfigManager; }
class CConfig *Config() { return m_pConfig; }
class IConsole *Console() { return m_pConsole; }
class IEngine *Engine() { return m_pEngine; }
@ -320,7 +328,8 @@ public:
CEditor() :
m_ZoomEnvelopeX(1.0f, 0.1f, 600.0f),
m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f)
m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f),
m_MapSettingsCommandContext(m_MapSettingsBackend.NewContext(&m_SettingsCommandInput))
{
m_EntitiesTexture.Invalidate();
m_FrontTexture.Invalidate();
@ -786,6 +795,8 @@ public:
static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
CLineInputBuffered<256> m_SettingsCommandInput;
CMapSettingsBackend m_MapSettingsBackend;
CMapSettingsBackend::CContext m_MapSettingsCommandContext;
CImageInfo m_TileartImageInfo;
char m_aTileartFilename[IO_MAX_PATH_LENGTH];
@ -811,8 +822,15 @@ public:
int DoButton_DraggableEx(const void *pID, const char *pText, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted, int Flags, const char *pToolTip = nullptr, int Corners = IGraphics::CORNER_ALL, float FontSize = 10.0f);
bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr);
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr);
bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr, const std::vector<STextColorSplit> &vColorSplits = {});
bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr, const std::vector<STextColorSplit> &vColorSplits = {});
void DoMapSettingsEditBox(CMapSettingsBackend::CContext *pContext, const CUIRect *pRect, float FontSize, float DropdownMaxHeight, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr);
template<typename T>
int DoEditBoxDropdown(SEditBoxDropdownContext *pDropdown, CLineInput *pLineInput, const CUIRect *pEditBoxRect, int x, float MaxHeight, bool AutoWidth, const std::vector<T> &vData, const FDropdownRenderCallback<T> &fnMatchCallback);
template<typename T>
int RenderEditBoxDropdown(SEditBoxDropdownContext *pDropdown, CUIRect View, CLineInput *pLineInput, int x, float MaxHeight, bool AutoWidth, const std::vector<T> &vData, const FDropdownRenderCallback<T> &fnMatchCallback);
void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness);
@ -962,7 +980,10 @@ public:
void RenderTooltip(CUIRect TooltipRect);
void RenderEnvelopeEditor(CUIRect View);
void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast);
static void MapSettingsDropdownRenderCallback(const SPossibleValueMatch &Match, char (&aOutput)[128], std::vector<STextColorSplit> &vColorSplits);
void RenderEditorHistory(CUIRect View);
enum class EDragSide // Which side is the drag bar on

View file

@ -1305,6 +1305,7 @@ void CEditorCommandAction::Undo()
}
case EType::EDIT:
{
printf("Restoring %s\n", m_PreviousCommand.c_str());
str_copy(Map.m_vSettings[m_CommandIndex].m_aCommand, m_PreviousCommand.c_str());
*m_pSelectedCommandIndex = m_CommandIndex;
break;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,333 @@
#ifndef GAME_EDITOR_EDITOR_SERVER_SETTINGS_H
#define GAME_EDITOR_EDITOR_SERVER_SETTINGS_H
#include "component.h"
#include "editor_ui.h"
#include <map>
#include <string>
#include <vector>
class CEditor;
struct SMapSettingInt;
struct SMapSettingCommand;
struct IMapSetting;
class CLineInput;
// A parsed map setting argument, storing the name and the type
// Used for validation and to display arguments names
struct SParsedMapSettingArg
{
char m_aName[32];
char m_Type;
};
// An argument for the current setting
struct SCurrentSettingArg
{
char m_aValue[160]; // Value of the argument
float m_X; // The X position
size_t m_Start; // Start offset within the input string
size_t m_End; // End offset within the input string
bool m_Error; // If the argument is wrong or not
char m_ExpectedType; // The expected type
};
struct SPossibleValueMatch
{
const char *m_pValue; // Possible value string
int m_ArgIndex; // Argument for that possible value
const void *m_pData; // Generic pointer to pass specific data
};
struct SCommandParseError
{
char m_aMessage[256];
int m_ArgIndex;
};
// --------------------------------------
// Builder classes & methods to generate list of possible values
// easily for specific settings and arguments.
// It uses a container stored inside CMapSettingsBackend.
// Usage:
// CValuesBuilder Builder(&m_Container);
// // Either do it in one go:
// Builder("tune").Argument(0).Add("value_1").Add("value_2");
// // Or reference the builder (useful when using in a loop):
// auto TuneBuilder = Builder("tune").Argument(0);
// TuneBuilder.Add("value_1");
// TuneBuilder.Add("value_2");
// // ...
using TArgumentValuesList = std::vector<const char *>; // List of possible values
using TSettingValues = std::map<int, TArgumentValuesList>; // Possible values per argument
using TSettingsArgumentValues = std::map<std::string, TSettingValues>; // Possible values per argument, per command/setting name
class CValuesBuilder;
class CSettingValuesBuilder;
class CArgumentValuesListBuilder
{
public:
CArgumentValuesListBuilder &Add(const char *pString)
{
m_pContainer->emplace_back(pString);
return *this;
}
private:
CArgumentValuesListBuilder(std::vector<const char *> *pContainer) :
m_pContainer(pContainer) {}
std::vector<const char *> *m_pContainer;
friend class CSettingValuesBuilder;
};
class CSettingValuesBuilder
{
public:
CArgumentValuesListBuilder Argument(int Arg) const
{
return CArgumentValuesListBuilder(&(*m_pContainer)[Arg]);
}
private:
CSettingValuesBuilder(TSettingValues *pContainer) :
m_pContainer(pContainer) {}
friend class CValuesBuilder;
TSettingValues *m_pContainer;
};
class CValuesBuilder
{
public:
CValuesBuilder(TSettingsArgumentValues *pContainer) :
m_pContainer(pContainer)
{
}
CSettingValuesBuilder operator()(const char *pSettingName) const
{
return CSettingValuesBuilder(&(*m_pContainer)[pSettingName]);
}
private:
TSettingsArgumentValues *m_pContainer;
};
// --------------------------------------
struct SValueLoader
{
static void LoadTuneValues(const CSettingValuesBuilder &TuneBuilder);
static void LoadTuneZoneValues(const CSettingValuesBuilder &TuneZoneBuilder);
static void LoadMapBugs(const CSettingValuesBuilder &BugBuilder);
static void LoadArgumentTuneValues(CArgumentValuesListBuilder &&ArgBuilder);
};
enum class EValidationResult
{
VALID = 0,
ERROR,
INCOMPLETE,
UNKNOWN,
OUT_OF_RANGE,
};
enum class ECollisionCheckResult
{
ERROR,
REPLACE,
ADD
};
class CMapSettingsBackend : public CEditorComponent
{
typedef void (*FLoaderFunction)(const CSettingValuesBuilder &);
public: // General methods
CMapSettingsBackend() = default;
void Init(CEditor *pEditor) override;
bool OnInput(const IInput::CEvent &Event) override;
void OnUpdate();
public: // Constraints methods
enum class EArgConstraint
{
DEFAULT = 0,
UNIQUE,
MULTIPLE,
};
EArgConstraint ArgConstraint(const char *pSettingName, int Arg) const
{
return m_ArgConstraintsPerCommand.at(pSettingName).at(Arg);
}
public: // Backend methods
const std::vector<SParsedMapSettingArg> &ParsedArgs(const std::shared_ptr<IMapSetting> &pSetting) const
{
return m_ParsedCommandArgs.at(pSetting);
}
public: // CContext
class CContext
{
static const ColorRGBA ms_ArgumentStringColor;
static const ColorRGBA ms_ArgumentNumberColor;
static const ColorRGBA ms_ArgumentUnknownColor;
static const ColorRGBA ms_ErrorColor;
friend class CMapSettingsBackend;
public:
bool CommandIsValid() const { return m_pCurrentSetting != nullptr; }
int CurrentArg() const { return m_CursorArgIndex; }
const char *CurrentArgName() const { return (!m_pCurrentSetting || m_CursorArgIndex < 0 || m_CursorArgIndex >= (int)m_pBackend->m_ParsedCommandArgs.at(m_pCurrentSetting).size()) ? nullptr : m_pBackend->m_ParsedCommandArgs.at(m_pCurrentSetting).at(m_CursorArgIndex).m_aName; }
float CurrentArgPos() const { return m_CursorArgIndex == -1 ? 0 : m_vCurrentArgs[m_CursorArgIndex].m_X; }
size_t CurrentArgOffset() const { return m_CursorArgIndex == -1 ? 0 : m_vCurrentArgs[m_CursorArgIndex].m_Start; }
const char *CurrentArgValue() const { return m_CursorArgIndex == -1 ? m_aCommand : m_vCurrentArgs[m_CursorArgIndex].m_aValue; }
const std::vector<SPossibleValueMatch> &PossibleMatches() const { return m_vPossibleMatches; }
bool HasError() const { return m_Error.m_aMessage[0] != '\0'; }
size_t ErrorOffset() const { return m_Error.m_ArgIndex < 0 ? 0 : m_vCurrentArgs.at(m_Error.m_ArgIndex).m_Start; }
const char *Error() const { return m_Error.m_aMessage; }
int ArgCount() const { return (int)m_vCurrentArgs.size(); }
const SCurrentSettingArg &Arg(int Index) const { return m_vCurrentArgs.at(Index); }
const std::shared_ptr<IMapSetting> &Setting() const { return m_pCurrentSetting; }
CLineInput *LineInput() const { return m_pLineInput; }
int CheckCollision(ECollisionCheckResult &Result) const;
void Update();
bool UpdateCursor(bool Force = false);
void Reset();
void GetCommandHelpText(char *pStr, int Length) const;
bool Valid() const;
void ColorArguments(std::vector<STextColorSplit> &vColorSplits) const;
bool m_AllowUnknownCommands;
SEditBoxDropdownContext m_DropdownContext;
int m_CurrentCompletionIndex;
private: // Methods
CContext(CMapSettingsBackend *pMaster, CLineInput *pLineinput) :
m_DropdownContext(), m_pLineInput(pLineinput), m_pBackend(pMaster)
{
m_AllowUnknownCommands = false;
Reset();
}
void ClearError();
EValidationResult ValidateArg(int Index, const char *pArg);
void UpdatePossibleMatches();
void ParseArgs(const char *pStr);
bool OnInput(const IInput::CEvent &Event);
const char *InputString() const;
void UpdateCompositionString();
private: // Fields
std::shared_ptr<IMapSetting> m_pCurrentSetting; // Current setting, can be nullptr in case of invalid setting name
std::vector<SCurrentSettingArg> m_vCurrentArgs; // Current parsed arguments from lineinput string
int m_CursorArgIndex; // The current argument the cursor is over
std::vector<SPossibleValueMatch> m_vPossibleMatches; // The current matches from cursor argument
size_t m_LastCursorOffset; // Last cursor offset
CLineInput *m_pLineInput;
char m_aCommand[128]; // The current command, not necessarily valid
SCommandParseError m_Error; // Error
CMapSettingsBackend *m_pBackend;
std::string m_CompositionStringBuffer;
};
CContext NewContext(CLineInput *pLineInput)
{
return CContext(this, pLineInput);
}
private: // Loader methods
void LoadAllMapSettings();
void LoadCommand(const char *pName, const char *pArgs, const char *pHelp);
void LoadSettingInt(const std::shared_ptr<SMapSettingInt> &pSetting);
void LoadSettingCommand(const std::shared_ptr<SMapSettingCommand> &pSetting);
void InitValueLoaders();
void LoadPossibleValues(const CSettingValuesBuilder &Builder, const std::shared_ptr<IMapSetting> &pSetting);
void RegisterLoader(const char *pSettingName, const FLoaderFunction &pfnLoader);
void LoadConstraints();
static void PossibleConfigVariableCallback(const struct SConfigVariable *pVariable, void *pUserData);
private: // Argument constraints
using TArgumentConstraints = std::map<int, EArgConstraint>; // Constraint per argument index
using TCommandArgumentConstraints = std::map<std::string, TArgumentConstraints>; // Constraints per command/setting name
// Argument constraints builder
// Used to define arguments constraints for specific commands
// It uses a container stored in CMapSettingsBackend.
// Usage:
// CCommandArgumentConstraintBuilder Command(&m_Container);
// Command("tune", 2).Unique(0); // Defines argument 0 of command "tune" having 2 args as UNIQUE
// Command("tune_zone", 3).Multiple(0).Unique(1);
// // ^ Multiple() currently is only for readable purposes. It can be omited:
// // Command("tune_zone", 3).Unique(1);
//
class CCommandArgumentConstraintBuilder;
class CArgumentConstraintsBuilder
{
friend class CCommandArgumentConstraintBuilder;
private:
CArgumentConstraintsBuilder(TArgumentConstraints *pContainer) :
m_pContainer(pContainer){};
TArgumentConstraints *m_pContainer;
public:
CArgumentConstraintsBuilder &Multiple(int Arg)
{
// Define a multiple argument constraint
(*m_pContainer)[Arg] = EArgConstraint::MULTIPLE;
return *this;
}
CArgumentConstraintsBuilder &Unique(int Arg)
{
// Define a unique argument constraint
(*m_pContainer)[Arg] = EArgConstraint::UNIQUE;
return *this;
}
};
class CCommandArgumentConstraintBuilder
{
public:
CCommandArgumentConstraintBuilder(TCommandArgumentConstraints *pContainer) :
m_pContainer(pContainer) {}
CArgumentConstraintsBuilder operator()(const char *pSettingName, int ArgCount)
{
for(int i = 0; i < ArgCount; i++)
(*m_pContainer)[pSettingName][i] = EArgConstraint::DEFAULT;
return CArgumentConstraintsBuilder(&(*m_pContainer)[pSettingName]);
}
private:
TCommandArgumentConstraints *m_pContainer;
};
TCommandArgumentConstraints m_ArgConstraintsPerCommand;
private: // Backend fields
std::vector<std::shared_ptr<IMapSetting>> m_vpMapSettings;
std::map<std::shared_ptr<IMapSetting>, std::vector<SParsedMapSettingArg>> m_ParsedCommandArgs; // Parsed available settings arguments, used for validation
TSettingsArgumentValues m_PossibleValuesPerCommand;
std::map<std::string, FLoaderFunction> m_LoaderFunctions;
static CContext *ms_pActiveContext;
friend class CEditor;
};
#endif

View file

@ -0,0 +1,17 @@
#ifndef GAME_EDITOR_EDITOR_UI_H
#define GAME_EDITOR_EDITOR_UI_H
#include <game/client/ui_listbox.h>
struct SEditBoxDropdownContext
{
bool m_Visible = false;
int m_Selected = -1;
CListBox m_ListBox;
bool m_ShortcutUsed = false;
bool m_DidBecomeVisible = false;
bool m_MousePressedInside = false;
bool m_ShouldHide = false;
};
#endif