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 | |
4 | #include <engine/demo.h> |
5 | #include <engine/graphics.h> |
6 | #include <engine/shared/config.h> |
7 | #include <game/gamecore.h> |
8 | #include <game/generated/client_data.h> |
9 | #include <game/generated/protocol.h> |
10 | |
11 | #include <game/mapitems.h> |
12 | |
13 | #include <game/client/animstate.h> |
14 | #include <game/client/gameclient.h> |
15 | #include <game/client/render.h> |
16 | |
17 | #include <game/client/components/controls.h> |
18 | #include <game/client/components/effects.h> |
19 | #include <game/client/components/flow.h> |
20 | #include <game/client/components/skins.h> |
21 | #include <game/client/components/sounds.h> |
22 | |
23 | #include "players.h" |
24 | |
25 | #include <base/color.h> |
26 | #include <base/math.h> |
27 | |
28 | void CPlayers::RenderHand(const CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha) |
29 | { |
30 | vec2 HandPos = CenterPos + Dir; |
31 | float Angle = angle(a: Dir); |
32 | if(Dir.x < 0) |
33 | Angle -= AngleOffset; |
34 | else |
35 | Angle += AngleOffset; |
36 | |
37 | vec2 DirX = Dir; |
38 | vec2 DirY(-Dir.y, Dir.x); |
39 | |
40 | if(Dir.x < 0) |
41 | DirY = -DirY; |
42 | |
43 | HandPos += DirX * PostRotOffset.x; |
44 | HandPos += DirY * PostRotOffset.y; |
45 | |
46 | const CSkin::SSkinTextures *pSkinTextures = pInfo->m_CustomColoredSkin ? &pInfo->m_ColorableRenderSkin : &pInfo->m_OriginalRenderSkin; |
47 | |
48 | Graphics()->SetColor(r: pInfo->m_ColorBody.r, g: pInfo->m_ColorBody.g, b: pInfo->m_ColorBody.b, a: Alpha); |
49 | |
50 | // two passes |
51 | for(int i = 0; i < 2; i++) |
52 | { |
53 | int QuadOffset = NUM_WEAPONS * 2 + i; |
54 | Graphics()->QuadsSetRotation(Angle); |
55 | Graphics()->TextureSet(Texture: i == 0 ? pSkinTextures->m_HandsOutline : pSkinTextures->m_Hands); |
56 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: HandPos.x, Y: HandPos.y); |
57 | } |
58 | } |
59 | |
60 | inline float AngularMixDirection(float Src, float Dst) { return std::sin(x: Dst - Src) > 0 ? 1 : -1; } |
61 | |
62 | inline float AngularApproach(float Src, float Dst, float Amount) |
63 | { |
64 | float d = AngularMixDirection(Src, Dst); |
65 | float n = Src + Amount * d; |
66 | if(AngularMixDirection(Src: n, Dst) != d) |
67 | return Dst; |
68 | return n; |
69 | } |
70 | |
71 | float CPlayers::GetPlayerTargetAngle( |
72 | const CNetObj_Character *pPrevChar, |
73 | const CNetObj_Character *pPlayerChar, |
74 | int ClientId, |
75 | float Intra) |
76 | { |
77 | float AngleIntraTick = Intra; |
78 | // using unpredicted angle when rendering other players in-game |
79 | if(ClientId >= 0) |
80 | AngleIntraTick = Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
81 | if(ClientId >= 0 && m_pClient->m_Snap.m_aCharacters[ClientId].m_HasExtendedDisplayInfo) |
82 | { |
83 | CNetObj_DDNetCharacter *pExtendedData = &m_pClient->m_Snap.m_aCharacters[ClientId].m_ExtendedData; |
84 | if(m_pClient->m_Snap.m_aCharacters[ClientId].m_PrevExtendedData) |
85 | { |
86 | const CNetObj_DDNetCharacter *PrevExtendedData = m_pClient->m_Snap.m_aCharacters[ClientId].m_PrevExtendedData; |
87 | |
88 | float MixX = mix(a: (float)PrevExtendedData->m_TargetX, b: (float)pExtendedData->m_TargetX, amount: AngleIntraTick); |
89 | float MixY = mix(a: (float)PrevExtendedData->m_TargetY, b: (float)pExtendedData->m_TargetY, amount: AngleIntraTick); |
90 | |
91 | return angle(a: vec2(MixX, MixY)); |
92 | } |
93 | else |
94 | { |
95 | return angle(a: vec2(pExtendedData->m_TargetX, pExtendedData->m_TargetY)); |
96 | } |
97 | } |
98 | else |
99 | { |
100 | // If the player moves their weapon through top, then change |
101 | // the end angle by 2*Pi, so that the mix function will use the |
102 | // short path and not the long one. |
103 | if(pPlayerChar->m_Angle > (256.0f * pi) && pPrevChar->m_Angle < 0) |
104 | { |
105 | return mix(a: (float)pPrevChar->m_Angle, b: (float)(pPlayerChar->m_Angle - 256.0f * 2 * pi), amount: AngleIntraTick) / 256.0f; |
106 | } |
107 | else if(pPlayerChar->m_Angle < 0 && pPrevChar->m_Angle > (256.0f * pi)) |
108 | { |
109 | return mix(a: (float)pPrevChar->m_Angle, b: (float)(pPlayerChar->m_Angle + 256.0f * 2 * pi), amount: AngleIntraTick) / 256.0f; |
110 | } |
111 | else |
112 | { |
113 | return mix(a: (float)pPrevChar->m_Angle, b: (float)pPlayerChar->m_Angle, amount: AngleIntraTick) / 256.0f; |
114 | } |
115 | } |
116 | } |
117 | |
118 | void CPlayers::RenderHookCollLine( |
119 | const CNetObj_Character *pPrevChar, |
120 | const CNetObj_Character *pPlayerChar, |
121 | int ClientId, |
122 | float Intra) |
123 | { |
124 | CNetObj_Character Prev; |
125 | CNetObj_Character Player; |
126 | Prev = *pPrevChar; |
127 | Player = *pPlayerChar; |
128 | |
129 | bool Local = m_pClient->m_Snap.m_LocalClientId == ClientId; |
130 | bool OtherTeam = m_pClient->IsOtherTeam(ClientId); |
131 | float Alpha = (OtherTeam || ClientId < 0) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f; |
132 | Alpha *= (float)g_Config.m_ClHookCollAlpha / 100; |
133 | |
134 | float IntraTick = Intra; |
135 | if(ClientId >= 0) |
136 | IntraTick = m_pClient->m_aClients[ClientId].m_IsPredicted ? Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy) : Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
137 | |
138 | float Angle; |
139 | if(Local && (!m_pClient->m_Snap.m_SpecInfo.m_Active || m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW) && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
140 | { |
141 | // just use the direct input if it's the local player we are rendering |
142 | Angle = angle(a: m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] * m_pClient->m_Camera.m_Zoom); |
143 | } |
144 | else |
145 | { |
146 | Angle = GetPlayerTargetAngle(pPrevChar: &Prev, pPlayerChar: &Player, ClientId, Intra: IntraTick); |
147 | } |
148 | |
149 | vec2 Direction = direction(angle: Angle); |
150 | vec2 Position; |
151 | if(in_range(a: ClientId, upper: MAX_CLIENTS - 1)) |
152 | Position = m_pClient->m_aClients[ClientId].m_RenderPos; |
153 | else |
154 | Position = mix(a: vec2(Prev.m_X, Prev.m_Y), b: vec2(Player.m_X, Player.m_Y), amount: IntraTick); |
155 | // draw hook collision line |
156 | { |
157 | bool AlwaysRenderHookColl = GameClient()->m_GameInfo.m_AllowHookColl && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) == 2; |
158 | bool RenderHookCollPlayer = ClientId >= 0 && Player.m_PlayerFlags & PLAYERFLAG_AIM && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) > 0; |
159 | if(Local && GameClient()->m_GameInfo.m_AllowHookColl && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
160 | RenderHookCollPlayer = GameClient()->m_Controls.m_aShowHookColl[g_Config.m_ClDummy] && g_Config.m_ClShowHookCollOwn > 0; |
161 | |
162 | bool RenderHookCollVideo = true; |
163 | #if defined(CONF_VIDEORECORDER) |
164 | RenderHookCollVideo = !IVideo::Current() || g_Config.m_ClVideoShowHookCollOther || Local; |
165 | #endif |
166 | if((AlwaysRenderHookColl || RenderHookCollPlayer) && RenderHookCollVideo) |
167 | { |
168 | vec2 ExDirection = Direction; |
169 | |
170 | if(Local && (!m_pClient->m_Snap.m_SpecInfo.m_Active || m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW) && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
171 | { |
172 | ExDirection = normalize( |
173 | v: vec2((int)((int)m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy].x * m_pClient->m_Camera.m_Zoom), |
174 | (int)((int)m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy].y * m_pClient->m_Camera.m_Zoom))); |
175 | |
176 | // fix direction if mouse is exactly in the center |
177 | if(!(int)m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy].x && !(int)m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy].y) |
178 | ExDirection = vec2(1, 0); |
179 | } |
180 | Graphics()->TextureClear(); |
181 | vec2 InitPos = Position; |
182 | vec2 FinishPos = InitPos + ExDirection * (m_pClient->m_aTuning[g_Config.m_ClDummy].m_HookLength - 42.0f); |
183 | |
184 | if(g_Config.m_ClHookCollSize > 0) |
185 | Graphics()->QuadsBegin(); |
186 | else |
187 | Graphics()->LinesBegin(); |
188 | |
189 | ColorRGBA HookCollColor = color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorNoColl)); |
190 | |
191 | vec2 OldPos = InitPos + ExDirection * CCharacterCore::PhysicalSize() * 1.5f; |
192 | vec2 NewPos = OldPos; |
193 | |
194 | bool DoBreak = false; |
195 | |
196 | do |
197 | { |
198 | OldPos = NewPos; |
199 | NewPos = OldPos + ExDirection * m_pClient->m_aTuning[g_Config.m_ClDummy].m_HookFireSpeed; |
200 | |
201 | if(distance(a: InitPos, b: NewPos) > m_pClient->m_aTuning[g_Config.m_ClDummy].m_HookLength) |
202 | { |
203 | NewPos = InitPos + normalize(v: NewPos - InitPos) * m_pClient->m_aTuning[g_Config.m_ClDummy].m_HookLength; |
204 | DoBreak = true; |
205 | } |
206 | |
207 | int Hit = Collision()->IntersectLineTeleHook(Pos0: OldPos, Pos1: NewPos, pOutCollision: &FinishPos, pOutBeforeCollision: 0x0); |
208 | |
209 | if(!DoBreak && Hit) |
210 | { |
211 | if(Hit != TILE_NOHOOK) |
212 | { |
213 | HookCollColor = color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorHookableColl)); |
214 | } |
215 | } |
216 | |
217 | if(ClientId >= 0 && m_pClient->IntersectCharacter(HookPos: OldPos, NewPos: FinishPos, NewPos2&: FinishPos, ownId: ClientId) != -1) |
218 | { |
219 | HookCollColor = color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorTeeColl)); |
220 | break; |
221 | } |
222 | |
223 | if(Hit) |
224 | break; |
225 | |
226 | NewPos.x = round_to_int(f: NewPos.x); |
227 | NewPos.y = round_to_int(f: NewPos.y); |
228 | |
229 | if(OldPos == NewPos) |
230 | break; |
231 | |
232 | ExDirection.x = round_to_int(f: ExDirection.x * 256.0f) / 256.0f; |
233 | ExDirection.y = round_to_int(f: ExDirection.y * 256.0f) / 256.0f; |
234 | } while(!DoBreak); |
235 | |
236 | if(AlwaysRenderHookColl && RenderHookCollPlayer) |
237 | { |
238 | // invert the hook coll colors when using cl_show_hook_coll_always and +showhookcoll is pressed |
239 | HookCollColor = color_invert(col: HookCollColor); |
240 | } |
241 | Graphics()->SetColor(HookCollColor.WithAlpha(alpha: Alpha)); |
242 | if(g_Config.m_ClHookCollSize > 0) |
243 | { |
244 | float LineWidth = 0.5f + (float)(g_Config.m_ClHookCollSize - 1) * 0.25f; |
245 | vec2 PerpToAngle = normalize(v: vec2(ExDirection.y, -ExDirection.x)) * GameClient()->m_Camera.m_Zoom; |
246 | vec2 Pos0 = FinishPos + PerpToAngle * -LineWidth; |
247 | vec2 Pos1 = FinishPos + PerpToAngle * LineWidth; |
248 | vec2 Pos2 = InitPos + PerpToAngle * -LineWidth; |
249 | vec2 Pos3 = InitPos + PerpToAngle * LineWidth; |
250 | IGraphics::CFreeformItem FreeformItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y, Pos2.x, Pos2.y, Pos3.x, Pos3.y); |
251 | Graphics()->QuadsDrawFreeform(pArray: &FreeformItem, Num: 1); |
252 | Graphics()->QuadsEnd(); |
253 | } |
254 | else |
255 | { |
256 | IGraphics::CLineItem LineItem(InitPos.x, InitPos.y, FinishPos.x, FinishPos.y); |
257 | Graphics()->LinesDraw(pArray: &LineItem, Num: 1); |
258 | Graphics()->LinesEnd(); |
259 | } |
260 | } |
261 | } |
262 | } |
263 | void CPlayers::RenderHook( |
264 | const CNetObj_Character *pPrevChar, |
265 | const CNetObj_Character *pPlayerChar, |
266 | const CTeeRenderInfo *pRenderInfo, |
267 | int ClientId, |
268 | float Intra) |
269 | { |
270 | CNetObj_Character Prev; |
271 | CNetObj_Character Player; |
272 | Prev = *pPrevChar; |
273 | Player = *pPlayerChar; |
274 | |
275 | CTeeRenderInfo RenderInfo = *pRenderInfo; |
276 | |
277 | // don't render hooks to not active character cores |
278 | if(pPlayerChar->m_HookedPlayer != -1 && !m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Active) |
279 | return; |
280 | |
281 | float IntraTick = Intra; |
282 | if(ClientId >= 0) |
283 | IntraTick = (m_pClient->m_aClients[ClientId].m_IsPredicted) ? Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy) : Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
284 | |
285 | bool OtherTeam = m_pClient->IsOtherTeam(ClientId); |
286 | float Alpha = (OtherTeam || ClientId < 0) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f; |
287 | if(ClientId == -2) // ghost |
288 | Alpha = g_Config.m_ClRaceGhostAlpha / 100.0f; |
289 | |
290 | RenderInfo.m_Size = 64.0f; |
291 | |
292 | vec2 Position; |
293 | if(in_range(a: ClientId, upper: MAX_CLIENTS - 1)) |
294 | Position = m_pClient->m_aClients[ClientId].m_RenderPos; |
295 | else |
296 | Position = mix(a: vec2(Prev.m_X, Prev.m_Y), b: vec2(Player.m_X, Player.m_Y), amount: IntraTick); |
297 | |
298 | // draw hook |
299 | if(Prev.m_HookState > 0 && Player.m_HookState > 0) |
300 | { |
301 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
302 | if(ClientId < 0) |
303 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.5f); |
304 | |
305 | vec2 Pos = Position; |
306 | vec2 HookPos; |
307 | |
308 | if(in_range(a: pPlayerChar->m_HookedPlayer, upper: MAX_CLIENTS - 1)) |
309 | HookPos = m_pClient->m_aClients[pPlayerChar->m_HookedPlayer].m_RenderPos; |
310 | else |
311 | HookPos = mix(a: vec2(Prev.m_HookX, Prev.m_HookY), b: vec2(Player.m_HookX, Player.m_HookY), amount: IntraTick); |
312 | |
313 | float d = distance(a: Pos, b: HookPos); |
314 | vec2 Dir = normalize(v: Pos - HookPos); |
315 | |
316 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteHookHead); |
317 | Graphics()->QuadsSetRotation(Angle: angle(a: Dir) + pi); |
318 | // render head |
319 | int QuadOffset = NUM_WEAPONS * 2 + 2; |
320 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha); |
321 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: HookPos.x, Y: HookPos.y); |
322 | |
323 | // render chain |
324 | ++QuadOffset; |
325 | static IGraphics::SRenderSpriteInfo s_aHookChainRenderInfo[1024]; |
326 | int HookChainCount = 0; |
327 | for(float f = 24; f < d && HookChainCount < 1024; f += 24, ++HookChainCount) |
328 | { |
329 | vec2 p = HookPos + Dir * f; |
330 | s_aHookChainRenderInfo[HookChainCount].m_Pos[0] = p.x; |
331 | s_aHookChainRenderInfo[HookChainCount].m_Pos[1] = p.y; |
332 | s_aHookChainRenderInfo[HookChainCount].m_Scale = 1; |
333 | s_aHookChainRenderInfo[HookChainCount].m_Rotation = angle(a: Dir) + pi; |
334 | } |
335 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteHookChain); |
336 | Graphics()->RenderQuadContainerAsSpriteMultiple(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, DrawCount: HookChainCount, pRenderInfo: s_aHookChainRenderInfo); |
337 | |
338 | Graphics()->QuadsSetRotation(Angle: 0); |
339 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
340 | |
341 | RenderHand(pInfo: &RenderInfo, CenterPos: Position, Dir: normalize(v: HookPos - Pos), AngleOffset: -pi / 2, PostRotOffset: vec2(20, 0), Alpha); |
342 | } |
343 | } |
344 | |
345 | void CPlayers::RenderPlayer( |
346 | const CNetObj_Character *pPrevChar, |
347 | const CNetObj_Character *pPlayerChar, |
348 | const CTeeRenderInfo *pRenderInfo, |
349 | int ClientId, |
350 | float Intra) |
351 | { |
352 | CNetObj_Character Prev; |
353 | CNetObj_Character Player; |
354 | Prev = *pPrevChar; |
355 | Player = *pPlayerChar; |
356 | |
357 | CTeeRenderInfo RenderInfo = *pRenderInfo; |
358 | |
359 | bool Local = m_pClient->m_Snap.m_LocalClientId == ClientId; |
360 | bool OtherTeam = m_pClient->IsOtherTeam(ClientId); |
361 | float Alpha = (OtherTeam || ClientId < 0) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f; |
362 | if(ClientId == -2) // ghost |
363 | Alpha = g_Config.m_ClRaceGhostAlpha / 100.0f; |
364 | |
365 | // set size |
366 | RenderInfo.m_Size = 64.0f; |
367 | |
368 | float IntraTick = Intra; |
369 | if(ClientId >= 0) |
370 | IntraTick = m_pClient->m_aClients[ClientId].m_IsPredicted ? Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy) : Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
371 | |
372 | static float s_LastGameTickTime = Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
373 | static float s_LastPredIntraTick = Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy); |
374 | if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) |
375 | { |
376 | s_LastGameTickTime = Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
377 | s_LastPredIntraTick = Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy); |
378 | } |
379 | |
380 | bool PredictLocalWeapons = false; |
381 | float AttackTime = (Client()->PrevGameTick(Conn: g_Config.m_ClDummy) - Player.m_AttackTick) / (float)Client()->GameTickSpeed() + Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
382 | float LastAttackTime = (Client()->PrevGameTick(Conn: g_Config.m_ClDummy) - Player.m_AttackTick) / (float)Client()->GameTickSpeed() + s_LastGameTickTime; |
383 | if(ClientId >= 0 && m_pClient->m_aClients[ClientId].m_IsPredictedLocal && m_pClient->AntiPingGunfire()) |
384 | { |
385 | PredictLocalWeapons = true; |
386 | AttackTime = (Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy) + (Client()->PredGameTick(Conn: g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)Client()->GameTickSpeed(); |
387 | LastAttackTime = (s_LastPredIntraTick + (Client()->PredGameTick(Conn: g_Config.m_ClDummy) - 1 - Player.m_AttackTick)) / (float)Client()->GameTickSpeed(); |
388 | } |
389 | float AttackTicksPassed = AttackTime * (float)Client()->GameTickSpeed(); |
390 | |
391 | float Angle; |
392 | if(Local && (!m_pClient->m_Snap.m_SpecInfo.m_Active || m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW) && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
393 | { |
394 | // just use the direct input if it's the local player we are rendering |
395 | Angle = angle(a: m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] * m_pClient->m_Camera.m_Zoom); |
396 | } |
397 | else |
398 | { |
399 | Angle = GetPlayerTargetAngle(pPrevChar: &Prev, pPlayerChar: &Player, ClientId, Intra: IntraTick); |
400 | } |
401 | |
402 | vec2 Direction = direction(angle: Angle); |
403 | vec2 Position; |
404 | if(in_range(a: ClientId, upper: MAX_CLIENTS - 1)) |
405 | Position = m_pClient->m_aClients[ClientId].m_RenderPos; |
406 | else |
407 | Position = mix(a: vec2(Prev.m_X, Prev.m_Y), b: vec2(Player.m_X, Player.m_Y), amount: IntraTick); |
408 | vec2 Vel = mix(a: vec2(Prev.m_VelX / 256.0f, Prev.m_VelY / 256.0f), b: vec2(Player.m_VelX / 256.0f, Player.m_VelY / 256.0f), amount: IntraTick); |
409 | |
410 | m_pClient->m_Flow.Add(Pos: Position, Vel: Vel * 100.0f, Size: 10.0f); |
411 | |
412 | RenderInfo.m_GotAirJump = Player.m_Jumped & 2 ? false : true; |
413 | |
414 | RenderInfo.m_FeetFlipped = false; |
415 | |
416 | bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1; |
417 | bool InAir = !Collision()->CheckPoint(x: Player.m_X, y: Player.m_Y + 16); |
418 | bool Running = Player.m_VelX >= 5000 || Player.m_VelX <= -5000; |
419 | bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0); |
420 | bool Inactive = ClientId >= 0 && (m_pClient->m_aClients[ClientId].m_Afk || m_pClient->m_aClients[ClientId].m_Paused); |
421 | |
422 | // evaluate animation |
423 | float WalkTime = std::fmod(x: Position.x, y: 100.0f) / 100.0f; |
424 | float RunTime = std::fmod(x: Position.x, y: 200.0f) / 200.0f; |
425 | |
426 | // Don't do a moon walk outside the left border |
427 | if(WalkTime < 0) |
428 | WalkTime += 1; |
429 | if(RunTime < 0) |
430 | RunTime += 1; |
431 | |
432 | CAnimState State; |
433 | State.Set(pAnim: &g_pData->m_aAnimations[ANIM_BASE], Time: 0); |
434 | |
435 | if(InAir) |
436 | State.Add(pAnim: &g_pData->m_aAnimations[ANIM_INAIR], Time: 0, Amount: 1.0f); // TODO: some sort of time here |
437 | else if(Stationary) |
438 | { |
439 | if(Inactive) |
440 | { |
441 | State.Add(pAnim: Direction.x < 0 ? &g_pData->m_aAnimations[ANIM_SIT_LEFT] : &g_pData->m_aAnimations[ANIM_SIT_RIGHT], Time: 0, Amount: 1.0f); // TODO: some sort of time here |
442 | RenderInfo.m_FeetFlipped = true; |
443 | } |
444 | else |
445 | State.Add(pAnim: &g_pData->m_aAnimations[ANIM_IDLE], Time: 0, Amount: 1.0f); // TODO: some sort of time here |
446 | } |
447 | else if(!WantOtherDir) |
448 | { |
449 | if(Running) |
450 | State.Add(pAnim: Player.m_VelX < 0 ? &g_pData->m_aAnimations[ANIM_RUN_LEFT] : &g_pData->m_aAnimations[ANIM_RUN_RIGHT], Time: RunTime, Amount: 1.0f); |
451 | else |
452 | State.Add(pAnim: &g_pData->m_aAnimations[ANIM_WALK], Time: WalkTime, Amount: 1.0f); |
453 | } |
454 | |
455 | if(Player.m_Weapon == WEAPON_HAMMER) |
456 | State.Add(pAnim: &g_pData->m_aAnimations[ANIM_HAMMER_SWING], Time: clamp(val: LastAttackTime * 5.0f, lo: 0.0f, hi: 1.0f), Amount: 1.0f); |
457 | if(Player.m_Weapon == WEAPON_NINJA) |
458 | State.Add(pAnim: &g_pData->m_aAnimations[ANIM_NINJA_SWING], Time: clamp(val: LastAttackTime * 2.0f, lo: 0.0f, hi: 1.0f), Amount: 1.0f); |
459 | |
460 | // do skidding |
461 | if(!InAir && WantOtherDir && length(a: Vel * 50) > 500.0f) |
462 | { |
463 | static int64_t SkidSoundTime = 0; |
464 | if(time() - SkidSoundTime > time_freq() / 10) |
465 | { |
466 | if(g_Config.m_SndGame) |
467 | m_pClient->m_Sounds.PlayAt(Channel: CSounds::CHN_WORLD, SetId: SOUND_PLAYER_SKID, Vol: 0.25f, Pos: Position); |
468 | SkidSoundTime = time(); |
469 | } |
470 | |
471 | m_pClient->m_Effects.SkidTrail( |
472 | Pos: Position + vec2(-Player.m_Direction * 6, 12), |
473 | Vel: vec2(-Player.m_Direction * 100 * length(a: Vel), -50), |
474 | Alpha); |
475 | } |
476 | |
477 | // draw gun |
478 | { |
479 | if(!(RenderInfo.m_TeeRenderFlags & TEE_NO_WEAPON)) |
480 | { |
481 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
482 | Graphics()->QuadsSetRotation(Angle: State.GetAttach()->m_Angle * pi * 2 + Angle); |
483 | |
484 | if(ClientId < 0) |
485 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.5f); |
486 | |
487 | // normal weapons |
488 | int CurrentWeapon = clamp(val: Player.m_Weapon, lo: 0, hi: NUM_WEAPONS - 1); |
489 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpriteWeapons[CurrentWeapon]); |
490 | int QuadOffset = CurrentWeapon * 2 + (Direction.x < 0 ? 1 : 0); |
491 | |
492 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha); |
493 | |
494 | vec2 Dir = Direction; |
495 | float Recoil = 0.0f; |
496 | vec2 WeaponPosition; |
497 | bool IsSit = Inactive && !InAir && Stationary; |
498 | |
499 | if(Player.m_Weapon == WEAPON_HAMMER) |
500 | { |
501 | // static position for hammer |
502 | WeaponPosition = Position + vec2(State.GetAttach()->m_X, State.GetAttach()->m_Y); |
503 | WeaponPosition.y += g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsety; |
504 | if(Direction.x < 0) |
505 | WeaponPosition.x -= g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsetx; |
506 | if(IsSit) |
507 | WeaponPosition.y += 3.0f; |
508 | |
509 | // if active and attack is under way, bash stuffs |
510 | if(!Inactive || LastAttackTime < m_pClient->m_aTuning[g_Config.m_ClDummy].GetWeaponFireDelay(Weapon: Player.m_Weapon)) |
511 | { |
512 | if(Direction.x < 0) |
513 | Graphics()->QuadsSetRotation(Angle: -pi / 2 - State.GetAttach()->m_Angle * pi * 2); |
514 | else |
515 | Graphics()->QuadsSetRotation(Angle: -pi / 2 + State.GetAttach()->m_Angle * pi * 2); |
516 | } |
517 | else |
518 | Graphics()->QuadsSetRotation(Angle: Direction.x < 0 ? 100.0f : 500.0f); |
519 | |
520 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: WeaponPosition.x, Y: WeaponPosition.y); |
521 | } |
522 | else if(Player.m_Weapon == WEAPON_NINJA) |
523 | { |
524 | WeaponPosition = Position; |
525 | WeaponPosition.y += g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsety; |
526 | if(IsSit) |
527 | WeaponPosition.y += 3.0f; |
528 | |
529 | if(Direction.x < 0) |
530 | { |
531 | Graphics()->QuadsSetRotation(Angle: -pi / 2 - State.GetAttach()->m_Angle * pi * 2); |
532 | WeaponPosition.x -= g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsetx; |
533 | m_pClient->m_Effects.PowerupShine(Pos: WeaponPosition + vec2(32, 0), Size: vec2(32, 12), Alpha); |
534 | } |
535 | else |
536 | { |
537 | Graphics()->QuadsSetRotation(Angle: -pi / 2 + State.GetAttach()->m_Angle * pi * 2); |
538 | m_pClient->m_Effects.PowerupShine(Pos: WeaponPosition - vec2(32, 0), Size: vec2(32, 12), Alpha); |
539 | } |
540 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: WeaponPosition.x, Y: WeaponPosition.y); |
541 | |
542 | // HADOKEN |
543 | if(AttackTime <= 1 / 6.f && g_pData->m_Weapons.m_aId[CurrentWeapon].m_NumSpriteMuzzles) |
544 | { |
545 | int IteX = rand() % g_pData->m_Weapons.m_aId[CurrentWeapon].m_NumSpriteMuzzles; |
546 | static int s_LastIteX = IteX; |
547 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK) |
548 | { |
549 | const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); |
550 | if(pInfo->m_Paused) |
551 | IteX = s_LastIteX; |
552 | else |
553 | s_LastIteX = IteX; |
554 | } |
555 | else |
556 | { |
557 | if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED) |
558 | IteX = s_LastIteX; |
559 | else |
560 | s_LastIteX = IteX; |
561 | } |
562 | if(g_pData->m_Weapons.m_aId[CurrentWeapon].m_aSpriteMuzzles[IteX]) |
563 | { |
564 | if(PredictLocalWeapons || ClientId < 0) |
565 | Dir = vec2(pPlayerChar->m_X, pPlayerChar->m_Y) - vec2(pPrevChar->m_X, pPrevChar->m_Y); |
566 | else |
567 | Dir = vec2(m_pClient->m_Snap.m_aCharacters[ClientId].m_Cur.m_X, m_pClient->m_Snap.m_aCharacters[ClientId].m_Cur.m_Y) - vec2(m_pClient->m_Snap.m_aCharacters[ClientId].m_Prev.m_X, m_pClient->m_Snap.m_aCharacters[ClientId].m_Prev.m_Y); |
568 | float HadOkenAngle = 0; |
569 | if(absolute(a: Dir.x) > 0.0001f || absolute(a: Dir.y) > 0.0001f) |
570 | { |
571 | Dir = normalize(v: Dir); |
572 | HadOkenAngle = angle(a: Dir); |
573 | } |
574 | else |
575 | { |
576 | Dir = vec2(1, 0); |
577 | } |
578 | Graphics()->QuadsSetRotation(Angle: HadOkenAngle); |
579 | QuadOffset = IteX * 2; |
580 | vec2 DirY(-Dir.y, Dir.x); |
581 | WeaponPosition = Position; |
582 | float OffsetX = g_pData->m_Weapons.m_aId[CurrentWeapon].m_Muzzleoffsetx; |
583 | WeaponPosition -= Dir * OffsetX; |
584 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aaSpriteWeaponsMuzzles[CurrentWeapon][IteX]); |
585 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[CurrentWeapon], QuadOffset, X: WeaponPosition.x, Y: WeaponPosition.y); |
586 | } |
587 | } |
588 | } |
589 | else |
590 | { |
591 | // TODO: should be an animation |
592 | Recoil = 0; |
593 | float a = AttackTicksPassed / 5.0f; |
594 | if(a < 1) |
595 | Recoil = std::sin(x: a * pi); |
596 | WeaponPosition = Position + Dir * g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsetx - Dir * Recoil * 10.0f; |
597 | WeaponPosition.y += g_pData->m_Weapons.m_aId[CurrentWeapon].m_Offsety; |
598 | if(IsSit) |
599 | WeaponPosition.y += 3.0f; |
600 | if(Player.m_Weapon == WEAPON_GUN && g_Config.m_ClOldGunPosition) |
601 | WeaponPosition.y -= 8; |
602 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: WeaponPosition.x, Y: WeaponPosition.y); |
603 | } |
604 | |
605 | if(Player.m_Weapon == WEAPON_GUN || Player.m_Weapon == WEAPON_SHOTGUN) |
606 | { |
607 | // check if we're firing stuff |
608 | if(g_pData->m_Weapons.m_aId[CurrentWeapon].m_NumSpriteMuzzles) // prev.attackticks) |
609 | { |
610 | float AlphaMuzzle = 0.0f; |
611 | if(AttackTicksPassed < g_pData->m_Weapons.m_aId[CurrentWeapon].m_Muzzleduration + 3) |
612 | { |
613 | float t = AttackTicksPassed / g_pData->m_Weapons.m_aId[CurrentWeapon].m_Muzzleduration; |
614 | AlphaMuzzle = mix(a: 2.0f, b: 0.0f, amount: minimum(a: 1.0f, b: maximum(a: 0.0f, b: t))); |
615 | } |
616 | |
617 | int IteX = rand() % g_pData->m_Weapons.m_aId[CurrentWeapon].m_NumSpriteMuzzles; |
618 | static int s_LastIteX = IteX; |
619 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK) |
620 | { |
621 | const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); |
622 | if(pInfo->m_Paused) |
623 | IteX = s_LastIteX; |
624 | else |
625 | s_LastIteX = IteX; |
626 | } |
627 | else |
628 | { |
629 | if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED) |
630 | IteX = s_LastIteX; |
631 | else |
632 | s_LastIteX = IteX; |
633 | } |
634 | if(AlphaMuzzle > 0.0f && g_pData->m_Weapons.m_aId[CurrentWeapon].m_aSpriteMuzzles[IteX]) |
635 | { |
636 | float OffsetY = -g_pData->m_Weapons.m_aId[CurrentWeapon].m_Muzzleoffsety; |
637 | QuadOffset = IteX * 2 + (Direction.x < 0 ? 1 : 0); |
638 | if(Direction.x < 0) |
639 | OffsetY = -OffsetY; |
640 | |
641 | vec2 DirY(-Dir.y, Dir.x); |
642 | vec2 MuzzlePos = WeaponPosition + Dir * g_pData->m_Weapons.m_aId[CurrentWeapon].m_Muzzleoffsetx + DirY * OffsetY; |
643 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aaSpriteWeaponsMuzzles[CurrentWeapon][IteX]); |
644 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[CurrentWeapon], QuadOffset, X: MuzzlePos.x, Y: MuzzlePos.y); |
645 | } |
646 | } |
647 | } |
648 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
649 | Graphics()->QuadsSetRotation(Angle: 0); |
650 | |
651 | switch(Player.m_Weapon) |
652 | { |
653 | case WEAPON_GUN: RenderHand(pInfo: &RenderInfo, CenterPos: WeaponPosition, Dir: Direction, AngleOffset: -3 * pi / 4, PostRotOffset: vec2(-15, 4), Alpha); break; |
654 | case WEAPON_SHOTGUN: RenderHand(pInfo: &RenderInfo, CenterPos: WeaponPosition, Dir: Direction, AngleOffset: -pi / 2, PostRotOffset: vec2(-5, 4), Alpha); break; |
655 | case WEAPON_GRENADE: RenderHand(pInfo: &RenderInfo, CenterPos: WeaponPosition, Dir: Direction, AngleOffset: -pi / 2, PostRotOffset: vec2(-4, 7), Alpha); break; |
656 | } |
657 | } |
658 | } |
659 | |
660 | // render the "shadow" tee |
661 | if(Local && ((g_Config.m_Debug && g_Config.m_ClUnpredictedShadow >= 0) || g_Config.m_ClUnpredictedShadow == 1)) |
662 | { |
663 | vec2 ShadowPosition = Position; |
664 | if(ClientId >= 0) |
665 | ShadowPosition = mix( |
666 | a: vec2(m_pClient->m_Snap.m_aCharacters[ClientId].m_Prev.m_X, m_pClient->m_Snap.m_aCharacters[ClientId].m_Prev.m_Y), |
667 | b: vec2(m_pClient->m_Snap.m_aCharacters[ClientId].m_Cur.m_X, m_pClient->m_Snap.m_aCharacters[ClientId].m_Cur.m_Y), |
668 | amount: Client()->IntraGameTick(Conn: g_Config.m_ClDummy)); |
669 | |
670 | CTeeRenderInfo Shadow = RenderInfo; |
671 | RenderTools()->RenderTee(pAnim: &State, pInfo: &Shadow, Emote: Player.m_Emote, Dir: Direction, Pos: ShadowPosition, Alpha: 0.5f); // render ghost |
672 | } |
673 | |
674 | RenderTools()->RenderTee(pAnim: &State, pInfo: &RenderInfo, Emote: Player.m_Emote, Dir: Direction, Pos: Position, Alpha); |
675 | |
676 | float TeeAnimScale, TeeBaseSize; |
677 | CRenderTools::GetRenderTeeAnimScaleAndBaseSize(pInfo: &RenderInfo, AnimScale&: TeeAnimScale, BaseSize&: TeeBaseSize); |
678 | vec2 BodyPos = Position + vec2(State.GetBody()->m_X, State.GetBody()->m_Y) * TeeAnimScale; |
679 | if(RenderInfo.m_TeeRenderFlags & TEE_EFFECT_FROZEN) |
680 | { |
681 | GameClient()->m_Effects.FreezingFlakes(Pos: BodyPos, Size: vec2(32, 32), Alpha); |
682 | } |
683 | |
684 | if(ClientId < 0) |
685 | return; |
686 | |
687 | int QuadOffsetToEmoticon = NUM_WEAPONS * 2 + 2 + 2; |
688 | if((Player.m_PlayerFlags & PLAYERFLAG_CHATTING) && !m_pClient->m_aClients[ClientId].m_Afk) |
689 | { |
690 | int CurEmoticon = (SPRITE_DOTDOT - SPRITE_OOP); |
691 | Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[CurEmoticon]); |
692 | int QuadOffset = QuadOffsetToEmoticon + CurEmoticon; |
693 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha); |
694 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: Position.x + 24.f, Y: Position.y - 40.f); |
695 | |
696 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
697 | Graphics()->QuadsSetRotation(Angle: 0); |
698 | } |
699 | |
700 | if(g_Config.m_ClAfkEmote && m_pClient->m_aClients[ClientId].m_Afk && !(Client()->DummyConnected() && ClientId == m_pClient->m_aLocalIds[!g_Config.m_ClDummy])) |
701 | { |
702 | int CurEmoticon = (SPRITE_ZZZ - SPRITE_OOP); |
703 | Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[CurEmoticon]); |
704 | int QuadOffset = QuadOffsetToEmoticon + CurEmoticon; |
705 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Alpha); |
706 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: Position.x + 24.f, Y: Position.y - 40.f); |
707 | |
708 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
709 | Graphics()->QuadsSetRotation(Angle: 0); |
710 | } |
711 | |
712 | if(g_Config.m_ClShowEmotes && !m_pClient->m_aClients[ClientId].m_EmoticonIgnore && m_pClient->m_aClients[ClientId].m_EmoticonStartTick != -1) |
713 | { |
714 | float SinceStart = (Client()->GameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_aClients[ClientId].m_EmoticonStartTick) + (Client()->IntraGameTickSincePrev(Conn: g_Config.m_ClDummy) - m_pClient->m_aClients[ClientId].m_EmoticonStartFraction); |
715 | float FromEnd = (2 * Client()->GameTickSpeed()) - SinceStart; |
716 | |
717 | if(0 <= SinceStart && FromEnd > 0) |
718 | { |
719 | float a = 1; |
720 | |
721 | if(FromEnd < Client()->GameTickSpeed() / 5) |
722 | a = FromEnd / (Client()->GameTickSpeed() / 5.0f); |
723 | |
724 | float h = 1; |
725 | if(SinceStart < Client()->GameTickSpeed() / 10) |
726 | h = SinceStart / (Client()->GameTickSpeed() / 10.0f); |
727 | |
728 | float Wiggle = 0; |
729 | if(SinceStart < Client()->GameTickSpeed() / 5) |
730 | Wiggle = SinceStart / (Client()->GameTickSpeed() / 5.0f); |
731 | |
732 | float WiggleAngle = std::sin(x: 5 * Wiggle); |
733 | |
734 | Graphics()->QuadsSetRotation(Angle: pi / 6 * WiggleAngle); |
735 | |
736 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: a * Alpha); |
737 | // client_datas::emoticon is an offset from the first emoticon |
738 | int QuadOffset = QuadOffsetToEmoticon + m_pClient->m_aClients[ClientId].m_Emoticon; |
739 | Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[m_pClient->m_aClients[ClientId].m_Emoticon]); |
740 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_WeaponEmoteQuadContainerIndex, QuadOffset, X: Position.x, Y: Position.y - 23.f - 32.f * h, ScaleX: 1.f, ScaleY: (64.f * h) / 64.f); |
741 | |
742 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
743 | Graphics()->QuadsSetRotation(Angle: 0); |
744 | } |
745 | } |
746 | } |
747 | |
748 | inline bool CPlayers::IsPlayerInfoAvailable(int ClientId) const |
749 | { |
750 | const void *pPrevInfo = Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: NETOBJTYPE_PLAYERINFO, Id: ClientId); |
751 | const void *pInfo = Client()->SnapFindItem(SnapId: IClient::SNAP_CURRENT, Type: NETOBJTYPE_PLAYERINFO, Id: ClientId); |
752 | return pPrevInfo && pInfo; |
753 | } |
754 | |
755 | void CPlayers::OnRender() |
756 | { |
757 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
758 | return; |
759 | |
760 | CTeeRenderInfo aRenderInfo[MAX_CLIENTS]; |
761 | |
762 | // update RenderInfo for ninja |
763 | bool IsTeamplay = false; |
764 | if(m_pClient->m_Snap.m_pGameInfoObj) |
765 | IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) != 0; |
766 | for(int i = 0; i < MAX_CLIENTS; ++i) |
767 | { |
768 | aRenderInfo[i] = m_pClient->m_aClients[i].m_RenderInfo; |
769 | aRenderInfo[i].m_TeeRenderFlags = 0; |
770 | if(m_pClient->m_aClients[i].m_FreezeEnd != 0) |
771 | aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_FROZEN | TEE_NO_WEAPON; |
772 | if(m_pClient->m_aClients[i].m_LiveFrozen) |
773 | aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_FROZEN; |
774 | |
775 | const CGameClient::CSnapState::CCharacterInfo &CharacterInfo = m_pClient->m_Snap.m_aCharacters[i]; |
776 | const bool Frozen = CharacterInfo.m_HasExtendedData && CharacterInfo.m_ExtendedData.m_FreezeEnd != 0; |
777 | if((CharacterInfo.m_Cur.m_Weapon == WEAPON_NINJA || (Frozen && !m_pClient->m_GameInfo.m_NoSkinChangeForFrozen)) && g_Config.m_ClShowNinja) |
778 | { |
779 | // change the skin for the player to the ninja |
780 | const auto *pSkin = m_pClient->m_Skins.FindOrNullptr(pName: "x_ninja" ); |
781 | if(pSkin != nullptr) |
782 | { |
783 | aRenderInfo[i].m_OriginalRenderSkin = pSkin->m_OriginalSkin; |
784 | aRenderInfo[i].m_ColorableRenderSkin = pSkin->m_ColorableSkin; |
785 | aRenderInfo[i].m_BloodColor = pSkin->m_BloodColor; |
786 | aRenderInfo[i].m_SkinMetrics = pSkin->m_Metrics; |
787 | aRenderInfo[i].m_CustomColoredSkin = IsTeamplay; |
788 | if(!IsTeamplay) |
789 | { |
790 | aRenderInfo[i].m_ColorBody = ColorRGBA(1, 1, 1); |
791 | aRenderInfo[i].m_ColorFeet = ColorRGBA(1, 1, 1); |
792 | } |
793 | } |
794 | } |
795 | } |
796 | const CSkin *pSkin = m_pClient->m_Skins.Find(pName: "x_spec" ); |
797 | CTeeRenderInfo RenderInfoSpec; |
798 | RenderInfoSpec.m_OriginalRenderSkin = pSkin->m_OriginalSkin; |
799 | RenderInfoSpec.m_ColorableRenderSkin = pSkin->m_ColorableSkin; |
800 | RenderInfoSpec.m_BloodColor = pSkin->m_BloodColor; |
801 | RenderInfoSpec.m_SkinMetrics = pSkin->m_Metrics; |
802 | RenderInfoSpec.m_CustomColoredSkin = false; |
803 | RenderInfoSpec.m_Size = 64.0f; |
804 | const int LocalClientId = m_pClient->m_Snap.m_LocalClientId; |
805 | |
806 | // get screen edges to avoid rendering offscreen |
807 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
808 | Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
809 | // expand the edges to prevent popping in/out onscreen |
810 | // |
811 | // it is assumed that the tee, all its weapons, and emotes fit into a 200x200 box centered on the tee |
812 | // this may need to be changed or calculated differently in the future |
813 | float BorderBuffer = 100; |
814 | ScreenX0 -= BorderBuffer; |
815 | ScreenX1 += BorderBuffer; |
816 | ScreenY0 -= BorderBuffer; |
817 | ScreenY1 += BorderBuffer; |
818 | |
819 | // render everyone else's hook, then our own |
820 | for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++) |
821 | { |
822 | if(ClientId == LocalClientId || !m_pClient->m_Snap.m_aCharacters[ClientId].m_Active || !IsPlayerInfoAvailable(ClientId)) |
823 | { |
824 | continue; |
825 | } |
826 | RenderHook(pPrevChar: &m_pClient->m_aClients[ClientId].m_RenderPrev, pPlayerChar: &m_pClient->m_aClients[ClientId].m_RenderCur, pRenderInfo: &aRenderInfo[ClientId], ClientId); |
827 | } |
828 | if(LocalClientId != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientId].m_Active && IsPlayerInfoAvailable(ClientId: LocalClientId)) |
829 | { |
830 | const CGameClient::CClientData *pLocalClientData = &m_pClient->m_aClients[LocalClientId]; |
831 | RenderHook(pPrevChar: &pLocalClientData->m_RenderPrev, pPlayerChar: &pLocalClientData->m_RenderCur, pRenderInfo: &aRenderInfo[LocalClientId], ClientId: LocalClientId); |
832 | } |
833 | |
834 | // render spectating players |
835 | for(auto &Client : m_pClient->m_aClients) |
836 | { |
837 | if(!Client.m_SpecCharPresent) |
838 | { |
839 | continue; |
840 | } |
841 | RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &RenderInfoSpec, Emote: EMOTE_BLINK, Dir: vec2(1, 0), Pos: Client.m_SpecChar); |
842 | } |
843 | |
844 | // render everyone else's tee, then either our own or the tee we are spectating. |
845 | const int RenderLastId = (m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW && m_pClient->m_Snap.m_SpecInfo.m_Active) ? m_pClient->m_Snap.m_SpecInfo.m_SpectatorId : LocalClientId; |
846 | |
847 | for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++) |
848 | { |
849 | if(ClientId == RenderLastId || !m_pClient->m_Snap.m_aCharacters[ClientId].m_Active || !IsPlayerInfoAvailable(ClientId)) |
850 | { |
851 | continue; |
852 | } |
853 | |
854 | RenderHookCollLine(pPrevChar: &m_pClient->m_aClients[ClientId].m_RenderPrev, pPlayerChar: &m_pClient->m_aClients[ClientId].m_RenderCur, ClientId); |
855 | |
856 | // don't render offscreen |
857 | vec2 *pRenderPos = &m_pClient->m_aClients[ClientId].m_RenderPos; |
858 | if(pRenderPos->x < ScreenX0 || pRenderPos->x > ScreenX1 || pRenderPos->y < ScreenY0 || pRenderPos->y > ScreenY1) |
859 | { |
860 | continue; |
861 | } |
862 | RenderPlayer(pPrevChar: &m_pClient->m_aClients[ClientId].m_RenderPrev, pPlayerChar: &m_pClient->m_aClients[ClientId].m_RenderCur, pRenderInfo: &aRenderInfo[ClientId], ClientId); |
863 | } |
864 | if(RenderLastId != -1 && m_pClient->m_Snap.m_aCharacters[RenderLastId].m_Active && IsPlayerInfoAvailable(ClientId: RenderLastId)) |
865 | { |
866 | const CGameClient::CClientData *pClientData = &m_pClient->m_aClients[RenderLastId]; |
867 | RenderHookCollLine(pPrevChar: &pClientData->m_RenderPrev, pPlayerChar: &pClientData->m_RenderCur, ClientId: RenderLastId); |
868 | RenderPlayer(pPrevChar: &pClientData->m_RenderPrev, pPlayerChar: &pClientData->m_RenderCur, pRenderInfo: &aRenderInfo[RenderLastId], ClientId: RenderLastId); |
869 | } |
870 | } |
871 | |
872 | void CPlayers::OnInit() |
873 | { |
874 | m_WeaponEmoteQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
875 | |
876 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
877 | |
878 | for(int i = 0; i < NUM_WEAPONS; ++i) |
879 | { |
880 | float ScaleX, ScaleY; |
881 | RenderTools()->GetSpriteScale(pSprite: g_pData->m_Weapons.m_aId[i].m_pSpriteBody, ScaleX, ScaleY); |
882 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
883 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, Width: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, Height: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY); |
884 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 1, BottomRightU: 1, BottomRightV: 0); |
885 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, Width: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, Height: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY); |
886 | } |
887 | float ScaleX, ScaleY; |
888 | |
889 | // at the end the hand |
890 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
891 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, Size: 20.f); |
892 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
893 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, Size: 20.f); |
894 | |
895 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
896 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, X: -12.f, Y: -8.f, Width: 24.f, Height: 16.f); |
897 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
898 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, X: -12.f, Y: -8.f, Width: 24.f, Height: 16.f); |
899 | |
900 | for(int i = 0; i < NUM_EMOTICONS; ++i) |
901 | { |
902 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
903 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_WeaponEmoteQuadContainerIndex, Size: 64.f); |
904 | } |
905 | Graphics()->QuadContainerUpload(ContainerIndex: m_WeaponEmoteQuadContainerIndex); |
906 | |
907 | for(int i = 0; i < NUM_WEAPONS; ++i) |
908 | { |
909 | m_aWeaponSpriteMuzzleQuadContainerIndex[i] = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
910 | for(int n = 0; n < g_pData->m_Weapons.m_aId[i].m_NumSpriteMuzzles; ++n) |
911 | { |
912 | if(g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n]) |
913 | { |
914 | if(i == WEAPON_GUN || i == WEAPON_SHOTGUN) |
915 | { |
916 | // TODO: hardcoded for now to get the same particle size as before |
917 | RenderTools()->GetSpriteScaleImpl(Width: 96, Height: 64, ScaleX, ScaleY); |
918 | } |
919 | else |
920 | RenderTools()->GetSpriteScale(pSprite: g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n], ScaleX, ScaleY); |
921 | } |
922 | |
923 | float SWidth = (g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX) * (4.0f / 3.0f); |
924 | float SHeight = g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY; |
925 | |
926 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
927 | if(WEAPON_NINJA == i) |
928 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[i], Width: 160.f * ScaleX, Height: 160.f * ScaleY); |
929 | else |
930 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[i], Width: SWidth, Height: SHeight); |
931 | |
932 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 1, BottomRightU: 1, BottomRightV: 0); |
933 | if(WEAPON_NINJA == i) |
934 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[i], Width: 160.f * ScaleX, Height: 160.f * ScaleY); |
935 | else |
936 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[i], Width: SWidth, Height: SHeight); |
937 | } |
938 | Graphics()->QuadContainerUpload(ContainerIndex: m_aWeaponSpriteMuzzleQuadContainerIndex[i]); |
939 | } |
940 | |
941 | Graphics()->QuadsSetSubset(TopLeftU: 0.f, TopLeftV: 0.f, BottomRightU: 1.f, BottomRightV: 1.f); |
942 | Graphics()->QuadsSetRotation(Angle: 0.f); |
943 | } |
944 | |