Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bindchat #35

Merged
merged 7 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,8 @@ if(CLIENT)
components/spectator.h
components/statboard.cpp
components/statboard.h
components/tclient/bindchat.cpp
components/tclient/bindchat.h
components/tclient/bindwheel.cpp
components/tclient/bindwheel.h
components/tclient/menus_tclient.cpp
Expand Down
11 changes: 9 additions & 2 deletions src/game/client/components/chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ bool CChat::OnInput(const IInput::CEvent &Event)
m_CommandsNeedSorting = false;
}

SendChatQueued(m_Input.GetString());
if (m_pClient->m_Bindchat.ChatDoBinds(m_Input.GetString()))
; // Do nothing as bindchat was executed
else
SendChatQueued(m_Input.GetString());
m_pHistoryEntry = nullptr;
DisableMode();
m_pClient->OnRelease();
Expand Down Expand Up @@ -312,7 +315,11 @@ bool CChat::OnInput(const IInput::CEvent &Event)
});
}

if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty())
if (m_pClient->m_Bindchat.ChatDoAutocomplete(ShiftPressed))
{

}
else if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty())
{
CCommand *pCompletionCommand = 0;

Expand Down
2 changes: 2 additions & 0 deletions src/game/client/components/chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class CChat : public CComponent
bool LineShouldHighlight(const char *pLine, const char *pName);
void StoreSave(const char *pText);

friend class CBindchat;

public:
CChat();
int Sizeof() const override { return sizeof(*this); }
Expand Down
1 change: 1 addition & 0 deletions src/game/client/components/menus.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class CMenus : public CComponent
int DoButton_CheckBox_Number(const void *pId, const char *pText, int Checked, const CUIRect *pRect);

bool DoSliderWithScaledValue(const void *pId, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, int Scale, const IScrollbarScale *pScale, unsigned Flags = 0u, const char *pSuffix = "");
bool DoEditBoxWithLabel(CLineInput *LineInput, const CUIRect *pRect, const char *pLabel, const char *pDefault, char *pBuf, size_t BufSize);

ColorHSLA DoLine_ColorPicker(CButtonContainer *pResetId, float LineSize, float LabelSize, float BottomMargin, CUIRect *pMainRect, const char *pText, unsigned int *pColorValue, ColorRGBA DefaultColor, bool CheckBoxSpacing = true, int *pCheckBoxValue = nullptr, bool Alpha = false);
ColorHSLA DoButton_ColorPicker(const CUIRect *pRect, unsigned int *pHslaColor, bool Alpha);
Expand Down
256 changes: 256 additions & 0 deletions src/game/client/components/tclient/bindchat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
#include <engine/shared/config.h>
#include <game/client/gameclient.h>

#include "../chat.h"
#include "../emoticon.h"

#include "bindchat.h"

CBindchat::CBindchat()
{
OnReset();
}

void CBindchat::ConAddBindchat(IConsole::IResult *pResult, void *pUserData)
{
const char *aName = pResult->GetString(0);
const char *aCommand = pResult->GetString(1);

CBindchat *pThis = static_cast<CBindchat *>(pUserData);
pThis->AddBind(aName, aCommand);
}

void CBindchat::ConBindchats(IConsole::IResult *pResult, void *pUserData)
{
CBindchat *pThis = static_cast<CBindchat *>(pUserData);
char aBuf[BINDCHAT_MAX_NAME + BINDCHAT_MAX_CMD + 32];
if(pResult->NumArguments() == 1)
{
const char *pName = pResult->GetString(0);
for(const CBind &Bind : pThis->m_vBinds)
{
if(str_comp_nocase(Bind.m_aName, pName) == 0)
{
str_format(aBuf, sizeof(aBuf), "%s = %s", Bind.m_aName, Bind.m_aCommand);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf);
return;
}
}
str_format(aBuf, sizeof(aBuf), "%s is not bound", pName);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf);
}
else
{
for(const CBind &Bind : pThis->m_vBinds)
{
str_format(aBuf, sizeof(aBuf), "%s = %s", Bind.m_aName, Bind.m_aCommand);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf);
}
}
}

void CBindchat::ConRemoveBindchat(IConsole::IResult *pResult, void *pUserData)
{
const char *aName = pResult->GetString(0);
CBindchat *pThis = static_cast<CBindchat *>(pUserData);
pThis->RemoveBind(aName);
}


void CBindchat::ConRemoveBindchatAll(IConsole::IResult *pResult, void *pUserData)
{
CBindchat *pThis = static_cast<CBindchat *>(pUserData);
pThis->RemoveAllBinds();
}

