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/textrender.h> |
6 | |
7 | #include <game/client/animstate.h> |
8 | #include <game/client/components/scoreboard.h> |
9 | #include <game/client/gameclient.h> |
10 | #include <game/client/render.h> |
11 | #include <game/generated/client_data.h> |
12 | #include <game/generated/protocol.h> |
13 | |
14 | #include <game/layers.h> |
15 | #include <game/localization.h> |
16 | |
17 | #include <cmath> |
18 | |
19 | #include "binds.h" |
20 | #include "camera.h" |
21 | #include "controls.h" |
22 | #include "hud.h" |
23 | #include "voting.h" |
24 | |
25 | CHud::CHud() |
26 | { |
27 | // won't work if zero |
28 | m_FrameTimeAvg = 0.0f; |
29 | m_FPSTextContainerIndex.Reset(); |
30 | m_DDRaceEffectsTextContainerIndex.Reset(); |
31 | } |
32 | |
33 | void CHud::ResetHudContainers() |
34 | { |
35 | for(auto &ScoreInfo : m_aScoreInfo) |
36 | { |
37 | TextRender()->DeleteTextContainer(TextContainerIndex&: ScoreInfo.m_OptionalNameTextContainerIndex); |
38 | TextRender()->DeleteTextContainer(TextContainerIndex&: ScoreInfo.m_TextRankContainerIndex); |
39 | TextRender()->DeleteTextContainer(TextContainerIndex&: ScoreInfo.m_TextScoreContainerIndex); |
40 | Graphics()->DeleteQuadContainer(ContainerIndex&: ScoreInfo.m_RoundRectQuadContainerIndex); |
41 | |
42 | ScoreInfo.Reset(); |
43 | } |
44 | |
45 | TextRender()->DeleteTextContainer(TextContainerIndex&: m_FPSTextContainerIndex); |
46 | TextRender()->DeleteTextContainer(TextContainerIndex&: m_DDRaceEffectsTextContainerIndex); |
47 | } |
48 | |
49 | void CHud::OnWindowResize() |
50 | { |
51 | ResetHudContainers(); |
52 | } |
53 | |
54 | void CHud::OnReset() |
55 | { |
56 | m_TimeCpDiff = 0.0f; |
57 | m_DDRaceTime = 0; |
58 | m_FinishTimeLastReceivedTick = 0; |
59 | m_TimeCpLastReceivedTick = 0; |
60 | m_ShowFinishTime = false; |
61 | m_ServerRecord = -1.0f; |
62 | m_aPlayerRecord[0] = -1.0f; |
63 | m_aPlayerRecord[1] = -1.0f; |
64 | |
65 | ResetHudContainers(); |
66 | } |
67 | |
68 | void CHud::OnInit() |
69 | { |
70 | OnReset(); |
71 | |
72 | m_HudQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
73 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
74 | PrepareAmmoHealthAndArmorQuads(); |
75 | |
76 | // all cursors for the different weapons |
77 | for(int i = 0; i < NUM_WEAPONS; ++i) |
78 | { |
79 | float ScaleX, ScaleY; |
80 | RenderTools()->GetSpriteScale(pSprite: g_pData->m_Weapons.m_aId[i].m_pSpriteCursor, ScaleX, ScaleY); |
81 | m_aCursorOffset[i] = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, Width: 64.f * ScaleX, Height: 64.f * ScaleY); |
82 | } |
83 | |
84 | // the flags |
85 | m_FlagOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 8.f, Height: 16.f); |
86 | |
87 | PreparePlayerStateQuads(); |
88 | |
89 | Graphics()->QuadContainerUpload(ContainerIndex: m_HudQuadContainerIndex); |
90 | } |
91 | |
92 | void CHud::RenderGameTimer() |
93 | { |
94 | float Half = m_Width / 2.0f; |
95 | |
96 | if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH)) |
97 | { |
98 | char aBuf[32]; |
99 | int Time = 0; |
100 | if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer <= 0)) |
101 | { |
102 | Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit * 60 - ((Client()->GameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / Client()->GameTickSpeed()); |
103 | |
104 | if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) |
105 | Time = 0; |
106 | } |
107 | else if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME) |
108 | { |
109 | // The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer |
110 | Time = (Client()->GameTick(Conn: g_Config.m_ClDummy) + m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) / Client()->GameTickSpeed(); |
111 | } |
112 | else |
113 | Time = (Client()->GameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / Client()->GameTickSpeed(); |
114 | |
115 | str_time(centisecs: (int64_t)Time * 100, format: TIME_DAYS, buffer: aBuf, buffer_size: sizeof(aBuf)); |
116 | float FontSize = 10.0f; |
117 | static float s_TextWidthM = TextRender()->TextWidth(Size: FontSize, pText: "00:00" , StrLength: -1, LineWidth: -1.0f); |
118 | static float s_TextWidthH = TextRender()->TextWidth(Size: FontSize, pText: "00:00:00" , StrLength: -1, LineWidth: -1.0f); |
119 | static float s_TextWidth0D = TextRender()->TextWidth(Size: FontSize, pText: "0d 00:00:00" , StrLength: -1, LineWidth: -1.0f); |
120 | static float s_TextWidth00D = TextRender()->TextWidth(Size: FontSize, pText: "00d 00:00:00" , StrLength: -1, LineWidth: -1.0f); |
121 | static float s_TextWidth000D = TextRender()->TextWidth(Size: FontSize, pText: "000d 00:00:00" , StrLength: -1, LineWidth: -1.0f); |
122 | float w = Time >= 3600 * 24 * 100 ? s_TextWidth000D : Time >= 3600 * 24 * 10 ? s_TextWidth00D : Time >= 3600 * 24 ? s_TextWidth0D : Time >= 3600 ? s_TextWidthH : s_TextWidthM; |
123 | // last 60 sec red, last 10 sec blink |
124 | if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer <= 0)) |
125 | { |
126 | float Alpha = Time <= 10 && (2 * time() / time_freq()) % 2 ? 0.5f : 1.0f; |
127 | TextRender()->TextColor(r: 1.0f, g: 0.25f, b: 0.25f, a: Alpha); |
128 | } |
129 | TextRender()->Text(x: Half - w / 2, y: 2, Size: FontSize, pText: aBuf, LineWidth: -1.0f); |
130 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
131 | } |
132 | } |
133 | |
134 | void CHud::RenderPauseNotification() |
135 | { |
136 | if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED && |
137 | !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) |
138 | { |
139 | const char *pText = Localize(pStr: "Game paused" ); |
140 | float FontSize = 20.0f; |
141 | float w = TextRender()->TextWidth(Size: FontSize, pText, StrLength: -1, LineWidth: -1.0f); |
142 | TextRender()->Text(x: 150.0f * Graphics()->ScreenAspect() + -w / 2.0f, y: 50.0f, Size: FontSize, pText, LineWidth: -1.0f); |
143 | } |
144 | } |
145 | |
146 | void CHud::RenderSuddenDeath() |
147 | { |
148 | if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH) |
149 | { |
150 | float Half = m_Width / 2.0f; |
151 | const char *pText = Localize(pStr: "Sudden Death" ); |
152 | float FontSize = 12.0f; |
153 | float w = TextRender()->TextWidth(Size: FontSize, pText, StrLength: -1, LineWidth: -1.0f); |
154 | TextRender()->Text(x: Half - w / 2, y: 2, Size: FontSize, pText, LineWidth: -1.0f); |
155 | } |
156 | } |
157 | |
158 | void CHud::RenderScoreHud() |
159 | { |
160 | // render small score hud |
161 | if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) |
162 | { |
163 | int GameFlags = m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags; |
164 | float StartY = 229.0f; // the height of this display is 56, so EndY is 285 |
165 | |
166 | const float ScoreSingleBoxHeight = 18.0f; |
167 | |
168 | bool ForceScoreInfoInit = !m_aScoreInfo[0].m_Initialized || !m_aScoreInfo[1].m_Initialized; |
169 | m_aScoreInfo[0].m_Initialized = m_aScoreInfo[1].m_Initialized = true; |
170 | |
171 | if(GameFlags & GAMEFLAG_TEAMS && m_pClient->m_Snap.m_pGameDataObj) |
172 | { |
173 | char aScoreTeam[2][16]; |
174 | str_format(buffer: aScoreTeam[TEAM_RED], buffer_size: sizeof(aScoreTeam[TEAM_RED]), format: "%d" , m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed); |
175 | str_format(buffer: aScoreTeam[TEAM_BLUE], buffer_size: sizeof(aScoreTeam[TEAM_BLUE]), format: "%d" , m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue); |
176 | |
177 | bool aRecreateTeamScore[2] = {str_comp(a: aScoreTeam[0], b: m_aScoreInfo[0].m_aScoreText) != 0, str_comp(a: aScoreTeam[1], b: m_aScoreInfo[1].m_aScoreText) != 0}; |
178 | |
179 | const int aFlagCarrier[2] = { |
180 | m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed, |
181 | m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue}; |
182 | |
183 | bool RecreateRect = ForceScoreInfoInit; |
184 | for(int t = 0; t < 2; t++) |
185 | { |
186 | if(aRecreateTeamScore[t]) |
187 | { |
188 | m_aScoreInfo[t].m_ScoreTextWidth = TextRender()->TextWidth(Size: 14.0f, pText: aScoreTeam[t == 0 ? TEAM_RED : TEAM_BLUE], StrLength: -1, LineWidth: -1.0f); |
189 | str_copy(dst&: m_aScoreInfo[t].m_aScoreText, src: aScoreTeam[t == 0 ? TEAM_RED : TEAM_BLUE]); |
190 | RecreateRect = true; |
191 | } |
192 | } |
193 | |
194 | static float s_TextWidth100 = TextRender()->TextWidth(Size: 14.0f, pText: "100" , StrLength: -1, LineWidth: -1.0f); |
195 | float ScoreWidthMax = maximum(a: maximum(a: m_aScoreInfo[0].m_ScoreTextWidth, b: m_aScoreInfo[1].m_ScoreTextWidth), b: s_TextWidth100); |
196 | float Split = 3.0f; |
197 | float ImageSize = (GameFlags & GAMEFLAG_FLAGS) ? 16.0f : Split; |
198 | for(int t = 0; t < 2; t++) |
199 | { |
200 | // draw box |
201 | if(RecreateRect) |
202 | { |
203 | Graphics()->DeleteQuadContainer(ContainerIndex&: m_aScoreInfo[t].m_RoundRectQuadContainerIndex); |
204 | |
205 | if(t == 0) |
206 | Graphics()->SetColor(r: 1.0f, g: 0.0f, b: 0.0f, a: 0.25f); |
207 | else |
208 | Graphics()->SetColor(r: 0.0f, g: 0.0f, b: 1.0f, a: 0.25f); |
209 | m_aScoreInfo[t].m_RoundRectQuadContainerIndex = Graphics()->CreateRectQuadContainer(x: m_Width - ScoreWidthMax - ImageSize - 2 * Split, y: StartY + t * 20, w: ScoreWidthMax + ImageSize + 2 * Split, h: ScoreSingleBoxHeight, r: 5.0f, Corners: IGraphics::CORNER_L); |
210 | } |
211 | Graphics()->TextureClear(); |
212 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
213 | if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1) |
214 | Graphics()->RenderQuadContainer(ContainerIndex: m_aScoreInfo[t].m_RoundRectQuadContainerIndex, QuadDrawNum: -1); |
215 | |
216 | // draw score |
217 | if(aRecreateTeamScore[t]) |
218 | { |
219 | CTextCursor Cursor; |
220 | TextRender()->SetCursor(pCursor: &Cursor, x: m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) / 2 - Split, y: StartY + t * 20 + (18.f - 14.f) / 2.f, FontSize: 14.0f, Flags: TEXTFLAG_RENDER); |
221 | Cursor.m_LineWidth = -1; |
222 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aScoreInfo[t].m_TextScoreContainerIndex, pCursor: &Cursor, pText: aScoreTeam[t]); |
223 | } |
224 | if(m_aScoreInfo[t].m_TextScoreContainerIndex.Valid()) |
225 | { |
226 | ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); |
227 | ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); |
228 | TextRender()->RenderTextContainer(TextContainerIndex: m_aScoreInfo[t].m_TextScoreContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor); |
229 | } |
230 | |
231 | if(GameFlags & GAMEFLAG_FLAGS) |
232 | { |
233 | int BlinkTimer = (m_pClient->m_aFlagDropTick[t] != 0 && |
234 | (Client()->GameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_aFlagDropTick[t]) / Client()->GameTickSpeed() >= 25) ? |
235 | 10 : |
236 | 20; |
237 | if(aFlagCarrier[t] == FLAG_ATSTAND || (aFlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick(Conn: g_Config.m_ClDummy) / BlinkTimer) & 1))) |
238 | { |
239 | // draw flag |
240 | Graphics()->TextureSet(Texture: t == 0 ? m_pClient->m_GameSkin.m_SpriteFlagRed : m_pClient->m_GameSkin.m_SpriteFlagBlue); |
241 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
242 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_FlagOffset, X: m_Width - ScoreWidthMax - ImageSize, Y: StartY + 1.0f + t * 20); |
243 | } |
244 | else if(aFlagCarrier[t] >= 0) |
245 | { |
246 | // draw name of the flag holder |
247 | int Id = aFlagCarrier[t] % MAX_CLIENTS; |
248 | const char *pName = m_pClient->m_aClients[Id].m_aName; |
249 | if(str_comp(a: pName, b: m_aScoreInfo[t].m_aPlayerNameText) != 0 || RecreateRect) |
250 | { |
251 | str_copy(dst&: m_aScoreInfo[t].m_aPlayerNameText, src: pName); |
252 | |
253 | float w = TextRender()->TextWidth(Size: 8.0f, pText: pName, StrLength: -1, LineWidth: -1.0f); |
254 | |
255 | CTextCursor Cursor; |
256 | TextRender()->SetCursor(pCursor: &Cursor, x: minimum(a: m_Width - w - 1.0f, b: m_Width - ScoreWidthMax - ImageSize - 2 * Split), y: StartY + (t + 1) * 20.0f - 2.0f, FontSize: 8.0f, Flags: TEXTFLAG_RENDER); |
257 | Cursor.m_LineWidth = -1; |
258 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aScoreInfo[t].m_OptionalNameTextContainerIndex, pCursor: &Cursor, pText: pName); |
259 | } |
260 | |
261 | if(m_aScoreInfo[t].m_OptionalNameTextContainerIndex.Valid()) |
262 | { |
263 | ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); |
264 | ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); |
265 | TextRender()->RenderTextContainer(TextContainerIndex: m_aScoreInfo[t].m_OptionalNameTextContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor); |
266 | } |
267 | |
268 | // draw tee of the flag holder |
269 | CTeeRenderInfo TeeInfo = m_pClient->m_aClients[Id].m_RenderInfo; |
270 | TeeInfo.m_Size = ScoreSingleBoxHeight; |
271 | |
272 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
273 | vec2 OffsetToMid; |
274 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: pIdleState, pInfo: &TeeInfo, TeeOffsetToMid&: OffsetToMid); |
275 | vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); |
276 | |
277 | RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &TeeInfo, Emote: EMOTE_NORMAL, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
278 | } |
279 | } |
280 | StartY += 8.0f; |
281 | } |
282 | } |
283 | else |
284 | { |
285 | int Local = -1; |
286 | int aPos[2] = {1, 2}; |
287 | const CNetObj_PlayerInfo *apPlayerInfo[2] = {0, 0}; |
288 | int i = 0; |
289 | for(int t = 0; t < 2 && i < MAX_CLIENTS && m_pClient->m_Snap.m_apInfoByScore[i]; ++i) |
290 | { |
291 | if(m_pClient->m_Snap.m_apInfoByScore[i]->m_Team != TEAM_SPECTATORS) |
292 | { |
293 | apPlayerInfo[t] = m_pClient->m_Snap.m_apInfoByScore[i]; |
294 | if(apPlayerInfo[t]->m_ClientId == m_pClient->m_Snap.m_LocalClientId) |
295 | Local = t; |
296 | ++t; |
297 | } |
298 | } |
299 | // search local player info if not a spectator, nor within top2 scores |
300 | if(Local == -1 && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) |
301 | { |
302 | for(; i < MAX_CLIENTS && m_pClient->m_Snap.m_apInfoByScore[i]; ++i) |
303 | { |
304 | if(m_pClient->m_Snap.m_apInfoByScore[i]->m_Team != TEAM_SPECTATORS) |
305 | ++aPos[1]; |
306 | if(m_pClient->m_Snap.m_apInfoByScore[i]->m_ClientId == m_pClient->m_Snap.m_LocalClientId) |
307 | { |
308 | apPlayerInfo[1] = m_pClient->m_Snap.m_apInfoByScore[i]; |
309 | Local = 1; |
310 | break; |
311 | } |
312 | } |
313 | } |
314 | char aScore[2][16]; |
315 | for(int t = 0; t < 2; ++t) |
316 | { |
317 | if(apPlayerInfo[t]) |
318 | { |
319 | if(m_pClient->m_GameInfo.m_TimeScore) |
320 | { |
321 | if(apPlayerInfo[t]->m_Score != -9999) |
322 | str_time(centisecs: (int64_t)absolute(a: apPlayerInfo[t]->m_Score) * 100, format: TIME_HOURS, buffer: aScore[t], buffer_size: sizeof(aScore[t])); |
323 | else |
324 | aScore[t][0] = 0; |
325 | } |
326 | else |
327 | str_format(buffer: aScore[t], buffer_size: sizeof(aScore[t]), format: "%d" , apPlayerInfo[t]->m_Score); |
328 | } |
329 | else |
330 | aScore[t][0] = 0; |
331 | } |
332 | |
333 | static int LocalClientId = -1; |
334 | bool RecreateScores = str_comp(a: aScore[0], b: m_aScoreInfo[0].m_aScoreText) != 0 || str_comp(a: aScore[1], b: m_aScoreInfo[1].m_aScoreText) != 0 || LocalClientId != m_pClient->m_Snap.m_LocalClientId; |
335 | LocalClientId = m_pClient->m_Snap.m_LocalClientId; |
336 | |
337 | bool RecreateRect = ForceScoreInfoInit; |
338 | for(int t = 0; t < 2; t++) |
339 | { |
340 | if(RecreateScores) |
341 | { |
342 | m_aScoreInfo[t].m_ScoreTextWidth = TextRender()->TextWidth(Size: 14.0f, pText: aScore[t], StrLength: -1, LineWidth: -1.0f); |
343 | str_copy(dst&: m_aScoreInfo[t].m_aScoreText, src: aScore[t]); |
344 | RecreateRect = true; |
345 | } |
346 | |
347 | if(apPlayerInfo[t]) |
348 | { |
349 | int Id = apPlayerInfo[t]->m_ClientId; |
350 | if(Id >= 0 && Id < MAX_CLIENTS) |
351 | { |
352 | const char *pName = m_pClient->m_aClients[Id].m_aName; |
353 | if(str_comp(a: pName, b: m_aScoreInfo[t].m_aPlayerNameText) != 0) |
354 | RecreateRect = true; |
355 | } |
356 | } |
357 | else |
358 | { |
359 | if(m_aScoreInfo[t].m_aPlayerNameText[0] != 0) |
360 | RecreateRect = true; |
361 | } |
362 | |
363 | char aBuf[16]; |
364 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d." , aPos[t]); |
365 | if(str_comp(a: aBuf, b: m_aScoreInfo[t].m_aRankText) != 0) |
366 | RecreateRect = true; |
367 | } |
368 | |
369 | static float s_TextWidth10 = TextRender()->TextWidth(Size: 14.0f, pText: "10" , StrLength: -1, LineWidth: -1.0f); |
370 | float ScoreWidthMax = maximum(a: maximum(a: m_aScoreInfo[0].m_ScoreTextWidth, b: m_aScoreInfo[1].m_ScoreTextWidth), b: s_TextWidth10); |
371 | float Split = 3.0f, ImageSize = 16.0f, PosSize = 16.0f; |
372 | |
373 | for(int t = 0; t < 2; t++) |
374 | { |
375 | // draw box |
376 | if(RecreateRect) |
377 | { |
378 | Graphics()->DeleteQuadContainer(ContainerIndex&: m_aScoreInfo[t].m_RoundRectQuadContainerIndex); |
379 | |
380 | if(t == Local) |
381 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.25f); |
382 | else |
383 | Graphics()->SetColor(r: 0.0f, g: 0.0f, b: 0.0f, a: 0.25f); |
384 | m_aScoreInfo[t].m_RoundRectQuadContainerIndex = Graphics()->CreateRectQuadContainer(x: m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize, y: StartY + t * 20, w: ScoreWidthMax + ImageSize + 2 * Split + PosSize, h: ScoreSingleBoxHeight, r: 5.0f, Corners: IGraphics::CORNER_L); |
385 | } |
386 | Graphics()->TextureClear(); |
387 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
388 | if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1) |
389 | Graphics()->RenderQuadContainer(ContainerIndex: m_aScoreInfo[t].m_RoundRectQuadContainerIndex, QuadDrawNum: -1); |
390 | |
391 | if(RecreateScores) |
392 | { |
393 | CTextCursor Cursor; |
394 | TextRender()->SetCursor(pCursor: &Cursor, x: m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) - Split, y: StartY + t * 20 + (18.f - 14.f) / 2.f, FontSize: 14.0f, Flags: TEXTFLAG_RENDER); |
395 | Cursor.m_LineWidth = -1; |
396 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aScoreInfo[t].m_TextScoreContainerIndex, pCursor: &Cursor, pText: aScore[t]); |
397 | } |
398 | // draw score |
399 | if(m_aScoreInfo[t].m_TextScoreContainerIndex.Valid()) |
400 | { |
401 | ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); |
402 | ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); |
403 | TextRender()->RenderTextContainer(TextContainerIndex: m_aScoreInfo[t].m_TextScoreContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor); |
404 | } |
405 | |
406 | if(apPlayerInfo[t]) |
407 | { |
408 | // draw name |
409 | int Id = apPlayerInfo[t]->m_ClientId; |
410 | if(Id >= 0 && Id < MAX_CLIENTS) |
411 | { |
412 | const char *pName = m_pClient->m_aClients[Id].m_aName; |
413 | if(RecreateRect) |
414 | { |
415 | str_copy(dst&: m_aScoreInfo[t].m_aPlayerNameText, src: pName); |
416 | |
417 | CTextCursor Cursor; |
418 | float w = TextRender()->TextWidth(Size: 8.0f, pText: pName, StrLength: -1, LineWidth: -1.0f); |
419 | TextRender()->SetCursor(pCursor: &Cursor, x: minimum(a: m_Width - w - 1.0f, b: m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize), y: StartY + (t + 1) * 20.0f - 2.0f, FontSize: 8.0f, Flags: TEXTFLAG_RENDER); |
420 | Cursor.m_LineWidth = -1; |
421 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aScoreInfo[t].m_OptionalNameTextContainerIndex, pCursor: &Cursor, pText: pName); |
422 | } |
423 | |
424 | if(m_aScoreInfo[t].m_OptionalNameTextContainerIndex.Valid()) |
425 | { |
426 | ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); |
427 | ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); |
428 | TextRender()->RenderTextContainer(TextContainerIndex: m_aScoreInfo[t].m_OptionalNameTextContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor); |
429 | } |
430 | |
431 | // draw tee |
432 | CTeeRenderInfo TeeInfo = m_pClient->m_aClients[Id].m_RenderInfo; |
433 | TeeInfo.m_Size = ScoreSingleBoxHeight; |
434 | |
435 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
436 | vec2 OffsetToMid; |
437 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: pIdleState, pInfo: &TeeInfo, TeeOffsetToMid&: OffsetToMid); |
438 | vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); |
439 | |
440 | RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &TeeInfo, Emote: EMOTE_NORMAL, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
441 | } |
442 | } |
443 | else |
444 | { |
445 | m_aScoreInfo[t].m_aPlayerNameText[0] = 0; |
446 | } |
447 | |
448 | // draw position |
449 | char aBuf[16]; |
450 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d." , aPos[t]); |
451 | if(RecreateRect) |
452 | { |
453 | str_copy(dst&: m_aScoreInfo[t].m_aRankText, src: aBuf); |
454 | |
455 | CTextCursor Cursor; |
456 | TextRender()->SetCursor(pCursor: &Cursor, x: m_Width - ScoreWidthMax - ImageSize - Split - PosSize, y: StartY + t * 20 + (18.f - 10.f) / 2.f, FontSize: 10.0f, Flags: TEXTFLAG_RENDER); |
457 | Cursor.m_LineWidth = -1; |
458 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aScoreInfo[t].m_TextRankContainerIndex, pCursor: &Cursor, pText: aBuf); |
459 | } |
460 | if(m_aScoreInfo[t].m_TextRankContainerIndex.Valid()) |
461 | { |
462 | ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); |
463 | ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); |
464 | TextRender()->RenderTextContainer(TextContainerIndex: m_aScoreInfo[t].m_TextRankContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor); |
465 | } |
466 | |
467 | StartY += 8.0f; |
468 | } |
469 | } |
470 | } |
471 | } |
472 | |
473 | void CHud::RenderWarmupTimer() |
474 | { |
475 | // render warmup timer |
476 | if(m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer > 0 && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME)) |
477 | { |
478 | char aBuf[256]; |
479 | float FontSize = 20.0f; |
480 | float w = TextRender()->TextWidth(Size: FontSize, pText: Localize(pStr: "Warmup" ), StrLength: -1, LineWidth: -1.0f); |
481 | TextRender()->Text(x: 150 * Graphics()->ScreenAspect() + -w / 2, y: 50, Size: FontSize, pText: Localize(pStr: "Warmup" ), LineWidth: -1.0f); |
482 | |
483 | int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer / Client()->GameTickSpeed(); |
484 | if(Seconds < 5) |
485 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d.%d" , Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer * 10 / Client()->GameTickSpeed()) % 10); |
486 | else |
487 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , Seconds); |
488 | w = TextRender()->TextWidth(Size: FontSize, pText: aBuf, StrLength: -1, LineWidth: -1.0f); |
489 | TextRender()->Text(x: 150 * Graphics()->ScreenAspect() + -w / 2, y: 75, Size: FontSize, pText: aBuf, LineWidth: -1.0f); |
490 | } |
491 | } |
492 | |
493 | void CHud::RenderTextInfo() |
494 | { |
495 | int Showfps = g_Config.m_ClShowfps; |
496 | #if defined(CONF_VIDEORECORDER) |
497 | if(IVideo::Current()) |
498 | Showfps = 0; |
499 | #endif |
500 | if(Showfps) |
501 | { |
502 | // calculate avg. fps |
503 | m_FrameTimeAvg = m_FrameTimeAvg * 0.9f + Client()->RenderFrameTime() * 0.1f; |
504 | char aBuf[64]; |
505 | int FrameTime = (int)(1.0f / m_FrameTimeAvg + 0.5f); |
506 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , FrameTime); |
507 | |
508 | static float s_TextWidth0 = TextRender()->TextWidth(Size: 12.f, pText: "0" , StrLength: -1, LineWidth: -1.0f); |
509 | static float s_TextWidth00 = TextRender()->TextWidth(Size: 12.f, pText: "00" , StrLength: -1, LineWidth: -1.0f); |
510 | static float s_TextWidth000 = TextRender()->TextWidth(Size: 12.f, pText: "000" , StrLength: -1, LineWidth: -1.0f); |
511 | static float s_TextWidth0000 = TextRender()->TextWidth(Size: 12.f, pText: "0000" , StrLength: -1, LineWidth: -1.0f); |
512 | static float s_TextWidth00000 = TextRender()->TextWidth(Size: 12.f, pText: "00000" , StrLength: -1, LineWidth: -1.0f); |
513 | static const float s_aTextWidth[5] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000}; |
514 | |
515 | int DigitIndex = GetDigitsIndex(Value: FrameTime, Max: 4); |
516 | |
517 | CTextCursor Cursor; |
518 | TextRender()->SetCursor(pCursor: &Cursor, x: m_Width - 10 - s_aTextWidth[DigitIndex], y: 5, FontSize: 12, Flags: TEXTFLAG_RENDER); |
519 | Cursor.m_LineWidth = -1; |
520 | auto OldFlags = TextRender()->GetRenderFlags(); |
521 | TextRender()->SetRenderFlags(OldFlags | TEXT_RENDER_FLAG_ONE_TIME_USE); |
522 | if(m_FPSTextContainerIndex.Valid()) |
523 | TextRender()->RecreateTextContainerSoft(TextContainerIndex&: m_FPSTextContainerIndex, pCursor: &Cursor, pText: aBuf); |
524 | else |
525 | TextRender()->CreateTextContainer(TextContainerIndex&: m_FPSTextContainerIndex, pCursor: &Cursor, pText: "0" ); |
526 | TextRender()->SetRenderFlags(OldFlags); |
527 | if(m_FPSTextContainerIndex.Valid()) |
528 | { |
529 | TextRender()->RenderTextContainer(TextContainerIndex: m_FPSTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor()); |
530 | } |
531 | } |
532 | if(g_Config.m_ClShowpred && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
533 | { |
534 | char aBuf[64]; |
535 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , Client()->GetPredictionTime()); |
536 | TextRender()->Text(x: m_Width - 10 - TextRender()->TextWidth(Size: 12, pText: aBuf, StrLength: -1, LineWidth: -1.0f), y: Showfps ? 20 : 5, Size: 12, pText: aBuf, LineWidth: -1.0f); |
537 | } |
538 | } |
539 | |
540 | void CHud::RenderConnectionWarning() |
541 | { |
542 | if(Client()->ConnectionProblems()) |
543 | { |
544 | const char *pText = Localize(pStr: "Connection Problems…" ); |
545 | float w = TextRender()->TextWidth(Size: 24, pText, StrLength: -1, LineWidth: -1.0f); |
546 | TextRender()->Text(x: 150 * Graphics()->ScreenAspect() - w / 2, y: 50, Size: 24, pText, LineWidth: -1.0f); |
547 | } |
548 | } |
549 | |
550 | void CHud::RenderTeambalanceWarning() |
551 | { |
552 | // render prompt about team-balance |
553 | bool Flash = time() / (time_freq() / 2) % 2 == 0; |
554 | if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) |
555 | { |
556 | int TeamDiff = m_pClient->m_Snap.m_aTeamSize[TEAM_RED] - m_pClient->m_Snap.m_aTeamSize[TEAM_BLUE]; |
557 | if(g_Config.m_ClWarningTeambalance && (TeamDiff >= 2 || TeamDiff <= -2)) |
558 | { |
559 | const char *pText = Localize(pStr: "Please balance teams!" ); |
560 | if(Flash) |
561 | TextRender()->TextColor(r: 1, g: 1, b: 0.5f, a: 1); |
562 | else |
563 | TextRender()->TextColor(r: 0.7f, g: 0.7f, b: 0.2f, a: 1.0f); |
564 | TextRender()->Text(x: 5, y: 50, Size: 6, pText, LineWidth: -1.0f); |
565 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
566 | } |
567 | } |
568 | } |
569 | |
570 | void CHud::RenderCursor() |
571 | { |
572 | if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK) |
573 | return; |
574 | |
575 | RenderTools()->MapScreenToInterface(CenterX: m_pClient->m_Camera.m_Center.x, CenterY: m_pClient->m_Camera.m_Center.y); |
576 | |
577 | // render cursor |
578 | int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS; |
579 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
580 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_aSpriteWeaponCursors[CurWeapon]); |
581 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_aCursorOffset[CurWeapon], X: m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].x, Y: m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].y); |
582 | } |
583 | |
584 | void CHud::PrepareAmmoHealthAndArmorQuads() |
585 | { |
586 | float x = 5; |
587 | float y = 5; |
588 | IGraphics::CQuadItem Array[10]; |
589 | |
590 | // ammo of the different weapons |
591 | for(int i = 0; i < NUM_WEAPONS; ++i) |
592 | { |
593 | // 0.6 |
594 | for(int n = 0; n < 10; n++) |
595 | Array[n] = IGraphics::CQuadItem(x + n * 12, y, 10, 10); |
596 | |
597 | m_aAmmoOffset[i] = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
598 | |
599 | // 0.7 |
600 | if(i == WEAPON_GRENADE) |
601 | { |
602 | // special case for 0.7 grenade |
603 | for(int n = 0; n < 10; n++) |
604 | Array[n] = IGraphics::CQuadItem(1 + x + n * 12, y, 10, 10); |
605 | } |
606 | else |
607 | { |
608 | for(int n = 0; n < 10; n++) |
609 | Array[n] = IGraphics::CQuadItem(x + n * 12, y, 12, 12); |
610 | } |
611 | |
612 | Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
613 | } |
614 | |
615 | // health |
616 | for(int i = 0; i < 10; ++i) |
617 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10); |
618 | m_HealthOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
619 | |
620 | // 0.7 |
621 | for(int i = 0; i < 10; ++i) |
622 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); |
623 | Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
624 | |
625 | // empty health |
626 | for(int i = 0; i < 10; ++i) |
627 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10); |
628 | m_EmptyHealthOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
629 | |
630 | // 0.7 |
631 | for(int i = 0; i < 10; ++i) |
632 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); |
633 | Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
634 | |
635 | // armor meter |
636 | for(int i = 0; i < 10; ++i) |
637 | Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10); |
638 | m_ArmorOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
639 | |
640 | // 0.7 |
641 | for(int i = 0; i < 10; ++i) |
642 | Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 12, 12); |
643 | Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
644 | |
645 | // empty armor meter |
646 | for(int i = 0; i < 10; ++i) |
647 | Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10); |
648 | m_EmptyArmorOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
649 | |
650 | // 0.7 |
651 | for(int i = 0; i < 10; ++i) |
652 | Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 12, 12); |
653 | Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
654 | } |
655 | |
656 | void CHud::RenderAmmoHealthAndArmor(const CNetObj_Character *pCharacter) |
657 | { |
658 | if(!pCharacter) |
659 | return; |
660 | |
661 | bool IsSixupGameSkin = m_pClient->m_GameSkin.IsSixup(); |
662 | int QuadOffsetSixup = (IsSixupGameSkin ? 10 : 0); |
663 | |
664 | if(GameClient()->m_GameInfo.m_HudAmmo) |
665 | { |
666 | // ammo display |
667 | float AmmoOffsetY = GameClient()->m_GameInfo.m_HudHealthArmor ? 24 : 0; |
668 | int CurWeapon = pCharacter->m_Weapon % NUM_WEAPONS; |
669 | if(m_pClient->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon].IsValid()) |
670 | { |
671 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon]); |
672 | if(AmmoOffsetY > 0) |
673 | { |
674 | Graphics()->RenderQuadContainerEx(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_aAmmoOffset[CurWeapon] + QuadOffsetSixup, QuadDrawNum: minimum(a: pCharacter->m_AmmoCount, b: 10), X: 0, Y: AmmoOffsetY); |
675 | } |
676 | else |
677 | { |
678 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_aAmmoOffset[CurWeapon] + QuadOffsetSixup, QuadDrawNum: minimum(a: pCharacter->m_AmmoCount, b: 10)); |
679 | } |
680 | } |
681 | } |
682 | |
683 | if(GameClient()->m_GameInfo.m_HudHealthArmor) |
684 | { |
685 | // health display |
686 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_SpriteHealthFull); |
687 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_HealthOffset + QuadOffsetSixup, QuadDrawNum: minimum(a: pCharacter->m_Health, b: 10)); |
688 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_SpriteHealthEmpty); |
689 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_EmptyHealthOffset + QuadOffsetSixup + minimum(a: pCharacter->m_Health, b: 10), QuadDrawNum: 10 - minimum(a: pCharacter->m_Health, b: 10)); |
690 | |
691 | // armor display |
692 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_SpriteArmorFull); |
693 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_ArmorOffset + QuadOffsetSixup, QuadDrawNum: minimum(a: pCharacter->m_Armor, b: 10)); |
694 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_SpriteArmorEmpty); |
695 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_ArmorOffset + QuadOffsetSixup + minimum(a: pCharacter->m_Armor, b: 10), QuadDrawNum: 10 - minimum(a: pCharacter->m_Armor, b: 10)); |
696 | } |
697 | } |
698 | |
699 | void CHud::PreparePlayerStateQuads() |
700 | { |
701 | float x = 5; |
702 | float y = 5 + 24; |
703 | IGraphics::CQuadItem Array[10]; |
704 | |
705 | // Quads for displaying the available and used jumps |
706 | for(int i = 0; i < 10; ++i) |
707 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); |
708 | m_AirjumpOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
709 | |
710 | for(int i = 0; i < 10; ++i) |
711 | Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12); |
712 | m_AirjumpEmptyOffset = Graphics()->QuadContainerAddQuads(ContainerIndex: m_HudQuadContainerIndex, pArray: Array, Num: 10); |
713 | |
714 | // Quads for displaying weapons |
715 | for(int Weapon = 0; Weapon < NUM_WEAPONS; ++Weapon) |
716 | { |
717 | const CDataWeaponspec &WeaponSpec = g_pData->m_Weapons.m_aId[Weapon]; |
718 | float ScaleX, ScaleY; |
719 | RenderTools()->GetSpriteScale(pSprite: WeaponSpec.m_pSpriteBody, ScaleX, ScaleY); |
720 | constexpr float HudWeaponScale = 0.25f; |
721 | float Width = WeaponSpec.m_VisualSize * ScaleX * HudWeaponScale; |
722 | float Height = WeaponSpec.m_VisualSize * ScaleY * HudWeaponScale; |
723 | m_aWeaponOffset[Weapon] = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, Width, Height); |
724 | } |
725 | |
726 | // Quads for displaying capabilities |
727 | m_EndlessJumpOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
728 | m_EndlessHookOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
729 | m_JetpackOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
730 | m_TeleportGrenadeOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
731 | m_TeleportGunOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
732 | m_TeleportLaserOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
733 | |
734 | // Quads for displaying prohibited capabilities |
735 | m_SoloOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
736 | m_CollisionDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
737 | m_HookHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
738 | m_HammerHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
739 | m_GunHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
740 | m_ShotgunHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
741 | m_GrenadeHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
742 | m_LaserHitDisabledOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
743 | |
744 | // Quads for displaying freeze status |
745 | m_DeepFrozenOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
746 | m_LiveFrozenOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
747 | |
748 | // Quads for displaying dummy actions |
749 | m_DummyHammerOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
750 | m_DummyCopyOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
751 | |
752 | // Quads for displaying team modes |
753 | m_PracticeModeOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
754 | m_LockModeOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
755 | m_Team0ModeOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_HudQuadContainerIndex, X: 0.f, Y: 0.f, Width: 12.f, Height: 12.f); |
756 | } |
757 | |
758 | void CHud::RenderPlayerState(const int ClientId) |
759 | { |
760 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
761 | |
762 | // pCharacter contains the predicted character for local players or the last snap for players who are spectated |
763 | CCharacterCore *pCharacter = &m_pClient->m_aClients[ClientId].m_Predicted; |
764 | CNetObj_Character *pPlayer = &m_pClient->m_aClients[ClientId].m_RenderCur; |
765 | int TotalJumpsToDisplay = 0; |
766 | if(g_Config.m_ClShowhudJumpsIndicator) |
767 | { |
768 | int AvailableJumpsToDisplay; |
769 | if(m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo) |
770 | { |
771 | bool Grounded = false; |
772 | if(Collision()->CheckPoint(x: pPlayer->m_X + CCharacterCore::PhysicalSize() / 2, |
773 | y: pPlayer->m_Y + CCharacterCore::PhysicalSize() / 2 + 5)) |
774 | { |
775 | Grounded = true; |
776 | } |
777 | if(Collision()->CheckPoint(x: pPlayer->m_X - CCharacterCore::PhysicalSize() / 2, |
778 | y: pPlayer->m_Y + CCharacterCore::PhysicalSize() / 2 + 5)) |
779 | { |
780 | Grounded = true; |
781 | } |
782 | |
783 | int UsedJumps = pCharacter->m_JumpedTotal; |
784 | if(pCharacter->m_Jumps > 1) |
785 | { |
786 | UsedJumps += !Grounded; |
787 | } |
788 | else if(pCharacter->m_Jumps == 1) |
789 | { |
790 | // If the player has only one jump, each jump is the last one |
791 | UsedJumps = pPlayer->m_Jumped & 2; |
792 | } |
793 | else if(pCharacter->m_Jumps == -1) |
794 | { |
795 | // The player has only one ground jump |
796 | UsedJumps = !Grounded; |
797 | } |
798 | |
799 | if(pCharacter->m_EndlessJump && UsedJumps >= absolute(a: pCharacter->m_Jumps)) |
800 | { |
801 | UsedJumps = absolute(a: pCharacter->m_Jumps) - 1; |
802 | } |
803 | |
804 | int UnusedJumps = absolute(a: pCharacter->m_Jumps) - UsedJumps; |
805 | if(!(pPlayer->m_Jumped & 2) && UnusedJumps <= 0) |
806 | { |
807 | // In some edge cases when the player just got another number of jumps, UnusedJumps is not correct |
808 | UnusedJumps = 1; |
809 | } |
810 | TotalJumpsToDisplay = maximum(a: minimum(a: absolute(a: pCharacter->m_Jumps), b: 10), b: 0); |
811 | AvailableJumpsToDisplay = maximum(a: minimum(a: UnusedJumps, b: TotalJumpsToDisplay), b: 0); |
812 | } |
813 | else |
814 | { |
815 | TotalJumpsToDisplay = AvailableJumpsToDisplay = absolute(a: m_pClient->m_Snap.m_aCharacters[ClientId].m_ExtendedData.m_Jumps); |
816 | } |
817 | |
818 | // render available and used jumps |
819 | int JumpsOffsetY = ((GameClient()->m_GameInfo.m_HudHealthArmor && g_Config.m_ClShowhudHealthAmmo ? 24 : 0) + |
820 | (GameClient()->m_GameInfo.m_HudAmmo && g_Config.m_ClShowhudHealthAmmo ? 12 : 0)); |
821 | if(JumpsOffsetY > 0) |
822 | { |
823 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudAirjump); |
824 | Graphics()->RenderQuadContainerEx(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_AirjumpOffset, QuadDrawNum: AvailableJumpsToDisplay, X: 0, Y: JumpsOffsetY); |
825 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudAirjumpEmpty); |
826 | Graphics()->RenderQuadContainerEx(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_AirjumpEmptyOffset + AvailableJumpsToDisplay, QuadDrawNum: TotalJumpsToDisplay - AvailableJumpsToDisplay, X: 0, Y: JumpsOffsetY); |
827 | } |
828 | else |
829 | { |
830 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudAirjump); |
831 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_AirjumpOffset, QuadDrawNum: AvailableJumpsToDisplay); |
832 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudAirjumpEmpty); |
833 | Graphics()->RenderQuadContainer(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_AirjumpEmptyOffset + AvailableJumpsToDisplay, QuadDrawNum: TotalJumpsToDisplay - AvailableJumpsToDisplay); |
834 | } |
835 | } |
836 | |
837 | float x = 5 + 12; |
838 | float y = (5 + 12 + (GameClient()->m_GameInfo.m_HudHealthArmor && g_Config.m_ClShowhudHealthAmmo ? 24 : 0) + |
839 | (GameClient()->m_GameInfo.m_HudAmmo && g_Config.m_ClShowhudHealthAmmo ? 12 : 0)); |
840 | |
841 | // render weapons |
842 | { |
843 | constexpr float aWeaponWidth[NUM_WEAPONS] = {16, 12, 12, 12, 12, 12}; |
844 | constexpr float aWeaponInitialOffset[NUM_WEAPONS] = {-3, -4, -1, -1, -2, -4}; |
845 | bool InitialOffsetAdded = false; |
846 | for(int Weapon = 0; Weapon < NUM_WEAPONS; ++Weapon) |
847 | { |
848 | if(!pCharacter->m_aWeapons[Weapon].m_Got) |
849 | continue; |
850 | if(!InitialOffsetAdded) |
851 | { |
852 | x += aWeaponInitialOffset[Weapon]; |
853 | InitialOffsetAdded = true; |
854 | } |
855 | if(pPlayer->m_Weapon != Weapon) |
856 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.4f); |
857 | Graphics()->QuadsSetRotation(Angle: pi * 7 / 4); |
858 | Graphics()->TextureSet(Texture: m_pClient->m_GameSkin.m_aSpritePickupWeapons[Weapon]); |
859 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_aWeaponOffset[Weapon], X: x, Y: y); |
860 | Graphics()->QuadsSetRotation(Angle: 0); |
861 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
862 | x += aWeaponWidth[Weapon]; |
863 | } |
864 | if(pCharacter->m_aWeapons[WEAPON_NINJA].m_Got) |
865 | { |
866 | const int Max = g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000; |
867 | float NinjaProgress = clamp(val: pCharacter->m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000 - Client()->GameTick(Conn: g_Config.m_ClDummy), lo: 0, hi: Max) / (float)Max; |
868 | if(NinjaProgress > 0.0f && m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo) |
869 | { |
870 | RenderNinjaBarPos(x, y: y - 12, width: 6.f, height: 24.f, Progress: NinjaProgress); |
871 | } |
872 | } |
873 | } |
874 | |
875 | // render capabilities |
876 | x = 5; |
877 | y += 12; |
878 | if(TotalJumpsToDisplay > 0) |
879 | { |
880 | y += 12; |
881 | } |
882 | bool HasCapabilities = false; |
883 | if(pCharacter->m_EndlessJump) |
884 | { |
885 | HasCapabilities = true; |
886 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudEndlessJump); |
887 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_EndlessJumpOffset, X: x, Y: y); |
888 | x += 12; |
889 | } |
890 | if(pCharacter->m_EndlessHook) |
891 | { |
892 | HasCapabilities = true; |
893 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudEndlessHook); |
894 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_EndlessHookOffset, X: x, Y: y); |
895 | x += 12; |
896 | } |
897 | if(pCharacter->m_Jetpack) |
898 | { |
899 | HasCapabilities = true; |
900 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudJetpack); |
901 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_JetpackOffset, X: x, Y: y); |
902 | x += 12; |
903 | } |
904 | if(pCharacter->m_HasTelegunGun && pCharacter->m_aWeapons[WEAPON_GUN].m_Got) |
905 | { |
906 | HasCapabilities = true; |
907 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudTeleportGun); |
908 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_TeleportGunOffset, X: x, Y: y); |
909 | x += 12; |
910 | } |
911 | if(pCharacter->m_HasTelegunGrenade && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got) |
912 | { |
913 | HasCapabilities = true; |
914 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudTeleportGrenade); |
915 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_TeleportGrenadeOffset, X: x, Y: y); |
916 | x += 12; |
917 | } |
918 | if(pCharacter->m_HasTelegunLaser && pCharacter->m_aWeapons[WEAPON_LASER].m_Got) |
919 | { |
920 | HasCapabilities = true; |
921 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudTeleportLaser); |
922 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_TeleportLaserOffset, X: x, Y: y); |
923 | } |
924 | |
925 | // render prohibited capabilities |
926 | x = 5; |
927 | if(HasCapabilities) |
928 | { |
929 | y += 12; |
930 | } |
931 | bool HasProhibitedCapabilities = false; |
932 | if(pCharacter->m_Solo) |
933 | { |
934 | HasProhibitedCapabilities = true; |
935 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudSolo); |
936 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_SoloOffset, X: x, Y: y); |
937 | x += 12; |
938 | } |
939 | if(pCharacter->m_CollisionDisabled) |
940 | { |
941 | HasProhibitedCapabilities = true; |
942 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudCollisionDisabled); |
943 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_CollisionDisabledOffset, X: x, Y: y); |
944 | x += 12; |
945 | } |
946 | if(pCharacter->m_HookHitDisabled) |
947 | { |
948 | HasProhibitedCapabilities = true; |
949 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudHookHitDisabled); |
950 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_HookHitDisabledOffset, X: x, Y: y); |
951 | x += 12; |
952 | } |
953 | if(pCharacter->m_HammerHitDisabled) |
954 | { |
955 | HasProhibitedCapabilities = true; |
956 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudHammerHitDisabled); |
957 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_HammerHitDisabledOffset, X: x, Y: y); |
958 | x += 12; |
959 | } |
960 | if((pCharacter->m_GrenadeHitDisabled && pCharacter->m_HasTelegunGun && pCharacter->m_aWeapons[WEAPON_GUN].m_Got)) |
961 | { |
962 | HasProhibitedCapabilities = true; |
963 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudGunHitDisabled); |
964 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_LaserHitDisabledOffset, X: x, Y: y); |
965 | x += 12; |
966 | } |
967 | if((pCharacter->m_ShotgunHitDisabled && pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got)) |
968 | { |
969 | HasProhibitedCapabilities = true; |
970 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudShotgunHitDisabled); |
971 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_ShotgunHitDisabledOffset, X: x, Y: y); |
972 | x += 12; |
973 | } |
974 | if((pCharacter->m_GrenadeHitDisabled && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got)) |
975 | { |
976 | HasProhibitedCapabilities = true; |
977 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudGrenadeHitDisabled); |
978 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_GrenadeHitDisabledOffset, X: x, Y: y); |
979 | x += 12; |
980 | } |
981 | if((pCharacter->m_LaserHitDisabled && pCharacter->m_aWeapons[WEAPON_LASER].m_Got)) |
982 | { |
983 | HasProhibitedCapabilities = true; |
984 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudLaserHitDisabled); |
985 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_LaserHitDisabledOffset, X: x, Y: y); |
986 | } |
987 | |
988 | // render dummy actions and freeze state |
989 | x = 5; |
990 | if(HasProhibitedCapabilities) |
991 | { |
992 | y += 12; |
993 | } |
994 | if(m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo && m_pClient->m_Snap.m_aCharacters[ClientId].m_ExtendedData.m_Flags & CHARACTERFLAG_LOCK_MODE) |
995 | { |
996 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudLockMode); |
997 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_LockModeOffset, X: x, Y: y); |
998 | x += 12; |
999 | } |
1000 | if(m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo && m_pClient->m_Snap.m_aCharacters[ClientId].m_ExtendedData.m_Flags & CHARACTERFLAG_PRACTICE_MODE) |
1001 | { |
1002 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudPracticeMode); |
1003 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_PracticeModeOffset, X: x, Y: y); |
1004 | x += 12; |
1005 | } |
1006 | if(m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo && m_pClient->m_Snap.m_aCharacters[ClientId].m_ExtendedData.m_Flags & CHARACTERFLAG_TEAM0_MODE) |
1007 | { |
1008 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudTeam0Mode); |
1009 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_Team0ModeOffset, X: x, Y: y); |
1010 | x += 12; |
1011 | } |
1012 | if(pCharacter->m_DeepFrozen) |
1013 | { |
1014 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudDeepFrozen); |
1015 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_DeepFrozenOffset, X: x, Y: y); |
1016 | x += 12; |
1017 | } |
1018 | if(pCharacter->m_LiveFrozen) |
1019 | { |
1020 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudLiveFrozen); |
1021 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_LiveFrozenOffset, X: x, Y: y); |
1022 | } |
1023 | } |
1024 | |
1025 | void CHud::RenderNinjaBarPos(const float x, float y, const float width, const float height, float Progress, const float Alpha) |
1026 | { |
1027 | Progress = clamp(val: Progress, lo: 0.0f, hi: 1.0f); |
1028 | |
1029 | // what percentage of the end pieces is used for the progress indicator and how much is the rest |
1030 | // half of the ends are used for the progress display |
1031 | const float RestPct = 0.5f; |
1032 | const float ProgPct = 0.5f; |
1033 | |
1034 | const float EndHeight = width; // to keep the correct scale - the width of the sprite is as long as the height |
1035 | const float BarWidth = width; |
1036 | const float WholeBarHeight = height; |
1037 | const float MiddleBarHeight = WholeBarHeight - (EndHeight * 2.0f); |
1038 | const float EndProgressHeight = EndHeight * ProgPct; |
1039 | const float EndRestHeight = EndHeight * RestPct; |
1040 | const float ProgressBarHeight = WholeBarHeight - (EndProgressHeight * 2.0f); |
1041 | const float EndProgressProportion = EndProgressHeight / ProgressBarHeight; |
1042 | const float MiddleProgressProportion = MiddleBarHeight / ProgressBarHeight; |
1043 | |
1044 | // beginning piece |
1045 | float BeginningPieceProgress = 1; |
1046 | if(Progress <= 1) |
1047 | { |
1048 | if(Progress <= (EndProgressProportion + MiddleProgressProportion)) |
1049 | { |
1050 | BeginningPieceProgress = 0; |
1051 | } |
1052 | else |
1053 | { |
1054 | BeginningPieceProgress = (Progress - EndProgressProportion - MiddleProgressProportion) / EndProgressProportion; |
1055 | } |
1056 | } |
1057 | // empty |
1058 | Graphics()->WrapClamp(); |
1059 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight); |
1060 | Graphics()->QuadsBegin(); |
1061 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1062 | // Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise |
1063 | Graphics()->QuadsSetSubsetFree(x0: 1, y0: 1, x1: 1, y1: 0, x2: ProgPct - ProgPct * (1.0f - BeginningPieceProgress), y2: 0, x3: ProgPct - ProgPct * (1.0f - BeginningPieceProgress), y3: 1); |
1064 | IGraphics::CQuadItem QuadEmptyBeginning(x, y, BarWidth, EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress)); |
1065 | Graphics()->QuadsDrawTL(pArray: &QuadEmptyBeginning, Num: 1); |
1066 | Graphics()->QuadsEnd(); |
1067 | // full |
1068 | if(BeginningPieceProgress > 0.0f) |
1069 | { |
1070 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft); |
1071 | Graphics()->QuadsBegin(); |
1072 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1073 | // Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise |
1074 | Graphics()->QuadsSetSubsetFree(x0: RestPct + ProgPct * (1.0f - BeginningPieceProgress), y0: 1, x1: RestPct + ProgPct * (1.0f - BeginningPieceProgress), y1: 0, x2: 1, y2: 0, x3: 1, y3: 1); |
1075 | IGraphics::CQuadItem QuadFullBeginning(x, y + (EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress)), BarWidth, EndProgressHeight * BeginningPieceProgress); |
1076 | Graphics()->QuadsDrawTL(pArray: &QuadFullBeginning, Num: 1); |
1077 | Graphics()->QuadsEnd(); |
1078 | } |
1079 | |
1080 | // middle piece |
1081 | y += EndHeight; |
1082 | |
1083 | float MiddlePieceProgress = 1; |
1084 | if(Progress <= EndProgressProportion + MiddleProgressProportion) |
1085 | { |
1086 | if(Progress <= EndProgressProportion) |
1087 | { |
1088 | MiddlePieceProgress = 0; |
1089 | } |
1090 | else |
1091 | { |
1092 | MiddlePieceProgress = (Progress - EndProgressProportion) / MiddleProgressProportion; |
1093 | } |
1094 | } |
1095 | |
1096 | const float FullMiddleBarHeight = MiddleBarHeight * MiddlePieceProgress; |
1097 | const float EmptyMiddleBarHeight = MiddleBarHeight - FullMiddleBarHeight; |
1098 | |
1099 | // empty ninja bar |
1100 | if(EmptyMiddleBarHeight > 0.0f) |
1101 | { |
1102 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmpty); |
1103 | Graphics()->QuadsBegin(); |
1104 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1105 | // select the middle portion of the sprite so we don't get edge bleeding |
1106 | if(EmptyMiddleBarHeight <= EndHeight) |
1107 | { |
1108 | // prevent pixel puree, select only a small slice |
1109 | // Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise |
1110 | Graphics()->QuadsSetSubsetFree(x0: 1, y0: 1, x1: 1, y1: 0, x2: 1.0f - (EmptyMiddleBarHeight / EndHeight), y2: 0, x3: 1.0f - (EmptyMiddleBarHeight / EndHeight), y3: 1); |
1111 | } |
1112 | else |
1113 | { |
1114 | // Subset: btm_r, top_r, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise |
1115 | Graphics()->QuadsSetSubsetFree(x0: 1, y0: 1, x1: 1, y1: 0, x2: 0, y2: 0, x3: 0, y3: 1); |
1116 | } |
1117 | IGraphics::CQuadItem QuadEmpty(x, y, BarWidth, EmptyMiddleBarHeight); |
1118 | Graphics()->QuadsDrawTL(pArray: &QuadEmpty, Num: 1); |
1119 | Graphics()->QuadsEnd(); |
1120 | } |
1121 | |
1122 | // full ninja bar |
1123 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarFull); |
1124 | Graphics()->QuadsBegin(); |
1125 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1126 | // select the middle portion of the sprite so we don't get edge bleeding |
1127 | if(FullMiddleBarHeight <= EndHeight) |
1128 | { |
1129 | // prevent pixel puree, select only a small slice |
1130 | // Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise |
1131 | Graphics()->QuadsSetSubsetFree(x0: 1.0f - (FullMiddleBarHeight / EndHeight), y0: 1, x1: 1.0f - (FullMiddleBarHeight / EndHeight), y1: 0, x2: 1, y2: 0, x3: 1, y3: 1); |
1132 | } |
1133 | else |
1134 | { |
1135 | // Subset: btm_l, top_l, top_r, btm_r | it is rotated 90 degrees clockwise |
1136 | Graphics()->QuadsSetSubsetFree(x0: 0, y0: 1, x1: 0, y1: 0, x2: 1, y2: 0, x3: 1, y3: 1); |
1137 | } |
1138 | IGraphics::CQuadItem QuadFull(x, y + EmptyMiddleBarHeight, BarWidth, FullMiddleBarHeight); |
1139 | Graphics()->QuadsDrawTL(pArray: &QuadFull, Num: 1); |
1140 | Graphics()->QuadsEnd(); |
1141 | |
1142 | // ending piece |
1143 | y += MiddleBarHeight; |
1144 | float EndingPieceProgress = 1; |
1145 | if(Progress <= EndProgressProportion) |
1146 | { |
1147 | EndingPieceProgress = Progress / EndProgressProportion; |
1148 | } |
1149 | // empty |
1150 | if(EndingPieceProgress < 1.0f) |
1151 | { |
1152 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight); |
1153 | Graphics()->QuadsBegin(); |
1154 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1155 | // Subset: btm_l, top_l, top_m, btm_m | it is rotated 90 degrees clockwise |
1156 | Graphics()->QuadsSetSubsetFree(x0: 0, y0: 1, x1: 0, y1: 0, x2: ProgPct - ProgPct * EndingPieceProgress, y2: 0, x3: ProgPct - ProgPct * EndingPieceProgress, y3: 1); |
1157 | IGraphics::CQuadItem QuadEmptyEnding(x, y, BarWidth, EndProgressHeight * (1.0f - EndingPieceProgress)); |
1158 | Graphics()->QuadsDrawTL(pArray: &QuadEmptyEnding, Num: 1); |
1159 | Graphics()->QuadsEnd(); |
1160 | } |
1161 | // full |
1162 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft); |
1163 | Graphics()->QuadsBegin(); |
1164 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
1165 | // Subset: btm_m, top_m, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise |
1166 | Graphics()->QuadsSetSubsetFree(x0: RestPct + ProgPct * EndingPieceProgress, y0: 1, x1: RestPct + ProgPct * EndingPieceProgress, y1: 0, x2: 0, y2: 0, x3: 0, y3: 1); |
1167 | IGraphics::CQuadItem QuadFullEnding(x, y + (EndProgressHeight * (1.0f - EndingPieceProgress)), BarWidth, EndRestHeight + EndProgressHeight * EndingPieceProgress); |
1168 | Graphics()->QuadsDrawTL(pArray: &QuadFullEnding, Num: 1); |
1169 | Graphics()->QuadsEnd(); |
1170 | |
1171 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
1172 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
1173 | Graphics()->WrapNormal(); |
1174 | } |
1175 | |
1176 | void CHud::RenderDummyActions() |
1177 | { |
1178 | if(!g_Config.m_ClShowhudDummyActions || (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) || !Client()->DummyConnected()) |
1179 | { |
1180 | return; |
1181 | } |
1182 | // render small dummy actions hud |
1183 | const float BoxHeight = 29.0f; |
1184 | const float BoxWidth = 16.0f; |
1185 | |
1186 | float StartX = m_Width - BoxWidth; |
1187 | float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display; |
1188 | if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle) |
1189 | { |
1190 | StartY -= 4; |
1191 | } |
1192 | StartY -= GetMovementInformationBoxHeight(); |
1193 | |
1194 | if(g_Config.m_ClShowhudScore) |
1195 | { |
1196 | StartY -= 56; |
1197 | } |
1198 | |
1199 | Graphics()->DrawRect(x: StartX, y: StartY, w: BoxWidth, h: BoxHeight, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), Corners: IGraphics::CORNER_L, Rounding: 5.0f); |
1200 | |
1201 | float y = StartY + 2; |
1202 | float x = StartX + 2; |
1203 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.4f); |
1204 | if(g_Config.m_ClDummyHammer) |
1205 | { |
1206 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
1207 | } |
1208 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudDummyHammer); |
1209 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_DummyHammerOffset, X: x, Y: y); |
1210 | y += 13; |
1211 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.4f); |
1212 | if(g_Config.m_ClDummyCopyMoves) |
1213 | { |
1214 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
1215 | } |
1216 | Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudDummyCopy); |
1217 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_HudQuadContainerIndex, QuadOffset: m_DummyCopyOffset, X: x, Y: y); |
1218 | } |
1219 | |
1220 | inline int CHud::GetDigitsIndex(int Value, int Max) |
1221 | { |
1222 | if(Value < 0) |
1223 | { |
1224 | Value *= -1; |
1225 | } |
1226 | int DigitsIndex = std::log10(x: (Value ? Value : 1)); |
1227 | if(DigitsIndex > Max) |
1228 | { |
1229 | DigitsIndex = Max; |
1230 | } |
1231 | if(DigitsIndex < 0) |
1232 | { |
1233 | DigitsIndex = 0; |
1234 | } |
1235 | return DigitsIndex; |
1236 | } |
1237 | |
1238 | inline float CHud::GetMovementInformationBoxHeight() |
1239 | { |
1240 | float BoxHeight = 3 * MOVEMENT_INFORMATION_LINE_HEIGHT * (g_Config.m_ClShowhudPlayerPosition + g_Config.m_ClShowhudPlayerSpeed) + 2 * MOVEMENT_INFORMATION_LINE_HEIGHT * g_Config.m_ClShowhudPlayerAngle; |
1241 | if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle) |
1242 | { |
1243 | BoxHeight += 2; |
1244 | } |
1245 | return BoxHeight; |
1246 | } |
1247 | |
1248 | void CHud::RenderMovementInformation(const int ClientId) |
1249 | { |
1250 | // Draw the infomations depending on settings: Position, speed and target angle |
1251 | // This display is only to present the available information from the last snapshot, not to interpolate or predict |
1252 | if(!g_Config.m_ClShowhudPlayerPosition && !g_Config.m_ClShowhudPlayerSpeed && !g_Config.m_ClShowhudPlayerAngle) |
1253 | { |
1254 | return; |
1255 | } |
1256 | const float LineSpacer = 1.0f; // above and below each entry |
1257 | const float Fontsize = 6.0f; |
1258 | |
1259 | float BoxHeight = GetMovementInformationBoxHeight(); |
1260 | const float BoxWidth = 62.0f; |
1261 | |
1262 | float StartX = m_Width - BoxWidth; |
1263 | float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display; |
1264 | if(g_Config.m_ClShowhudScore) |
1265 | { |
1266 | StartY -= 56; |
1267 | } |
1268 | |
1269 | Graphics()->DrawRect(x: StartX, y: StartY, w: BoxWidth, h: BoxHeight, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), Corners: IGraphics::CORNER_L, Rounding: 5.0f); |
1270 | |
1271 | const CNetObj_Character *pPrevChar = &m_pClient->m_Snap.m_aCharacters[ClientId].m_Prev; |
1272 | const CNetObj_Character *pCurChar = &m_pClient->m_Snap.m_aCharacters[ClientId].m_Cur; |
1273 | const float IntraTick = Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
1274 | |
1275 | // To make the player position relative to blocks we need to divide by the block size |
1276 | const vec2 Pos = mix(a: vec2(pPrevChar->m_X, pPrevChar->m_Y), b: vec2(pCurChar->m_X, pCurChar->m_Y), amount: IntraTick) / 32.0f; |
1277 | |
1278 | const vec2 Vel = mix(a: vec2(pPrevChar->m_VelX, pPrevChar->m_VelY), b: vec2(pCurChar->m_VelX, pCurChar->m_VelY), amount: IntraTick); |
1279 | |
1280 | float VelspeedX = Vel.x / 256.0f * Client()->GameTickSpeed(); |
1281 | if(Vel.x >= -1 && Vel.x <= 1) |
1282 | { |
1283 | VelspeedX = 0; |
1284 | } |
1285 | float VelspeedY = Vel.y / 256.0f * Client()->GameTickSpeed(); |
1286 | if(Vel.y >= -128 && Vel.y <= 128) |
1287 | { |
1288 | VelspeedY = 0; |
1289 | } |
1290 | // We show the speed in Blocks per Second (Bps) and therefore have to divide by the block size |
1291 | float DisplaySpeedX = VelspeedX / 32; |
1292 | float VelspeedLength = length(a: vec2(Vel.x, Vel.y) / 256.0f) * Client()->GameTickSpeed(); |
1293 | // Todo: Use Velramp tuning of each individual player |
1294 | // Since these tuning parameters are almost never changed, the default values are sufficient in most cases |
1295 | float Ramp = VelocityRamp(Value: VelspeedLength, Start: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature); |
1296 | DisplaySpeedX *= Ramp; |
1297 | float DisplaySpeedY = VelspeedY / 32; |
1298 | |
1299 | float Angle = m_pClient->m_Players.GetPlayerTargetAngle(pPrevChar, pPlayerChar: pCurChar, ClientId, Intra: IntraTick); |
1300 | if(Angle < 0) |
1301 | { |
1302 | Angle += 2.0f * pi; |
1303 | } |
1304 | float DisplayAngle = Angle * 180.0f / pi; |
1305 | |
1306 | char aBuf[128]; |
1307 | float w; |
1308 | |
1309 | float y = StartY + LineSpacer * 2; |
1310 | float xl = StartX + 2; |
1311 | float xr = m_Width - 2; |
1312 | int DigitsIndex; |
1313 | |
1314 | static float s_TextWidth0 = TextRender()->TextWidth(Size: Fontsize, pText: "0.00" , StrLength: -1, LineWidth: -1.0f); |
1315 | static float s_TextWidth00 = TextRender()->TextWidth(Size: Fontsize, pText: "00.00" , StrLength: -1, LineWidth: -1.0f); |
1316 | static float s_TextWidth000 = TextRender()->TextWidth(Size: Fontsize, pText: "000.00" , StrLength: -1, LineWidth: -1.0f); |
1317 | static float s_TextWidth0000 = TextRender()->TextWidth(Size: Fontsize, pText: "0000.00" , StrLength: -1, LineWidth: -1.0f); |
1318 | static float s_TextWidth00000 = TextRender()->TextWidth(Size: Fontsize, pText: "00000.00" , StrLength: -1, LineWidth: -1.0f); |
1319 | static float s_TextWidth000000 = TextRender()->TextWidth(Size: Fontsize, pText: "000000.00" , StrLength: -1, LineWidth: -1.0f); |
1320 | static float s_aTextWidth[6] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000, s_TextWidth000000}; |
1321 | static float s_TextWidthMinus0 = TextRender()->TextWidth(Size: Fontsize, pText: "-0.00" , StrLength: -1, LineWidth: -1.0f); |
1322 | static float s_TextWidthMinus00 = TextRender()->TextWidth(Size: Fontsize, pText: "-00.00" , StrLength: -1, LineWidth: -1.0f); |
1323 | static float s_TextWidthMinus000 = TextRender()->TextWidth(Size: Fontsize, pText: "-000.00" , StrLength: -1, LineWidth: -1.0f); |
1324 | static float s_TextWidthMinus0000 = TextRender()->TextWidth(Size: Fontsize, pText: "-0000.00" , StrLength: -1, LineWidth: -1.0f); |
1325 | static float s_TextWidthMinus00000 = TextRender()->TextWidth(Size: Fontsize, pText: "-00000.00" , StrLength: -1, LineWidth: -1.0f); |
1326 | static float s_TextWidthMinus000000 = TextRender()->TextWidth(Size: Fontsize, pText: "-000000.00" , StrLength: -1, LineWidth: -1.0f); |
1327 | static float s_aTextWidthMinus[6] = {s_TextWidthMinus0, s_TextWidthMinus00, s_TextWidthMinus000, s_TextWidthMinus0000, s_TextWidthMinus00000, s_TextWidthMinus000000}; |
1328 | |
1329 | if(g_Config.m_ClShowhudPlayerPosition) |
1330 | { |
1331 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: Localize(pStr: "Position:" ), LineWidth: -1.0f); |
1332 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1333 | |
1334 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: "X:" , LineWidth: -1.0f); |
1335 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f" , Pos.x); |
1336 | DigitsIndex = GetDigitsIndex(Value: Pos.x, Max: 5); |
1337 | w = (Pos.x < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex]; |
1338 | TextRender()->Text(x: xr - w, y, Size: Fontsize, pText: aBuf, LineWidth: -1.0f); |
1339 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1340 | |
1341 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: "Y:" , LineWidth: -1.0f); |
1342 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f" , Pos.y); |
1343 | DigitsIndex = GetDigitsIndex(Value: Pos.y, Max: 5); |
1344 | w = (Pos.y < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex]; |
1345 | TextRender()->Text(x: xr - w, y, Size: Fontsize, pText: aBuf, LineWidth: -1.0f); |
1346 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1347 | } |
1348 | |
1349 | if(g_Config.m_ClShowhudPlayerSpeed) |
1350 | { |
1351 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: Localize(pStr: "Speed:" ), LineWidth: -1.0f); |
1352 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1353 | |
1354 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: "X:" , LineWidth: -1.0f); |
1355 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f" , DisplaySpeedX); |
1356 | DigitsIndex = GetDigitsIndex(Value: DisplaySpeedX, Max: 5); |
1357 | w = (DisplaySpeedX < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex]; |
1358 | TextRender()->Text(x: xr - w, y, Size: Fontsize, pText: aBuf, LineWidth: -1.0f); |
1359 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1360 | |
1361 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: "Y:" , LineWidth: -1.0f); |
1362 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f" , DisplaySpeedY); |
1363 | DigitsIndex = GetDigitsIndex(Value: DisplaySpeedY, Max: 5); |
1364 | w = (DisplaySpeedY < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex]; |
1365 | TextRender()->Text(x: xr - w, y, Size: Fontsize, pText: aBuf, LineWidth: -1.0f); |
1366 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1367 | } |
1368 | |
1369 | if(g_Config.m_ClShowhudPlayerAngle) |
1370 | { |
1371 | TextRender()->Text(x: xl, y, Size: Fontsize, pText: Localize(pStr: "Angle:" ), LineWidth: -1.0f); |
1372 | y += MOVEMENT_INFORMATION_LINE_HEIGHT; |
1373 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f" , DisplayAngle); |
1374 | DigitsIndex = GetDigitsIndex(Value: DisplayAngle, Max: 5); |
1375 | w = (DisplayAngle < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex]; |
1376 | TextRender()->Text(x: xr - w, y, Size: Fontsize, pText: aBuf, LineWidth: -1.0f); |
1377 | } |
1378 | } |
1379 | |
1380 | void CHud::RenderSpectatorHud() |
1381 | { |
1382 | // draw the box |
1383 | Graphics()->DrawRect(x: m_Width - 180.0f, y: m_Height - 15.0f, w: 180.0f, h: 15.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), Corners: IGraphics::CORNER_TL, Rounding: 5.0f); |
1384 | |
1385 | // draw the text |
1386 | char aBuf[128]; |
1387 | if(GameClient()->m_MultiViewActivated) |
1388 | { |
1389 | str_copy(dst&: aBuf, src: Localize(pStr: "Multi-View" )); |
1390 | } |
1391 | else if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW) |
1392 | { |
1393 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Following %s" , pContext: "Spectating" ), m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorId].m_aName); |
1394 | } |
1395 | else |
1396 | { |
1397 | str_copy(dst&: aBuf, src: Localize(pStr: "Free-View" )); |
1398 | } |
1399 | TextRender()->Text(x: m_Width - 174.0f, y: m_Height - 15.0f + (15.f - 8.f) / 2.f, Size: 8.0f, pText: aBuf, LineWidth: -1.0f); |
1400 | } |
1401 | |
1402 | void CHud::RenderLocalTime(float x) |
1403 | { |
1404 | if(!g_Config.m_ClShowLocalTimeAlways && !m_pClient->m_Scoreboard.Active()) |
1405 | return; |
1406 | |
1407 | // draw the box |
1408 | Graphics()->DrawRect(x: x - 30.0f, y: 0.0f, w: 25.0f, h: 12.5f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), Corners: IGraphics::CORNER_B, Rounding: 3.75f); |
1409 | |
1410 | // draw the text |
1411 | char aTimeStr[6]; |
1412 | str_timestamp_format(buffer: aTimeStr, buffer_size: sizeof(aTimeStr), format: "%H:%M" ); |
1413 | TextRender()->Text(x: x - 25.0f, y: (12.5f - 5.f) / 2.f, Size: 5.0f, pText: aTimeStr, LineWidth: -1.0f); |
1414 | } |
1415 | |
1416 | void CHud::OnRender() |
1417 | { |
1418 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
1419 | return; |
1420 | |
1421 | if(!m_pClient->m_Snap.m_pGameInfoObj) |
1422 | return; |
1423 | |
1424 | m_Width = 300.0f * Graphics()->ScreenAspect(); |
1425 | m_Height = 300.0f; |
1426 | Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: m_Width, BottomRightY: m_Height); |
1427 | |
1428 | #if defined(CONF_VIDEORECORDER) |
1429 | if((IVideo::Current() && g_Config.m_ClVideoShowhud) || (!IVideo::Current() && g_Config.m_ClShowhud)) |
1430 | #else |
1431 | if(g_Config.m_ClShowhud) |
1432 | #endif |
1433 | { |
1434 | if(m_pClient->m_Snap.m_pLocalCharacter && !m_pClient->m_Snap.m_SpecInfo.m_Active && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) |
1435 | { |
1436 | if(g_Config.m_ClShowhudHealthAmmo) |
1437 | { |
1438 | RenderAmmoHealthAndArmor(pCharacter: m_pClient->m_Snap.m_pLocalCharacter); |
1439 | } |
1440 | if(m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_HasExtendedData && g_Config.m_ClShowhudDDRace && GameClient()->m_GameInfo.m_HudDDRace) |
1441 | { |
1442 | RenderPlayerState(ClientId: m_pClient->m_Snap.m_LocalClientId); |
1443 | } |
1444 | RenderMovementInformation(ClientId: m_pClient->m_Snap.m_LocalClientId); |
1445 | RenderDDRaceEffects(); |
1446 | } |
1447 | else if(m_pClient->m_Snap.m_SpecInfo.m_Active) |
1448 | { |
1449 | int SpectatorId = m_pClient->m_Snap.m_SpecInfo.m_SpectatorId; |
1450 | if(SpectatorId != SPEC_FREEVIEW && g_Config.m_ClShowhudHealthAmmo) |
1451 | { |
1452 | RenderAmmoHealthAndArmor(pCharacter: &m_pClient->m_Snap.m_aCharacters[SpectatorId].m_Cur); |
1453 | } |
1454 | if(SpectatorId != SPEC_FREEVIEW && |
1455 | m_pClient->m_Snap.m_aCharacters[SpectatorId].m_HasExtendedData && |
1456 | g_Config.m_ClShowhudDDRace && |
1457 | (!GameClient()->m_MultiViewActivated || GameClient()->m_MultiViewShowHud) && |
1458 | GameClient()->m_GameInfo.m_HudDDRace) |
1459 | { |
1460 | RenderPlayerState(ClientId: SpectatorId); |
1461 | } |
1462 | if(SpectatorId != SPEC_FREEVIEW) |
1463 | { |
1464 | RenderMovementInformation(ClientId: SpectatorId); |
1465 | } |
1466 | RenderSpectatorHud(); |
1467 | } |
1468 | |
1469 | if(g_Config.m_ClShowhudTimer) |
1470 | RenderGameTimer(); |
1471 | RenderPauseNotification(); |
1472 | RenderSuddenDeath(); |
1473 | if(g_Config.m_ClShowhudScore) |
1474 | RenderScoreHud(); |
1475 | RenderDummyActions(); |
1476 | RenderWarmupTimer(); |
1477 | RenderTextInfo(); |
1478 | RenderLocalTime(x: (m_Width / 7) * 3); |
1479 | if(Client()->State() != IClient::STATE_DEMOPLAYBACK) |
1480 | RenderConnectionWarning(); |
1481 | RenderTeambalanceWarning(); |
1482 | m_pClient->m_Voting.Render(); |
1483 | if(g_Config.m_ClShowRecord) |
1484 | RenderRecord(); |
1485 | } |
1486 | RenderCursor(); |
1487 | } |
1488 | |
1489 | void CHud::OnMessage(int MsgType, void *pRawMsg) |
1490 | { |
1491 | if(MsgType == NETMSGTYPE_SV_DDRACETIME || MsgType == NETMSGTYPE_SV_DDRACETIMELEGACY) |
1492 | { |
1493 | CNetMsg_Sv_DDRaceTime *pMsg = (CNetMsg_Sv_DDRaceTime *)pRawMsg; |
1494 | |
1495 | m_DDRaceTime = pMsg->m_Time; |
1496 | |
1497 | m_ShowFinishTime = pMsg->m_Finish != 0; |
1498 | |
1499 | if(!m_ShowFinishTime) |
1500 | { |
1501 | m_TimeCpDiff = (float)pMsg->m_Check / 100; |
1502 | m_TimeCpLastReceivedTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
1503 | } |
1504 | else |
1505 | { |
1506 | m_FinishTimeDiff = (float)pMsg->m_Check / 100; |
1507 | m_FinishTimeLastReceivedTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
1508 | } |
1509 | } |
1510 | else if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY) |
1511 | { |
1512 | CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; |
1513 | |
1514 | // NETMSGTYPE_SV_RACETIME on old race servers |
1515 | if(MsgType == NETMSGTYPE_SV_RECORDLEGACY && m_pClient->m_GameInfo.m_DDRaceRecordMessage) |
1516 | { |
1517 | m_DDRaceTime = pMsg->m_ServerTimeBest; // First value: m_Time |
1518 | |
1519 | m_FinishTimeLastReceivedTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
1520 | |
1521 | if(pMsg->m_PlayerTimeBest) // Second value: m_Check |
1522 | { |
1523 | m_TimeCpDiff = (float)pMsg->m_PlayerTimeBest / 100; |
1524 | m_TimeCpLastReceivedTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
1525 | } |
1526 | } |
1527 | else if(MsgType == NETMSGTYPE_SV_RECORD || m_pClient->m_GameInfo.m_RaceRecordMessage) |
1528 | { |
1529 | m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100; |
1530 | m_aPlayerRecord[g_Config.m_ClDummy] = (float)pMsg->m_PlayerTimeBest / 100; |
1531 | } |
1532 | } |
1533 | } |
1534 | |
1535 | void CHud::RenderDDRaceEffects() |
1536 | { |
1537 | if(m_DDRaceTime) |
1538 | { |
1539 | char aBuf[64]; |
1540 | char aTime[32]; |
1541 | if(m_ShowFinishTime && m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
1542 | { |
1543 | str_time(centisecs: m_DDRaceTime, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1544 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Finish time: %s" , aTime); |
1545 | |
1546 | // calculate alpha (4 sec 1 than get lower the next 2 sec) |
1547 | float Alpha = 1.0f; |
1548 | if(m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 4 < Client()->GameTick(Conn: g_Config.m_ClDummy) && m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
1549 | { |
1550 | // lower the alpha slowly to blend text out |
1551 | Alpha = ((float)(m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6) - (float)Client()->GameTick(Conn: g_Config.m_ClDummy)) / (float)(Client()->GameTickSpeed() * 2); |
1552 | } |
1553 | |
1554 | TextRender()->TextColor(r: 1, g: 1, b: 1, a: Alpha); |
1555 | CTextCursor Cursor; |
1556 | TextRender()->SetCursor(pCursor: &Cursor, x: 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(Size: 12, pText: aBuf, StrLength: -1, LineWidth: -1.0f) / 2, y: 20, FontSize: 12, Flags: TEXTFLAG_RENDER); |
1557 | Cursor.m_LineWidth = -1.0f; |
1558 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_DDRaceEffectsTextContainerIndex, pCursor: &Cursor, pText: aBuf); |
1559 | if(m_FinishTimeDiff != 0.0f && m_DDRaceEffectsTextContainerIndex.Valid()) |
1560 | { |
1561 | if(m_FinishTimeDiff < 0) |
1562 | { |
1563 | str_time_float(secs: -m_FinishTimeDiff, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1564 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "-%s" , aTime); |
1565 | TextRender()->TextColor(r: 0.5f, g: 1.0f, b: 0.5f, a: Alpha); // green |
1566 | } |
1567 | else |
1568 | { |
1569 | str_time_float(secs: m_FinishTimeDiff, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1570 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "+%s" , aTime); |
1571 | TextRender()->TextColor(r: 1.0f, g: 0.5f, b: 0.5f, a: Alpha); // red |
1572 | } |
1573 | TextRender()->SetCursor(pCursor: &Cursor, x: 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(Size: 10, pText: aBuf, StrLength: -1, LineWidth: -1.0f) / 2, y: 34, FontSize: 10, Flags: TEXTFLAG_RENDER); |
1574 | Cursor.m_LineWidth = -1.0f; |
1575 | TextRender()->AppendTextContainer(TextContainerIndex: m_DDRaceEffectsTextContainerIndex, pCursor: &Cursor, pText: aBuf); |
1576 | } |
1577 | if(m_DDRaceEffectsTextContainerIndex.Valid()) |
1578 | { |
1579 | auto OutlineColor = TextRender()->DefaultTextOutlineColor(); |
1580 | OutlineColor.a *= Alpha; |
1581 | TextRender()->RenderTextContainer(TextContainerIndex: m_DDRaceEffectsTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: OutlineColor); |
1582 | } |
1583 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
1584 | } |
1585 | else if(g_Config.m_ClShowhudTimeCpDiff && !m_ShowFinishTime && m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
1586 | { |
1587 | if(m_TimeCpDiff < 0) |
1588 | { |
1589 | str_time_float(secs: -m_TimeCpDiff, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1590 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "-%s" , aTime); |
1591 | } |
1592 | else |
1593 | { |
1594 | str_time_float(secs: m_TimeCpDiff, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1595 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "+%s" , aTime); |
1596 | } |
1597 | |
1598 | // calculate alpha (4 sec 1 than get lower the next 2 sec) |
1599 | float Alpha = 1.0f; |
1600 | if(m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 4 < Client()->GameTick(Conn: g_Config.m_ClDummy) && m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
1601 | { |
1602 | // lower the alpha slowly to blend text out |
1603 | Alpha = ((float)(m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6) - (float)Client()->GameTick(Conn: g_Config.m_ClDummy)) / (float)(Client()->GameTickSpeed() * 2); |
1604 | } |
1605 | |
1606 | if(m_TimeCpDiff > 0) |
1607 | TextRender()->TextColor(r: 1.0f, g: 0.5f, b: 0.5f, a: Alpha); // red |
1608 | else if(m_TimeCpDiff < 0) |
1609 | TextRender()->TextColor(r: 0.5f, g: 1.0f, b: 0.5f, a: Alpha); // green |
1610 | else if(!m_TimeCpDiff) |
1611 | TextRender()->TextColor(r: 1, g: 1, b: 1, a: Alpha); // white |
1612 | |
1613 | CTextCursor Cursor; |
1614 | TextRender()->SetCursor(pCursor: &Cursor, x: 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(Size: 10, pText: aBuf, StrLength: -1, LineWidth: -1.0f) / 2, y: 20, FontSize: 10, Flags: TEXTFLAG_RENDER); |
1615 | Cursor.m_LineWidth = -1.0f; |
1616 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_DDRaceEffectsTextContainerIndex, pCursor: &Cursor, pText: aBuf); |
1617 | |
1618 | if(m_DDRaceEffectsTextContainerIndex.Valid()) |
1619 | { |
1620 | auto OutlineColor = TextRender()->DefaultTextOutlineColor(); |
1621 | OutlineColor.a *= Alpha; |
1622 | TextRender()->RenderTextContainer(TextContainerIndex: m_DDRaceEffectsTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: OutlineColor); |
1623 | } |
1624 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
1625 | } |
1626 | } |
1627 | } |
1628 | |
1629 | void CHud::RenderRecord() |
1630 | { |
1631 | if(m_ServerRecord > 0.0f) |
1632 | { |
1633 | char aBuf[64]; |
1634 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Server best:" )); |
1635 | TextRender()->Text(x: 5, y: 75, Size: 6, pText: aBuf, LineWidth: -1.0f); |
1636 | char aTime[32]; |
1637 | str_time_float(secs: m_ServerRecord, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1638 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s%s" , m_ServerRecord > 3600 ? "" : " " , aTime); |
1639 | TextRender()->Text(x: 53, y: 75, Size: 6, pText: aBuf, LineWidth: -1.0f); |
1640 | } |
1641 | |
1642 | const float PlayerRecord = m_aPlayerRecord[g_Config.m_ClDummy]; |
1643 | if(PlayerRecord > 0.0f) |
1644 | { |
1645 | char aBuf[64]; |
1646 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Personal best:" )); |
1647 | TextRender()->Text(x: 5, y: 82, Size: 6, pText: aBuf, LineWidth: -1.0f); |
1648 | char aTime[32]; |
1649 | str_time_float(secs: PlayerRecord, format: TIME_HOURS_CENTISECS, buffer: aTime, buffer_size: sizeof(aTime)); |
1650 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s%s" , PlayerRecord > 3600 ? "" : " " , aTime); |
1651 | TextRender()->Text(x: 53, y: 82, Size: 6, pText: aBuf, LineWidth: -1.0f); |
1652 | } |
1653 | } |
1654 | |