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 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
134 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
135 Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: Height);
136
137 m_InfoMsgCurrent = (m_InfoMsgCurrent + 1) % MAX_INFOMSGS;
138 DeleteTextContainers(InfoMsg&: m_aInfoMsgs[m_InfoMsgCurrent]);
139 m_aInfoMsgs[m_InfoMsgCurrent] = InfoMsg;
140 CreateTextContainersIfNotCreated(InfoMsg&: m_aInfoMsgs[m_InfoMsgCurrent]);
141
142 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
143}
144
145void CInfoMessages::CreateTextContainersIfNotCreated(CInfoMsg &InfoMsg)
146{
147 const auto &&NameColor = [&](int ClientId) -> ColorRGBA {
148 unsigned Color;
149 if(ClientId == GameClient()->m_Snap.m_LocalClientId)
150 {
151 Color = g_Config.m_ClKillMessageHighlightColor;
152 }
153 else
154 {
155 Color = g_Config.m_ClKillMessageNormalColor;
156 }
157 return color_cast<ColorRGBA>(hsl: ColorHSLA(Color));
158 };
159
160 if(!InfoMsg.m_VictimTextContainerIndex.Valid() && InfoMsg.m_aVictimName[0] != '\0')
161 {
162 CTextCursor Cursor;
163 Cursor.m_FontSize = FONT_SIZE;
164 TextRender()->TextColor(Color: NameColor(InfoMsg.m_aVictimIds[0]));
165 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_VictimTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aVictimName);
166 }
167
168 if(!InfoMsg.m_KillerTextContainerIndex.Valid() && InfoMsg.m_aKillerName[0] != '\0')
169 {
170 CTextCursor Cursor;
171 Cursor.m_FontSize = FONT_SIZE;
172 TextRender()->TextColor(Color: NameColor(InfoMsg.m_KillerId));
173 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_KillerTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aKillerName);
174 }
175
176 if(!InfoMsg.m_DiffTextContainerIndex.Valid() && InfoMsg.m_aDiffText[0] != '\0')
177 {
178 CTextCursor Cursor;
179 Cursor.m_FontSize = FONT_SIZE;
180
181 if(InfoMsg.m_Diff > 0)
182 TextRender()->TextColor(r: 1.0f, g: 0.5f, b: 0.5f, a: 1.0f); // red
183 else if(InfoMsg.m_Diff < 0)
184 TextRender()->TextColor(r: 0.5f, g: 1.0f, b: 0.5f, a: 1.0f); // green
185 else
186 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
187
188 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_DiffTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aDiffText);
189 }
190
191 if(!InfoMsg.m_TimeTextContainerIndex.Valid() && InfoMsg.m_aTimeText[0] != '\0')
192 {
193 CTextCursor Cursor;
194 Cursor.m_FontSize = FONT_SIZE;
195 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
196 TextRender()->CreateTextContainer(TextContainerIndex&: InfoMsg.m_TimeTextContainerIndex, pCursor: &Cursor, pText: InfoMsg.m_aTimeText);
197 }
198
199 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
200}
201
202void CInfoMessages::OnMessage(int MsgType, void *pRawMsg)
203{
204 if(GameClient()->m_SuppressEvents)
205 return;
206
207 switch(MsgType)
208 {
209 case NETMSGTYPE_SV_KILLMSGTEAM:
210 OnTeamKillMessage(pMsg: static_cast<CNetMsg_Sv_KillMsgTeam *>(pRawMsg));
211 break;
212 case NETMSGTYPE_SV_KILLMSG:
213 OnKillMessage(pMsg: static_cast<CNetMsg_Sv_KillMsg *>(pRawMsg));
214 break;
215 case NETMSGTYPE_SV_RACEFINISH:
216 OnRaceFinishMessage(pMsg: static_cast<CNetMsg_Sv_RaceFinish *>(pRawMsg));
217 break;
218 }
219}
220
221void CInfoMessages::OnTeamKillMessage(const CNetMsg_Sv_KillMsgTeam *pMsg)
222{
223 std::vector<std::pair<int, int>> vStrongWeakSorted;
224 vStrongWeakSorted.reserve(n: MAX_CLIENTS);
225 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
226 {
227 if(GameClient()->m_aClients[ClientId].m_Active &&
228 GameClient()->m_Teams.Team(ClientId) == pMsg->m_Team)
229 {
230 CCharacter *pChr = GameClient()->m_GameWorld.GetCharacterById(Id: ClientId);
231 vStrongWeakSorted.emplace_back(args&: ClientId, args: pMsg->m_First == ClientId ? MAX_CLIENTS : (pChr ? pChr->GetStrongWeakId() : 0));
232 }
233 }
234 std::stable_sort(first: vStrongWeakSorted.begin(), last: vStrongWeakSorted.end(), comp: [](auto &Left, auto &Right) { return Left.second > Right.second; });
235
236 CInfoMsg Kill = CreateInfoMsg(Type: TYPE_KILL);
237 Kill.m_TeamSize = minimum<int>(a: vStrongWeakSorted.size(), b: MAX_KILLMSG_TEAM_MEMBERS);
238
239 Kill.m_VictimDDTeam = pMsg->m_Team;
240 for(int i = 0; i < Kill.m_TeamSize; i++)
241 {
242 const int VictimId = vStrongWeakSorted[i].first;
243 Kill.m_aVictimIds[i] = VictimId;
244 Kill.m_apVictimManagedTeeRenderInfos[i] = GameClient()->CreateManagedTeeRenderInfo(Client: GameClient()->m_aClients[VictimId]);
245 }
246 str_format(buffer: Kill.m_aVictimName, buffer_size: sizeof(Kill.m_aVictimName), format: Localize(pStr: "Team %d"), pMsg->m_Team);
247
248 AddInfoMsg(InfoMsg: Kill);
249}
250
251void CInfoMessages::OnKillMessage(const CNetMsg_Sv_KillMsg *pMsg)
252{
253 CInfoMsg Kill = CreateInfoMsg(Type: TYPE_KILL);
254
255 if(GameClient()->m_aClients[pMsg->m_Victim].m_Active)
256 {
257 Kill.m_TeamSize = 1;
258 Kill.m_aVictimIds[0] = pMsg->m_Victim;
259 Kill.m_VictimDDTeam = GameClient()->m_Teams.Team(ClientId: Kill.m_aVictimIds[0]);
260 str_copy(dst&: Kill.m_aVictimName, src: GameClient()->m_aClients[Kill.m_aVictimIds[0]].m_aName);
261 Kill.m_apVictimManagedTeeRenderInfos[0] = GameClient()->CreateManagedTeeRenderInfo(Client: GameClient()->m_aClients[Kill.m_aVictimIds[0]]);
262 }
263
264 if(GameClient()->m_aClients[pMsg->m_Killer].m_Active)
265 {
266 Kill.m_KillerId = pMsg->m_Killer;
267 str_copy(dst&: Kill.m_aKillerName, src: GameClient()->m_aClients[Kill.m_KillerId].m_aName);
268 Kill.m_pKillerManagedTeeRenderInfo = GameClient()->CreateManagedTeeRenderInfo(Client: GameClient()->m_aClients[Kill.m_KillerId]);
269 }
270
271 Kill.m_Weapon = pMsg->m_Weapon;
272 Kill.m_ModeSpecial = pMsg->m_ModeSpecial;
273 Kill.m_FlagCarrierBlue = GameClient()->m_Snap.m_pGameDataObj ? GameClient()->m_Snap.m_pGameDataObj->m_FlagCarrierBlue : -1;
274
275 if(Kill.m_TeamSize == 0 && Kill.m_KillerId == -1 && Kill.m_Weapon < 0)
276 {
277 return; // message would be empty
278 }
279
280 AddInfoMsg(InfoMsg: Kill);
281}
282
283void CInfoMessages::OnRaceFinishMessage(const CNetMsg_Sv_RaceFinish *pMsg)
284{
285 if(!GameClient()->m_aClients[pMsg->m_ClientId].m_Active)
286 {
287 return;
288 }
289
290 CInfoMsg Finish = CreateInfoMsg(Type: TYPE_FINISH);
291
292 Finish.m_TeamSize = 1;
293 Finish.m_aVictimIds[0] = pMsg->m_ClientId;
294 Finish.m_VictimDDTeam = GameClient()->m_Teams.Team(ClientId: Finish.m_aVictimIds[0]);
295 str_copy(dst&: Finish.m_aVictimName, src: GameClient()->m_aClients[Finish.m_aVictimIds[0]].m_aName);
296 Finish.m_apVictimManagedTeeRenderInfos[0] = GameClient()->CreateManagedTeeRenderInfo(Client: GameClient()->m_aClients[pMsg->m_ClientId]);
297
298 Finish.m_Diff = pMsg->m_Diff;
299 Finish.m_RecordPersonal = pMsg->m_RecordPersonal || pMsg->m_RecordServer;
300 if(Finish.m_Diff)
301 {
302 char aBuf[64];
303 str_time_float(secs: absolute(a: Finish.m_Diff) / 1000.0f, format: ETimeFormat::HOURS_CENTISECS, buffer: aBuf, buffer_size: sizeof(aBuf));
304 str_format(buffer: Finish.m_aDiffText, buffer_size: sizeof(Finish.m_aDiffText), format: "(%c%s)", Finish.m_Diff < 0 ? '-' : '+', aBuf);
305 }
306 str_time_float(secs: pMsg->m_Time / 1000.0f, format: ETimeFormat::HOURS_CENTISECS, buffer: Finish.m_aTimeText, buffer_size: sizeof(Finish.m_aTimeText));
307
308 AddInfoMsg(InfoMsg: Finish);
309}
310
311void CInfoMessages::RenderKillMsg(const CInfoMsg &InfoMsg, float x, float y)
312{
313 ColorRGBA TextColor;
314 if(InfoMsg.m_VictimDDTeam)
315 TextColor = GameClient()->GetDDTeamColor(DDTeam: InfoMsg.m_VictimDDTeam, Lightness: 0.75f);
316 else
317 TextColor = TextRender()->DefaultTextColor();
318
319 // render victim name
320 if(InfoMsg.m_VictimTextContainerIndex.Valid())
321 {
322 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex).m_W;
323 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
324 }
325
326 // render victim flag
327 x -= 24.0f;
328 if(GameClient()->m_Snap.m_pGameInfoObj && (GameClient()->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && (InfoMsg.m_ModeSpecial & 1))
329 {
330 int QuadOffset;
331 if(InfoMsg.m_aVictimIds[0] == InfoMsg.m_FlagCarrierBlue)
332 {
333 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue);
334 QuadOffset = 0;
335 }
336 else
337 {
338 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed);
339 QuadOffset = 1;
340 }
341 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset, X: x, Y: y - 16);
342 }
343
344 // render victim tees
345 for(int j = (InfoMsg.m_TeamSize - 1); j >= 0; j--)
346 {
347 vec2 OffsetToMid;
348 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_apVictimManagedTeeRenderInfos[j]->TeeRenderInfo(), TeeOffsetToMid&: OffsetToMid);
349 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
350 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_apVictimManagedTeeRenderInfos[j]->TeeRenderInfo(), Emote: EMOTE_PAIN, Dir: vec2(-1, 0), Pos: TeeRenderPos);
351 x -= 44.0f;
352 }
353
354 // render weapon
355 x -= 32.0f;
356 if(InfoMsg.m_Weapon >= 0)
357 {
358 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpriteWeapons[InfoMsg.m_Weapon]);
359 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset: 4 + InfoMsg.m_Weapon, X: x, Y: y + 28);
360 }
361 x -= 52.0f;
362
363 // render killer (only if different from victim)
364 if(InfoMsg.m_aVictimIds[0] != InfoMsg.m_KillerId)
365 {
366 // render killer flag
367 if(GameClient()->m_Snap.m_pGameInfoObj && (GameClient()->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && (InfoMsg.m_ModeSpecial & 2))
368 {
369 int QuadOffset;
370 if(InfoMsg.m_KillerId == InfoMsg.m_FlagCarrierBlue)
371 {
372 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue);
373 QuadOffset = 2;
374 }
375 else
376 {
377 Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed);
378 QuadOffset = 3;
379 }
380 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset, X: x - 56, Y: y - 16);
381 }
382
383 // render killer tee
384 x -= 24.0f;
385 if(InfoMsg.m_pKillerManagedTeeRenderInfo != nullptr)
386 {
387 vec2 OffsetToMid;
388 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_pKillerManagedTeeRenderInfo->TeeRenderInfo(), TeeOffsetToMid&: OffsetToMid);
389 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
390 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_pKillerManagedTeeRenderInfo->TeeRenderInfo(), Emote: EMOTE_ANGRY, Dir: vec2(1, 0), Pos: TeeRenderPos);
391 }
392 x -= 32.0f;
393
394 // render killer name
395 if(InfoMsg.m_KillerTextContainerIndex.Valid())
396 {
397 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_KillerTextContainerIndex).m_W;
398 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_KillerTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
399 }
400 }
401}
402
403void CInfoMessages::RenderFinishMsg(const CInfoMsg &InfoMsg, float x, float y)
404{
405 // render time diff
406 if(InfoMsg.m_DiffTextContainerIndex.Valid())
407 {
408 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_DiffTextContainerIndex).m_W;
409 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_DiffTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
410 }
411
412 // render time
413 if(InfoMsg.m_TimeTextContainerIndex.Valid())
414 {
415 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_TimeTextContainerIndex).m_W;
416 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_TimeTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
417 }
418
419 // render flag
420 x -= RACE_FLAG_SIZE;
421 Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_RACEFLAG].m_Id);
422 Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_SpriteQuadContainerIndex, QuadOffset: m_QuadOffsetRaceFlag, X: x, Y: y);
423
424 // render victim name
425 if(InfoMsg.m_VictimTextContainerIndex.Valid())
426 {
427 x -= TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex).m_W;
428 ColorRGBA TextColor;
429 if(InfoMsg.m_VictimDDTeam)
430 TextColor = GameClient()->GetDDTeamColor(DDTeam: InfoMsg.m_VictimDDTeam, Lightness: 0.75f);
431 else
432 TextColor = TextRender()->DefaultTextColor();
433 TextRender()->RenderTextContainer(TextContainerIndex: InfoMsg.m_VictimTextContainerIndex, TextColor, TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: x, Y: y + (ROW_HEIGHT - FONT_SIZE) / 2.0f);
434 }
435
436 // render victim tee
437 x -= 24.0f;
438 vec2 OffsetToMid;
439 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_apVictimManagedTeeRenderInfos[0]->TeeRenderInfo(), TeeOffsetToMid&: OffsetToMid);
440 const vec2 TeeRenderPos = vec2(x, y + ROW_HEIGHT / 2.0f + OffsetToMid.y);
441 const int Emote = InfoMsg.m_RecordPersonal ? EMOTE_HAPPY : EMOTE_NORMAL;
442 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &InfoMsg.m_apVictimManagedTeeRenderInfos[0]->TeeRenderInfo(), Emote, Dir: vec2(-1, 0), Pos: TeeRenderPos);
443}
444
445void CInfoMessages::OnRender()
446{
447 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
448 return;
449
450 const float Height = 1.5f * 400.0f * 3.0f;
451 const float Width = Height * Graphics()->ScreenAspect();
452
453 Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: Height);
454 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
455
456 int Showfps = g_Config.m_ClShowfps;
457#if defined(CONF_VIDEORECORDER)
458 if(IVideo::Current())
459 Showfps = 0;
460#endif
461 const float StartX = Width - 10.0f;
462 const float StartY = 30.0f + (Showfps ? 100.0f : 0.0f) + (g_Config.m_ClShowpred && Client()->State() != IClient::STATE_DEMOPLAYBACK ? 100.0f : 0.0f);
463
464 float y = StartY;
465 for(int i = 1; i <= MAX_INFOMSGS; i++)
466 {
467 CInfoMsg &InfoMsg = m_aInfoMsgs[(m_InfoMsgCurrent + i) % MAX_INFOMSGS];
468 if(InfoMsg.m_Tick == -1)
469 {
470 continue;
471 }
472 if(Client()->GameTick(Conn: g_Config.m_ClDummy) > InfoMsg.m_Tick + Client()->GameTickSpeed() * 10)
473 {
474 ResetMessage(InfoMsg);
475 continue;
476 }
477
478 CreateTextContainersIfNotCreated(InfoMsg);
479
480 if(InfoMsg.m_Type == EType::TYPE_KILL && g_Config.m_ClShowKillMessages)
481 {
482 RenderKillMsg(InfoMsg, x: StartX, y);
483 y += ROW_HEIGHT;
484 }
485 else if(InfoMsg.m_Type == EType::TYPE_FINISH && g_Config.m_ClShowFinishMessages)
486 {
487 RenderFinishMsg(InfoMsg, x: StartX, y);
488 y += ROW_HEIGHT;
489 }
490 }
491}
492