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