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/graphics.h>
4#include <engine/shared/config.h>
5#include <engine/shared/protocol.h>
6#include <engine/textrender.h>
7#include <game/generated/client_data.h>
8#include <game/generated/protocol.h>
9#include <game/localization.h>
10
11#include "infomessages.h"
12#include <game/client/animstate.h>
13#include <game/client/gameclient.h>
14#include <game/client/prediction/entities/character.h>
15#include <game/client/prediction/gameworld.h>
16
17static constexpr float ROW_HEIGHT = 46.0f;
18static constexpr float FONT_SIZE = 36.0f;
19
20void CInfoMessages::OnWindowResize()
21{
22 for(auto &InfoMsg : m_aInfoMsgs)
23 {
24 DeleteTextContainers(InfoMsg);
25 }
26}
27
28void CInfoMessages::OnReset()
29{
30 m_InfoMsgCurrent = 0;
31 for(auto &InfoMsg : m_aInfoMsgs)
32 {
33 InfoMsg.m_Tick = -100000;
34 DeleteTextContainers(InfoMsg);
35 }
36}
37
38void CInfoMessages::DeleteTextContainers(CInfoMsg &InfoMsg)
39{
40 TextRender()->DeleteTextContainer(TextContainerIndex&: InfoMsg.m_VictimTextContainerIndex);
41 TextRender()->DeleteTextContainer(TextContainerIndex&: InfoMsg.m_KillerTextContainerIndex);
42 TextRender()->DeleteTextContainer(TextContainerIndex&: InfoMsg.m_DiffTextContainerIndex);
43 TextRender()->DeleteTextContainer(TextContainerIndex&: InfoMsg.m_TimeTextContainerIndex);
44}
45
46void CInfoMessages::OnInit()
47{
48 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f);
49 m_SpriteQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false);
50
51 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
52 RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_SpriteQuadContainerIndex, X: 0.f, Y: 0.f, Width: 28.f, Height: 56.f);
53 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
54 RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_SpriteQuadContainerIndex, X: 0.f, Y: 0.f, Width: 28.f, Height: 56.f);
55
56 Graphics()->QuadsSetSubset(TopLeftU: 1, TopLeftV: 0, BottomRightU: 0, BottomRightV: 1);
57 RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_SpriteQuadContainerIndex, X: 0.f, Y: 0.f, Width: 28.f, Height: 56.f);
58 Graphics()->QuadsSetSubset(TopLeftU: 1, TopLeftV: 0, BottomRightU: 0, BottomRightV: 1);
59 RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_SpriteQuadContainerIndex, X: 0.f, Y: 0.f, Width: 28.f, Height: 56.f);
60
61 for(int i = 0; i < NUM_WEAPONS; ++i)
62 {
63 float ScaleX, ScaleY;
64 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
65 RenderTools()->GetSpriteScale(pSprite: g_pData->m_Weapons.m_aId[i].m_pSpriteBody, ScaleX, ScaleY);
66 RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_SpriteQuadContainerIndex, Width: 96.f * ScaleX, Height: 96.f * ScaleY);
67 }
68 Graphics()->QuadContainerUpload(ContainerIndex: m_SpriteQuadContainerIndex);
69}
70
71CInfoMessages::CInfoMsg CInfoMessages::CreateInfoMsg(EType Type)
72{
73 CInfoMsg InfoMsg;
74 InfoMsg.m_Type = Type;
75 InfoMsg.m_Tick = Client()->GameTick(Conn: g_Config.m_ClDummy);
76
77 for(int i = 0; i < MAX_KILLMSG_TEAM_MEMBERS; i++)
78 {
79 InfoMsg.m_aVictimIds[i] = -1;
80 InfoMsg.m_aVictimRenderInfo[i].Reset();
81 }
82 InfoMsg.m_VictimDDTeam = 0;
83 InfoMsg.m_aVictimName[0] = '\0';
84 InfoMsg.m_VictimTextContainerIndex.Reset();
85
86 InfoMsg.m_KillerId = -1;
87 InfoMsg.m_aKillerName[0] = '\0';
88 InfoMsg.m_KillerTextContainerIndex.Reset();
89 InfoMsg.m_KillerRenderInfo.Reset();
90
91 InfoMsg.m_Weapon = -1;
92 InfoMsg.m_ModeSpecial = 0;
93 InfoMsg.m_FlagCarrierBlue = -1;
94 InfoMsg.m_TeamSize = 0;
95
96 InfoMsg.m_Diff = 0;
97 InfoMsg.m_aTimeText[0] = '\0';
98 InfoMsg.m_aDiffText[0] = '\0';
99 InfoMsg.m_TimeTextContainerIndex.Reset();
100 InfoMsg.m_DiffTextContainerIndex.Reset();
101 InfoMsg.m_RecordPersonal = false;
102 return InfoMsg;
103}
104
105void CInfoMessages::AddInfoMsg(const CInfoMsg &InfoMsg)
106{
107 if(InfoMsg.m_KillerId >= 0 && !InfoMsg.m_KillerRenderInfo.Valid())
108 return;
109 for(int i = 0; i < InfoMsg.m_TeamSize; i++)
110 {
111 if(InfoMsg.m_aVictimIds[i] < 0 || !InfoMsg.m_aVictimRenderInfo[i].Valid())
112 return;
113 }
114
115 const float Height = 1.5f * 400.0f * 3.0f;
116 const float Width = Height * Graphics()->ScreenAspect();
117
118 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
119 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
120 Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: Height);
121
122 m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS;
123 DeleteTextContainers(InfoMsg&: m_aInfoMsgs[m_InfoMsgCurrent]);
124 m_aInfoMsgs[m_InfoMsgCurrent] = InfoMsg;
125 CreateTextContainersIfNotCreated(InfoMsg&: m_aInfoMsgs[m_InfoMsgCurrent]);
126
127 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
128}
129
130void CInfoMessages::CreateTextContainersIfNotCreated(CInfoMsg &InfoMsg)
131{
132 const auto &&NameColor = [&](int ClientId) -> ColorRGBA {
133 unsigned Color;
134 if(ClientId == m_pClient->m_Snap.m_LocalClientId)
135 {
136 Color = g_Config.m_ClKillMessageHighlightColor;
137 }
138 else
139 {
140 Color = g_Config.m_ClKillMessageNormalColor;
141 }
142 return color_cast<ColorRGBA>(hsl: ColorHSLA(Color));
143 };
144
145 if(!InfoMsg.m_VictimTextContainerIndex.Valid() && InfoMsg.m_aVictimName[0] != '\0')
146 {
147 CTextCursor Cursor;
148 TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize: FONT_SIZE, Flags: TEXTFLAG_RENDER);
149 TextRender()->TextColor(rgb: NameColor(InfoMsg.m_aVictimIds[0]));
150 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_VictimTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aVictimName);
151 }
152
153 if(!InfoMsg.m_KillerTextContainerIndex.Valid() && InfoMsg.m_aKillerName[0] != '\0')
154 {
155 CTextCursor Cursor;
156 TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize: FONT_SIZE, Flags: TEXTFLAG_RENDER);
157 TextRender()->TextColor(rgb: NameColor(InfoMsg.m_KillerId));
158 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_KillerTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aKillerName);
159 }
160
161 if(!InfoMsg.m_DiffTextContainerIndex.Valid() && InfoMsg.m_aDiffText[0] != '\0')
162 {
163 CTextCursor Cursor;
164 TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize: FONT_SIZE, Flags: TEXTFLAG_RENDER);
165
166 if(InfoMsg.m_Diff > 0)
167 TextRender()->TextColor(r: 1.0f, g: 0.5f, b: 0.5f, a: 1.0f); // red
168 else if(InfoMsg.m_Diff < 0)
169 TextRender()->TextColor(r: 0.5f, g: 1.0f, b: 0.5f, a: 1.0f); // green
170 else
171 TextRender()->TextColor(rgb: TextRender()->DefaultTextColor());
172
173 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_DiffTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aDiffText);
174 }
175
176 if(!InfoMsg.m_TimeTextContainerIndex.Valid() && InfoMsg.m_aTimeText[0] != '\0')
177 {
178 CTextCursor Cursor;
179 TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize: FONT_SIZE, Flags: TEXTFLAG_RENDER);
180 TextRender()->TextColor(rgb: TextRender()->DefaultTextColor());
181 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_TimeTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aTimeText);
182 }
183
184 TextRender()->TextColor(rgb: TextRender()->DefaultTextColor());
185}
186
187void CInfoMessages::OnMessage(int MsgType, void *pRawMsg)
188{
189 if(m_pClient->m_SuppressEvents)
190 return;
191
192 switch(MsgType)
193 {
194 case NETMSGTYPE_SV_KILLMSGTEAM:
195 OnTeamKillMessage(pMsg: static_cast<CNetMsg_Sv_KillMsgTeam *>(pRawMsg));
196 break;
197 case NETMSGTYPE_SV_KILLMSG:
198 OnKillMessage(pMsg: static_cast<CNetMsg_Sv_KillMsg *>(pRawMsg));
199 break;
200 case NETMSGTYPE_SV_RACEFINISH:
201 OnRaceFinishMessage(pMsg: static_cast<CNetMsg_Sv_RaceFinish *>(pRawMsg));
202 break;
203 }
204}
205
206void CInfoMessages::OnTeamKillMessage(const CNetMsg_Sv_KillMsgTeam *pMsg)
207{
208 std::vector<std::pair<int, int>> vStrongWeakSorted;
209 for(int i = 0; i < MAX_CLIENTS; i++)
210 {
211 if(m_pClient->m_Teams.Team(ClientId: i) == pMsg->m_Team)
212 {
213 CCharacter *pChr = m_pClient->m_GameWorld.GetCharacterById(Id: i);
214 vStrongWeakSorted.emplace_back(args&: i, args: pMsg->m_First == i ? MAX_CLIENTS : pChr ? pChr->GetStrongWeakId() : 0);
215 }
216 }
217 std::stable_sort(first: vStrongWeakSorted.begin(), last: vStrongWeakSorted.end(), comp: [](auto &Left, auto &Right) { return Left.second > Right.second; });
218
219 CInfoMsg Kill = CreateInfoMsg(Type: TYPE_KILL);
220 Kill.m_TeamSize = minimum<int>(a: vStrongWeakSorted.size(), b: MAX_KILLMSG_TEAM_MEMBERS);
221
222 Kill.m_VictimDDTeam = pMsg->m_Team;
223 for(int i = 0; i < Kill.m_TeamSize; i++)
224 {
225 if(m_pClient->m_aClients[vStrongWeakSorted[i].first].m_Active)
226 {
227 Kill.m_aVictimIds[i] = vStrongWeakSorted[i].first;
228 Kill.m_aVictimRenderInfo[i] = m_pClient->m_aClients[vStrongWeakSorted[i].first].m_RenderInfo;
229 }
230 }
231 str_format(buffer: Kill.m_aVictimName, buffer_size: sizeof(Kill.m_aVictimName), format: Localize(pStr: "Team %d"), pMsg->m_Team);
232
233 AddInfoMsg(InfoMsg: Kill);
234}
235
236void CInfoMessages::OnKillMessage(const CNetMsg_Sv_KillMsg *pMsg)
237{
238 CInfoMsg Kill = CreateInfoMsg(Type: TYPE_KILL);
239
240 Kill.m_TeamSize = 1;
241 Kill.m_aVictimIds[0] = pMsg->m_Victim;
242 Kill.m_VictimDDTeam = m_pClient->m_Teams.Team(ClientId: Kill.m_aVictimIds[0]);
243 str_copy(dst&: Kill.m_aVictimName, src: m_pClient->m_aClients[Kill.m_aVictimIds[0]].m_aName);
244 Kill.m_aVictimRenderInfo[0] = m_pClient->m_aClients[Kill.m_aVictimIds[0]].m_RenderInfo;
245
246 Kill.m_KillerId = pMsg->m_Killer;
247 str_copy(dst&: Kill.m_aKillerName, src: m_pClient->m_aClients[Kill.m_KillerId].m_aName);
248 Kill.m_KillerRenderInfo = m_pClient->m_aClients[Kill.m_KillerId].m_RenderInfo;
249
250 Kill.m_Weapon = pMsg->m_Weapon;
251 Kill.m_ModeSpecial = pMsg->m_ModeSpecial;
252 Kill.m_FlagCarrierBlue = m_pClient->m_Snap.m_pGameDataObj ? m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue : -1;
253
254 AddInfoMsg(InfoMsg: Kill);
255}
256
257void CInfoMessages::OnRaceFinishMessage(const CNetMsg_Sv_RaceFinish *pMsg)
258{
259 CInfoMsg Finish = CreateInfoMsg(Type: TYPE_FINISH);
260
261 Finish.m_TeamSize = 1;
262 Finish.m_aVictimIds[0] = pMsg->m_ClientId;
263 Finish.m_VictimDDTeam = m_pClient->m_Teams.Team(ClientId: Finish.m_aVictimIds[0]);
264 str_copy(dst&: Finish.m_aVictimName, src: m_pClient->m_aClients[Finish.m_aVictimIds[0]].m_aName);
265 Finish.m_aVictimRenderInfo[0] = m_pClient->m_aClients[pMsg->m_ClientId].m_RenderInfo;
266
267 Finish.m_Diff = pMsg->m_Diff;
268 Finish.m_RecordPersonal = pMsg->m_RecordPersonal || pMsg->m_RecordServer;
269 if(Finish.m_Diff)
270 {
271 char aBuf[64];
272 str_time_float(secs: absolute(a: Finish.m_Diff) / 1000.0f, format: TIME_HOURS_CENTISECS, buffer: aBuf, buffer_size: sizeof(aBuf));
273 str_format(buffer: Finish.m_aDiffText, buffer_size: sizeof(Finish.m_aDiffText), format: "(%c%s)", Finish.m_Diff < 0 ? '-' : '+', aBuf);
274 }
275 str_time_float(secs: pMsg->m_Time / 1000.0f, format: TIME_HOURS_CENTISECS, buffer: Finish.m_aTimeText, buffer_size: sizeof(Finish.m_aTimeText));
276
277 AddInfoMsg(InfoMsg: Finish);
278}
279
280void CInfoMessages::RenderKillMsg(const CInfoMsg &InfoMsg, float x, float y)
281{
282 ColorRGBA TextColor;
283 if(InfoMsg.m_VictimDDTeam)
284 TextColor = m_pClient->GetDDTeamColor(DDTeam: InfoMsg.m_VictimDDTeam, Lightness: 0.75f);
285 else
286 TextColor = TextRender()->DefaultTextColor();
287
288 // render victim name
289 if(InfoMsg.m_VictimTextContainerIndex.Valid())
290 {
291 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex).m_W;
292 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
293 }
294
295 // render victim flag
296 x -= 24.0f;
297 if(m_pClient->m_Snap.m_pGameInfoObj && (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && (InfoMsg.m_ModeSpecial & 1))
298 {
299 int QuadOffset;
300 if(InfoMsg.m_aVictimIds[0] == InfoMsg.m_FlagCarrierBlue)
301 {
302 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue);
303 QuadOffset = 0;
304 }
305 else
306 {
307 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed);
308 QuadOffset = 1;
309 }
310 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset, X: x, Y: y - 16);
311 }
312
313 // render victim tees
314 for(int j = (InfoMsg.m_TeamSize - 1); j >= 0; j--)
315 {
316 if(InfoMsg.m_aVictimIds[j] < 0)
317 continue;
318
319 vec2 OffsetToMid;
320 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_aVictimRenderInfo[j], TeeOffsetToMid&: OffsetToMid);
321 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
322 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_aVictimRenderInfo[j], Emote: EMOTE_PAIN, Dir: vec2(-1, 0), Pos: TeeRenderPos);
323 x -= 44.0f;
324 }
325
326 // render weapon
327 x -= 32.0f;
328 if(InfoMsg.m_Weapon >= 0)
329 {
330 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpriteWeapons[InfoMsg.m_Weapon]);
331 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset: 4 + InfoMsg.m_Weapon, X: x, Y: y + 28);
332 }
333 x -= 52.0f;
334
335 // render killer (only if different from victim)
336 if(InfoMsg.m_aVictimIds[0] != InfoMsg.m_KillerId)
337 {
338 // render killer flag
339 if(m_pClient->m_Snap.m_pGameInfoObj && (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && (InfoMsg.m_ModeSpecial & 2))
340 {
341 int QuadOffset;
342 if(InfoMsg.m_KillerId == InfoMsg.m_FlagCarrierBlue)
343 {
344 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue);
345 QuadOffset = 2;
346 }
347 else
348 {
349 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed);
350 QuadOffset = 3;
351 }
352 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset, X: x - 56, Y: y - 16);
353 }
354
355 // render killer tee
356 x -= 24.0f;
357 if(InfoMsg.m_KillerId >= 0)
358 {
359 vec2 OffsetToMid;
360 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_KillerRenderInfo, TeeOffsetToMid&: OffsetToMid);
361 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
362 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_KillerRenderInfo, Emote: EMOTE_ANGRY, Dir: vec2(1, 0), Pos: TeeRenderPos);
363 }
364 x -= 32.0f;
365
366 // render killer name
367 if(InfoMsg.m_KillerTextContainerIndex.Valid())
368 {
369 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_KillerTextContainerIndex).m_W;
370 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_KillerTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
371 }
372 }
373}
374
375void CInfoMessages::RenderFinishMsg(const CInfoMsg &InfoMsg, float x, float y)
376{
377 // render time diff
378 if(InfoMsg.m_DiffTextContainerIndex.Valid())
379 {
380 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_DiffTextContainerIndex).m_W;
381 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_DiffTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
382 }
383
384 // render time
385 if(InfoMsg.m_TimeTextContainerIndex.Valid())
386 {
387 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_TimeTextContainerIndex).m_W;
388 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_TimeTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
389 }
390
391 // render flag
392 const float FlagSize = 52.0f;
393 x -= FlagSize;
394 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_RACEFLAG].m_Id);
395 Graphics()->QuadsBegin();
396 IGraphics::CQuadItem QuadItem(x, y, FlagSize, FlagSize);
397 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
398 Graphics()->QuadsEnd();
399
400 // render victim name
401 if(InfoMsg.m_VictimTextContainerIndex.Valid())
402 {
403 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex).m_W;
404 ColorRGBA TextColor;
405 if(InfoMsg.m_VictimDDTeam)
406 TextColor = m_pClient->GetDDTeamColor(DDTeam: InfoMsg.m_VictimDDTeam, Lightness: 0.75f);
407 else
408 TextColor = TextRender()->DefaultTextColor();
409 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
410 }
411
412 // render victim tee
413 x -= 24.0f;
414 vec2 OffsetToMid;
415 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_aVictimRenderInfo[0], TeeOffsetToMid&: OffsetToMid);
416 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
417 const int Emote = InfoMsg.m_RecordPersonal ? EMOTE_HAPPY : EMOTE_NORMAL;
418 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_aVictimRenderInfo[0], Emote, Dir: vec2(-1, 0), Pos: TeeRenderPos);
419}
420
421void CInfoMessages::OnRender()
422{
423 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
424 return;
425
426 const float Height = 1.5f * 400.0f * 3.0f;
427 const float Width = Height * Graphics()->ScreenAspect();
428
429 Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: Height);
430 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
431
432 int Showfps = g_Config.m_ClShowfps;
433#if defined(CONF_VIDEORECORDER)
434 if(IVideo::Current())
435 Showfps = 0;
436#endif
437 const float StartX = Width - 10.0f;
438 const float StartY = 30.0f + (Showfps ? 100.0f : 0.0f) + (g_Config.m_ClShowpred && Client()->State() != IClient::STATE_DEMOPLAYBACK ? 100.0f : 0.0f);
439
440 float y = StartY;
441 for(int i = 1; i <= MAX_INFOMSGS; i++)
442 {
443 CInfoMsg &InfoMsg = m_aInfoMsgs[(m_InfoMsgCurrent + i) % MAX_INFOMSGS];
444 if(Client()->GameTick(Conn: g_Config.m_ClDummy) > InfoMsg.m_Tick + Client()->GameTickSpeed() * 10)
445 continue;
446
447 CreateTextContainersIfNotCreated(InfoMsg);
448
449 if(InfoMsg.m_Type == EType::TYPE_KILL && g_Config.m_ClShowKillMessages)
450 {
451 RenderKillMsg(InfoMsg, x: StartX, y);
452 y += ROW_HEIGHT;
453 }
454 else if(InfoMsg.m_Type == EType::TYPE_FINISH && g_Config.m_ClShowFinishMessages)
455 {
456 RenderFinishMsg(InfoMsg, x: StartX, y);
457 y += ROW_HEIGHT;
458 }
459 }
460}
461
462void CInfoMessages::OnRefreshSkins()
463{
464 for(auto &InfoMsg : m_aInfoMsgs)
465 {
466 InfoMsg.m_KillerRenderInfo.Reset();
467 if(InfoMsg.m_KillerId >= 0)
468 {
469 const CGameClient::CClientData &Client = GameClient()->m_aClients[InfoMsg.m_KillerId];
470 if(Client.m_Active && Client.m_aSkinName[0] != '\0')
471 InfoMsg.m_KillerRenderInfo = Client.m_RenderInfo;
472 else
473 InfoMsg.m_KillerId = -1;
474 }
475
476 for(int i = 0; i < MAX_KILLMSG_TEAM_MEMBERS; i++)
477 {
478 InfoMsg.m_aVictimRenderInfo[i].Reset();
479 if(InfoMsg.m_aVictimIds[i] >= 0)
480 {
481 const CGameClient::CClientData &Client = GameClient()->m_aClients[InfoMsg.m_aVictimIds[i]];
482 if(Client.m_Active && Client.m_aSkinName[0] != '\0')
483 InfoMsg.m_aVictimRenderInfo[i] = Client.m_RenderInfo;
484 else
485 InfoMsg.m_aVictimIds[i] = -1;
486 }
487 }
488 }
489}
490