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 | |
18 | CEffects::CEffects() |
19 | { |
20 | m_Add5hz = false; |
21 | m_Add50hz = false; |
22 | m_Add100hz = false; |
23 | } |
24 | |
25 | void 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 | |
51 | void CEffects::DamageIndicator(vec2 Pos, vec2 Dir, float Alpha) |
52 | { |
53 | m_pClient->m_DamageInd.Create(Pos, Dir, Alpha); |
54 | } |
55 | |
56 | void CEffects::ResetDamageIndicator() |
57 | { |
58 | m_pClient->m_DamageInd.Reset(); |
59 | } |
60 | |
61 | void 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 | |
84 | void 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 | |
111 | void 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 | |
131 | void 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 | |
151 | void 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 | |
169 | void 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 | |
193 | void 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 | |
234 | void 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 | |
288 | void 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 | |
355 | void 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 | |
373 | void 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 | |