void CBindchat::ConBindchatDefaults(IConsole::IResult *pResult, void *pUserData)
{
CBindchat *pThis = static_cast<CBindchat *>(pUserData);
pThis->AddBind("!shrug", "say ¯\\_(ツ)_/¯");
pThis->AddBind("!flip", "say (╯°□°)╯︵ ┻━┻");
pThis->AddBind("!unflip", "say ┬─┬ノ( º _ ºノ)");
}

void CBindchat::AddBind(const char *pName, const char *pCommand)
{
if((pName[0] == '\0' && pCommand[0] == '\0') || m_vBinds.size() >= BINDCHAT_MAX_BINDS)
return;

RemoveBind(pName); // Prevent duplicates

CBind Bind;
str_copy(Bind.m_aName, pName);
str_copy(Bind.m_aCommand, pCommand);
m_vBinds.push_back(Bind);
}

void CBindchat::RemoveBind(const char *pName)
{
if(pName[0] == '\0')
return;
for (auto It = m_vBinds.begin(); It != m_vBinds.end(); ++It)
{
if(str_comp(It->m_aName, pName) == 0)
{
m_vBinds.erase(It);
return;
}
}
}

void CBindchat::RemoveBind(int Index)
{
if(Index >= static_cast<int>(m_vBinds.size()) || Index < 0)
return;
auto It = m_vBinds.begin() + Index;
m_vBinds.erase(It);
}

void CBindchat::RemoveAllBinds()
{
m_vBinds.clear();
}

int CBindchat::GetBind(const char *pCommand)
{
if(pCommand[0] == '\0')
return -1;
for(auto It = m_vBinds.begin(); It != m_vBinds.end(); ++It)
{
if(str_comp_nocase(It->m_aCommand, pCommand) == 0)
return &*It - m_vBinds.data();
}
return -1;
}

CBindchat::CBind *CBindchat::Get(int Index)
{
if(Index < 0 || Index >= (int)m_vBinds.size())
return nullptr;
return &m_vBinds[Index];
}

void CBindchat::OnConsoleInit()
{
IConfigManager *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
if(pConfigManager)
pConfigManager->RegisterTCallback(ConfigSaveCallback, this);

Console()->Register("bindchat", "s[name] r[command]", CFGFLAG_CLIENT, ConAddBindchat, this, "Add a chat bind");
Console()->Register("bindchats", "?s[name]", CFGFLAG_CLIENT, ConBindchats, this, "Print command executed by this name or all chat binds");
Console()->Register("unbindchat", "s[name] r[command]", CFGFLAG_CLIENT, ConRemoveBindchat, this, "Remove a chat bind");
Console()->Register("unbindchatall", "", CFGFLAG_CLIENT, ConRemoveBindchatAll, this, "Removes all chat binds");
Console()->Register("bindchatdefaults", "", CFGFLAG_CLIENT, ConBindchatDefaults, this, "Adds default chat binds");
}

void CBindchat::ExecuteBind(int Bind, const char *pArgs)
{
char aBuf[BINDCHAT_MAX_CMD] = "";
str_append(aBuf, m_vBinds[Bind].m_aCommand);
if(pArgs)
{
str_append(aBuf, " ");
str_append(aBuf, pArgs);
}
Console()->ExecuteLine(aBuf);
}

bool CBindchat::ChatDoBinds(const char *pText)
{
CChat &Chat = GameClient()->m_Chat;
const char *pSpace = str_find(pText, " ");
size_t SpaceIndex = pSpace ? pSpace - pText : strlen(pText);
for(const CBind &Bind : m_vBinds)
{
if(str_comp_nocase_num(pText, Bind.m_aName, SpaceIndex) == 0)
{
ExecuteBind(&Bind - m_vBinds.data(), pSpace ? pSpace + 1 : nullptr);
// Add to history (see CChat::SendChatQueued)
const int Length = str_length(pText);
CChat::CHistoryEntry *pEntry = Chat.m_History.Allocate(sizeof(CChat::CHistoryEntry) + Length);
pEntry->m_Team = 0; // All
str_copy(pEntry->m_aText, pText, Length + 1);
return true;
}
}
return false;
}

