mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-19 22:48:18 +00:00
Added dialog to fix invalid map settings on load
This commit is contained in:
parent
9cc8a28305
commit
aca398f3c9
|
@ -11,6 +11,280 @@
|
||||||
|
|
||||||
CConfig g_Config;
|
CConfig g_Config;
|
||||||
|
|
||||||
|
// ----------------------- Config Variables
|
||||||
|
|
||||||
|
static void EscapeParam(char *pDst, const char *pSrc, int Size)
|
||||||
|
{
|
||||||
|
str_escape(&pDst, pSrc, pDst + Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SConfigVariable::ExecuteLine(const char *pLine) const
|
||||||
|
{
|
||||||
|
m_pConsole->ExecuteLine(pLine, (m_Flags & CFGFLAG_GAME) != 0 ? IConsole::CLIENT_ID_GAME : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SConfigVariable::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----
|
||||||
|
|
||||||
|
void SIntConfigVariable::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 SIntConfigVariable::Register()
|
||||||
|
{
|
||||||
|
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SIntConfigVariable::IsDefault() const
|
||||||
|
{
|
||||||
|
return *m_pVariable == m_Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIntConfigVariable::Serialize(char *pOut, size_t Size, int Value) const
|
||||||
|
{
|
||||||
|
str_format(pOut, Size, "%s %i", m_pScriptName, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIntConfigVariable::Serialize(char *pOut, size_t Size) const
|
||||||
|
{
|
||||||
|
Serialize(pOut, Size, *m_pVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIntConfigVariable::SetValue(int Value)
|
||||||
|
{
|
||||||
|
if(CheckReadOnly())
|
||||||
|
return;
|
||||||
|
char aBuf[IConsole::CMDLINE_LENGTH];
|
||||||
|
Serialize(aBuf, sizeof(aBuf), Value);
|
||||||
|
ExecuteLine(aBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIntConfigVariable::ResetToDefault()
|
||||||
|
{
|
||||||
|
SetValue(m_Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIntConfigVariable::ResetToOld()
|
||||||
|
{
|
||||||
|
*m_pVariable = m_OldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----
|
||||||
|
|
||||||
|
void SColorConfigVariable::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 SColorConfigVariable::Register()
|
||||||
|
{
|
||||||
|
m_pConsole->Register(m_pScriptName, "?i", m_Flags, CommandCallback, this, m_pHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SColorConfigVariable::IsDefault() const
|
||||||
|
{
|
||||||
|
return *m_pVariable == m_Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SColorConfigVariable::Serialize(char *pOut, size_t Size, unsigned Value) const
|
||||||
|
{
|
||||||
|
str_format(pOut, Size, "%s %u", m_pScriptName, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SColorConfigVariable::Serialize(char *pOut, size_t Size) const
|
||||||
|
{
|
||||||
|
Serialize(pOut, Size, *m_pVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SColorConfigVariable::SetValue(unsigned Value)
|
||||||
|
{
|
||||||
|
if(CheckReadOnly())
|
||||||
|
return;
|
||||||
|
char aBuf[IConsole::CMDLINE_LENGTH];
|
||||||
|
Serialize(aBuf, sizeof(aBuf), Value);
|
||||||
|
ExecuteLine(aBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SColorConfigVariable::ResetToDefault()
|
||||||
|
{
|
||||||
|
SetValue(m_Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SColorConfigVariable::ResetToOld()
|
||||||
|
{
|
||||||
|
*m_pVariable = m_OldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----
|
||||||
|
|
||||||
|
SStringConfigVariable::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SStringConfigVariable::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 SStringConfigVariable::Register()
|
||||||
|
{
|
||||||
|
m_pConsole->Register(m_pScriptName, "?r", m_Flags, CommandCallback, this, m_pHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SStringConfigVariable::IsDefault() const
|
||||||
|
{
|
||||||
|
return str_comp(m_pStr, m_pDefault) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SStringConfigVariable::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 SStringConfigVariable::Serialize(char *pOut, size_t Size) const
|
||||||
|
{
|
||||||
|
Serialize(pOut, Size, m_pStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SStringConfigVariable::SetValue(const char *pValue)
|
||||||
|
{
|
||||||
|
if(CheckReadOnly())
|
||||||
|
return;
|
||||||
|
char aBuf[2048];
|
||||||
|
Serialize(aBuf, sizeof(aBuf), pValue);
|
||||||
|
ExecuteLine(aBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SStringConfigVariable::ResetToDefault()
|
||||||
|
{
|
||||||
|
SetValue(m_pDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SStringConfigVariable::ResetToOld()
|
||||||
|
{
|
||||||
|
str_copy(m_pStr, m_pOldValue, m_MaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------- Config Manager
|
||||||
CConfigManager::CConfigManager()
|
CConfigManager::CConfigManager()
|
||||||
{
|
{
|
||||||
m_pConsole = nullptr;
|
m_pConsole = nullptr;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#define ENGINE_SHARED_CONFIG_H
|
#define ENGINE_SHARED_CONFIG_H
|
||||||
|
|
||||||
#include <base/detect.h>
|
#include <base/detect.h>
|
||||||
#include <base/system.h>
|
|
||||||
|
|
||||||
#include <engine/config.h>
|
#include <engine/config.h>
|
||||||
#include <engine/console.h>
|
#include <engine/console.h>
|
||||||
|
@ -59,11 +58,6 @@ enum
|
||||||
CFGFLAG_INSENSITIVE = 1 << 12,
|
CFGFLAG_INSENSITIVE = 1 << 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void EscapeParam(char *pDst, const char *pSrc, int Size)
|
|
||||||
{
|
|
||||||
str_escape(&pDst, pSrc, pDst + Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SConfigVariable
|
struct SConfigVariable
|
||||||
{
|
{
|
||||||
enum EVariableType
|
enum EVariableType
|
||||||
|
@ -99,20 +93,8 @@ struct SConfigVariable
|
||||||
virtual void ResetToOld() = 0;
|
virtual void ResetToOld() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ExecuteLine(const char *pLine) const
|
void ExecuteLine(const char *pLine) const;
|
||||||
{
|
bool CheckReadOnly() 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
|
struct SIntConfigVariable : public SConfigVariable
|
||||||
|
@ -136,76 +118,14 @@ struct SIntConfigVariable : public SConfigVariable
|
||||||
|
|
||||||
~SIntConfigVariable() override = default;
|
~SIntConfigVariable() override = default;
|
||||||
|
|
||||||
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
|
static void CommandCallback(IConsole::IResult *pResult, void *pUserData);
|
||||||
{
|
void Register() override;
|
||||||
SIntConfigVariable *pData = static_cast<SIntConfigVariable *>(pUserData);
|
bool IsDefault() const override;
|
||||||
|
void Serialize(char *pOut, size_t Size, int Value) const;
|
||||||
if(pResult->NumArguments())
|
void Serialize(char *pOut, size_t Size) const override;
|
||||||
{
|
void SetValue(int Value);
|
||||||
if(pData->CheckReadOnly())
|
void ResetToDefault() override;
|
||||||
return;
|
void ResetToOld() override;
|
||||||
|
|
||||||
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
|
struct SColorConfigVariable : public SConfigVariable
|
||||||
|
@ -229,84 +149,14 @@ struct SColorConfigVariable : public SConfigVariable
|
||||||
|
|
||||||
~SColorConfigVariable() override = default;
|
~SColorConfigVariable() override = default;
|
||||||
|
|
||||||
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
|
static void CommandCallback(IConsole::IResult *pResult, void *pUserData);
|
||||||
{
|
void Register() override;
|
||||||
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
|
bool IsDefault() const override;
|
||||||
|
void Serialize(char *pOut, size_t Size, unsigned Value) const;
|
||||||
if(pResult->NumArguments())
|
void Serialize(char *pOut, size_t Size) const override;
|
||||||
{
|
void SetValue(unsigned Value);
|
||||||
if(pData->CheckReadOnly())
|
void ResetToDefault() override;
|
||||||
return;
|
void ResetToOld() override;
|
||||||
|
|
||||||
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
|
struct SStringConfigVariable : public SConfigVariable
|
||||||
|
@ -316,102 +166,17 @@ struct SStringConfigVariable : public SConfigVariable
|
||||||
size_t m_MaxSize;
|
size_t m_MaxSize;
|
||||||
char *m_pOldValue;
|
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) :
|
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;
|
~SStringConfigVariable() override = default;
|
||||||
|
|
||||||
static void CommandCallback(IConsole::IResult *pResult, void *pUserData)
|
static void CommandCallback(IConsole::IResult *pResult, void *pUserData);
|
||||||
{
|
void Register() override;
|
||||||
SStringConfigVariable *pData = static_cast<SStringConfigVariable *>(pUserData);
|
bool IsDefault() const override;
|
||||||
|
void Serialize(char *pOut, size_t Size, const char *pValue) const;
|
||||||
if(pResult->NumArguments())
|
void Serialize(char *pOut, size_t Size) const override;
|
||||||
{
|
void SetValue(const char *pValue);
|
||||||
if(pData->CheckReadOnly())
|
void ResetToDefault() override;
|
||||||
return;
|
void ResetToOld() override;
|
||||||
|
|
||||||
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
|
class CConfigManager : public IConfigManager
|
||||||
|
|
|
@ -147,7 +147,7 @@ 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_REDO = "\xEF\x8B\xB9";
|
||||||
|
|
||||||
MAYBE_UNUSED static const char *FONT_ICON_ARROWS_ROTATE = "\xEF\x80\xA1";
|
MAYBE_UNUSED static const char *FONT_ICON_ARROWS_ROTATE = "\xEF\x80\xA1";
|
||||||
MAYBE_UNUSED static const char *FONT_ICON_QUESTION = "\x3F";
|
MAYBE_UNUSED static const char *FONT_ICON_QUESTION = "?";
|
||||||
} // end namespace FontIcons
|
} // end namespace FontIcons
|
||||||
|
|
||||||
enum ETextCursorSelectionMode
|
enum ETextCursorSelectionMode
|
||||||
|
|
|
@ -779,6 +779,7 @@ bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUse
|
||||||
if(pEditor->Load(pFileName, StorageType))
|
if(pEditor->Load(pFileName, StorageType))
|
||||||
{
|
{
|
||||||
pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder || (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentLink && str_comp(pEditor->m_aFileDialogCurrentLink, "themes") == 0));
|
pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder || (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentLink && str_comp(pEditor->m_aFileDialogCurrentLink, "themes") == 0));
|
||||||
|
if(pEditor->m_Dialog == DIALOG_FILE)
|
||||||
pEditor->m_Dialog = DIALOG_NONE;
|
pEditor->m_Dialog = DIALOG_NONE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -8038,6 +8039,12 @@ void CEditor::Render()
|
||||||
UI()->SetHotItem(&s_NullUiTarget);
|
UI()->SetHotItem(&s_NullUiTarget);
|
||||||
RenderFileDialog();
|
RenderFileDialog();
|
||||||
}
|
}
|
||||||
|
else if(m_Dialog == DIALOG_MAPSETTINGS_ERROR)
|
||||||
|
{
|
||||||
|
static int s_NullUiTarget = 0;
|
||||||
|
UI()->SetHotItem(&s_NullUiTarget);
|
||||||
|
RenderMapSettingsErrorDialog();
|
||||||
|
}
|
||||||
|
|
||||||
if(m_PopupEventActivated)
|
if(m_PopupEventActivated)
|
||||||
{
|
{
|
||||||
|
|
|
@ -60,6 +60,7 @@ enum
|
||||||
|
|
||||||
DIALOG_NONE = 0,
|
DIALOG_NONE = 0,
|
||||||
DIALOG_FILE,
|
DIALOG_FILE,
|
||||||
|
DIALOG_MAPSETTINGS_ERROR
|
||||||
};
|
};
|
||||||
|
|
||||||
class CEditorImage;
|
class CEditorImage;
|
||||||
|
@ -122,16 +123,7 @@ public:
|
||||||
CMapInfo m_MapInfo;
|
CMapInfo m_MapInfo;
|
||||||
CMapInfo m_MapInfoTmp;
|
CMapInfo m_MapInfoTmp;
|
||||||
|
|
||||||
struct CSetting
|
std::vector<CEditorMapSetting> m_vSettings;
|
||||||
{
|
|
||||||
char m_aCommand[256];
|
|
||||||
|
|
||||||
CSetting(const char *pCommand)
|
|
||||||
{
|
|
||||||
str_copy(m_aCommand, pCommand);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
std::vector<CSetting> m_vSettings;
|
|
||||||
|
|
||||||
std::shared_ptr<class CLayerGame> m_pGameLayer;
|
std::shared_ptr<class CLayerGame> m_pGameLayer;
|
||||||
std::shared_ptr<CLayerGroup> m_pGameGroup;
|
std::shared_ptr<CLayerGroup> m_pGameGroup;
|
||||||
|
@ -981,6 +973,7 @@ public:
|
||||||
|
|
||||||
void RenderEnvelopeEditor(CUIRect View);
|
void RenderEnvelopeEditor(CUIRect View);
|
||||||
|
|
||||||
|
void RenderMapSettingsErrorDialog();
|
||||||
void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast);
|
void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast);
|
||||||
static void MapSettingsDropdownRenderCallback(const SPossibleValueMatch &Match, char (&aOutput)[128], std::vector<STextColorSplit> &vColorSplits);
|
static void MapSettingsDropdownRenderCallback(const SPossibleValueMatch &Match, char (&aOutput)[128], std::vector<STextColorSplit> &vColorSplits);
|
||||||
|
|
||||||
|
|
|
@ -1305,7 +1305,6 @@ void CEditorCommandAction::Undo()
|
||||||
}
|
}
|
||||||
case EType::EDIT:
|
case EType::EDIT:
|
||||||
{
|
{
|
||||||
printf("Restoring %s\n", m_PreviousCommand.c_str());
|
|
||||||
str_copy(Map.m_vSettings[m_CommandIndex].m_aCommand, m_PreviousCommand.c_str());
|
str_copy(Map.m_vSettings[m_CommandIndex].m_aCommand, m_PreviousCommand.c_str());
|
||||||
*m_pSelectedCommandIndex = m_CommandIndex;
|
*m_pSelectedCommandIndex = m_CommandIndex;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "editor_server_settings.h"
|
#include "editor_server_settings.h"
|
||||||
#include "editor.h"
|
#include "editor.h"
|
||||||
|
|
||||||
#include <engine/keys.h>
|
#include <engine/keys.h>
|
||||||
|
@ -268,7 +268,7 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd
|
||||||
if(s_CommandSelectedIndex != NewSelected || s_ListBox.WasItemSelected())
|
if(s_CommandSelectedIndex != NewSelected || s_ListBox.WasItemSelected())
|
||||||
{
|
{
|
||||||
s_CommandSelectedIndex = NewSelected;
|
s_CommandSelectedIndex = NewSelected;
|
||||||
if(m_SettingsCommandInput.IsEmpty() || Input()->ModifierIsPressed()) // Allow ctrl+click to fill the input even if empty
|
if(m_SettingsCommandInput.IsEmpty() || !Input()->ModifierIsPressed()) // Allow ctrl+click to only change selection
|
||||||
{
|
{
|
||||||
m_SettingsCommandInput.Set(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand);
|
m_SettingsCommandInput.Set(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand);
|
||||||
m_MapSettingsCommandContext.Update();
|
m_MapSettingsCommandContext.Update();
|
||||||
|
@ -288,6 +288,7 @@ void CEditor::DoMapSettingsEditBox(CMapSettingsBackend::CContext *pContext, cons
|
||||||
|
|
||||||
auto *pLineInput = pContext->LineInput();
|
auto *pLineInput = pContext->LineInput();
|
||||||
auto &Context = *pContext;
|
auto &Context = *pContext;
|
||||||
|
Context.SetFontSize(FontSize);
|
||||||
|
|
||||||
// Set current active context if input is active
|
// Set current active context if input is active
|
||||||
if(pLineInput->IsActive())
|
if(pLineInput->IsActive())
|
||||||
|
@ -540,6 +541,464 @@ int CEditor::RenderEditBoxDropdown(SEditBoxDropdownContext *pDropdown, CUIRect V
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CEditor::RenderMapSettingsErrorDialog()
|
||||||
|
{
|
||||||
|
auto &LoadedMapSettings = m_MapSettingsBackend.m_LoadedMapSettings;
|
||||||
|
auto &vSettingsInvalid = LoadedMapSettings.m_vSettingsInvalid;
|
||||||
|
auto &vSettingsValid = LoadedMapSettings.m_vSettingsValid;
|
||||||
|
auto &SettingsDuplicate = LoadedMapSettings.m_SettingsDuplicate;
|
||||||
|
|
||||||
|
UI()->MapScreen();
|
||||||
|
CUIRect Overlay = *UI()->Screen();
|
||||||
|
|
||||||
|
Overlay.Draw(ColorRGBA(0, 0, 0, 0.33f), IGraphics::CORNER_NONE, 0.0f);
|
||||||
|
CUIRect Background;
|
||||||
|
Overlay.VMargin(150.0f, &Background);
|
||||||
|
Background.HMargin(50.0f, &Background);
|
||||||
|
Background.Draw(ColorRGBA(0, 0, 0, 0.80f), IGraphics::CORNER_ALL, 5.0f);
|
||||||
|
|
||||||
|
CUIRect View;
|
||||||
|
Background.Margin(10.0f, &View);
|
||||||
|
|
||||||
|
CUIRect Title, ButtonBar, Label;
|
||||||
|
View.HSplitTop(18.0f, &Title, &View);
|
||||||
|
View.HSplitTop(5.0f, nullptr, &View); // some spacing
|
||||||
|
View.HSplitBottom(18.0f, &View, &ButtonBar);
|
||||||
|
View.HSplitBottom(10.0f, &View, nullptr); // some spacing
|
||||||
|
|
||||||
|
// title bar
|
||||||
|
Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
|
||||||
|
Title.VMargin(10.0f, &Title);
|
||||||
|
UI()->DoLabel(&Title, "Map settings error", 12.0f, TEXTALIGN_ML);
|
||||||
|
|
||||||
|
// Render body
|
||||||
|
{
|
||||||
|
static CLineInputBuffered<256> s_Input;
|
||||||
|
static CMapSettingsBackend::CContext s_Context = m_MapSettingsBackend.NewContext(&s_Input);
|
||||||
|
|
||||||
|
// Some text
|
||||||
|
SLabelProperties Props;
|
||||||
|
CUIRect Text;
|
||||||
|
View.HSplitTop(30.0f, &Text, &View);
|
||||||
|
Props.m_MaxWidth = Text.w;
|
||||||
|
UI()->DoLabel(&Text, "Below is a report of the invalid map settings found when loading the map. Please fix them before proceeding further.", 10.0f, TEXTALIGN_MC, Props);
|
||||||
|
|
||||||
|
// Mixed list
|
||||||
|
CUIRect List = View;
|
||||||
|
View.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 3.0f);
|
||||||
|
|
||||||
|
const float RowHeight = 18.0f;
|
||||||
|
static CScrollRegion s_ScrollRegion;
|
||||||
|
vec2 ScrollOffset(0.0f, 0.0f);
|
||||||
|
CScrollRegionParams ScrollParams;
|
||||||
|
ScrollParams.m_ScrollUnit = 120.0f;
|
||||||
|
s_ScrollRegion.Begin(&List, &ScrollOffset, &ScrollParams);
|
||||||
|
const float EndY = List.y + List.h;
|
||||||
|
List.y += ScrollOffset.y;
|
||||||
|
|
||||||
|
List.HSplitTop(20.0f, nullptr, &List);
|
||||||
|
|
||||||
|
static int s_FixingCommandIndex = -1;
|
||||||
|
|
||||||
|
auto &&SetInput = [&](const char *pString) {
|
||||||
|
s_Input.Set(pString);
|
||||||
|
s_Context.Update();
|
||||||
|
s_Context.UpdateCursor(true);
|
||||||
|
UI()->SetActiveItem(&s_Input);
|
||||||
|
};
|
||||||
|
|
||||||
|
CUIRect FixInput;
|
||||||
|
bool DisplayFixInput = false;
|
||||||
|
float DropdownHeight = 110.0f;
|
||||||
|
|
||||||
|
for(int i = 0; i < (int)m_Map.m_vSettings.size(); i++)
|
||||||
|
{
|
||||||
|
CUIRect Slot;
|
||||||
|
|
||||||
|
auto pInvalidSetting = std::find_if(vSettingsInvalid.begin(), vSettingsInvalid.end(), [i](const SInvalidSetting &Setting) { return Setting.m_Index == i; });
|
||||||
|
if(pInvalidSetting != vSettingsInvalid.end())
|
||||||
|
{ // This setting is invalid, only display it if its not a duplicate
|
||||||
|
if(!(pInvalidSetting->m_Type & SInvalidSetting::TYPE_DUPLICATE))
|
||||||
|
{
|
||||||
|
bool IsFixing = s_FixingCommandIndex == i;
|
||||||
|
List.HSplitTop(RowHeight, &Slot, &List);
|
||||||
|
|
||||||
|
// Draw a reddish background if setting is marked as deleted
|
||||||
|
if(pInvalidSetting->m_Context.m_Deleted)
|
||||||
|
Slot.Draw(ColorRGBA(0.85f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_ALL, 3.0f);
|
||||||
|
|
||||||
|
Slot.VMargin(5.0f, &Slot);
|
||||||
|
Slot.HMargin(1.0f, &Slot);
|
||||||
|
|
||||||
|
if(!IsFixing && !pInvalidSetting->m_Context.m_Fixed)
|
||||||
|
{ // Display "Fix" and "delete" buttons if we're not fixing the command and the command has not been fixed
|
||||||
|
CUIRect FixBtn, DelBtn;
|
||||||
|
Slot.VSplitRight(30.0f, &Slot, &DelBtn);
|
||||||
|
Slot.VSplitRight(5.0f, &Slot, nullptr);
|
||||||
|
DelBtn.HMargin(1.0f, &DelBtn);
|
||||||
|
|
||||||
|
Slot.VSplitRight(30.0f, &Slot, &FixBtn);
|
||||||
|
Slot.VSplitRight(10.0f, &Slot, nullptr);
|
||||||
|
FixBtn.HMargin(1.0f, &FixBtn);
|
||||||
|
|
||||||
|
// Delete button
|
||||||
|
if(DoButton_FontIcon(&pInvalidSetting->m_Context.m_Deleted, FONT_ICON_TRASH, pInvalidSetting->m_Context.m_Deleted, &DelBtn, 0, "Delete this command", IGraphics::CORNER_ALL, 10.0f))
|
||||||
|
pInvalidSetting->m_Context.m_Deleted = !pInvalidSetting->m_Context.m_Deleted;
|
||||||
|
|
||||||
|
// Fix button
|
||||||
|
if(DoButton_Editor(&pInvalidSetting->m_Context.m_Fixed, "Fix", !pInvalidSetting->m_Context.m_Deleted ? (s_FixingCommandIndex == -1 ? 0 : (IsFixing ? 1 : -1)) : -1, &FixBtn, 0, "Fix this command"))
|
||||||
|
{
|
||||||
|
s_FixingCommandIndex = i;
|
||||||
|
SetInput(pInvalidSetting->m_aSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(IsFixing)
|
||||||
|
{ // If we're fixing this command, then display "Done" and "Cancel" buttons
|
||||||
|
// Also setup the input rect
|
||||||
|
CUIRect OkBtn, CancelBtn;
|
||||||
|
Slot.VSplitRight(50.0f, &Slot, &CancelBtn);
|
||||||
|
Slot.VSplitRight(5.0f, &Slot, nullptr);
|
||||||
|
CancelBtn.HMargin(1.0f, &CancelBtn);
|
||||||
|
|
||||||
|
Slot.VSplitRight(30.0f, &Slot, &OkBtn);
|
||||||
|
Slot.VSplitRight(10.0f, &Slot, nullptr);
|
||||||
|
OkBtn.HMargin(1.0f, &OkBtn);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
static int s_Cancel = 0, s_Ok = 0;
|
||||||
|
if(DoButton_Editor(&s_Cancel, "Cancel", 0, &CancelBtn, 0, "Cancel fixing this command") || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||||||
|
{
|
||||||
|
s_FixingCommandIndex = -1;
|
||||||
|
s_Input.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Done" button only enabled if the fixed setting is valid
|
||||||
|
// For that we use a local CContext s_Context and use it to check
|
||||||
|
// that the setting is valid and that it is not a duplicate
|
||||||
|
ECollisionCheckResult Res = ECollisionCheckResult::ERROR;
|
||||||
|
s_Context.CheckCollision(vSettingsValid, Res);
|
||||||
|
bool Valid = s_Context.Valid() && Res == ECollisionCheckResult::ADD;
|
||||||
|
|
||||||
|
if(DoButton_Editor(&s_Ok, "Done", Valid ? 0 : -1, &OkBtn, 0, "Confirm edition of this command") || (s_Input.IsActive() && Valid && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
|
||||||
|
{
|
||||||
|
if(Valid) // Just to make sure
|
||||||
|
{
|
||||||
|
// Mark the setting is being fixed
|
||||||
|
pInvalidSetting->m_Context.m_Fixed = true;
|
||||||
|
str_copy(pInvalidSetting->m_aSetting, s_Input.GetString());
|
||||||
|
// Add it to the list for future collision checks
|
||||||
|
vSettingsValid.emplace_back(s_Input.GetString());
|
||||||
|
|
||||||
|
// Clear the input & fixing command index
|
||||||
|
s_FixingCommandIndex = -1;
|
||||||
|
s_Input.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label = Slot;
|
||||||
|
Props.m_EllipsisAtEnd = true;
|
||||||
|
Props.m_MaxWidth = Label.w;
|
||||||
|
|
||||||
|
if(IsFixing)
|
||||||
|
{
|
||||||
|
// Setup input rect, which will be used to draw the map settings input later
|
||||||
|
Label.HMargin(1.0, &FixInput);
|
||||||
|
DisplayFixInput = true;
|
||||||
|
DropdownHeight = minimum(DropdownHeight, EndY - FixInput.y - 16.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Draw label in case we're not fixing this setting.
|
||||||
|
// Deleted settings are shown in gray with a red line through them
|
||||||
|
// Fixed settings are shown in green
|
||||||
|
// Invalid settings are shown in red
|
||||||
|
if(!pInvalidSetting->m_Context.m_Deleted)
|
||||||
|
{
|
||||||
|
if(pInvalidSetting->m_Context.m_Fixed)
|
||||||
|
TextRender()->TextColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
|
else
|
||||||
|
TextRender()->TextColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
UI()->DoLabel(&Label, pInvalidSetting->m_aSetting, 10.0f, TEXTALIGN_ML, Props);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TextRender()->TextColor(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
UI()->DoLabel(&Label, pInvalidSetting->m_aSetting, 10.0f, TEXTALIGN_ML, Props);
|
||||||
|
|
||||||
|
CUIRect Line = Label;
|
||||||
|
Line.y = Label.y + Label.h / 2;
|
||||||
|
Line.h = 1;
|
||||||
|
Line.Draw(ColorRGBA(1, 0, 0, 1), IGraphics::CORNER_NONE, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // This setting is valid
|
||||||
|
// Check for duplicates
|
||||||
|
const std::vector<int> &vDuplicates = SettingsDuplicate.at(i);
|
||||||
|
int Chosen = -1; // This is the chosen duplicate setting. -1 means the first valid setting that was found which was not a duplicate
|
||||||
|
for(int d = 0; d < (int)vDuplicates.size(); d++)
|
||||||
|
{
|
||||||
|
int DupIndex = vDuplicates[d];
|
||||||
|
if(vSettingsInvalid[DupIndex].m_Context.m_Chosen)
|
||||||
|
{
|
||||||
|
Chosen = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List.HSplitTop(RowHeight * (vDuplicates.size() + 1) + 2.0f, &Slot, &List);
|
||||||
|
Slot.HMargin(1.0f, &Slot);
|
||||||
|
|
||||||
|
// Draw a background to highlight group of duplicates
|
||||||
|
if(!vDuplicates.empty())
|
||||||
|
Slot.Draw(ColorRGBA(1, 1, 1, 0.15f), IGraphics::CORNER_ALL, 3.0f);
|
||||||
|
|
||||||
|
Slot.VMargin(5.0f, &Slot);
|
||||||
|
Slot.HSplitTop(RowHeight, &Label, &Slot);
|
||||||
|
Label.HMargin(1.0f, &Label);
|
||||||
|
|
||||||
|
// Draw a "choose" button next to the label in case we have duplicates for this line
|
||||||
|
if(!vDuplicates.empty())
|
||||||
|
{
|
||||||
|
CUIRect ChooseBtn;
|
||||||
|
Label.VSplitRight(50.0f, &Label, &ChooseBtn);
|
||||||
|
Label.VSplitRight(5.0f, &Label, nullptr);
|
||||||
|
ChooseBtn.HMargin(1.0f, &ChooseBtn);
|
||||||
|
if(DoButton_Editor(&vDuplicates, "Choose", Chosen == -1, &ChooseBtn, 0, "Choose this command"))
|
||||||
|
{
|
||||||
|
if(Chosen != -1)
|
||||||
|
vSettingsInvalid[vDuplicates[Chosen]].m_Context.m_Chosen = false;
|
||||||
|
Chosen = -1; // Choosing this means that we do not choose any of the duplicates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the label
|
||||||
|
Props.m_MaxWidth = Label.w;
|
||||||
|
UI()->DoLabel(&Label, m_Map.m_vSettings[i].m_aCommand, 10.0f, TEXTALIGN_ML, Props);
|
||||||
|
|
||||||
|
// Draw the list of duplicates, with a "Choose" button for each duplicate
|
||||||
|
// In case a duplicate is also invalid, then we draw a "Fix" button which behaves like the fix button above
|
||||||
|
// Duplicate settings name are shown in light blue, or in purple if they are also invalid
|
||||||
|
Slot.VSplitLeft(10.0f, nullptr, &Slot);
|
||||||
|
for(int DuplicateIndex = 0; DuplicateIndex < (int)vDuplicates.size(); DuplicateIndex++)
|
||||||
|
{
|
||||||
|
auto &Duplicate = vSettingsInvalid.at(vDuplicates[DuplicateIndex]);
|
||||||
|
bool IsFixing = s_FixingCommandIndex == Duplicate.m_Index;
|
||||||
|
bool IsInvalid = Duplicate.m_Type & SInvalidSetting::TYPE_INVALID;
|
||||||
|
|
||||||
|
ColorRGBA Color(0.329f, 0.714f, 0.859f, 1.0f);
|
||||||
|
CUIRect SubSlot;
|
||||||
|
Slot.HSplitTop(RowHeight, &SubSlot, &Slot);
|
||||||
|
SubSlot.HMargin(1.0f, &SubSlot);
|
||||||
|
|
||||||
|
if(!IsFixing)
|
||||||
|
{
|
||||||
|
// If not fixing, then display "Choose" and maybe "Fix" buttons.
|
||||||
|
|
||||||
|
CUIRect ChooseBtn;
|
||||||
|
SubSlot.VSplitRight(50.0f, &SubSlot, &ChooseBtn);
|
||||||
|
SubSlot.VSplitRight(5.0f, &SubSlot, nullptr);
|
||||||
|
ChooseBtn.HMargin(1.0f, &ChooseBtn);
|
||||||
|
if(DoButton_Editor(&Duplicate.m_Context.m_Chosen, "Choose", IsInvalid && !Duplicate.m_Context.m_Fixed ? -1 : Duplicate.m_Context.m_Chosen, &ChooseBtn, 0, "Override with this command"))
|
||||||
|
{
|
||||||
|
Duplicate.m_Context.m_Chosen = !Duplicate.m_Context.m_Chosen;
|
||||||
|
if(Chosen != -1 && Chosen != DuplicateIndex)
|
||||||
|
vSettingsInvalid[vDuplicates[Chosen]].m_Context.m_Chosen = false;
|
||||||
|
Chosen = DuplicateIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInvalid)
|
||||||
|
{
|
||||||
|
if(!Duplicate.m_Context.m_Fixed)
|
||||||
|
{
|
||||||
|
Color = ColorRGBA(1, 0, 1, 1);
|
||||||
|
CUIRect FixBtn;
|
||||||
|
SubSlot.VSplitRight(30.0f, &SubSlot, &FixBtn);
|
||||||
|
SubSlot.VSplitRight(10.0f, &SubSlot, nullptr);
|
||||||
|
FixBtn.HMargin(1.0f, &FixBtn);
|
||||||
|
if(DoButton_Editor(&Duplicate.m_Context.m_Fixed, "Fix", s_FixingCommandIndex == -1 ? 0 : (IsFixing ? 1 : -1), &FixBtn, 0, "Fix this command (needed before it can be chosen)"))
|
||||||
|
{
|
||||||
|
s_FixingCommandIndex = Duplicate.m_Index;
|
||||||
|
SetInput(Duplicate.m_aSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Color = ColorRGBA(0.329f, 0.714f, 0.859f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we're fixing, display "Done" and "Cancel" buttons
|
||||||
|
CUIRect OkBtn, CancelBtn;
|
||||||
|
SubSlot.VSplitRight(50.0f, &SubSlot, &CancelBtn);
|
||||||
|
SubSlot.VSplitRight(5.0f, &SubSlot, nullptr);
|
||||||
|
CancelBtn.HMargin(1.0f, &CancelBtn);
|
||||||
|
|
||||||
|
SubSlot.VSplitRight(30.0f, &SubSlot, &OkBtn);
|
||||||
|
SubSlot.VSplitRight(10.0f, &SubSlot, nullptr);
|
||||||
|
OkBtn.HMargin(1.0f, &OkBtn);
|
||||||
|
|
||||||
|
static int s_Cancel = 0, s_Ok = 0;
|
||||||
|
if(DoButton_Editor(&s_Cancel, "Cancel", 0, &CancelBtn, 0, "Cancel fixing this command") || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||||||
|
{
|
||||||
|
s_FixingCommandIndex = -1;
|
||||||
|
s_Input.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the local CContext s_Context to validate the input
|
||||||
|
// We also need to make sure the fixed setting matches the initial duplicate setting
|
||||||
|
// For example:
|
||||||
|
// sv_deepfly 0
|
||||||
|
// sv_deepfly 5 <- This is invalid and duplicate. We can only fix it by writing "sv_deepfly 0" or "sv_deepfly 1".
|
||||||
|
// If we write any other setting, like "sv_hit 1", it won't work as it does not match "sv_deepfly".
|
||||||
|
// To do that, we use the context and we check for collision with the current map setting
|
||||||
|
ECollisionCheckResult Res = ECollisionCheckResult::ERROR;
|
||||||
|
s_Context.CheckCollision({m_Map.m_vSettings[i]}, Res);
|
||||||
|
bool Valid = s_Context.Valid() && Res == ECollisionCheckResult::REPLACE;
|
||||||
|
|
||||||
|
if(DoButton_Editor(&s_Ok, "Done", Valid ? 0 : -1, &OkBtn, 0, "Confirm edition of this command") || (s_Input.IsActive() && Valid && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
|
||||||
|
{
|
||||||
|
if(Valid) // Just to make sure
|
||||||
|
{
|
||||||
|
// Mark the setting as fixed
|
||||||
|
Duplicate.m_Context.m_Fixed = true;
|
||||||
|
str_copy(Duplicate.m_aSetting, s_Input.GetString());
|
||||||
|
|
||||||
|
s_FixingCommandIndex = -1;
|
||||||
|
s_Input.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label = SubSlot;
|
||||||
|
Props.m_MaxWidth = Label.w;
|
||||||
|
|
||||||
|
if(IsFixing)
|
||||||
|
{
|
||||||
|
// Setup input rect in case we are fixing the setting
|
||||||
|
Label.HMargin(1.0, &FixInput);
|
||||||
|
DisplayFixInput = true;
|
||||||
|
DropdownHeight = minimum(DropdownHeight, EndY - FixInput.y - 16.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Otherwise, render the setting label
|
||||||
|
TextRender()->TextColor(Color);
|
||||||
|
UI()->DoLabel(&Label, Duplicate.m_aSetting, 10.0f, TEXTALIGN_ML, Props);
|
||||||
|
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, add the slot to the scroll region
|
||||||
|
s_ScrollRegion.AddRect(Slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some padding to the bottom so the dropdown can actually display some values in case we
|
||||||
|
// fix an invalid setting at the bottom of the list
|
||||||
|
CUIRect PaddingBottom;
|
||||||
|
List.HSplitTop(30.0f, &PaddingBottom, &List);
|
||||||
|
s_ScrollRegion.AddRect(PaddingBottom);
|
||||||
|
|
||||||
|
// Display the map settings edit box after having rendered all the lines, so the dropdown shows in
|
||||||
|
// front of everything, but is still being clipped by the scroll region.
|
||||||
|
if(DisplayFixInput)
|
||||||
|
DoMapSettingsEditBox(&s_Context, &FixInput, 10.0f, maximum(DropdownHeight, 30.0f));
|
||||||
|
|
||||||
|
s_ScrollRegion.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm button
|
||||||
|
static int s_ConfirmButton = 0, s_CancelButton = 0;
|
||||||
|
CUIRect ConfimButton, CancelButton;
|
||||||
|
ButtonBar.VSplitLeft(110.0f, &CancelButton, &ButtonBar);
|
||||||
|
ButtonBar.VSplitRight(110.0f, &ButtonBar, &ConfimButton);
|
||||||
|
|
||||||
|
bool CanConfirm = true;
|
||||||
|
for(auto &InvalidSetting : vSettingsInvalid)
|
||||||
|
{
|
||||||
|
if(!InvalidSetting.m_Context.m_Fixed && !InvalidSetting.m_Context.m_Deleted && !(InvalidSetting.m_Type & SInvalidSetting::TYPE_DUPLICATE))
|
||||||
|
{
|
||||||
|
CanConfirm = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &&Execute = [&]() {
|
||||||
|
// Execute will modify the actual map settings according to the fixes that were just made within the dialog.
|
||||||
|
|
||||||
|
// Fix fixed settings, erase deleted settings
|
||||||
|
for(auto &FixedSetting : vSettingsInvalid)
|
||||||
|
{
|
||||||
|
if(FixedSetting.m_Context.m_Fixed)
|
||||||
|
{
|
||||||
|
str_copy(m_Map.m_vSettings[FixedSetting.m_Index].m_aCommand, FixedSetting.m_aSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose chosen settings
|
||||||
|
// => Erase settings that don't match
|
||||||
|
// => Erase settings that were not chosen
|
||||||
|
std::vector<CEditorMapSetting> vSettingsToErase;
|
||||||
|
for(auto &Setting : vSettingsInvalid)
|
||||||
|
{
|
||||||
|
if(Setting.m_Type & SInvalidSetting::TYPE_DUPLICATE)
|
||||||
|
{
|
||||||
|
if(!Setting.m_Context.m_Chosen)
|
||||||
|
vSettingsToErase.emplace_back(Setting.m_aSetting);
|
||||||
|
else
|
||||||
|
vSettingsToErase.emplace_back(m_Map.m_vSettings[Setting.m_CollidingIndex].m_aCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase deleted settings
|
||||||
|
for(auto &DeletedSetting : vSettingsInvalid)
|
||||||
|
{
|
||||||
|
if(DeletedSetting.m_Context.m_Deleted)
|
||||||
|
{
|
||||||
|
m_Map.m_vSettings.erase(
|
||||||
|
std::remove_if(m_Map.m_vSettings.begin(), m_Map.m_vSettings.end(), [&](const CEditorMapSetting &MapSetting) {
|
||||||
|
return str_comp_nocase(MapSetting.m_aCommand, DeletedSetting.m_aSetting) == 0;
|
||||||
|
}),
|
||||||
|
m_Map.m_vSettings.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase settings to erase
|
||||||
|
for(auto &Setting : vSettingsToErase)
|
||||||
|
{
|
||||||
|
m_Map.m_vSettings.erase(
|
||||||
|
std::remove_if(m_Map.m_vSettings.begin(), m_Map.m_vSettings.end(), [&](const CEditorMapSetting &MapSetting) {
|
||||||
|
return str_comp_nocase(MapSetting.m_aCommand, Setting.m_aCommand) == 0;
|
||||||
|
}),
|
||||||
|
m_Map.m_vSettings.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Map.OnModify();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Confirm - execute the fixes
|
||||||
|
if(DoButton_Editor(&s_ConfirmButton, "Confirm", CanConfirm ? 0 : -1, &ConfimButton, 0, nullptr) || (CanConfirm && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
|
||||||
|
{
|
||||||
|
Execute();
|
||||||
|
m_Dialog = DIALOG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel - we load a new empty map
|
||||||
|
if(DoButton_Editor(&s_CancelButton, "Cancel", 0, &CancelButton, 0, nullptr) || (UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)))
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
m_aFileName[0] = 0;
|
||||||
|
m_Dialog = DIALOG_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CEditor::MapSettingsDropdownRenderCallback(const SPossibleValueMatch &Match, char (&aOutput)[128], std::vector<STextColorSplit> &vColorSplits)
|
void CEditor::MapSettingsDropdownRenderCallback(const SPossibleValueMatch &Match, char (&aOutput)[128], std::vector<STextColorSplit> &vColorSplits)
|
||||||
{
|
{
|
||||||
// Check the match argument index.
|
// Check the match argument index.
|
||||||
|
@ -661,8 +1120,7 @@ void CMapSettingsBackend::LoadSettingCommand(const std::shared_ptr<SMapSettingCo
|
||||||
size_t Len = pIterator - pNameStart;
|
size_t Len = pIterator - pNameStart;
|
||||||
pIterator++; // Skip ']'
|
pIterator++; // Skip ']'
|
||||||
|
|
||||||
if(Len + 1 >= sizeof(SParsedMapSettingArg::m_aName))
|
dbg_assert(Len + 1 < sizeof(SParsedMapSettingArg::m_aName), "Length of server setting name exceeds limit.");
|
||||||
dbg_msg("editor", "Warning: length of server setting name exceeds limit.");
|
|
||||||
|
|
||||||
// Append parsed arg
|
// Append parsed arg
|
||||||
m_ParsedCommandArgs[pSetting].emplace_back();
|
m_ParsedCommandArgs[pSetting].emplace_back();
|
||||||
|
@ -739,6 +1197,11 @@ void CMapSettingsBackend::CContext::Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMapSettingsBackend::CContext::Update()
|
void CMapSettingsBackend::CContext::Update()
|
||||||
|
{
|
||||||
|
UpdateFromString(InputString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMapSettingsBackend::CContext::UpdateFromString(const char *pStr)
|
||||||
{
|
{
|
||||||
// This is the main method that does all the argument parsing and validating.
|
// This is the main method that does all the argument parsing and validating.
|
||||||
// It fills pretty much all the context values, the arguments, their position,
|
// It fills pretty much all the context values, the arguments, their position,
|
||||||
|
@ -747,7 +1210,6 @@ void CMapSettingsBackend::CContext::Update()
|
||||||
m_pCurrentSetting = nullptr;
|
m_pCurrentSetting = nullptr;
|
||||||
m_vCurrentArgs.clear();
|
m_vCurrentArgs.clear();
|
||||||
|
|
||||||
const char *pStr = InputString();
|
|
||||||
const char *pIterator = pStr;
|
const char *pIterator = pStr;
|
||||||
|
|
||||||
// Get the command/setting
|
// Get the command/setting
|
||||||
|
@ -768,10 +1230,10 @@ void CMapSettingsBackend::CContext::Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse args
|
// Parse args
|
||||||
ParseArgs(pIterator);
|
ParseArgs(InputString(), pIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMapSettingsBackend::CContext::ParseArgs(const char *pStr)
|
void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const char *pStr)
|
||||||
{
|
{
|
||||||
// This method parses the arguments of the current command, starting at pStr
|
// This method parses the arguments of the current command, starting at pStr
|
||||||
|
|
||||||
|
@ -789,7 +1251,6 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pStr)
|
||||||
ClearError();
|
ClearError();
|
||||||
|
|
||||||
const char *pIterator = pStr;
|
const char *pIterator = pStr;
|
||||||
const char *pLineInputStr = InputString();
|
|
||||||
|
|
||||||
if(!pStr || *pStr == '\0')
|
if(!pStr || *pStr == '\0')
|
||||||
return; // No arguments
|
return; // No arguments
|
||||||
|
@ -864,8 +1325,8 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pStr)
|
||||||
|
|
||||||
// Also keep track of the visual X position of each argument within the input
|
// Also keep track of the visual X position of each argument within the input
|
||||||
float PosX = 0;
|
float PosX = 0;
|
||||||
const float WW = m_pBackend->TextRender()->TextWidth(FONT_SIZE, " ");
|
const float WW = m_pBackend->TextRender()->TextWidth(m_FontSize, " ");
|
||||||
PosX += m_pBackend->TextRender()->TextWidth(FONT_SIZE, m_aCommand);
|
PosX += m_pBackend->TextRender()->TextWidth(m_FontSize, m_aCommand);
|
||||||
|
|
||||||
// Parsing beings
|
// Parsing beings
|
||||||
while(*pIterator)
|
while(*pIterator)
|
||||||
|
@ -969,12 +1430,15 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pStr)
|
||||||
size_t ErrorArgIndex = m_vCurrentArgs.size() - 1;
|
size_t ErrorArgIndex = m_vCurrentArgs.size() - 1;
|
||||||
SCurrentSettingArg &ErrorArg = m_vCurrentArgs.back();
|
SCurrentSettingArg &ErrorArg = m_vCurrentArgs.back();
|
||||||
SParsedMapSettingArg &SettingArg = m_pBackend->m_ParsedCommandArgs[m_pCurrentSetting].at(ArgIndex);
|
SParsedMapSettingArg &SettingArg = m_pBackend->m_ParsedCommandArgs[m_pCurrentSetting].at(ArgIndex);
|
||||||
|
char aFormattedValue[256];
|
||||||
|
FormatDisplayValue(ErrorArg.m_aValue, aFormattedValue);
|
||||||
|
|
||||||
if(Error == ERROR_INVALID_VALUE || Error == ERROR_UNKNOWN_VALUE)
|
if(Error == ERROR_INVALID_VALUE || Error == ERROR_UNKNOWN_VALUE)
|
||||||
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "%s argument value: %s at position %d for argument '%s'", Error == ERROR_INVALID_VALUE ? "Invalid" : "Unknown", ErrorArg.m_aValue, (int)ErrorArg.m_Start, SettingArg.m_aName);
|
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "%s argument value: %s at position %d for argument '%s'", Error == ERROR_INVALID_VALUE ? "Invalid" : "Unknown", aFormattedValue, (int)ErrorArg.m_Start, SettingArg.m_aName);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::shared_ptr<SMapSettingInt> pSettingInt = std::static_pointer_cast<SMapSettingInt>(m_pCurrentSetting);
|
std::shared_ptr<SMapSettingInt> pSettingInt = std::static_pointer_cast<SMapSettingInt>(m_pCurrentSetting);
|
||||||
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Invalid argument value: %s at position %d for argument '%s': out of range [%d, %d]", ErrorArg.m_aValue, (int)ErrorArg.m_Start, SettingArg.m_aName, pSettingInt->m_Min, pSettingInt->m_Max);
|
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Invalid argument value: %s at position %d for argument '%s': out of range [%d, %d]", aFormattedValue, (int)ErrorArg.m_Start, SettingArg.m_aName, pSettingInt->m_Min, pSettingInt->m_Max);
|
||||||
}
|
}
|
||||||
m_Error.m_ArgIndex = (int)ErrorArgIndex;
|
m_Error.m_ArgIndex = (int)ErrorArgIndex;
|
||||||
break;
|
break;
|
||||||
|
@ -989,13 +1453,15 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pStr)
|
||||||
}
|
}
|
||||||
else if(!m_AllowUnknownCommands)
|
else if(!m_AllowUnknownCommands)
|
||||||
{
|
{
|
||||||
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Unknown server setting: %s", m_aCommand);
|
char aFormattedValue[256];
|
||||||
|
FormatDisplayValue(m_aCommand, aFormattedValue);
|
||||||
|
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Unknown server setting: %s", aFormattedValue);
|
||||||
m_Error.m_ArgIndex = -1;
|
m_Error.m_ArgIndex = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PosX += m_pBackend->TextRender()->TextWidth(FONT_SIZE, pArgStart, Length); // Advance argument position
|
PosX += m_pBackend->TextRender()->TextWidth(m_FontSize, pArgStart, Length); // Advance argument position
|
||||||
ArgIndex++;
|
ArgIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1013,6 +1479,9 @@ bool CMapSettingsBackend::CContext::UpdateCursor(bool Force)
|
||||||
// and the possible values matches if the argument index changes.
|
// and the possible values matches if the argument index changes.
|
||||||
// Returns true in case the cursor changed position
|
// Returns true in case the cursor changed position
|
||||||
|
|
||||||
|
if(!m_pLineInput)
|
||||||
|
return false;
|
||||||
|
|
||||||
size_t Offset = m_pLineInput->GetCursorOffset();
|
size_t Offset = m_pLineInput->GetCursorOffset();
|
||||||
if(Offset == m_LastCursorOffset && !Force)
|
if(Offset == m_LastCursorOffset && !Force)
|
||||||
return false;
|
return false;
|
||||||
|
@ -1142,7 +1611,9 @@ void CMapSettingsBackend::CContext::UpdatePossibleMatches()
|
||||||
if(m_vPossibleMatches.empty() && !m_AllowUnknownCommands)
|
if(m_vPossibleMatches.empty() && !m_AllowUnknownCommands)
|
||||||
{
|
{
|
||||||
// Fill the error if we do not allow unknown commands
|
// Fill the error if we do not allow unknown commands
|
||||||
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Unknown server setting: %s", m_aCommand);
|
char aFormattedValue[256];
|
||||||
|
FormatDisplayValue(m_aCommand, aFormattedValue);
|
||||||
|
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "Unknown server setting: %s", aFormattedValue);
|
||||||
m_Error.m_ArgIndex = -1;
|
m_Error.m_ArgIndex = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1202,6 +1673,9 @@ void CMapSettingsBackend::CContext::UpdatePossibleMatches()
|
||||||
|
|
||||||
bool CMapSettingsBackend::CContext::OnInput(const IInput::CEvent &Event)
|
bool CMapSettingsBackend::CContext::OnInput(const IInput::CEvent &Event)
|
||||||
{
|
{
|
||||||
|
if(!m_pLineInput)
|
||||||
|
return false;
|
||||||
|
|
||||||
if(!m_pLineInput->IsActive())
|
if(!m_pLineInput->IsActive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -1227,42 +1701,11 @@ bool CMapSettingsBackend::CContext::OnInput(const IInput::CEvent &Event)
|
||||||
|
|
||||||
const char *CMapSettingsBackend::CContext::InputString() const
|
const char *CMapSettingsBackend::CContext::InputString() const
|
||||||
{
|
{
|
||||||
|
if(!m_pLineInput)
|
||||||
|
return nullptr;
|
||||||
return m_pBackend->Input()->HasComposition() ? m_CompositionStringBuffer.c_str() : m_pLineInput->GetString();
|
return m_pBackend->Input()->HasComposition() ? m_CompositionStringBuffer.c_str() : m_pLineInput->GetString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMapSettingsBackend::CContext::UpdateCompositionString()
|
|
||||||
{
|
|
||||||
const bool HasComposition = m_pBackend->Input()->HasComposition();
|
|
||||||
|
|
||||||
if(HasComposition)
|
|
||||||
{
|
|
||||||
const size_t CursorOffset = m_pLineInput->GetCursorOffset();
|
|
||||||
const size_t DisplayCursorOffset = m_pLineInput->OffsetFromActualToDisplay(CursorOffset);
|
|
||||||
const std::string DisplayStr = std::string(m_pLineInput->GetString());
|
|
||||||
std::string CompositionBuffer = DisplayStr.substr(0, DisplayCursorOffset) + m_pBackend->Input()->GetComposition() + DisplayStr.substr(DisplayCursorOffset);
|
|
||||||
if(CompositionBuffer != m_CompositionStringBuffer)
|
|
||||||
{
|
|
||||||
m_CompositionStringBuffer = CompositionBuffer;
|
|
||||||
Update();
|
|
||||||
UpdateCursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CMapSettingsBackend::OnInput(const IInput::CEvent &Event)
|
|
||||||
{
|
|
||||||
if(ms_pActiveContext)
|
|
||||||
return ms_pActiveContext->OnInput(Event);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMapSettingsBackend::OnUpdate()
|
|
||||||
{
|
|
||||||
if(ms_pActiveContext && ms_pActiveContext->m_pLineInput->IsActive())
|
|
||||||
ms_pActiveContext->UpdateCompositionString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentStringColor = ColorRGBA(84 / 255.0f, 1.0f, 1.0f, 1.0f);
|
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentStringColor = ColorRGBA(84 / 255.0f, 1.0f, 1.0f, 1.0f);
|
||||||
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentNumberColor = ColorRGBA(0.1f, 0.9f, 0.05f, 1.0f);
|
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentNumberColor = ColorRGBA(0.1f, 0.9f, 0.05f, 1.0f);
|
||||||
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentUnknownColor = ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f);
|
const ColorRGBA CMapSettingsBackend::CContext::ms_ArgumentUnknownColor = ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f);
|
||||||
|
@ -1290,7 +1733,7 @@ void CMapSettingsBackend::CContext::ColorArguments(std::vector<STextColorSplit>
|
||||||
vColorSplits.emplace_back(Argument.m_Start, Argument.m_End - Argument.m_Start, Color);
|
vColorSplits.emplace_back(Argument.m_Start, Argument.m_End - Argument.m_Start, Color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!m_pLineInput->IsEmpty())
|
if(m_pLineInput && !m_pLineInput->IsEmpty())
|
||||||
{
|
{
|
||||||
if(!CommandIsValid() && !m_AllowUnknownCommands)
|
if(!CommandIsValid() && !m_AllowUnknownCommands)
|
||||||
{
|
{
|
||||||
|
@ -1306,6 +1749,16 @@ void CMapSettingsBackend::CContext::ColorArguments(std::vector<STextColorSplit>
|
||||||
}
|
}
|
||||||
|
|
||||||
int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result) const
|
int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result) const
|
||||||
|
{
|
||||||
|
return CheckCollision(m_pBackend->Editor()->m_Map.m_vSettings, Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CMapSettingsBackend::CContext::CheckCollision(const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const
|
||||||
|
{
|
||||||
|
return CheckCollision(InputString(), vSettings, Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CMapSettingsBackend::CContext::CheckCollision(const char *pInputString, const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const
|
||||||
{
|
{
|
||||||
// Checks for a collision with the current map settings.
|
// Checks for a collision with the current map settings.
|
||||||
// A collision is when a setting with the same arguments already exists and that it can't be added multiple times.
|
// A collision is when a setting with the same arguments already exists and that it can't be added multiple times.
|
||||||
|
@ -1315,7 +1768,7 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
// This method CheckCollision(ECollisionCheckResult&) returns an integer which is the index of the colliding line. If no
|
// This method CheckCollision(ECollisionCheckResult&) returns an integer which is the index of the colliding line. If no
|
||||||
// colliding line was found, then it returns -1.
|
// colliding line was found, then it returns -1.
|
||||||
|
|
||||||
const char *pInputString = InputString();
|
const int InputLength = str_length(pInputString);
|
||||||
|
|
||||||
struct SArgument
|
struct SArgument
|
||||||
{
|
{
|
||||||
|
@ -1332,8 +1785,6 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
std::vector<SArgument> m_vArgs;
|
std::vector<SArgument> m_vArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto &vSettings = m_pBackend->Editor()->m_Map.m_vSettings;
|
|
||||||
|
|
||||||
// For now we split each map setting corresponding to the setting we want to add by spaces
|
// For now we split each map setting corresponding to the setting we want to add by spaces
|
||||||
auto &&SplitSetting = [](const char *pStr) {
|
auto &&SplitSetting = [](const char *pStr) {
|
||||||
std::vector<SArgument> vaArgs;
|
std::vector<SArgument> vaArgs;
|
||||||
|
@ -1355,7 +1806,7 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
if(!m_AllowUnknownCommands)
|
if(!m_AllowUnknownCommands)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if(m_pLineInput->GetLength() == 0)
|
if(InputLength == 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// If we get here, it means we allow unknown commands.
|
// If we get here, it means we allow unknown commands.
|
||||||
|
@ -1395,7 +1846,7 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
// can have is REPLACE.
|
// can have is REPLACE.
|
||||||
// In this case, the collision is found only by checking the command name for every setting in the current map settings.
|
// In this case, the collision is found only by checking the command name for every setting in the current map settings.
|
||||||
char aBuffer[256];
|
char aBuffer[256];
|
||||||
auto It = std::find_if(vSettings.begin(), vSettings.end(), [&](const CEditorMap::CSetting &Setting) {
|
auto It = std::find_if(vSettings.begin(), vSettings.end(), [&](const CEditorMapSetting &Setting) {
|
||||||
const char *pLineSettingValue = Setting.m_aCommand; // Get the map setting command
|
const char *pLineSettingValue = Setting.m_aCommand; // Get the map setting command
|
||||||
pLineSettingValue = str_next_token(pLineSettingValue, " ", aBuffer, sizeof(aBuffer)); // Get the first token before the first space
|
pLineSettingValue = str_next_token(pLineSettingValue, " ", aBuffer, sizeof(aBuffer)); // Get the first token before the first space
|
||||||
return str_comp_nocase(aBuffer, pSetting->m_pName) == 0; // Check if that equals our current command
|
return str_comp_nocase(aBuffer, pSetting->m_pName) == 0; // Check if that equals our current command
|
||||||
|
@ -1427,10 +1878,10 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
|
|
||||||
std::shared_ptr<SMapSettingCommand> pSettingCommand = std::static_pointer_cast<SMapSettingCommand>(pSetting);
|
std::shared_ptr<SMapSettingCommand> pSettingCommand = std::static_pointer_cast<SMapSettingCommand>(pSetting);
|
||||||
// Get matching lines for that command
|
// Get matching lines for that command
|
||||||
std::vector<SLineArgs> vvArgs;
|
std::vector<SLineArgs> vLineArgs;
|
||||||
for(int i = 0; i < (int)vSettings.size(); i++)
|
for(int i = 0; i < (int)vSettings.size(); i++)
|
||||||
{
|
{
|
||||||
auto &Setting = vSettings.at(i);
|
const auto &Setting = vSettings.at(i);
|
||||||
|
|
||||||
// Split this setting into its arguments
|
// Split this setting into its arguments
|
||||||
std::vector<SArgument> vArgs = SplitSetting(Setting.m_aCommand);
|
std::vector<SArgument> vArgs = SplitSetting(Setting.m_aCommand);
|
||||||
|
@ -1439,7 +1890,7 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
{
|
{
|
||||||
// When that's the case, we save them
|
// When that's the case, we save them
|
||||||
vArgs.erase(vArgs.begin());
|
vArgs.erase(vArgs.begin());
|
||||||
vvArgs.push_back(SLineArgs{
|
vLineArgs.push_back(SLineArgs{
|
||||||
i,
|
i,
|
||||||
vArgs,
|
vArgs,
|
||||||
});
|
});
|
||||||
|
@ -1453,7 +1904,7 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
{
|
{
|
||||||
bool Collide = false;
|
bool Collide = false;
|
||||||
const char *pValue = Arg(ArgIndex).m_aValue;
|
const char *pValue = Arg(ArgIndex).m_aValue;
|
||||||
for(auto &Line : vvArgs)
|
for(auto &Line : vLineArgs)
|
||||||
{
|
{
|
||||||
// Check first colliding line
|
// Check first colliding line
|
||||||
if(str_comp_nocase(pValue, Line.m_vArgs[ArgIndex].m_aValue) == 0)
|
if(str_comp_nocase(pValue, Line.m_vArgs[ArgIndex].m_aValue) == 0)
|
||||||
|
@ -1470,6 +1921,13 @@ int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result)
|
||||||
// (or if we had an error)
|
// (or if we had an error)
|
||||||
if(!Collide || Error)
|
if(!Collide || Error)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Otherwise, remove non-colliding args from the list
|
||||||
|
vLineArgs.erase(
|
||||||
|
std::remove_if(vLineArgs.begin(), vLineArgs.end(), [&](const SLineArgs &Line) {
|
||||||
|
return str_comp_nocase(pValue, Line.m_vArgs[ArgIndex].m_aValue) != 0;
|
||||||
|
}),
|
||||||
|
vLineArgs.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The result is either REPLACE when we found a collision, or ADD
|
// The result is either REPLACE when we found a collision, or ADD
|
||||||
|
@ -1496,8 +1954,7 @@ bool CMapSettingsBackend::CContext::Valid() const
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check that we have the same number of arguments
|
// Check that we have the same number of arguments
|
||||||
const bool ArgCountValid = m_vCurrentArgs.size() == m_pBackend->m_ParsedCommandArgs.at(m_pCurrentSetting).size();
|
return m_vCurrentArgs.size() == m_pBackend->m_ParsedCommandArgs.at(m_pCurrentSetting).size();
|
||||||
return ArgCountValid;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1515,6 +1972,127 @@ void CMapSettingsBackend::CContext::GetCommandHelpText(char *pStr, int Length) c
|
||||||
str_copy(pStr, m_pCurrentSetting->m_pHelp, Length);
|
str_copy(pStr, m_pCurrentSetting->m_pHelp, Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMapSettingsBackend::CContext::UpdateCompositionString()
|
||||||
|
{
|
||||||
|
if(!m_pLineInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool HasComposition = m_pBackend->Input()->HasComposition();
|
||||||
|
|
||||||
|
if(HasComposition)
|
||||||
|
{
|
||||||
|
const size_t CursorOffset = m_pLineInput->GetCursorOffset();
|
||||||
|
const size_t DisplayCursorOffset = m_pLineInput->OffsetFromActualToDisplay(CursorOffset);
|
||||||
|
const std::string DisplayStr = std::string(m_pLineInput->GetString());
|
||||||
|
std::string CompositionBuffer = DisplayStr.substr(0, DisplayCursorOffset) + m_pBackend->Input()->GetComposition() + DisplayStr.substr(DisplayCursorOffset);
|
||||||
|
if(CompositionBuffer != m_CompositionStringBuffer)
|
||||||
|
{
|
||||||
|
m_CompositionStringBuffer = CompositionBuffer;
|
||||||
|
Update();
|
||||||
|
UpdateCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N>
|
||||||
|
void CMapSettingsBackend::CContext::FormatDisplayValue(const char *pValue, char (&aOut)[N])
|
||||||
|
{
|
||||||
|
const int MaxLength = 32;
|
||||||
|
if(str_length(pValue) > MaxLength)
|
||||||
|
{
|
||||||
|
str_copy(aOut, pValue, MaxLength);
|
||||||
|
str_append(aOut, "...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str_copy(aOut, pValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMapSettingsBackend::OnInput(const IInput::CEvent &Event)
|
||||||
|
{
|
||||||
|
if(ms_pActiveContext)
|
||||||
|
return ms_pActiveContext->OnInput(Event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMapSettingsBackend::OnUpdate()
|
||||||
|
{
|
||||||
|
if(ms_pActiveContext && ms_pActiveContext->m_pLineInput && ms_pActiveContext->m_pLineInput->IsActive())
|
||||||
|
ms_pActiveContext->UpdateCompositionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMapSettingsBackend::OnMapLoad()
|
||||||
|
{
|
||||||
|
// Load & validate all map settings
|
||||||
|
m_LoadedMapSettings.Reset();
|
||||||
|
|
||||||
|
auto &vLoadedMapSettings = Editor()->m_Map.m_vSettings;
|
||||||
|
|
||||||
|
// Keep a vector of valid map settings, to check collision against: m_vValidLoadedMapSettings
|
||||||
|
|
||||||
|
// Create a local context with no lineinput, only used to parse the commands
|
||||||
|
CContext LocalContext = NewContext(nullptr);
|
||||||
|
|
||||||
|
// Iterate through map settings
|
||||||
|
// Two steps:
|
||||||
|
// 1. Save valid and invalid settings
|
||||||
|
// 2. Check for duplicates
|
||||||
|
|
||||||
|
std::vector<std::tuple<int, bool, CEditorMapSetting>> vSettingsInvalid;
|
||||||
|
|
||||||
|
for(int i = 0; i < (int)vLoadedMapSettings.size(); i++)
|
||||||
|
{
|
||||||
|
CEditorMapSetting &Setting = vLoadedMapSettings.at(i);
|
||||||
|
// Parse the setting using the context
|
||||||
|
LocalContext.UpdateFromString(Setting.m_aCommand);
|
||||||
|
|
||||||
|
bool Valid = LocalContext.Valid();
|
||||||
|
ECollisionCheckResult Result = ECollisionCheckResult::ERROR;
|
||||||
|
LocalContext.CheckCollision(Setting.m_aCommand, m_LoadedMapSettings.m_vSettingsValid, Result);
|
||||||
|
|
||||||
|
if(Valid && Result == ECollisionCheckResult::ADD)
|
||||||
|
m_LoadedMapSettings.m_vSettingsValid.emplace_back(Setting);
|
||||||
|
else
|
||||||
|
vSettingsInvalid.emplace_back(i, Valid, Setting);
|
||||||
|
|
||||||
|
LocalContext.Reset();
|
||||||
|
|
||||||
|
// Empty duplicates for this line, might be filled later
|
||||||
|
m_LoadedMapSettings.m_SettingsDuplicate.insert({i, {}});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto &[Index, Valid, Setting] : vSettingsInvalid)
|
||||||
|
{
|
||||||
|
LocalContext.UpdateFromString(Setting.m_aCommand);
|
||||||
|
|
||||||
|
ECollisionCheckResult Result = ECollisionCheckResult::ERROR;
|
||||||
|
int CollidingLineIndex = LocalContext.CheckCollision(Setting.m_aCommand, m_LoadedMapSettings.m_vSettingsValid, Result);
|
||||||
|
int RealCollidingLineIndex = CollidingLineIndex;
|
||||||
|
|
||||||
|
if(CollidingLineIndex != -1)
|
||||||
|
RealCollidingLineIndex = std::find_if(vLoadedMapSettings.begin(), vLoadedMapSettings.end(), [&](const CEditorMapSetting &MapSetting) {
|
||||||
|
return str_comp_nocase(MapSetting.m_aCommand, m_LoadedMapSettings.m_vSettingsValid.at(CollidingLineIndex).m_aCommand) == 0;
|
||||||
|
}) - vLoadedMapSettings.begin();
|
||||||
|
|
||||||
|
int Type = 0;
|
||||||
|
if(!Valid)
|
||||||
|
Type |= SInvalidSetting::TYPE_INVALID;
|
||||||
|
if(Result == ECollisionCheckResult::REPLACE)
|
||||||
|
Type |= SInvalidSetting::TYPE_DUPLICATE;
|
||||||
|
|
||||||
|
m_LoadedMapSettings.m_vSettingsInvalid.emplace_back(Index, Setting.m_aCommand, Type, RealCollidingLineIndex);
|
||||||
|
if(Type & SInvalidSetting::TYPE_DUPLICATE)
|
||||||
|
m_LoadedMapSettings.m_SettingsDuplicate[RealCollidingLineIndex].emplace_back(m_LoadedMapSettings.m_vSettingsInvalid.size() - 1);
|
||||||
|
|
||||||
|
LocalContext.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!m_LoadedMapSettings.m_vSettingsInvalid.empty())
|
||||||
|
Editor()->m_Dialog = DIALOG_MAPSETTINGS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
// ------ loaders
|
// ------ loaders
|
||||||
|
|
||||||
void CMapSettingsBackend::InitValueLoaders()
|
void CMapSettingsBackend::InitValueLoaders()
|
||||||
|
|
|
@ -14,6 +14,16 @@ struct SMapSettingCommand;
|
||||||
struct IMapSetting;
|
struct IMapSetting;
|
||||||
class CLineInput;
|
class CLineInput;
|
||||||
|
|
||||||
|
struct CEditorMapSetting
|
||||||
|
{
|
||||||
|
char m_aCommand[256];
|
||||||
|
|
||||||
|
CEditorMapSetting(const char *pCommand)
|
||||||
|
{
|
||||||
|
str_copy(m_aCommand, pCommand);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// A parsed map setting argument, storing the name and the type
|
// A parsed map setting argument, storing the name and the type
|
||||||
// Used for validation and to display arguments names
|
// Used for validation and to display arguments names
|
||||||
struct SParsedMapSettingArg
|
struct SParsedMapSettingArg
|
||||||
|
@ -46,6 +56,32 @@ struct SCommandParseError
|
||||||
int m_ArgIndex;
|
int m_ArgIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SInvalidSetting
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TYPE_INVALID = 1 << 0,
|
||||||
|
TYPE_DUPLICATE = 1 << 1
|
||||||
|
};
|
||||||
|
int m_Index; // Index of the command in the loaded map settings list
|
||||||
|
char m_aSetting[256]; // String of the setting
|
||||||
|
int m_Type; // Type of that invalid setting
|
||||||
|
int m_CollidingIndex; // The colliding line index in case type is TYPE_DUPLICATE
|
||||||
|
|
||||||
|
struct SContext
|
||||||
|
{
|
||||||
|
bool m_Fixed;
|
||||||
|
bool m_Deleted;
|
||||||
|
bool m_Chosen;
|
||||||
|
} m_Context;
|
||||||
|
|
||||||
|
SInvalidSetting(int Index, const char *pSetting, int Type, int CollidingIndex) :
|
||||||
|
m_Index(Index), m_Type(Type), m_CollidingIndex(CollidingIndex), m_Context()
|
||||||
|
{
|
||||||
|
str_copy(m_aSetting, pSetting);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --------------------------------------
|
// --------------------------------------
|
||||||
// Builder classes & methods to generate list of possible values
|
// Builder classes & methods to generate list of possible values
|
||||||
// easily for specific settings and arguments.
|
// easily for specific settings and arguments.
|
||||||
|
@ -151,7 +187,8 @@ public: // General methods
|
||||||
|
|
||||||
void Init(CEditor *pEditor) override;
|
void Init(CEditor *pEditor) override;
|
||||||
bool OnInput(const IInput::CEvent &Event) override;
|
bool OnInput(const IInput::CEvent &Event) override;
|
||||||
void OnUpdate();
|
void OnUpdate() override;
|
||||||
|
void OnMapLoad() override;
|
||||||
|
|
||||||
public: // Constraints methods
|
public: // Constraints methods
|
||||||
enum class EArgConstraint
|
enum class EArgConstraint
|
||||||
|
@ -197,9 +234,14 @@ public: // CContext
|
||||||
const SCurrentSettingArg &Arg(int Index) const { return m_vCurrentArgs.at(Index); }
|
const SCurrentSettingArg &Arg(int Index) const { return m_vCurrentArgs.at(Index); }
|
||||||
const std::shared_ptr<IMapSetting> &Setting() const { return m_pCurrentSetting; }
|
const std::shared_ptr<IMapSetting> &Setting() const { return m_pCurrentSetting; }
|
||||||
CLineInput *LineInput() const { return m_pLineInput; }
|
CLineInput *LineInput() const { return m_pLineInput; }
|
||||||
|
void SetFontSize(float FontSize) { m_FontSize = FontSize; }
|
||||||
|
|
||||||
int CheckCollision(ECollisionCheckResult &Result) const;
|
int CheckCollision(ECollisionCheckResult &Result) const;
|
||||||
|
int CheckCollision(const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const;
|
||||||
|
int CheckCollision(const char *pInputString, const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const;
|
||||||
|
|
||||||
void Update();
|
void Update();
|
||||||
|
void UpdateFromString(const char *pStr);
|
||||||
bool UpdateCursor(bool Force = false);
|
bool UpdateCursor(bool Force = false);
|
||||||
void Reset();
|
void Reset();
|
||||||
void GetCommandHelpText(char *pStr, int Length) const;
|
void GetCommandHelpText(char *pStr, int Length) const;
|
||||||
|
@ -221,11 +263,14 @@ public: // CContext
|
||||||
void ClearError();
|
void ClearError();
|
||||||
EValidationResult ValidateArg(int Index, const char *pArg);
|
EValidationResult ValidateArg(int Index, const char *pArg);
|
||||||
void UpdatePossibleMatches();
|
void UpdatePossibleMatches();
|
||||||
void ParseArgs(const char *pStr);
|
void ParseArgs(const char *pLineInputStr, const char *pStr);
|
||||||
bool OnInput(const IInput::CEvent &Event);
|
bool OnInput(const IInput::CEvent &Event);
|
||||||
const char *InputString() const;
|
const char *InputString() const;
|
||||||
void UpdateCompositionString();
|
void UpdateCompositionString();
|
||||||
|
|
||||||
|
template<int N>
|
||||||
|
void FormatDisplayValue(const char *pValue, char (&aOut)[N]);
|
||||||
|
|
||||||
private: // Fields
|
private: // Fields
|
||||||
std::shared_ptr<IMapSetting> m_pCurrentSetting; // Current setting, can be nullptr in case of invalid setting name
|
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
|
std::vector<SCurrentSettingArg> m_vCurrentArgs; // Current parsed arguments from lineinput string
|
||||||
|
@ -238,6 +283,7 @@ public: // CContext
|
||||||
|
|
||||||
CMapSettingsBackend *m_pBackend;
|
CMapSettingsBackend *m_pBackend;
|
||||||
std::string m_CompositionStringBuffer;
|
std::string m_CompositionStringBuffer;
|
||||||
|
float m_FontSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
CContext NewContext(CLineInput *pLineInput)
|
CContext NewContext(CLineInput *pLineInput)
|
||||||
|
@ -328,6 +374,27 @@ private: // Backend fields
|
||||||
static CContext *ms_pActiveContext;
|
static CContext *ms_pActiveContext;
|
||||||
|
|
||||||
friend class CEditor;
|
friend class CEditor;
|
||||||
|
|
||||||
|
private: // Map settings validation on load
|
||||||
|
struct SLoadedMapSettings
|
||||||
|
{
|
||||||
|
std::vector<SInvalidSetting> m_vSettingsInvalid;
|
||||||
|
std::vector<CEditorMapSetting> m_vSettingsValid;
|
||||||
|
std::map<int, std::vector<int>> m_SettingsDuplicate;
|
||||||
|
|
||||||
|
SLoadedMapSettings() :
|
||||||
|
m_vSettingsInvalid(), m_vSettingsValid(), m_SettingsDuplicate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
m_vSettingsInvalid.clear();
|
||||||
|
m_vSettingsValid.clear();
|
||||||
|
m_SettingsDuplicate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} m_LoadedMapSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue