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