1 /*
2 * This software is licensed under the terms of the MIT License.
3 * See COPYING for further information.
4 * ---
5 * Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
6 * Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
7 */
8
9 #include "taisei.h"
10
11 #include "global.h"
12 #include "plrmodes.h"
13 #include "youmu.h"
14
youmu_homing_target(cmplx org,cmplx fallback)15 static cmplx youmu_homing_target(cmplx org, cmplx fallback) {
16 return plrutil_homing_target(org, fallback);
17 }
18
youmu_homing_draw_common(Projectile * p,float clrfactor,float scale,float alpha)19 static void youmu_homing_draw_common(Projectile *p, float clrfactor, float scale, float alpha) {
20 Color c = p->color;
21 color_mul(&c, RGBA(0.7f + 0.3f * clrfactor, 0.9f + 0.1f * clrfactor, 1, 1));
22
23 if(alpha <= 0) {
24 return;
25 }
26
27 bool special_snowflake_shader_bullshit = p->shader_params.vector[1] != 0;
28
29 if(special_snowflake_shader_bullshit) {
30 // FIXME: maybe move this to logic someh-- nah. Don't even bother with this crap.
31 float old = p->shader_params.vector[1];
32 p->shader_params.vector[1] = alpha;
33 youmu_common_draw_proj(p, &c, scale);
34 p->shader_params.vector[1] = old;
35 } else {
36 color_mul_scalar(&c, alpha);
37 youmu_common_draw_proj(p, &c, scale);
38 }
39 }
40
youmu_homing_draw_proj(Projectile * p,int t)41 static void youmu_homing_draw_proj(Projectile *p, int t) {
42 float a = clamp(1.0f - (float)t / p->args[2], 0, 1);
43 youmu_homing_draw_common(p, a, 1, 0.5f);
44 }
45
youmu_homing_draw_trail(Projectile * p,int t)46 static void youmu_homing_draw_trail(Projectile *p, int t) {
47 float a = clamp(1.0f - (float)t / p->timeout, 0, 1);
48 youmu_homing_draw_common(p, a, 5 * (1 - a), 0.15f * a);
49 }
50
youmu_trap_draw_trail(Projectile * p,int t)51 static void youmu_trap_draw_trail(Projectile *p, int t) {
52 float a = clamp(1.0f - (float)t / p->timeout, 0, 1);
53 youmu_homing_draw_common(p, a, 2 - a, 0.15f * a);
54 }
55
youmu_trap_draw_child_proj(Projectile * p,int t)56 static void youmu_trap_draw_child_proj(Projectile *p, int t) {
57 float to = p->args[2];
58 float a = clamp(1.0 - 3 * ((t - (to - to/3)) / to), 0, 1);
59 a = 1 - pow(1 - a, 2);
60 youmu_homing_draw_common(p, a, 1 + 2 * pow(1 - a, 2), a);
61 }
62
youmu_trap_charge(int t)63 static float youmu_trap_charge(int t) {
64 return pow(clamp(t / 60.0, 0, 1), 1.5);
65 }
66
youmu_homing_trail(Projectile * p,cmplx v,int to)67 static Projectile* youmu_homing_trail(Projectile *p, cmplx v, int to) {
68 return PARTICLE(
69 .sprite_ptr = p->sprite,
70 .pos = p->pos,
71 .color = &p->color,
72 .angle = p->angle,
73 .rule = linear,
74 .timeout = to,
75 .draw_rule = youmu_homing_draw_trail,
76 .args = { v },
77 .flags = PFLAG_NOREFLECT,
78 .shader_ptr = p->shader,
79 .shader_params = &p->shader_params,
80 .layer = LAYER_PARTICLE_LOW,
81 );
82 }
83
youmu_homing(Projectile * p,int t)84 static int youmu_homing(Projectile *p, int t) { // a[0]: velocity, a[1]: aim (r: base, i: gain), a[2]: (r: timeout, i: charge), a[3]: initial target
85 if(t == EVENT_BIRTH) {
86 return ACTION_ACK;
87 }
88
89 if(t == EVENT_DEATH) {
90 PARTICLE(
91 .sprite = "blast",
92 .color = color_lerp(RGBA(0.5, 0.7, 1.0, 0.5), RGBA(1.0, 0.65, 0.8, 0.5), cimag(p->args[2])),
93 .pos = p->pos,
94 .timeout = 20,
95 .draw_rule = ScaleFade,
96 .layer = LAYER_PARTICLE_HIGH,
97 .args = { 0, 0, 0.5 * I },
98 .flags = PFLAG_NOREFLECT,
99 .angle = M_PI*nfrand(),
100 );
101 return ACTION_ACK;
102 }
103
104 if(t > creal(p->args[2])) {
105 return ACTION_DESTROY;
106 }
107
108 p->args[3] = youmu_homing_target(p->pos, p->args[3]);
109
110 double v = cabs(p->args[0]);
111 cmplx aimdir = cexp(I*carg(p->args[3] - p->pos));
112
113 p->args[0] += creal(p->args[1]) * aimdir;
114 // p->args[0] = v * cexp(I*carg(p->args[0])) + cimag(p->args[1]) * aimdir;
115 p->args[0] *= v / cabs(p->args[0]);
116
117 p->args[1] = creal(p->args[1]) + cimag(p->args[1]) * (1 + I);
118
119 p->angle = carg(p->args[0]);
120 p->pos += p->args[0];
121
122 Projectile *trail = youmu_homing_trail(p, 0.5 * p->args[0], 12);
123 trail->args[2] = p->args[2];
124
125 p->shader_params.vector[0] = cimag(p->args[2]);
126 trail->shader_params.vector[0] = cimag(p->args[2]);
127
128 return 1;
129 }
130
youmu_trap_trail(Projectile * p,cmplx v,int t,bool additive)131 static Projectile* youmu_trap_trail(Projectile *p, cmplx v, int t, bool additive) {
132 Projectile *trail = youmu_homing_trail(p, v, t);
133 trail->draw_rule = youmu_trap_draw_trail;
134 // trail->args[3] = global.frames - p->birthtime;
135 trail->shader_params.vector[0] = p->shader_params.vector[0];
136 trail->flags |= PFLAG_REQUIREDPARTICLE;
137
138 if(additive) {
139 trail->color.a = 0;
140 } else {
141 trail->flags |= PFLAG_PLRSPECIALPARTICLE;
142 }
143
144 return trail;
145 }
146
youmu_trap(Projectile * p,int t)147 static int youmu_trap(Projectile *p, int t) {
148 if(t == EVENT_DEATH) {
149 PARTICLE(
150 .proto = pp_blast,
151 .pos = p->pos,
152 .timeout = 15,
153 .draw_rule = Blast,
154 .flags = PFLAG_REQUIREDPARTICLE,
155 .layer = LAYER_PARTICLE_LOW,
156 );
157 return ACTION_ACK;
158 }
159
160 // FIXME: replace this with timeout?
161 double expiretime = creal(p->args[1]);
162
163 if(t > expiretime) {
164 return ACTION_DESTROY;
165 }
166
167 if(t < 0) {
168 return ACTION_ACK;
169 }
170
171 float charge = youmu_trap_charge(t);
172 p->shader_params.vector[0] = charge;
173
174 if(!(global.plr.inputflags & INFLAG_FOCUS)) {
175 PARTICLE(
176 .proto = pp_blast,
177 .pos = p->pos,
178 .timeout = 20,
179 .draw_rule = Blast,
180 .flags = PFLAG_REQUIREDPARTICLE,
181 .layer = LAYER_PARTICLE_LOW,
182 );
183
184 PARTICLE(
185 .proto = pp_blast,
186 .pos = p->pos,
187 .timeout = 23,
188 .draw_rule = Blast,
189 .flags = PFLAG_REQUIREDPARTICLE,
190 .layer = LAYER_PARTICLE_LOW,
191 );
192
193 int cnt = round(creal(p->args[2]));
194 int dmg = cimag(p->args[2]);
195 cmplx aim = p->args[3];
196
197 for(int i = 0; i < cnt; ++i) {
198 int dur = 120; // 55 + 20 * nfrand();
199 float a = (i / (float)cnt) * M_PI * 2;
200 cmplx dir = cexp(I*(a));
201
202 PROJECTILE(
203 .proto = pp_youmu,
204 .pos = p->pos,
205 .color = RGBA(1, 1, 1, 0.85),
206 .rule = youmu_homing,
207 .args = { 5 * (1 + charge) * dir, aim, dur + charge*I, creal(p->pos) - VIEWPORT_H*I },
208 .type = PROJ_PLAYER,
209 .damage = dmg,
210 .draw_rule = youmu_trap_draw_child_proj,
211 .shader = "sprite_youmu_charged_shot",
212 .shader_params = &(ShaderCustomParams){{ 0, 1 }},
213 );
214 }
215
216 // TODO: dedicated sound for this?
217 play_sound("enemydeath");
218 play_sound("hit");
219
220 return ACTION_DESTROY;
221 }
222
223 p->angle = global.frames + t;
224 p->pos += p->args[0] * (0.01 + 0.99 * max(0, (10 - t) / 10.0));
225
226 youmu_trap_trail(p, cexp(I*p->angle), 30 * (1 + charge), true);
227 youmu_trap_trail(p, cexp(I*-p->angle), 30, false);
228 return 1;
229 }
230
youmu_particle_slice_draw(Projectile * p,int t)231 static void youmu_particle_slice_draw(Projectile *p, int t) {
232 double lifetime = p->timeout;
233 double tt = t/lifetime;
234 double f = 0;
235 if(tt > 0.1) {
236 f = min(1,(tt-0.1)/0.2);
237 }
238 if(tt > 0.5) {
239 f = 1+(tt-0.5)/0.5;
240 }
241
242 r_mat_mv_push();
243 r_mat_mv_translate(creal(p->pos), cimag(p->pos),0);
244 r_mat_mv_rotate(p->angle, 0, 0, 1);
245 r_mat_mv_scale(f, 1, 1);
246 ProjDrawCore(p, &p->color);
247 r_mat_mv_pop();
248
249 double slicelen = 500;
250 cmplx slicepos = p->pos-(tt>0.1)*slicelen*I*cexp(I*p->angle)*(5*pow(tt-0.1,1.1)-0.5);
251
252 r_draw_sprite(&(SpriteParams) {
253 .sprite_ptr = aniplayer_get_frame(&global.plr.ani),
254 .pos = { creal(slicepos), cimag(slicepos) },
255 });
256 }
257
youmu_slice_petal(Projectile * p,int t)258 static int youmu_slice_petal(Projectile *p, int t) {
259 int r = accelerated(p, t);
260
261 if(t >= 0) {
262 p->color = *color_mul_scalar(RGBA(0.2, 0.2, 1, 0), min(1, t / 40.0));
263 }
264
265 return r;
266 }
267
youmu_particle_slice_logic(Projectile * p,int t)268 static int youmu_particle_slice_logic(Projectile *p, int t) {
269 if(t < 0) {
270 return ACTION_ACK;
271 }
272
273 double lifetime = p->timeout;
274 double tt = t/lifetime;
275 double a = 0;
276
277 if(tt > 0.) {
278 a = min(1,(tt-0.)/0.2);
279 }
280 if(tt > 0.5) {
281 a = max(0,1-(tt-0.5)/0.5);
282 }
283
284 p->color = *RGBA(a, a, a, 0);
285
286 cmplx phase = cexp(p->angle * I);
287
288 if(t%5 == 0) {
289 tsrand_fill(4);
290 PARTICLE(
291 .sprite = "petal",
292 .pos = p->pos-400*phase,
293 .rule = youmu_slice_petal,
294 .draw_rule = Petal,
295 .args = {
296 phase,
297 phase*cexp(0.1*I),
298 afrand(1) + afrand(2)*I,
299 afrand(3) + 360.0*I*afrand(0)
300 },
301 .layer = LAYER_PARTICLE_HIGH | 0x2,
302 .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
303 );
304 }
305
306 Ellipse e = {
307 .origin = p->pos,
308 .axes = CMPLX(512, 64),
309 .angle = p->angle + M_PI * 0.5,
310 };
311
312 // FIXME: this may still be too slow for lasers in some cases
313 stage_clear_hazards_in_ellipse(e, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
314 ent_area_damage_ellipse(e, &(DamageInfo) { 52, DMG_PLAYER_BOMB }, NULL, NULL);
315
316 return ACTION_NONE;
317 }
318
YoumuSlash(Enemy * e,int t,bool render)319 static void YoumuSlash(Enemy *e, int t, bool render) {
320 }
321
youmu_slash(Enemy * e,int t)322 static int youmu_slash(Enemy *e, int t) {
323 if(t > creal(e->args[0]))
324 return ACTION_DESTROY;
325 if(t < 0)
326 return 1;
327
328 if(global.frames - global.plr.recovery > 0) {
329 return ACTION_DESTROY;
330 }
331
332 TIMER(&t);
333 FROM_TO(0,10000,3) {
334 cmplx pos = cexp(I*_i)*(100+10*_i*_i*0.01);
335 PARTICLE(
336 .sprite = "youmu_slice",
337 .color = RGBA(1, 1, 1, 0),
338 .pos = e->pos+pos,
339 .draw_rule = youmu_particle_slice_draw,
340 .rule = youmu_particle_slice_logic,
341 .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
342 .timeout = 100,
343 .angle = carg(pos),
344 .layer = LAYER_PARTICLE_HIGH | 0x1,
345 );
346 }
347
348 return 1;
349 }
350
youmu_asymptotic(Projectile * p,int t)351 static int youmu_asymptotic(Projectile *p, int t) {
352 if(t < 0) {
353 return ACTION_ACK;
354 }
355
356 p->angle = carg(p->args[0]);
357 p->args[1] *= 0.8;
358 p->pos += p->args[0] * (p->args[1] + 1);
359
360 youmu_homing_trail(p, cexp(I*p->angle), 5);
361 return 1;
362 }
363
youmu_haunting_power_shot(Player * plr,int p)364 static void youmu_haunting_power_shot(Player *plr, int p) {
365 int d = -2;
366 double spread = 0.5 * (1 + 0.25 * sin(global.frames/10.0));
367 double speed = 8;
368
369 if(2 * plr->power / 100 < p || (global.frames + d * p) % 12) {
370 return;
371 }
372
373 float np = (float)p / (2 * plr->power / 100);
374
375 for(int sign = -1; sign < 2; sign += 2) {
376 cmplx dir = cexp(I*carg(sign*p*spread-speed*I));
377
378 PROJECTILE(
379 .proto = pp_hghost,
380 .pos = plr->pos,
381 .rule = youmu_asymptotic,
382 .color = RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0),
383 .draw_rule = youmu_homing_draw_proj,
384 .args = { speed * dir * (1 - 0.25 * (1 - np)), 3 * (1 - pow(1 - np, 2)), 60, },
385 .type = PROJ_PLAYER,
386 .damage = 20,
387 .shader = "sprite_default",
388 );
389 }
390 }
391
youmu_haunting_shot(Player * plr)392 static void youmu_haunting_shot(Player *plr) {
393 youmu_common_shot(plr);
394
395 if(player_should_shoot(plr, true)) {
396 if(plr->inputflags & INFLAG_FOCUS) {
397 int pwr = plr->power / 100;
398
399 if(!(global.frames % (45 - 4 * pwr))) {
400 int pcnt = 11 + pwr * 4;
401 int pdmg = 120 - 18 * 4 * (1 - pow(1 - pwr / 4.0, 1.5));
402 cmplx aim = 0.15*I;
403
404 PROJECTILE(
405 .proto = pp_youhoming,
406 .pos = plr->pos,
407 .color = RGB(1, 1, 1),
408 .rule = youmu_trap,
409 .args = { -30.0*I, 120, pcnt+pdmg*I, aim },
410 .type = PROJ_PLAYER,
411 .damage = 1000,
412 .shader = "sprite_youmu_charged_shot",
413 .shader_params = &(ShaderCustomParams){{ 0, 1 }},
414 );
415 }
416 } else {
417 if(!(global.frames % 6)) {
418 PROJECTILE(
419 .proto = pp_hghost,
420 .pos = plr->pos,
421 .color = RGB(0.75, 0.9, 1),
422 .rule = youmu_homing,
423 .args = { -10.0*I, 0.02*I, 60, VIEWPORT_W*0.5 },
424 .type = PROJ_PLAYER,
425 .damage = 120,
426 .shader = "sprite_default",
427 );
428 }
429
430 for(int p = 1; p <= 2*PLR_MAX_POWER/100; ++p) {
431 youmu_haunting_power_shot(plr, p);
432 }
433 }
434 }
435 }
436
youmu_haunting_bomb(Player * plr)437 static void youmu_haunting_bomb(Player *plr) {
438 play_sound("bomb_youmu_b");
439 create_enemy_p(&plr->slaves, global.plr.pos, ENEMY_BOMB, YoumuSlash, youmu_slash, 280,0,0,0);
440 }
441
youmu_haunting_preload(void)442 static void youmu_haunting_preload(void) {
443 const int flags = RESF_DEFAULT;
444
445 preload_resources(RES_SPRITE, flags,
446 "proj/youmu",
447 "part/youmu_slice",
448 NULL);
449
450 preload_resources(RES_SHADER_PROGRAM, flags,
451 "sprite_youmu_charged_shot",
452 NULL);
453
454 preload_resources(RES_TEXTURE, flags,
455 "youmu_bombbg1",
456 NULL);
457
458 preload_resources(RES_SFX, flags | RESF_OPTIONAL,
459 "bomb_youmu_b",
460 NULL);
461 }
462
youmu_haunting_init(Player * plr)463 static void youmu_haunting_init(Player *plr) {
464 youmu_common_bomb_buffer_init();
465 }
466
467 PlayerMode plrmode_youmu_b = {
468 .name = "Haunting Revelation",
469 .description = "Ghosts are real, and quite hospitable too. The Netherworld shall welcome your foes, if you choose a good time to send the invitation.",
470 .spellcard_name = "Aegis Sword “Saigyō Omnidirectional Slash”",
471 .character = &character_youmu,
472 .dialog = &dialog_youmu,
473 .shot_mode = PLR_SHOT_YOUMU_HAUNTING,
474 .procs = {
475 .property = youmu_common_property,
476 .bomb = youmu_haunting_bomb,
477 .bombbg = youmu_common_bombbg,
478 .init = youmu_haunting_init,
479 .shot = youmu_haunting_shot,
480 .preload = youmu_haunting_preload,
481 },
482 };
483