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>
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;
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()
51 void CChat::OnStateChange(int NewState
, int OldState
)
53 if(OldState
<= IClient::STATE_CONNECTING
)
56 for(int i
= 0; i
< MAX_LINES
; i
++)
57 m_aLines
[i
].m_Time
= 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);
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
)
101 if(Event
.m_Flags
&IInput::FLAG_PRESS
&& Event
.m_Key
== KEY_ESCAPE
)
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;
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
])
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
))
150 else if(!str_comp_nocase_num(m_pClient
->m_aClients
[Index
].m_aName
, m_aCompletionBuffer
, str_length(m_aCompletionBuffer
)))
155 pCompletionString
= m_pClient
->m_aClients
[Index
].m_aName
;
156 m_CompletionChosen
= Index
+SearchType
*MAX_CLIENTS
;
162 if(pCompletionString
)
165 // add part before the name
166 str_copy(aBuf
, m_Input
.GetString(), min(static_cast<int>(sizeof(aBuf
)), m_PlaceholderOffset
+1));
169 str_append(aBuf
, pCompletionString
, sizeof(aBuf
));
172 const char *pSeparator
= "";
173 if(*(m_Input
.GetString()+m_PlaceholderOffset
+m_PlaceholderLength
) != ' ')
174 pSeparator
= m_PlaceholderOffset
== 0 ? ": " : " ";
175 else if(m_PlaceholderOffset
== 0)
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();
186 m_Input
.SetCursorOffset(m_PlaceholderOffset
+m_PlaceholderLength
);
187 m_InputUpdate
= true;
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
)
204 char *pTest
= m_History
.Prev(m_pHistoryEntry
);
207 m_pHistoryEntry
= pTest
;
210 m_pHistoryEntry
= m_History
.Last();
213 m_Input
.Set(m_pHistoryEntry
);
215 else if (Event
.m_Flags
&IInput::FLAG_PRESS
&& Event
.m_Key
== KEY_DOWN
)
218 m_pHistoryEntry
= m_History
.Next(m_pHistoryEntry
);
221 m_Input
.Set(m_pHistoryEntry
);
230 void CChat::EnableMode(int Team
)
232 if(Client()->State() == IClient::STATE_DEMOPLAYBACK
)
235 if(m_Mode
== MODE_NONE
)
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
))
263 bool Highlighted
= false;
264 char *p
= const_cast<char*>(pLine
);
269 // find line seperator and strip multiline
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
);
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] == ' ')))
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
);
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
);
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
);
326 m_pClient
->m_pSounds
->Play(CSounds::CHN_GUI
, SOUND_CHAT_SERVER
, 0, vec2(0,0));
328 m_pClient
->m_pSounds
->Play(CSounds::CHN_GUI
, SOUND_CHAT_HIGHLIGHT
, 0, vec2(0.0f
, 0.0f
));
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
);
338 float y
= 300.0f
-20.0f
;
339 if(m_Mode
!= MODE_NONE
)
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);
352 TextRender()->TextEx(&Cursor
, Localize("Chat"), -1);
354 TextRender()->TextEx(&Cursor
, ": ", -1);
356 // check if the visible text has to be moved
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();
366 CTextCursor Temp
= Cursor
;
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
;
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);
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
;
396 float FontSize
= 6.0f
;
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
)
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
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
;
423 TextRender()->SetCursor(&Cursor
, Begin
, y
, FontSize
, TEXTFLAG_RENDER
);
424 Cursor
.m_LineWidth
= LineWidth
;
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
438 TextRender()->TextColor(0.8f
, 0.8f
, 0.8f
, Blend
);
440 TextRender()->TextEx(&Cursor
, m_aLines
[r
].m_aName
, -1);
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
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
)
463 Msg
.m_pMessage
= pLine
;
464 Client()->SendPackMsg(&Msg
, MSGFLAG_VITAL
);