1#include <game/client/gameclient.h>
2
3#include "freezebars.h"
4
5void CFreezeBars::RenderFreezeBar(const int ClientId)
6{
7 const float FreezeBarWidth = 64.0f;
8 const float FreezeBarHalfWidth = 32.0f;
9 const float FreezeBarHight = 16.0f;
10
11 // pCharacter contains the predicted character for local players or the last snap for players who are spectated
12 CCharacterCore *pCharacter = &m_pClient->m_aClients[ClientId].m_Predicted;
13
14 if(pCharacter->m_FreezeEnd <= 0 || pCharacter->m_FreezeStart == 0 || pCharacter->m_FreezeEnd <= pCharacter->m_FreezeStart || !m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo || (pCharacter->m_IsInFreeze && g_Config.m_ClFreezeBarsAlphaInsideFreeze == 0))
15 {
16 return;
17 }
18
19 const int Max = pCharacter->m_FreezeEnd - pCharacter->m_FreezeStart;
20 float FreezeProgress = clamp(val: Max - (Client()->GameTick(Conn: g_Config.m_ClDummy) - pCharacter->m_FreezeStart), lo: 0, hi: Max) / (float)Max;
21 if(FreezeProgress <= 0.0f)
22 {
23 return;
24 }
25
26 vec2 Position = m_pClient->m_aClients[ClientId].m_RenderPos;
27 Position.x -= FreezeBarHalfWidth;
28 Position.y += 32;
29
30 float Alpha = m_pClient->IsOtherTeam(ClientId) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f;
31 if(pCharacter->m_IsInFreeze)
32 {
33 Alpha *= g_Config.m_ClFreezeBarsAlphaInsideFreeze / 100.0f;
34 }
35
36 RenderFreezeBarPos(x: Position.x, y: Position.y, width: FreezeBarWidth, height: FreezeBarHight, Progress: FreezeProgress, Alpha);
37}
38
39void CFreezeBars::RenderFreezeBarPos(float x, const float y, const float width, const float height, float Progress, const float Alpha)
40{
41 Progress = clamp(val: Progress, lo: 0.0f, hi: 1.0f);
42
43 // what percentage of the end pieces is used for the progress indicator and how much is the rest
44 // half of the ends are used for the progress display
45 const float RestPct = 0.5f;
46 const float ProgPct = 0.5f;
47
48 const float EndWidth = height; // to keep the correct scale - the height of the sprite is as long as the width
49 const float BarHeight = height;
50 const float WholeBarWidth = width;
51 const float MiddleBarWidth = WholeBarWidth - (EndWidth * 2.0f);
52 const float EndProgressWidth = EndWidth * ProgPct;
53 const float EndRestWidth = EndWidth * RestPct;
54 const float ProgressBarWidth = WholeBarWidth - (EndProgressWidth * 2.0f);
55 const float EndProgressProportion = EndProgressWidth / ProgressBarWidth;
56 const float MiddleProgressProportion = MiddleBarWidth / ProgressBarWidth;
57
58 // beginning piece
59 float BeginningPieceProgress = 1;
60 if(Progress <= EndProgressProportion)
61 {
62 BeginningPieceProgress = Progress / EndProgressProportion;
63 }
64
65 // full
66 Graphics()->WrapClamp();
67 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarFullLeft);
68 Graphics()->QuadsBegin();
69 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
70 // Subset: top_l, top_m, btm_m, btm_l
71 Graphics()->QuadsSetSubsetFree(x0: 0, y0: 0, x1: RestPct + ProgPct * BeginningPieceProgress, y1: 0, x2: RestPct + ProgPct * BeginningPieceProgress, y2: 1, x3: 0, y3: 1);
72 IGraphics::CQuadItem QuadFullBeginning(x, y, EndRestWidth + EndProgressWidth * BeginningPieceProgress, BarHeight);
73 Graphics()->QuadsDrawTL(pArray: &QuadFullBeginning, Num: 1);
74 Graphics()->QuadsEnd();
75
76 // empty
77 if(BeginningPieceProgress < 1.0f)
78 {
79 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmptyRight);
80 Graphics()->QuadsBegin();
81 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
82 // Subset: top_m, top_l, btm_l, btm_m | it is mirrored on the horizontal axe and rotated 180 degrees
83 Graphics()->QuadsSetSubsetFree(x0: ProgPct - ProgPct * BeginningPieceProgress, y0: 0, x1: 0, y1: 0, x2: 0, y2: 1, x3: ProgPct - ProgPct * BeginningPieceProgress, y3: 1);
84 IGraphics::CQuadItem QuadEmptyBeginning(x + EndRestWidth + EndProgressWidth * BeginningPieceProgress, y, EndProgressWidth * (1.0f - BeginningPieceProgress), BarHeight);
85 Graphics()->QuadsDrawTL(pArray: &QuadEmptyBeginning, Num: 1);
86 Graphics()->QuadsEnd();
87 }
88
89 // middle piece
90 x += EndWidth;
91
92 float MiddlePieceProgress = 1;
93 if(Progress <= EndProgressProportion + MiddleProgressProportion)
94 {
95 if(Progress <= EndProgressProportion)
96 {
97 MiddlePieceProgress = 0;
98 }
99 else
100 {
101 MiddlePieceProgress = (Progress - EndProgressProportion) / MiddleProgressProportion;
102 }
103 }
104
105 const float FullMiddleBarWidth = MiddleBarWidth * MiddlePieceProgress;
106 const float EmptyMiddleBarWidth = MiddleBarWidth - FullMiddleBarWidth;
107
108 // full freeze bar
109 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarFull);
110 Graphics()->QuadsBegin();
111 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
112 // select the middle portion of the sprite so we don't get edge bleeding
113 if(FullMiddleBarWidth <= EndWidth)
114 {
115 // prevent pixel puree, select only a small slice
116 // Subset: top_l, top_m, btm_m, btm_l
117 Graphics()->QuadsSetSubsetFree(x0: 0, y0: 0, x1: FullMiddleBarWidth / EndWidth, y1: 0, x2: FullMiddleBarWidth / EndWidth, y2: 1, x3: 0, y3: 1);
118 }
119 else
120 {
121 // Subset: top_l, top_r, btm_r, btm_l
122 Graphics()->QuadsSetSubsetFree(x0: 0, y0: 0, x1: 1, y1: 0, x2: 1, y2: 1, x3: 0, y3: 1);
123 }
124 IGraphics::CQuadItem QuadFull(x, y, FullMiddleBarWidth, BarHeight);
125 Graphics()->QuadsDrawTL(pArray: &QuadFull, Num: 1);
126 Graphics()->QuadsEnd();
127
128 // empty freeze bar
129 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmpty);
130 Graphics()->QuadsBegin();
131 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
132 // select the middle portion of the sprite so we don't get edge bleeding
133 if(EmptyMiddleBarWidth <= EndWidth)
134 {
135 // prevent pixel puree, select only a small slice
136 // Subset: top_m, top_l, btm_l, btm_m | it is mirrored on the horizontal axe and rotated 180 degrees
137 Graphics()->QuadsSetSubsetFree(x0: EmptyMiddleBarWidth / EndWidth, y0: 0, x1: 0, y1: 0, x2: 0, y2: 1, x3: EmptyMiddleBarWidth / EndWidth, y3: 1);
138 }
139 else
140 {
141 // Subset: top_r, top_l, btm_l, btm_r | it is mirrored on the horizontal axe and rotated 180 degrees
142 Graphics()->QuadsSetSubsetFree(x0: 1, y0: 0, x1: 0, y1: 0, x2: 0, y2: 1, x3: 1, y3: 1);
143 }
144
145 IGraphics::CQuadItem QuadEmpty(x + FullMiddleBarWidth, y, EmptyMiddleBarWidth, BarHeight);
146 Graphics()->QuadsDrawTL(pArray: &QuadEmpty, Num: 1);
147 Graphics()->QuadsEnd();
148
149 // end piece
150 x += MiddleBarWidth;
151 float EndingPieceProgress = 1;
152 if(Progress <= 1)
153 {
154 if(Progress <= (EndProgressProportion + MiddleProgressProportion))
155 {
156 EndingPieceProgress = 0;
157 }
158 else
159 {
160 EndingPieceProgress = (Progress - EndProgressProportion - MiddleProgressProportion) / EndProgressProportion;
161 }
162 }
163 if(EndingPieceProgress > 0.0f)
164 {
165 // full
166 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarFullLeft);
167 Graphics()->QuadsBegin();
168 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
169 // Subset: top_r, top_m, btm_m, btm_r | it is mirrored on the horizontal axe and rotated 180 degrees
170 Graphics()->QuadsSetSubsetFree(x0: 1, y0: 0, x1: 1.0f - ProgPct * EndingPieceProgress, y1: 0, x2: 1.0f - ProgPct * EndingPieceProgress, y2: 1, x3: 1, y3: 1);
171 IGraphics::CQuadItem QuadFullEnding(x, y, EndProgressWidth * EndingPieceProgress, BarHeight);
172 Graphics()->QuadsDrawTL(pArray: &QuadFullEnding, Num: 1);
173 Graphics()->QuadsEnd();
174 }
175 // empty
176 Graphics()->TextureSet(Texture: m_pClient->m_HudSkin.m_SpriteHudFreezeBarEmptyRight);
177 Graphics()->QuadsBegin();
178 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha);
179 // Subset: top_m, top_r, btm_r, btm_m
180 Graphics()->QuadsSetSubsetFree(x0: ProgPct - ProgPct * (1.0f - EndingPieceProgress), y0: 0, x1: 1, y1: 0, x2: 1, y2: 1, x3: ProgPct - ProgPct * (1.0f - EndingPieceProgress), y3: 1);
181 IGraphics::CQuadItem QuadEmptyEnding(x + EndProgressWidth * EndingPieceProgress, y, EndProgressWidth * (1.0f - EndingPieceProgress) + EndRestWidth, BarHeight);
182 Graphics()->QuadsDrawTL(pArray: &QuadEmptyEnding, Num: 1);
183 Graphics()->QuadsEnd();
184
185 Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1);
186 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f);
187 Graphics()->WrapNormal();
188}
189
190inline bool CFreezeBars::IsPlayerInfoAvailable(int ClientId) const
191{
192 const void *pPrevInfo = Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: NETOBJTYPE_PLAYERINFO, Id: ClientId);
193 const void *pInfo = Client()->SnapFindItem(SnapId: IClient::SNAP_CURRENT, Type: NETOBJTYPE_PLAYERINFO, Id: ClientId);
194 return pPrevInfo && pInfo;
195}
196
197void CFreezeBars::OnRender()
198{
199 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
200 return;
201
202 if(!g_Config.m_ClShowFreezeBars)
203 {
204 return;
205 }
206 // get screen edges to avoid rendering offscreen
207 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
208 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
209 // expand the edges to prevent popping in/out onscreen
210 //
211 // it is assumed that the tee with the freeze bar fit into a 240x240 box centered on the tee
212 // this may need to be changed or calculated differently in the future
213 float BorderBuffer = 120;
214 ScreenX0 -= BorderBuffer;
215 ScreenX1 += BorderBuffer;
216 ScreenY0 -= BorderBuffer;
217 ScreenY1 += BorderBuffer;
218
219 int LocalClientId = m_pClient->m_Snap.m_LocalClientId;
220
221 // render everyone else's freeze bar, then our own
222 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
223 {
224 if(ClientId == LocalClientId || !m_pClient->m_Snap.m_aCharacters[ClientId].m_Active || !IsPlayerInfoAvailable(ClientId))
225 {
226 continue;
227 }
228
229 //don't render if the tee is offscreen
230 vec2 *pRenderPos = &m_pClient->m_aClients[ClientId].m_RenderPos;
231 if(pRenderPos->x < ScreenX0 || pRenderPos->x > ScreenX1 || pRenderPos->y < ScreenY0 || pRenderPos->y > ScreenY1)
232 {
233 continue;
234 }
235
236 RenderFreezeBar(ClientId);
237 }
238 if(LocalClientId != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientId].m_Active && IsPlayerInfoAvailable(ClientId: LocalClientId))
239 {
240 RenderFreezeBar(ClientId: LocalClientId);
241 }
242}
243