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
6#include <engine/shared/config.h>
7
8#include <game/generated/client_data.h>
9
10#include <game/client/components/damageind.h>
11#include <game/client/components/flow.h>
12#include <game/client/components/particles.h>
13#include <game/client/components/sounds.h>
14#include <game/client/gameclient.h>
15
16#include "effects.h"
17
18CEffects::CEffects()
19{
20 m_Add5hz = false;
21 m_Add50hz = false;
22 m_Add100hz = false;
23}
24
25void CEffects::AirJump(vec2 Pos, float Alpha)
26{
27 CParticle p;
28 p.SetDefault();
29 p.m_Spr = SPRITE_PART_AIRJUMP;
30 p.m_Pos = Pos + vec2(-6.0f, 16.0f);
31 p.m_Vel = vec2(0, -200);
32 p.m_LifeSpan = 0.5f;
33 p.m_StartSize = 48.0f;
34 p.m_EndSize = 0;
35 p.m_Rot = random_angle();
36 p.m_Rotspeed = pi * 2;
37 p.m_Gravity = 500;
38 p.m_Friction = 0.7f;
39 p.m_FlowAffected = 0.0f;
40 p.m_Color.a = Alpha;
41 p.m_StartAlpha = Alpha;
42 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
43
44 p.m_Pos = Pos + vec2(6.0f, 16.0f);
45 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
46
47 if(g_Config.m_SndGame)
48 m_pClient->m_Sounds.PlayAt(Channel: CSounds::CHN_WORLD, SetId: SOUND_PLAYER_AIRJUMP, Vol: 1.0f, Pos);
49}
50
51void CEffects::DamageIndicator(vec2 Pos, vec2 Dir, float Alpha)
52{
53 m_pClient->m_DamageInd.Create(Pos, Dir, Alpha);
54}
55
56void CEffects::ResetDamageIndicator()
57{
58 m_pClient->m_DamageInd.Reset();
59}
60
61void CEffects::PowerupShine(vec2 Pos, vec2 Size, float Alpha)
62{
63 if(!m_Add50hz)
64 return;
65
66 CParticle p;
67 p.SetDefault();
68 p.m_Spr = SPRITE_PART_SLICE;
69 p.m_Pos = Pos + vec2(random_float(min: -0.5f, max: 0.5f), random_float(min: -0.5f, max: 0.5f)) * Size;
70 p.m_Vel = vec2(0, 0);
71 p.m_LifeSpan = 0.5f;
72 p.m_StartSize = 16.0f;
73 p.m_EndSize = 0;
74 p.m_Rot = random_angle();
75 p.m_Rotspeed = pi * 2;
76 p.m_Gravity = 500;
77 p.m_Friction = 0.9f;
78 p.m_FlowAffected = 0.0f;
79 p.m_Color.a = Alpha;
80 p.m_StartAlpha = Alpha;
81 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
82}
83
84void CEffects::FreezingFlakes(vec2 Pos, vec2 Size, float Alpha)
85{
86 if(!m_Add5hz)
87 return;
88
89 CParticle p;
90 p.SetDefault();
91 p.m_Spr = SPRITE_PART_SNOWFLAKE;
92 p.m_Pos = Pos + vec2(random_float(min: -0.5f, max: 0.5f), random_float(min: -0.5f, max: 0.5f)) * Size;
93 p.m_Vel = vec2(0, 0);
94 p.m_LifeSpan = 1.5f;
95 p.m_StartSize = random_float(min: 0.5f, max: 1.5f) * 16.0f;
96 p.m_EndSize = p.m_StartSize * 0.5f;
97 p.m_UseAlphaFading = true;
98 p.m_StartAlpha = 1.0f;
99 p.m_EndAlpha = 0.0f;
100 p.m_Rot = random_angle();
101 p.m_Rotspeed = pi;
102 p.m_Gravity = random_float(max: 250.0f);
103 p.m_Friction = 0.9f;
104 p.m_FlowAffected = 0.0f;
105 p.m_Collides = false;
106 p.m_Color.a = Alpha;
107 p.m_StartAlpha = Alpha;
108 m_pClient->m_Particles.Add(Group: CParticles::GROUP_EXTRA, pPart: &p);
109}
110
111void CEffects::SmokeTrail(vec2 Pos, vec2 Vel, float Alpha, float TimePassed)
112{
113 if(!m_Add50hz && TimePassed < 0.001f)
114 return;
115
116 CParticle p;
117 p.SetDefault();
118 p.m_Spr = SPRITE_PART_SMOKE;
119 p.m_Pos = Pos;
120 p.m_Vel = Vel + random_direction() * 50.0f;
121 p.m_LifeSpan = random_float(min: 0.5f, max: 1.0f);
122 p.m_StartSize = random_float(min: 12.0f, max: 20.0f);
123 p.m_EndSize = 0;
124 p.m_Friction = 0.7f;
125 p.m_Gravity = random_float(max: -500.0f);
126 p.m_Color.a = Alpha;
127 p.m_StartAlpha = Alpha;
128 m_pClient->m_Particles.Add(Group: CParticles::GROUP_PROJECTILE_TRAIL, pPart: &p, TimePassed);
129}
130
131void CEffects::SkidTrail(vec2 Pos, vec2 Vel, float Alpha)
132{
133 if(!m_Add100hz)
134 return;
135
136 CParticle p;
137 p.SetDefault();
138 p.m_Spr = SPRITE_PART_SMOKE;
139 p.m_Pos = Pos;
140 p.m_Vel = Vel + random_direction() * 50.0f;
141 p.m_LifeSpan = random_float(min: 0.5f, max: 1.0f);
142 p.m_StartSize = random_float(min: 24.0f, max: 36.0f);
143 p.m_EndSize = 0;
144 p.m_Friction = 0.7f;
145 p.m_Gravity = random_float(max: -500.0f);
146 p.m_Color = ColorRGBA(0.75f, 0.75f, 0.75f, Alpha);
147 p.m_StartAlpha = Alpha;
148 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
149}
150
151void CEffects::BulletTrail(vec2 Pos, float Alpha, float TimePassed)
152{
153 if(!m_Add100hz && TimePassed < 0.001f)
154 return;
155
156 CParticle p;
157 p.SetDefault();
158 p.m_Spr = SPRITE_PART_BALL;
159 p.m_Pos = Pos;
160 p.m_LifeSpan = random_float(min: 0.25f, max: 0.5f);
161 p.m_StartSize = 8.0f;
162 p.m_EndSize = 0;
163 p.m_Friction = 0.7f;
164 p.m_Color.a *= Alpha;
165 p.m_StartAlpha = Alpha;
166 m_pClient->m_Particles.Add(Group: CParticles::GROUP_PROJECTILE_TRAIL, pPart: &p, TimePassed);
167}
168
169void CEffects::PlayerSpawn(vec2 Pos, float Alpha)
170{
171 for(int i = 0; i < 32; i++)
172 {
173 CParticle p;
174 p.SetDefault();
175 p.m_Spr = SPRITE_PART_SHELL;
176 p.m_Pos = Pos;
177 p.m_Vel = random_direction() * (std::pow(x: random_float(), y: 3) * 600.0f);
178 p.m_LifeSpan = random_float(min: 0.3f, max: 0.6f);
179 p.m_StartSize = random_float(min: 64.0f, max: 96.0f);
180 p.m_EndSize = 0;
181 p.m_Rot = random_angle();
182 p.m_Rotspeed = random_float();
183 p.m_Gravity = random_float(max: -400.0f);
184 p.m_Friction = 0.7f;
185 p.m_Color = ColorRGBA(0xb5 / 255.0f, 0x50 / 255.0f, 0xcb / 255.0f, Alpha);
186 p.m_StartAlpha = Alpha;
187 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
188 }
189 if(g_Config.m_SndGame)
190 m_pClient->m_Sounds.PlayAt(Channel: CSounds::CHN_WORLD, SetId: SOUND_PLAYER_SPAWN, Vol: 1.0f, Pos);
191}
192
193void CEffects::PlayerDeath(vec2 Pos, int ClientId, float Alpha)
194{
195 ColorRGBA BloodColor(1.0f, 1.0f, 1.0f);
196
197 if(ClientId >= 0)
198 {
199 // Use m_RenderInfo.m_CustomColoredSkin instead of m_UseCustomColor
200 // m_UseCustomColor says if the player's skin has a custom color (value sent from the client side)
201
202 // m_RenderInfo.m_CustomColoredSkin Defines if in the context of the game the color is being customized,
203 // Using this value if the game is teams (red and blue), this value will be true even if the skin is with the normal color.
204 // And will use the team body color to create player death effect instead of tee color
205 if(m_pClient->m_aClients[ClientId].m_RenderInfo.m_CustomColoredSkin)
206 BloodColor = m_pClient->m_aClients[ClientId].m_RenderInfo.m_ColorBody;
207 else
208 {
209 BloodColor = m_pClient->m_aClients[ClientId].m_RenderInfo.m_BloodColor;
210 }
211 }
212
213 for(int i = 0; i < 64; i++)
214 {
215 CParticle p;
216 p.SetDefault();
217 p.m_Spr = SPRITE_PART_SPLAT01 + (rand() % 3);
218 p.m_Pos = Pos;
219 p.m_Vel = random_direction() * (random_float(min: 0.1f, max: 1.1f) * 900.0f);
220 p.m_LifeSpan = random_float(min: 0.3f, max: 0.6f);
221 p.m_StartSize = random_float(min: 24.0f, max: 40.0f);
222 p.m_EndSize = 0;
223 p.m_Rot = random_angle();
224 p.m_Rotspeed = random_float(min: -0.5f, max: 0.5f) * pi;
225 p.m_Gravity = 800.0f;
226 p.m_Friction = 0.8f;
227 ColorRGBA c = BloodColor.v4() * random_float(min: 0.75f, max: 1.0f);
228 p.m_Color = ColorRGBA(c.r, c.g, c.b, 0.75f * Alpha);
229 p.m_StartAlpha = Alpha;
230 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
231 }
232}
233
234void CEffects::FinishConfetti(vec2 Pos, float Alpha)
235{
236 ColorRGBA Red(1.0f, 0.4f, 0.4f);
237 ColorRGBA Green(0.4f, 1.0f, 0.4f);
238 ColorRGBA Blue(0.4f, 0.4f, 1.0f);
239 ColorRGBA Yellow(1.0f, 1.0f, 0.4f);
240 ColorRGBA Cyan(0.4f, 1.0f, 1.0f);
241 ColorRGBA Magenta(1.0f, 0.4f, 1.0f);
242
243 ColorRGBA aConfettiColors[] = {Red, Green, Blue, Yellow, Cyan, Magenta};
244
245 // powerful confettis
246 for(int i = 0; i < 32; i++)
247 {
248 CParticle p;
249 p.SetDefault();
250 p.m_Spr = SPRITE_PART_SPLAT01 + (rand() % 3);
251 p.m_Pos = Pos;
252 p.m_Vel = direction(angle: -0.5f * pi + random_float(min: -0.2f, max: 0.2f)) * random_float(min: 0.01f, max: 1.0f) * 2000.0f;
253 p.m_LifeSpan = random_float(min: 1.0f, max: 1.2f);
254 p.m_StartSize = random_float(min: 12.0f, max: 24.0f);
255 p.m_EndSize = 0;
256 p.m_Rot = random_angle();
257 p.m_Rotspeed = random_float(min: -0.5f, max: 0.5f) * pi;
258 p.m_Gravity = -700.0f;
259 p.m_Friction = 0.6f;
260 ColorRGBA c = aConfettiColors[(rand() % std::size(aConfettiColors))];
261 p.m_Color = c.WithMultipliedAlpha(alpha: 0.75f * Alpha);
262 p.m_StartAlpha = Alpha;
263 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
264 }
265
266 // broader confettis
267 for(int i = 0; i < 32; i++)
268 {
269 CParticle p;
270 p.SetDefault();
271 p.m_Spr = SPRITE_PART_SPLAT01 + (rand() % 3);
272 p.m_Pos = Pos;
273 p.m_Vel = direction(angle: -0.5f * pi + random_float(min: -0.8f, max: 0.8f)) * random_float(min: 0.01f, max: 1.0f) * 1500.0f;
274 p.m_LifeSpan = random_float(min: 0.8f, max: 1.0f);
275 p.m_StartSize = random_float(min: 12.0f, max: 24.0f);
276 p.m_EndSize = 0;
277 p.m_Rot = random_angle();
278 p.m_Rotspeed = random_float(min: -0.5f, max: 0.5f) * pi;
279 p.m_Gravity = -700.0f;
280 p.m_Friction = 0.6f;
281 ColorRGBA c = aConfettiColors[(rand() % std::size(aConfettiColors))];
282 p.m_Color = c.WithMultipliedAlpha(alpha: 0.75f * Alpha);
283 p.m_StartAlpha = Alpha;
284 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
285 }
286}
287
288void CEffects::Explosion(vec2 Pos, float Alpha)
289{
290 // add to flow
291 for(int y = -8; y <= 8; y++)
292 for(int x = -8; x <= 8; x++)
293 {
294 if(x == 0 && y == 0)
295 continue;
296
297 float a = 1 - (length(a: vec2(x, y)) / length(a: vec2(8, 8)));
298 m_pClient->m_Flow.Add(Pos: Pos + vec2(x, y) * 16, Vel: normalize(v: vec2(x, y)) * 5000.0f * a, Size: 10.0f);
299 }
300
301 // add the explosion
302 CParticle p;
303 p.SetDefault();
304 p.m_Spr = SPRITE_PART_EXPL01;
305 p.m_Pos = Pos;
306 p.m_LifeSpan = 0.4f;
307 p.m_StartSize = 150.0f;
308 p.m_EndSize = 0;
309 p.m_Rot = random_angle();
310 p.m_Color.a = Alpha;
311 p.m_StartAlpha = Alpha;
312 m_pClient->m_Particles.Add(Group: CParticles::GROUP_EXPLOSIONS, pPart: &p);
313
314 // Nudge position slightly to edge of closest tile so the
315 // smoke doesn't get stuck inside the tile.
316 if(Collision()->CheckPoint(Pos))
317 {
318 const vec2 DistanceToTopLeft = Pos - vec2(round_truncate(f: Pos.x / 32), round_truncate(f: Pos.y / 32)) * 32;
319
320 vec2 CheckOffset;
321 CheckOffset.x = (DistanceToTopLeft.x > 16 ? 32 : -1);
322 CheckOffset.y = (DistanceToTopLeft.y > 16 ? 32 : -1);
323 CheckOffset -= DistanceToTopLeft;
324
325 for(vec2 Mask : {vec2(1.0f, 0.0f), vec2(0.0f, 1.0f), vec2(1.0f, 1.0f)})
326 {
327 const vec2 NewPos = Pos + CheckOffset * Mask;
328 if(!Collision()->CheckPoint(Pos: NewPos))
329 {
330 Pos = NewPos;
331 break;
332 }
333 }
334 }
335
336 // add the smoke
337 for(int i = 0; i < 24; i++)
338 {
339 p.SetDefault();
340 p.m_Spr = SPRITE_PART_SMOKE;
341 p.m_Pos = Pos;
342 p.m_Vel = random_direction() * (random_float(min: 1.0f, max: 1.2f) * 1000.0f);
343 p.m_LifeSpan = random_float(min: 0.5f, max: 0.9f);
344 p.m_StartSize = random_float(min: 32.0f, max: 40.0f);
345 p.m_EndSize = 0;
346 p.m_Gravity = random_float(max: -800.0f);
347 p.m_Friction = 0.4f;
348 p.m_Color = mix(a: vec4(0.75f, 0.75f, 0.75f, 1.0f), b: vec4(0.5f, 0.5f, 0.5f, 1.0f), amount: random_float());
349 p.m_Color.a *= Alpha;
350 p.m_StartAlpha = p.m_Color.a;
351 m_pClient->m_Particles.Add(Group: CParticles::GROUP_GENERAL, pPart: &p);
352 }
353}
354
355void CEffects::HammerHit(vec2 Pos, float Alpha)
356{
357 // add the explosion
358 CParticle p;
359 p.SetDefault();
360 p.m_Spr = SPRITE_PART_HIT01;
361 p.m_Pos = Pos;
362 p.m_LifeSpan = 0.3f;
363 p.m_StartSize = 120.0f;
364 p.m_EndSize = 0;
365 p.m_Rot = random_angle();
366 p.m_Color.a = Alpha;
367 p.m_StartAlpha = Alpha;
368 m_pClient->m_Particles.Add(Group: CParticles::GROUP_EXPLOSIONS, pPart: &p);
369 if(g_Config.m_SndGame)
370 m_pClient->m_Sounds.PlayAt(Channel: CSounds::CHN_WORLD, SetId: SOUND_HAMMER_HIT, Vol: 1.0f, Pos);
371}
372
373void CEffects::OnRender()
374{
375 static int64_t s_LastUpdate100hz = 0;
376 static int64_t s_LastUpdate50hz = 0;
377 static int64_t s_LastUpdate5hz = 0;
378
379 float Speed = 1.0f;
380 if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
381 Speed = DemoPlayer()->BaseInfo()->m_Speed;
382
383 if(time() - s_LastUpdate100hz > time_freq() / (100 * Speed))
384 {
385 m_Add100hz = true;
386 s_LastUpdate100hz = time();
387 }
388 else
389 m_Add100hz = false;
390
391 if(time() - s_LastUpdate50hz > time_freq() / (50 * Speed))
392 {
393 m_Add50hz = true;
394 s_LastUpdate50hz = time();
395 }
396 else
397 m_Add50hz = false;
398
399 if(time() - s_LastUpdate5hz > time_freq() / (5 * Speed))
400 {
401 m_Add5hz = true;
402 s_LastUpdate5hz = time();
403 }
404 else
405 m_Add5hz = false;
406
407 if(m_Add50hz)
408 m_pClient->m_Flow.Update();
409}
410