ddnet/src/game/client/components/chat.cpp

983 lines
28 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
2008-08-27 19:41:02 +00:00
2011-08-31 11:56:04 +00:00
#include <base/tl/string.h>
2011-02-27 16:56:03 +00:00
#include <engine/engine.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/textrender.h>
#include <engine/keys.h>
#include <engine/shared/config.h>
2010-05-29 07:25:38 +00:00
#include <game/generated/protocol.h>
#include <game/generated/client_data.h>
2010-05-29 07:25:38 +00:00
#include <game/client/gameclient.h>
2008-08-29 05:34:18 +00:00
#include <game/client/components/scoreboard.h>
2010-05-29 07:25:38 +00:00
#include <game/client/components/sounds.h>
#include <game/localization.h>
2015-08-11 01:10:05 +00:00
#ifdef CONF_PLATFORM_MACOSX
#include <osx/notification.h>
#endif
2010-05-29 07:25:38 +00:00
#include "chat.h"
CChat::CChat()
{
2010-05-29 07:25:38 +00:00
OnReset();
}
void CChat::OnReset()
{
for(int i = 0; i < MAX_LINES; i++)
2009-05-31 09:44:20 +00:00
{
2010-05-29 07:25:38 +00:00
m_aLines[i].m_Time = 0;
m_aLines[i].m_aText[0] = 0;
m_aLines[i].m_aName[0] = 0;
m_aLines[i].m_Friend = false;
2010-05-29 07:25:38 +00:00
}
m_ReverseTAB = false;
m_Mode = MODE_NONE;
m_Show = false;
m_InputUpdate = false;
m_ChatStringOffset = 0;
m_CompletionChosen = -1;
m_aCompletionBuffer[0] = 0;
m_PlaceholderOffset = 0;
m_PlaceholderLength = 0;
2011-03-20 15:17:06 +00:00
m_pHistoryEntry = 0x0;
2012-01-09 22:13:51 +00:00
m_PendingChatCounter = 0;
m_LastChatSend = 0;
for(int i = 0; i < CHAT_NUM; ++i)
2012-01-09 22:13:51 +00:00
m_aLastSoundPlayed[i] = 0;
2010-05-29 07:25:38 +00:00
}
void CChat::OnRelease()
{
m_Show = false;
}
2010-05-29 07:25:38 +00:00
void CChat::OnStateChange(int NewState, int OldState)
{
if(OldState <= IClient::STATE_CONNECTING)
{
m_Mode = MODE_NONE;
Input()->SetIMEState(false);
2009-05-31 09:44:20 +00:00
for(int i = 0; i < MAX_LINES; i++)
2010-05-29 07:25:38 +00:00
m_aLines[i].m_Time = 0;
m_CurrentLine = 0;
2009-05-31 09:44:20 +00:00
}
}
2010-05-29 07:25:38 +00:00
void CChat::ConSay(IConsole::IResult *pResult, void *pUserData)
2008-08-27 19:41:02 +00:00
{
2010-05-29 07:25:38 +00:00
((CChat*)pUserData)->Say(0, pResult->GetString(0));
2008-08-27 19:41:02 +00:00
}
2010-05-29 07:25:38 +00:00
void CChat::ConSayTeam(IConsole::IResult *pResult, void *pUserData)
2008-08-27 19:41:02 +00:00
{
2010-05-29 07:25:38 +00:00
((CChat*)pUserData)->Say(1, pResult->GetString(0));
2008-08-27 19:41:02 +00:00
}
2010-05-29 07:25:38 +00:00
void CChat::ConChat(IConsole::IResult *pResult, void *pUserData)
2008-08-27 19:41:02 +00:00
{
2010-05-29 07:25:38 +00:00
const char *pMode = pResult->GetString(0);
if(str_comp(pMode, "all") == 0)
((CChat*)pUserData)->EnableMode(0);
else if(str_comp(pMode, "team") == 0)
((CChat*)pUserData)->EnableMode(1);
2008-08-27 19:41:02 +00:00
else
((CChat*)pUserData)->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", "expected all or team as mode");
if(pResult->GetString(1)[0] || g_Config.m_ClChatReset)
((CChat*)pUserData)->m_Input.Set(pResult->GetString(1));
2008-08-27 19:41:02 +00:00
}
void CChat::ConShowChat(IConsole::IResult *pResult, void *pUserData)
{
((CChat *)pUserData)->m_Show = pResult->GetInteger(0) != 0;
}
void CChat::ConEcho(IConsole::IResult *pResult, void *pUserData)
{
2017-01-05 23:30:21 +00:00
((CChat *)pUserData)->AddLine(-2, 0, pResult->GetString(0));
}
2010-05-29 07:25:38 +00:00
void CChat::OnConsoleInit()
2008-08-27 19:41:02 +00:00
{
Console()->Register("say", "r[message]", CFGFLAG_CLIENT, ConSay, this, "Say in chat");
Console()->Register("say_team", "r[message]", CFGFLAG_CLIENT, ConSayTeam, this, "Say in team chat");
Console()->Register("chat", "s['team'|'all'] ?r[message]", CFGFLAG_CLIENT, ConChat, this, "Enable chat with all/team mode");
Console()->Register("+show_chat", "", CFGFLAG_CLIENT, ConShowChat, this, "Show chat");
Console()->Register("echo", "r[message]", CFGFLAG_CLIENT, ConEcho, this, "Echo the text in chat window");
2008-08-27 19:41:02 +00:00
}
2011-03-20 15:36:10 +00:00
bool CChat::OnInput(IInput::CEvent Event)
{
2010-05-29 07:25:38 +00:00
if(m_Mode == MODE_NONE)
return false;
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V))
{
const char *Text = Input()->GetClipboardText();
if(Text)
{
// if the text has more than one line, we send all lines except the last one
// the last one is set as in the text field
char Line[256];
int i, Begin = 0;
for(i = 0; i < str_length(Text); i++)
{
if(Text[i] == '\n')
{
2016-05-11 16:00:27 +00:00
int max = min(i - Begin + 1, (int)sizeof(Line));
str_copy(Line, Text + Begin, max);
Begin = i+1;
SayChat(Line);
while(Text[i] == '\n') i++;
}
}
2016-05-11 16:00:27 +00:00
int max = min(i - Begin + 1, (int)sizeof(Line));
str_copy(Line, Text + Begin, max);
Begin = i+1;
m_Input.Add(Line);
}
}
if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C))
{
Input()->SetClipboardText(m_Input.GetString());
}
if(Input()->KeyIsPressed(KEY_LCTRL)) // jump to spaces and special ASCII characters
{
int SearchDirection = 0;
if(Input()->KeyPress(KEY_LEFT) || Input()->KeyPress(KEY_BACKSPACE))
SearchDirection = -1;
else if(Input()->KeyPress(KEY_RIGHT))
SearchDirection = 1;
if(SearchDirection != 0)
{
int FoundAt = SearchDirection > 0 ? m_Input.GetLength() - 1 : 0;
for(int i = m_Input.GetCursorOffset() + SearchDirection; SearchDirection > 0 ? i < m_Input.GetLength() - 1 : i > 0; i += SearchDirection)
{
2017-02-18 08:02:29 +00:00
int Next = i + SearchDirection;
if( (m_Input.GetString()[Next] == ' ') ||
(m_Input.GetString()[Next] >= 32 && m_Input.GetString()[Next] <= 47) ||
(m_Input.GetString()[Next] >= 58 && m_Input.GetString()[Next] <= 64) ||
(m_Input.GetString()[Next] >= 91 && m_Input.GetString()[Next] <= 96))
{
FoundAt = i;
if (SearchDirection < 0)
FoundAt++;
break;
}
}
if(Input()->KeyPress(KEY_BACKSPACE))
{
if(m_Input.GetCursorOffset() != 0)
{
2017-02-18 08:02:29 +00:00
char aText[512];
str_copy(aText, m_Input.GetString(), FoundAt + 1);
2017-02-14 16:06:51 +00:00
if(m_Input.GetCursorOffset() != str_length(m_Input.GetString()))
2017-02-18 08:02:29 +00:00
str_append(aText, m_Input.GetString() + m_Input.GetCursorOffset(), str_length(m_Input.GetString()));
2017-02-14 16:06:51 +00:00
2017-02-18 08:02:29 +00:00
m_Input.Set(aText);
}
}
m_Input.SetCursorOffset(FoundAt);
}
}
2011-03-20 15:36:10 +00:00
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
{
2010-05-29 07:25:38 +00:00
m_Mode = MODE_NONE;
m_pClient->OnRelease();
if(g_Config.m_ClChatReset)
m_Input.Clear();
2016-08-15 06:02:07 +00:00
// abort text editing when pressing escape
Input()->SetIMEState(false);
}
2011-03-20 15:36:10 +00:00
else if(Event.m_Flags&IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER))
{
2010-05-29 07:25:38 +00:00
if(m_Input.GetString()[0])
2011-03-20 15:17:06 +00:00
{
bool AddEntry = false;
2012-01-09 22:13:51 +00:00
if(m_LastChatSend+time_freq() < time_get())
{
2012-01-09 22:13:51 +00:00
Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString());
AddEntry = true;
}
else if(m_PendingChatCounter < 3)
{
2012-01-09 22:13:51 +00:00
++m_PendingChatCounter;
AddEntry = true;
}
if(AddEntry)
{
CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+m_Input.GetLength());
pEntry->m_Team = m_Mode == MODE_ALL ? 0 : 1;
mem_copy(pEntry->m_aText, m_Input.GetString(), m_Input.GetLength()+1);
}
2011-03-20 15:17:06 +00:00
}
m_pHistoryEntry = 0x0;
2010-05-29 07:25:38 +00:00
m_Mode = MODE_NONE;
m_pClient->OnRelease();
m_Input.Clear();
2016-08-15 06:02:07 +00:00
// stop text editing after send chat.
Input()->SetIMEState(false);
}
2011-03-20 15:36:10 +00:00
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_TAB)
{
// fill the completion buffer
if(m_CompletionChosen < 0)
{
const char *pCursor = m_Input.GetString()+m_Input.GetCursorOffset();
for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor-1) != ' '; --pCursor, ++Count);
m_PlaceholderOffset = pCursor-m_Input.GetString();
for(m_PlaceholderLength = 0; *pCursor && *pCursor != ' '; ++pCursor)
++m_PlaceholderLength;
str_copy(m_aCompletionBuffer, m_Input.GetString()+m_PlaceholderOffset, min(static_cast<int>(sizeof(m_aCompletionBuffer)), m_PlaceholderLength+1));
}
// find next possible name
const char *pCompletionString = 0;
if(m_ReverseTAB)
m_CompletionChosen = (m_CompletionChosen-1 + 2*MAX_CLIENTS)%(2*MAX_CLIENTS);
else
m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS);
for(int i = 0; i < 2*MAX_CLIENTS; ++i)
{
int SearchType;
int Index;
if(m_ReverseTAB)
{
SearchType = ((m_CompletionChosen-i +2*MAX_CLIENTS)%(2*MAX_CLIENTS))/MAX_CLIENTS;
Index = (m_CompletionChosen-i + MAX_CLIENTS )%MAX_CLIENTS;
}
else
{
SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS;
Index = (m_CompletionChosen+i)%MAX_CLIENTS;
}
2014-10-06 11:06:35 +00:00
if(!m_pClient->m_Snap.m_paInfoByName[Index])
continue;
2014-10-06 11:06:35 +00:00
int Index2 = m_pClient->m_Snap.m_paInfoByName[Index]->m_ClientID;
bool Found = false;
if(SearchType == 1)
{
2014-10-06 11:06:35 +00:00
if(str_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) &&
str_find_nocase(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer))
Found = true;
}
2014-10-06 11:06:35 +00:00
else if(!str_comp_nocase_num(m_pClient->m_aClients[Index2].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)))
Found = true;
if(Found)
{
2014-10-06 11:06:35 +00:00
pCompletionString = m_pClient->m_aClients[Index2].m_aName;
m_CompletionChosen = Index+SearchType*MAX_CLIENTS;
break;
}
}
// insert the name
if(pCompletionString)
{
char aBuf[256];
// add part before the name
str_copy(aBuf, m_Input.GetString(), min(static_cast<int>(sizeof(aBuf)), m_PlaceholderOffset+1));
// add the name
str_append(aBuf, pCompletionString, sizeof(aBuf));
// add seperator
const char *pSeparator = "";
if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ')
pSeparator = m_PlaceholderOffset == 0 ? ": " : " ";
else if(m_PlaceholderOffset == 0)
pSeparator = ":";
if(*pSeparator)
str_append(aBuf, pSeparator, sizeof(aBuf));
// add part after the name
str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf));
m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString);
m_OldChatStringLength = m_Input.GetLength();
m_Input.Set(aBuf); // TODO: Use Add instead
m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength);
m_InputUpdate = true;
}
}
else
{
// reset name completion process
2011-03-20 15:36:10 +00:00
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key != KEY_TAB)
if(Event.m_Key != KEY_LSHIFT)
m_CompletionChosen = -1;
m_OldChatStringLength = m_Input.GetLength();
2011-03-20 15:36:10 +00:00
m_Input.ProcessInput(Event);
m_InputUpdate = true;
}
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_LSHIFT)
{
m_ReverseTAB = true;
}
else if(Event.m_Flags&IInput::FLAG_RELEASE && Event.m_Key == KEY_LSHIFT)
{
m_ReverseTAB = false;
}
2011-03-20 15:36:10 +00:00
if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
2011-03-20 15:17:06 +00:00
{
2012-01-09 22:13:51 +00:00
if(m_pHistoryEntry)
2011-03-20 15:17:06 +00:00
{
2012-01-09 22:13:51 +00:00
CHistoryEntry *pTest = m_History.Prev(m_pHistoryEntry);
2011-03-20 15:17:06 +00:00
2012-01-09 22:13:51 +00:00
if(pTest)
2011-03-20 15:17:06 +00:00
m_pHistoryEntry = pTest;
}
else
m_pHistoryEntry = m_History.Last();
2012-01-09 22:13:51 +00:00
if(m_pHistoryEntry)
m_Input.Set(m_pHistoryEntry->m_aText);
2011-03-20 15:17:06 +00:00
}
2011-03-20 15:36:10 +00:00
else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
2011-03-20 15:17:06 +00:00
{
2012-01-09 22:13:51 +00:00
if(m_pHistoryEntry)
2011-03-20 15:17:06 +00:00
m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
if (m_pHistoryEntry)
2012-01-09 22:13:51 +00:00
m_Input.Set(m_pHistoryEntry->m_aText);
2011-03-20 15:17:06 +00:00
else
m_Input.Clear();
}
return true;
}
2010-05-29 07:25:38 +00:00
void CChat::EnableMode(int Team)
{
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
return;
2010-05-29 07:25:38 +00:00
if(m_Mode == MODE_NONE)
{
2010-05-29 07:25:38 +00:00
if(Team)
m_Mode = MODE_TEAM;
else
2010-05-29 07:25:38 +00:00
m_Mode = MODE_ALL;
Input()->SetIMEState(true);
Input()->Clear();
m_CompletionChosen = -1;
2014-06-16 11:29:18 +00:00
UI()->AndroidShowTextInput("", Team ? Localize("Team chat") : Localize("Chat"));
}
}
2010-05-29 07:25:38 +00:00
void CChat::OnMessage(int MsgType, void *pRawMsg)
{
2010-05-29 07:25:38 +00:00
if(MsgType == NETMSGTYPE_SV_CHAT)
{
2010-05-29 07:25:38 +00:00
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage);
}
}
2015-07-22 13:37:00 +00:00
bool CChat::LineShouldHighlight(const char *pLine, const char *pName)
{
const char *pHL = str_find_nocase(pLine, pName);
if (pHL)
{
2015-08-17 19:35:12 +00:00
int Length = str_length(pName);
2015-07-22 13:37:00 +00:00
if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || pHL[Length] == '.' || pHL[Length] == '!' || pHL[Length] == ',' || pHL[Length] == '?' || pHL[Length] == ':'))
return true;
}
return false;
}
void CChat::AddLine(int ClientID, int Team, const char *pLine)
{
if(*pLine == 0 ||
(ClientID == -1 && !g_Config.m_ClShowChatSystem) ||
(ClientID >= 0 && (m_pClient->m_aClients[ClientID].m_aName[0] == '\0' || // unknown client
m_pClient->m_aClients[ClientID].m_ChatIgnore ||
2015-07-22 20:16:49 +00:00
(m_pClient->m_Snap.m_LocalClientID != ClientID && g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[ClientID].m_Friend) ||
(m_pClient->m_Snap.m_LocalClientID != ClientID && m_pClient->m_aClients[ClientID].m_Foe))))
return;
2013-12-27 03:09:45 +00:00
// trim right and set maximum length to 256 utf8-characters
2013-04-01 18:30:58 +00:00
int Length = 0;
const char *pStr = pLine;
const char *pEnd = 0;
while(*pStr)
2015-07-09 00:08:14 +00:00
{
2013-04-01 18:30:58 +00:00
const char *pStrOld = pStr;
int Code = str_utf8_decode(&pStr);
// check if unicode is not empty
if(str_utf8_isspace(Code))
2013-04-01 18:30:58 +00:00
{
pEnd = 0;
}
else if(pEnd == 0)
pEnd = pStrOld;
2013-12-27 03:09:45 +00:00
if(++Length >= 256)
2013-04-01 18:30:58 +00:00
{
*(const_cast<char *>(pStr)) = 0;
break;
}
2015-07-09 00:08:14 +00:00
}
2013-04-01 18:30:58 +00:00
if(pEnd != 0)
*(const_cast<char *>(pEnd)) = 0;
bool Highlighted = false;
2010-05-29 07:25:38 +00:00
char *p = const_cast<char*>(pLine);
while(*p)
{
Highlighted = false;
2010-05-29 07:25:38 +00:00
pLine = p;
// find line seperator and strip multiline
while(*p)
{
if(*p++ == '\n')
{
*(p-1) = 0;
break;
}
}
2010-05-29 07:25:38 +00:00
m_CurrentLine = (m_CurrentLine+1)%MAX_LINES;
m_aLines[m_CurrentLine].m_Time = time_get();
2010-10-15 17:26:20 +00:00
m_aLines[m_CurrentLine].m_YOffset[0] = -1.0f;
m_aLines[m_CurrentLine].m_YOffset[1] = -1.0f;
m_aLines[m_CurrentLine].m_ClientID = ClientID;
2010-05-29 07:25:38 +00:00
m_aLines[m_CurrentLine].m_Team = Team;
m_aLines[m_CurrentLine].m_NameColor = -2;
2017-04-26 23:33:44 +00:00
m_aLines[m_CurrentLine].m_Emojis.clear();
2011-08-11 08:59:14 +00:00
// check for highlighted name
2015-07-22 13:37:00 +00:00
if (Client()->State() != IClient::STATE_DEMOPLAYBACK)
{
if(ClientID != m_pClient->m_LocalIDs[0])
2015-08-20 14:01:34 +00:00
{
// main character
if (LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->m_LocalIDs[0]].m_aName))
2015-08-20 14:01:34 +00:00
Highlighted = true;
// dummy
if(m_pClient->Client()->DummyConnected() && LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->m_LocalIDs[1]].m_aName))
2015-08-20 14:01:34 +00:00
Highlighted = true;
}
}
2015-07-22 13:37:00 +00:00
else
2014-05-17 20:45:44 +00:00
{
2015-07-22 13:37:00 +00:00
// on demo playback use local id from snap directly,
// since m_LocalIDs isn't valid there
if (LineShouldHighlight(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName))
Highlighted = true;
2014-05-17 20:45:44 +00:00
}
2015-07-22 13:37:00 +00:00
2014-05-17 20:45:44 +00:00
m_aLines[m_CurrentLine].m_Highlighted = Highlighted;
2010-05-29 07:25:38 +00:00
if(ClientID < 0) // server or client message
2010-05-29 07:25:38 +00:00
{
str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName));
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)
2011-01-03 11:50:38 +00:00
m_aLines[m_CurrentLine].m_NameColor = TEAM_SPECTATORS;
2010-05-29 07:25:38 +00:00
2011-03-04 16:08:10 +00:00
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)
2010-05-29 07:25:38 +00:00
{
if(m_pClient->m_aClients[ClientID].m_Team == TEAM_RED)
2011-01-03 11:50:38 +00:00
m_aLines[m_CurrentLine].m_NameColor = TEAM_RED;
else if(m_pClient->m_aClients[ClientID].m_Team == TEAM_BLUE)
2011-01-03 11:50:38 +00:00
m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE;
2010-05-29 07:25:38 +00:00
}
if (Team == 2) // whisper send
{
str_format(m_aLines[m_CurrentLine].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName), "→ %s", m_pClient->m_aClients[ClientID].m_aName);
m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE;
m_aLines[m_CurrentLine].m_Highlighted = false;
m_aLines[m_CurrentLine].m_Team = 0;
Highlighted = false;
}
else if (Team == 3) // whisper recv
{
str_format(m_aLines[m_CurrentLine].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName), "← %s", m_pClient->m_aClients[ClientID].m_aName);
m_aLines[m_CurrentLine].m_NameColor = TEAM_RED;
m_aLines[m_CurrentLine].m_Highlighted = true;
m_aLines[m_CurrentLine].m_Team = 0;
Highlighted = true;
}
else
str_copy(m_aLines[m_CurrentLine].m_aName, m_pClient->m_aClients[ClientID].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName));
2010-05-29 07:25:38 +00:00
str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), ": %s", pLine);
2017-03-12 16:37:16 +00:00
m_aLines[m_CurrentLine].m_Friend = m_pClient->m_aClients[ClientID].m_Friend;
}
m_aLines[m_CurrentLine].m_Friend = ClientID >= 0 ? m_pClient->m_aClients[ClientID].m_Friend : false;
char aBuffer[64];
int Length = str_length(m_aLines[m_CurrentLine].m_aText);
2017-10-28 07:06:53 +00:00
// lookup for aliases of emojis
for(int i = 0; i < Length; )
{
const char *pIndex1 = str_find(m_aLines[m_CurrentLine].m_aText + i, ":");
if (pIndex1 == NULL) break;
const char *pIndex2 = str_find(pIndex1 + 1, ":");
if (pIndex2 == NULL) break;
i = pIndex2 - m_aLines[m_CurrentLine].m_aText;
2017-10-28 07:06:53 +00:00
// prevents buffer overflow
if ((size_t)(pIndex2 - pIndex1 + 2) > sizeof(aBuffer)) continue;
str_copy(aBuffer, pIndex1, pIndex2 - pIndex1 + 2); // extra place for ":\0"
2017-10-28 07:06:53 +00:00
// skip "::" and those aliases containing space
if (str_length(aBuffer) <= 2 || str_find(aBuffer, " ")) continue;
CEmojis::CEmoji const *pEmoji = m_pClient->m_pEmojis->GetByAlias(aBuffer);
if (pEmoji == NULL) continue;
2017-10-28 07:06:53 +00:00
// to prevent usage of the same ":"
i++;
CEmojis::CEmojiInfo Info;
Info.index = pIndex1 - m_aLines[m_CurrentLine].m_aText;
Info.length = str_length(aBuffer);
Info.m_ID = pEmoji->m_ID;
m_aLines[m_CurrentLine].m_Emojis.add(Info);
}
2017-10-28 07:06:53 +00:00
// lookup for utf emojis
for(int i = 0; i < m_pClient->m_pEmojis->Num(); i++)
{
int Offset = 0;
CEmojis::CEmoji const *pEmoji = m_pClient->m_pEmojis->GetByIndex(i);
const char *pResult = str_find(m_aLines[m_CurrentLine].m_aText + Offset, pEmoji->m_UTF);
while (pResult != NULL)
{
2017-04-26 23:33:44 +00:00
CEmojis::CEmojiInfo Info;
Info.index = pResult - m_aLines[m_CurrentLine].m_aText;
Info.length = str_length(pEmoji->m_UTF);
Info.m_ID = pEmoji->m_ID;
2017-04-26 23:33:44 +00:00
m_aLines[m_CurrentLine].m_Emojis.add(Info);
Offset = Info.index + Info.length;
pResult = str_find(m_aLines[m_CurrentLine].m_aText + Offset, pEmoji->m_UTF);
2017-04-26 23:33:44 +00:00
}
}
m_aLines[m_CurrentLine].m_Emojis.sort_range();
2010-05-29 07:25:38 +00:00
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText);
2014-12-20 12:37:11 +00:00
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, Team >= 2?"whisper":(m_aLines[m_CurrentLine].m_Team?"teamchat":"chat"), aBuf, Highlighted);
}
2010-05-29 07:25:38 +00:00
// play sound
int64 Now = time_get();
if(ClientID == -1) // Server message
{
2013-12-30 18:32:51 +00:00
if(Now-m_aLastSoundPlayed[CHAT_SERVER] >= time_freq()*3/10)
{
if(g_Config.m_SndServerMessage)
{
m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0);
m_aLastSoundPlayed[CHAT_SERVER] = Now;
}
}
}
else if(ClientID == -2) // Client message
{
// No sound yet
}
else if(Highlighted)
{
if(Now-m_aLastSoundPlayed[CHAT_HIGHLIGHT] >= time_freq()*3/10)
{
2015-08-11 01:10:05 +00:00
#ifdef CONF_PLATFORM_MACOSX
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText);
2017-07-26 02:30:56 +00:00
CNotification::Notify("DDNet-Chat", aBuf);
2015-08-11 01:10:05 +00:00
#else
Graphics()->NotifyWindow();
2015-08-11 01:10:05 +00:00
#endif
if(g_Config.m_SndHighlight)
{
m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0);
m_aLastSoundPlayed[CHAT_HIGHLIGHT] = Now;
}
}
}
else if(Team != 2)
{
if(Now-m_aLastSoundPlayed[CHAT_CLIENT] >= time_freq()*3/10)
{
2014-05-04 16:35:37 +00:00
if ((g_Config.m_SndTeamChat || !m_aLines[m_CurrentLine].m_Team)
&& (g_Config.m_SndChat || m_aLines[m_CurrentLine].m_Team))
{
m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0);
m_aLastSoundPlayed[CHAT_CLIENT] = Now;
}
}
}
}
2010-05-29 07:25:38 +00:00
void CChat::OnRender()
{
2012-01-09 22:13:51 +00:00
// send pending chat messages
if(m_PendingChatCounter > 0 && m_LastChatSend+time_freq() < time_get())
{
CHistoryEntry *pEntry = m_History.Last();
for(int i = m_PendingChatCounter-1; pEntry; --i, pEntry = m_History.Prev(pEntry))
{
if(i == 0)
{
Say(pEntry->m_Team, pEntry->m_aText);
break;
}
}
--m_PendingChatCounter;
}
float Width = 300.0f*Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f);
float x = 5.0f;
2008-09-07 08:44:30 +00:00
float y = 300.0f-20.0f;
2010-05-29 07:25:38 +00:00
if(m_Mode != MODE_NONE)
{
// render chat input
2010-05-29 07:25:38 +00:00
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = Width-190.0f;
Cursor.m_MaxLines = 2;
2010-05-29 07:25:38 +00:00
if(m_Mode == MODE_ALL)
TextRender()->TextEx(&Cursor, Localize("All"), -1);
else if(m_Mode == MODE_TEAM)
TextRender()->TextEx(&Cursor, Localize("Team"), -1);
else
2010-05-29 07:25:38 +00:00
TextRender()->TextEx(&Cursor, Localize("Chat"), -1);
2009-06-15 07:34:25 +00:00
2010-05-29 07:25:38 +00:00
TextRender()->TextEx(&Cursor, ": ", -1);
// IME candidate editing
bool Editing = false;
int EditingCursor = Input()->GetEditingCursor();
if (Input()->GetIMEState())
{
if(str_length(Input()->GetIMECandidate()))
{
m_Input.Editing(Input()->GetIMECandidate(), EditingCursor);
Editing = true;
}
}
// check if the visible text has to be moved
if(m_InputUpdate)
{
if(m_ChatStringOffset > 0 && m_Input.GetLength(Editing) < m_OldChatStringLength)
m_ChatStringOffset = max(0, m_ChatStringOffset-(m_OldChatStringLength-m_Input.GetLength(Editing)));
if(m_ChatStringOffset > m_Input.GetCursorOffset(Editing))
m_ChatStringOffset -= m_ChatStringOffset-m_Input.GetCursorOffset(Editing);
else
{
CTextCursor Temp = Cursor;
Temp.m_Flags = 0;
TextRender()->TextEx(&Temp, m_Input.GetString(Editing)+m_ChatStringOffset, m_Input.GetCursorOffset(Editing)-m_ChatStringOffset);
TextRender()->TextEx(&Temp, "|", -1);
while(Temp.m_LineCount > 2)
{
++m_ChatStringOffset;
Temp = Cursor;
Temp.m_Flags = 0;
TextRender()->TextEx(&Temp, m_Input.GetString(Editing)+m_ChatStringOffset, m_Input.GetCursorOffset(Editing)-m_ChatStringOffset);
TextRender()->TextEx(&Temp, "|", -1);
}
}
m_InputUpdate = false;
}
TextRender()->TextEx(&Cursor, m_Input.GetString(Editing)+m_ChatStringOffset, m_Input.GetCursorOffset(Editing)-m_ChatStringOffset);
2011-06-09 21:54:11 +00:00
static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3;
2010-05-29 07:25:38 +00:00
CTextCursor Marker = Cursor;
2011-06-09 21:54:11 +00:00
Marker.m_X -= MarkerOffset;
2010-05-29 07:25:38 +00:00
TextRender()->TextEx(&Marker, "|", -1);
TextRender()->TextEx(&Cursor, m_Input.GetString(Editing)+m_Input.GetCursorOffset(Editing), -1);
}
if (!g_Config.m_ClShowChat)
return;
y -= 8.0f;
2014-06-16 11:29:18 +00:00
#if defined(__ANDROID__)
x += 120.0f;
#endif
2014-06-16 11:29:18 +00:00
#if defined(__ANDROID__)
float FontSize = 10.0f;
#else
2010-10-15 17:26:20 +00:00
float FontSize = 6.0f;
2014-06-16 11:29:18 +00:00
#endif
2017-03-22 08:56:28 +00:00
int64 Now = time_get();
2017-04-10 01:06:16 +00:00
float LineWidth = m_pClient->m_pScoreboard->Active() ? 90.0f : 200.0f;
2017-03-22 08:56:28 +00:00
float HeightLimit = m_pClient->m_pScoreboard->Active() ? 230.0f : m_Show ? 50.0f : 200.0f;
float Begin = x;
2010-10-15 17:26:20 +00:00
CTextCursor Cursor;
int OffsetType = m_pClient->m_pScoreboard->Active() ? 1 : 0;
for(int i = 0; i < MAX_LINES; i++)
{
2017-05-24 21:01:13 +00:00
2010-05-29 07:25:38 +00:00
int r = ((m_CurrentLine-i)+MAX_LINES)%MAX_LINES;
if(Now > m_aLines[r].m_Time+16*time_freq() && !m_Show)
break;
char aName[64] = "";
if(g_Config.m_ClShowIDs && m_aLines[r].m_ClientID != -1 && m_aLines[r].m_aName[0] != '\0')
{
if (m_aLines[r].m_ClientID >= 10)
str_format(aName, sizeof(aName),"%d: ", m_aLines[r].m_ClientID);
else
str_format(aName, sizeof(aName),"%d: ", m_aLines[r].m_ClientID);
str_append(aName, m_aLines[r].m_aName,sizeof(aName));
}
else
{
str_copy(aName, m_aLines[r].m_aName, sizeof(aName));
}
2010-10-15 17:26:20 +00:00
// get the y offset (calculate it if we haven't done that yet)
if(m_aLines[r].m_YOffset[OffsetType] < 0.0f)
{
TextRender()->SetCursor(&Cursor, Begin, 0.0f, FontSize, 0);
Cursor.m_LineWidth = LineWidth;
2017-04-12 23:14:53 +00:00
TextRender()->TextEx(&Cursor, "", -1);
TextRender()->TextEx(&Cursor, aName, -1);
2017-10-28 07:06:53 +00:00
int index = 0;
for(int j = 0; j < m_aLines[r].m_Emojis.size(); j++)
{
CEmojis::CEmojiInfo info = m_aLines[r].m_Emojis[j];
TextRender()->TextEx(&Cursor, &m_aLines[r].m_aText[index], info.index - index);
Cursor.m_X += Cursor.m_FontSize + (Cursor.m_EmojiX - Cursor.m_X);
2017-10-28 07:06:53 +00:00
if(Begin + Cursor.m_LineWidth < Cursor.m_X)
{
2017-10-28 07:06:53 +00:00
Cursor.m_X = Cursor.m_StartX;
Cursor.m_Y += Cursor.m_FontSize;
Cursor.m_LineCount++;
}
2017-10-28 07:06:53 +00:00
index = info.index + info.length;
}
2017-10-28 07:06:53 +00:00
TextRender()->TextEx(&Cursor, &m_aLines[r].m_aText[index], -1);
m_aLines[r].m_YOffset[OffsetType] = Cursor.m_Y + Cursor.m_FontSize;
2010-10-15 17:26:20 +00:00
}
2017-10-28 07:06:53 +00:00
2010-10-15 17:26:20 +00:00
y -= m_aLines[r].m_YOffset[OffsetType];
2008-09-07 08:44:30 +00:00
// cut off if msgs waste too much space
if(y < HeightLimit)
2008-09-07 08:44:30 +00:00
break;
float Blend = Now > m_aLines[r].m_Time+14*time_freq() && !m_Show ? 1.0f-(Now-m_aLines[r].m_Time-14*time_freq())/(2.0f*time_freq()) : 1.0f;
// reset the cursor
2010-05-29 07:25:38 +00:00
TextRender()->SetCursor(&Cursor, Begin, y, FontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = LineWidth;
if(g_Config.m_ClMessageFriend)
{
2017-04-09 01:46:02 +00:00
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageFriendHue / 255.0f, g_Config.m_ClMessageFriendSat / 255.0f, g_Config.m_ClMessageFriendLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, m_aLines[r].m_Friend ? Blend : 0); //Less ugly hack to align messages
TextRender()->TextEx(&Cursor, "", -1);
}
// render name
if (m_aLines[r].m_ClientID == -1) // system
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageSystemHue / 255.0f, g_Config.m_ClMessageSystemSat / 255.0f, g_Config.m_ClMessageSystemLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else if (m_aLines[r].m_ClientID == -2) // client
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageClientHue / 255.0f, g_Config.m_ClMessageClientSat / 255.0f, g_Config.m_ClMessageClientLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else if (m_aLines[r].m_Team)
{
2017-03-06 09:24:00 +00:00
vec3 rgb = CalculateNameColor(vec3(g_Config.m_ClMessageTeamHue / 255.0f, g_Config.m_ClMessageTeamSat / 255.0f, g_Config.m_ClMessageTeamLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend); // team message
}
2011-01-03 11:50:38 +00:00
else if(m_aLines[r].m_NameColor == TEAM_RED)
TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // red
2011-01-03 11:50:38 +00:00
else if(m_aLines[r].m_NameColor == TEAM_BLUE)
TextRender()->TextColor(0.7f, 0.7f, 1.0f, Blend); // blue
2011-01-03 11:50:38 +00:00
else if(m_aLines[r].m_NameColor == TEAM_SPECTATORS)
TextRender()->TextColor(0.75f, 0.5f, 0.75f, Blend); // spectator
2014-04-15 20:10:46 +00:00
else if(m_aLines[r].m_ClientID >= 0 && g_Config.m_ClChatTeamColors && m_pClient->m_Teams.Team(m_aLines[r].m_ClientID))
2014-04-14 22:49:24 +00:00
{
vec3 rgb = HslToRgb(vec3(m_pClient->m_Teams.Team(m_aLines[r].m_ClientID) / 64.0f, 1.0f, 0.75f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
2010-10-15 17:26:20 +00:00
else
TextRender()->TextColor(0.8f, 0.8f, 0.8f, Blend);
TextRender()->TextEx(&Cursor, aName, -1);
// render line
if (m_aLines[r].m_ClientID == -1) // system
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageSystemHue / 255.0f, g_Config.m_ClMessageSystemSat / 255.0f, g_Config.m_ClMessageSystemLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else if (m_aLines[r].m_ClientID == -2) // client
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageClientHue / 255.0f, g_Config.m_ClMessageClientSat / 255.0f, g_Config.m_ClMessageClientLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else if (m_aLines[r].m_Highlighted) // highlighted
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHighlightHue / 255.0f, g_Config.m_ClMessageHighlightSat / 255.0f, g_Config.m_ClMessageHighlightLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else if (m_aLines[r].m_Team) // team message
{
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageTeamHue / 255.0f, g_Config.m_ClMessageTeamSat / 255.0f, g_Config.m_ClMessageTeamLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
else // regular message
{
2014-07-08 17:09:30 +00:00
vec3 rgb = HslToRgb(vec3(g_Config.m_ClMessageHue / 255.0f, g_Config.m_ClMessageSat / 255.0f, g_Config.m_ClMessageLht / 255.0f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, Blend);
}
2015-07-09 00:08:14 +00:00
2017-10-28 07:06:53 +00:00
int index = 0;
2017-05-24 17:37:51 +00:00
2017-10-28 07:06:53 +00:00
for(int j = 0; j < m_aLines[r].m_Emojis.size(); j++)
{
CEmojis::CEmojiInfo info = m_aLines[r].m_Emojis[j];
TextRender()->TextEx(&Cursor, &m_aLines[r].m_aText[index], info.index - index);
m_pClient->m_pEmojis->Render(info.m_ID, (Cursor.m_EmojiX) + Cursor.m_FontSize/2, Cursor.m_Y + Cursor.m_FontSize-2, Cursor.m_FontSize, Cursor.m_FontSize);
Cursor.m_X += Cursor.m_FontSize + (Cursor.m_EmojiX - Cursor.m_X);
2017-05-24 17:37:51 +00:00
2017-10-28 07:06:53 +00:00
if(Begin + Cursor.m_LineWidth < Cursor.m_X)
{
2017-10-28 07:06:53 +00:00
Cursor.m_X = Cursor.m_StartX;
Cursor.m_Y += Cursor.m_FontSize;
Cursor.m_LineCount++;
2017-04-26 23:33:44 +00:00
}
2017-10-28 07:06:53 +00:00
index = info.index + info.length;
2017-04-26 23:33:44 +00:00
}
2017-10-28 07:06:53 +00:00
TextRender()->TextEx(&Cursor, &m_aLines[r].m_aText[index], -1);
}
2010-10-15 17:26:20 +00:00
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
2014-06-16 11:29:18 +00:00
#if defined(__ANDROID__)
static int deferEvent = 0;
if( UI()->AndroidTextInputShown() )
{
if(m_Mode == MODE_NONE)
{
deferEvent++;
if( deferEvent > 2 )
EnableMode(0);
}
else
deferEvent = 0;
}
else
{
if(m_Mode != MODE_NONE)
{
deferEvent++;
if( deferEvent > 2 )
{
IInput::CEvent Event;
Event.m_Flags = IInput::FLAG_PRESS;
Event.m_Key = KEY_RETURN;
OnInput(Event);
}
}
else
deferEvent = 0;
}
#endif
}
2010-05-29 07:25:38 +00:00
void CChat::Say(int Team, const char *pLine)
{
2012-01-09 22:13:51 +00:00
m_LastChatSend = time_get();
// send chat message
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_Say Msg;
Msg.m_Team = Team;
Msg.m_pMessage = pLine;
Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
}
void CChat::SayChat(const char *pLine)
{
if(!pLine || str_length(pLine) < 1)
return;
bool AddEntry = false;
if(m_LastChatSend+time_freq() < time_get())
{
Say(m_Mode == MODE_ALL ? 0 : 1, pLine);
AddEntry = true;
}
else if(m_PendingChatCounter < 3)
{
++m_PendingChatCounter;
AddEntry = true;
}
if(AddEntry)
{
CHistoryEntry *pEntry = m_History.Allocate(sizeof(CHistoryEntry)+str_length(pLine)-1);
pEntry->m_Team = m_Mode == MODE_ALL ? 0 : 1;
mem_copy(pEntry->m_aText, pLine, str_length(pLine));
}
}