mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-19 14:38:18 +00:00
Merge pull request #2042 from Dune-jr/feature-chat-commands
Chat commands
This commit is contained in:
commit
cf8212a663
|
@ -1913,6 +1913,38 @@ void str_clean_whitespaces(char *str_in)
|
|||
}
|
||||
}
|
||||
|
||||
/* removes leading and trailing spaces */
|
||||
void str_clean_whitespaces_simple(char *str_in)
|
||||
{
|
||||
char *read = str_in;
|
||||
char *write = str_in;
|
||||
|
||||
/* skip initial whitespace */
|
||||
while(*read == ' ')
|
||||
read++;
|
||||
|
||||
/* end of read string is detected in the loop */
|
||||
while(1)
|
||||
{
|
||||
/* skip whitespace */
|
||||
int found_whitespace = 0;
|
||||
for(; *read == ' ' && !found_whitespace; read++)
|
||||
found_whitespace = 1;
|
||||
/* if not at the end of the string, put a found whitespace here */
|
||||
if(*read)
|
||||
{
|
||||
if(found_whitespace)
|
||||
*write++ = ' ';
|
||||
*write++ = *read++;
|
||||
}
|
||||
else
|
||||
{
|
||||
*write = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *str_skip_to_whitespace(char *str)
|
||||
{
|
||||
while(*str && (*str != ' ' && *str != '\t' && *str != '\n'))
|
||||
|
@ -1920,6 +1952,13 @@ char *str_skip_to_whitespace(char *str)
|
|||
return str;
|
||||
}
|
||||
|
||||
const char *str_skip_to_whitespace_const(const char *str)
|
||||
{
|
||||
while(*str && (*str != ' ' && *str != '\t' && *str != '\n'))
|
||||
str++;
|
||||
return str;
|
||||
}
|
||||
|
||||
char *str_skip_whitespaces(char *str)
|
||||
{
|
||||
while(*str && (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r'))
|
||||
|
|
|
@ -912,6 +912,18 @@ int str_check_pathname(const char* str);
|
|||
*/
|
||||
void str_clean_whitespaces(char *str);
|
||||
|
||||
/*
|
||||
Function: str_clean_whitespaces_simple
|
||||
Removes leading and trailing spaces
|
||||
|
||||
Parameters:
|
||||
str - String to clean up
|
||||
|
||||
Remarks:
|
||||
- The strings are treated as zero-terminated strings.
|
||||
*/
|
||||
void str_clean_whitespaces_simple(char *str);
|
||||
|
||||
/*
|
||||
Function: str_skip_to_whitespace
|
||||
Skips leading non-whitespace characters(all but ' ', '\t', '\n', '\r').
|
||||
|
@ -928,6 +940,12 @@ void str_clean_whitespaces(char *str);
|
|||
*/
|
||||
char *str_skip_to_whitespace(char *str);
|
||||
|
||||
/*
|
||||
Function: str_skip_to_whitespace_const
|
||||
See str_skip_to_whitespace.
|
||||
*/
|
||||
const char *str_skip_to_whitespace_const(const char *str);
|
||||
|
||||
/*
|
||||
Function: str_skip_whitespaces
|
||||
Skips leading whitespace characters(' ', '\t', '\n', '\r').
|
||||
|
|
|
@ -22,9 +22,28 @@
|
|||
|
||||
CChat::CChat()
|
||||
{
|
||||
// init chat commands (must be in alphabetical order)
|
||||
static CChatCommand s_aapCommands[] = {
|
||||
{"/all", " - Switch to all chat", &Com_All},
|
||||
{"/friend", " <player name>", &Com_Befriend},
|
||||
{"/m", " <player name>", &Com_Mute},
|
||||
{"/mute", " <player name>", &Com_Mute},
|
||||
{"/r", " - Reply to a whisper", &Com_Reply},
|
||||
{"/team", " - Switch to team chat", &Com_Team},
|
||||
{"/w", " <player name>", &Com_Whisper},
|
||||
{"/whisper", " <player name>", &Com_Whisper},
|
||||
};
|
||||
const int CommandsCount = sizeof(s_aapCommands) / sizeof(CChatCommand);
|
||||
m_pCommands = new CChatCommands(s_aapCommands, CommandsCount);
|
||||
|
||||
OnReset();
|
||||
}
|
||||
|
||||
CChat::~CChat()
|
||||
{
|
||||
delete m_pCommands;
|
||||
}
|
||||
|
||||
void CChat::OnReset()
|
||||
{
|
||||
for(int i = 0; i < MAX_LINES; i++)
|
||||
|
@ -35,6 +54,8 @@ void CChat::OnReset()
|
|||
}
|
||||
|
||||
m_Mode = CHAT_NONE;
|
||||
// m_WhisperTarget = -1;
|
||||
m_LastWhisperFrom = -1;
|
||||
m_ReverseCompletion = false;
|
||||
m_Show = false;
|
||||
m_InputUpdate = false;
|
||||
|
@ -47,6 +68,8 @@ void CChat::OnReset()
|
|||
m_pHistoryEntry = 0x0;
|
||||
m_PendingChatCounter = 0;
|
||||
m_LastChatSend = 0;
|
||||
m_IgnoreCommand = false;
|
||||
m_pCommands->Reset();
|
||||
|
||||
for(int i = 0; i < CHAT_NUM; ++i)
|
||||
m_aLastSoundPlayed[i] = 0;
|
||||
|
@ -160,36 +183,50 @@ bool CChat::OnInput(IInput::CEvent Event)
|
|||
|
||||
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
|
||||
{
|
||||
m_Mode = CHAT_NONE;
|
||||
m_pClient->OnRelease();
|
||||
if(IsTypingCommand())
|
||||
{
|
||||
m_IgnoreCommand = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Mode = CHAT_NONE;
|
||||
m_pClient->OnRelease();
|
||||
}
|
||||
}
|
||||
else if(Event.m_Flags&IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER))
|
||||
{
|
||||
if(m_Input.GetString()[0])
|
||||
if(IsTypingCommand() && ExecuteCommand())
|
||||
{
|
||||
bool AddEntry = false;
|
||||
|
||||
if(m_PendingChatCounter == 0 && m_LastChatSend+time_freq() < time_get())
|
||||
{
|
||||
Say(m_Mode, m_Input.GetString());
|
||||
AddEntry = true;
|
||||
}
|
||||
else if(m_PendingChatCounter < 3)
|
||||
{
|
||||
++m_PendingChatCounter;
|
||||
AddEntry = true;
|
||||
}
|
||||
|
||||
if(AddEntry)
|
||||
{
|
||||
CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+m_Input.GetLength());
|
||||
pEntry->m_Mode = m_Mode;
|
||||
mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength()+1);
|
||||
}
|
||||
// everything is handled within
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_Input.GetString()[0])
|
||||
{
|
||||
bool AddEntry = false;
|
||||
|
||||
if(m_PendingChatCounter == 0 && m_LastChatSend+time_freq() < time_get())
|
||||
{
|
||||
Say(m_Mode, m_Input.GetString());
|
||||
AddEntry = true;
|
||||
}
|
||||
else if(m_PendingChatCounter < 3)
|
||||
{
|
||||
++m_PendingChatCounter;
|
||||
AddEntry = true;
|
||||
}
|
||||
|
||||
if(AddEntry)
|
||||
{
|
||||
CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+m_Input.GetLength());
|
||||
pEntry->m_Mode = m_Mode;
|
||||
mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength()+1);
|
||||
}
|
||||
}
|
||||
m_pHistoryEntry = 0x0;
|
||||
m_Mode = CHAT_NONE;
|
||||
m_pClient->OnRelease();
|
||||
}
|
||||
m_pHistoryEntry = 0x0;
|
||||
m_Mode = CHAT_NONE;
|
||||
m_pClient->OnRelease();
|
||||
}
|
||||
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_TAB)
|
||||
{
|
||||
|
@ -324,48 +361,74 @@ bool CChat::OnInput(IInput::CEvent Event)
|
|||
|
||||
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
if(IsTypingCommand())
|
||||
{
|
||||
CHistoryEntry *pTest = m_History.Prev(m_pHistoryEntry);
|
||||
|
||||
if(pTest)
|
||||
m_pHistoryEntry = pTest;
|
||||
m_pCommands->SelectPreviousCommand();
|
||||
}
|
||||
else
|
||||
m_pHistoryEntry = m_History.Last();
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
{
|
||||
CHistoryEntry *pTest = m_History.Prev(m_pHistoryEntry);
|
||||
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry->m_aText);
|
||||
if(pTest)
|
||||
m_pHistoryEntry = pTest;
|
||||
}
|
||||
else
|
||||
m_pHistoryEntry = m_History.Last();
|
||||
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry->m_aText);
|
||||
}
|
||||
}
|
||||
else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
|
||||
|
||||
if (m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry->m_aText);
|
||||
if(IsTypingCommand())
|
||||
{
|
||||
m_pCommands->SelectNextCommand();
|
||||
}
|
||||
else
|
||||
m_Input.Clear();
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
|
||||
|
||||
if (m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry->m_aText);
|
||||
else
|
||||
m_Input.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CChat::EnableMode(int Mode)
|
||||
void CChat::EnableMode(int Mode, const char* pText)
|
||||
{
|
||||
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||||
return;
|
||||
|
||||
if(m_Mode == CHAT_NONE)
|
||||
m_Mode = Mode;
|
||||
ClearInput();
|
||||
|
||||
if(pText) // optional text to initalize with
|
||||
{
|
||||
m_Mode = Mode;
|
||||
m_Input.Clear();
|
||||
Input()->Clear();
|
||||
m_CompletionChosen = -1;
|
||||
m_Input.Set(pText);
|
||||
m_Input.SetCursorOffset(str_length(pText));
|
||||
m_InputUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::ClearInput()
|
||||
{
|
||||
m_Input.Clear();
|
||||
Input()->Clear();
|
||||
m_CompletionChosen = -1;
|
||||
|
||||
m_IgnoreCommand = false;
|
||||
m_pCommands->Reset();
|
||||
}
|
||||
|
||||
void CChat::OnMessage(int MsgType, void *pRawMsg)
|
||||
{
|
||||
if(MsgType == NETMSGTYPE_SV_CHAT)
|
||||
|
@ -377,7 +440,7 @@ void CChat::OnMessage(int MsgType, void *pRawMsg)
|
|||
|
||||
void CChat::AddLine(int ClientID, int Mode, const char *pLine, int TargetID)
|
||||
{
|
||||
if(*pLine == 0 || (ClientID != -1 && (!g_Config.m_ClShowsocial || !m_pClient->m_aClients[ClientID].m_Active || // unknown client
|
||||
if(*pLine == 0 || (ClientID >= 0 && (!g_Config.m_ClShowsocial || !m_pClient->m_aClients[ClientID].m_Active || // unknown client
|
||||
m_pClient->m_aClients[ClientID].m_ChatIgnore ||
|
||||
g_Config.m_ClFilterchat == 2 ||
|
||||
(m_pClient->m_LocalClientID != ClientID && g_Config.m_ClFilterchat == 1 && !m_pClient->m_aClients[ClientID].m_Friend))))
|
||||
|
@ -385,7 +448,7 @@ void CChat::AddLine(int ClientID, int Mode, const char *pLine, int TargetID)
|
|||
if(Mode == CHAT_WHISPER)
|
||||
{
|
||||
// unknown client
|
||||
if(ClientID == -1 || !m_pClient->m_aClients[ClientID].m_Active || TargetID == -1 || !m_pClient->m_aClients[TargetID].m_Active)
|
||||
if(ClientID < 0 || !m_pClient->m_aClients[ClientID].m_Active || TargetID < 0 || !m_pClient->m_aClients[TargetID].m_Active)
|
||||
return;
|
||||
// should be sender or receiver
|
||||
if(ClientID != m_pClient->m_LocalClientID && TargetID != m_pClient->m_LocalClientID)
|
||||
|
@ -451,7 +514,7 @@ void CChat::AddLine(int ClientID, int Mode, const char *pLine, int TargetID)
|
|||
// check for highlighted name
|
||||
Highlighted = false;
|
||||
// do not highlight our own messages, whispers and system messages
|
||||
if(Mode != CHAT_WHISPER && ClientID != -1 && ClientID != m_pClient->m_LocalClientID)
|
||||
if(Mode != CHAT_WHISPER && ClientID >= 0 && ClientID != m_pClient->m_LocalClientID)
|
||||
{
|
||||
const char *pHL = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_LocalClientID].m_aName);
|
||||
if(pHL)
|
||||
|
@ -474,11 +537,16 @@ void CChat::AddLine(int ClientID, int Mode, const char *pLine, int TargetID)
|
|||
if(Mode == CHAT_WHISPER && ClientID == m_pClient->m_LocalClientID && TargetID >= 0)
|
||||
NameCID = TargetID;
|
||||
|
||||
if(ClientID == -1) // server message
|
||||
if(ClientID == SERVER_MSG)
|
||||
{
|
||||
m_aLines[m_CurrentLine].m_aName[0] = 0;
|
||||
str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "*** %s", pLine);
|
||||
}
|
||||
else if(ClientID == CLIENT_MSG)
|
||||
{
|
||||
m_aLines[m_CurrentLine].m_aName[0] = 0;
|
||||
str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "— %s", pLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_pClient->m_aClients[ClientID].m_Team == TEAM_SPECTATORS)
|
||||
|
@ -509,9 +577,12 @@ void CChat::AddLine(int ClientID, int Mode, const char *pLine, int TargetID)
|
|||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, aBufMode, aBuf, Highlighted || Mode == CHAT_WHISPER);
|
||||
}
|
||||
|
||||
if(Mode == CHAT_WHISPER && m_pClient->m_LocalClientID != ClientID)
|
||||
m_LastWhisperFrom = ClientID; // we received a a whisper
|
||||
|
||||
// play sound
|
||||
int64 Now = time_get();
|
||||
if(ClientID == -1)
|
||||
if(ClientID < 0)
|
||||
{
|
||||
if(Now-m_aLastSoundPlayed[CHAT_SERVER] >= time_freq()*3/10)
|
||||
{
|
||||
|
@ -566,6 +637,8 @@ void CChat::OnRender()
|
|||
const int LocalCID = m_pClient->m_LocalClientID;
|
||||
const CGameClient::CClientData& LocalClient = m_pClient->m_aClients[LocalCID];
|
||||
const int LocalTteam = LocalClient.m_Team;
|
||||
// bool showCommands;
|
||||
float CategoryWidth = 0;
|
||||
|
||||
if(m_Mode == CHAT_WHISPER && !m_pClient->m_aClients[m_WhisperTarget].m_Active)
|
||||
m_Mode = CHAT_NONE;
|
||||
|
@ -573,8 +646,8 @@ void CChat::OnRender()
|
|||
{
|
||||
// calculate category text size
|
||||
// TODO: rework TextRender. Writing the same code twice to calculate a simple thing as width is ridiculus
|
||||
float CategoryWidth = 0;
|
||||
float CategoryHeight;
|
||||
const float IconOffsetX = m_Mode == CHAT_WHISPER ? 6.0f : 0.0f;
|
||||
const float CategoryFontSize = 8.0f;
|
||||
const float InputFontSize = 8.0f;
|
||||
char aCatText[48];
|
||||
|
@ -617,8 +690,6 @@ void CChat::OnRender()
|
|||
else if(m_Mode == CHAT_WHISPER)
|
||||
CatRectColor = CRCWhisper;
|
||||
|
||||
const float IconOffsetX = m_Mode == CHAT_WHISPER ? 6.0f : 0.0f;
|
||||
|
||||
CUIRect CatRect;
|
||||
CatRect.x = 0;
|
||||
CatRect.y = y;
|
||||
|
@ -758,7 +829,7 @@ void CChat::OnRender()
|
|||
else if(Line.m_Mode == CHAT_WHISPER)
|
||||
str_format(aBuf, sizeof(aBuf), "[%s] ", Localize("Whisper"));
|
||||
|
||||
if(Line.m_ClientID != -1)
|
||||
if(Line.m_ClientID >= 0)
|
||||
{
|
||||
Cursor.m_X += RenderTools()->GetClientIdRectSize(Cursor.m_FontSize);
|
||||
str_append(aBuf, Line.m_aName, sizeof(aBuf));
|
||||
|
@ -908,7 +979,7 @@ void CChat::OnRender()
|
|||
}
|
||||
|
||||
// render name
|
||||
if(Line.m_ClientID == -1)
|
||||
if(Line.m_ClientID < 0)
|
||||
TextColor = ColorSystem;
|
||||
else if(Line.m_Mode == CHAT_WHISPER)
|
||||
TextColor = ColorWhisper;
|
||||
|
@ -923,7 +994,7 @@ void CChat::OnRender()
|
|||
else
|
||||
TextColor = ColorAllPre;
|
||||
|
||||
if(Line.m_ClientID != -1)
|
||||
if(Line.m_ClientID >= 0)
|
||||
{
|
||||
int NameCID = Line.m_ClientID;
|
||||
if(Line.m_Mode == CHAT_WHISPER && Line.m_ClientID == m_pClient->m_LocalClientID && Line.m_TargetID >= 0)
|
||||
|
@ -938,7 +1009,7 @@ void CChat::OnRender()
|
|||
}
|
||||
|
||||
// render line
|
||||
if(Line.m_ClientID == -1)
|
||||
if(Line.m_ClientID < 0)
|
||||
TextColor = ColorSystem;
|
||||
else if(Line.m_Mode == CHAT_WHISPER)
|
||||
TextColor = ColorWhisper;
|
||||
|
@ -964,6 +1035,8 @@ void CChat::OnRender()
|
|||
|
||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.3f);
|
||||
|
||||
HandleCommands(x+CategoryWidth, Height - 24.f, 200.0f-CategoryWidth);
|
||||
}
|
||||
|
||||
void CChat::Say(int Mode, const char *pLine)
|
||||
|
@ -977,3 +1050,319 @@ void CChat::Say(int Mode, const char *pLine)
|
|||
Msg.m_pMessage = pLine;
|
||||
Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
|
||||
}
|
||||
|
||||
bool CChat::IsTypingCommand() const
|
||||
{
|
||||
return m_Input.GetString()[0] == '/' && !m_IgnoreCommand;
|
||||
}
|
||||
|
||||
// chat commands handlers
|
||||
void CChat::HandleCommands(float x, float y, float w)
|
||||
{
|
||||
// render commands menu
|
||||
if(m_Mode != CHAT_NONE && IsTypingCommand())
|
||||
{
|
||||
const float Alpha = 0.90f;
|
||||
const float LineWidth = w;
|
||||
const float LineHeight = 8.0f;
|
||||
|
||||
m_pCommands->Filter(m_Input.GetString()); // flag active commands, update selected command
|
||||
const int ActiveCount = m_pCommands->CountActiveCommands();
|
||||
|
||||
if(ActiveCount > 0) // at least one command to display
|
||||
{
|
||||
CUIRect Rect = {x, y-(ActiveCount+1)*LineHeight, LineWidth, (ActiveCount+1)*LineHeight};
|
||||
RenderTools()->DrawUIRect(&Rect, vec4(0.125f, 0.125f, 0.125f, Alpha), CUI::CORNER_ALL, 3.0f);
|
||||
|
||||
// render notification
|
||||
{
|
||||
y -= LineHeight;
|
||||
CTextCursor Cursor;
|
||||
TextRender()->SetCursor(&Cursor, Rect.x + 5.0f, y, 5.0f, TEXTFLAG_RENDER);
|
||||
TextRender()->TextColor(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
TextRender()->TextEx(&Cursor, Localize("Press Enter to confirm or Esc to cancel"), -1);
|
||||
}
|
||||
|
||||
// render commands
|
||||
for(int i = ActiveCount - 1; i >= 0; i--)
|
||||
{
|
||||
y -= LineHeight;
|
||||
CUIRect HighlightRect = {Rect.x, y, LineWidth, LineHeight-1};
|
||||
|
||||
// retrieve command
|
||||
const CChatCommand* pCommand = m_pCommands->GetCommand(i);
|
||||
|
||||
// draw selection box
|
||||
if(pCommand == m_pCommands->GetSelectedCommand())
|
||||
RenderTools()->DrawUIRect(&HighlightRect, vec4(0.25f, 0.25f, 0.6f, Alpha), CUI::CORNER_ALL, 2.0f);
|
||||
|
||||
// print command
|
||||
CTextCursor Cursor;
|
||||
TextRender()->SetCursor(&Cursor, Rect.x + 5.0f, y, 5.0f, TEXTFLAG_RENDER);
|
||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
TextRender()->TextEx(&Cursor, pCommand->m_pCommandText, -1);
|
||||
TextRender()->TextColor(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
TextRender()->TextEx(&Cursor, pCommand->m_pHelpText, -1);
|
||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CChat::ExecuteCommand()
|
||||
{
|
||||
if(m_pCommands->CountActiveCommands() == 0)
|
||||
return false;
|
||||
|
||||
const char* pCommandStr = m_Input.GetString();
|
||||
const CChatCommand* pCommand = m_pCommands->GetSelectedCommand();
|
||||
dbg_assert(pCommand != 0, "selected command does not exist");
|
||||
bool IsFullMatch = str_find(pCommandStr, pCommand->m_pCommandText); // if the command text is fully inside pCommandStr (aka, not a shortcut)
|
||||
|
||||
if(IsFullMatch)
|
||||
{
|
||||
// execute command
|
||||
if(pCommand->m_pfnFunc != 0)
|
||||
pCommand->m_pfnFunc(this, pCommandStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// autocomplete command
|
||||
char aBuf[128];
|
||||
str_copy(aBuf, pCommand->m_pCommandText, sizeof(aBuf));
|
||||
str_append(aBuf, " ", sizeof(aBuf));
|
||||
|
||||
m_Input.Set(aBuf);
|
||||
m_Input.SetCursorOffset(str_length(aBuf));
|
||||
m_InputUpdate = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns -1 if not found or duplicate
|
||||
int CChat::IdentifyNameParameter(const char* pCommand) const
|
||||
{
|
||||
// retrieve name parameter
|
||||
const char* pParameter = str_skip_to_whitespace_const(pCommand);
|
||||
if(!pParameter)
|
||||
return -1;
|
||||
|
||||
// do not count leading and trailing whitespaces
|
||||
char aName[MAX_NAME_LENGTH];
|
||||
str_copy(aName, pParameter+1, sizeof(aName));
|
||||
str_clean_whitespaces_simple(aName);
|
||||
|
||||
int TargetID = -1;
|
||||
for(int i = 0; i < MAX_CLIENTS; ++i)
|
||||
{
|
||||
if(!m_pClient->m_aClients[i].m_Active || i == m_pClient->m_LocalClientID) // skip local user
|
||||
continue;
|
||||
if(str_length(m_pClient->m_aClients[i].m_aName) == str_length(aName) && str_comp(m_pClient->m_aClients[i].m_aName, aName) == 0)
|
||||
{
|
||||
// name strictly matches
|
||||
if(TargetID != -1)
|
||||
{
|
||||
// duplicate; be conservative
|
||||
dbg_msg("chat", "name duplicate found, aborting whisper command");
|
||||
return -1;
|
||||
}
|
||||
TargetID = i;
|
||||
}
|
||||
}
|
||||
return TargetID;
|
||||
}
|
||||
|
||||
|
||||
// callback functions for commands
|
||||
void CChat::Com_All(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
const char* pParameter = str_skip_to_whitespace_const(pCommand);
|
||||
char *pBuf = 0x0;
|
||||
if(pParameter++ && *pParameter) // skip the first space
|
||||
{
|
||||
// save the parameter in a buffer before EnableMode clears it
|
||||
pBuf = (char*)mem_alloc(str_length(pParameter) + 1, 1);
|
||||
str_copy(pBuf, pParameter, str_length(pParameter) + 1);
|
||||
}
|
||||
pChatData->EnableMode(CHAT_ALL, pBuf);
|
||||
mem_free(pBuf);
|
||||
}
|
||||
|
||||
void CChat::Com_Team(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
const char* pParameter = str_skip_to_whitespace_const(pCommand);
|
||||
char *pBuf = 0x0;
|
||||
if(pParameter++ && *pParameter) // skip the first space
|
||||
{
|
||||
// save the parameter in a buffer before EnableMode clears it
|
||||
pBuf = (char*)mem_alloc(str_length(pParameter) + 1, 1);
|
||||
str_copy(pBuf, pParameter, str_length(pParameter) + 1);
|
||||
}
|
||||
pChatData->EnableMode(CHAT_TEAM, pBuf);
|
||||
mem_free(pBuf);
|
||||
}
|
||||
|
||||
void CChat::Com_Reply(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
if(pChatData->m_LastWhisperFrom == -1)
|
||||
pChatData->ClearInput(); // just reset the chat
|
||||
else
|
||||
{
|
||||
pChatData->m_WhisperTarget = pChatData->m_LastWhisperFrom;
|
||||
|
||||
const char* pParameter = str_skip_to_whitespace_const(pCommand);
|
||||
char *pBuf = 0x0;
|
||||
if(pParameter++ && *pParameter) // skip the first space
|
||||
{
|
||||
// save the parameter in a buffer before EnableMode clears it
|
||||
pBuf = (char*)mem_alloc(str_length(pParameter) + 1, 1);
|
||||
str_copy(pBuf, pParameter, sizeof(pBuf));
|
||||
}
|
||||
pChatData->EnableMode(CHAT_WHISPER, pBuf);
|
||||
mem_free(pBuf);
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::Com_Whisper(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
int TargetID = pChatData->IdentifyNameParameter(pCommand);
|
||||
if(TargetID != -1)
|
||||
{
|
||||
pChatData->m_WhisperTarget = TargetID;
|
||||
pChatData->EnableMode(CHAT_WHISPER);
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::Com_Mute(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
int TargetID = pChatData->IdentifyNameParameter(pCommand);
|
||||
if(TargetID != -1)
|
||||
{
|
||||
pChatData->m_pClient->m_aClients[TargetID].m_ChatIgnore ^= 1;
|
||||
|
||||
pChatData->ClearInput();
|
||||
|
||||
char aMsg[128];
|
||||
str_format(aMsg, sizeof(aMsg), pChatData->m_pClient->m_aClients[TargetID].m_ChatIgnore ? Localize("'%s' was muted") : Localize("'%s' was unmuted"), pChatData->m_pClient->m_aClients[TargetID].m_aName);
|
||||
pChatData->AddLine(CLIENT_MSG, CHAT_ALL, aMsg, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::Com_Befriend(CChat *pChatData, const char* pCommand)
|
||||
{
|
||||
int TargetID = pChatData->IdentifyNameParameter(pCommand);
|
||||
if(TargetID != -1)
|
||||
{
|
||||
bool isFriend = pChatData->m_pClient->m_aClients[TargetID].m_Friend;
|
||||
if(isFriend)
|
||||
pChatData->m_pClient->Friends()->RemoveFriend(pChatData->m_pClient->m_aClients[TargetID].m_aName, pChatData->m_pClient->m_aClients[TargetID].m_aClan);
|
||||
else
|
||||
pChatData->m_pClient->Friends()->AddFriend(pChatData->m_pClient->m_aClients[TargetID].m_aName, pChatData->m_pClient->m_aClients[TargetID].m_aClan);
|
||||
pChatData->m_pClient->m_aClients[TargetID].m_Friend ^= 1;
|
||||
|
||||
pChatData->ClearInput();
|
||||
|
||||
char aMsg[128];
|
||||
str_format(aMsg, sizeof(aMsg), !isFriend ? Localize("'%s' was added as a friend") : Localize("'%s' was removed as a friend"), pChatData->m_pClient->m_aClients[TargetID].m_aName);
|
||||
pChatData->AddLine(CLIENT_MSG, CHAT_ALL, aMsg, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CChatCommands methods
|
||||
CChat::CChatCommands::CChatCommands(CChatCommand apCommands[], int Count) : m_apCommands(apCommands), m_Count(Count), m_pSelectedCommand(0x0) { }
|
||||
CChat::CChatCommands::~CChatCommands() { }
|
||||
|
||||
// selection
|
||||
void CChat::CChatCommands::Reset()
|
||||
{
|
||||
m_pSelectedCommand = 0x0;
|
||||
}
|
||||
|
||||
// Example: /whisper command will match "/whi", "/whisper" and "/whisper tee"
|
||||
void CChat::CChatCommands::Filter(const char* pLine)
|
||||
{
|
||||
char aCommandStr[64];
|
||||
str_copy(aCommandStr, pLine, sizeof(aCommandStr));
|
||||
// truncate the string at the first whitespace to get the command
|
||||
char* pFirstWhitespace = str_skip_to_whitespace(aCommandStr);
|
||||
if(pFirstWhitespace)
|
||||
*pFirstWhitespace = 0;
|
||||
|
||||
for(int i = 0; i < m_Count; i++)
|
||||
m_apCommands[i].m_aFiltered = (str_find_nocase(m_apCommands[i].m_pCommandText, aCommandStr) != m_apCommands[i].m_pCommandText);
|
||||
|
||||
// also update selected command
|
||||
if(!GetSelectedCommand() || GetSelectedCommand()->m_aFiltered)
|
||||
{
|
||||
if(CountActiveCommands() > 0)
|
||||
m_pSelectedCommand = &m_apCommands[GetActiveIndex(0)]; // default to first command
|
||||
else
|
||||
m_pSelectedCommand = 0x0;
|
||||
}
|
||||
}
|
||||
|
||||
// this will not return a correct value if we are not writing a command (m_Input.GetString()[0] == '/')
|
||||
int CChat::CChatCommands::CountActiveCommands() const
|
||||
{
|
||||
int n = m_Count;
|
||||
for(int i = 0; i < m_Count; i++)
|
||||
n -= m_apCommands[i].m_aFiltered;
|
||||
return n;
|
||||
}
|
||||
|
||||
const CChat::CChatCommand* CChat::CChatCommands::GetCommand(int index) const
|
||||
{
|
||||
return &m_apCommands[GetActiveIndex(index)];
|
||||
}
|
||||
|
||||
const CChat::CChatCommand* CChat::CChatCommands::GetSelectedCommand() const
|
||||
{
|
||||
return m_pSelectedCommand;
|
||||
}
|
||||
|
||||
void CChat::CChatCommands::SelectPreviousCommand()
|
||||
{
|
||||
CChatCommand* LastCommand = 0x0;
|
||||
for(int i = 0; i < m_Count; i++)
|
||||
{
|
||||
if(m_apCommands[i].m_aFiltered)
|
||||
continue;
|
||||
if(&m_apCommands[i] == m_pSelectedCommand)
|
||||
{
|
||||
m_pSelectedCommand = LastCommand;
|
||||
return;
|
||||
}
|
||||
LastCommand = &m_apCommands[i];
|
||||
}
|
||||
}
|
||||
|
||||
void CChat::CChatCommands::SelectNextCommand()
|
||||
{
|
||||
bool FoundSelected = false;
|
||||
for(int i = 0; i < m_Count; i++)
|
||||
{
|
||||
if(m_apCommands[i].m_aFiltered)
|
||||
continue;
|
||||
if(FoundSelected)
|
||||
{
|
||||
m_pSelectedCommand = &m_apCommands[i];
|
||||
return;
|
||||
}
|
||||
if(&m_apCommands[i] == m_pSelectedCommand)
|
||||
FoundSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
int CChat::CChatCommands::GetActiveIndex(int index) const
|
||||
{
|
||||
for(int i = 0; i < m_Count; i++)
|
||||
{
|
||||
if(m_apCommands[i].m_aFiltered)
|
||||
index++;
|
||||
if(i == index)
|
||||
return i;
|
||||
}
|
||||
dbg_break();
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
#ifndef GAME_CLIENT_COMPONENTS_CHAT_H
|
||||
#define GAME_CLIENT_COMPONENTS_CHAT_H
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/ringbuffer.h>
|
||||
#include <game/client/component.h>
|
||||
#include <game/client/lineinput.h>
|
||||
|
@ -28,10 +29,17 @@ class CChat : public CComponent
|
|||
bool m_Highlighted;
|
||||
};
|
||||
|
||||
// client IDs for special messages
|
||||
enum
|
||||
{
|
||||
CLIENT_MSG = -2,
|
||||
SERVER_MSG = -1,
|
||||
};
|
||||
|
||||
CLine m_aLines[MAX_LINES];
|
||||
int m_CurrentLine;
|
||||
|
||||
// chat
|
||||
// chat sounds
|
||||
enum
|
||||
{
|
||||
CHAT_SERVER=0,
|
||||
|
@ -42,6 +50,7 @@ class CChat : public CComponent
|
|||
|
||||
int m_Mode;
|
||||
int m_WhisperTarget;
|
||||
int m_LastWhisperFrom;
|
||||
bool m_Show;
|
||||
bool m_InputUpdate;
|
||||
int m_ChatStringOffset;
|
||||
|
@ -64,6 +73,51 @@ class CChat : public CComponent
|
|||
int64 m_LastChatSend;
|
||||
int64 m_aLastSoundPlayed[CHAT_NUM];
|
||||
|
||||
// chat commands
|
||||
struct CChatCommand
|
||||
{
|
||||
const char* m_pCommandText;
|
||||
const char* m_pHelpText;
|
||||
void (*m_pfnFunc)(CChat *pChatData, const char* pCommand);
|
||||
bool m_aFiltered; // 0 = shown, 1 = hidden
|
||||
};
|
||||
|
||||
class CChatCommands
|
||||
{
|
||||
CChatCommand *m_apCommands;
|
||||
int m_Count;
|
||||
CChatCommand *m_pSelectedCommand;
|
||||
|
||||
private:
|
||||
int GetActiveIndex(int index) const;
|
||||
public:
|
||||
CChatCommands(CChatCommand apCommands[], int Count);
|
||||
~CChatCommands();
|
||||
void Reset();
|
||||
void Filter(const char* pLine);
|
||||
int CountActiveCommands() const;
|
||||
const CChatCommand* GetCommand(int index) const;
|
||||
const CChatCommand* GetSelectedCommand() const;
|
||||
void SelectPreviousCommand();
|
||||
void SelectNextCommand();
|
||||
};
|
||||
|
||||
CChatCommands *m_pCommands;
|
||||
bool m_IgnoreCommand;
|
||||
bool IsTypingCommand() const;
|
||||
void HandleCommands(float x, float y, float w);
|
||||
bool ExecuteCommand();
|
||||
int IdentifyNameParameter(const char* pCommand) const;
|
||||
|
||||
static void Com_All(CChat *pChatData, const char* pCommand);
|
||||
static void Com_Team(CChat *pChatData, const char* pCommand);
|
||||
static void Com_Reply(CChat *pChatData, const char* pCommand);
|
||||
static void Com_Whisper(CChat *pChatData, const char* pCommand);
|
||||
static void Com_Mute(CChat *pChatData, const char* pCommand);
|
||||
static void Com_Befriend(CChat *pChatData, const char* pCommand);
|
||||
|
||||
void ClearInput();
|
||||
|
||||
static void ConSay(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConSayTeam(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConWhisper(IConsole::IResult *pResult, void *pUserData);
|
||||
|
@ -72,12 +126,13 @@ class CChat : public CComponent
|
|||
|
||||
public:
|
||||
CChat();
|
||||
~CChat();
|
||||
|
||||
bool IsActive() const { return m_Mode != CHAT_NONE; }
|
||||
|
||||
void AddLine(int ClientID, int Team, const char *pLine, int TargetID = -1);
|
||||
|
||||
void EnableMode(int Team);
|
||||
void EnableMode(int Team, const char* pText = NULL);
|
||||
|
||||
void Say(int Team, const char *pLine);
|
||||
|
||||
|
|
Loading…
Reference in a new issue