PageRenderTime 362ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://github.com/Pilen/teeworlds-dikumod
C++ | 465 lines | 379 code | 65 blank | 21 comment | 141 complexity | 4fcdc1d9e49b0180a0f0d84c6356b905 MD5 | raw file
  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. */
  3. #include <engine/engine.h>
  4. #include <engine/graphics.h>
  5. #include <engine/textrender.h>
  6. #include <engine/keys.h>
  7. #include <engine/shared/config.h>
  8. #include <game/generated/protocol.h>
  9. #include <game/generated/client_data.h>
  10. #include <game/client/gameclient.h>
  11. #include <game/client/components/scoreboard.h>
  12. #include <game/client/components/sounds.h>
  13. #include <game/localization.h>
  14. #include "chat.h"
  15. CChat::CChat()
  16. {
  17. OnReset();
  18. }
  19. void CChat::OnReset()
  20. {
  21. for(int i = 0; i < MAX_LINES; i++)
  22. {
  23. m_aLines[i].m_Time = 0;
  24. m_aLines[i].m_aText[0] = 0;
  25. m_aLines[i].m_aName[0] = 0;
  26. }
  27. m_Show = false;
  28. m_InputUpdate = false;
  29. m_ChatStringOffset = 0;
  30. m_CompletionChosen = -1;
  31. m_aCompletionBuffer[0] = 0;
  32. m_PlaceholderOffset = 0;
  33. m_PlaceholderLength = 0;
  34. m_pHistoryEntry = 0x0;
  35. }
  36. void CChat::OnRelease()
  37. {
  38. m_Show = false;
  39. }
  40. void CChat::OnStateChange(int NewState, int OldState)
  41. {
  42. if(OldState <= IClient::STATE_CONNECTING)
  43. {
  44. m_Mode = MODE_NONE;
  45. for(int i = 0; i < MAX_LINES; i++)
  46. m_aLines[i].m_Time = 0;
  47. m_CurrentLine = 0;
  48. }
  49. }
  50. void CChat::ConSay(IConsole::IResult *pResult, void *pUserData)
  51. {
  52. ((CChat*)pUserData)->Say(0, pResult->GetString(0));
  53. }
  54. void CChat::ConSayTeam(IConsole::IResult *pResult, void *pUserData)
  55. {
  56. ((CChat*)pUserData)->Say(1, pResult->GetString(0));
  57. }
  58. void CChat::ConChat(IConsole::IResult *pResult, void *pUserData)
  59. {
  60. const char *pMode = pResult->GetString(0);
  61. if(str_comp(pMode, "all") == 0)
  62. ((CChat*)pUserData)->EnableMode(0);
  63. else if(str_comp(pMode, "team") == 0)
  64. ((CChat*)pUserData)->EnableMode(1);
  65. else
  66. ((CChat*)pUserData)->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "console", "expected all or team as mode");
  67. }
  68. void CChat::ConShowChat(IConsole::IResult *pResult, void *pUserData)
  69. {
  70. ((CChat *)pUserData)->m_Show = pResult->GetInteger(0) != 0;
  71. }
  72. void CChat::OnConsoleInit()
  73. {
  74. Console()->Register("say", "r", CFGFLAG_CLIENT, ConSay, this, "Say in chat");
  75. Console()->Register("say_team", "r", CFGFLAG_CLIENT, ConSayTeam, this, "Say in team chat");
  76. Console()->Register("chat", "s", CFGFLAG_CLIENT, ConChat, this, "Enable chat with all/team mode");
  77. Console()->Register("+show_chat", "", CFGFLAG_CLIENT, ConShowChat, this, "Show chat");
  78. }
  79. bool CChat::OnInput(IInput::CEvent Event)
  80. {
  81. if(m_Mode == MODE_NONE)
  82. return false;
  83. if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
  84. {
  85. m_Mode = MODE_NONE;
  86. m_pClient->OnRelease();
  87. }
  88. else if(Event.m_Flags&IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER))
  89. {
  90. if(m_Input.GetString()[0])
  91. {
  92. Say(m_Mode == MODE_ALL ? 0 : 1, m_Input.GetString());
  93. char *pEntry = m_History.Allocate(m_Input.GetLength()+1);
  94. mem_copy(pEntry, m_Input.GetString(), m_Input.GetLength()+1);
  95. }
  96. m_pHistoryEntry = 0x0;
  97. m_Mode = MODE_NONE;
  98. m_pClient->OnRelease();
  99. }
  100. if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_TAB)
  101. {
  102. // fill the completion buffer
  103. if(m_CompletionChosen < 0)
  104. {
  105. const char *pCursor = m_Input.GetString()+m_Input.GetCursorOffset();
  106. for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor-1) != ' '; --pCursor, ++Count);
  107. m_PlaceholderOffset = pCursor-m_Input.GetString();
  108. for(m_PlaceholderLength = 0; *pCursor && *pCursor != ' '; ++pCursor)
  109. ++m_PlaceholderLength;
  110. str_copy(m_aCompletionBuffer, m_Input.GetString()+m_PlaceholderOffset, min(static_cast<int>(sizeof(m_aCompletionBuffer)), m_PlaceholderLength+1));
  111. }
  112. // find next possible name
  113. const char *pCompletionString = 0;
  114. m_CompletionChosen = (m_CompletionChosen+1)%(2*MAX_CLIENTS);
  115. for(int i = 0; i < 2*MAX_CLIENTS; ++i)
  116. {
  117. int SearchType = ((m_CompletionChosen+i)%(2*MAX_CLIENTS))/MAX_CLIENTS;
  118. int Index = (m_CompletionChosen+i)%MAX_CLIENTS;
  119. if(!m_pClient->m_Snap.m_paPlayerInfos[Index])
  120. continue;
  121. bool Found = false;
  122. if(SearchType == 1)
  123. {
  124. if(str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)) &&
  125. str_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer))
  126. Found = true;
  127. }
  128. else if(!str_comp_nocase_num(m_pClient->m_aClients[Index].m_aName, m_aCompletionBuffer, str_length(m_aCompletionBuffer)))
  129. Found = true;
  130. if(Found)
  131. {
  132. pCompletionString = m_pClient->m_aClients[Index].m_aName;
  133. m_CompletionChosen = Index+SearchType*MAX_CLIENTS;
  134. break;
  135. }
  136. }
  137. // insert the name
  138. if(pCompletionString)
  139. {
  140. char aBuf[256];
  141. // add part before the name
  142. str_copy(aBuf, m_Input.GetString(), min(static_cast<int>(sizeof(aBuf)), m_PlaceholderOffset+1));
  143. // add the name
  144. str_append(aBuf, pCompletionString, sizeof(aBuf));
  145. // add seperator
  146. const char *pSeparator = "";
  147. if(*(m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength) != ' ')
  148. pSeparator = m_PlaceholderOffset == 0 ? ": " : " ";
  149. else if(m_PlaceholderOffset == 0)
  150. pSeparator = ":";
  151. if(*pSeparator)
  152. str_append(aBuf, pSeparator, sizeof(aBuf));
  153. // add part after the name
  154. str_append(aBuf, m_Input.GetString()+m_PlaceholderOffset+m_PlaceholderLength, sizeof(aBuf));
  155. m_PlaceholderLength = str_length(pSeparator)+str_length(pCompletionString);
  156. m_OldChatStringLength = m_Input.GetLength();
  157. m_Input.Set(aBuf);
  158. m_Input.SetCursorOffset(m_PlaceholderOffset+m_PlaceholderLength);
  159. m_InputUpdate = true;
  160. }
  161. }
  162. else
  163. {
  164. // reset name completion process
  165. if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key != KEY_TAB)
  166. m_CompletionChosen = -1;
  167. m_OldChatStringLength = m_Input.GetLength();
  168. m_Input.ProcessInput(Event);
  169. m_InputUpdate = true;
  170. }
  171. if(Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_UP)
  172. {
  173. if (m_pHistoryEntry)
  174. {
  175. char *pTest = m_History.Prev(m_pHistoryEntry);
  176. if (pTest)
  177. m_pHistoryEntry = pTest;
  178. }
  179. else
  180. m_pHistoryEntry = m_History.Last();
  181. if (m_pHistoryEntry)
  182. m_Input.Set(m_pHistoryEntry);
  183. }
  184. else if (Event.m_Flags&IInput::FLAG_PRESS && Event.m_Key == KEY_DOWN)
  185. {
  186. if (m_pHistoryEntry)
  187. m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
  188. if (m_pHistoryEntry)
  189. m_Input.Set(m_pHistoryEntry);
  190. else
  191. m_Input.Clear();
  192. }
  193. return true;
  194. }
  195. void CChat::EnableMode(int Team)
  196. {
  197. if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
  198. return;
  199. if(m_Mode == MODE_NONE)
  200. {
  201. if(Team)
  202. m_Mode = MODE_TEAM;
  203. else
  204. m_Mode = MODE_ALL;
  205. m_Input.Clear();
  206. Input()->ClearEvents();
  207. m_CompletionChosen = -1;
  208. }
  209. }
  210. void CChat::OnMessage(int MsgType, void *pRawMsg)
  211. {
  212. if(MsgType == NETMSGTYPE_SV_CHAT)
  213. {
  214. CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
  215. AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage);
  216. }
  217. }
  218. void CChat::AddLine(int ClientID, int Team, const char *pLine)
  219. {
  220. if(ClientID != -1 && (m_pClient->m_aClients[ClientID].m_aName[0] == '\0' || // unknown client
  221. m_pClient->m_aClients[ClientID].m_ChatIgnore))
  222. return;
  223. bool Highlighted = false;
  224. char *p = const_cast<char*>(pLine);
  225. while(*p)
  226. {
  227. Highlighted = false;
  228. pLine = p;
  229. // find line seperator and strip multiline
  230. while(*p)
  231. {
  232. if(*p++ == '\n')
  233. {
  234. *(p-1) = 0;
  235. break;
  236. }
  237. }
  238. m_CurrentLine = (m_CurrentLine+1)%MAX_LINES;
  239. m_aLines[m_CurrentLine].m_Time = time_get();
  240. m_aLines[m_CurrentLine].m_YOffset[0] = -1.0f;
  241. m_aLines[m_CurrentLine].m_YOffset[1] = -1.0f;
  242. m_aLines[m_CurrentLine].m_ClientID = ClientID;
  243. m_aLines[m_CurrentLine].m_Team = Team;
  244. m_aLines[m_CurrentLine].m_NameColor = -2;
  245. // check for highlighted name
  246. const char *pHL = str_find_nocase(pLine, m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
  247. if(pHL)
  248. {
  249. int Length = str_length(m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_aName);
  250. if((pLine == pHL || pHL[-1] == ' ') && (pHL[Length] == 0 || pHL[Length] == ' ' || (pHL[Length] == ':' && pHL[Length+1] == ' ')))
  251. Highlighted = true;
  252. }
  253. m_aLines[m_CurrentLine].m_Highlighted = Highlighted;
  254. if(ClientID == -1) // server message
  255. {
  256. str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName));
  257. str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "%s", pLine);
  258. }
  259. else
  260. {
  261. if(m_pClient->m_aClients[ClientID].m_Team == TEAM_SPECTATORS)
  262. m_aLines[m_CurrentLine].m_NameColor = TEAM_SPECTATORS;
  263. if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)
  264. {
  265. if(m_pClient->m_aClients[ClientID].m_Team == TEAM_RED)
  266. m_aLines[m_CurrentLine].m_NameColor = TEAM_RED;
  267. else if(m_pClient->m_aClients[ClientID].m_Team == TEAM_BLUE)
  268. m_aLines[m_CurrentLine].m_NameColor = TEAM_BLUE;
  269. }
  270. str_copy(m_aLines[m_CurrentLine].m_aName, m_pClient->m_aClients[ClientID].m_aName, sizeof(m_aLines[m_CurrentLine].m_aName));
  271. str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), ": %s", pLine);
  272. }
  273. char aBuf[1024];
  274. str_format(aBuf, sizeof(aBuf), "%s%s", m_aLines[m_CurrentLine].m_aName, m_aLines[m_CurrentLine].m_aText);
  275. Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_aLines[m_CurrentLine].m_Team?"teamchat":"chat", aBuf);
  276. }
  277. // play sound
  278. if(ClientID == -1)
  279. m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_SERVER, 0, vec2(0,0));
  280. else if(Highlighted)
  281. m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_HIGHLIGHT, 0, vec2(0.0f, 0.0f));
  282. else
  283. m_pClient->m_pSounds->Play(CSounds::CHN_GUI, SOUND_CHAT_CLIENT, 0, vec2(0,0));
  284. }
  285. void CChat::OnRender()
  286. {
  287. float Width = 300.0f*Graphics()->ScreenAspect();
  288. Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f);
  289. float x = 5.0f;
  290. float y = 300.0f-20.0f;
  291. if(m_Mode != MODE_NONE)
  292. {
  293. // render chat input
  294. CTextCursor Cursor;
  295. TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER);
  296. Cursor.m_LineWidth = Width-190.0f;
  297. Cursor.m_MaxLines = 2;
  298. if(m_Mode == MODE_ALL)
  299. TextRender()->TextEx(&Cursor, Localize("All"), -1);
  300. else if(m_Mode == MODE_TEAM)
  301. TextRender()->TextEx(&Cursor, Localize("Team"), -1);
  302. else
  303. TextRender()->TextEx(&Cursor, Localize("Chat"), -1);
  304. TextRender()->TextEx(&Cursor, ": ", -1);
  305. // check if the visible text has to be moved
  306. if(m_InputUpdate)
  307. {
  308. if(m_ChatStringOffset > 0 && m_Input.GetLength() < m_OldChatStringLength)
  309. m_ChatStringOffset = max(0, m_ChatStringOffset-(m_OldChatStringLength-m_Input.GetLength()));
  310. if(m_ChatStringOffset > m_Input.GetCursorOffset())
  311. m_ChatStringOffset -= m_ChatStringOffset-m_Input.GetCursorOffset();
  312. else
  313. {
  314. CTextCursor Temp = Cursor;
  315. Temp.m_Flags = 0;
  316. TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
  317. TextRender()->TextEx(&Temp, "|", -1);
  318. while(Temp.m_LineCount > 2)
  319. {
  320. ++m_ChatStringOffset;
  321. Temp = Cursor;
  322. Temp.m_Flags = 0;
  323. TextRender()->TextEx(&Temp, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
  324. TextRender()->TextEx(&Temp, "|", -1);
  325. }
  326. }
  327. m_InputUpdate = false;
  328. }
  329. TextRender()->TextEx(&Cursor, m_Input.GetString()+m_ChatStringOffset, m_Input.GetCursorOffset()-m_ChatStringOffset);
  330. static float MarkerOffset = TextRender()->TextWidth(0, 8.0f, "|", -1)/3;
  331. CTextCursor Marker = Cursor;
  332. Marker.m_X -= MarkerOffset;
  333. TextRender()->TextEx(&Marker, "|", -1);
  334. TextRender()->TextEx(&Cursor, m_Input.GetString()+m_Input.GetCursorOffset(), -1);
  335. }
  336. y -= 8.0f;
  337. int64 Now = time_get();
  338. float LineWidth = m_pClient->m_pScoreboard->Active() ? 90.0f : 200.0f;
  339. float HeightLimit = m_pClient->m_pScoreboard->Active() ? 230.0f : m_Show ? 50.0f : 200.0f;
  340. float Begin = x;
  341. float FontSize = 6.0f;
  342. CTextCursor Cursor;
  343. int OffsetType = m_pClient->m_pScoreboard->Active() ? 1 : 0;
  344. for(int i = 0; i < MAX_LINES; i++)
  345. {
  346. int r = ((m_CurrentLine-i)+MAX_LINES)%MAX_LINES;
  347. if(Now > m_aLines[r].m_Time+16*time_freq() && !m_Show)
  348. break;
  349. // get the y offset (calculate it if we haven't done that yet)
  350. if(m_aLines[r].m_YOffset[OffsetType] < 0.0f)
  351. {
  352. TextRender()->SetCursor(&Cursor, Begin, 0.0f, FontSize, 0);
  353. Cursor.m_LineWidth = LineWidth;
  354. TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1);
  355. TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1);
  356. m_aLines[r].m_YOffset[OffsetType] = Cursor.m_Y + Cursor.m_FontSize;
  357. }
  358. y -= m_aLines[r].m_YOffset[OffsetType];
  359. // cut off if msgs waste too much space
  360. if(y < HeightLimit)
  361. break;
  362. 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;
  363. // reset the cursor
  364. TextRender()->SetCursor(&Cursor, Begin, y, FontSize, TEXTFLAG_RENDER);
  365. Cursor.m_LineWidth = LineWidth;
  366. // render name
  367. if(m_aLines[r].m_ClientID == -1)
  368. TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system
  369. else if(m_aLines[r].m_Team)
  370. TextRender()->TextColor(0.45f, 0.9f, 0.45f, Blend); // team message
  371. else if(m_aLines[r].m_NameColor == TEAM_RED)
  372. TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // red
  373. else if(m_aLines[r].m_NameColor == TEAM_BLUE)
  374. TextRender()->TextColor(0.7f, 0.7f, 1.0f, Blend); // blue
  375. else if(m_aLines[r].m_NameColor == TEAM_SPECTATORS)
  376. TextRender()->TextColor(0.75f, 0.5f, 0.75f, Blend); // spectator
  377. else
  378. TextRender()->TextColor(0.8f, 0.8f, 0.8f, Blend);
  379. TextRender()->TextEx(&Cursor, m_aLines[r].m_aName, -1);
  380. // render line
  381. if(m_aLines[r].m_ClientID == -1)
  382. TextRender()->TextColor(1.0f, 1.0f, 0.5f, Blend); // system
  383. else if(m_aLines[r].m_Highlighted)
  384. TextRender()->TextColor(1.0f, 0.5f, 0.5f, Blend); // highlighted
  385. else if(m_aLines[r].m_Team)
  386. TextRender()->TextColor(0.65f, 1.0f, 0.65f, Blend); // team message
  387. else
  388. TextRender()->TextColor(1.0f, 1.0f, 1.0f, Blend);
  389. TextRender()->TextEx(&Cursor, m_aLines[r].m_aText, -1);
  390. }
  391. TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
  392. }
  393. void CChat::Say(int Team, const char *pLine)
  394. {
  395. // send chat message
  396. CNetMsg_Cl_Say Msg;
  397. Msg.m_Team = Team;
  398. Msg.m_pMessage = pLine;
  399. Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
  400. }