mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-19 22:48:18 +00:00
Various fixes to map settings handling
This commit is contained in:
parent
aca398f3c9
commit
0cd002e08c
|
@ -1757,7 +1757,15 @@ public:
|
|||
if(PrevCharCount >= Split.m_CharIndex && (Split.m_Length == -1 || PrevCharCount < Split.m_CharIndex + Split.m_Length))
|
||||
Color = Split.m_Color;
|
||||
if(Split.m_Length != -1 && PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1))
|
||||
{
|
||||
ColorOption++;
|
||||
if(ColorOption < (int)pCursor->m_vColorSplits.size())
|
||||
{ // Handle splits that are
|
||||
Split = pCursor->m_vColorSplits.at(ColorOption);
|
||||
if(PrevCharCount >= Split.m_CharIndex)
|
||||
Color = Split.m_Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't add text that isn't drawn, the color overwrite is used for that
|
||||
|
|
|
@ -232,8 +232,9 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd
|
|||
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();
|
||||
}
|
||||
|
||||
m_Map.OnModify();
|
||||
s_ListBox.ScrollToSelected();
|
||||
m_SettingsCommandInput.Clear();
|
||||
m_MapSettingsCommandContext.Reset(); // Reset context
|
||||
|
@ -308,7 +309,7 @@ void CEditor::DoMapSettingsEditBox(CMapSettingsBackend::CContext *pContext, cons
|
|||
CUIRect Label;
|
||||
Background.VSplitLeft(PartMargin, nullptr, &Label);
|
||||
TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
UI()->DoLabel(&Label, pStr, FontSize, TEXTALIGN_MIDDLE);
|
||||
UI()->DoLabel(&Label, pStr, FontSize, TEXTALIGN_ML);
|
||||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||
};
|
||||
|
||||
|
@ -350,7 +351,7 @@ void CEditor::DoMapSettingsEditBox(CMapSettingsBackend::CContext *pContext, cons
|
|||
{
|
||||
// If line input is active, let's display a floating part for either the current argument name
|
||||
// or for the error, if any. The error is only displayed when the cursor is at the end of the input.
|
||||
const bool IsAtEnd = pLineInput->GetCursorOffset() == pLineInput->GetLength();
|
||||
const bool IsAtEnd = pLineInput->GetCursorOffset() >= (m_MapSettingsCommandContext.CommentOffset() != -1 ? m_MapSettingsCommandContext.CommentOffset() : pLineInput->GetLength());
|
||||
|
||||
if(Context.CurrentArgName() && (!Context.HasError() || !IsAtEnd)) // Render argument name
|
||||
RenderFloatingPart(&ToolBar, x, Context.CurrentArgName());
|
||||
|
@ -467,12 +468,11 @@ int CEditor::RenderEditBoxDropdown(SEditBoxDropdownContext *pDropdown, CUIRect V
|
|||
const int NumEntries = vData.size();
|
||||
|
||||
// Setup the rect
|
||||
static float s_Width = View.w;
|
||||
CUIRect CommandsDropdown = View;
|
||||
CommandsDropdown.y += View.h + 0.1f;
|
||||
CommandsDropdown.x = x;
|
||||
if(AutoWidth)
|
||||
CommandsDropdown.w = s_Width + pListBox->ScrollbarWidth();
|
||||
CommandsDropdown.w = pDropdown->m_Width + pListBox->ScrollbarWidth();
|
||||
|
||||
pListBox->SetActive(NumEntries > 0);
|
||||
if(NumEntries > 0)
|
||||
|
@ -520,7 +520,7 @@ int CEditor::RenderEditBoxDropdown(SEditBoxDropdownContext *pDropdown, CUIRect V
|
|||
}
|
||||
}
|
||||
|
||||
s_Width = LargestWidth;
|
||||
pDropdown->m_Width = LargestWidth;
|
||||
|
||||
int EndIndex = pListBox->DoEnd();
|
||||
if(NewIndex == Selected)
|
||||
|
@ -679,20 +679,17 @@ void CEditor::RenderMapSettingsErrorDialog()
|
|||
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(DoButton_Editor(&s_Ok, "Done", Valid ? 0 : -1, &OkBtn, 0, "Confirm editing 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());
|
||||
// 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();
|
||||
}
|
||||
// Clear the input & fixing command index
|
||||
s_FixingCommandIndex = -1;
|
||||
s_Input.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -862,7 +859,7 @@ void CEditor::RenderMapSettingsErrorDialog()
|
|||
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(DoButton_Editor(&s_Ok, "Done", Valid ? 0 : -1, &OkBtn, 0, "Confirm editing of this command") || (s_Input.IsActive() && Valid && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
|
||||
{
|
||||
if(Valid) // Just to make sure
|
||||
{
|
||||
|
@ -915,17 +912,22 @@ void CEditor::RenderMapSettingsErrorDialog()
|
|||
}
|
||||
|
||||
// Confirm button
|
||||
static int s_ConfirmButton = 0, s_CancelButton = 0;
|
||||
CUIRect ConfimButton, CancelButton;
|
||||
static int s_ConfirmButton = 0, s_CancelButton = 0, s_FixAllButton = 0;
|
||||
CUIRect ConfimButton, CancelButton, FixAllUnknownButton;
|
||||
ButtonBar.VSplitLeft(110.0f, &CancelButton, &ButtonBar);
|
||||
ButtonBar.VSplitRight(110.0f, &ButtonBar, &ConfimButton);
|
||||
ButtonBar.VSplitRight(5.0f, &ButtonBar, nullptr);
|
||||
ButtonBar.VSplitRight(150.0f, &ButtonBar, &FixAllUnknownButton);
|
||||
|
||||
bool CanConfirm = true;
|
||||
bool CanFixAllUnknown = false;
|
||||
for(auto &InvalidSetting : vSettingsInvalid)
|
||||
{
|
||||
if(!InvalidSetting.m_Context.m_Fixed && !InvalidSetting.m_Context.m_Deleted && !(InvalidSetting.m_Type & SInvalidSetting::TYPE_DUPLICATE))
|
||||
{
|
||||
CanConfirm = false;
|
||||
if(InvalidSetting.m_Unknown)
|
||||
CanFixAllUnknown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -983,6 +985,19 @@ void CEditor::RenderMapSettingsErrorDialog()
|
|||
m_Map.OnModify();
|
||||
};
|
||||
|
||||
auto &&FixAllUnknown = [&] {
|
||||
// Mark unknown settings as fixed
|
||||
for(auto &InvalidSetting : vSettingsInvalid)
|
||||
if(!InvalidSetting.m_Context.m_Fixed && !InvalidSetting.m_Context.m_Deleted && !(InvalidSetting.m_Type & SInvalidSetting::TYPE_DUPLICATE) && InvalidSetting.m_Unknown)
|
||||
InvalidSetting.m_Context.m_Fixed = true;
|
||||
};
|
||||
|
||||
// Fix all unknown settings
|
||||
if(DoButton_Editor(&s_FixAllButton, "Allow all unknown settings", CanFixAllUnknown ? 0 : -1, &FixAllUnknownButton, 0, nullptr))
|
||||
{
|
||||
FixAllUnknown();
|
||||
}
|
||||
|
||||
// Confirm - execute the fixes
|
||||
if(DoButton_Editor(&s_ConfirmButton, "Confirm", CanConfirm ? 0 : -1, &ConfimButton, 0, nullptr) || (CanConfirm && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)))
|
||||
{
|
||||
|
@ -1192,6 +1207,7 @@ void CMapSettingsBackend::CContext::Reset()
|
|||
m_DropdownContext.m_MousePressedInside = false;
|
||||
m_DropdownContext.m_Visible = false;
|
||||
m_DropdownContext.m_ShouldHide = false;
|
||||
m_CommentOffset = -1;
|
||||
|
||||
ClearError();
|
||||
}
|
||||
|
@ -1209,15 +1225,49 @@ void CMapSettingsBackend::CContext::UpdateFromString(const char *pStr)
|
|||
|
||||
m_pCurrentSetting = nullptr;
|
||||
m_vCurrentArgs.clear();
|
||||
m_CommentOffset = -1;
|
||||
|
||||
const char *pIterator = pStr;
|
||||
|
||||
// Check for comment
|
||||
const char *pEnd = pStr;
|
||||
int InString = 0;
|
||||
|
||||
while(*pEnd)
|
||||
{
|
||||
if(*pEnd == '"')
|
||||
InString ^= 1;
|
||||
else if(*pEnd == '\\') // Escape sequences
|
||||
{
|
||||
if(pEnd[1] == '"')
|
||||
pEnd++;
|
||||
}
|
||||
else if(!InString)
|
||||
{
|
||||
if(*pEnd == '#') // Found comment
|
||||
{
|
||||
m_CommentOffset = pEnd - pStr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pEnd++;
|
||||
}
|
||||
|
||||
if(m_CommentOffset == 0)
|
||||
return;
|
||||
|
||||
// End command at start of comment, if any
|
||||
char aInputString[256];
|
||||
str_copy(aInputString, pStr, m_CommentOffset != -1 ? m_CommentOffset + 1 : sizeof(aInputString));
|
||||
pIterator = aInputString;
|
||||
|
||||
// Get the command/setting
|
||||
m_aCommand[0] = '\0';
|
||||
while(pIterator && *pIterator != ' ' && *pIterator != '\0')
|
||||
pIterator++;
|
||||
|
||||
str_copy(m_aCommand, pStr, (pIterator - pStr) + 1);
|
||||
str_copy(m_aCommand, aInputString, (pIterator - aInputString) + 1);
|
||||
|
||||
// Get the command if it is a recognized one
|
||||
for(auto &pSetting : m_pBackend->m_vpMapSettings)
|
||||
|
@ -1230,24 +1280,13 @@ void CMapSettingsBackend::CContext::UpdateFromString(const char *pStr)
|
|||
}
|
||||
|
||||
// Parse args
|
||||
ParseArgs(InputString(), pIterator);
|
||||
ParseArgs(aInputString, pIterator);
|
||||
}
|
||||
|
||||
void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const char *pStr)
|
||||
{
|
||||
// This method parses the arguments of the current command, starting at pStr
|
||||
|
||||
enum EError
|
||||
{
|
||||
ERROR_NONE = 0,
|
||||
ERROR_TOO_MANY_ARGS,
|
||||
ERROR_INVALID_VALUE,
|
||||
ERROR_UNKNOWN_VALUE,
|
||||
ERROR_INCOMPLETE,
|
||||
ERROR_OUT_OF_RANGE,
|
||||
ERROR_UNKNOWN
|
||||
};
|
||||
|
||||
ClearError();
|
||||
|
||||
const char *pIterator = pStr;
|
||||
|
@ -1321,7 +1360,7 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
|
||||
const int CommandArgCount = m_pCurrentSetting != nullptr ? m_pBackend->m_ParsedCommandArgs.at(m_pCurrentSetting).size() : 0;
|
||||
int ArgIndex = 0;
|
||||
EError Error = ERROR_NONE;
|
||||
SCommandParseError::EErrorType Error = SCommandParseError::ERROR_NONE;
|
||||
|
||||
// Also keep track of the visual X position of each argument within the input
|
||||
float PosX = 0;
|
||||
|
@ -1331,6 +1370,7 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
// Parsing beings
|
||||
while(*pIterator)
|
||||
{
|
||||
Error = SCommandParseError::ERROR_NONE;
|
||||
pIterator++; // Skip whitespace
|
||||
PosX += WW; // Add whitespace width
|
||||
|
||||
|
@ -1344,7 +1384,8 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
// Add new argument, copy the argument contents
|
||||
m_vCurrentArgs.emplace_back();
|
||||
auto &NewArg = m_vCurrentArgs.back();
|
||||
str_copy(NewArg.m_aValue, pArgStart, Length + 1);
|
||||
// Fill argument value, with a maximum length of 256
|
||||
str_copy(NewArg.m_aValue, pArgStart, minimum((int)sizeof(SCurrentSettingArg::m_aValue), Length + 1));
|
||||
|
||||
// Validate argument from the parsed argument of the current setting.
|
||||
// If current setting is not valid, then there are no arguments which results in an error.
|
||||
|
@ -1365,26 +1406,26 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
str_copy(NewArg.m_aValue, pArgStart, Length + 1);
|
||||
}
|
||||
|
||||
if(!Valid || (Char != '"' && !ValidateStr(NewArg.m_aValue)))
|
||||
Error = ERROR_INVALID_VALUE;
|
||||
if(!Valid)
|
||||
Error = SCommandParseError::ERROR_INVALID_VALUE;
|
||||
}
|
||||
else if(Arg.m_Type == 'i')
|
||||
{
|
||||
// Validate int
|
||||
if(!str_toint(NewArg.m_aValue, nullptr))
|
||||
Error = ERROR_INVALID_VALUE;
|
||||
Error = SCommandParseError::ERROR_INVALID_VALUE;
|
||||
}
|
||||
else if(Arg.m_Type == 'f')
|
||||
{
|
||||
// Validate float
|
||||
if(!str_tofloat(NewArg.m_aValue, nullptr))
|
||||
Error = ERROR_INVALID_VALUE;
|
||||
Error = SCommandParseError::ERROR_INVALID_VALUE;
|
||||
}
|
||||
else if(Arg.m_Type == 's')
|
||||
{
|
||||
// Validate string
|
||||
if(!Valid)
|
||||
Error = ERROR_INVALID_VALUE;
|
||||
if(!Valid || (Char != '"' && !ValidateStr(NewArg.m_aValue)))
|
||||
Error = SCommandParseError::ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
// Extended argument validation:
|
||||
|
@ -1394,30 +1435,45 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
if(Length && !Error && Result != EValidationResult::VALID)
|
||||
{
|
||||
if(Result == EValidationResult::ERROR)
|
||||
Error = ERROR_INVALID_VALUE; // Invalid argument value (invalid int, invalid float)
|
||||
Error = SCommandParseError::ERROR_INVALID_VALUE; // Invalid argument value (invalid int, invalid float)
|
||||
else if(Result == EValidationResult::UNKNOWN)
|
||||
Error = ERROR_UNKNOWN_VALUE; // Unknown argument value
|
||||
Error = SCommandParseError::ERROR_UNKNOWN_VALUE; // Unknown argument value
|
||||
else if(Result == EValidationResult::INCOMPLETE)
|
||||
Error = ERROR_INCOMPLETE; // Incomplete argument in case of possible values
|
||||
Error = SCommandParseError::ERROR_INCOMPLETE; // Incomplete argument in case of possible values
|
||||
else if(Result == EValidationResult::OUT_OF_RANGE)
|
||||
Error = ERROR_OUT_OF_RANGE; // Out of range argument value in case of int settings
|
||||
Error = SCommandParseError::ERROR_OUT_OF_RANGE; // Out of range argument value in case of int settings
|
||||
else
|
||||
Error = ERROR_UNKNOWN; // Unknown error
|
||||
Error = SCommandParseError::ERROR_UNKNOWN; // Unknown error
|
||||
}
|
||||
|
||||
Type = Arg.m_Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error: too many arguments
|
||||
Error = ERROR_TOO_MANY_ARGS;
|
||||
// Error: too many arguments if no comment after
|
||||
if(m_CommentOffset == -1)
|
||||
Error = SCommandParseError::ERROR_TOO_MANY_ARGS;
|
||||
else
|
||||
{ // Otherwise, check if there are any arguments left between this argument and the comment
|
||||
const char *pSubIt = pArgStart;
|
||||
pSubIt = str_skip_whitespaces_const(pSubIt);
|
||||
if(*pSubIt != '\0')
|
||||
{ // If there aren't only spaces between the last argument and the comment, then this is an error
|
||||
Error = SCommandParseError::ERROR_TOO_MANY_ARGS;
|
||||
}
|
||||
else // If there are, then just exit the loop to avoid getting an error
|
||||
{
|
||||
m_vCurrentArgs.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill argument informations
|
||||
NewArg.m_X = PosX;
|
||||
NewArg.m_Start = Offset;
|
||||
NewArg.m_End = Offset + Length;
|
||||
NewArg.m_Error = Error != ERROR_NONE || Length == 0;
|
||||
NewArg.m_Error = Error != SCommandParseError::ERROR_NONE || Length == 0 || m_Error.m_Type != SCommandParseError::ERROR_NONE;
|
||||
NewArg.m_ExpectedType = Type;
|
||||
|
||||
// Do not emit an error if we allow unknown commands and the current setting is invalid
|
||||
|
@ -1425,39 +1481,55 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
NewArg.m_Error = false;
|
||||
|
||||
// Check error and fill the error field with different messages
|
||||
if(Error == ERROR_INVALID_VALUE || Error == ERROR_UNKNOWN_VALUE || Error == ERROR_OUT_OF_RANGE)
|
||||
if(Error == SCommandParseError::ERROR_INVALID_VALUE || Error == SCommandParseError::ERROR_UNKNOWN_VALUE || Error == SCommandParseError::ERROR_OUT_OF_RANGE || Error == SCommandParseError::ERROR_INCOMPLETE)
|
||||
{
|
||||
size_t ErrorArgIndex = m_vCurrentArgs.size() - 1;
|
||||
SCurrentSettingArg &ErrorArg = m_vCurrentArgs.back();
|
||||
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)
|
||||
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
|
||||
{
|
||||
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]", aFormattedValue, (int)ErrorArg.m_Start, SettingArg.m_aName, pSettingInt->m_Min, pSettingInt->m_Max);
|
||||
}
|
||||
m_Error.m_ArgIndex = (int)ErrorArgIndex;
|
||||
break;
|
||||
}
|
||||
else if(Error == ERROR_TOO_MANY_ARGS)
|
||||
{
|
||||
if(m_pCurrentSetting != nullptr)
|
||||
{
|
||||
str_copy(m_Error.m_aMessage, "Too many arguments");
|
||||
m_Error.m_ArgIndex = ArgIndex;
|
||||
break;
|
||||
}
|
||||
else if(!m_AllowUnknownCommands)
|
||||
// Only keep first error
|
||||
if(!m_Error.m_aMessage[0])
|
||||
{
|
||||
int ErrorArgIndex = (int)m_vCurrentArgs.size() - 1;
|
||||
SCurrentSettingArg &ErrorArg = m_vCurrentArgs.back();
|
||||
SParsedMapSettingArg &SettingArg = m_pBackend->m_ParsedCommandArgs[m_pCurrentSetting].at(ArgIndex);
|
||||
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;
|
||||
break;
|
||||
FormatDisplayValue(ErrorArg.m_aValue, aFormattedValue);
|
||||
|
||||
if(Error == SCommandParseError::ERROR_INVALID_VALUE || Error == SCommandParseError::ERROR_UNKNOWN_VALUE || Error == SCommandParseError::ERROR_INCOMPLETE)
|
||||
{
|
||||
static const std::map<int, const char *> s_Names = {
|
||||
{SCommandParseError::ERROR_INVALID_VALUE, "Invalid"},
|
||||
{SCommandParseError::ERROR_UNKNOWN_VALUE, "Unknown"},
|
||||
{SCommandParseError::ERROR_INCOMPLETE, "Incomplete"},
|
||||
};
|
||||
str_format(m_Error.m_aMessage, sizeof(m_Error.m_aMessage), "%s argument value: %s at position %d for argument '%s'", s_Names.at(Error), aFormattedValue, (int)ErrorArg.m_Start, SettingArg.m_aName);
|
||||
}
|
||||
else
|
||||
{
|
||||
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]", aFormattedValue, (int)ErrorArg.m_Start, SettingArg.m_aName, pSettingInt->m_Min, pSettingInt->m_Max);
|
||||
}
|
||||
m_Error.m_ArgIndex = ErrorArgIndex;
|
||||
m_Error.m_Type = Error;
|
||||
}
|
||||
}
|
||||
else if(Error == SCommandParseError::ERROR_TOO_MANY_ARGS)
|
||||
{
|
||||
// Only keep first error
|
||||
if(!m_Error.m_aMessage[0])
|
||||
{
|
||||
if(m_pCurrentSetting != nullptr)
|
||||
{
|
||||
str_copy(m_Error.m_aMessage, "Too many arguments");
|
||||
m_Error.m_ArgIndex = ArgIndex;
|
||||
break;
|
||||
}
|
||||
else if(!m_AllowUnknownCommands)
|
||||
{
|
||||
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;
|
||||
break;
|
||||
}
|
||||
m_Error.m_Type = Error;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1469,6 +1541,7 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c
|
|||
void CMapSettingsBackend::CContext::ClearError()
|
||||
{
|
||||
m_Error.m_aMessage[0] = '\0';
|
||||
m_Error.m_Type = SCommandParseError::ERROR_NONE;
|
||||
}
|
||||
|
||||
bool CMapSettingsBackend::CContext::UpdateCursor(bool Force)
|
||||
|
@ -1490,23 +1563,37 @@ bool CMapSettingsBackend::CContext::UpdateCursor(bool Force)
|
|||
int NewArg = m_CursorArgIndex;
|
||||
|
||||
// Update current argument under cursor
|
||||
bool FoundArg = false;
|
||||
for(int i = (int)m_vCurrentArgs.size() - 1; i >= 0; i--)
|
||||
if(m_CommentOffset != -1 && Offset >= (size_t)m_CommentOffset)
|
||||
{
|
||||
if(Offset >= m_vCurrentArgs[i].m_Start)
|
||||
{
|
||||
NewArg = i;
|
||||
FoundArg = true;
|
||||
break;
|
||||
}
|
||||
NewArg = (int)m_vCurrentArgs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool FoundArg = false;
|
||||
for(int i = (int)m_vCurrentArgs.size() - 1; i >= 0; i--)
|
||||
{
|
||||
if(Offset >= m_vCurrentArgs[i].m_Start)
|
||||
{
|
||||
NewArg = i;
|
||||
FoundArg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!FoundArg)
|
||||
NewArg = -1;
|
||||
if(!FoundArg)
|
||||
NewArg = -1;
|
||||
}
|
||||
|
||||
bool ShouldUpdate = NewArg != m_CursorArgIndex;
|
||||
m_CursorArgIndex = NewArg;
|
||||
|
||||
// Do not show error if current argument is incomplete, as we are editing it
|
||||
if(m_pLineInput != nullptr)
|
||||
{
|
||||
if(Offset == m_pLineInput->GetLength() && m_Error.m_aMessage[0] && m_Error.m_ArgIndex == m_CursorArgIndex && m_Error.m_Type == SCommandParseError::ERROR_INCOMPLETE)
|
||||
ClearError();
|
||||
}
|
||||
|
||||
if(m_DropdownContext.m_Selected == -1 || ShouldUpdate || Force)
|
||||
{
|
||||
// Update possible commands from cursor
|
||||
|
@ -1552,15 +1639,13 @@ EValidationResult CMapSettingsBackend::CContext::ValidateArg(int Index, const ch
|
|||
// If equals, then argument is valid
|
||||
if(EqualsAny)
|
||||
return EValidationResult::VALID;
|
||||
else if(Index != m_CursorArgIndex)
|
||||
return EValidationResult::ERROR; // If we're not checking current argument, this is an error
|
||||
|
||||
// Here we check if argument is incomplete
|
||||
const bool StartsAny = std::any_of(ValuesIt->second.begin(), ValuesIt->second.end(), [pArg](auto *pValue) { return str_startswith_nocase(pValue, pArg) != nullptr; });
|
||||
if(StartsAny)
|
||||
return EValidationResult::INCOMPLETE;
|
||||
|
||||
return EValidationResult::ERROR;
|
||||
return EValidationResult::UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1587,12 +1672,15 @@ void CMapSettingsBackend::CContext::UpdatePossibleMatches()
|
|||
m_vPossibleMatches.clear();
|
||||
m_DropdownContext.m_Selected = -1;
|
||||
|
||||
if(m_CommentOffset == 0)
|
||||
return;
|
||||
|
||||
// First case: argument index under cursor is -1 => we're on the command/setting name
|
||||
if(m_CursorArgIndex == -1)
|
||||
{
|
||||
// Use a substring from the start of the input to the cursor offset
|
||||
char aSubString[128];
|
||||
str_copy(aSubString, m_aCommand, minimum((int)m_LastCursorOffset + 1, 128));
|
||||
str_copy(aSubString, m_aCommand, minimum(m_LastCursorOffset + 1, sizeof(aSubString)));
|
||||
|
||||
// Iterate through available map settings and find those which the beginning matches with the command/setting name we are writing
|
||||
for(auto &pSetting : m_pBackend->m_vpMapSettings)
|
||||
|
@ -1650,7 +1738,7 @@ void CMapSettingsBackend::CContext::UpdatePossibleMatches()
|
|||
int SubstringLength = minimum(m_LastCursorOffset, CurrentArg.m_End) - CurrentArg.m_Start;
|
||||
|
||||
// Substring based on the cursor position inside that argument
|
||||
char aSubString[160];
|
||||
char aSubString[256];
|
||||
str_copy(aSubString, CurrentArg.m_aValue, SubstringLength + 1);
|
||||
|
||||
for(auto &pValue : ValuesIt->second)
|
||||
|
@ -1709,6 +1797,7 @@ const char *CMapSettingsBackend::CContext::InputString() const
|
|||
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_ArgumentUnknownColor = ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
const ColorRGBA CMapSettingsBackend::CContext::ms_CommentColor = ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
const ColorRGBA CMapSettingsBackend::CContext::ms_ErrorColor = ColorRGBA(240 / 255.0f, 70 / 255.0f, 70 / 255.0f, 1.0f);
|
||||
|
||||
void CMapSettingsBackend::CContext::ColorArguments(std::vector<STextColorSplit> &vColorSplits) const
|
||||
|
@ -1735,17 +1824,27 @@ void CMapSettingsBackend::CContext::ColorArguments(std::vector<STextColorSplit>
|
|||
|
||||
if(m_pLineInput && !m_pLineInput->IsEmpty())
|
||||
{
|
||||
if(!CommandIsValid() && !m_AllowUnknownCommands)
|
||||
if(!CommandIsValid() && !m_AllowUnknownCommands && m_CommentOffset != 0)
|
||||
{
|
||||
// If command is invalid, override color splits with red
|
||||
vColorSplits = {{0, -1, ms_ErrorColor}};
|
||||
// If command is invalid, override color splits with red, but not comment
|
||||
int ErrorLength = m_CommentOffset == -1 ? -1 : m_CommentOffset;
|
||||
vColorSplits = {{0, ErrorLength, ms_ErrorColor}};
|
||||
}
|
||||
else if(HasError())
|
||||
{
|
||||
// If there is an error, then color the wrong part of the input
|
||||
vColorSplits.emplace_back(ErrorOffset(), -1, ms_ErrorColor);
|
||||
// If there is an error, then color the wrong part of the input, excluding comment
|
||||
int ErrorLength = m_CommentOffset == -1 ? -1 : m_CommentOffset - ErrorOffset();
|
||||
vColorSplits.emplace_back(ErrorOffset(), ErrorLength, ms_ErrorColor);
|
||||
}
|
||||
if(m_CommentOffset != -1)
|
||||
{ // Color comment if there is one
|
||||
vColorSplits.emplace_back(m_CommentOffset, -1, ms_CommentColor);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(vColorSplits.begin(), vColorSplits.end(), [](const STextColorSplit &a, const STextColorSplit &b) {
|
||||
return a.m_CharIndex < b.m_CharIndex;
|
||||
});
|
||||
}
|
||||
|
||||
int CMapSettingsBackend::CContext::CheckCollision(ECollisionCheckResult &Result) const
|
||||
|
@ -1768,6 +1867,12 @@ int CMapSettingsBackend::CContext::CheckCollision(const char *pInputString, cons
|
|||
// 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.
|
||||
|
||||
if(m_CommentOffset == 0)
|
||||
{ // Ignore comments
|
||||
Result = ECollisionCheckResult::ADD;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int InputLength = str_length(pInputString);
|
||||
|
||||
struct SArgument
|
||||
|
@ -1790,7 +1895,7 @@ int CMapSettingsBackend::CContext::CheckCollision(const char *pInputString, cons
|
|||
std::vector<SArgument> vaArgs;
|
||||
const char *pIt = pStr;
|
||||
char aBuffer[128];
|
||||
while((pIt = str_next_token(pIt, " ", aBuffer, 128)))
|
||||
while((pIt = str_next_token(pIt, " ", aBuffer, sizeof(aBuffer))))
|
||||
vaArgs.emplace_back(aBuffer);
|
||||
return vaArgs;
|
||||
};
|
||||
|
@ -1942,6 +2047,9 @@ bool CMapSettingsBackend::CContext::Valid() const
|
|||
{
|
||||
// Check if the entire setting is valid or not
|
||||
|
||||
if(m_CommentOffset == 0)
|
||||
return true; // A "comment" setting is considered valid.
|
||||
|
||||
// Check if command is valid
|
||||
if(m_pCurrentSetting)
|
||||
{
|
||||
|
@ -2082,7 +2190,7 @@ void CMapSettingsBackend::OnMapLoad()
|
|||
if(Result == ECollisionCheckResult::REPLACE)
|
||||
Type |= SInvalidSetting::TYPE_DUPLICATE;
|
||||
|
||||
m_LoadedMapSettings.m_vSettingsInvalid.emplace_back(Index, Setting.m_aCommand, Type, RealCollidingLineIndex);
|
||||
m_LoadedMapSettings.m_vSettingsInvalid.emplace_back(Index, Setting.m_aCommand, Type, RealCollidingLineIndex, !LocalContext.CommandIsValid());
|
||||
if(Type & SInvalidSetting::TYPE_DUPLICATE)
|
||||
m_LoadedMapSettings.m_SettingsDuplicate[RealCollidingLineIndex].emplace_back(m_LoadedMapSettings.m_vSettingsInvalid.size() - 1);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ struct SParsedMapSettingArg
|
|||
// An argument for the current setting
|
||||
struct SCurrentSettingArg
|
||||
{
|
||||
char m_aValue[160]; // Value of the argument
|
||||
char m_aValue[256]; // 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
|
||||
|
@ -54,6 +54,18 @@ struct SCommandParseError
|
|||
{
|
||||
char m_aMessage[256];
|
||||
int m_ArgIndex;
|
||||
|
||||
enum EErrorType
|
||||
{
|
||||
ERROR_NONE = 0,
|
||||
ERROR_TOO_MANY_ARGS,
|
||||
ERROR_INVALID_VALUE,
|
||||
ERROR_UNKNOWN_VALUE,
|
||||
ERROR_INCOMPLETE,
|
||||
ERROR_OUT_OF_RANGE,
|
||||
ERROR_UNKNOWN
|
||||
};
|
||||
EErrorType m_Type;
|
||||
};
|
||||
|
||||
struct SInvalidSetting
|
||||
|
@ -67,6 +79,7 @@ struct SInvalidSetting
|
|||
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
|
||||
bool m_Unknown; // Is the command unknown
|
||||
|
||||
struct SContext
|
||||
{
|
||||
|
@ -75,8 +88,8 @@ struct SInvalidSetting
|
|||
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()
|
||||
SInvalidSetting(int Index, const char *pSetting, int Type, int CollidingIndex, bool Unknown) :
|
||||
m_Index(Index), m_Type(Type), m_CollidingIndex(CollidingIndex), m_Unknown(Unknown), m_Context()
|
||||
{
|
||||
str_copy(m_aSetting, pSetting);
|
||||
}
|
||||
|
@ -215,6 +228,7 @@ public: // CContext
|
|||
static const ColorRGBA ms_ArgumentStringColor;
|
||||
static const ColorRGBA ms_ArgumentNumberColor;
|
||||
static const ColorRGBA ms_ArgumentUnknownColor;
|
||||
static const ColorRGBA ms_CommentColor;
|
||||
static const ColorRGBA ms_ErrorColor;
|
||||
|
||||
friend class CMapSettingsBackend;
|
||||
|
@ -223,7 +237,7 @@ public: // CContext
|
|||
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; }
|
||||
float CurrentArgPos() const { return (m_CursorArgIndex < 0 || m_CursorArgIndex >= (int)m_vCurrentArgs.size()) ? 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; }
|
||||
|
@ -235,6 +249,7 @@ public: // CContext
|
|||
const std::shared_ptr<IMapSetting> &Setting() const { return m_pCurrentSetting; }
|
||||
CLineInput *LineInput() const { return m_pLineInput; }
|
||||
void SetFontSize(float FontSize) { m_FontSize = FontSize; }
|
||||
int CommentOffset() const { return m_CommentOffset; };
|
||||
|
||||
int CheckCollision(ECollisionCheckResult &Result) const;
|
||||
int CheckCollision(const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const;
|
||||
|
@ -280,6 +295,7 @@ public: // CContext
|
|||
CLineInput *m_pLineInput;
|
||||
char m_aCommand[128]; // The current command, not necessarily valid
|
||||
SCommandParseError m_Error; // Error
|
||||
int m_CommentOffset; // Position of the comment, if there is one
|
||||
|
||||
CMapSettingsBackend *m_pBackend;
|
||||
std::string m_CompositionStringBuffer;
|
||||
|
|
|
@ -12,6 +12,7 @@ struct SEditBoxDropdownContext
|
|||
bool m_DidBecomeVisible = false;
|
||||
bool m_MousePressedInside = false;
|
||||
bool m_ShouldHide = false;
|
||||
int m_Width = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue