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
28void 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
60inline float AngularMixDirection(float Src, float Dst) { return std::sin(x: Dst - Src) > 0 ? 1 : -1; }
61
62inline 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
71float 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
118void 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}
263void 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
345void 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
748inline 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
755void 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
872void 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