fixed marker position in chat
[twcon.git] / src / game / client / components / chat.cpp
blobaba38bf65e2b9d4251c33561a886a9ef214af750
1 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2 /* If you are missing that file, acquire a complete release at teeworlds.com. */
4 #include <engine/engine.h>
5 #include <engine/graphics.h>
6 #include <engine/textrender.h>
7 #include <engine/keys.h>
8 #include <engine/shared/config.h>
10 #include <game/generated/protocol.h>
11 #include <game/generated/client_data.h>
13 #include <game/client/gameclient.h>
15 #include <game/client/components/scoreboard.h>
16 #include <game/client/components/sounds.h>
17 #include <game/localization.h>
19 #include "chat.h"
22 CChat::CChat()
24 OnReset();
27 void CChat::OnReset()
29 for(int i = 0; i < MAX_LINES; i++)
31 m_aLines[i].m_Time = 0;
32 m_aLines[i].m_aText[0] = 0;
33 m_aLines[i].m_aName[0] = 0;
36 m_Show = false;
37 m_InputUpdate = false;
38 m_ChatStringOffset = 0;
39 m_CompletionChosen = -1;
40 m_aCompletionBuffer[0] = 0;
41 m_PlaceholderOffset = 0;
42 m_PlaceholderLength = 0;
43 m_pHistoryEntry = 0x0;
46 void CChat::OnRelease()
48 m_Show = false;
51 void CChat::OnStateChange(int NewState, int OldState)
53 if(OldState <= IClient::STATE_CONNECTING)
55 m_Mode = MODE_NONE;
56 for(int i = 0; i < MAX_LINES; i++)
57 m_aLines[i].m_Time = 0;
58 m_CurrentLine = 0;
62 void CChat::ConSay(IConsole::IResult *pResult, void *pUserData)
64 ((CChat*)pUserData)->Say(0, pResult->GetString(0));
67 void CChat::ConSayTeam(IConsole::IResult *pResult, void *pUserData)
69 ((CChat*)pUserData)->Say(1, pResult->GetString(0));
72 void CChat::ConChat(IConsole::IResult *pResult, void *pUserData)
74 const char *pMode = pResult->GetString(0);
75 if(str_comp(pMode, "all") == 0)
76 ((CChat*)pUserData)->EnableMode(0);
77 else if(str_comp(pMode, "team") == 0)
78 ((CChat*)pUserData)->EnableMode(1);
79 else
80 ((CChat*)pUserData)->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", "expected all or team as mode");
83 void CChat::ConShowChat(IConsole::IResult *pResult, void *pUserData)
85 ((CChat *)pUserData)->m_Show = pResult->GetInteger(0) != 0;
88 void CChat::OnConsoleInit()
90 Console()->Register("say", "r", CFGFLAG_CLIENT, ConSay, this, "Say in chat");
91 Console()->Register("say_team", "r", CFGFLAG_CLIENT, ConSayTeam, this, "Say in team chat");
92 Console()->Register("chat", "s", CFGFLAG_CLIENT, ConChat, this, "Enable chat with all/team mode");
93 Console()->Register("+show_chat", "", CFGFLAG_CLIENT, ConShowChat, this, "Show chat");
96 bool CChat::OnInput(IInput::CEvent Event)
98 if(m_Mode == MODE_NONE)
99 return false;
101 if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
103 m_Mode = MODE_NONE;
104 m_pClient->OnRelease();
106 else if(Event.m_Flags&IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER))
108 if(m_Input.GetString()[0])
110 Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString());
111 char *pEntry = m_History.Allocate(m_Input.GetLength()+1);
112 mem_copy(pEntry, m_Input.GetString(), m_Input.GetLength()+1);
114 m_pHistoryEntry = 0x0;
115 m_Mode = MODE_NONE;
116 m_pClient->OnRelease();
118 if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_TAB)
120 // fill the completion buffer
121 if(m_CompletionChosen < 0)
123 const char *pCursor = m_Input.GetString()+m_Input.GetCursorOffset();
124 for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor-1) != ' '; --pCursor, ++Count);
125 m_PlaceholderOffset = pCursor-m_Input.GetString();
127 for(m_PlaceholderLength = 0; *pCursor && *pCursor != ' '; ++pCursor)
128 ++m_PlaceholderLength;
130 str_copy(m_aCompletionBuffer, m_Input.GetString()+m_PlaceholderOffset, min(static_cast<int>(sizeof(m_aCompletionBuffer)), m_PlaceholderLength+1));
133 // find next possible name
134 const char *pCompletionString = 0;
135 m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS);
136 for(int i = 0; i < 2*MAX_CLIENTS; ++i)
138 int SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS;
139 int Index = (m_CompletionChosen+i)%MAX_CLIENTS;
140 if(!m_pClient->m_Snap.m_paPlayerInfos[Index])
141 continue;
143 bool Found = false;
144 if(SearchType == 1)
146 if(str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) &&
147 str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer))
148 Found = true;
150 else if(!str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)))
151 Found = true;
153 if(Found)
155 pCompletionString = m_pClient->m_aClients[Index].m_aName;
156 m_CompletionChosen = Index+SearchType*MAX_CLIENTS;
157 break;
161 // insert the name
162 if(pCompletionString)
164 char aBuf[256];
165 // add part before the name
166 str_copy(aBuf, m_Input.GetString(), min(static_cast<int>(sizeof(aBuf)), m_PlaceholderOffset+1));
168 // add the name
169 str_append(aBuf, pCompletionString, sizeof(aBuf));
171 // add seperator
172 const char *pSeparator = "";
173 if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ')
174 pSeparator = m_PlaceholderOffset == 0 ? ": " : " ";
175 else if(m_PlaceholderOffset == 0)
176 pSeparator = ":";
177 if(*pSeparator)
178 str_append(aBuf, pSeparator, sizeof(aBuf));
180 // add part after the name
181 str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf));
183 m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString);
184 m_OldChatStringLength = m_Input.GetLength();
185 m_Input.Set(aBuf);
186 m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength);
187 m_InputUpdate = true;
190 else
192 // reset name completion process
193 if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key != KEY_TAB)
194 m_CompletionChosen = -1;
196 m_OldChatStringLength = m_Input.GetLength();
197 m_Input.ProcessInput(Event);
198 m_InputUpdate = true;
200 if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
202 if (m_pHistoryEntry)
204 char *pTest = m_History.Prev(m_pHistoryEntry);
206 if (pTest)
207 m_pHistoryEntry = pTest;
209 else
210 m_pHistoryEntry = m_History.Last();
212 if (m_pHistoryEntry)
213 m_Input.Set(m_pHistoryEntry);
215 else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
217 if (m_pHistoryEntry)
218 m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
220 if (m_pHistoryEntry)
221 m_Input.Set(m_pHistoryEntry);
222 else
223 m_Input.Clear();
226 return true;
230 void CChat::EnableMode(int Team)
232 if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
233 return;
235 if(m_Mode == MODE_NONE)
237 if(Team)
238 m_Mode = MODE_TEAM;
239 else
240 m_Mode = MODE_ALL;
242 m_Input.Clear();
243 Input()->ClearEvents();
244 m_CompletionChosen = -1;
248 void CChat::OnMessage(int MsgType, void *pRawMsg)
250 if(MsgType == NETMSGTYPE_SV_CHAT)
252 CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
253 AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage);
257 void CChat::AddLine(int ClientID, int Team, const char *pLine)
259 if(ClientID != -1 && (m_pClient->m_aClients[ClientID].m_aName[0] == '\0' || // unknown client
260 m_pClient->m_aClients[ClientID].m_ChatIgnore))
261 return;
263 bool Highlighted = false;
264 char *p = const_cast<char*>(pLine);
265 while(*p)
267 Highlighted = false;
268 pLine = p;
269 // find line seperator and strip multiline
270 while(*p)
272 if(*p++ == '\n')
274 *(p-1) = 0;
275 break;
279 m_CurrentLine = (m_CurrentLine+1)%MAX_LINES;
280 m_aLines[m_CurrentLine].m_Time = time_get();
281 m_aLines[m_CurrentLine].m_YOffset[0] = -1.0f;
282 m_aLines[m_CurrentLine].m_YOffset[1] = -1.0f;
283 m_aLines[m_CurrentLine].m_ClientID = ClientID;
284 m_aLines[m_CurrentLine].m_Team = Team;
285 m_aLines[m_CurrentLine].m_NameColor = -2;
287 // check for highlighted name
288 const char *pHL = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
289 if(pHL)
291 int Length = str_length(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
292 if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || (pHL[Length] == ':' && pHL[Length+1] == ' ')))
293 Highlighted = true;
295 m_aLines[m_CurrentLine].m_Highlighted = Highlighted;
297 if(ClientID == -1) // server message
299 str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName));
300 str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "%s", pLine);
302 else
304 if(m_pClient->m_aClients[ClientID].m_Team == TEAM_SPECTATORS)
305 m_aLines[m_CurrentLine].m_NameColor = TEAM_SPECTATORS;
307 if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)
309 if(m_pClient->m_aClients[ClientID].m_Team == TEAM_RED)
310 m_aLines[m_CurrentLine].m_NameColor = TEAM_RED;
311 else if(m_pClient->m_aClients[ClientID].m_Team == TEAM_BLUE)
312 m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE;
315 str_copy(m_aLines[m_CurrentLine].m_aName, m_pClient->m_aClients[ClientID].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName));
316 str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), ": %s", pLine);
319 char aBuf[1024];
320 str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText);
321 Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_aLines[m_CurrentLine].m_Team?"teamchat":"chat", aBuf);
324 // play sound
325 if(ClientID == -1)
326 m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0, vec2(0,0));
327 else if(Highlighted)
328 m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0, vec2(0.0f, 0.0f));
329 else
330 m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0, vec2(0,0));
333 void CChat::OnRender()
335 float Width = 300.0f*Graphics()->ScreenAspect();
336 Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f);
337 float x = 5.0f;
338 float y = 300.0f-20.0f;
339 if(m_Mode != MODE_NONE)
341 // render chat input
342 CTextCursor Cursor;
343 TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER);
344 Cursor.m_LineWidth = Width-190.0f;
345 Cursor.m_MaxLines = 2;
347 if(m_Mode == MODE_ALL)
348 TextRender()->TextEx(&Cursor, Localize("All"), -1);
349 else if(m_Mode == MODE_TEAM)
350 TextRender()->TextEx(&Cursor, Localize("Team"), -1);
351 else
352 TextRender()->TextEx(&Cursor, Localize("Chat"), -1);
354 TextRender()->TextEx(&Cursor, ": ", -1);
356 // check if the visible text has to be moved
357 if(m_InputUpdate)
359 if(m_ChatStringOffset > 0 && m_Input.GetLength() < m_OldChatStringLength)
360 m_ChatStringOffset = max(0, m_ChatStringOffset-(m_OldChatStringLength-m_Input.GetLength()));
362 if(m_ChatStringOffset > m_Input.GetCursorOffset())
363 m_ChatStringOffset -= m_ChatStringOffset-m_Input.GetCursorOffset();
364 else
366 CTextCursor Temp = Cursor;
367 Temp.m_Flags = 0;
368 TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
369 TextRender()->TextEx(&Temp, "|", -1);
370 while(Temp.m_LineCount > 2)
372 ++m_ChatStringOffset;
373 Temp = Cursor;
374 Temp.m_Flags = 0;
375 TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
376 TextRender()->TextEx(&Temp, "|", -1);
379 m_InputUpdate = false;
382 TextRender()->TextEx(&Cursor, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
383 static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3;
384 CTextCursor Marker = Cursor;
385 Marker.m_X -= MarkerOffset;
386 TextRender()->TextEx(&Marker, "|", -1);
387 TextRender()->TextEx(&Cursor, m_Input.GetString()+m_Input.GetCursorOffset(), -1);
390 y -= 8.0f;
392 int64 Now = time_get();
393 float LineWidth = m_pClient->m_pScoreboard->Active() ? 90.0f : 200.0f;
394 float HeightLimit = m_pClient->m_pScoreboard->Active() ? 230.0f : m_Show ? 50.0f : 200.0f;
395 float Begin = x;
396 float FontSize = 6.0f;
397 CTextCursor Cursor;
398 int OffsetType = m_pClient->m_pScoreboard->Active() ? 1 : 0;
399 for(int i = 0; i < MAX_LINES; i++)
401 int r = ((m_CurrentLine-i)+MAX_LINES)%MAX_LINES;
402 if(Now > m_aLines[r].m_Time+16*time_freq() && !m_Show)
403 break;
405 // get the y offset (calculate it if we haven't done that yet)
406 if(m_aLines[r].m_YOffset[OffsetType] < 0.0f)
408 TextRender()->SetCursor(&Cursor, Begin, 0.0f, FontSize, 0);
409 Cursor.m_LineWidth = LineWidth;
410 TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1);
411 TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1);
412 m_aLines[r].m_YOffset[OffsetType] = Cursor.m_Y + Cursor.m_FontSize;
414 y -= m_aLines[r].m_YOffset[OffsetType];
416 // cut off if msgs waste too much space
417 if(y < HeightLimit)
418 break;
420 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;
422 // reset the cursor
423 TextRender()->SetCursor(&Cursor, Begin, y, FontSize, TEXTFLAG_RENDER);
424 Cursor.m_LineWidth = LineWidth;
426 // render name
427 if(m_aLines[r].m_ClientID == -1)
428 TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system
429 else if(m_aLines[r].m_Team)
430 TextRender()->TextColor(0.45f, 0.9f, 0.45f, Blend); // team message
431 else if(m_aLines[r].m_NameColor == TEAM_RED)
432 TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // red
433 else if(m_aLines[r].m_NameColor == TEAM_BLUE)
434 TextRender()->TextColor(0.7f, 0.7f, 1.0f, Blend); // blue
435 else if(m_aLines[r].m_NameColor == TEAM_SPECTATORS)
436 TextRender()->TextColor(0.75f, 0.5f, 0.75f, Blend); // spectator
437 else
438 TextRender()->TextColor(0.8f, 0.8f, 0.8f, Blend);
440 TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1);
442 // render line
443 if(m_aLines[r].m_ClientID == -1)
444 TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system
445 else if(m_aLines[r].m_Highlighted)
446 TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // highlighted
447 else if(m_aLines[r].m_Team)
448 TextRender()->TextColor(0.65f, 1.0f, 0.65f, Blend); // team message
449 else
450 TextRender()->TextColor(1.0f, 1.0f, 1.0f, Blend);
452 TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1);
455 TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
458 void CChat::Say(int Team, const char *pLine)
460 // send chat message
461 CNetMsg_Cl_Say Msg;
462 Msg.m_Team = Team;
463 Msg.m_pMessage = pLine;
464 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);