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