bool CBindchat::ChatDoAutocomplete(bool ShiftPressed) {
CChat &Chat = GameClient()->m_Chat;

if(m_vBinds.size() == 0)
return false;
if(*Chat.m_aCompletionBuffer == '\0')
return false;

const CBind *pCompletionBind = nullptr;

if(ShiftPressed && Chat.m_CompletionUsed)
Chat.m_CompletionChosen--;
else if(!ShiftPressed)
Chat.m_CompletionChosen++;
Chat.m_CompletionChosen = (Chat.m_CompletionChosen + m_vBinds.size()) % m_vBinds.size(); // size != 0

Chat.m_CompletionUsed = true;
for(const CBind &Bind : m_vBinds)
{
if(str_startswith_nocase(Bind.m_aName, Chat.m_aCompletionBuffer))
{
pCompletionBind = &Bind;
Chat.m_CompletionChosen = &Bind - m_vBinds.data();
break;
}
}

// insert the command
if(pCompletionBind)
{
char aBuf[CChat::MAX_LINE_LENGTH];
// add part before the name
str_truncate(aBuf, sizeof(aBuf), Chat.m_Input.GetString(), Chat.m_PlaceholderOffset);

// add the command
str_append(aBuf, pCompletionBind->m_aName);

// add separator
// TODO: figure out if the command would accept an extra param
// char commandBuf[128];
// str_next_token(pCompletionBind->m_aCommand, " ", commandBuf, sizeof(commandBuf));
// CCommandInfo *pInfo = m_pClient->Console()->GetCommandInfo(commandBuf, CFGFLAG_CLIENT, false);
// if(pInfo && pInfo->m_pParams != '\0')
const char pSeperator[] = " ";
str_append(aBuf, pSeperator);

// add part after the name
str_append(aBuf, Chat.m_Input.GetString() + Chat.m_PlaceholderOffset + Chat.m_PlaceholderLength);

Chat.m_PlaceholderLength = sizeof(pSeperator) + str_length(pCompletionBind->m_aName) + 1;
Chat.m_Input.Set(aBuf);
Chat.m_Input.SetCursorOffset(Chat.m_PlaceholderOffset + Chat.m_PlaceholderLength);
}

return pCompletionBind != nullptr;
}

void CBindchat::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
{
CBindchat *pThis = (CBindchat *)pUserData;

for(CBind &Bind : pThis->m_vBinds)
{
char aBuf[BINDCHAT_MAX_CMD * 2] = "";
char *pEnd = aBuf + sizeof(aBuf);
char *pDst;
str_append(aBuf, "bindchat \"");
// Escape name
pDst = aBuf + str_length(aBuf);
str_escape(&pDst, Bind.m_aName, pEnd);
str_append(aBuf, "\" \"");
// Escape command
pDst = aBuf + str_length(aBuf);
str_escape(&pDst, Bind.m_aCommand, pEnd);
str_append(aBuf, "\"");
pConfigManager->WriteLine(aBuf);
}
}
58 changes: 58 additions & 0 deletions src/game/client/components/tclient/bindchat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

#ifndef GAME_CLIENT_COMPONENTS_BINDCHAT_H
#define GAME_CLIENT_COMPONENTS_BINDCHAT_H
#include <game/client/component.h>
class IConfigManager;

enum
{
BINDCHAT_MAX_NAME = 64,
BINDCHAT_MAX_CMD = 1024,
BINDCHAT_MAX_BINDS = 256,
};

class CBindchat : public CComponent
{
static void ConAddBindchat(IConsole::IResult *pResult, void *pUserData);
static void ConBindchats(IConsole::IResult *pResult, void *pUserData);
static void ConRemoveBindchat(IConsole::IResult *pResult, void *pUserData);
static void ConRemoveBindchatAll(IConsole::IResult *pResult, void *pUserData);
static void ConBindchatDefaults(IConsole::IResult *pResult, void *pUserData);

static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData);

void ExecuteBind(int Bind, const char *pArgs);

public:
class CBind
{
public:
char m_aName[BINDCHAT_MAX_NAME];
char m_aCommand[BINDCHAT_MAX_CMD];

bool operator==(const CBind &Other) const
{
return str_comp(m_aName, Other.m_aName) == 0 && str_comp(m_aCommand, Other.m_aCommand) == 0;
}
};

std::vector<CBind> m_vBinds;

CBindchat();
virtual int Sizeof() const override { return sizeof(*this); }

virtual void OnConsoleInit() override;

void AddBind(const char *pName, const char *pCommand);
void RemoveBind(const char *pName);
void RemoveBind(int Index);
void RemoveAllBinds();

int GetBind(const char *pCommand);
CBind *Get(int Index);

bool ChatDoBinds(const char *pText);
bool ChatDoAutocomplete(bool ShiftPressed);
};

#endif
Loading
Loading