diff --git a/src/engine/console.h b/src/engine/console.h index 1a7e41d6d..c7cbe15d4 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -82,14 +82,16 @@ public: typedef void (*FTeeHistorianCommandCallback)(int ClientID, int FlagMask, const char *pCmd, IResult *pResult, void *pUser); typedef void (*FPrintCallback)(const char *pStr, void *pUser, ColorRGBA PrintColor); - typedef void (*FPossibleCallback)(const char *pCmd, void *pUser); + typedef void (*FPossibleCallback)(int Index, const char *pCmd, void *pUser); typedef void (*FCommandCallback)(IResult *pResult, void *pUserData); typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData); + static void EmptyPossibleCommandCallback(int Index, const char *pCmd, void *pUser) {} + virtual void Init() = 0; virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const = 0; virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp) = 0; - virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) = 0; + virtual int PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback = EmptyPossibleCommandCallback, void *pUser = nullptr) = 0; virtual void ParseArguments(int NumArgs, const char **ppArguments) = 0; virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index ff7dd27f0..618f2f27b 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -551,16 +551,21 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo } } -void CConsole::PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) +int CConsole::PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) { + int Index = 0; for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) { if(pCommand->m_Flags & FlagMask && pCommand->m_Temp == Temp) { if(str_find_nocase(pCommand->m_pName, pStr)) - pfnCallback(pCommand->m_pName, pUser); + { + pfnCallback(Index, pCommand->m_pName, pUser); + Index++; + } } } + return Index; } CConsole::CCommand *CConsole::FindCommand(const char *pName, int FlagMask) diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 47fd4b3f0..45ce408d6 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -196,7 +196,7 @@ public: void Init() override; const CCommandInfo *FirstCommandInfo(int AccessLevel, int FlagMask) const override; const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp) override; - void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) override; + int PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) override; void ParseArguments(int NumArgs, const char **ppArguments) override; void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) override; diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp index df0c11f19..fbabf181d 100644 --- a/src/game/client/components/binds.cpp +++ b/src/game/client/components/binds.cpp @@ -254,7 +254,7 @@ void CBinds::OnConsoleInit() pConfigManager->RegisterCallback(ConfigSaveCallback, this); Console()->Register("bind", "s[key] r[command]", CFGFLAG_CLIENT, ConBind, this, "Bind key to execute the command"); - Console()->Register("dump_binds", "?s[key]", CFGFLAG_CLIENT, ConDumpBinds, this, "Print command executed by this keybindind or all binds"); + Console()->Register("binds", "?s[key]", CFGFLAG_CLIENT, ConBinds, this, "Print command executed by this keybinding or all binds"); Console()->Register("unbind", "s[key]", CFGFLAG_CLIENT, ConUnbind, this, "Unbind key"); Console()->Register("unbindall", "", CFGFLAG_CLIENT, ConUnbindAll, this, "Unbind all keys"); @@ -280,7 +280,7 @@ void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData) pBinds->Bind(KeyID, pResult->GetString(1), false, Modifier); } -void CBinds::ConDumpBinds(IConsole::IResult *pResult, void *pUserData) +void CBinds::ConBinds(IConsole::IResult *pResult, void *pUserData) { CBinds *pBinds = (CBinds *)pUserData; if(pResult->NumArguments() == 1) diff --git a/src/game/client/components/binds.h b/src/game/client/components/binds.h index 268df2d27..300d671bf 100644 --- a/src/game/client/components/binds.h +++ b/src/game/client/components/binds.h @@ -15,7 +15,7 @@ class CBinds : public CComponent int GetKeyID(const char *pKeyName); static void ConBind(IConsole::IResult *pResult, void *pUserData); - static void ConDumpBinds(IConsole::IResult *pResult, void *pUserData); + static void ConBinds(IConsole::IResult *pResult, void *pUserData); static void ConUnbind(IConsole::IResult *pResult, void *pUserData); static void ConUnbindAll(IConsole::IResult *pResult, void *pUserData); class IConsole *GetConsole() const { return Console(); } diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 33aea863b..978f3e6cb 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -76,6 +76,13 @@ void CConsoleLogger::OnConsoleDeletion() m_pConsole = nullptr; } +// TODO: support "tune_zone", which has tuning as second argument +static const char *gs_apTuningCommands[] = {"tune ", "tune_reset ", "toggle_tune "}; +static bool IsTuningCommandPrefix(const char *pStr) +{ + return std::any_of(std::begin(gs_apTuningCommands), std::end(gs_apTuningCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); }); +} + CGameConsole::CInstance::CInstance(int Type) { m_pHistoryEntry = 0x0; @@ -83,15 +90,21 @@ CGameConsole::CInstance::CInstance(int Type) m_Type = Type; if(Type == CGameConsole::CONSOLETYPE_LOCAL) + { + m_pName = "local_console"; m_CompletionFlagmask = CFGFLAG_CLIENT; + } else + { + m_pName = "remote_console"; m_CompletionFlagmask = CFGFLAG_SERVER; + } m_aCompletionBuffer[0] = 0; - m_CompletionUsed = false; m_CompletionChosen = -1; - m_CompletionRenderOffset = 0.0f; - m_ReverseTAB = false; + m_aCompletionBufferArgument[0] = 0; + m_CompletionChosenArgument = -1; + Reset(); m_aUser[0] = '\0'; m_UserGot = false; @@ -131,6 +144,11 @@ void CGameConsole::CInstance::ClearHistory() m_pHistoryEntry = 0; } +void CGameConsole::CInstance::Reset() +{ + m_CompletionRenderOffset = 0.0f; +} + void CGameConsole::CInstance::ExecuteLine(const char *pLine) { if(m_Type == CGameConsole::CONSOLETYPE_LOCAL) @@ -155,12 +173,33 @@ void CGameConsole::CInstance::ExecuteLine(const char *pLine) } } -void CGameConsole::CInstance::PossibleCommandsCompleteCallback(const char *pStr, void *pUser) +void CGameConsole::CInstance::PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser) { CGameConsole::CInstance *pInstance = (CGameConsole::CInstance *)pUser; - if(pInstance->m_CompletionChosen == pInstance->m_CompletionEnumerationCount) + if(pInstance->m_CompletionChosen == Index) pInstance->m_Input.Set(pStr); - pInstance->m_CompletionEnumerationCount++; +} + +static void StrCopyUntilSpace(char *pDest, size_t DestSize, const char *pSrc) +{ + const char *pSpace = str_find(pSrc, " "); + str_copy(pDest, pSrc, minimum(pSpace ? pSpace - pSrc + 1 : 1, DestSize)); +} + +void CGameConsole::CInstance::PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser) +{ + CGameConsole::CInstance *pInstance = (CGameConsole::CInstance *)pUser; + if(pInstance->m_CompletionChosenArgument == Index) + { + // get command + char aBuf[512]; + StrCopyUntilSpace(aBuf, sizeof(aBuf), pInstance->GetString()); + str_append(aBuf, " ", sizeof(aBuf)); + + // append argument + str_append(aBuf, pStr, sizeof(aBuf)); + pInstance->m_Input.Set(aBuf); + } } void CGameConsole::CInstance::OnInput(IInput::CEvent Event) @@ -323,23 +362,46 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) } else if(Event.m_Key == KEY_TAB) { + const int Direction = m_pGameConsole->m_pClient->Input()->KeyIsPressed(KEY_LSHIFT) || m_pGameConsole->m_pClient->Input()->KeyIsPressed(KEY_RSHIFT) ? -1 : 1; + + // command completion if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) { - if(m_ReverseTAB && m_CompletionUsed) - m_CompletionChosen--; - else if(!m_ReverseTAB) - m_CompletionChosen++; - m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); - - m_CompletionUsed = true; - - // handle wrapping - if(m_CompletionEnumerationCount && (m_CompletionChosen >= m_CompletionEnumerationCount || m_CompletionChosen < 0)) + const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(); + const int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands); + if(CompletionEnumerationCount) { - m_CompletionChosen = (m_CompletionChosen + m_CompletionEnumerationCount) % m_CompletionEnumerationCount; - m_CompletionEnumerationCount = 0; - m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands(), PossibleCommandsCompleteCallback, this); + if(m_CompletionChosen == -1 && Direction < 0) + m_CompletionChosen = 0; + m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; + m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); + } + else if(m_CompletionChosen != -1) + { + m_CompletionChosen = -1; + Reset(); + } + } + + // argument completion (tuning, ...) + if(m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed()) + { + const bool TuningCompletion = IsTuningCommandPrefix(GetString()); + if(TuningCompletion) + { + int CompletionEnumerationCount = m_pGameConsole->m_pClient->m_aTuning[g_Config.m_ClDummy].PossibleTunings(m_aCompletionBufferArgument); + if(CompletionEnumerationCount) + { + if(m_CompletionChosenArgument == -1 && Direction < 0) + m_CompletionChosenArgument = 0; + m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; + m_pGameConsole->m_pClient->m_aTuning[g_Config.m_ClDummy].PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this); + } + else if(m_CompletionChosenArgument != -1) + { + m_CompletionChosenArgument = -1; + Reset(); + } } } } @@ -367,16 +429,6 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) m_BacklogCurPage = 0; m_pGameConsole->m_HasSelection = false; } - else if(Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = true; - Handled = true; - } - } - if(Event.m_Flags & IInput::FLAG_RELEASE && Event.m_Key == KEY_LSHIFT) - { - m_ReverseTAB = false; - Handled = true; } if(!Handled) @@ -384,23 +436,27 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event) if(Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_TEXT)) { - if((Event.m_Key != KEY_TAB) && (Event.m_Key != KEY_LSHIFT)) + if(Event.m_Key != KEY_TAB && Event.m_Key != KEY_LSHIFT && Event.m_Key != KEY_RSHIFT) { - m_CompletionUsed = false; m_CompletionChosen = -1; str_copy(m_aCompletionBuffer, m_Input.GetString()); - m_CompletionRenderOffset = 0.0f; + + for(const auto *pCmd : gs_apTuningCommands) + { + if(str_startswith_nocase(m_Input.GetString(), pCmd)) + { + m_CompletionChosenArgument = -1; + str_copy(m_aCompletionBufferArgument, &m_Input.GetString()[str_length(pCmd)]); + } + } + + Reset(); } // find the current command { - char aBuf[64] = {0}; - const char *pSrc = GetString(); - int i = 0; - for(; i < (int)sizeof(aBuf) - 1 && *pSrc && *pSrc != ' '; i++, pSrc++) - aBuf[i] = *pSrc; - aBuf[i] = 0; - + char aBuf[sizeof(m_aCommandName)]; + StrCopyUntilSpace(aBuf, sizeof(aBuf), GetString()); const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands()); if(pCommand) @@ -461,6 +517,7 @@ CGameConsole::CInstance *CGameConsole::CurrentConsole() void CGameConsole::OnReset() { + m_RemoteConsole.Reset(); } // only defined for 0<=t<=1 @@ -470,7 +527,7 @@ static float ConsoleScaleFunc(float t) return sinf(acosf(1.0f - t)); } -struct CRenderInfo +struct CCompletionOptionRenderInfo { CGameConsole *m_pSelf; CTextCursor m_Cursor; @@ -481,9 +538,9 @@ struct CRenderInfo float m_Width; }; -void CGameConsole::PossibleCommandsRenderCallback(const char *pStr, void *pUser) +void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser) { - CRenderInfo *pInfo = static_cast(pUser); + CCompletionOptionRenderInfo *pInfo = static_cast(pUser); if(pInfo->m_EnumCount == pInfo->m_WantedCompletion) { @@ -618,15 +675,8 @@ void CGameConsole::OnRender() float x = 3; float y = ConsoleHeight - RowHeight - 5.0f; - CRenderInfo Info; - Info.m_pSelf = this; - Info.m_WantedCompletion = pConsole->m_CompletionUsed ? pConsole->m_CompletionChosen : -1; - Info.m_EnumCount = 0; - Info.m_Offset = pConsole->m_CompletionRenderOffset; - Info.m_Width = Screen.w; - Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer; - TextRender()->SetCursor(&Info.m_Cursor, x + Info.m_Offset, y + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); - Info.m_Cursor.m_LineWidth = std::numeric_limits::max(); + const float InitialX = x; + const float InitialY = y; // render prompt CTextCursor Cursor; @@ -706,24 +756,40 @@ void CGameConsole::OnRender() Input()->SetEditingPosition(Marker.m_X, Marker.m_Y + Marker.m_FontSize); // render possible commands - if(m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) + if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && pConsole->m_Input.GetString()[0]) { - if(pConsole->m_Input.GetString()[0] != 0) - { - m_pConsole->PossibleCommands(pConsole->m_aCompletionBuffer, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); - pConsole->m_CompletionRenderOffset = Info.m_Offset; + CCompletionOptionRenderInfo Info; + Info.m_pSelf = this; + Info.m_WantedCompletion = pConsole->m_CompletionChosen; + Info.m_EnumCount = 0; + Info.m_Offset = pConsole->m_CompletionRenderOffset; + Info.m_Width = Screen.w; + Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer; + TextRender()->SetCursor(&Info.m_Cursor, InitialX + Info.m_Offset, InitialY + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); + Info.m_Cursor.m_LineWidth = std::numeric_limits::max(); + m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); + pConsole->m_CompletionRenderOffset = Info.m_Offset; - if(Info.m_EnumCount <= 0) + if(Info.m_EnumCount <= 0 && pConsole->m_IsCommand) + { + const bool TuningCompletion = IsTuningCommandPrefix(Info.m_pCurrentCmd); + if(TuningCompletion) { - if(pConsole->m_IsCommand) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp); - TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); - TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); - str_format(aBuf, sizeof(aBuf), "Usage: %s %s", pConsole->m_aCommandName, pConsole->m_aCommandParams); - TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); - } + Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument; + Info.m_EnumCount = 0; + Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument; + m_pClient->m_aTuning[g_Config.m_ClDummy].PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info); + pConsole->m_CompletionRenderOffset = Info.m_Offset; + } + + if(Info.m_EnumCount <= 0 && pConsole->m_IsCommand) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp); + TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); + TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); + str_format(aBuf, sizeof(aBuf), "Usage: %s %s", pConsole->m_aCommandName, pConsole->m_aCommandParams); + TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); } } } @@ -922,23 +988,28 @@ void CGameConsole::Toggle(int Type) void CGameConsole::Dump(int Type) { CInstance *pConsole = Type == CONSOLETYPE_REMOTE ? &m_RemoteConsole : &m_LocalConsole; + char aBuf[IO_MAX_PATH_LENGTH + 64]; char aFilename[IO_MAX_PATH_LENGTH]; - char aDate[20]; - - str_timestamp(aDate, sizeof(aDate)); - str_format(aFilename, sizeof(aFilename), "dumps/%s_dump_%s.txt", Type == CONSOLETYPE_REMOTE ? "remote_console" : "local_console", aDate); - IOHANDLE io = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); - if(io) + str_timestamp(aBuf, sizeof(aBuf)); + str_format(aFilename, sizeof(aFilename), "dumps/%s_dump_%s.txt", pConsole->m_pName, aBuf); + IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); + if(File) { pConsole->m_BacklogLock.lock(); for(CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.First(); pEntry; pEntry = pConsole->m_Backlog.Next(pEntry)) { - io_write(io, pEntry->m_aText, str_length(pEntry->m_aText)); - io_write_newline(io); + io_write(File, pEntry->m_aText, str_length(pEntry->m_aText)); + io_write_newline(File); } pConsole->m_BacklogLock.unlock(); - io_close(io); + io_close(File); + str_format(aBuf, sizeof(aBuf), "%s contents were written to '%s'", pConsole->m_pName, aFilename); } + else + { + str_format(aBuf, sizeof(aBuf), "Failed to open '%s'", aFilename); + } + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", aBuf); } void CGameConsole::ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData) @@ -1014,8 +1085,8 @@ void CGameConsole::OnConsoleInit() Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console"); Console()->Register("clear_local_console", "", CFGFLAG_CLIENT, ConClearLocalConsole, this, "Clear local console"); Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console"); - Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console"); - Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console"); + Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Write local console contents to a text file"); + Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Write remote console contents to a text file"); Console()->Register("console_page_up", "", CFGFLAG_CLIENT, ConConsolePageUp, this, "Previous page in console"); Console()->Register("console_page_down", "", CFGFLAG_CLIENT, ConConsolePageDown, this, "Next page in console"); diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 833ded971..e13f34472 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -38,18 +38,18 @@ class CGameConsole : public CComponent char *m_pHistoryEntry; CLineInput m_Input; + const char *m_pName; int m_Type; - int m_CompletionEnumerationCount; int m_BacklogCurPage; CGameConsole *m_pGameConsole; char m_aCompletionBuffer[128]; - bool m_CompletionUsed; int m_CompletionChosen; + char m_aCompletionBufferArgument[128]; + int m_CompletionChosenArgument; int m_CompletionFlagmask; float m_CompletionRenderOffset; - bool m_ReverseTAB; char m_aUser[32]; bool m_UserGot; @@ -66,6 +66,7 @@ class CGameConsole : public CComponent void ClearBacklog(); void ClearBacklogYOffsets(); void ClearHistory(); + void Reset(); void ExecuteLine(const char *pLine); @@ -73,7 +74,8 @@ class CGameConsole : public CComponent void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor); const char *GetString() const { return m_Input.GetString(); } - static void PossibleCommandsCompleteCallback(const char *pStr, void *pUser); + static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser); + static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser); }; class IConsole *m_pConsole; @@ -105,7 +107,7 @@ class CGameConsole : public CComponent void Toggle(int Type); void Dump(int Type); - static void PossibleCommandsRenderCallback(const char *pStr, void *pUser); + static void PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser); static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData); static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConClearLocalConsole(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp index 21507f322..2d4cb8d39 100644 --- a/src/game/client/components/debughud.cpp +++ b/src/game/client/components/debughud.cpp @@ -119,7 +119,7 @@ void CDebugHud::RenderTuning() TextRender()->Text(0x0, x - w, y + Count * 6, 5, aBuf, -1.0f); x += 5.0f; - TextRender()->Text(0x0, x, y + Count * 6, 5, CTuningParams::ms_apNames[i], -1.0f); + TextRender()->Text(0x0, x, y + Count * 6, 5, CTuningParams::Name(i), -1.0f); Count++; } diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 796f66ba1..a0098ba85 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -163,9 +163,9 @@ void CGameClient::OnConsoleInit() Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself to restart"); // register server dummy commands for tab completion - Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value"); - Console()->Register("tune_reset", "", CFGFLAG_SERVER, 0, 0, "Reset tuning"); - Console()->Register("tune_dump", "", CFGFLAG_SERVER, 0, 0, "Dump tuning"); + Console()->Register("tune", "s[tuning] ?i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value or show current value"); + Console()->Register("tune_reset", "?s[tuning]", CFGFLAG_SERVER, 0, 0, "Reset all or one tuning variable to default"); + Console()->Register("tunes", "", CFGFLAG_SERVER, 0, 0, "List all tuning variables and their values"); Console()->Register("change_map", "?r[map]", CFGFLAG_SERVER, 0, 0, "Change map"); Console()->Register("restart", "?i[seconds]", CFGFLAG_SERVER, 0, 0, "Restart in x seconds"); Console()->Register("broadcast", "r[message]", CFGFLAG_SERVER, 0, 0, "Broadcast message"); diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index cdfe1dbf7..0342ba9c1 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -34,7 +34,7 @@ bool CTuningParams::Get(int Index, float *pValue) const bool CTuningParams::Set(const char *pName, float Value) { for(int i = 0; i < Num(); i++) - if(str_comp_nocase(pName, ms_apNames[i]) == 0) + if(str_comp_nocase(pName, Name(i)) == 0) return Set(i, Value); return false; } @@ -42,12 +42,26 @@ bool CTuningParams::Set(const char *pName, float Value) bool CTuningParams::Get(const char *pName, float *pValue) const { for(int i = 0; i < Num(); i++) - if(str_comp_nocase(pName, ms_apNames[i]) == 0) + if(str_comp_nocase(pName, Name(i)) == 0) return Get(i, pValue); return false; } +int CTuningParams::PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCallback, void *pUser) +{ + int Index = 0; + for(int i = 0; i < Num(); i++) + { + if(str_find_nocase(Name(i), pStr)) + { + pfnCallback(Index, Name(i), pUser); + Index++; + } + } + return Index; +} + float VelocityRamp(float Value, float Start, float Range, float Curvature) { if(Value < Start) diff --git a/src/game/gamecore.h b/src/game/gamecore.h index 41bcd2abc..03047c7a4 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -40,6 +41,8 @@ public: class CTuningParams { + static const char *ms_apNames[]; + public: CTuningParams() { @@ -49,8 +52,6 @@ public: #undef MACRO_TUNING_PARAM } - static const char *ms_apNames[]; - #define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) CTuneParam m_##Name; #include "tuning.h" #undef MACRO_TUNING_PARAM @@ -63,6 +64,8 @@ public: bool Set(const char *pName, float Value); bool Get(int Index, float *pValue) const; bool Get(const char *pName, float *pValue) const; + static const char *Name(int Index) { return ms_apNames[Index]; } + int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCallback = IConsole::EmptyPossibleCommandCallback, void *pUser = nullptr); }; inline void StrToInts(int *pInts, int Num, const char *pStr) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 6155a0978..628d1e606 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2550,17 +2550,34 @@ void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; const char *pParamName = pResult->GetString(0); - float NewValue = pResult->GetFloat(1); - if(pSelf->Tuning()->Set(pParamName, NewValue)) + char aBuf[256]; + if(pResult->NumArguments() == 2) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "%s changed to %.2f", pParamName, NewValue); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); - pSelf->SendTuningParams(-1); + float NewValue = pResult->GetFloat(1); + if(pSelf->Tuning()->Set(pParamName, NewValue) && pSelf->Tuning()->Get(pParamName, &NewValue)) + { + str_format(aBuf, sizeof(aBuf), "%s changed to %.2f", pParamName, NewValue); + pSelf->SendTuningParams(-1); + } + else + { + str_format(aBuf, sizeof(aBuf), "No such tuning parameter: %s", pParamName); + } } else - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "No such tuning parameter"); + { + float Value; + if(pSelf->Tuning()->Get(pParamName, &Value)) + { + str_format(aBuf, sizeof(aBuf), "%s %.2f", pParamName, Value); + } + else + { + str_format(aBuf, sizeof(aBuf), "No such tuning parameter: %s", pParamName); + } + } + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); } void CGameContext::ConToggleTuneParam(IConsole::IResult *pResult, void *pUserData) @@ -2569,17 +2586,19 @@ void CGameContext::ConToggleTuneParam(IConsole::IResult *pResult, void *pUserDat const char *pParamName = pResult->GetString(0); float OldValue; + char aBuf[256]; if(!pSelf->Tuning()->Get(pParamName, &OldValue)) { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "No such tuning parameter"); + str_format(aBuf, sizeof(aBuf), "No such tuning parameter: %s", pParamName); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); return; } float NewValue = fabs(OldValue - pResult->GetFloat(1)) < 0.0001f ? pResult->GetFloat(2) : pResult->GetFloat(1); pSelf->Tuning()->Set(pParamName, NewValue); + pSelf->Tuning()->Get(pParamName, &NewValue); - char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s changed to %.2f", pParamName, NewValue); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); pSelf->SendTuningParams(-1); @@ -2588,11 +2607,31 @@ void CGameContext::ConToggleTuneParam(IConsole::IResult *pResult, void *pUserDat void CGameContext::ConTuneReset(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; - pSelf->ResetTuning(); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "Tuning reset"); + if(pResult->NumArguments()) + { + const char *pParamName = pResult->GetString(0); + float DefaultValue = 0.0f; + char aBuf[256]; + CTuningParams TuningParams; + if(TuningParams.Get(pParamName, &DefaultValue) && pSelf->Tuning()->Set(pParamName, DefaultValue) && pSelf->Tuning()->Get(pParamName, &DefaultValue)) + { + str_format(aBuf, sizeof(aBuf), "%s reset to %.2f", pParamName, DefaultValue); + pSelf->SendTuningParams(-1); + } + else + { + str_format(aBuf, sizeof(aBuf), "No such tuning parameter: %s", pParamName); + } + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); + } + else + { + pSelf->ResetTuning(); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "Tuning reset"); + } } -void CGameContext::ConTuneDump(IConsole::IResult *pResult, void *pUserData) +void CGameContext::ConTunes(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; char aBuf[256]; @@ -2600,7 +2639,7 @@ void CGameContext::ConTuneDump(IConsole::IResult *pResult, void *pUserData) { float Value; pSelf->Tuning()->Get(i, &Value); - str_format(aBuf, sizeof(aBuf), "%s %.2f", pSelf->Tuning()->ms_apNames[i], Value); + str_format(aBuf, sizeof(aBuf), "%s %.2f", CTuningParams::Name(i), Value); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); } } @@ -2614,15 +2653,17 @@ void CGameContext::ConTuneZone(IConsole::IResult *pResult, void *pUserData) if(List >= 0 && List < NUM_TUNEZONES) { - if(pSelf->TuningList()[List].Set(pParamName, NewValue)) + char aBuf[256]; + if(pSelf->TuningList()[List].Set(pParamName, NewValue) && pSelf->TuningList()[List].Get(pParamName, &NewValue)) { - char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s in zone %d changed to %.2f", pParamName, List, NewValue); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); pSelf->SendTuningParams(-1, List); } else - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "No such tuning parameter"); + { + str_format(aBuf, sizeof(aBuf), "No such tuning parameter: %s", pParamName); + } + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); } } @@ -2637,7 +2678,7 @@ void CGameContext::ConTuneDumpZone(IConsole::IResult *pResult, void *pUserData) { float Value; pSelf->TuningList()[List].Get(i, &Value); - str_format(aBuf, sizeof(aBuf), "zone %d: %s %.2f", List, pSelf->TuningList()[List].ms_apNames[i], Value); + str_format(aBuf, sizeof(aBuf), "zone %d: %s %.2f", List, CTuningParams::Name(i), Value); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf); } } @@ -3140,10 +3181,10 @@ void CGameContext::OnConsoleInit() m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); - Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneParam, this, "Tune variable to value"); + Console()->Register("tune", "s[tuning] ?i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneParam, this, "Tune variable to value or show current value"); Console()->Register("toggle_tune", "s[tuning] i[value 1] i[value 2]", CFGFLAG_SERVER | CFGFLAG_GAME, ConToggleTuneParam, this, "Toggle tune variable"); - Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning"); - Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning"); + Console()->Register("tune_reset", "?s[tuning]", CFGFLAG_SERVER, ConTuneReset, this, "Reset all or one tuning variable to default"); + Console()->Register("tunes", "", CFGFLAG_SERVER, ConTunes, this, "List all tuning variables and their values"); Console()->Register("tune_zone", "i[zone] s[tuning] i[value]", CFGFLAG_SERVER | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value"); Console()->Register("tune_zone_dump", "i[zone]", CFGFLAG_SERVER, ConTuneDumpZone, this, "Dump zone tuning in zone x"); Console()->Register("tune_zone_reset", "?i[zone]", CFGFLAG_SERVER, ConTuneResetZone, this, "reset zone tuning in zone x or in all zones"); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index c2f4b8263..6e5172ddf 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -90,7 +90,7 @@ class CGameContext : public IGameServer static void ConTuneParam(IConsole::IResult *pResult, void *pUserData); static void ConToggleTuneParam(IConsole::IResult *pResult, void *pUserData); static void ConTuneReset(IConsole::IResult *pResult, void *pUserData); - static void ConTuneDump(IConsole::IResult *pResult, void *pUserData); + static void ConTunes(IConsole::IResult *pResult, void *pUserData); static void ConTuneZone(IConsole::IResult *pResult, void *pUserData); static void ConTuneDumpZone(IConsole::IResult *pResult, void *pUserData); static void ConTuneResetZone(IConsole::IResult *pResult, void *pUserData);