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