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