mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-17 21:48:19 +00:00
Better map settings input (autocomplete, validation)
This commit is contained in:
parent
a2c8869025
commit
9cc8a28305
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
1555
src/game/editor/editor_server_settings.cpp
Normal file
1555
src/game/editor/editor_server_settings.cpp
Normal file
File diff suppressed because it is too large
Load diff
333
src/game/editor/editor_server_settings.h
Normal file
333
src/game/editor/editor_server_settings.h
Normal 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
|
17
src/game/editor/editor_ui.h
Normal file
17
src/game/editor/editor_ui.h
Normal 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
|
Loading…
Reference in a new issue