1 | #include <game/client/gameclient.h> |
2 | |
3 | #include "freezebars.h" |
4 | |
5 | void 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 | |
39 | void 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 | |
190 | inline 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 | |
197 | void 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 | |