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 "projectile.h"
12 
13 #include "global.h"
14 #include "list.h"
15 #include "stageobjects.h"
16 
17 ht_ptr2int_t shader_sublayer_map;
18 
19 static ProjArgs defaults_proj = {
20 	.sprite = "proj/",
21 	.draw_rule = ProjDraw,
22 	.dest = &global.projs,
23 	.type = PROJ_ENEMY,
24 	.damage_type = DMG_ENEMY_SHOT,
25 	.color = RGB(1, 1, 1),
26 	.blend = BLEND_PREMUL_ALPHA,
27 	.shader = "sprite_bullet",
28 	.layer = LAYER_BULLET,
29 };
30 
31 static ProjArgs defaults_part = {
32 	.sprite = "part/",
33 	.draw_rule = ProjDraw,
34 	.dest = &global.particles,
35 	.type = PROJ_PARTICLE,
36 	.damage_type = DMG_UNDEFINED,
37 	.color = RGB(1, 1, 1),
38 	.blend = BLEND_PREMUL_ALPHA,
39 	.shader = "sprite_default",
40 	.layer = LAYER_PARTICLE_MID,
41 };
42 
process_projectile_args(ProjArgs * args,ProjArgs * defaults)43 static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) {
44 	if(args->proto && args->proto->process_args) {
45 		args->proto->process_args(args->proto, args);
46 		return;
47 	}
48 
49 	// TODO: move this stuff into prototypes along with the defaults?
50 
51 	if(args->sprite) {
52 		args->sprite_ptr = prefix_get_sprite(args->sprite, defaults->sprite);
53 	}
54 
55 	if(!args->shader_ptr) {
56 		if(args->shader) {
57 			args->shader_ptr = r_shader_get(args->shader);
58 		} else {
59 			args->shader_ptr = defaults->shader_ptr;
60 		}
61 	}
62 
63 	if(!args->draw_rule) {
64 		args->draw_rule = ProjDraw;
65 	}
66 
67 	if(!args->blend) {
68 		args->blend = defaults->blend;
69 	}
70 
71 	if(!args->dest) {
72 		args->dest = defaults->dest;
73 	}
74 
75 	if(!args->type) {
76 		args->type = defaults->type;
77 	}
78 
79 	if(!args->color) {
80 		args->color = defaults->color;
81 	}
82 
83 	if(!args->max_viewport_dist && (args->type == PROJ_PARTICLE || args->type == PROJ_PLAYER)) {
84 		args->max_viewport_dist = 300;
85 	}
86 
87 	if(!args->layer) {
88 		if(args->type == PROJ_PLAYER) {
89 			args->layer = LAYER_PLAYER_SHOT;
90 		} else {
91 			args->layer = defaults->layer;
92 		}
93 	}
94 
95 	if(args->damage_type == DMG_UNDEFINED) {
96 		args->damage_type = defaults->damage_type;
97 
98 		if(args->type == PROJ_PLAYER && args->damage_type == DMG_ENEMY_SHOT) {
99 			args->damage_type = DMG_PLAYER_SHOT;
100 		}
101 	}
102 
103 	assert(args->type <= PROJ_PLAYER);
104 }
105 
projectile_size(Projectile * p,double * w,double * h)106 static void projectile_size(Projectile *p, double *w, double *h) {
107 	if(p->type == PROJ_PARTICLE && p->sprite != NULL) {
108 		*w = p->sprite->w;
109 		*h = p->sprite->h;
110 	} else {
111 		*w = creal(p->size);
112 		*h = cimag(p->size);
113 	}
114 
115 	assert(*w > 0);
116 	assert(*h > 0);
117 }
118 
119 static void ent_draw_projectile(EntityInterface *ent);
120 
event_name(int ev)121 static inline char* event_name(int ev) {
122 	switch(ev) {
123 		case EVENT_BIRTH: return "EVENT_BIRTH";
124 		case EVENT_DEATH: return "EVENT_DEATH";
125 		default: UNREACHABLE;
126 	}
127 }
128 
129 static Projectile* spawn_bullet_spawning_effect(Projectile *p);
130 
proj_call_rule(Projectile * p,int t)131 static inline int proj_call_rule(Projectile *p, int t) {
132 	int result = ACTION_NONE;
133 
134 	if(p->timeout > 0 && t >= p->timeout) {
135 		result = ACTION_DESTROY;
136 	} else if(p->rule != NULL) {
137 		result = p->rule(p, t);
138 
139 		if(t < 0 && result != ACTION_ACK) {
140 			set_debug_info(&p->debug);
141 			log_fatal(
142 				"Projectile rule didn't acknowledge %s (returned %i, expected %i)",
143 				event_name(t),
144 				result,
145 				ACTION_ACK
146 			);
147 		}
148 	}
149 
150 	if(/*t == 0 ||*/ t == EVENT_BIRTH) {
151 		p->prevpos = p->pos;
152 	}
153 
154 	if(t == 0) {
155 		spawn_bullet_spawning_effect(p);
156 	}
157 
158 	return result;
159 }
160 
projectile_set_prototype(Projectile * p,ProjPrototype * proto)161 void projectile_set_prototype(Projectile *p, ProjPrototype *proto) {
162 	if(p->proto && p->proto->deinit_projectile) {
163 		p->proto->deinit_projectile(p->proto, p);
164 	}
165 
166 	if(proto && proto->init_projectile) {
167 		proto->init_projectile(proto, p);
168 	}
169 
170 	p->proto = proto;
171 }
172 
projectile_graze_size(Projectile * p)173 cmplx projectile_graze_size(Projectile *p) {
174 	if(
175 		p->type == PROJ_ENEMY &&
176 		!(p->flags & (PFLAG_NOGRAZE | PFLAG_NOCOLLISION)) &&
177 		p->graze_counter < 3 &&
178 		global.frames >= p->graze_cooldown
179 	) {
180 		cmplx s = (p->size * 420 /* graze it */) / (2 * p->graze_counter + 1);
181 		return sqrt(creal(s)) + sqrt(cimag(s)) * I;
182 	}
183 
184 	return 0;
185 }
186 
projectile_rect_area(Projectile * p)187 static double projectile_rect_area(Projectile *p) {
188 	double w, h;
189 	projectile_size(p, &w, &h);
190 	return w * h;
191 }
192 
_create_projectile(ProjArgs * args)193 static Projectile* _create_projectile(ProjArgs *args) {
194 	if(IN_DRAW_CODE) {
195 		log_fatal("Tried to spawn a projectile while in drawing code");
196 	}
197 
198 	Projectile *p = (Projectile*)objpool_acquire(stage_object_pools.projectiles);
199 
200 	p->birthtime = global.frames;
201 	p->pos = p->pos0 = p->prevpos = args->pos;
202 	p->angle = args->angle;
203 	p->rule = args->rule;
204 	p->draw_rule = args->draw_rule;
205 	p->shader = args->shader_ptr;
206 	p->blend = args->blend;
207 	p->sprite = args->sprite_ptr;
208 	p->type = args->type;
209 	p->color = *args->color;
210 	p->max_viewport_dist = args->max_viewport_dist;
211 	p->size = args->size;
212 	p->collision_size = args->collision_size;
213 	p->flags = args->flags;
214 	p->timeout = args->timeout;
215 	p->damage = args->damage;
216 	p->damage_type = args->damage_type;
217 	p->clear_flags = 0;
218 
219 	if(args->shader_params != NULL) {
220 		p->shader_params = *args->shader_params;
221 	}
222 
223 	memcpy(p->args, args->args, sizeof(p->args));
224 
225 	p->ent.draw_layer = args->layer;
226 	p->ent.draw_func = ent_draw_projectile;
227 
228 	projectile_set_prototype(p, args->proto);
229 
230 	// p->collision_size *= 10;
231 	// p->size *= 5;
232 
233 	if((p->type == PROJ_ENEMY || p->type == PROJ_PLAYER) && (creal(p->size) <= 0 || cimag(p->size) <= 0)) {
234 		log_fatal("Tried to spawn a projectile with invalid size %f x %f", creal(p->size), cimag(p->size));
235 	}
236 
237 	if(!(p->ent.draw_layer & LAYER_LOW_MASK)) {
238 		drawlayer_low_t sublayer;
239 
240 		switch(p->type) {
241 			case PROJ_ENEMY:
242 				// 1. Large projectiles go below smaller ones.
243 				sublayer = LAYER_LOW_MASK - (drawlayer_low_t)projectile_rect_area(p);
244 				sublayer = (sublayer << 4) & LAYER_LOW_MASK;
245 				// 2. Group by shader (hardcoded precedence).
246 				sublayer |= ht_get(&shader_sublayer_map, p->shader, 0) & 0xf;
247 				// If specific blending order is required, then you should set up the sublayer manually.
248 				p->ent.draw_layer |= sublayer;
249 				break;
250 
251 			case PROJ_PARTICLE:
252 				// 1. Group by shader (hardcoded precedence).
253 				sublayer = ht_get(&shader_sublayer_map, p->shader, 0) & 0xf;
254 				sublayer <<= 4;
255 				sublayer |= 0x100;
256 				// If specific blending order is required, then you should set up the sublayer manually.
257 				p->ent.draw_layer |= sublayer;
258 				break;
259 
260 			default:
261 				break;
262 		}
263 	}
264 
265 	ent_register(&p->ent, ENT_PROJECTILE);
266 
267 	// TODO: Maybe allow ACTION_DESTROY here?
268 	// But in that case, code that uses this function's return value must be careful to not dereference a NULL pointer.
269 	proj_call_rule(p, EVENT_BIRTH);
270 	alist_append(args->dest, p);
271 
272 	return p;
273 }
274 
create_projectile(ProjArgs * args)275 Projectile* create_projectile(ProjArgs *args) {
276 	process_projectile_args(args, &defaults_proj);
277 	return _create_projectile(args);
278 }
279 
create_particle(ProjArgs * args)280 Projectile* create_particle(ProjArgs *args) {
281 	process_projectile_args(args, &defaults_part);
282 	return _create_projectile(args);
283 }
284 
285 #ifdef PROJ_DEBUG
_proj_attach_dbginfo(Projectile * p,DebugInfo * dbg,const char * callsite_str)286 Projectile* _proj_attach_dbginfo(Projectile *p, DebugInfo *dbg, const char *callsite_str) {
287 	// log_debug("Spawn: [%s]", callsite_str);
288 	memcpy(&p->debug, dbg, sizeof(DebugInfo));
289 	set_debug_info(dbg);
290 	return p;
291 }
292 #endif
293 
_delete_projectile(ListAnchor * projlist,List * proj,void * arg)294 static void* _delete_projectile(ListAnchor *projlist, List *proj, void *arg) {
295 	Projectile *p = (Projectile*)proj;
296 	proj_call_rule(p, EVENT_DEATH);
297 	ent_unregister(&p->ent);
298 	objpool_release(stage_object_pools.projectiles, alist_unlink(projlist, proj));
299 	return NULL;
300 }
301 
delete_projectile(ProjectileList * projlist,Projectile * proj)302 void delete_projectile(ProjectileList *projlist, Projectile *proj) {
303 	_delete_projectile((ListAnchor*)projlist, (List*)proj, NULL);
304 }
305 
delete_projectiles(ProjectileList * projlist)306 void delete_projectiles(ProjectileList *projlist) {
307 	alist_foreach(projlist, _delete_projectile, NULL);
308 }
309 
calc_projectile_collision(Projectile * p,ProjCollisionResult * out_col)310 void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) {
311 	assert(out_col != NULL);
312 
313 	out_col->type = PCOL_NONE;
314 	out_col->entity = NULL;
315 	out_col->fatal = false;
316 	out_col->location = p->pos;
317 	out_col->damage.amount = p->damage;
318 	out_col->damage.type = p->damage_type;
319 
320 	if(p->flags & PFLAG_NOCOLLISION) {
321 		goto skip_collision;
322 	}
323 
324 	if(p->type == PROJ_ENEMY) {
325 		Ellipse e_proj = {
326 			.axes = p->collision_size,
327 			.angle = p->angle + M_PI/2,
328 		};
329 
330 		LineSegment seg = {
331 			.a = global.plr.pos - global.plr.velocity - p->prevpos,
332 			.b = global.plr.pos - p->pos
333 		};
334 
335 		attr_unused double seglen = cabs(seg.a - seg.b);
336 
337 		if(seglen > 30) {
338 			log_debug(
339 				seglen > VIEWPORT_W
340 					? "Lerp over HUGE distance %f; this is ABSOLUTELY a bug! Player speed was %f. Spawned at %s:%d (%s); proj time = %d"
341 					: "Lerp over large distance %f; this is either a bug or a very fast projectile, investigate. Player speed was %f. Spawned at %s:%d (%s); proj time = %d",
342 				seglen,
343 				cabs(global.plr.velocity),
344 				p->debug.file,
345 				p->debug.line,
346 				p->debug.func,
347 				global.frames - p->birthtime
348 			);
349 		}
350 
351 		if(lineseg_ellipse_intersect(seg, e_proj)) {
352 			out_col->type = PCOL_ENTITY;
353 			out_col->entity = &global.plr.ent;
354 			out_col->fatal = true;
355 		} else {
356 			e_proj.axes = projectile_graze_size(p);
357 
358 			if(creal(e_proj.axes) > 1 && lineseg_ellipse_intersect(seg, e_proj)) {
359 				out_col->type = PCOL_PLAYER_GRAZE;
360 				out_col->entity = &global.plr.ent;
361 				out_col->location = p->pos;
362 			}
363 		}
364 	} else if(p->type == PROJ_PLAYER) {
365 		for(Enemy *e = global.enemies.first; e; e = e->next) {
366 			if(e->hp != ENEMY_IMMUNE && cabs(e->pos - p->pos) < 30) {
367 				out_col->type = PCOL_ENTITY;
368 				out_col->entity = &e->ent;
369 				out_col->fatal = true;
370 
371 				return;
372 			}
373 		}
374 
375 		if(global.boss && cabs(global.boss->pos - p->pos) < 42) {
376 			if(boss_is_vulnerable(global.boss)) {
377 				out_col->type = PCOL_ENTITY;
378 				out_col->entity = &global.boss->ent;
379 				out_col->fatal = true;
380 			}
381 		}
382 	}
383 
384 skip_collision:
385 
386 	if(out_col->type == PCOL_NONE && !projectile_in_viewport(p)) {
387 		out_col->type = PCOL_VOID;
388 		out_col->fatal = true;
389 	}
390 }
391 
apply_projectile_collision(ProjectileList * projlist,Projectile * p,ProjCollisionResult * col)392 void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col) {
393 	switch(col->type) {
394 		case PCOL_NONE:
395 		case PCOL_VOID:
396 			break;
397 
398 		case PCOL_PLAYER_GRAZE: {
399 			player_graze(ENT_CAST(col->entity, Player), col->location, 10 + 10 * p->graze_counter, 3 + p->graze_counter, &p->color);
400 
401 			p->graze_counter++;
402 			p->graze_cooldown = global.frames + 12;
403 			p->graze_counter_reset_timer = global.frames;
404 
405 			break;
406 		}
407 
408 		case PCOL_ENTITY: {
409 			ent_damage(col->entity, &col->damage);
410 			break;
411 		}
412 
413 		default:
414 			UNREACHABLE;
415 	}
416 
417 	if(col->fatal) {
418 		delete_projectile(projlist, p);
419 	}
420 }
421 
ent_draw_projectile(EntityInterface * ent)422 static void ent_draw_projectile(EntityInterface *ent) {
423 	Projectile *proj = ENT_CAST(ent, Projectile);
424 
425 	r_blend(proj->blend);
426 	r_shader_ptr(proj->shader);
427 
428 #ifdef PROJ_DEBUG
429 	static Projectile prev_state;
430 	memcpy(&prev_state, proj, sizeof(Projectile));
431 
432 	proj->draw_rule(proj, global.frames - proj->birthtime);
433 
434 	if(memcmp(&prev_state, proj, sizeof(Projectile))) {
435 		set_debug_info(&proj->debug);
436 		log_fatal("Projectile modified its state in draw rule");
437 	}
438 #else
439 	proj->draw_rule(proj, global.frames - proj->birthtime);
440 #endif
441 }
442 
projectile_in_viewport(Projectile * proj)443 bool projectile_in_viewport(Projectile *proj) {
444 	double w, h;
445 	int e = proj->max_viewport_dist;
446 	projectile_size(proj, &w, &h);
447 
448 	return !(creal(proj->pos) + w/2 + e < 0 || creal(proj->pos) - w/2 - e > VIEWPORT_W
449 		  || cimag(proj->pos) + h/2 + e < 0 || cimag(proj->pos) - h/2 - e > VIEWPORT_H);
450 }
451 
spawn_projectile_collision_effect(Projectile * proj)452 Projectile* spawn_projectile_collision_effect(Projectile *proj) {
453 	if(proj->flags & PFLAG_NOCOLLISIONEFFECT) {
454 		return NULL;
455 	}
456 
457 	if(proj->sprite == NULL) {
458 		return NULL;
459 	}
460 
461 	return PARTICLE(
462 		.sprite_ptr = proj->sprite,
463 		.size = proj->size,
464 		.pos = proj->pos,
465 		.color = &proj->color,
466 		.flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
467 		.layer = LAYER_PARTICLE_HIGH,
468 		.shader_ptr = proj->shader,
469 		.rule = linear,
470 		.draw_rule = DeathShrink,
471 		.angle = proj->angle,
472 		.args = { 5*cexp(I*proj->angle) },
473 		.timeout = 10,
474 	);
475 }
476 
really_clear_projectile(ProjectileList * projlist,Projectile * proj)477 static void really_clear_projectile(ProjectileList *projlist, Projectile *proj) {
478 	Projectile *effect = spawn_projectile_clear_effect(proj);
479 	Item *clear_item = NULL;
480 
481 	if(!(proj->flags & PFLAG_NOCLEARBONUS)) {
482 		clear_item = create_clear_item(proj->pos, proj->clear_flags);
483 	}
484 
485 	if(clear_item != NULL && effect != NULL) {
486 		effect->args[0] = add_ref(clear_item);
487 	}
488 
489 	delete_projectile(projlist, proj);
490 }
491 
clear_projectile(Projectile * proj,uint flags)492 bool clear_projectile(Projectile *proj, uint flags) {
493 	switch(proj->type) {
494 		case PROJ_PLAYER:
495 		case PROJ_PARTICLE:
496 			return false;
497 
498 		default: break;
499 	}
500 
501 	if(!(flags & CLEAR_HAZARDS_FORCE) && !projectile_is_clearable(proj)) {
502 		return false;
503 	}
504 
505 	proj->type = PROJ_DEAD;
506 	proj->clear_flags |= flags;
507 
508 	return true;
509 }
510 
process_projectiles(ProjectileList * projlist,bool collision)511 void process_projectiles(ProjectileList *projlist, bool collision) {
512 	ProjCollisionResult col = { 0 };
513 
514 	char killed = 0;
515 	int action;
516 	bool stage_cleared = stage_is_cleared();
517 
518 	for(Projectile *proj = projlist->first, *next; proj; proj = next) {
519 		next = proj->next;
520 		proj->prevpos = proj->pos;
521 
522 		if(stage_cleared) {
523 			clear_projectile(proj, CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_FORCE);
524 		}
525 
526 		action = proj_call_rule(proj, global.frames - proj->birthtime);
527 
528 		if(proj->graze_counter && proj->graze_counter_reset_timer - global.frames <= -90) {
529 			proj->graze_counter--;
530 			proj->graze_counter_reset_timer = global.frames;
531 		}
532 
533 		if(proj->type == PROJ_DEAD && killed < 10 && !(proj->clear_flags & CLEAR_HAZARDS_NOW)) {
534 			proj->clear_flags |= CLEAR_HAZARDS_NOW;
535 			killed++;
536 		}
537 
538 		if(action == ACTION_DESTROY) {
539 			memset(&col, 0, sizeof(col));
540 			col.fatal = true;
541 		} else if(collision) {
542 			calc_projectile_collision(proj, &col);
543 
544 			if(col.fatal && col.type != PCOL_VOID) {
545 				spawn_projectile_collision_effect(proj);
546 			}
547 		} else {
548 			memset(&col, 0, sizeof(col));
549 
550 			if(!projectile_in_viewport(proj)) {
551 				col.fatal = true;
552 			}
553 		}
554 
555 		apply_projectile_collision(projlist, proj, &col);
556 	}
557 
558 	for(Projectile *proj = projlist->first, *next; proj; proj = next) {
559 		next = proj->next;
560 
561 		if(proj->type == PROJ_DEAD && (proj->clear_flags & CLEAR_HAZARDS_NOW)) {
562 			really_clear_projectile(projlist, proj);
563 		}
564 	}
565 }
566 
trace_projectile(Projectile * p,ProjCollisionResult * out_col,ProjCollisionType stopflags,int timeofs)567 int trace_projectile(Projectile *p, ProjCollisionResult *out_col, ProjCollisionType stopflags, int timeofs) {
568 	int t;
569 
570 	for(t = timeofs; p; ++t) {
571 		int action = p->rule(p, t);
572 		calc_projectile_collision(p, out_col);
573 
574 		if(out_col->type & stopflags || action == ACTION_DESTROY) {
575 			return t;
576 		}
577 	}
578 
579 	return t;
580 }
581 
projectile_is_clearable(Projectile * p)582 bool projectile_is_clearable(Projectile *p) {
583 	if(p->type == PROJ_DEAD) {
584 		return true;
585 	}
586 
587 	if(p->type == PROJ_ENEMY) {
588 		return (p->flags & PFLAG_NOCLEAR) != PFLAG_NOCLEAR;
589 	}
590 
591 	return false;
592 }
593 
linear(Projectile * p,int t)594 int linear(Projectile *p, int t) { // sure is physics in here; a[0]: velocity
595 	if(t == EVENT_DEATH) {
596 		return ACTION_ACK;
597 	}
598 
599 	p->angle = carg(p->args[0]);
600 
601 	if(t == EVENT_BIRTH) {
602 		return ACTION_ACK;
603 	}
604 
605 	p->pos = p->pos0 + p->args[0]*t;
606 
607 	return ACTION_NONE;
608 }
609 
accelerated(Projectile * p,int t)610 int accelerated(Projectile *p, int t) {
611 	if(t == EVENT_DEATH) {
612 		return ACTION_ACK;
613 	}
614 
615 	p->angle = carg(p->args[0]);
616 
617 	if(t == EVENT_BIRTH) {
618 		return ACTION_ACK;
619 	}
620 
621 	p->pos += p->args[0];
622 	p->args[0] += p->args[1];
623 
624 	return 1;
625 }
626 
asymptotic(Projectile * p,int t)627 int asymptotic(Projectile *p, int t) { // v = a[0]*(a[1] + 1); a[1] -> 0
628 	if(t == EVENT_DEATH) {
629 		return ACTION_ACK;
630 	}
631 
632 	p->angle = carg(p->args[0]);
633 
634 	if(t == EVENT_BIRTH) {
635 		return ACTION_ACK;
636 	}
637 
638 	p->args[1] *= 0.8;
639 	p->pos += p->args[0]*(p->args[1] + 1);
640 
641 	return 1;
642 }
643 
proj_uses_spawning_effect(Projectile * proj,ProjFlags effect_flag)644 static inline bool proj_uses_spawning_effect(Projectile *proj, ProjFlags effect_flag) {
645 	if(proj->type != PROJ_ENEMY) {
646 		return false;
647 	}
648 
649 	if((proj->flags & effect_flag) == effect_flag) {
650 		return false;
651 	}
652 
653 	return true;
654 }
655 
proj_spawn_effect_factor(Projectile * proj,int t)656 static float proj_spawn_effect_factor(Projectile *proj, int t) {
657 	static const int maxt = 16;
658 
659 	if(t >= maxt || !proj_uses_spawning_effect(proj, PFLAG_NOSPAWNFADE)) {
660 		return 1;
661 	}
662 
663 	return t / (float)maxt;
664 }
665 
apply_common_transforms(Projectile * proj,int t)666 static inline void apply_common_transforms(Projectile *proj, int t) {
667 	r_mat_mv_translate(creal(proj->pos), cimag(proj->pos), 0);
668 	r_mat_mv_rotate(proj->angle + M_PI/2, 0, 0, 1);
669 
670 	/*
671 	float s = 0.75 + 0.25 * proj_spawn_effect_factor(proj, t);
672 
673 	if(s != 1) {
674 		r_mat_mv_scale(s, s, 1);
675 	}
676 	*/
677 }
678 
bullet_highlight_draw(Projectile * p,int t)679 static void bullet_highlight_draw(Projectile *p, int t) {
680 	float timefactor = t / p->timeout;
681 	float sx = creal(p->args[0]);
682 	float sy = cimag(p->args[0]);
683 
684 	float opacity = pow(1 - timefactor, 2);
685 	opacity = min(1, 1.5 * opacity) * min(1, timefactor * 10);
686 
687 	r_mat_tex_push();
688 	r_mat_tex_translate(0.5, 0.5, 0);
689 	r_mat_tex_rotate(p->args[1], 0, 0, 1);
690 	r_mat_tex_translate(-0.5, -0.5, 0);
691 
692 	r_draw_sprite(&(SpriteParams) {
693 		.sprite_ptr = p->sprite,
694 		.shader_ptr = p->shader,
695 		.shader_params = &(ShaderCustomParams) {{ 1 - opacity }},
696 		.color = &p->color,
697 		.scale = { .x = sx, .y = sy },
698 		.rotation.angle = p->angle + M_PI * 0.5,
699 		.pos = { creal(p->pos), cimag(p->pos) }
700 	});
701 
702 	r_mat_tex_pop();
703 }
704 
spawn_projectile_highlight_effect_internal(Projectile * p,bool flare)705 static Projectile* spawn_projectile_highlight_effect_internal(Projectile *p, bool flare) {
706 	if(!p->sprite) {
707 		return NULL;
708 	}
709 
710 	Color clr = p->color;
711 	clr.r = fmax(0.1, clr.r);
712 	clr.g = fmax(0.1, clr.g);
713 	clr.b = fmax(0.1, clr.b);
714 	float h, s, l;
715 	color_get_hsl(&clr, &h, &s, &l);
716 	s = s > 0 ? 0.75 : 0;
717 	l = 0.5;
718 	color_hsla(&clr, h, s, l, 0.05);
719 
720 	float sx, sy;
721 
722 	if(flare) {
723 		sx = pow(p->sprite->w, 0.7);
724 		sy = pow(p->sprite->h, 0.7);
725 
726 		PARTICLE(
727 			.sprite = "stardust_green",
728 			.shader = "sprite_bullet",
729 			.size = p->size * 4.5,
730 			.layer = LAYER_PARTICLE_HIGH | 0x40,
731 			.draw_rule = ScaleSquaredFade,
732 			.args = { 0, 0, (0 + 2*I) * 0.1 * fmax(sx, sy) * (1 - 0.2 * frand()) },
733 			.angle = frand() * M_PI * 2,
734 			.pos = p->pos + frand() * 8 * cexp(I*M_PI*2*frand()),
735 			.flags = PFLAG_NOREFLECT,
736 			.timeout = 24 + 2 * nfrand(),
737 			.color = &clr,
738 		);
739 	}
740 
741 	sx = pow((1.5 * p->sprite->w + 0.5 * p->sprite->h) * 0.5, 0.65);
742 	sy = pow((1.5 * p->sprite->h + 0.5 * p->sprite->w) * 0.5, 0.65);
743 	clr.a = 0.2;
744 
745 	return PARTICLE(
746 		.sprite = "bullet_cloud",
747 		.size = p->size * 4.5,
748 		.shader = "sprite_bullet",
749 		.layer = LAYER_PARTICLE_HIGH | 0x80,
750 		.draw_rule = bullet_highlight_draw,
751 		.args = { 0.125 * (sx + I * sy), frand() * M_PI * 2 },
752 		.angle = p->angle,
753 		.pos = p->pos + frand() * 5 * cexp(I*M_PI*2*frand()),
754 		.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
755 		.timeout = 32 + 2 * nfrand(),
756 		.color = &clr,
757 	);
758 }
759 
spawn_projectile_highlight_effect(Projectile * p)760 Projectile* spawn_projectile_highlight_effect(Projectile *p) {
761 	return spawn_projectile_highlight_effect_internal(p, true);
762 }
763 
spawn_bullet_spawning_effect(Projectile * p)764 static Projectile* spawn_bullet_spawning_effect(Projectile *p) {
765 	if(proj_uses_spawning_effect(p, PFLAG_NOSPAWNFLARE)) {
766 		return spawn_projectile_highlight_effect(p);
767 	}
768 
769 	return NULL;
770 }
771 
projectile_clear_effect_draw(Projectile * p,int t)772 static void projectile_clear_effect_draw(Projectile *p, int t) {
773 	r_mat_mv_push();
774 	apply_common_transforms(p, t);
775 
776 	float timefactor = t / p->timeout;
777 	float plrfactor = clamp(1 - (cabs(p->pos - global.plr.pos) - 64) / 128, 0, 1);
778 	plrfactor *= clamp(timefactor * 10, 0, 1);
779 	float f = 1 - (1 - timefactor) * (1 - plrfactor);
780 
781 	Sprite spr = *p->sprite;
782 	Sprite *ispr = get_sprite("item/bullet_point");
783 	spr.w = f * ispr->w + (1 - f) * spr.w;
784 	spr.h = f * ispr->h + (1 - f) * spr.h;
785 
786 	r_draw_sprite(&(SpriteParams) {
787 		.sprite_ptr = &spr,
788 		.color = RGBA(p->color.r, p->color.g, p->color.b, p->color.a * (1 - 0)),
789 		.shader_params = &(ShaderCustomParams){{ f }},
790 		// .scale.both = 1 + 0.5 * sqrt(tf),
791 	});
792 
793 	r_mat_mv_pop();
794 }
795 
projectile_clear_effect_logic(Projectile * p,int t)796 static int projectile_clear_effect_logic(Projectile *p, int t) {
797 	if(t == EVENT_DEATH) {
798 		free_ref(p->args[0]);
799 		return ACTION_ACK;
800 	}
801 
802 	if(t == EVENT_BIRTH) {
803 		return ACTION_ACK;
804 	}
805 
806 	if((int)p->args[0] < 0) {
807 		return ACTION_NONE;
808 	}
809 
810 	Item *i = REF(p->args[0]);
811 
812 	if(i != NULL) {
813 		p->pos = i->pos;
814 	}
815 
816 	return ACTION_NONE;
817 }
818 
spawn_projectile_clear_effect(Projectile * proj)819 Projectile* spawn_projectile_clear_effect(Projectile *proj) {
820 	if((proj->flags & PFLAG_NOCLEAREFFECT) || proj->sprite == NULL) {
821 		return NULL;
822 	}
823 
824 	// spawn_projectile_highlight_effect_internal(proj, false);
825 
826 	ShaderProgram *shader = proj->shader;
827 	uint32_t layer = LAYER_PARTICLE_BULLET_CLEAR;
828 
829 	if(proj->shader == defaults_proj.shader_ptr) {
830 		// HACK
831 		shader = r_shader_get("sprite_bullet_dead");
832 		layer |= 0x1;
833 	}
834 
835 	return PARTICLE(
836 		.sprite_ptr = proj->sprite,
837 		.size = proj->size,
838 		.pos = proj->pos,
839 		.color = &proj->color,
840 		.flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
841 		.shader_ptr = shader,
842 		.rule = projectile_clear_effect_logic,
843 		.draw_rule = projectile_clear_effect_draw,
844 		.angle = proj->angle,
845 		.timeout = 24,
846 		.args = { -1 },
847 		.layer = layer,
848 	);
849 }
850 
ProjDrawCore(Projectile * proj,const Color * c)851 void ProjDrawCore(Projectile *proj, const Color *c) {
852 	r_draw_sprite(&(SpriteParams) {
853 		.sprite_ptr = proj->sprite,
854 		.color = c,
855 		.shader_params = &proj->shader_params,
856 	});
857 }
858 
ProjDraw(Projectile * proj,int t)859 void ProjDraw(Projectile *proj, int t) {
860 	r_mat_mv_push();
861 	apply_common_transforms(proj, t);
862 
863 	float eff = proj_spawn_effect_factor(proj, t);
864 
865 	/*
866 	if(eff < 1 && proj->color.a > 0) {
867 		Color c = proj->color;
868 		c.a *= sqrt(eff);
869 		ProjDrawCore(proj, &c);
870 	} else {
871 		ProjDrawCore(proj, &proj->color);
872 	}
873 	*/
874 
875 	if(eff < 1) {
876 		float o = proj->shader_params.vector[0];
877 		float a = min(1, eff * 2);
878 		proj->shader_params.vector[0] = 1 - (1 - o) * a;
879 		Color c = proj->color;
880 		c.a *= eff;//sqrt(eff);
881 		ProjDrawCore(proj, &c);
882 		proj->shader_params.vector[0] = o;
883 	} else {
884 		ProjDrawCore(proj, &proj->color);
885 	}
886 
887 	r_mat_mv_pop();
888 }
889 
ProjNoDraw(Projectile * proj,int t)890 void ProjNoDraw(Projectile *proj, int t) {
891 }
892 
Blast(Projectile * p,int t)893 void Blast(Projectile *p, int t) {
894 	r_mat_mv_push();
895 	r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0);
896 	r_mat_mv_rotate(creal(p->args[1]) * DEG2RAD, cimag(p->args[1]), creal(p->args[2]), cimag(p->args[2]));
897 
898 	if(t != p->timeout && p->timeout != 0) {
899 		r_mat_mv_scale(t/(double)p->timeout, t/(double)p->timeout, 1);
900 	}
901 
902 	float fade = 1.0 - t / (double)p->timeout;
903 	r_color(RGBA_MUL_ALPHA(0.3, 0.6, 1.0, fade));
904 
905 	SpriteParams sp = {
906 		.sprite_ptr = p->sprite,
907 		.color = RGBA_MUL_ALPHA(0.3, 0.6, 1.0, fade),
908 	};
909 
910 	r_draw_sprite(&sp);
911 	r_mat_mv_scale(0.5+creal(p->args[2]), 0.5+creal(p->args[2]), 1);
912 	r_color4(0.3 * fade, 0.6 * fade, 1.0 * fade, 0);
913 	sp.color = RGBA(0.3 * fade, 0.6 * fade, 1.0 * fade, 0);
914 	r_draw_sprite(&sp);
915 	r_mat_mv_pop();
916 }
917 
Shrink(Projectile * p,int t)918 void Shrink(Projectile *p, int t) {
919 	r_mat_mv_push();
920 	apply_common_transforms(p, t);
921 
922 	float s = 2.0-t/(double)p->timeout*2;
923 	if(s != 1) {
924 		r_mat_mv_scale(s, s, 1);
925 	}
926 
927 	ProjDrawCore(p, &p->color);
928 	r_mat_mv_pop();
929 }
930 
DeathShrink(Projectile * p,int t)931 void DeathShrink(Projectile *p, int t) {
932 	r_mat_mv_push();
933 	apply_common_transforms(p, t);
934 
935 	float s = 2.0-t/(double)p->timeout*2;
936 	if(s != 1) {
937 		r_mat_mv_scale(s, 1, 1);
938 	}
939 
940 	ProjDrawCore(p, &p->color);
941 	r_mat_mv_pop();
942 }
943 
GrowFade(Projectile * p,int t)944 void GrowFade(Projectile *p, int t) {
945 	r_mat_mv_push();
946 	apply_common_transforms(p, t);
947 
948 	set_debug_info(&p->debug);
949 	assert(p->timeout != 0);
950 
951 	float s = t/(double)p->timeout*(1 + (creal(p->args[2])? p->args[2] : p->args[1]));
952 	if(s != 1) {
953 		r_mat_mv_scale(s, s, 1);
954 	}
955 
956 	ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), 1 - t/(double)p->timeout));
957 	r_mat_mv_pop();
958 }
959 
Fade(Projectile * p,int t)960 void Fade(Projectile *p, int t) {
961 	r_mat_mv_push();
962 	apply_common_transforms(p, t);
963 	ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), 1 - t/(double)p->timeout));
964 	r_mat_mv_pop();
965 }
966 
ScaleFadeImpl(Projectile * p,int t,int fade_exponent)967 static void ScaleFadeImpl(Projectile *p, int t, int fade_exponent) {
968 	r_mat_mv_push();
969 	apply_common_transforms(p, t);
970 
971 	double scale_min = creal(p->args[2]);
972 	double scale_max = cimag(p->args[2]);
973 	double timefactor = t / (double)p->timeout;
974 	double scale = scale_min * (1 - timefactor) + scale_max * timefactor;
975 	double alpha = pow(fabs(1 - timefactor), fade_exponent);
976 
977 	r_mat_mv_scale(scale, scale, 1);
978 	ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), alpha));
979 	r_mat_mv_pop();
980 }
981 
ScaleFade(Projectile * p,int t)982 void ScaleFade(Projectile *p, int t) {
983 	ScaleFadeImpl(p, t, 1);
984 }
985 
ScaleSquaredFade(Projectile * p,int t)986 void ScaleSquaredFade(Projectile *p, int t) {
987 	ScaleFadeImpl(p, t, 2);
988 }
989 
Petal(Projectile * p,int t)990 void Petal(Projectile *p, int t) {
991 	float x = creal(p->args[2]);
992 	float y = cimag(p->args[2]);
993 	float z = creal(p->args[3]);
994 
995 	float r = sqrt(x*x+y*y+z*z);
996 	x /= r; y /= r; z /= r;
997 
998 	r_disable(RCAP_CULL_FACE);
999 	r_mat_mv_push();
1000 	r_mat_mv_translate(creal(p->pos), cimag(p->pos),0);
1001 	r_mat_mv_rotate(DEG2RAD * (t*4.0 + cimag(p->args[3])), x, y, z);
1002 	ProjDrawCore(p, &p->color);
1003 	r_mat_mv_pop();
1004 }
1005 
petal_explosion(int n,cmplx pos)1006 void petal_explosion(int n, cmplx pos) {
1007 	for(int i = 0; i < n; i++) {
1008 		tsrand_fill(6);
1009 		float t = frand();
1010 
1011 		PARTICLE(
1012 			.sprite = "petal",
1013 			.pos = pos,
1014 			.color = RGBA(sin(5*t) * t, cos(5*t) * t, 0.5 * t, 0),
1015 			.rule = asymptotic,
1016 			.draw_rule = Petal,
1017 			.args = {
1018 				(3+5*afrand(2))*cexp(I*M_PI*2*afrand(3)),
1019 				5,
1020 				afrand(4) + afrand(5)*I,
1021 				afrand(1) + 360.0*I*afrand(0),
1022 			},
1023 			// TODO: maybe remove this noreflect, there shouldn't be a cull mode mess anymore
1024 			.flags = PFLAG_NOREFLECT | (n % 2 ? 0 : PFLAG_REQUIREDPARTICLE),
1025 			.layer = LAYER_PARTICLE_PETAL,
1026 		);
1027 	}
1028 }
1029 
projectiles_preload(void)1030 void projectiles_preload(void) {
1031 	const char *shaders[] = {
1032 		// This array defines a shader-based fallback draw order
1033 		"sprite_silhouette",
1034 		defaults_proj.shader,
1035 		defaults_part.shader,
1036 		"sprite_bullet_dead",
1037 	};
1038 
1039 	const uint num_shaders = sizeof(shaders)/sizeof(*shaders);
1040 
1041 	for(uint i = 0; i < num_shaders; ++i) {
1042 		preload_resource(RES_SHADER_PROGRAM, shaders[i], RESF_PERMANENT);
1043 	}
1044 
1045 	// FIXME: Why is this here?
1046 	preload_resources(RES_TEXTURE, RESF_PERMANENT,
1047 		"part/lasercurve",
1048 	NULL);
1049 
1050 	// TODO: Maybe split this up into stage-specific preloads too?
1051 	// some of these are ubiquitous, but some only appear in very specific parts.
1052 	preload_resources(RES_SPRITE, RESF_PERMANENT,
1053 		"part/blast",
1054 		"part/bullet_cloud",
1055 		"part/flare",
1056 		"part/graze",
1057 		"part/lightning0",
1058 		"part/lightning1",
1059 		"part/lightningball",
1060 		"part/petal",
1061 		"part/smoke",
1062 		"part/smoothdot",
1063 		"part/stain",
1064 		"part/stardust_green",
1065 	NULL);
1066 
1067 	preload_resources(RES_SFX, RESF_PERMANENT,
1068 		"shot1",
1069 		"shot2",
1070 		"shot3",
1071 		"shot1_loop",
1072 		"shot_special1",
1073 		"redirect",
1074 	NULL);
1075 
1076 	#define PP(name) (_pp_##name).preload(&_pp_##name);
1077 	#include "projectile_prototypes/all.inc.h"
1078 
1079 	ht_create(&shader_sublayer_map);
1080 
1081 	for(uint i = 0; i < num_shaders; ++i) {
1082 		ht_set(&shader_sublayer_map, r_shader_get(shaders[i]), i + 1);
1083 	}
1084 
1085 	defaults_proj.shader_ptr = r_shader_get(defaults_proj.shader);
1086 	defaults_part.shader_ptr = r_shader_get(defaults_part.shader);
1087 }
1088 
projectiles_free(void)1089 void projectiles_free(void) {
1090 	ht_destroy(&shader_sublayer_map);
1091 }
1092