6cba64f226
* Fix issue with debug console ignoring some keys This problem was reported for Server only, the Enter/Backspace keys were being ignored in the ImGui Debug Console. This wasn't an issue if the Server had loaded a map. The problem was with XConsole explicitly setting a bool in dedicated server mode. This caused text input to be processed by XConsole code and not passed further along to DebugConsole where it should have been handling it via ImGui. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> * Fix missing runtime dependency of ServerLauncher ServerLauncher in non-monolithic config was missing a runtime dependency on Legacy::CrySystem. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>
3235 lines
94 KiB
C++
3235 lines
94 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
// Description : implementation of the CXConsole class.
|
|
|
|
|
|
#include "CrySystem_precompiled.h"
|
|
#include "XConsole.h"
|
|
#include "XConsoleVariable.h"
|
|
#include "System.h"
|
|
#include "ConsoleBatchFile.h"
|
|
|
|
#include <ITimer.h>
|
|
#include <IRenderer.h>
|
|
#include <ISystem.h>
|
|
#include <ILog.h>
|
|
#include <IFont.h>
|
|
#include <ITexture.h>
|
|
#include "ConsoleHelpGen.h" // CConsoleHelpGen
|
|
|
|
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
|
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
|
#include <AzCore/std/string/conversions.h>
|
|
#include <AzCore/std/algorithm.h>
|
|
|
|
#include <LyShine/Bus/UiCursorBus.h>
|
|
//#define DEFENCE_CVAR_HASH_LOGGING
|
|
|
|
// s should point to a buffer at least 65 chars long
|
|
inline void BitsAlpha64(uint64 n, char* s)
|
|
{
|
|
for (int i = 0; n != 0; n >>= 1, i++)
|
|
{
|
|
if (n & 1)
|
|
{
|
|
*s++ = i < 32 ? static_cast<char>(i + 'z' - 31) : static_cast<char>(i + 'Z' - 63);
|
|
}
|
|
}
|
|
*s++ = '\0';
|
|
}
|
|
|
|
static inline void AssertName([[maybe_unused]] const char* szName)
|
|
{
|
|
#ifdef _DEBUG
|
|
assert(szName);
|
|
|
|
// test for good console variable / command name
|
|
const char* p = szName;
|
|
bool bFirstChar = true;
|
|
|
|
while (*p)
|
|
{
|
|
assert((*p >= 'a' && *p <= 'z')
|
|
|| (*p >= 'A' && *p <= 'Z')
|
|
|| (*p >= '0' && *p <= '9' && !bFirstChar)
|
|
|| *p == '_'
|
|
|| *p == '.');
|
|
|
|
++p;
|
|
bFirstChar = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ResetCVars(IConsoleCmdArgs*)
|
|
{
|
|
if (gEnv->pSystem)
|
|
{
|
|
CXConsole* pConsole = static_cast<CXConsole*>(gEnv->pSystem->GetIConsole());
|
|
if (pConsole)
|
|
{
|
|
pConsole->ResetCVarsToDefaults();
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// user defined comparison - for nicer printout
|
|
inline int GetCharPrio(char x)
|
|
{
|
|
if (x >= 'a' && x <= 'z')
|
|
{
|
|
x += 'A' - 'a'; // make upper case
|
|
}
|
|
if (x == '_')
|
|
{
|
|
return 300;
|
|
}
|
|
else
|
|
{
|
|
return x;
|
|
}
|
|
}
|
|
|
|
void Command_SetWaitSeconds(IConsoleCmdArgs* pCmd)
|
|
{
|
|
CXConsole* pConsole = (CXConsole*)gEnv->pConsole;
|
|
|
|
if (pCmd->GetArgCount() > 1)
|
|
{
|
|
pConsole->m_waitSeconds.SetSeconds(atof(pCmd->GetArg(1)));
|
|
pConsole->m_waitSeconds += gEnv->pTimer->GetFrameStartTime();
|
|
}
|
|
}
|
|
|
|
void Command_SetWaitFrames(IConsoleCmdArgs* pCmd)
|
|
{
|
|
CXConsole* pConsole = (CXConsole*)gEnv->pConsole;
|
|
|
|
if (pCmd->GetArgCount() > 1)
|
|
{
|
|
pConsole->m_waitFrames = max(0, atoi(pCmd->GetArg(1)));
|
|
}
|
|
}
|
|
|
|
void ConsoleShow(IConsoleCmdArgs*)
|
|
{
|
|
gEnv->pConsole->ShowConsole(true);
|
|
}
|
|
void ConsoleHide(IConsoleCmdArgs*)
|
|
{
|
|
gEnv->pConsole->ShowConsole(false);
|
|
}
|
|
|
|
void Bind(IConsoleCmdArgs* cmdArgs)
|
|
{
|
|
if (cmdArgs->GetArgCount() >= 3)
|
|
{
|
|
AZStd::string arg;
|
|
for (int i = 2; i < cmdArgs->GetArgCount(); ++i)
|
|
{
|
|
arg += cmdArgs->GetArg(i);
|
|
arg += " ";
|
|
}
|
|
gEnv->pConsole->CreateKeyBind(cmdArgs->GetArg(1), arg.c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CXConsole::con_display_last_messages = 0;
|
|
int CXConsole::con_line_buffer_size = 500;
|
|
int CXConsole::con_showonload = 0;
|
|
int CXConsole::con_debug = 0;
|
|
int CXConsole::con_restricted = 0;
|
|
|
|
namespace
|
|
{
|
|
const AzFramework::InputChannelId s_nullRepeatEventId("xconsole_null_repeat_event_id");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
CXConsole::CXConsole()
|
|
: AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDebug())
|
|
, AzFramework::InputTextEventListener(AzFramework::InputTextEventListener::GetPriorityDebug())
|
|
, m_nRepeatEventId(s_nullRepeatEventId)
|
|
{
|
|
m_fRepeatTimer = 0;
|
|
m_pSysDeactivateConsole = 0;
|
|
m_pFont = NULL;
|
|
m_pImage = NULL;
|
|
m_nCursorPos = 0;
|
|
m_nScrollPos = 0;
|
|
m_nScrollMax = 300;
|
|
m_nTempScrollMax = m_nScrollMax;
|
|
m_nScrollLine = 0;
|
|
m_nHistoryPos = -1;
|
|
m_nTabCount = 0;
|
|
m_bConsoleActive = false;
|
|
m_bActivationKeyEnable = true;
|
|
m_bIsProcessingGroup = false;
|
|
m_bIsConsoleKeyPressed = false;
|
|
m_sdScrollDir = sdNONE;
|
|
m_pSystem = NULL;
|
|
m_bDrawCursor = true;
|
|
m_fCursorBlinkTimer = 0;
|
|
|
|
m_nCheatHashRangeFirst = 0;
|
|
m_nCheatHashRangeLast = 0;
|
|
m_bCheatHashDirty = false;
|
|
m_nCheatHash = 0;
|
|
|
|
m_bStaticBackground = false;
|
|
m_nProgress = 0;
|
|
m_nProgressRange = 0;
|
|
m_nLoadingBackTexID = 0;
|
|
|
|
m_deferredExecution = false;
|
|
m_waitFrames = 0;
|
|
m_waitSeconds = 0.0f;
|
|
m_blockCounter = 0;
|
|
|
|
AzFramework::ConsoleRequestBus::Handler::BusConnect();
|
|
AzFramework::CommandRegistrationBus::Handler::BusConnect();
|
|
|
|
AddCommand("resetcvars", (ConsoleCommandFunc)ResetCVars, 0, "Resets all cvars to their initial values");
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CXConsole::~CXConsole()
|
|
{
|
|
AzFramework::ConsoleRequestBus::Handler::BusDisconnect();
|
|
AzFramework::CommandRegistrationBus::Handler::BusDisconnect();
|
|
|
|
if (gEnv->pSystem)
|
|
{
|
|
gEnv->pSystem->GetIRemoteConsole()->UnregisterListener(this);
|
|
}
|
|
|
|
if (!m_mapVariables.empty())
|
|
{
|
|
while (!m_mapVariables.empty())
|
|
{
|
|
m_mapVariables.begin()->second->Release();
|
|
}
|
|
|
|
m_mapVariables.clear();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::FreeRenderResources()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Release()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
|
|
#if ALLOW_AUDIT_CVARS
|
|
void Command_AuditCVars(IConsoleCmdArgs* pArg)
|
|
{
|
|
CXConsole* pConsole = (CXConsole*)gEnv->pConsole;
|
|
if (pConsole != NULL)
|
|
{
|
|
pConsole->AuditCVars(pArg);
|
|
}
|
|
}
|
|
#endif // ALLOW_AUDIT_CVARS
|
|
|
|
|
|
|
|
#if !defined(_RELEASE) && !defined(LINUX) && !defined(APPLE)
|
|
void Command_DumpCommandsVars(IConsoleCmdArgs* Cmd)
|
|
{
|
|
const char* arg = "";
|
|
|
|
if (Cmd->GetArgCount() > 1)
|
|
{
|
|
arg = Cmd->GetArg(1);
|
|
}
|
|
|
|
CXConsole* pConsole = (CXConsole*)gEnv->pConsole;
|
|
|
|
// txt
|
|
pConsole->DumpCommandsVarsTxt(arg);
|
|
|
|
#if defined(WIN32) || defined(WIN64)
|
|
// HTML
|
|
{
|
|
CConsoleHelpGen Generator(*pConsole);
|
|
|
|
Generator.Work();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Command_DumpVars(IConsoleCmdArgs* Cmd)
|
|
{
|
|
bool includeCheat = false;
|
|
|
|
if (Cmd->GetArgCount() > 1)
|
|
{
|
|
const char* arg = Cmd->GetArg(1);
|
|
int incCheat = atoi(arg);
|
|
if (incCheat == 1)
|
|
{
|
|
includeCheat = true;
|
|
}
|
|
}
|
|
|
|
CXConsole* pConsole = (CXConsole*)gEnv->pConsole;
|
|
|
|
// txt
|
|
pConsole->DumpVarsTxt(includeCheat);
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Init(ISystem* pSystem)
|
|
{
|
|
m_pSystem = static_cast<CSystem*>(pSystem);
|
|
if (pSystem->GetICryFont())
|
|
{
|
|
m_pFont = pSystem->GetICryFont()->GetFont("default");
|
|
}
|
|
m_pTimer = pSystem->GetITimer();
|
|
|
|
AzFramework::InputChannelEventListener::Connect();
|
|
AzFramework::InputTextEventListener::Connect();
|
|
|
|
#if defined(_RELEASE)
|
|
static const int kDeactivateConsoleDefault = 1;
|
|
#else
|
|
static const int kDeactivateConsoleDefault = 0;
|
|
#endif
|
|
m_pSysDeactivateConsole = REGISTER_INT("sys_DeactivateConsole", kDeactivateConsoleDefault, 0,
|
|
"0: normal console behavior\n"
|
|
"1: hide the console");
|
|
|
|
REGISTER_CVAR(con_display_last_messages, 0, VF_NULL, ""); // keep default at 1, needed for gameplay
|
|
REGISTER_CVAR(con_line_buffer_size, 1000, VF_NULL, "");
|
|
REGISTER_CVAR(con_showonload, 0, VF_NULL, "Show console on level loading");
|
|
REGISTER_CVAR(con_debug, 0, VF_CHEAT, "Log call stack on every GetCVar call");
|
|
REGISTER_CVAR(con_restricted, con_restricted, VF_RESTRICTEDMODE, "0=normal mode / 1=restricted access to the console"); // later on VF_RESTRICTEDMODE should be removed (to 0)
|
|
|
|
if (m_pSystem->IsDevMode() // unrestricted console for -DEVMODE
|
|
|| gEnv->IsDedicated()) // unrestricted console for dedicated server
|
|
{
|
|
con_restricted = 0;
|
|
}
|
|
|
|
m_nLoadingBackTexID = -1;
|
|
|
|
REGISTER_COMMAND("ConsoleShow", &ConsoleShow, VF_NULL, "Opens the console");
|
|
REGISTER_COMMAND("ConsoleHide", &ConsoleHide, VF_NULL, "Closes the console");
|
|
|
|
#if ALLOW_AUDIT_CVARS
|
|
REGISTER_COMMAND("audit_cvars", &Command_AuditCVars, VF_NULL, "Logs all console commands and cvars");
|
|
#endif // ALLOW_AUDIT_CVARS
|
|
|
|
#if !defined(_RELEASE) && !defined(LINUX) && !defined(APPLE)
|
|
REGISTER_COMMAND("DumpCommandsVars", &Command_DumpCommandsVars, VF_NULL,
|
|
"This console command dumps all console variables and commands to disk\n"
|
|
"DumpCommandsVars [prefix]");
|
|
REGISTER_COMMAND("DumpVars", &Command_DumpVars, VF_NULL,
|
|
"This console command dumps all console variables to disk\n"
|
|
"DumpVars [IncludeCheatCvars]");
|
|
#endif
|
|
|
|
REGISTER_COMMAND("Bind", &Bind, VF_NULL, "");
|
|
REGISTER_COMMAND("wait_seconds", &Command_SetWaitSeconds, VF_BLOCKFRAME,
|
|
"Forces the console to wait for a given number of seconds before the next deferred command is processed\n"
|
|
"Works only in deferred command mode");
|
|
REGISTER_COMMAND("wait_frames", &Command_SetWaitFrames, VF_BLOCKFRAME,
|
|
"Forces the console to wait for a given number of frames before the next deferred command is processed\n"
|
|
"Works only in deferred command mode");
|
|
|
|
CConsoleBatchFile::Init();
|
|
|
|
if (con_showonload)
|
|
{
|
|
ShowConsole(true);
|
|
}
|
|
|
|
pSystem->GetIRemoteConsole()->RegisterListener(this, "CXConsole");
|
|
}
|
|
|
|
void CXConsole::LogChangeMessage(const char* name, const bool isConst, const bool isCheat, const bool isReadOnly, const bool isDeprecated,
|
|
const char* oldValue, const char* newValue, [[maybe_unused]] const bool isProcessingGroup, const bool allowChange)
|
|
{
|
|
AZStd::string logMessage = AZStd::string::format
|
|
("[CVARS]: [%s] variable [%s] from [%s] to [%s]%s; Marked as%s%s%s%s",
|
|
(allowChange) ? "CHANGED" : "IGNORED CHANGE",
|
|
name,
|
|
oldValue,
|
|
newValue,
|
|
(m_bIsProcessingGroup) ? " as part of a cvar group" : "",
|
|
(isConst) ? " [VF_CONST_CVAR]" : "",
|
|
(isCheat) ? " [VF_CHEAT]" : "",
|
|
(isReadOnly) ? " [VF_READONLY]" : "",
|
|
(isDeprecated) ? " [VF_DEPRECATED]" : "");
|
|
|
|
if (allowChange)
|
|
{
|
|
gEnv->pLog->LogWarning("%s", logMessage.c_str());
|
|
gEnv->pLog->LogWarning("Modifying marked variables will not be allowed in Release mode!");
|
|
}
|
|
else
|
|
{
|
|
gEnv->pLog->LogError("%s", logMessage.c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RegisterVar(ICVar* pCVar, ConsoleVarFunc pChangeFunc)
|
|
{
|
|
// first register callback so setting the value from m_configVars
|
|
// is calling pChangeFunc (that would be more correct but to not introduce new problems this code was not changed)
|
|
// if (pChangeFunc)
|
|
// pCVar->SetOnChangeCallback(pChangeFunc);
|
|
|
|
bool isConst = pCVar->IsConstCVar();
|
|
bool isCheat = ((pCVar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0);
|
|
bool isReadOnly = ((pCVar->GetFlags() & VF_READONLY) != 0);
|
|
bool isDeprecated = ((pCVar->GetFlags() & VF_DEPRECATED) != 0);
|
|
|
|
ConfigVars::iterator it = m_configVars.find(pCVar->GetName());
|
|
if (it != m_configVars.end())
|
|
{
|
|
SConfigVar& var = it->second;
|
|
bool allowChange = true;
|
|
bool wasProcessingGroup = GetIsProcessingGroup();
|
|
SetProcessingGroup(var.m_partOfGroup);
|
|
|
|
if (
|
|
#if CVAR_GROUPS_ARE_PRIVILEGED
|
|
!m_bIsProcessingGroup &&
|
|
#endif // !CVAR_GROUPS_ARE_PRIVILEGED
|
|
(isConst || isCheat || isReadOnly || isDeprecated))
|
|
{
|
|
allowChange = !isDeprecated && ((gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor()));
|
|
if ((strcmp(pCVar->GetString(), var.m_value.c_str()) != 0) && (!(gEnv->IsEditor()) || isDeprecated))
|
|
{
|
|
#if LOG_CVAR_INFRACTIONS
|
|
LogChangeMessage(pCVar->GetName(), isConst, isCheat,
|
|
isReadOnly, isDeprecated, pCVar->GetString(), var.m_value.c_str(), m_bIsProcessingGroup, allowChange);
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
#endif // LOG_CVAR_INFRACTIONS
|
|
}
|
|
}
|
|
|
|
if (allowChange || ALLOW_CONST_CVAR_MODIFICATIONS)
|
|
{
|
|
pCVar->Set(var.m_value.c_str());
|
|
pCVar->SetFlags(pCVar->GetFlags() | VF_WASINCONFIG);
|
|
}
|
|
|
|
SetProcessingGroup(wasProcessingGroup);
|
|
}
|
|
else
|
|
{
|
|
// Variable is not modified when just registered.
|
|
pCVar->ClearFlags(VF_MODIFIED);
|
|
}
|
|
|
|
if (pChangeFunc)
|
|
{
|
|
pCVar->SetOnChangeCallback(pChangeFunc);
|
|
}
|
|
|
|
ConsoleVariablesMapItor::value_type value = ConsoleVariablesMapItor::value_type(pCVar->GetName(), pCVar);
|
|
|
|
m_mapVariables.insert(value);
|
|
|
|
int flags = pCVar->GetFlags();
|
|
|
|
if (flags & VF_CHEAT_ALWAYS_CHECK)
|
|
{
|
|
AddCheckedCVar(m_alwaysCheckedVariables, value);
|
|
}
|
|
else if ((flags & (VF_CHEAT | VF_CHEAT_NOCHECK)) == VF_CHEAT)
|
|
{
|
|
AddCheckedCVar(m_randomCheckedVariables, value);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddCheckedCVar(ConsoleVariablesVector& vector, const ConsoleVariablesVector::value_type& value)
|
|
{
|
|
ConsoleVariablesVector::iterator it = std::lower_bound(vector.begin(), vector.end(), value, CVarNameLess);
|
|
|
|
if ((it == vector.end()) || strcmp(it->first, value.first))
|
|
{
|
|
vector.insert(it, value);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::CVarNameLess(const std::pair<const char*, ICVar*>& lhs, const std::pair<const char*, ICVar*>& rhs)
|
|
{
|
|
return strcmp(lhs.first, rhs.first) < 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::LoadConfigVar(const char* sVariable, const char* sValue)
|
|
{
|
|
ICVar* pCVar = GetCVar(sVariable);
|
|
if (pCVar)
|
|
{
|
|
bool isConst = pCVar->IsConstCVar();
|
|
bool isCheat = ((pCVar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0);
|
|
bool isReadOnly = ((pCVar->GetFlags() & VF_READONLY) != 0);
|
|
bool isDeprecated = ((pCVar->GetFlags() & VF_DEPRECATED) != 0);
|
|
bool allowChange = true;
|
|
|
|
if (
|
|
#if CVAR_GROUPS_ARE_PRIVILEGED
|
|
!m_bIsProcessingGroup &&
|
|
#endif // !CVAR_GROUPS_ARE_PRIVILEGED
|
|
(isConst || isCheat || isReadOnly) || isDeprecated)
|
|
{
|
|
allowChange = !isDeprecated && (gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor());
|
|
if (!(gEnv->IsEditor()) || isDeprecated)
|
|
{
|
|
#if LOG_CVAR_INFRACTIONS
|
|
LogChangeMessage(pCVar->GetName(), isConst, isCheat,
|
|
isReadOnly, isDeprecated, pCVar->GetString(), sValue, m_bIsProcessingGroup, allowChange);
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
#endif // LOG_CVAR_INFRACTIONS
|
|
}
|
|
}
|
|
|
|
if (allowChange || ALLOW_CONST_CVAR_MODIFICATIONS)
|
|
{
|
|
pCVar->Set(sValue);
|
|
pCVar->SetFlags(pCVar->GetFlags() | VF_WASINCONFIG);
|
|
}
|
|
return;
|
|
}
|
|
|
|
SConfigVar temp;
|
|
temp.m_value = sValue;
|
|
temp.m_partOfGroup = m_bIsProcessingGroup;
|
|
|
|
m_configVars[sVariable] = temp;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::EnableActivationKey(bool bEnable)
|
|
{
|
|
m_bActivationKeyEnable = bEnable;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::SetClientDataProbeString(const char* pName, const char* pValue)
|
|
{
|
|
ICVar* pCVar = GetCVar(pName);
|
|
|
|
if (pCVar)
|
|
{
|
|
pCVar->SetDataProbeString(pValue);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::Register(const char* sName, int* src, int iValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc, bool allowModify)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->LogError("[CVARS]: [DUPLICATE] CXConsole::Register(int): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
|
|
if (!allowModify)
|
|
{
|
|
nFlags |= VF_CONST_CVAR;
|
|
}
|
|
pCVar = new CXConsoleVariableIntRef(this, sName, src, nFlags, help);
|
|
*src = iValue;
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::Register(const char* sName, float* src, float fValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc, bool allowModify)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->Log("[CVARS]: [DUPLICATE] CXConsole::Register(float): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
if (!allowModify)
|
|
{
|
|
nFlags |= VF_CONST_CVAR;
|
|
}
|
|
pCVar = new CXConsoleVariableFloatRef(this, sName, src, nFlags, help);
|
|
*src = fValue;
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::Register(const char* sName, const char** src, const char* defaultValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc, bool allowModify)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->Log("[CVARS]: [DUPLICATE] CXConsole::Register(const char*): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
if (!allowModify)
|
|
{
|
|
nFlags |= VF_CONST_CVAR;
|
|
}
|
|
pCVar = new CXConsoleVariableStringRef(this, sName, src, defaultValue, nFlags, help);
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::RegisterString(const char* sName, const char* sValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->Log("[CVARS]: [DUPLICATE] CXConsole::RegisterString(const char*): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
|
|
pCVar = new CXConsoleVariableString(this, sName, sValue, nFlags, help);
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::RegisterFloat(const char* sName, float fValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->Log("[CVARS]: [DUPLICATE] CXConsole::RegisterFloat(): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
|
|
pCVar = new CXConsoleVariableFloat(this, sName, fValue, nFlags, help);
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::RegisterInt(const char* sName, int iValue, int nFlags, const char* help, ConsoleVarFunc pChangeFunc)
|
|
{
|
|
AssertName(sName);
|
|
|
|
ICVar* pCVar = stl::find_in_map(m_mapVariables, sName, NULL);
|
|
if (pCVar)
|
|
{
|
|
gEnv->pLog->Log("[CVARS]: [DUPLICATE] CXConsole::RegisterInt(): variable [%s] is already registered", pCVar->GetName());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
return pCVar;
|
|
}
|
|
|
|
pCVar = new CXConsoleVariableInt(this, sName, iValue, nFlags, help);
|
|
RegisterVar(pCVar, pChangeFunc);
|
|
return pCVar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::UnregisterVariable(const char* sVarName, [[maybe_unused]] bool bDelete)
|
|
{
|
|
ConsoleVariablesMapItor itor;
|
|
itor = m_mapVariables.find(sVarName);
|
|
|
|
if (itor == m_mapVariables.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ICVar* pCVar = itor->second;
|
|
|
|
int32 flags = pCVar->GetFlags();
|
|
|
|
if (flags & VF_CHEAT_ALWAYS_CHECK)
|
|
{
|
|
RemoveCheckedCVar(m_alwaysCheckedVariables, *itor);
|
|
}
|
|
else if ((flags & (VF_CHEAT | VF_CHEAT_NOCHECK)) == VF_CHEAT)
|
|
{
|
|
RemoveCheckedCVar(m_randomCheckedVariables, *itor);
|
|
}
|
|
|
|
m_mapVariables.erase(sVarName);
|
|
|
|
delete pCVar;
|
|
}
|
|
|
|
void CXConsole::RemoveCheckedCVar(ConsoleVariablesVector& vector, const ConsoleVariablesVector::value_type& value)
|
|
{
|
|
ConsoleVariablesVector::iterator it = std::lower_bound(vector.begin(), vector.end(), value, CVarNameLess);
|
|
|
|
if ((it != vector.end()) && !strcmp(it->first, value.first))
|
|
{
|
|
vector.erase(it);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::SetScrollMax(int value)
|
|
{
|
|
m_nScrollMax = value;
|
|
m_nTempScrollMax = m_nScrollMax;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::SetImage(ITexture* pImage, bool bDeleteCurrent)
|
|
{
|
|
if (bDeleteCurrent)
|
|
{
|
|
pImage->Release();
|
|
}
|
|
|
|
m_pImage = pImage;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ShowConsole(bool show, const int iRequestScrollMax)
|
|
{
|
|
if (m_pSysDeactivateConsole->GetIVal())
|
|
{
|
|
show = false;
|
|
}
|
|
|
|
if (show && !m_bConsoleActive)
|
|
{
|
|
UiCursorBus::Broadcast(&UiCursorBus::Events::IncrementVisibleCounter);
|
|
|
|
AzFramework::InputSystemCursorRequestBus::EventResult(m_previousSystemCursorState,
|
|
AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::GetSystemCursorState);
|
|
AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::SetSystemCursorState,
|
|
AzFramework::SystemCursorState::UnconstrainedAndVisible);
|
|
}
|
|
else if (!show && m_bConsoleActive)
|
|
{
|
|
UiCursorBus::Broadcast(&UiCursorBus::Events::DecrementVisibleCounter);
|
|
|
|
AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::SetSystemCursorState,
|
|
m_previousSystemCursorState);
|
|
}
|
|
|
|
SetStatus(show);
|
|
|
|
if (iRequestScrollMax > 0)
|
|
{
|
|
m_nTempScrollMax = iRequestScrollMax; // temporary user request
|
|
}
|
|
else
|
|
{
|
|
m_nTempScrollMax = m_nScrollMax; // reset
|
|
}
|
|
if (m_bConsoleActive)
|
|
{
|
|
m_sdScrollDir = sdDOWN;
|
|
}
|
|
else
|
|
{
|
|
m_sdScrollDir = sdUP;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::CreateKeyBind(const char* sCmd, const char* sRes)
|
|
{
|
|
m_mapBinds.insert(ConsoleBindsMapItor::value_type(sCmd, sRes));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::DumpKeyBinds(IKeyBindDumpSink* pCallback)
|
|
{
|
|
for (ConsoleBindsMap::iterator it = m_mapBinds.begin(); it != m_mapBinds.end(); ++it)
|
|
{
|
|
pCallback->OnKeyBindFound(it->first.c_str(), it->second.c_str());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::FindKeyBind(const char* sCmd) const
|
|
{
|
|
ConsoleBindsMap::const_iterator it = m_mapBinds.find(sCmd);
|
|
|
|
if (it != m_mapBinds.end())
|
|
{
|
|
return it->second.c_str();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::DumpCVars(ICVarDumpSink* pCallback, unsigned int nFlagsFilter)
|
|
{
|
|
ConsoleVariablesMapItor It = m_mapVariables.begin();
|
|
|
|
while (It != m_mapVariables.end())
|
|
{
|
|
if ((nFlagsFilter == 0) || ((nFlagsFilter != 0) && (It->second->GetFlags() & nFlagsFilter)))
|
|
{
|
|
pCallback->OnElementFound(It->second);
|
|
}
|
|
++It;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ICVar* CXConsole::GetCVar(const char* sName)
|
|
{
|
|
assert(this);
|
|
assert(sName);
|
|
|
|
if (con_debug)
|
|
{
|
|
// Log call stack on get cvar.
|
|
CryLog("GetCVar(\"%s\") called", sName);
|
|
m_pSystem->debug_LogCallStack();
|
|
}
|
|
|
|
// Fast map lookup for case-sensitive match.
|
|
ConsoleVariablesMapItor it;
|
|
|
|
it = m_mapVariables.find(sName);
|
|
if (it != m_mapVariables.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
/*
|
|
if(!bCaseSensitive)
|
|
{
|
|
// Much slower but allows names with wrong case (use only where performance doesn't matter).
|
|
for(it=m_mapVariables.begin(); it!=m_mapVariables.end(); ++it)
|
|
{
|
|
if(azstricmp(it->first,sName)==0)
|
|
return it->second;
|
|
}
|
|
}
|
|
test else
|
|
{
|
|
for(it=m_mapVariables.begin(); it!=m_mapVariables.end(); ++it)
|
|
{
|
|
if(azstricmp(it->first,sName)==0)
|
|
{
|
|
CryFatalError("Error: Wrong case for '%s','%s'",it->first,sName);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
return NULL; // haven't found this name
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
char* CXConsole::GetVariable([[maybe_unused]] const char* szVarName, [[maybe_unused]] const char* szFileName, [[maybe_unused]] const char* def_val)
|
|
{
|
|
assert(m_pSystem);
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CXConsole::GetVariable([[maybe_unused]] const char* szVarName, [[maybe_unused]] const char* szFileName, [[maybe_unused]] float def_val)
|
|
{
|
|
assert(m_pSystem);
|
|
return 0;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::GetStatus()
|
|
{
|
|
return m_bConsoleActive;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Clear()
|
|
{
|
|
m_dqConsoleBuffer.clear();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Update()
|
|
{
|
|
if (!m_pSystem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_bIsConsoleKeyPressed)
|
|
{
|
|
m_sInputBuffer.clear();
|
|
m_nCursorPos = 0;
|
|
m_bIsConsoleKeyPressed = false;
|
|
}
|
|
|
|
// Execute the deferred commands
|
|
ExecuteDeferredCommands();
|
|
|
|
if (!m_bConsoleActive)
|
|
{
|
|
m_nRepeatEventId = s_nullRepeatEventId;
|
|
}
|
|
|
|
// Process Key press repeat (backspace and cursor on PC)
|
|
if (m_nRepeatEventId != s_nullRepeatEventId)
|
|
{
|
|
const float fRepeatDelay = 1.0f / 40.0f; // in sec (similar to Windows default but might differ from actual setting)
|
|
const float fHitchDelay = 1.0f / 10.0f; // in sec. Very low, but still reasonable frame-rate (debug builds)
|
|
|
|
m_fRepeatTimer -= gEnv->pTimer->GetRealFrameTime(); // works even when time is manipulated
|
|
// m_fRepeatTimer -= gEnv->pTimer->GetFrameTime(ITimer::ETIMER_UI); // can be used once ETIMER_UI works even with t_FixedTime
|
|
|
|
if (m_fRepeatTimer <= 0.0f)
|
|
{
|
|
if (m_fRepeatTimer < -fHitchDelay)
|
|
{
|
|
// bad framerate or hitch
|
|
m_nRepeatEventId = s_nullRepeatEventId;
|
|
}
|
|
else
|
|
{
|
|
const AzFramework::InputChannel* repeatInputChannel = AzFramework::InputChannelRequests::FindInputChannel(m_nRepeatEventId);
|
|
if (repeatInputChannel)
|
|
{
|
|
ProcessInput(*repeatInputChannel);
|
|
}
|
|
m_fRepeatTimer = fRepeatDelay; // next repeat even in .. sec
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//enable this for now, we need it for profiling etc
|
|
//MUST DISABLE FOR TCG BUILDS
|
|
# define PROCESS_XCONSOLE_INPUT
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
|
|
{
|
|
#ifdef PROCESS_XCONSOLE_INPUT
|
|
const AzFramework::InputDeviceId& deviceId = inputChannel.GetInputDevice().GetInputDeviceId();
|
|
if (!AzFramework::InputDeviceKeyboard::IsKeyboardDevice(deviceId))
|
|
{
|
|
// Don't consume non-keyboard events
|
|
return false;
|
|
}
|
|
|
|
if (inputChannel.IsStateEnded() && m_bConsoleActive)
|
|
{
|
|
m_nRepeatEventId = s_nullRepeatEventId;
|
|
}
|
|
|
|
if (!inputChannel.IsStateBegan())
|
|
{
|
|
// Consume keyboard events if the console is active
|
|
return m_bConsoleActive;
|
|
}
|
|
|
|
const AzFramework::InputChannelId& channelId = inputChannel.GetInputChannelId();
|
|
const AzFramework::ModifierKeyStates* customData = inputChannel.GetCustomData<AzFramework::ModifierKeyStates>();
|
|
const AzFramework::ModifierKeyStates modifierKeyStates = customData ? *customData : AzFramework::ModifierKeyStates();
|
|
|
|
// restart cursor blinking
|
|
m_fCursorBlinkTimer = 0.0f;
|
|
m_bDrawCursor = true;
|
|
|
|
// key repeat
|
|
const float fStartRepeatDelay = 0.5f; // in sec (similar to Windows default but might differ from actual setting)
|
|
m_nRepeatEventId = channelId;
|
|
m_fRepeatTimer = fStartRepeatDelay;
|
|
|
|
//execute Binds
|
|
if (!m_bConsoleActive)
|
|
{
|
|
const char* cmd = 0;
|
|
|
|
if (modifierKeyStates.GetActiveModifierKeys() == AzFramework::ModifierKeyMask::None)
|
|
{
|
|
// fast
|
|
cmd = FindKeyBind(channelId.GetName());
|
|
}
|
|
else
|
|
{
|
|
// slower
|
|
char szCombinedName[255];
|
|
int iLen = 0;
|
|
|
|
if (modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::CtrlAny))
|
|
{
|
|
azstrcpy(szCombinedName, AZ_ARRAY_SIZE(szCombinedName), "ctrl_");
|
|
iLen += 5;
|
|
}
|
|
if (modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::ShiftAny))
|
|
{
|
|
azstrcpy(&szCombinedName[iLen], AZ_ARRAY_SIZE(szCombinedName) - iLen, "shift_");
|
|
iLen += 6;
|
|
}
|
|
if (modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::AltAny))
|
|
{
|
|
azstrcpy(&szCombinedName[iLen], AZ_ARRAY_SIZE(szCombinedName) - iLen, "alt_");
|
|
iLen += 4;
|
|
}
|
|
if (modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::SuperAny))
|
|
{
|
|
azstrcpy(&szCombinedName[iLen], AZ_ARRAY_SIZE(szCombinedName) - iLen, "win_");
|
|
iLen += 4;
|
|
}
|
|
|
|
assert(sizeof(szCombinedName) > (iLen + strlen(channelId.GetName()) + 1));
|
|
azstrcpy(&szCombinedName[iLen], AZ_ARRAY_SIZE(szCombinedName) - iLen, channelId.GetName());
|
|
|
|
cmd = FindKeyBind(szCombinedName);
|
|
}
|
|
|
|
if (cmd)
|
|
{
|
|
SetInputLine("");
|
|
ExecuteStringInternal(cmd, true); // keybinds are treated as they would come from console
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (channelId != AzFramework::InputDeviceKeyboard::Key::EditTab)
|
|
{
|
|
ResetAutoCompletion();
|
|
}
|
|
|
|
if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericC &&
|
|
modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::CtrlAny))
|
|
{
|
|
Copy();
|
|
|
|
// Consume keyboard events if the console is active, which it will be if we get here
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericV &&
|
|
modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::CtrlAny))
|
|
{
|
|
Paste();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (channelId == AzFramework::InputDeviceKeyboard::Key::PunctuationTilde)
|
|
{
|
|
if (m_bActivationKeyEnable)
|
|
{
|
|
ShowConsole(!GetStatus());
|
|
m_nRepeatEventId = s_nullRepeatEventId;
|
|
m_bIsConsoleKeyPressed = true;
|
|
return true;
|
|
}
|
|
}
|
|
if (channelId == AzFramework::InputDeviceKeyboard::Key::Escape)
|
|
{
|
|
// hide console if it's active
|
|
if (GetStatus())
|
|
{
|
|
ShowConsole(false);
|
|
m_bIsConsoleKeyPressed = true;
|
|
return true;
|
|
}
|
|
|
|
//switch process or page or other things
|
|
if (m_pSystem)
|
|
{
|
|
ISystemUserCallback* pCallback = ((CSystem*)m_pSystem)->GetUserCallback();
|
|
if (pCallback)
|
|
{
|
|
pCallback->OnProcessSwitch();
|
|
m_bIsConsoleKeyPressed = true;
|
|
// Mark this input as handled. Pressing escape here is used in the editor to exit game mode, and return to edit mode.
|
|
// If AI/Physics mode was enabled before entering game mode, when returning to edit mode it will be enabled again.
|
|
// When it is enabled, it will reset input. If this returns false, then other handlers on the ebus would continue to process
|
|
// input events after the input had been reset. By returning true, the input is marked as handled.
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ProcessInput(inputChannel);
|
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::OnInputTextEventFiltered(const AZStd::string& textUTF8)
|
|
{
|
|
#ifdef PROCESS_XCONSOLE_INPUT
|
|
// Ignore tilde/accent/power of two character since it is reserved for toggling the console
|
|
const bool isTilde = (textUTF8 == "~" || textUTF8 == "`" || textUTF8 == "\xC2\xB2");
|
|
if (m_bConsoleActive && !isTilde)
|
|
{
|
|
AddInputUTF8(textUTF8);
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::ProcessInput(const AzFramework::InputChannel& inputChannel)
|
|
{
|
|
#ifdef PROCESS_XCONSOLE_INPUT
|
|
|
|
if (!m_bConsoleActive)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const AzFramework::InputChannelId& channelId = inputChannel.GetInputChannelId();
|
|
const AzFramework::ModifierKeyStates* customData = inputChannel.GetCustomData<AzFramework::ModifierKeyStates>();
|
|
const AzFramework::ModifierKeyStates modifierKeyStates = customData ? *customData : AzFramework::ModifierKeyStates();
|
|
|
|
const bool isAltModifierActive = modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::AltAny);
|
|
const bool isCtrlModifierActive = modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::CtrlAny);
|
|
|
|
if (channelId == AzFramework::InputDeviceKeyboard::Key::EditEnter ||
|
|
channelId == AzFramework::InputDeviceKeyboard::Key::NumPadEnter)
|
|
{
|
|
ExecuteInputBuffer();
|
|
m_nScrollLine = 0;
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::EditBackspace)
|
|
{
|
|
RemoveInputChar(true);
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationArrowLeft)
|
|
{
|
|
if (m_nCursorPos)
|
|
{
|
|
const char* pCursor = m_sInputBuffer.c_str() + m_nCursorPos;
|
|
pCursor -= Utf8::Internal::sequence_length(pCursor); // Note: This moves back one UCS code-point, but doesn't necessarily match one displayed character (ie, combining diacritics)
|
|
m_nCursorPos = pCursor - m_sInputBuffer.c_str();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationArrowRight)
|
|
{
|
|
if (m_nCursorPos < (int)(m_sInputBuffer.length()))
|
|
{
|
|
const char* pCursor = m_sInputBuffer.c_str() + m_nCursorPos;
|
|
pCursor += Utf8::Internal::sequence_length(pCursor); // Note: This moves forward one UCS code-point, but doesn't necessarily match one displayed character (ie, combining diacritics)
|
|
m_nCursorPos = pCursor - m_sInputBuffer.c_str();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationArrowUp)
|
|
{
|
|
const char* szHistoryLine = GetHistoryElement(true); // true=UP
|
|
|
|
if (szHistoryLine)
|
|
{
|
|
m_sInputBuffer = szHistoryLine;
|
|
m_nCursorPos = (int)m_sInputBuffer.size();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationArrowDown)
|
|
{
|
|
const char* szHistoryLine = GetHistoryElement(false); // false=DOWN
|
|
|
|
if (szHistoryLine)
|
|
{
|
|
m_sInputBuffer = szHistoryLine;
|
|
m_nCursorPos = (int)m_sInputBuffer.size();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::EditTab)
|
|
{
|
|
if (!isAltModifierActive)
|
|
{
|
|
m_sInputBuffer = ProcessCompletion(m_sInputBuffer.c_str());
|
|
m_nCursorPos = m_sInputBuffer.size();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationPageUp)
|
|
{
|
|
if (isCtrlModifierActive)
|
|
{
|
|
m_nScrollLine = min((int)(m_dqConsoleBuffer.size() - 1), m_nScrollLine + 21);
|
|
}
|
|
else
|
|
{
|
|
m_nScrollLine = min((int)(m_dqConsoleBuffer.size() - 1), m_nScrollLine + 1);
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationPageDown)
|
|
{
|
|
if (isCtrlModifierActive)
|
|
{
|
|
m_nScrollLine = max(0, m_nScrollLine - 21);
|
|
}
|
|
else
|
|
{
|
|
m_nScrollLine = max(0, m_nScrollLine - 1);
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationHome)
|
|
{
|
|
if (isCtrlModifierActive)
|
|
{
|
|
m_nScrollLine = static_cast<int>(m_dqConsoleBuffer.size() - 1);
|
|
}
|
|
else
|
|
{
|
|
m_nCursorPos = 0;
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationEnd)
|
|
{
|
|
if (isCtrlModifierActive)
|
|
{
|
|
m_nScrollLine = 0;
|
|
}
|
|
else
|
|
{
|
|
m_nCursorPos = (int)m_sInputBuffer.length();
|
|
}
|
|
return true;
|
|
}
|
|
else if (channelId == AzFramework::InputDeviceKeyboard::Key::NavigationDelete)
|
|
{
|
|
RemoveInputChar(false);
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Consume keyboard events if the console is active, which it will be if we get here
|
|
return true;
|
|
}
|
|
|
|
#ifdef PROCESS_XCONSOLE_INPUT
|
|
# undef PROCESS_XCONSOLE_INPUT
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::OnConsoleCommand(const char* cmd)
|
|
{
|
|
ExecuteString(cmd, false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::GetHistoryElement(const bool bUpOrDown)
|
|
{
|
|
if (bUpOrDown)
|
|
{
|
|
if (!m_dqHistory.empty())
|
|
{
|
|
if (m_nHistoryPos < (int)(m_dqHistory.size() - 1))
|
|
{
|
|
m_nHistoryPos++;
|
|
m_sReturnString = m_dqHistory[m_nHistoryPos];
|
|
return m_sReturnString.c_str();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_nHistoryPos > 0)
|
|
{
|
|
m_nHistoryPos--;
|
|
m_sReturnString = m_dqHistory[m_nHistoryPos];
|
|
return m_sReturnString.c_str();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Draw()
|
|
{
|
|
}
|
|
|
|
void CXConsole::DrawBuffer(int, const char*)
|
|
{
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::GetLineNo(const int indwLineNo, char* outszBuffer, const int indwBufferSize) const
|
|
{
|
|
assert(outszBuffer);
|
|
assert(indwBufferSize > 0);
|
|
|
|
outszBuffer[0] = 0;
|
|
|
|
ConsoleBuffer::const_reverse_iterator ritor = m_dqConsoleBuffer.rbegin();
|
|
|
|
ritor += indwLineNo;
|
|
|
|
if (indwLineNo >= (int)m_dqConsoleBuffer.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char* buf = ritor->c_str();// GetBuf(k);
|
|
|
|
if (*buf > 0 && *buf < 32)
|
|
{
|
|
buf++; // to jump over verbosity level character
|
|
}
|
|
azstrcpy(outszBuffer, indwBufferSize, buf);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CXConsole::GetLineCount() const
|
|
{
|
|
return static_cast<int>(m_dqConsoleBuffer.size());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ScrollConsole()
|
|
{
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::AddCommand(const char* sCommand, ConsoleCommandFunc func, int nFlags, const char* sHelp)
|
|
{
|
|
AssertName(sCommand);
|
|
|
|
if (m_mapCommands.find(sCommand) == m_mapCommands.end())
|
|
{
|
|
CConsoleCommand cmd;
|
|
cmd.m_sName = sCommand;
|
|
cmd.m_func = func;
|
|
if (sHelp)
|
|
{
|
|
cmd.m_sHelp = sHelp;
|
|
}
|
|
cmd.m_nFlags = nFlags;
|
|
m_mapCommands.insert(std::make_pair(cmd.m_sName, cmd));
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
gEnv->pLog->LogError("[CVARS]: [DUPLICATE] CXConsole::AddCommand(): console command [%s] is already registered", sCommand);
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::AddCommand(const char* sCommand, const char* sScriptFunc, int nFlags, const char* sHelp)
|
|
{
|
|
AssertName(sCommand);
|
|
|
|
if (m_mapCommands.find(sCommand) == m_mapCommands.end())
|
|
{
|
|
CConsoleCommand cmd;
|
|
cmd.m_sName = sCommand;
|
|
cmd.m_sCommand = sScriptFunc;
|
|
if (sHelp)
|
|
{
|
|
cmd.m_sHelp = sHelp;
|
|
}
|
|
cmd.m_nFlags = nFlags;
|
|
m_mapCommands.insert(std::make_pair(cmd.m_sName, cmd));
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
gEnv->pLog->LogError("[CVARS]: [DUPLICATE] CXConsole::AddCommand(): script command [%s] is already registered", sCommand);
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RemoveCommand(const char* sName)
|
|
{
|
|
ConsoleCommandsMap::iterator ite = m_mapCommands.find(sName);
|
|
if (ite != m_mapCommands.end())
|
|
{
|
|
m_mapCommands.erase(ite);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
inline bool hasprefix(const char* s, const char* prefix)
|
|
{
|
|
while (*prefix)
|
|
{
|
|
if (*prefix++ != *s++)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::GetFlagsString(const uint32 dwFlags)
|
|
{
|
|
static char sFlags[256];
|
|
|
|
// hiding this makes it a bit more difficult for cheaters
|
|
// if(dwFlags&VF_CHEAT) azstrcat( sFlags,"CHEAT, ");
|
|
|
|
azstrcpy(sFlags, AZ_ARRAY_SIZE(sFlags), "");
|
|
|
|
if (dwFlags & VF_READONLY)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "READONLY, ");
|
|
}
|
|
if (dwFlags & VF_DEPRECATED)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "DEPRECATED, ");
|
|
}
|
|
if (dwFlags & VF_DUMPTODISK)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "DUMPTODISK, ");
|
|
}
|
|
if (dwFlags & VF_REQUIRE_LEVEL_RELOAD)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "REQUIRE_LEVEL_RELOAD, ");
|
|
}
|
|
if (dwFlags & VF_REQUIRE_APP_RESTART)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "REQUIRE_APP_RESTART, ");
|
|
}
|
|
if (dwFlags & VF_RESTRICTEDMODE)
|
|
{
|
|
azstrcat(sFlags, AZ_ARRAY_SIZE(sFlags), "RESTRICTEDMODE, ");
|
|
}
|
|
|
|
if (sFlags[0] != 0)
|
|
{
|
|
sFlags[strlen(sFlags) - 2] = 0; // remove ending ", "
|
|
}
|
|
return sFlags;
|
|
}
|
|
|
|
#if ALLOW_AUDIT_CVARS
|
|
void CXConsole::AuditCVars(IConsoleCmdArgs* pArg)
|
|
{
|
|
int numArgs = pArg->GetArgCount();
|
|
int cheatMask = VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK;
|
|
int constMask = VF_CONST_CVAR;
|
|
int readOnlyMask = VF_READONLY;
|
|
int devOnlyMask = VF_DEV_ONLY;
|
|
int dediOnlyMask = VF_DEDI_ONLY;
|
|
int excludeMask = cheatMask | constMask | readOnlyMask | devOnlyMask | dediOnlyMask;
|
|
|
|
if (numArgs > 1)
|
|
{
|
|
while (numArgs > 1)
|
|
{
|
|
const char* arg = pArg->GetArg(numArgs - 1);
|
|
|
|
if (azstricmp(arg, "cheat") == 0)
|
|
{
|
|
excludeMask &= ~cheatMask;
|
|
}
|
|
|
|
if (azstricmp(arg, "const") == 0)
|
|
{
|
|
excludeMask &= ~constMask;
|
|
}
|
|
|
|
if (azstricmp(arg, "readonly") == 0)
|
|
{
|
|
excludeMask &= ~readOnlyMask;
|
|
}
|
|
|
|
if (azstricmp(arg, "dev") == 0)
|
|
{
|
|
excludeMask &= ~devOnlyMask;
|
|
}
|
|
|
|
if (azstricmp(arg, "dedi") == 0)
|
|
{
|
|
excludeMask &= ~dediOnlyMask;
|
|
}
|
|
|
|
--numArgs;
|
|
}
|
|
}
|
|
|
|
int commandCount = 0;
|
|
int cvarCount = 0;
|
|
|
|
CryLogAlways("[CVARS]: [BEGIN AUDIT]");
|
|
|
|
for (ConsoleCommandsMapItor it = m_mapCommands.begin(); it != m_mapCommands.end(); ++it)
|
|
{
|
|
CConsoleCommand& command = it->second;
|
|
|
|
int cheatFlags = (command.m_nFlags & cheatMask);
|
|
int devOnlyFlags = (command.m_nFlags & devOnlyMask);
|
|
int dediOnlyFlags = (command.m_nFlags & dediOnlyMask);
|
|
bool shouldLog = ((cheatFlags | devOnlyFlags | dediOnlyFlags) == 0) || (((cheatFlags | devOnlyFlags | dediOnlyFlags) & ~excludeMask) != 0);
|
|
if (shouldLog)
|
|
{
|
|
CryLogAlways("[CVARS]: [COMMAND] %s%s%s%s%s",
|
|
command.m_sName.c_str(),
|
|
(cheatFlags != 0) ? " [VF_CHEAT]" : "",
|
|
(devOnlyFlags != 0) ? " [VF_DEV_ONLY]" : "",
|
|
(dediOnlyFlags != 0) ? " [VF_DEDI_ONLY]" : "",
|
|
""
|
|
);
|
|
++commandCount;
|
|
}
|
|
}
|
|
|
|
for (ConsoleVariablesMapItor it = m_mapVariables.begin(); it != m_mapVariables.end(); ++it)
|
|
{
|
|
ICVar* pVariable = it->second;
|
|
int flags = pVariable->GetFlags();
|
|
|
|
int cheatFlags = (flags & cheatMask);
|
|
int constFlags = (flags & constMask);
|
|
int readOnlyFlags = (flags & readOnlyMask);
|
|
int devOnlyFlags = (flags & devOnlyMask);
|
|
int dediOnlyFlags = (flags & dediOnlyMask);
|
|
bool shouldLog = ((cheatFlags | constFlags | readOnlyFlags | devOnlyFlags | dediOnlyFlags) == 0) || (((cheatFlags | constFlags | readOnlyFlags | devOnlyFlags | dediOnlyFlags) & ~excludeMask) != 0);
|
|
if (shouldLog)
|
|
{
|
|
CryLogAlways("[CVARS]: [VARIABLE] %s%s%s%s%s%s%s",
|
|
pVariable->GetName(),
|
|
(cheatFlags != 0) ? " [VF_CHEAT]" : "",
|
|
(constFlags != 0) ? " [VF_CONST_CVAR]" : "",
|
|
(readOnlyFlags != 0) ? " [VF_READONLY]" : "",
|
|
(devOnlyFlags != 0) ? " [VF_DEV_ONLY]" : "",
|
|
(dediOnlyFlags != 0) ? " [VF_DEDI_ONLY]" : "",
|
|
""
|
|
);
|
|
++cvarCount;
|
|
}
|
|
}
|
|
|
|
CryLogAlways("[CVARS]: [END AUDIT] (commands %d/%" PRISIZE_T "; variables %d/%" PRISIZE_T ")", commandCount, m_mapCommands.size(), cvarCount, m_mapVariables.size());
|
|
}
|
|
#endif // ALLOW_AUDIT_CVARS
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
#ifndef _RELEASE
|
|
void CXConsole::DumpCommandsVarsTxt(const char* prefix)
|
|
{
|
|
FILE* f0 = nullptr;
|
|
azfopen(&f0, "consolecommandsandvars.txt", "w");
|
|
|
|
if (!f0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ConsoleCommandsMapItor itrCmd, itrCmdEnd = m_mapCommands.end();
|
|
ConsoleVariablesMapItor itrVar, itrVarEnd = m_mapVariables.end();
|
|
|
|
fprintf(f0, " CHEAT: stays in the default value if cheats are not disabled\n");
|
|
fprintf(f0, " REQUIRE_NET_SYNC: cannot be changed on client and when connecting it's sent to the client\n");
|
|
fprintf(f0, " SAVEGAME: stored when saving a savegame\n");
|
|
fprintf(f0, " READONLY: can not be changed by the user\n");
|
|
fprintf(f0, "-------------------------\n");
|
|
fprintf(f0, "\n");
|
|
|
|
for (itrCmd = m_mapCommands.begin(); itrCmd != itrCmdEnd; ++itrCmd)
|
|
{
|
|
CConsoleCommand& cmd = itrCmd->second;
|
|
|
|
if (hasprefix(cmd.m_sName.c_str(), prefix))
|
|
{
|
|
const char* sFlags = GetFlagsString(cmd.m_nFlags);
|
|
|
|
fprintf(f0, "Command: %s %s\nscript: %s\nhelp: %s\n\n", cmd.m_sName.c_str(), sFlags, cmd.m_sCommand.c_str(), cmd.m_sHelp.c_str());
|
|
}
|
|
}
|
|
|
|
for (itrVar = m_mapVariables.begin(); itrVar != itrVarEnd; ++itrVar)
|
|
{
|
|
ICVar* var = itrVar->second;
|
|
const char* types[] = { "?", "int", "float", "string", "?" };
|
|
|
|
var->GetRealIVal(); // assert inside checks consistency for all cvars
|
|
|
|
if (hasprefix(var->GetName(), prefix))
|
|
{
|
|
const char* sFlags = GetFlagsString(var->GetFlags());
|
|
|
|
fprintf(f0, "variable: %s %s\ntype: %s\ncurrent: %s\nhelp: %s\n\n", var->GetName(), sFlags, types[var->GetType()], var->GetString(), var->GetHelp());
|
|
}
|
|
}
|
|
|
|
fclose(f0);
|
|
|
|
ConsoleLogInputResponse("successfully wrote consolecommandsandvars.txt");
|
|
}
|
|
|
|
void CXConsole::DumpVarsTxt(const bool includeCheat)
|
|
{
|
|
FILE* f0 = nullptr;
|
|
azfopen(&f0, "consolevars.txt", "w");
|
|
if (!f0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ConsoleVariablesMapItor itrVar, itrVarEnd = m_mapVariables.end();
|
|
|
|
fprintf(f0, " REQUIRE_NET_SYNC: cannot be changed on client and when connecting it's sent to the client\n");
|
|
fprintf(f0, " SAVEGAME: stored when saving a savegame\n");
|
|
fprintf(f0, " READONLY: can not be changed by the user\n");
|
|
fprintf(f0, "-------------------------\n");
|
|
fprintf(f0, "\n");
|
|
|
|
for (itrVar = m_mapVariables.begin(); itrVar != itrVarEnd; ++itrVar)
|
|
{
|
|
ICVar* var = itrVar->second;
|
|
const char* types[] = { "?", "int", "float", "string", "?" };
|
|
|
|
var->GetRealIVal(); // assert inside checks consistency for all cvars
|
|
const int flags = var->GetFlags();
|
|
|
|
if ((includeCheat == true) || (flags & VF_CHEAT) == 0)
|
|
{
|
|
const char* sFlags = GetFlagsString(flags);
|
|
fprintf(f0, "variable: %s %s\ntype: %s\ncurrent: %s\nhelp: %s\n\n", var->GetName(), sFlags, types[var->GetType()], var->GetString(), var->GetHelp());
|
|
}
|
|
}
|
|
|
|
fclose(f0);
|
|
|
|
ConsoleLogInputResponse("successfully wrote consolevars.txt");
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::DisplayHelp(const char* help, const char* name)
|
|
{
|
|
if (help == 0 || *help == 0)
|
|
{
|
|
ConsoleLogInputResponse("No help available for $3%s", name);
|
|
}
|
|
else
|
|
{
|
|
char* start, * pos;
|
|
for (pos = strstr((char*)help, "\n"), start = (char*)help; pos; start = ++pos)
|
|
{
|
|
AZStd::string s = start;
|
|
s.resize(pos - start);
|
|
ConsoleLogInputResponse(" $3%s", s.c_str());
|
|
pos = strstr(pos, "\n");
|
|
}
|
|
ConsoleLogInputResponse(" $3%s", start);
|
|
}
|
|
}
|
|
|
|
|
|
void CXConsole::ExecuteString(const char* command, const bool bSilentMode, const bool bDeferExecution)
|
|
{
|
|
if (!m_deferredExecution && !bDeferExecution)
|
|
{
|
|
// This is a regular mode
|
|
ExecuteStringInternal(command, false, bSilentMode); // not from console
|
|
|
|
return;
|
|
}
|
|
|
|
// Store the string commands into a list and defer the execution for later.
|
|
// The commands will be processed in CXConsole::Update()
|
|
AZStd::string str(command);
|
|
AZ::StringFunc::TrimWhiteSpace(str, true, false);
|
|
|
|
// Unroll the exec command
|
|
|
|
bool unroll = (0 == AZ::StringFunc::Find(str, "exec", 0, false, false));
|
|
|
|
if (unroll)
|
|
{
|
|
bool oldDeferredExecution = m_deferredExecution;
|
|
|
|
// Make sure that the unrolled commands are processed with deferred mode on
|
|
m_deferredExecution = true;
|
|
ExecuteStringInternal(str.c_str(), false, bSilentMode);
|
|
|
|
// Restore to the previous setting
|
|
m_deferredExecution = oldDeferredExecution;
|
|
}
|
|
else
|
|
{
|
|
m_deferredCommands.push_back(SDeferredCommand(str.c_str(), bSilentMode));
|
|
}
|
|
}
|
|
|
|
// This method is used by the ConsoleRequestBus to allow executing of console commands
|
|
// This can be used from anywhere in code or via script since the bus is relected to the behavior context
|
|
void CXConsole::ExecuteConsoleCommand(const char* command)
|
|
{
|
|
ExecuteString(command, true, true);
|
|
}
|
|
|
|
void CXConsole::ResetCVarsToDefaults()
|
|
{
|
|
ConsoleVariablesMapItor It = m_mapVariables.begin();
|
|
|
|
while (It != m_mapVariables.end())
|
|
{
|
|
It->second->Reset();
|
|
++It;
|
|
}
|
|
|
|
}
|
|
|
|
void CXConsole::SplitCommands(const char* line, std::list<AZStd::string>& split)
|
|
{
|
|
const char* start = line;
|
|
AZStd::string working;
|
|
|
|
while (true)
|
|
{
|
|
char ch = *line++;
|
|
switch (ch)
|
|
{
|
|
case '\'':
|
|
case '\"':
|
|
while ((*line++ != ch) && *line)
|
|
{
|
|
;
|
|
}
|
|
break;
|
|
case '\n':
|
|
case '\r':
|
|
case ';':
|
|
case '\0':
|
|
{
|
|
working.assign(start, line - 1);
|
|
AZ::StringFunc::TrimWhiteSpace(working, true, true);
|
|
|
|
if (!working.empty())
|
|
{
|
|
split.push_back(working);
|
|
}
|
|
start = line;
|
|
|
|
if (ch == '\0')
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ExecuteStringInternal(const char* command, const bool bFromConsole, const bool bSilentMode)
|
|
{
|
|
AzFramework::ConsoleNotificationBus::Broadcast(&AzFramework::ConsoleNotificationBus::Events::OnConsoleCommandExecuted, command);
|
|
|
|
assert(command);
|
|
assert(command[0] != '\\'); // caller should remove leading "\\"
|
|
|
|
///////////////////////////
|
|
//Execute as string
|
|
if (command[0] == '#' || command[0] == '@')
|
|
{
|
|
if (!con_restricted || !bFromConsole) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
AddLine(command);
|
|
|
|
if (m_pSystem->IsDevMode())
|
|
{
|
|
m_bDrawCursor = 0;
|
|
}
|
|
else
|
|
{
|
|
// Warning.
|
|
// No Cheat warnings. ConsoleWarning("Console execution is cheat protected");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
ConsoleCommandsMapItor itrCmd;
|
|
ConsoleVariablesMapItor itrVar;
|
|
|
|
std::list<AZStd::string> lineCommands;
|
|
SplitCommands(command, lineCommands);
|
|
|
|
AZStd::string sTemp;
|
|
AZStd::string sCommand, sLineCommand;
|
|
|
|
while (!lineCommands.empty())
|
|
{
|
|
AZStd::string::size_type nPos;
|
|
|
|
{
|
|
sTemp = lineCommands.front();
|
|
sCommand = lineCommands.front();
|
|
sLineCommand = sCommand;
|
|
lineCommands.pop_front();
|
|
|
|
if (!bSilentMode)
|
|
{
|
|
if (GetStatus())
|
|
{
|
|
AddLine(sTemp.c_str());
|
|
}
|
|
}
|
|
|
|
nPos = sTemp.find_first_of('=');
|
|
|
|
if (nPos != AZStd::string::npos)
|
|
{
|
|
sCommand = sTemp.substr(0, nPos);
|
|
}
|
|
else if ((nPos = sTemp.find_first_of(' ')) != AZStd::string::npos)
|
|
{
|
|
sCommand = sTemp.substr(0, nPos);
|
|
}
|
|
else
|
|
{
|
|
sCommand = sTemp;
|
|
}
|
|
|
|
AZ::StringFunc::TrimWhiteSpace(sCommand, true, true);
|
|
|
|
//////////////////////////////////////////
|
|
// Search for CVars
|
|
if (sCommand.length() > 1 && sCommand[0] == '?')
|
|
{
|
|
sTemp = sCommand.substr(1);
|
|
FindVar(sTemp.c_str());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
//Check if is a command
|
|
itrCmd = m_mapCommands.find(sCommand);
|
|
if (itrCmd != m_mapCommands.end())
|
|
{
|
|
if (((itrCmd->second).m_nFlags & VF_RESTRICTEDMODE) || !con_restricted || !bFromConsole) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
if (itrCmd->second.m_nFlags & VF_BLOCKFRAME)
|
|
{
|
|
m_blockCounter++;
|
|
}
|
|
|
|
{
|
|
sTemp = sLineCommand;
|
|
}
|
|
ExecuteCommand((itrCmd->second), sTemp);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
//Check if is a variable
|
|
itrVar = m_mapVariables.find(sCommand.c_str());
|
|
if (itrVar != m_mapVariables.end())
|
|
{
|
|
ICVar* pCVar = itrVar->second;
|
|
|
|
if ((pCVar->GetFlags() & VF_RESTRICTEDMODE) || !con_restricted || !bFromConsole) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
if (pCVar->GetFlags() & VF_BLOCKFRAME)
|
|
{
|
|
m_blockCounter++;
|
|
}
|
|
|
|
if (nPos != AZStd::string::npos)
|
|
{
|
|
sTemp = sTemp.substr(nPos + 1); // remove the command from sTemp
|
|
AZ::StringFunc::StripEnds(sTemp, " \t\r\n\"\'");
|
|
|
|
if (sTemp == "?")
|
|
{
|
|
ICVar* v = itrVar->second;
|
|
DisplayHelp(v->GetHelp(), sCommand.c_str());
|
|
return;
|
|
}
|
|
|
|
if (!sTemp.empty() || (pCVar->GetType() == CVAR_STRING))
|
|
{
|
|
pCVar->Set(sTemp.c_str());
|
|
}
|
|
}
|
|
|
|
// the following line calls AddLine() indirectly
|
|
if (!bSilentMode)
|
|
{
|
|
DisplayVarValue(pCVar);
|
|
}
|
|
//ConsoleLogInputResponse("%s=%s",pCVar->GetName(),pCVar->GetString());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!bSilentMode)
|
|
{
|
|
ConsoleWarning("Unknown command: %s", sCommand.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ExecuteDeferredCommands()
|
|
{
|
|
TDeferredCommandList::iterator it;
|
|
|
|
if (m_waitFrames)
|
|
{
|
|
--m_waitFrames;
|
|
return;
|
|
}
|
|
|
|
if (m_waitSeconds.GetValue())
|
|
{
|
|
if (m_waitSeconds > gEnv->pTimer->GetFrameStartTime())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Help to avoid overflow problems
|
|
m_waitSeconds.SetValue(0);
|
|
}
|
|
|
|
const int blockCounter = m_blockCounter;
|
|
|
|
// Signal the console that we executing a deferred command
|
|
//m_deferredExecution = true;
|
|
|
|
while (!m_deferredCommands.empty())
|
|
{
|
|
it = m_deferredCommands.begin();
|
|
ExecuteStringInternal(it->command.c_str(), false, it->silentMode);
|
|
m_deferredCommands.pop_front();
|
|
|
|
// A blocker command was executed
|
|
if (m_blockCounter != blockCounter)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ExecuteCommand(CConsoleCommand& cmd, AZStd::string& str, bool bIgnoreDevMode)
|
|
{
|
|
CryLog ("[CONSOLE] Executing console command '%s'", str.c_str());
|
|
INDENT_LOG_DURING_SCOPE();
|
|
|
|
std::vector<AZStd::string> args;
|
|
size_t t;
|
|
|
|
{
|
|
t = 1;
|
|
|
|
const char* start = str.c_str();
|
|
const char* commandLine = start;
|
|
while (char ch = *commandLine++)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '\'':
|
|
case '\"':
|
|
{
|
|
while ((*commandLine++ != ch) && *commandLine)
|
|
{
|
|
;
|
|
}
|
|
args.push_back(AZStd::string(start + 1, commandLine - 1));
|
|
start = commandLine;
|
|
break;
|
|
}
|
|
case ' ':
|
|
start = commandLine;
|
|
break;
|
|
default:
|
|
{
|
|
if ((*commandLine == ' ') || !*commandLine)
|
|
{
|
|
args.push_back(AZStd::string(start, commandLine));
|
|
start = commandLine + 1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (args.size() >= 2 && args[1] == "?")
|
|
{
|
|
DisplayHelp(cmd.m_sHelp.c_str(), cmd.m_sName.c_str());
|
|
return;
|
|
}
|
|
|
|
if (((cmd.m_nFlags & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0) && !(gEnv->IsEditor()))
|
|
{
|
|
#if LOG_CVAR_INFRACTIONS
|
|
gEnv->pLog->LogError("[CVARS]: [EXECUTE] command %s is marked [VF_CHEAT]", cmd.m_sName.c_str());
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
#endif // LOG_CVAR_INFRACTIONS
|
|
if (!(gEnv->IsEditor()) && !(m_pSystem->IsDevMode()) && !bIgnoreDevMode)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmd.m_func)
|
|
{
|
|
// This is function command, execute it with a list of parameters.
|
|
CConsoleCommandArgs cmdArgs(str, args);
|
|
cmd.m_func(&cmdArgs);
|
|
return;
|
|
}
|
|
|
|
AZStd::string buf;
|
|
{
|
|
// only do this for commands with script implementation
|
|
for (;; )
|
|
{
|
|
t = str.find_first_of("\\", t);
|
|
if (t == AZStd::string::npos)
|
|
{
|
|
break;
|
|
}
|
|
str.replace(t, 1, "\\\\", 2);
|
|
t += 2;
|
|
}
|
|
|
|
for (t = 1;; )
|
|
{
|
|
t = str.find_first_of("\"", t);
|
|
if (t == AZStd::string::npos)
|
|
{
|
|
break;
|
|
}
|
|
str.replace(t, 1, "\\\"", 2);
|
|
t += 2;
|
|
}
|
|
|
|
buf = cmd.m_sCommand;
|
|
|
|
size_t pp = buf.find("%%");
|
|
if (pp != AZStd::string::npos)
|
|
{
|
|
AZStd::string list = "";
|
|
for (unsigned int i = 1; i < args.size(); i++)
|
|
{
|
|
list += "\"" + args[i] + "\"";
|
|
if (i < args.size() - 1)
|
|
{
|
|
list += ",";
|
|
}
|
|
}
|
|
buf.replace(pp, 2, list);
|
|
}
|
|
else if ((pp = buf.find("%line")) != AZStd::string::npos)
|
|
{
|
|
AZStd::string tmp = "\"" + str.substr(str.find(" ") + 1) + "\"";
|
|
if (args.size() > 1)
|
|
{
|
|
buf.replace(pp, 5, tmp);
|
|
}
|
|
else
|
|
{
|
|
buf.replace(pp, 5, "");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 1; i <= args.size(); i++)
|
|
{
|
|
char pat[10];
|
|
azsprintf(pat, "%%%d", i);
|
|
size_t pos = buf.find(pat);
|
|
if (pos == AZStd::string::npos)
|
|
{
|
|
if (i != args.size())
|
|
{
|
|
ConsoleWarning("Too many arguments for: %s", cmd.m_sName.c_str());
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (i == args.size())
|
|
{
|
|
ConsoleWarning("Not enough arguments for: %s", cmd.m_sName.c_str());
|
|
return;
|
|
}
|
|
AZStd::string arg = "\"" + args[i] + "\"";
|
|
buf.replace(pos, strlen(pat), arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_bDrawCursor = 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Exit(const char* szExitComments, ...)
|
|
{
|
|
char sResultMessageText[1024] = "";
|
|
|
|
if (szExitComments)
|
|
{
|
|
// make result string
|
|
va_list arglist;
|
|
va_start(arglist, szExitComments);
|
|
vsprintf_s(sResultMessageText, szExitComments, arglist);
|
|
va_end(arglist);
|
|
}
|
|
else
|
|
{
|
|
azstrcpy(sResultMessageText, AZ_ARRAY_SIZE(sResultMessageText), "No comments from application");
|
|
}
|
|
|
|
CryFatalError("%s", sResultMessageText);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RegisterAutoComplete(const char* sVarOrCommand, IConsoleArgumentAutoComplete* pArgAutoComplete)
|
|
{
|
|
m_mapArgumentAutoComplete[sVarOrCommand] = pArgAutoComplete;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::UnRegisterAutoComplete(const char* sVarOrCommand)
|
|
{
|
|
ArgumentAutoCompleteMap::iterator it;
|
|
it = m_mapArgumentAutoComplete.find(sVarOrCommand);
|
|
if (it != m_mapArgumentAutoComplete.end())
|
|
{
|
|
m_mapArgumentAutoComplete.erase(it);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ResetAutoCompletion()
|
|
{
|
|
m_nTabCount = 0;
|
|
m_sPrevTab = "";
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::ProcessCompletion(const char* szInputBuffer)
|
|
{
|
|
m_sInputBuffer = szInputBuffer;
|
|
|
|
int offset = (szInputBuffer[0] == '\\' ? 1 : 0); // legacy support
|
|
|
|
if ((m_sPrevTab.size() > strlen(szInputBuffer + offset)) || _strnicmp(m_sPrevTab.c_str(), (szInputBuffer + offset), m_sPrevTab.size()))
|
|
{
|
|
m_nTabCount = 0;
|
|
m_sPrevTab = "";
|
|
}
|
|
|
|
if (m_sInputBuffer.empty())
|
|
{
|
|
return (char*)m_sInputBuffer.c_str();
|
|
}
|
|
|
|
int nMatch = 0;
|
|
ConsoleCommandsMapItor itrCmds;
|
|
ConsoleVariablesMapItor itrVars;
|
|
bool showlist = !m_nTabCount && m_sPrevTab == "";
|
|
|
|
if (m_nTabCount == 0)
|
|
{
|
|
if (m_sInputBuffer.size() > 0)
|
|
{
|
|
if (m_sInputBuffer[0] == '\\')
|
|
{
|
|
m_sPrevTab = &m_sInputBuffer.c_str()[1]; // legacy support
|
|
}
|
|
else
|
|
{
|
|
m_sPrevTab = m_sInputBuffer;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
m_sPrevTab = "";
|
|
}
|
|
}
|
|
//try to search in command list
|
|
bool bArgumentAutoComplete = false;
|
|
std::vector<AZStd::string> matches;
|
|
|
|
if (m_sPrevTab.find(' ') != AZStd::string::npos)
|
|
{
|
|
bool bProcessAutoCompl = true;
|
|
|
|
// Find command.
|
|
AZStd::string sVar = m_sPrevTab.substr(0, m_sPrevTab.find(' '));
|
|
ICVar* pCVar = GetCVar(sVar.c_str());
|
|
if (pCVar)
|
|
{
|
|
if (!(pCVar->GetFlags() & VF_RESTRICTEDMODE) && con_restricted) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
bProcessAutoCompl = false;
|
|
}
|
|
}
|
|
|
|
ConsoleCommandsMap::iterator it = m_mapCommands.find(sVar);
|
|
if (it != m_mapCommands.end())
|
|
{
|
|
CConsoleCommand& ccmd = it->second;
|
|
|
|
if (!(ccmd.m_nFlags & VF_RESTRICTEDMODE) && con_restricted) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
bProcessAutoCompl = false;
|
|
}
|
|
}
|
|
|
|
if (bProcessAutoCompl)
|
|
{
|
|
IConsoleArgumentAutoComplete* pArgumentAutoComplete = stl::find_in_map(m_mapArgumentAutoComplete, sVar, 0);
|
|
if (pArgumentAutoComplete)
|
|
{
|
|
int nMatches = pArgumentAutoComplete->GetCount();
|
|
for (int i = 0; i < nMatches; i++)
|
|
{
|
|
AZStd::string cmd = AZStd::string(sVar) + " " + pArgumentAutoComplete->GetValue(i);
|
|
if (_strnicmp(m_sPrevTab.c_str(), cmd.c_str(), m_sPrevTab.length()) == 0)
|
|
{
|
|
{
|
|
bArgumentAutoComplete = true;
|
|
matches.push_back(cmd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bArgumentAutoComplete)
|
|
{
|
|
itrCmds = m_mapCommands.begin();
|
|
while (itrCmds != m_mapCommands.end())
|
|
{
|
|
CConsoleCommand& cmd = itrCmds->second;
|
|
|
|
if ((cmd.m_nFlags & VF_RESTRICTEDMODE) || !con_restricted) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{
|
|
if (_strnicmp(m_sPrevTab.c_str(), itrCmds->first.c_str(), m_sPrevTab.length()) == 0)
|
|
{
|
|
{
|
|
matches.push_back((char* const)itrCmds->first.c_str());
|
|
}
|
|
}
|
|
}
|
|
++itrCmds;
|
|
}
|
|
|
|
// try to search in console variables
|
|
|
|
itrVars = m_mapVariables.begin();
|
|
while (itrVars != m_mapVariables.end())
|
|
{
|
|
ICVar* pVar = itrVars->second;
|
|
|
|
if ((pVar->GetFlags() & VF_RESTRICTEDMODE) || !con_restricted) // in restricted mode we allow only VF_RESTRICTEDMODE CVars&CCmd
|
|
{//if(itrVars->first.compare(0,m_sPrevTab.length(),m_sPrevTab)==0)
|
|
if (_strnicmp(m_sPrevTab.c_str(), itrVars->first, m_sPrevTab.length()) == 0)
|
|
{
|
|
{
|
|
matches.push_back((char* const)itrVars->first);
|
|
}
|
|
}
|
|
}
|
|
++itrVars;
|
|
}
|
|
}
|
|
|
|
if (!matches.empty())
|
|
{
|
|
std::sort(matches.begin(), matches.end()); // to sort commands with variables
|
|
}
|
|
if (showlist && !matches.empty())
|
|
{
|
|
ConsoleLogInput(" "); // empty line before auto completion
|
|
|
|
for (std::vector<AZStd::string>::iterator i = matches.begin(); i != matches.end(); ++i)
|
|
{
|
|
// List matching variables
|
|
const char* sVar = i->c_str();
|
|
ICVar* pVar = GetCVar(sVar);
|
|
|
|
if (pVar)
|
|
{
|
|
DisplayVarValue(pVar);
|
|
}
|
|
else
|
|
{
|
|
ConsoleLogInputResponse(" $3%s $6(Command)", sVar);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::vector<AZStd::string>::iterator i = matches.begin(); i != matches.end(); ++i)
|
|
{
|
|
if (m_nTabCount <= nMatch)
|
|
{
|
|
m_sInputBuffer = *i;
|
|
m_sInputBuffer += " ";
|
|
m_nTabCount = nMatch + 1;
|
|
return (char*)m_sInputBuffer.c_str();
|
|
}
|
|
nMatch++;
|
|
}
|
|
|
|
if (m_nTabCount > 0)
|
|
{
|
|
m_nTabCount = 0;
|
|
m_sInputBuffer = m_sPrevTab;
|
|
m_sInputBuffer = ProcessCompletion(m_sInputBuffer.c_str());
|
|
}
|
|
|
|
return (char*)m_sInputBuffer.c_str();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::DisplayVarValue(ICVar* pVar)
|
|
{
|
|
if (!pVar)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const char* sFlagsString = GetFlagsString(pVar->GetFlags());
|
|
|
|
AZStd::string sValue = (pVar->GetFlags() & VF_INVISIBLE) ? "" : pVar->GetString();
|
|
AZStd::string sVar = pVar->GetName();
|
|
|
|
char szRealState[40] = "";
|
|
|
|
if (pVar->GetType() == CVAR_INT)
|
|
{
|
|
int iRealState = pVar->GetRealIVal();
|
|
|
|
if (iRealState != pVar->GetIVal())
|
|
{
|
|
if (iRealState == -1)
|
|
{
|
|
azstrcpy(szRealState, AZ_ARRAY_SIZE(szRealState), " RealState=Custom");
|
|
}
|
|
else
|
|
{
|
|
sprintf_s(szRealState, " RealState=%d", iRealState);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pVar->GetFlags() & VF_BITFIELD)
|
|
{
|
|
uint64 val64 = pVar->GetI64Val();
|
|
uint64 alphaBits = val64 & ~63LL;
|
|
uint32 nonAlphaBits = val64 & 63;
|
|
|
|
if (alphaBits != 0)
|
|
{
|
|
// the bottom 6 bits can't be set by char entry, so show them separately
|
|
char alphaChars[65]; // 1 char per bit + '\0'
|
|
BitsAlpha64(alphaBits, alphaChars);
|
|
sValue += " (";
|
|
if (nonAlphaBits != 0)
|
|
{
|
|
char nonAlphaChars[3] = { 0 }; // 1..63 + '\0'
|
|
azitoa(nonAlphaBits, nonAlphaChars, AZ_ARRAY_SIZE(nonAlphaChars), 10);
|
|
sValue += nonAlphaChars;
|
|
sValue += ", ";
|
|
}
|
|
sValue += alphaChars;
|
|
sValue += ")";
|
|
}
|
|
}
|
|
|
|
if (gEnv->IsEditor())
|
|
{
|
|
ConsoleLogInputResponse("%s=%s [ %s ]%s", sVar.c_str(), sValue.c_str(), sFlagsString, szRealState);
|
|
}
|
|
else
|
|
{
|
|
ConsoleLogInputResponse(" $3%s = $6%s $5[%s]$4%s", sVar.c_str(), sValue.c_str(), sFlagsString, szRealState);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::IsOpened()
|
|
{
|
|
return m_nScrollPos == m_nTempScrollMax;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::PrintLine(const char* s)
|
|
{
|
|
AddLine(s);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::PrintLinePlus(const char* s)
|
|
{
|
|
AddLinePlus(s);
|
|
}
|
|
|
|
static const char* FindNextEndOfLineCharacter(const char* str, size_t length)
|
|
{
|
|
size_t index = 0;
|
|
while (index < length)
|
|
{
|
|
if ((str[index] == '\r') || (str[index] == '\n'))
|
|
{
|
|
return (str + index);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddLine(const char* inputStr)
|
|
{
|
|
if ((inputStr == nullptr) || (inputStr[0] == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
size_t totalLen = strlen(inputStr);
|
|
|
|
// strip trailing \n or \r.
|
|
while ((totalLen > 0) && (inputStr[totalLen - 1] == '\n') || (inputStr[totalLen - 1] == '\r'))
|
|
{
|
|
totalLen--;
|
|
}
|
|
|
|
// split out each line in a memory efficient way
|
|
size_t remainingLength = totalLen;
|
|
const char* lastLine = inputStr;
|
|
const char* cursor = FindNextEndOfLineCharacter(inputStr, remainingLength);
|
|
while (cursor != nullptr)
|
|
{
|
|
size_t subStrLength = (cursor - lastLine);
|
|
|
|
PostLine(lastLine, subStrLength);
|
|
|
|
// bump us up to the cursor + 1 to move past the end of line character
|
|
remainingLength = remainingLength - (subStrLength + 1);
|
|
lastLine = (cursor + 1);
|
|
|
|
// Find the next non-end of line character
|
|
while ((remainingLength > 0) && ((*lastLine == '\n') || (*lastLine == '\r')))
|
|
{
|
|
remainingLength--;
|
|
lastLine++;
|
|
}
|
|
|
|
cursor = FindNextEndOfLineCharacter(lastLine, remainingLength);
|
|
}
|
|
|
|
// check for leftover
|
|
if (remainingLength > 0)
|
|
{
|
|
PostLine(lastLine, remainingLength);
|
|
}
|
|
}
|
|
|
|
void CXConsole::PostLine(const char* lineOfText, size_t len)
|
|
{
|
|
AZStd::string line = AZStd::string(lineOfText, len);
|
|
m_dqConsoleBuffer.push_back(line);
|
|
|
|
int nBufferSize = con_line_buffer_size;
|
|
|
|
while (((int)(m_dqConsoleBuffer.size())) > nBufferSize)
|
|
{
|
|
m_dqConsoleBuffer.pop_front();
|
|
}
|
|
|
|
// tell everyone who is interested (e.g. dedicated server printout)
|
|
{
|
|
std::vector<IOutputPrintSink*>::iterator it;
|
|
|
|
for (it = m_OutputSinks.begin(); it != m_OutputSinks.end(); ++it)
|
|
{
|
|
(*it)->Print(line.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ResetProgressBar(int)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::TickProgressBar()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::SetLoadingImage(const char*)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddOutputPrintSink(IOutputPrintSink* inpSink)
|
|
{
|
|
assert(inpSink);
|
|
m_OutputSinks.push_back(inpSink);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RemoveOutputPrintSink(IOutputPrintSink* inpSink)
|
|
{
|
|
assert(inpSink);
|
|
|
|
int nCount = static_cast<int>(m_OutputSinks.size());
|
|
|
|
for (int i = 0; i < nCount; i++)
|
|
{
|
|
if (m_OutputSinks[i] == inpSink)
|
|
{
|
|
if (nCount <= 1)
|
|
{
|
|
m_OutputSinks.clear();
|
|
}
|
|
else
|
|
{
|
|
m_OutputSinks[i] = m_OutputSinks.back();
|
|
m_OutputSinks.pop_back();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
assert(0);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddLinePlus(const char* inputStr)
|
|
{
|
|
AZStd::string str, tmpStr;
|
|
|
|
{
|
|
if (!m_dqConsoleBuffer.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
str = inputStr;
|
|
|
|
// strip trailing \n or \r.
|
|
if (!str.empty() && (str[str.size() - 1] == '\n' || str[str.size() - 1] == '\r'))
|
|
{
|
|
str.resize(str.size() - 1);
|
|
}
|
|
|
|
AZStd::string::size_type nPos;
|
|
while ((nPos = str.find('\n')) != AZStd::string::npos)
|
|
{
|
|
str.replace(nPos, 1, 1, ' ');
|
|
}
|
|
|
|
while ((nPos = str.find('\r')) != AZStd::string::npos)
|
|
{
|
|
str.replace(nPos, 1, 1, ' ');
|
|
}
|
|
|
|
tmpStr = m_dqConsoleBuffer.back();// += str;
|
|
|
|
m_dqConsoleBuffer.pop_back();
|
|
|
|
if (tmpStr.size() < 256)
|
|
{
|
|
m_dqConsoleBuffer.push_back(tmpStr + str);
|
|
}
|
|
else
|
|
{
|
|
m_dqConsoleBuffer.push_back(tmpStr);
|
|
}
|
|
}
|
|
|
|
// tell everyone who is interested (e.g. dedicated server printout)
|
|
{
|
|
std::vector<IOutputPrintSink*>::iterator it;
|
|
|
|
for (it = m_OutputSinks.begin(); it != m_OutputSinks.end(); ++it)
|
|
{
|
|
(*it)->Print((tmpStr + str).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddInputUTF8(const AZStd::string& textUTF8)
|
|
{
|
|
// Ignore control characters like backspace and tab
|
|
AZStd::string textUTF8ToInsert;
|
|
for (int i = 0; i < textUTF8.length(); ++i)
|
|
{
|
|
const char charToInsert = textUTF8.at(i);
|
|
if (!AZStd::is_cntrl(charToInsert))
|
|
{
|
|
textUTF8ToInsert += charToInsert;
|
|
}
|
|
}
|
|
|
|
const char* utf8_buf = textUTF8ToInsert.c_str();
|
|
|
|
if (m_nCursorPos < (int)(m_sInputBuffer.length()))
|
|
{
|
|
m_sInputBuffer.insert(m_nCursorPos, utf8_buf);
|
|
}
|
|
else
|
|
{
|
|
m_sInputBuffer = m_sInputBuffer + utf8_buf;
|
|
}
|
|
m_nCursorPos += strlen(utf8_buf);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ExecuteInputBuffer()
|
|
{
|
|
AZStd::string sTemp = m_sInputBuffer;
|
|
if (m_sInputBuffer.empty())
|
|
{
|
|
return;
|
|
}
|
|
m_sInputBuffer = "";
|
|
|
|
AddCommandToHistory(sTemp.c_str());
|
|
|
|
{
|
|
ExecuteStringInternal(sTemp.c_str(), true); // from console
|
|
}
|
|
|
|
m_nCursorPos = 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RemoveInputChar(bool bBackSpace)
|
|
{
|
|
if (m_sInputBuffer.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bBackSpace)
|
|
{
|
|
if (m_nCursorPos > 0)
|
|
{
|
|
const char* const pBase = m_sInputBuffer.c_str();
|
|
const char* pCursor = pBase + m_nCursorPos;
|
|
const char* const pEnd = pCursor;
|
|
pCursor -= Utf8::Internal::sequence_length(pCursor); // Remove one UCS code-point, doesn't account for combining diacritics
|
|
size_t length = pEnd - pCursor;
|
|
m_sInputBuffer.erase(pCursor - pBase, length);
|
|
m_nCursorPos -= length;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_nCursorPos < (int)(m_sInputBuffer.length()))
|
|
{
|
|
const char* const pBase = m_sInputBuffer.c_str();
|
|
const char* pCursor = pBase + m_nCursorPos;
|
|
const char* const pBegin = pCursor;
|
|
pCursor -= Utf8::Internal::sequence_length(pCursor); // Remove one UCS code-point, doesn't account for combining diacritics
|
|
size_t length = pCursor - pBegin;
|
|
m_sInputBuffer.erase(pBegin - pBase, length);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddCommandToHistory(const char* szCommand)
|
|
{
|
|
assert(szCommand);
|
|
|
|
m_nHistoryPos = -1;
|
|
|
|
if (!m_dqHistory.empty())
|
|
{
|
|
// add only if the command is != than the last
|
|
if (m_dqHistory.front() != szCommand)
|
|
{
|
|
m_dqHistory.push_front(szCommand);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_dqHistory.push_front(szCommand);
|
|
}
|
|
|
|
while (m_dqHistory.size() > MAX_HISTORY_ENTRIES)
|
|
{
|
|
m_dqHistory.pop_back();
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Copy()
|
|
{
|
|
#ifdef AZ_PLATFORM_WINDOWS
|
|
if (m_sInputBuffer.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!OpenClipboard(NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
size_t stringLength = m_sInputBuffer.length();
|
|
size_t requiredBufferSize = stringLength + 1; // for the null
|
|
|
|
if (HGLOBAL hGlobal = GlobalAlloc(GHND, requiredBufferSize))
|
|
{
|
|
if (LPVOID pGlobal = GlobalLock(hGlobal))
|
|
{
|
|
azstrcpy((char*)pGlobal, requiredBufferSize, m_sInputBuffer.c_str());
|
|
GlobalUnlock(hGlobal);
|
|
EmptyClipboard();
|
|
SetClipboardData(CF_TEXT, hGlobal);
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
return;
|
|
#endif //AZ_PLATFORM_WINDOWS
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::Paste()
|
|
{
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
if (OpenClipboard(NULL) != 0)
|
|
{
|
|
AZStd::string data;
|
|
const HANDLE wideData = GetClipboardData(CF_UNICODETEXT);
|
|
if (wideData)
|
|
{
|
|
const LPCWSTR pWideData = (LPCWSTR)GlobalLock(wideData);
|
|
if (pWideData)
|
|
{
|
|
AZStd::to_string(data, pWideData);
|
|
GlobalUnlock(wideData);
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
|
|
Utf8::Unchecked::octet_iterator end(data.end());
|
|
for (Utf8::Unchecked::octet_iterator it(data.begin()); it != end; ++it)
|
|
{
|
|
const wchar_t cp = static_cast<wchar_t>(*it);
|
|
if (cp != '\r')
|
|
{
|
|
// Convert UCS code-point into UTF-8 string
|
|
AZStd::fixed_string<5> utf8_buf = {0};
|
|
AZStd::to_string(utf8_buf.data(), 5, { &cp, 1 });
|
|
AddInputUTF8(utf8_buf.c_str());
|
|
}
|
|
}
|
|
}
|
|
#endif //AZ_PLATFORM_WINDOWS
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CXConsole::GetNumVars()
|
|
{
|
|
return static_cast<int>(m_mapVariables.size());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CXConsole::GetNumVisibleVars()
|
|
{
|
|
int numVars = 0;
|
|
|
|
for (auto& v : m_mapVariables)
|
|
{
|
|
if ((v.second->GetFlags() & VF_INVISIBLE) == 0)
|
|
++numVars;
|
|
}
|
|
|
|
return numVars;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
size_t CXConsole::GetSortedVars(AZStd::vector<AZStd::string_view>& pszArray, const char* szPrefix)
|
|
{
|
|
// This method used to insert instead of push_back, so we need to clear first
|
|
pszArray.clear();
|
|
|
|
size_t iPrefixLen = szPrefix ? strlen(szPrefix) : 0;
|
|
|
|
// variables
|
|
{
|
|
ConsoleVariablesMap::const_iterator it, end = m_mapVariables.end();
|
|
for (it = m_mapVariables.begin(); it != end; ++it)
|
|
{
|
|
if (szPrefix)
|
|
{
|
|
if (_strnicmp(it->first, szPrefix, iPrefixLen) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (it->second->GetFlags() & VF_INVISIBLE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pszArray.push_back(it->first);
|
|
}
|
|
}
|
|
|
|
// commands
|
|
{
|
|
ConsoleCommandsMap::iterator it, end = m_mapCommands.end();
|
|
for (it = m_mapCommands.begin(); it != end; ++it)
|
|
{
|
|
if (szPrefix)
|
|
{
|
|
if (_strnicmp(it->first.c_str(), szPrefix, iPrefixLen) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (it->second.m_nFlags & VF_INVISIBLE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pszArray.push_back(it->first.c_str());
|
|
}
|
|
}
|
|
|
|
std::sort(pszArray.begin(), pszArray.end());
|
|
return pszArray.size();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::FindVar(const char* substr)
|
|
{
|
|
AZStd::vector<AZStd::string_view> cmds;
|
|
size_t cmdCount = GetSortedVars(cmds);
|
|
|
|
for (size_t i = 0; i < cmdCount; i++)
|
|
{
|
|
if (AZ::StringFunc::Find(cmds[i], substr) != AZStd::string::npos)
|
|
{
|
|
ICVar* pCvar = gEnv->pConsole->GetCVar(cmds[i].data());
|
|
if (pCvar)
|
|
{
|
|
DisplayVarValue(pCvar);
|
|
}
|
|
else
|
|
{
|
|
ConsoleLogInputResponse(" $3%.*s $6(Command)", aznumeric_cast<int>(cmds[i].size()), cmds[i].data());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::AutoComplete(const char* substr)
|
|
{
|
|
// following code can be optimized
|
|
|
|
AZStd::vector<AZStd::string_view> cmds;
|
|
size_t cmdCount = GetSortedVars(cmds);
|
|
|
|
size_t substrLen = substr ? strlen(substr) : 0;
|
|
|
|
// If substring is empty return first command.
|
|
if (substrLen == 0 && cmdCount > 0)
|
|
{
|
|
return cmds[0].data();
|
|
}
|
|
|
|
// find next
|
|
for (size_t i = 0; i < cmdCount; i++)
|
|
{
|
|
const char* szCmd = cmds[i].data();
|
|
size_t cmdlen = cmds[i].size();
|
|
if (cmdlen >= substrLen && memcmp(szCmd, substr, substrLen) == 0)
|
|
{
|
|
if (substrLen == cmdlen)
|
|
{
|
|
i++;
|
|
if (i < cmdCount)
|
|
{
|
|
return cmds[i].data();
|
|
}
|
|
return cmds[i - 1].data();
|
|
}
|
|
return cmds[i].data();
|
|
}
|
|
}
|
|
|
|
// then first matching case insensitive
|
|
for (size_t i = 0; i < cmdCount; i++)
|
|
{
|
|
const char* szCmd = cmds[i].data();
|
|
|
|
size_t cmdlen = cmds[i].size();
|
|
if (cmdlen >= substrLen && azstrnicmp(szCmd, substr, substrLen) == 0)
|
|
{
|
|
if (substrLen == cmdlen)
|
|
{
|
|
i++;
|
|
if (i < cmdCount)
|
|
{
|
|
return cmds[i].data();
|
|
}
|
|
return cmds[i - 1].data();
|
|
}
|
|
return cmds[i].data();
|
|
}
|
|
}
|
|
|
|
// Not found.
|
|
return "";
|
|
}
|
|
|
|
void CXConsole::SetInputLine(const char* szLine)
|
|
{
|
|
assert(szLine);
|
|
|
|
m_sInputBuffer = szLine;
|
|
m_nCursorPos = m_sInputBuffer.size();
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const char* CXConsole::AutoCompletePrev(const char* substr)
|
|
{
|
|
AZStd::vector<AZStd::string_view> cmds;
|
|
GetSortedVars(cmds);
|
|
|
|
// If substring is empty return last command.
|
|
if (strlen(substr) == 0 && !cmds.empty())
|
|
{
|
|
return cmds.back().data();
|
|
}
|
|
|
|
for (const AZStd::string_view& cmd : cmds)
|
|
{
|
|
if (azstricmp(substr, cmd.data()) == 0)
|
|
{
|
|
return cmd.data();
|
|
}
|
|
}
|
|
return AutoComplete(substr);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
inline size_t sizeOf (const AZStd::string& str)
|
|
{
|
|
return str.capacity() + 1;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
inline size_t sizeOf (const char* sz)
|
|
{
|
|
return sz ? strlen(sz) + 1 : 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ConsoleLogInputResponse(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
gEnv->pLog->LogV(ILog::eInputResponse, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ConsoleLogInput(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
gEnv->pLog->LogV(ILog::eInput, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ConsoleWarning(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
gEnv->pLog->LogV(ILog::eWarningAlways, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::OnBeforeVarChange(ICVar* pVar, const char* sNewValue)
|
|
{
|
|
bool isConst = pVar->IsConstCVar();
|
|
bool isCheat = ((pVar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0);
|
|
bool isReadOnly = ((pVar->GetFlags() & VF_READONLY) != 0);
|
|
bool isDeprecated = ((pVar->GetFlags() & VF_DEPRECATED) != 0);
|
|
|
|
if (
|
|
#if CVAR_GROUPS_ARE_PRIVILEGED
|
|
!m_bIsProcessingGroup &&
|
|
#endif // !CVAR_GROUPS_ARE_PRIVILEGED
|
|
(isConst || isCheat || isReadOnly || isDeprecated))
|
|
{
|
|
bool allowChange = !isDeprecated && ((gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor()));
|
|
if (!(gEnv->IsEditor()) || isDeprecated)
|
|
{
|
|
#if LOG_CVAR_INFRACTIONS
|
|
LogChangeMessage(pVar->GetName(), isConst, isCheat,
|
|
isReadOnly, isDeprecated, pVar->GetString(), sNewValue, m_bIsProcessingGroup, allowChange);
|
|
#if LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
gEnv->pSystem->debug_LogCallStack();
|
|
#endif // LOG_CVAR_INFRACTIONS_CALLSTACK
|
|
#endif // LOG_CVAR_INFRACTIONS
|
|
}
|
|
|
|
if (!allowChange && !ALLOW_CONST_CVAR_MODIFICATIONS)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!m_consoleVarSinks.empty())
|
|
{
|
|
ConsoleVarSinks::iterator it, next;
|
|
for (it = m_consoleVarSinks.begin(); it != m_consoleVarSinks.end(); it = next)
|
|
{
|
|
next = it;
|
|
next++;
|
|
if (!(*it)->OnBeforeVarChange(pVar, sNewValue))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::OnAfterVarChange(ICVar* pVar)
|
|
{
|
|
if (!m_consoleVarSinks.empty())
|
|
{
|
|
ConsoleVarSinks::iterator it, next;
|
|
for (it = m_consoleVarSinks.begin(); it != m_consoleVarSinks.end(); it = next)
|
|
{
|
|
next = it;
|
|
next++;
|
|
(*it)->OnAfterVarChange(pVar);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole_ExecuteCommandTrampoline(IConsoleCmdArgs* args)
|
|
{
|
|
if (gEnv && gEnv->pSystem && gEnv->pSystem->GetIConsole())
|
|
{
|
|
CXConsole* pConsole = static_cast<CXConsole*>(gEnv->pSystem->GetIConsole());
|
|
pConsole->ExecuteRegisteredCommand(args);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::ExecuteRegisteredCommand(IConsoleCmdArgs* args)
|
|
{
|
|
if (args->GetArgCount() == 0)
|
|
{
|
|
AZ_Error("console", false, "Invalid number of args sent");
|
|
return;
|
|
}
|
|
|
|
const char* commandIdentifier = args->GetArg(0);
|
|
auto itCommandEntry = m_commandRegistrationMap.find(commandIdentifier);
|
|
if (itCommandEntry == m_commandRegistrationMap.end())
|
|
{
|
|
AZ_Error("console", false, "Command %s not found in the command registry", commandIdentifier);
|
|
return;
|
|
}
|
|
|
|
AZStd::vector<AZStd::string_view> input;
|
|
input.reserve(args->GetArgCount());
|
|
for (int i = 0; i < args->GetArgCount(); ++i)
|
|
{
|
|
input.push_back(args->GetArg(i));
|
|
}
|
|
|
|
CommandRegistrationEntry& entry = itCommandEntry->second;
|
|
const AzFramework::CommandResult output = entry.m_callback(input);
|
|
if (output != AzFramework::CommandResult::Success)
|
|
{
|
|
if (output == AzFramework::CommandResult::ErrorWrongNumberOfArguments)
|
|
{
|
|
AZ_Warning("console", false, "Command does not have the right number of arguments (send = %d)", input.size());
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning("console", false, "Command returned a generic error");
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::RegisterCommand(AZStd::string_view identifier, AZStd::string_view helpText, AZ::u32 commandFlags, AzFramework::CommandFunction callback)
|
|
{
|
|
if (identifier.empty())
|
|
{
|
|
AZ_Error("console", false, "RegisterCommand() requires a valid identifier");
|
|
return false;
|
|
}
|
|
|
|
if (!callback)
|
|
{
|
|
AZ_Error("console", false, "RegisterCommand() requires a valid callback");
|
|
return false;
|
|
}
|
|
|
|
if (m_commandRegistrationMap.find(identifier) != m_commandRegistrationMap.end())
|
|
{
|
|
AZ_Warning("console", false, "Command '%.*s' already found in the command registry.", static_cast<int>(identifier.size()), identifier.data());
|
|
return false;
|
|
}
|
|
|
|
// command flags should match "enum EVarFlags" values
|
|
const int flags = static_cast<int>(commandFlags);
|
|
|
|
CommandRegistrationEntry entry;
|
|
entry.m_callback = callback;
|
|
entry.m_id = identifier;
|
|
if (!helpText.empty())
|
|
{
|
|
entry.m_helpText = helpText;
|
|
}
|
|
|
|
if (!AddCommand(entry.m_id.c_str(), CXConsole_ExecuteCommandTrampoline, flags, entry.m_helpText.empty() ? nullptr : entry.m_helpText.c_str()))
|
|
{
|
|
AZ_Warning("console", false, "Command %s already found in the command registry.", entry.m_id.c_str());
|
|
return false;
|
|
}
|
|
|
|
m_commandRegistrationMap.insert(AZStd::make_pair(entry.m_id, entry));
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CXConsole::UnregisterCommand(AZStd::string_view identifier)
|
|
{
|
|
if (m_commandRegistrationMap.erase(identifier) == 1)
|
|
{
|
|
RemoveCommand(identifier.data());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::AddConsoleVarSink(IConsoleVarSink* pSink)
|
|
{
|
|
m_consoleVarSinks.push_back(pSink);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CXConsole::RemoveConsoleVarSink(IConsoleVarSink* pSink)
|
|
{
|
|
m_consoleVarSinks.remove(pSink);
|
|
}
|