1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | #include <engine/demo.h> |
4 | #include <engine/graphics.h> |
5 | #include <engine/shared/config.h> |
6 | |
7 | #include <game/generated/client_data.h> |
8 | #include <game/generated/protocol.h> |
9 | |
10 | #include <game/mapitems.h> |
11 | |
12 | #include <game/client/gameclient.h> |
13 | #include <game/client/laser_data.h> |
14 | #include <game/client/pickup_data.h> |
15 | #include <game/client/projectile_data.h> |
16 | #include <game/client/render.h> |
17 | |
18 | #include <game/client/prediction/entities/laser.h> |
19 | #include <game/client/prediction/entities/pickup.h> |
20 | #include <game/client/prediction/entities/projectile.h> |
21 | |
22 | #include <game/client/components/effects.h> |
23 | |
24 | #include "items.h" |
25 | |
26 | void CItems::RenderProjectile(const CProjectileData *pCurrent, int ItemId) |
27 | { |
28 | int CurWeapon = clamp(val: pCurrent->m_Type, lo: 0, hi: NUM_WEAPONS - 1); |
29 | |
30 | // get positions |
31 | float Curvature = 0; |
32 | float Speed = 0; |
33 | const CTuningParams *pTuning = GameClient()->GetTuning(i: pCurrent->m_TuneZone); |
34 | if(CurWeapon == WEAPON_GRENADE) |
35 | { |
36 | Curvature = pTuning->m_GrenadeCurvature; |
37 | Speed = pTuning->m_GrenadeSpeed; |
38 | } |
39 | else if(CurWeapon == WEAPON_SHOTGUN) |
40 | { |
41 | Curvature = pTuning->m_ShotgunCurvature; |
42 | Speed = pTuning->m_ShotgunSpeed; |
43 | } |
44 | else if(CurWeapon == WEAPON_GUN) |
45 | { |
46 | Curvature = pTuning->m_GunCurvature; |
47 | Speed = pTuning->m_GunSpeed; |
48 | } |
49 | |
50 | bool LocalPlayerInGame = false; |
51 | |
52 | if(m_pClient->m_Snap.m_pLocalInfo) |
53 | LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientId].m_Team != TEAM_SPECTATORS; |
54 | |
55 | static float s_LastGameTickTime = Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
56 | if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) |
57 | s_LastGameTickTime = Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
58 | |
59 | bool IsOtherTeam = (pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(ClientId: pCurrent->m_Owner)); |
60 | |
61 | float Ct; |
62 | if(m_pClient->Predict() && m_pClient->AntiPingGrenade() && LocalPlayerInGame && !IsOtherTeam) |
63 | Ct = ((float)(Client()->PredGameTick(Conn: g_Config.m_ClDummy) - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed(); |
64 | else |
65 | Ct = (Client()->PrevGameTick(Conn: g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() + s_LastGameTickTime; |
66 | if(Ct < 0) |
67 | { |
68 | if(Ct > -s_LastGameTickTime / 2) |
69 | { |
70 | // Fixup the timing which might be screwed during demo playback because |
71 | // s_LastGameTickTime depends on the system timer, while the other part |
72 | // (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() |
73 | // is virtually constant (for projectiles fired on the current game tick): |
74 | // (x - (x+2)) / 50 = -0.04 |
75 | // |
76 | // We have a strict comparison for the passed time being more than the time between ticks |
77 | // if(CurtickStart > m_Info.m_CurrentTime) in CDemoPlayer::Update() |
78 | // so on the practice the typical value of s_LastGameTickTime varies from 0.02386 to 0.03999 |
79 | // which leads to Ct from -0.00001 to -0.01614. |
80 | // Round up those to 0.0 to fix missing rendering of the projectile. |
81 | Ct = 0; |
82 | } |
83 | else |
84 | { |
85 | return; // projectile haven't been shot yet |
86 | } |
87 | } |
88 | |
89 | vec2 Pos = CalcPos(Pos: pCurrent->m_StartPos, Velocity: pCurrent->m_StartVel, Curvature, Speed, Time: Ct); |
90 | vec2 PrevPos = CalcPos(Pos: pCurrent->m_StartPos, Velocity: pCurrent->m_StartVel, Curvature, Speed, Time: Ct - 0.001f); |
91 | |
92 | float Alpha = 1.f; |
93 | if(IsOtherTeam) |
94 | { |
95 | Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; |
96 | } |
97 | |
98 | vec2 Vel = Pos - PrevPos; |
99 | |
100 | // add particle for this projectile |
101 | // don't check for validity of the projectile for the current weapon here, so particle effects are rendered for mod compatibility |
102 | if(CurWeapon == WEAPON_GRENADE) |
103 | { |
104 | m_pClient->m_Effects.SmokeTrail(Pos, Vel: Vel * -1, Alpha); |
105 | static float s_Time = 0.0f; |
106 | static float s_LastLocalTime = LocalTime(); |
107 | |
108 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK) |
109 | { |
110 | const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); |
111 | if(!pInfo->m_Paused) |
112 | s_Time += (LocalTime() - s_LastLocalTime) * pInfo->m_Speed; |
113 | } |
114 | else |
115 | { |
116 | if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) |
117 | s_Time += LocalTime() - s_LastLocalTime; |
118 | } |
119 | |
120 | Graphics()->QuadsSetRotation(Angle: s_Time * pi * 2 * 2 + ItemId); |
121 | s_LastLocalTime = LocalTime(); |
122 | } |
123 | else |
124 | { |
125 | m_pClient->m_Effects.BulletTrail(Pos, Alpha); |
126 | |
127 | if(length(a: Vel) > 0.00001f) |
128 | Graphics()->QuadsSetRotation(Angle: angle(a: Vel)); |
129 | else |
130 | Graphics()->QuadsSetRotation(Angle: 0); |
131 | } |
132 | |
133 | if(GameClient()->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon].IsValid()) |
134 | { |
135 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon]); |
136 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: Alpha); |
137 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_ItemsQuadContainerIndex, QuadOffset: m_aProjectileOffset[CurWeapon], X: Pos.x, Y: Pos.y); |
138 | } |
139 | } |
140 | |
141 | void CItems::RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent, bool IsPredicted) |
142 | { |
143 | int CurWeapon = clamp(val: pCurrent->m_Subtype, lo: 0, hi: NUM_WEAPONS - 1); |
144 | int QuadOffset = 2; |
145 | float Angle = 0.0f; |
146 | float IntraTick = IsPredicted ? Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy) : Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
147 | vec2 Pos = mix(a: vec2(pPrev->m_X, pPrev->m_Y), b: vec2(pCurrent->m_X, pCurrent->m_Y), amount: IntraTick); |
148 | if(pCurrent->m_Type == POWERUP_HEALTH) |
149 | { |
150 | QuadOffset = m_PickupHealthOffset; |
151 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpritePickupHealth); |
152 | } |
153 | else if(pCurrent->m_Type == POWERUP_ARMOR) |
154 | { |
155 | QuadOffset = m_PickupArmorOffset; |
156 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpritePickupArmor); |
157 | } |
158 | else if(pCurrent->m_Type == POWERUP_WEAPON) |
159 | { |
160 | QuadOffset = m_aPickupWeaponOffset[CurWeapon]; |
161 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpritePickupWeapons[CurWeapon]); |
162 | } |
163 | else if(pCurrent->m_Type == POWERUP_NINJA) |
164 | { |
165 | QuadOffset = m_PickupNinjaOffset; |
166 | m_pClient->m_Effects.PowerupShine(Pos, Size: vec2(96, 18), Alpha: 1.0f); |
167 | Pos.x -= 10.0f; |
168 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpritePickupNinja); |
169 | } |
170 | else if(pCurrent->m_Type >= POWERUP_ARMOR_SHOTGUN && pCurrent->m_Type <= POWERUP_ARMOR_LASER) |
171 | { |
172 | QuadOffset = m_aPickupWeaponArmorOffset[pCurrent->m_Type - POWERUP_ARMOR_SHOTGUN]; |
173 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_aSpritePickupWeaponArmor[pCurrent->m_Type - POWERUP_ARMOR_SHOTGUN]); |
174 | } |
175 | Graphics()->QuadsSetRotation(Angle: 0); |
176 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
177 | Graphics()->QuadsSetRotation(Angle); |
178 | |
179 | static float s_Time = 0.0f; |
180 | static float s_LastLocalTime = LocalTime(); |
181 | float Offset = Pos.y / 32.0f + Pos.x / 32.0f; |
182 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK) |
183 | { |
184 | const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); |
185 | if(!pInfo->m_Paused) |
186 | s_Time += (LocalTime() - s_LastLocalTime) * pInfo->m_Speed; |
187 | } |
188 | else |
189 | { |
190 | if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) |
191 | s_Time += LocalTime() - s_LastLocalTime; |
192 | } |
193 | Pos += direction(angle: s_Time * 2.0f + Offset) * 2.5f; |
194 | s_LastLocalTime = LocalTime(); |
195 | |
196 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_ItemsQuadContainerIndex, QuadOffset, X: Pos.x, Y: Pos.y); |
197 | } |
198 | |
199 | void CItems::RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, const CNetObj_GameData *pPrevGameData, const CNetObj_GameData *pCurGameData) |
200 | { |
201 | float Angle = 0.0f; |
202 | float Size = 42.0f; |
203 | |
204 | if(pCurrent->m_Team == TEAM_RED) |
205 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed); |
206 | else |
207 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue); |
208 | Graphics()->QuadsSetRotation(Angle: 0); |
209 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
210 | int QuadOffset; |
211 | if(pCurrent->m_Team == TEAM_RED) |
212 | { |
213 | QuadOffset = m_RedFlagOffset; |
214 | } |
215 | else |
216 | { |
217 | QuadOffset = m_BlueFlagOffset; |
218 | } |
219 | |
220 | Graphics()->QuadsSetRotation(Angle); |
221 | |
222 | vec2 Pos = mix(a: vec2(pPrev->m_X, pPrev->m_Y), b: vec2(pCurrent->m_X, pCurrent->m_Y), amount: Client()->IntraGameTick(Conn: g_Config.m_ClDummy)); |
223 | |
224 | if(pCurGameData) |
225 | { |
226 | int FlagCarrier = (pCurrent->m_Team == TEAM_RED) ? pCurGameData->m_FlagCarrierRed : pCurGameData->m_FlagCarrierBlue; |
227 | // use the flagcarriers position if available |
228 | if(FlagCarrier >= 0 && m_pClient->m_Snap.m_aCharacters[FlagCarrier].m_Active) |
229 | Pos = m_pClient->m_aClients[FlagCarrier].m_RenderPos; |
230 | |
231 | // make sure that the flag isn't interpolated between capture and return |
232 | if(pPrevGameData && |
233 | ((pCurrent->m_Team == TEAM_RED && pPrevGameData->m_FlagCarrierRed != pCurGameData->m_FlagCarrierRed) || |
234 | (pCurrent->m_Team == TEAM_BLUE && pPrevGameData->m_FlagCarrierBlue != pCurGameData->m_FlagCarrierBlue))) |
235 | Pos = vec2(pCurrent->m_X, pCurrent->m_Y); |
236 | } |
237 | |
238 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_ItemsQuadContainerIndex, QuadOffset, X: Pos.x, Y: Pos.y - Size * 0.75f); |
239 | } |
240 | |
241 | void CItems::RenderLaser(const CLaserData *pCurrent, bool IsPredicted) |
242 | { |
243 | int Type = clamp(val: pCurrent->m_Type, lo: -1, hi: NUM_LASERTYPES - 1); |
244 | |
245 | ColorRGBA RGB; |
246 | vec2 Pos = pCurrent->m_To; |
247 | vec2 From = pCurrent->m_From; |
248 | float Len = distance(a: Pos, b: From); |
249 | |
250 | int ColorIn, ColorOut; |
251 | switch(Type) |
252 | { |
253 | case LASERTYPE_RIFLE: |
254 | ColorOut = g_Config.m_ClLaserRifleOutlineColor; |
255 | ColorIn = g_Config.m_ClLaserRifleInnerColor; |
256 | break; |
257 | case LASERTYPE_SHOTGUN: |
258 | ColorOut = g_Config.m_ClLaserShotgunOutlineColor; |
259 | ColorIn = g_Config.m_ClLaserShotgunInnerColor; |
260 | break; |
261 | case LASERTYPE_DRAGGER: |
262 | case LASERTYPE_DOOR: |
263 | ColorOut = g_Config.m_ClLaserDoorOutlineColor; |
264 | ColorIn = g_Config.m_ClLaserDoorInnerColor; |
265 | break; |
266 | case LASERTYPE_FREEZE: |
267 | ColorOut = g_Config.m_ClLaserFreezeOutlineColor; |
268 | ColorIn = g_Config.m_ClLaserFreezeInnerColor; |
269 | break; |
270 | case LASERTYPE_GUN: |
271 | case LASERTYPE_PLASMA: |
272 | if(pCurrent->m_Subtype == LASERGUNTYPE_FREEZE || pCurrent->m_Subtype == LASERGUNTYPE_EXPFREEZE) |
273 | { |
274 | ColorOut = g_Config.m_ClLaserFreezeOutlineColor; |
275 | ColorIn = g_Config.m_ClLaserFreezeInnerColor; |
276 | } |
277 | else |
278 | { |
279 | ColorOut = g_Config.m_ClLaserRifleOutlineColor; |
280 | ColorIn = g_Config.m_ClLaserRifleInnerColor; |
281 | } |
282 | break; |
283 | default: |
284 | ColorOut = g_Config.m_ClLaserRifleOutlineColor; |
285 | ColorIn = g_Config.m_ClLaserRifleInnerColor; |
286 | } |
287 | |
288 | RGB = color_cast<ColorRGBA>(hsl: ColorHSLA(ColorOut)); |
289 | ColorRGBA OuterColor(RGB.r, RGB.g, RGB.b, 1.0f); |
290 | RGB = color_cast<ColorRGBA>(hsl: ColorHSLA(ColorIn)); |
291 | ColorRGBA InnerColor(RGB.r, RGB.g, RGB.b, 1.0f); |
292 | |
293 | int TuneZone = GameClient()->m_GameWorld.m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Index: Collision()->GetMapIndex(Pos: From)) : 0; |
294 | bool IsOtherTeam = (pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(ClientId: pCurrent->m_Owner)); |
295 | |
296 | float Alpha = 1.f; |
297 | if(IsOtherTeam) |
298 | { |
299 | Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; |
300 | } |
301 | |
302 | vec2 Dir; |
303 | if(Len > 0) |
304 | { |
305 | Dir = normalize_pre_length(v: Pos - From, len: Len); |
306 | |
307 | float Ticks; |
308 | if(IsPredicted) |
309 | Ticks = (float)(Client()->PredGameTick(Conn: g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy); |
310 | else |
311 | Ticks = (float)(Client()->GameTick(Conn: g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->IntraGameTick(Conn: g_Config.m_ClDummy); |
312 | float Ms = (Ticks / Client()->GameTickSpeed()) * 1000.0f; |
313 | float a = Ms / m_pClient->GetTuning(i: TuneZone)->m_LaserBounceDelay; |
314 | a = clamp(val: a, lo: 0.0f, hi: 1.0f); |
315 | float Ia = 1 - a; |
316 | |
317 | vec2 Out; |
318 | |
319 | Graphics()->TextureClear(); |
320 | Graphics()->QuadsBegin(); |
321 | |
322 | // do outline |
323 | Graphics()->SetColor(r: OuterColor.r, g: OuterColor.g, b: OuterColor.b, a: Alpha); |
324 | Out = vec2(Dir.y, -Dir.x) * (7.0f * Ia); |
325 | |
326 | IGraphics::CFreeformItem Freeform( |
327 | From.x - Out.x, From.y - Out.y, |
328 | From.x + Out.x, From.y + Out.y, |
329 | Pos.x - Out.x, Pos.y - Out.y, |
330 | Pos.x + Out.x, Pos.y + Out.y); |
331 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
332 | |
333 | // do inner |
334 | Out = vec2(Dir.y, -Dir.x) * (5.0f * Ia); |
335 | Graphics()->SetColor(r: InnerColor.r, g: InnerColor.g, b: InnerColor.b, a: Alpha); // center |
336 | |
337 | Freeform = IGraphics::CFreeformItem( |
338 | From.x - Out.x, From.y - Out.y, |
339 | From.x + Out.x, From.y + Out.y, |
340 | Pos.x - Out.x, Pos.y - Out.y, |
341 | Pos.x + Out.x, Pos.y + Out.y); |
342 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
343 | |
344 | Graphics()->QuadsEnd(); |
345 | } |
346 | |
347 | // render head |
348 | { |
349 | int CurParticle = (Client()->GameTick(Conn: g_Config.m_ClDummy) % 3); |
350 | Graphics()->TextureSet(Texture: GameClient()->m_ParticlesSkin.m_aSpriteParticleSplat[CurParticle]); |
351 | Graphics()->QuadsSetRotation(Angle: Client()->GameTick(Conn: g_Config.m_ClDummy)); |
352 | Graphics()->SetColor(r: OuterColor.r, g: OuterColor.g, b: OuterColor.b, a: Alpha); |
353 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_ItemsQuadContainerIndex, QuadOffset: m_aParticleSplatOffset[CurParticle], X: Pos.x, Y: Pos.y); |
354 | Graphics()->SetColor(r: InnerColor.r, g: InnerColor.g, b: InnerColor.b, a: Alpha); |
355 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_ItemsQuadContainerIndex, QuadOffset: m_aParticleSplatOffset[CurParticle], X: Pos.x, Y: Pos.y, ScaleX: 20.f / 24.f, ScaleY: 20.f / 24.f); |
356 | } |
357 | } |
358 | |
359 | void CItems::OnRender() |
360 | { |
361 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
362 | return; |
363 | |
364 | bool IsSuper = m_pClient->IsLocalCharSuper(); |
365 | int Ticks = Client()->GameTick(Conn: g_Config.m_ClDummy) % Client()->GameTickSpeed(); |
366 | bool BlinkingPickup = (Ticks % 22) < 4; |
367 | bool BlinkingGun = (Ticks % 22) < 4; |
368 | bool BlinkingDragger = (Ticks % 22) < 4; |
369 | bool BlinkingProj = (Ticks % 20) < 2; |
370 | bool BlinkingProjEx = (Ticks % 6) < 2; |
371 | bool BlinkingLight = (Ticks % 6) < 2; |
372 | int SwitcherTeam = m_pClient->SwitchStateTeam(); |
373 | int DraggerStartTick = maximum(a: (Client()->GameTick(Conn: g_Config.m_ClDummy) / 7) * 7, b: Client()->GameTick(Conn: g_Config.m_ClDummy) - 4); |
374 | int GunStartTick = (Client()->GameTick(Conn: g_Config.m_ClDummy) / 7) * 7; |
375 | |
376 | bool UsePredicted = GameClient()->Predict() && GameClient()->AntiPingGunfire(); |
377 | auto &aSwitchers = GameClient()->Switchers(); |
378 | if(UsePredicted) |
379 | { |
380 | for(auto *pProj = (CProjectile *)GameClient()->m_PredictedWorld.FindFirst(Type: CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->NextEntity()) |
381 | { |
382 | if(!IsSuper && pProj->m_Number > 0 && pProj->m_Number < (int)aSwitchers.size() && !aSwitchers[pProj->m_Number].m_aStatus[SwitcherTeam] && (pProj->m_Explosive ? BlinkingProjEx : BlinkingProj)) |
383 | continue; |
384 | |
385 | CProjectileData Data = pProj->GetData(); |
386 | RenderProjectile(pCurrent: &Data, ItemId: pProj->GetId()); |
387 | } |
388 | for(CEntity *pEnt = GameClient()->m_PredictedWorld.FindFirst(Type: CGameWorld::ENTTYPE_LASER); pEnt; pEnt = pEnt->NextEntity()) |
389 | { |
390 | auto *const pLaser = dynamic_cast<CLaser *>(pEnt); |
391 | if(!pLaser || pLaser->GetOwner() < 0 || !GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal) |
392 | continue; |
393 | CLaserData Data = pLaser->GetData(); |
394 | RenderLaser(pCurrent: &Data, IsPredicted: true); |
395 | } |
396 | for(auto *pPickup = (CPickup *)GameClient()->m_PredictedWorld.FindFirst(Type: CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup *)pPickup->NextEntity()) |
397 | { |
398 | if(!IsSuper && pPickup->m_Layer == LAYER_SWITCH && pPickup->m_Number > 0 && pPickup->m_Number < (int)aSwitchers.size() && !aSwitchers[pPickup->m_Number].m_aStatus[SwitcherTeam] && BlinkingPickup) |
399 | continue; |
400 | |
401 | if(pPickup->InDDNetTile()) |
402 | { |
403 | if(auto *pPrev = (CPickup *)GameClient()->m_PrevPredictedWorld.GetEntity(Id: pPickup->GetId(), EntityType: CGameWorld::ENTTYPE_PICKUP)) |
404 | { |
405 | CNetObj_Pickup Data, Prev; |
406 | pPickup->FillInfo(pPickup: &Data); |
407 | pPrev->FillInfo(pPickup: &Prev); |
408 | RenderPickup(pPrev: &Prev, pCurrent: &Data, IsPredicted: true); |
409 | } |
410 | } |
411 | } |
412 | } |
413 | |
414 | for(const CSnapEntities &Ent : m_pClient->SnapEntities()) |
415 | { |
416 | const IClient::CSnapItem Item = Ent.m_Item; |
417 | const void *pData = Ent.m_pData; |
418 | const CNetObj_EntityEx *pEntEx = Ent.m_pDataEx; |
419 | |
420 | if(Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDRACEPROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE) |
421 | { |
422 | CProjectileData Data = ExtractProjectileInfo(NetObjType: Item.m_Type, pData, pGameWorld: &GameClient()->m_GameWorld, pEntEx); |
423 | bool Inactive = !IsSuper && Data.m_SwitchNumber > 0 && Data.m_SwitchNumber < (int)aSwitchers.size() && !aSwitchers[Data.m_SwitchNumber].m_aStatus[SwitcherTeam]; |
424 | if(Inactive && (Data.m_Explosive ? BlinkingProjEx : BlinkingProj)) |
425 | continue; |
426 | if(UsePredicted) |
427 | |
428 | { |
429 | if(auto *pProj = (CProjectile *)GameClient()->m_GameWorld.FindMatch(ObjId: Item.m_Id, ObjType: Item.m_Type, pObjData: pData)) |
430 | { |
431 | bool IsOtherTeam = m_pClient->IsOtherTeam(ClientId: pProj->GetOwner()); |
432 | if(pProj->m_LastRenderTick <= 0 && (pProj->m_Type != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets |
433 | && (pProj->m_Type == WEAPON_SHOTGUN || absolute(a: length(a: pProj->m_Direction) - 1.f) < 0.02f) // workaround to skip grenades on ball mod |
434 | && (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal || IsOtherTeam) // skip locally predicted projectiles |
435 | && !Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: Item.m_Type, Id: Item.m_Id)) |
436 | { |
437 | ReconstructSmokeTrail(pCurrent: &Data, DestroyTick: pProj->m_DestroyTick); |
438 | } |
439 | pProj->m_LastRenderTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
440 | if(!IsOtherTeam) |
441 | continue; |
442 | } |
443 | } |
444 | RenderProjectile(pCurrent: &Data, ItemId: Item.m_Id); |
445 | } |
446 | else if(Item.m_Type == NETOBJTYPE_PICKUP || Item.m_Type == NETOBJTYPE_DDNETPICKUP) |
447 | { |
448 | CPickupData Data = ExtractPickupInfo(NetObjType: Item.m_Type, pData, pEntEx); |
449 | bool Inactive = !IsSuper && Data.m_SwitchNumber > 0 && Data.m_SwitchNumber < (int)aSwitchers.size() && !aSwitchers[Data.m_SwitchNumber].m_aStatus[SwitcherTeam]; |
450 | |
451 | if(Inactive && BlinkingPickup) |
452 | continue; |
453 | if(UsePredicted) |
454 | { |
455 | auto *pPickup = (CPickup *)GameClient()->m_GameWorld.FindMatch(ObjId: Item.m_Id, ObjType: Item.m_Type, pObjData: pData); |
456 | if(pPickup && pPickup->InDDNetTile()) |
457 | continue; |
458 | } |
459 | const void *pPrev = Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: Item.m_Type, Id: Item.m_Id); |
460 | if(pPrev) |
461 | RenderPickup(pPrev: (const CNetObj_Pickup *)pPrev, pCurrent: (const CNetObj_Pickup *)pData); |
462 | } |
463 | else if(Item.m_Type == NETOBJTYPE_LASER || Item.m_Type == NETOBJTYPE_DDNETLASER) |
464 | { |
465 | if(UsePredicted) |
466 | { |
467 | auto *pLaser = dynamic_cast<CLaser *>(GameClient()->m_GameWorld.FindMatch(ObjId: Item.m_Id, ObjType: Item.m_Type, pObjData: pData)); |
468 | if(pLaser && pLaser->GetOwner() >= 0 && GameClient()->m_aClients[pLaser->GetOwner()].m_IsPredictedLocal) |
469 | continue; |
470 | } |
471 | |
472 | CLaserData Data = ExtractLaserInfo(NetObjType: Item.m_Type, pData, pGameWorld: &GameClient()->m_GameWorld, pEntEx); |
473 | bool Inactive = !IsSuper && Data.m_SwitchNumber > 0 && Data.m_SwitchNumber < (int)aSwitchers.size() && !aSwitchers[Data.m_SwitchNumber].m_aStatus[SwitcherTeam]; |
474 | |
475 | bool IsEntBlink = false; |
476 | int EntStartTick = -1; |
477 | if(Data.m_Type == LASERTYPE_FREEZE) |
478 | { |
479 | IsEntBlink = BlinkingLight; |
480 | EntStartTick = DraggerStartTick; |
481 | } |
482 | else if(Data.m_Type == LASERTYPE_GUN) |
483 | { |
484 | IsEntBlink = BlinkingGun; |
485 | EntStartTick = GunStartTick; |
486 | } |
487 | else if(Data.m_Type == LASERTYPE_DRAGGER) |
488 | { |
489 | IsEntBlink = BlinkingDragger; |
490 | EntStartTick = DraggerStartTick; |
491 | } |
492 | else if(Data.m_Type == LASERTYPE_DOOR) |
493 | { |
494 | if(Data.m_Predict && (Inactive || IsSuper)) |
495 | { |
496 | Data.m_From.x = Data.m_To.x; |
497 | Data.m_From.y = Data.m_To.y; |
498 | } |
499 | EntStartTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
500 | } |
501 | else |
502 | { |
503 | IsEntBlink = BlinkingDragger; |
504 | EntStartTick = Client()->GameTick(Conn: g_Config.m_ClDummy); |
505 | } |
506 | |
507 | if(Data.m_Predict && Inactive && IsEntBlink) |
508 | { |
509 | continue; |
510 | } |
511 | |
512 | if(Data.m_StartTick <= 0 && EntStartTick != -1) |
513 | { |
514 | Data.m_StartTick = EntStartTick; |
515 | } |
516 | |
517 | RenderLaser(pCurrent: &Data); |
518 | } |
519 | } |
520 | |
521 | int Num = Client()->SnapNumItems(SnapId: IClient::SNAP_CURRENT); |
522 | |
523 | // render flag |
524 | for(int i = 0; i < Num; i++) |
525 | { |
526 | IClient::CSnapItem Item; |
527 | const void *pData = Client()->SnapGetItem(SnapId: IClient::SNAP_CURRENT, Index: i, pItem: &Item); |
528 | |
529 | if(Item.m_Type == NETOBJTYPE_FLAG) |
530 | { |
531 | const void *pPrev = Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: Item.m_Type, Id: Item.m_Id); |
532 | if(pPrev) |
533 | { |
534 | const void *pPrevGameData = Client()->SnapFindItem(SnapId: IClient::SNAP_PREV, Type: NETOBJTYPE_GAMEDATA, Id: m_pClient->m_Snap.m_GameDataSnapId); |
535 | RenderFlag(pPrev: static_cast<const CNetObj_Flag *>(pPrev), pCurrent: static_cast<const CNetObj_Flag *>(pData), |
536 | pPrevGameData: static_cast<const CNetObj_GameData *>(pPrevGameData), pCurGameData: m_pClient->m_Snap.m_pGameDataObj); |
537 | } |
538 | } |
539 | } |
540 | |
541 | Graphics()->QuadsSetRotation(Angle: 0); |
542 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
543 | } |
544 | |
545 | void CItems::OnInit() |
546 | { |
547 | Graphics()->QuadsSetRotation(Angle: 0); |
548 | Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f); |
549 | |
550 | m_ItemsQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
551 | |
552 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
553 | m_RedFlagOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, X: -21.f, Y: -42.f, Width: 42.f, Height: 84.f); |
554 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
555 | m_BlueFlagOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, X: -21.f, Y: -42.f, Width: 42.f, Height: 84.f); |
556 | |
557 | float ScaleX, ScaleY; |
558 | RenderTools()->GetSpriteScale(Id: SPRITE_PICKUP_HEALTH, ScaleX, ScaleY); |
559 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
560 | m_PickupHealthOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Width: 64.f * ScaleX, Height: 64.f * ScaleY); |
561 | RenderTools()->GetSpriteScale(Id: SPRITE_PICKUP_ARMOR, ScaleX, ScaleY); |
562 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
563 | m_PickupArmorOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Width: 64.f * ScaleX, Height: 64.f * ScaleY); |
564 | |
565 | for(int i = 0; i < NUM_WEAPONS; ++i) |
566 | { |
567 | RenderTools()->GetSpriteScale(pSprite: g_pData->m_Weapons.m_aId[i].m_pSpriteBody, ScaleX, ScaleY); |
568 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
569 | m_aPickupWeaponOffset[i] = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Width: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, Height: g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY); |
570 | } |
571 | RenderTools()->GetSpriteScale(Id: SPRITE_PICKUP_NINJA, ScaleX, ScaleY); |
572 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
573 | m_PickupNinjaOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Width: 128.f * ScaleX, Height: 128.f * ScaleY); |
574 | |
575 | for(int i = 0; i < 4; i++) |
576 | { |
577 | RenderTools()->GetSpriteScale(Id: SPRITE_PICKUP_ARMOR_SHOTGUN + i, ScaleX, ScaleY); |
578 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
579 | m_aPickupWeaponArmorOffset[i] = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Width: 64.f * ScaleX, Height: 64.f * ScaleY); |
580 | } |
581 | |
582 | for(int &ProjectileOffset : m_aProjectileOffset) |
583 | { |
584 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
585 | ProjectileOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Size: 32.f); |
586 | } |
587 | |
588 | for(int &ParticleSplatOffset : m_aParticleSplatOffset) |
589 | { |
590 | Graphics()->QuadsSetSubset(TopLeftU: 0, TopLeftV: 0, BottomRightU: 1, BottomRightV: 1); |
591 | ParticleSplatOffset = RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_ItemsQuadContainerIndex, Size: 24.f); |
592 | } |
593 | |
594 | Graphics()->QuadContainerUpload(ContainerIndex: m_ItemsQuadContainerIndex); |
595 | } |
596 | |
597 | void CItems::ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyTick) |
598 | { |
599 | bool LocalPlayerInGame = false; |
600 | |
601 | if(m_pClient->m_Snap.m_pLocalInfo) |
602 | LocalPlayerInGame = m_pClient->m_aClients[m_pClient->m_Snap.m_pLocalInfo->m_ClientId].m_Team != TEAM_SPECTATORS; |
603 | if(!m_pClient->AntiPingGunfire() || !LocalPlayerInGame) |
604 | return; |
605 | if(Client()->PredGameTick(Conn: g_Config.m_ClDummy) == pCurrent->m_StartTick) |
606 | return; |
607 | |
608 | // get positions |
609 | float Curvature = 0; |
610 | float Speed = 0; |
611 | const CTuningParams *pTuning = GameClient()->GetTuning(i: pCurrent->m_TuneZone); |
612 | |
613 | if(pCurrent->m_Type == WEAPON_GRENADE) |
614 | { |
615 | Curvature = pTuning->m_GrenadeCurvature; |
616 | Speed = pTuning->m_GrenadeSpeed; |
617 | } |
618 | else if(pCurrent->m_Type == WEAPON_SHOTGUN) |
619 | { |
620 | Curvature = pTuning->m_ShotgunCurvature; |
621 | Speed = pTuning->m_ShotgunSpeed; |
622 | } |
623 | else if(pCurrent->m_Type == WEAPON_GUN) |
624 | { |
625 | Curvature = pTuning->m_GunCurvature; |
626 | Speed = pTuning->m_GunSpeed; |
627 | } |
628 | |
629 | float Pt = ((float)(Client()->PredGameTick(Conn: g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed(); |
630 | if(Pt < 0) |
631 | return; // projectile haven't been shot yet |
632 | |
633 | float Gt = (Client()->PrevGameTick(Conn: g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)Client()->GameTickSpeed() + Client()->GameTickTime(Conn: g_Config.m_ClDummy); |
634 | |
635 | float Alpha = 1.f; |
636 | if(pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(ClientId: pCurrent->m_Owner)) |
637 | { |
638 | Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; |
639 | } |
640 | |
641 | float T = Pt; |
642 | if(DestroyTick >= 0) |
643 | T = minimum(a: Pt, b: ((float)(DestroyTick - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(Conn: g_Config.m_ClDummy)) / (float)Client()->GameTickSpeed()); |
644 | |
645 | float MinTrailSpan = 0.4f * ((pCurrent->m_Type == WEAPON_GRENADE) ? 0.5f : 0.25f); |
646 | float Step = maximum(a: Client()->FrameTimeAvg(), b: (pCurrent->m_Type == WEAPON_GRENADE) ? 0.02f : 0.01f); |
647 | for(int i = 1 + (int)(Gt / Step); i < (int)(T / Step); i++) |
648 | { |
649 | float t = Step * (float)i + 0.4f * Step * random_float(min: -0.5f, max: 0.5f); |
650 | vec2 Pos = CalcPos(Pos: pCurrent->m_StartPos, Velocity: pCurrent->m_StartVel, Curvature, Speed, Time: t); |
651 | vec2 PrevPos = CalcPos(Pos: pCurrent->m_StartPos, Velocity: pCurrent->m_StartVel, Curvature, Speed, Time: t - 0.001f); |
652 | vec2 Vel = Pos - PrevPos; |
653 | float TimePassed = Pt - t; |
654 | if(Pt - MinTrailSpan > 0.01f) |
655 | TimePassed = minimum(a: TimePassed, b: (TimePassed - MinTrailSpan) / (Pt - MinTrailSpan) * (MinTrailSpan * 0.5f) + MinTrailSpan); |
656 | // add particle for this projectile |
657 | if(pCurrent->m_Type == WEAPON_GRENADE) |
658 | m_pClient->m_Effects.SmokeTrail(Pos, Vel: Vel * -1, Alpha, TimePassed); |
659 | else |
660 | m_pClient->m_Effects.BulletTrail(Pos, Alpha, TimePassed); |
661 | } |
662 | } |
663 | |