2010-10-12 10:55:23 +02:00
|
|
|
/*
|
2011-03-05 19:03:32 +01:00
|
|
|
* This software is licensed under the terms of the MIT-License
|
2017-02-10 23:05:22 +01:00
|
|
|
* See COPYING for further information.
|
2011-03-05 19:03:32 +01:00
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
|
2010-10-12 10:55:23 +02:00
|
|
|
*/
|
|
|
|
|
2017-11-25 20:45:11 +01:00
|
|
|
#include "taisei.h"
|
|
|
|
|
2010-10-12 10:55:23 +02:00
|
|
|
#include "projectile.h"
|
|
|
|
|
|
|
|
#include "global.h"
|
2010-10-27 19:51:49 +02:00
|
|
|
#include "list.h"
|
2017-12-13 20:05:12 +01:00
|
|
|
#include "stageobjects.h"
|
2011-04-26 16:55:18 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
ht_ptr2int_t shader_sublayer_map;
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
static ProjArgs defaults_proj = {
|
2018-02-06 07:19:25 +01:00
|
|
|
.sprite = "proj/",
|
2017-11-10 21:49:16 +01:00
|
|
|
.draw_rule = ProjDraw,
|
|
|
|
.dest = &global.projs,
|
|
|
|
.type = EnemyProj,
|
2018-07-30 09:04:09 +02:00
|
|
|
.damage_type = DMG_ENEMY_SHOT,
|
2017-11-10 21:49:16 +01:00
|
|
|
.color = RGB(1, 1, 1),
|
2018-07-23 19:07:59 +02:00
|
|
|
.blend = BLEND_PREMUL_ALPHA,
|
2018-04-12 16:08:48 +02:00
|
|
|
.shader = "sprite_bullet",
|
2018-04-13 21:13:48 +02:00
|
|
|
.layer = LAYER_BULLET,
|
2017-11-10 21:49:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static ProjArgs defaults_part = {
|
2018-02-06 07:19:25 +01:00
|
|
|
.sprite = "part/",
|
2017-11-10 21:49:16 +01:00
|
|
|
.draw_rule = ProjDraw,
|
|
|
|
.dest = &global.particles,
|
|
|
|
.type = Particle,
|
2018-07-30 09:04:09 +02:00
|
|
|
.damage_type = DMG_UNDEFINED,
|
2017-11-10 21:49:16 +01:00
|
|
|
.color = RGB(1, 1, 1),
|
2018-07-23 19:07:59 +02:00
|
|
|
.blend = BLEND_PREMUL_ALPHA,
|
2018-04-12 16:08:48 +02:00
|
|
|
.shader = "sprite_default",
|
2019-01-04 23:59:39 +01:00
|
|
|
.layer = LAYER_PARTICLE_MID,
|
2017-11-10 21:49:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) {
|
2018-05-02 06:46:48 +02:00
|
|
|
// Detect the deprecated way to spawn projectiles and remap it to prototypes,
|
|
|
|
// if possible. This is so that I can conserve the remains of my sanity by
|
|
|
|
// not having to convert every single PROJECTILE call in the game manually
|
|
|
|
// and in one go. So, TODO: convert every single PROJECTILE call in the game
|
|
|
|
// and remove this mess.
|
|
|
|
|
|
|
|
if(!args->proto && args->sprite && args->size == 0) {
|
|
|
|
static struct {
|
|
|
|
const char *name;
|
|
|
|
ProjPrototype *proto;
|
|
|
|
} proto_map[] = {
|
|
|
|
#define PP(name) { #name, &_pp_##name },
|
|
|
|
#include "projectile_prototypes/all.inc.h"
|
|
|
|
};
|
|
|
|
|
|
|
|
for(int i = 0; i < sizeof(proto_map)/sizeof(*proto_map); ++i) {
|
|
|
|
if(!strcmp(args->sprite, proto_map[i].name)) {
|
|
|
|
args->proto = proto_map[i].proto;
|
|
|
|
args->sprite = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
if(args->proto && args->proto->process_args) {
|
|
|
|
args->proto->process_args(args->proto, args);
|
|
|
|
return;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
// TODO: move this stuff into prototypes along with the defaults?
|
|
|
|
|
2018-02-06 07:19:25 +01:00
|
|
|
if(args->sprite) {
|
|
|
|
args->sprite_ptr = prefix_get_sprite(args->sprite, defaults->sprite);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
if(!args->shader_ptr) {
|
|
|
|
if(args->shader) {
|
|
|
|
args->shader_ptr = r_shader_get(args->shader);
|
|
|
|
} else {
|
|
|
|
args->shader_ptr = defaults->shader_ptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
if(!args->draw_rule) {
|
|
|
|
args->draw_rule = ProjDraw;
|
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
if(!args->blend) {
|
|
|
|
args->blend = defaults->blend;
|
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
if(!args->dest) {
|
|
|
|
args->dest = defaults->dest;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!args->type) {
|
|
|
|
args->type = defaults->type;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!args->color) {
|
|
|
|
args->color = defaults->color;
|
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
if(!args->max_viewport_dist && (args->type == Particle || args->type == PlrProj)) {
|
2017-11-10 21:49:16 +01:00
|
|
|
args->max_viewport_dist = 300;
|
|
|
|
}
|
2018-04-13 21:13:48 +02:00
|
|
|
|
|
|
|
if(!args->layer) {
|
2018-07-30 09:04:09 +02:00
|
|
|
if(args->type == PlrProj) {
|
2018-04-13 21:13:48 +02:00
|
|
|
args->layer = LAYER_PLAYER_SHOT;
|
|
|
|
} else {
|
|
|
|
args->layer = defaults->layer;
|
|
|
|
}
|
|
|
|
}
|
2018-07-30 09:04:09 +02:00
|
|
|
|
|
|
|
if(args->damage_type == DMG_UNDEFINED) {
|
|
|
|
args->damage_type = defaults->damage_type;
|
|
|
|
|
|
|
|
if(args->type == PlrProj && args->damage_type == DMG_ENEMY_SHOT) {
|
|
|
|
args->damage_type = DMG_PLAYER_SHOT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(args->type == EnemyProj && args->proto) {
|
|
|
|
// args->proto = pp_crystal;
|
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
assert(args->type <= PlrProj);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
|
|
|
|
2017-11-23 16:27:41 +01:00
|
|
|
static void projectile_size(Projectile *p, double *w, double *h) {
|
2018-05-02 06:46:48 +02:00
|
|
|
if(p->type == Particle && p->sprite != NULL) {
|
2018-02-06 07:19:25 +01:00
|
|
|
*w = p->sprite->w;
|
|
|
|
*h = p->sprite->h;
|
2017-11-23 16:27:41 +01:00
|
|
|
} else {
|
|
|
|
*w = creal(p->size);
|
|
|
|
*h = cimag(p->size);
|
|
|
|
}
|
2018-05-02 06:46:48 +02:00
|
|
|
|
|
|
|
assert(*w > 0);
|
|
|
|
assert(*h > 0);
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_projectile(EntityInterface *ent);
|
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
static inline char* event_name(int ev) {
|
|
|
|
switch(ev) {
|
|
|
|
case EVENT_BIRTH: return "EVENT_BIRTH";
|
|
|
|
case EVENT_DEATH: return "EVENT_DEATH";
|
2019-01-26 02:54:57 +01:00
|
|
|
default: UNREACHABLE;
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static Projectile* spawn_bullet_spawning_effect(Projectile *p);
|
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
static inline int proj_call_rule(Projectile *p, int t) {
|
2018-05-16 03:39:33 +02:00
|
|
|
int result = ACTION_NONE;
|
2018-01-06 07:06:12 +01:00
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
if(p->timeout > 0 && t >= p->timeout) {
|
|
|
|
result = ACTION_DESTROY;
|
|
|
|
} else if(p->rule != NULL) {
|
|
|
|
result = p->rule(p, t);
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
if(t < 0 && result != ACTION_ACK) {
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
set_debug_info(&p->debug);
|
|
|
|
log_fatal(
|
|
|
|
"Projectile rule didn't acknowledge %s (returned %i, expected %i)",
|
|
|
|
event_name(t),
|
2018-05-16 03:39:33 +02:00
|
|
|
result,
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
ACTION_ACK
|
|
|
|
);
|
|
|
|
}
|
2018-05-16 03:39:33 +02:00
|
|
|
}
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
if(/*t == 0 ||*/ t == EVENT_BIRTH) {
|
|
|
|
p->prevpos = p->pos;
|
2018-01-06 07:06:12 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
if(t == 0) {
|
|
|
|
spawn_bullet_spawning_effect(p);
|
|
|
|
}
|
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
return result;
|
2017-11-23 16:27:41 +01:00
|
|
|
}
|
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
void projectile_set_prototype(Projectile *p, ProjPrototype *proto) {
|
|
|
|
if(p->proto && p->proto->deinit_projectile) {
|
|
|
|
p->proto->deinit_projectile(p->proto, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(proto && proto->init_projectile) {
|
|
|
|
proto->init_projectile(proto, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
p->proto = proto;
|
2018-01-08 17:56:46 +01:00
|
|
|
}
|
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
complex projectile_graze_size(Projectile *p) {
|
2019-03-28 17:40:04 +01:00
|
|
|
if(
|
|
|
|
p->type == EnemyProj &&
|
|
|
|
!(p->flags & (PFLAG_NOGRAZE | PFLAG_NOCOLLISION)) &&
|
|
|
|
p->graze_counter < 3 &&
|
|
|
|
global.frames >= p->graze_cooldown
|
|
|
|
) {
|
2018-05-13 15:08:58 +02:00
|
|
|
complex s = (p->size * 420 /* graze it */) / (2 * p->graze_counter + 1);
|
|
|
|
return sqrt(creal(s)) + sqrt(cimag(s)) * I;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-12 02:10:21 +02:00
|
|
|
static double projectile_rect_area(Projectile *p) {
|
|
|
|
double w, h;
|
|
|
|
projectile_size(p, &w, &h);
|
|
|
|
return w * h;
|
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
static Projectile* _create_projectile(ProjArgs *args) {
|
2017-11-15 07:05:47 +01:00
|
|
|
if(IN_DRAW_CODE) {
|
|
|
|
log_fatal("Tried to spawn a projectile while in drawing code");
|
|
|
|
}
|
|
|
|
|
2017-12-13 20:05:12 +01:00
|
|
|
Projectile *p = (Projectile*)objpool_acquire(stage_object_pools.projectiles);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-03-07 07:26:43 +01:00
|
|
|
p->birthtime = global.frames;
|
2018-05-13 15:08:58 +02:00
|
|
|
p->pos = p->pos0 = p->prevpos = args->pos;
|
2017-11-10 21:49:16 +01:00
|
|
|
p->angle = args->angle;
|
|
|
|
p->rule = args->rule;
|
|
|
|
p->draw_rule = args->draw_rule;
|
2018-04-12 16:08:48 +02:00
|
|
|
p->shader = args->shader_ptr;
|
|
|
|
p->blend = args->blend;
|
2018-02-06 07:19:25 +01:00
|
|
|
p->sprite = args->sprite_ptr;
|
2017-11-10 21:49:16 +01:00
|
|
|
p->type = args->type;
|
2018-07-23 19:07:59 +02:00
|
|
|
p->color = *args->color;
|
2017-11-10 21:49:16 +01:00
|
|
|
p->max_viewport_dist = args->max_viewport_dist;
|
|
|
|
p->size = args->size;
|
2018-05-13 15:08:58 +02:00
|
|
|
p->collision_size = args->collision_size;
|
2017-11-10 21:49:16 +01:00
|
|
|
p->flags = args->flags;
|
2018-05-02 06:46:48 +02:00
|
|
|
p->timeout = args->timeout;
|
2018-07-30 09:04:09 +02:00
|
|
|
p->damage = args->damage;
|
|
|
|
p->damage_type = args->damage_type;
|
2019-02-22 00:56:03 +01:00
|
|
|
p->clear_flags = 0;
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
if(args->shader_params != NULL) {
|
|
|
|
p->shader_params = *args->shader_params;
|
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
memcpy(p->args, args->args, sizeof(p->args));
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
p->ent.draw_layer = args->layer;
|
2018-05-02 06:46:48 +02:00
|
|
|
p->ent.draw_func = ent_draw_projectile;
|
|
|
|
|
|
|
|
projectile_set_prototype(p, args->proto);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
// p->collision_size *= 10;
|
|
|
|
// p->size *= 5;
|
2018-05-02 06:46:48 +02:00
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
if((p->type == EnemyProj || p->type == PlrProj) && (creal(p->size) <= 0 || cimag(p->size) <= 0)) {
|
2018-05-02 06:46:48 +02:00
|
|
|
log_fatal("Tried to spawn a projectile with invalid size %f x %f", creal(p->size), cimag(p->size));
|
|
|
|
}
|
2018-04-13 21:13:48 +02:00
|
|
|
|
2018-08-12 02:10:21 +02:00
|
|
|
if(!(p->ent.draw_layer & LAYER_LOW_MASK)) {
|
2019-01-04 23:59:39 +01:00
|
|
|
drawlayer_low_t sublayer;
|
|
|
|
|
2018-08-12 02:10:21 +02:00
|
|
|
switch(p->type) {
|
|
|
|
case EnemyProj:
|
2019-01-04 23:59:39 +01:00
|
|
|
// 1. Large projectiles go below smaller ones.
|
|
|
|
sublayer = LAYER_LOW_MASK - (drawlayer_low_t)projectile_rect_area(p);
|
|
|
|
sublayer = (sublayer << 4) & LAYER_LOW_MASK;
|
|
|
|
// 2. Group by shader (hardcoded precedence).
|
|
|
|
sublayer |= ht_get(&shader_sublayer_map, p->shader, 0) & 0xf;
|
|
|
|
// If specific blending order is required, then you should set up the sublayer manually.
|
|
|
|
p->ent.draw_layer |= sublayer;
|
|
|
|
break;
|
|
|
|
|
2019-03-28 17:40:04 +01:00
|
|
|
case Particle:
|
2019-01-04 23:59:39 +01:00
|
|
|
// 1. Group by shader (hardcoded precedence).
|
|
|
|
sublayer = ht_get(&shader_sublayer_map, p->shader, 0) & 0xf;
|
|
|
|
sublayer <<= 4;
|
|
|
|
sublayer |= 0x100;
|
2018-08-12 02:10:21 +02:00
|
|
|
// If specific blending order is required, then you should set up the sublayer manually.
|
|
|
|
p->ent.draw_layer |= sublayer;
|
|
|
|
break;
|
|
|
|
|
2019-03-28 17:40:04 +01:00
|
|
|
default:
|
2018-08-12 02:10:21 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_register(&p->ent, ENT_PROJECTILE);
|
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
// TODO: Maybe allow ACTION_DESTROY here?
|
|
|
|
// But in that case, code that uses this function's return value must be careful to not dereference a NULL pointer.
|
|
|
|
proj_call_rule(p, EVENT_BIRTH);
|
2019-01-04 23:59:39 +01:00
|
|
|
alist_append(args->dest, p);
|
2017-10-20 20:31:21 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
return p;
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
Projectile* create_projectile(ProjArgs *args) {
|
|
|
|
process_projectile_args(args, &defaults_proj);
|
|
|
|
return _create_projectile(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
Projectile* create_particle(ProjArgs *args) {
|
|
|
|
process_projectile_args(args, &defaults_part);
|
|
|
|
return _create_projectile(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef PROJ_DEBUG
|
2017-11-23 16:27:41 +01:00
|
|
|
Projectile* _proj_attach_dbginfo(Projectile *p, DebugInfo *dbg, const char *callsite_str) {
|
|
|
|
// log_debug("Spawn: [%s]", callsite_str);
|
2017-11-15 07:05:47 +01:00
|
|
|
memcpy(&p->debug, dbg, sizeof(DebugInfo));
|
|
|
|
set_debug_info(dbg);
|
2017-11-10 21:49:16 +01:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
static void* _delete_projectile(ListAnchor *projlist, List *proj, void *arg) {
|
2017-11-21 15:45:01 +01:00
|
|
|
Projectile *p = (Projectile*)proj;
|
2018-05-02 06:46:48 +02:00
|
|
|
proj_call_rule(p, EVENT_DEATH);
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_unregister(&p->ent);
|
2018-06-01 20:40:18 +02:00
|
|
|
objpool_release(stage_object_pools.projectiles, (ObjectInterface*)alist_unlink(projlist, proj));
|
2017-11-21 15:45:01 +01:00
|
|
|
return NULL;
|
2011-04-26 22:39:50 +02:00
|
|
|
}
|
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
void delete_projectile(ProjectileList *projlist, Projectile *proj) {
|
|
|
|
_delete_projectile((ListAnchor*)projlist, (List*)proj, NULL);
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
void delete_projectiles(ProjectileList *projlist) {
|
|
|
|
alist_foreach(projlist, _delete_projectile, NULL);
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) {
|
|
|
|
assert(out_col != NULL);
|
|
|
|
|
|
|
|
out_col->type = PCOL_NONE;
|
|
|
|
out_col->entity = NULL;
|
|
|
|
out_col->fatal = false;
|
|
|
|
out_col->location = p->pos;
|
2018-07-30 09:04:09 +02:00
|
|
|
out_col->damage.amount = p->damage;
|
|
|
|
out_col->damage.type = p->damage_type;
|
2017-12-28 10:05:54 +01:00
|
|
|
|
2019-03-26 12:57:01 +01:00
|
|
|
if(p->flags & PFLAG_NOCOLLISION) {
|
|
|
|
goto skip_collision;
|
|
|
|
}
|
|
|
|
|
2017-11-05 15:02:49 +01:00
|
|
|
if(p->type == EnemyProj) {
|
2018-05-13 15:08:58 +02:00
|
|
|
Ellipse e_proj = {
|
|
|
|
.axes = p->collision_size,
|
|
|
|
.angle = p->angle + M_PI/2,
|
|
|
|
};
|
2017-11-01 05:47:36 +01:00
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
LineSegment seg = {
|
|
|
|
.a = global.plr.pos - global.plr.velocity - p->prevpos,
|
|
|
|
.b = global.plr.pos - p->pos
|
|
|
|
};
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
attr_unused double seglen = cabs(seg.a - seg.b);
|
|
|
|
|
|
|
|
if(seglen > 30) {
|
|
|
|
log_debug(
|
|
|
|
seglen > VIEWPORT_W
|
|
|
|
? "Lerp over HUGE distance %f; this is ABSOLUTELY a bug! Player speed was %f. Spawned at %s:%d (%s)"
|
|
|
|
: "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)",
|
|
|
|
seglen,
|
|
|
|
cabs(global.plr.velocity),
|
|
|
|
p->debug.file,
|
|
|
|
p->debug.line,
|
|
|
|
p->debug.func
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
if(lineseg_ellipse_intersect(seg, e_proj)) {
|
2018-07-30 09:04:09 +02:00
|
|
|
out_col->type = PCOL_ENTITY;
|
|
|
|
out_col->entity = &global.plr.ent;
|
2017-12-28 10:05:54 +01:00
|
|
|
out_col->fatal = true;
|
2018-05-13 15:08:58 +02:00
|
|
|
} else {
|
|
|
|
e_proj.axes = projectile_graze_size(p);
|
|
|
|
|
|
|
|
if(creal(e_proj.axes) > 1 && lineseg_ellipse_intersect(seg, e_proj)) {
|
|
|
|
out_col->type = PCOL_PLAYER_GRAZE;
|
2018-07-30 09:04:09 +02:00
|
|
|
out_col->entity = &global.plr.ent;
|
2019-01-04 23:59:39 +01:00
|
|
|
out_col->location = p->pos;
|
2018-05-13 15:08:58 +02:00
|
|
|
}
|
2012-08-12 21:29:10 +02:00
|
|
|
}
|
2018-07-30 09:04:09 +02:00
|
|
|
} else if(p->type == PlrProj) {
|
2018-06-01 20:40:18 +02:00
|
|
|
for(Enemy *e = global.enemies.first; e; e = e->next) {
|
2017-10-28 05:41:45 +02:00
|
|
|
if(e->hp != ENEMY_IMMUNE && cabs(e->pos - p->pos) < 30) {
|
2018-07-30 09:04:09 +02:00
|
|
|
out_col->type = PCOL_ENTITY;
|
|
|
|
out_col->entity = &e->ent;
|
2017-12-28 10:05:54 +01:00
|
|
|
out_col->fatal = true;
|
2017-03-06 13:02:46 +01:00
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
return;
|
2010-10-17 10:39:07 +02:00
|
|
|
}
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-09-27 11:08:32 +02:00
|
|
|
if(global.boss && cabs(global.boss->pos - p->pos) < 42) {
|
2017-12-28 10:05:54 +01:00
|
|
|
if(boss_is_vulnerable(global.boss)) {
|
2018-07-30 09:04:09 +02:00
|
|
|
out_col->type = PCOL_ENTITY;
|
|
|
|
out_col->entity = &global.boss->ent;
|
2017-12-28 10:05:54 +01:00
|
|
|
out_col->fatal = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-26 12:57:01 +01:00
|
|
|
skip_collision:
|
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
if(out_col->type == PCOL_NONE && !projectile_in_viewport(p)) {
|
|
|
|
out_col->type = PCOL_VOID;
|
|
|
|
out_col->fatal = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col) {
|
2017-12-28 10:05:54 +01:00
|
|
|
switch(col->type) {
|
2018-08-11 21:13:48 +02:00
|
|
|
case PCOL_NONE:
|
|
|
|
case PCOL_VOID:
|
2017-12-28 10:05:54 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case PCOL_PLAYER_GRAZE: {
|
2018-01-06 10:44:27 +01:00
|
|
|
if(p->flags & PFLAG_GRAZESPAM) {
|
2019-01-04 23:59:39 +01:00
|
|
|
player_graze(ENT_CAST(col->entity, Player), col->location, 10, 2, &p->color);
|
2018-01-06 10:44:27 +01:00
|
|
|
} else {
|
2019-01-04 23:59:39 +01:00
|
|
|
player_graze(ENT_CAST(col->entity, Player), col->location, 10 + 10 * p->graze_counter, 3 + p->graze_counter, &p->color);
|
2018-01-06 10:44:27 +01:00
|
|
|
}
|
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
p->graze_counter++;
|
2019-02-22 00:56:03 +01:00
|
|
|
p->graze_cooldown = global.frames + 12;
|
2018-05-13 15:08:58 +02:00
|
|
|
p->graze_counter_reset_timer = global.frames;
|
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
case PCOL_ENTITY: {
|
2018-08-11 21:13:48 +02:00
|
|
|
ent_damage(col->entity, &col->damage);
|
2017-12-28 10:05:54 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-08-11 21:13:48 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE;
|
2010-10-17 10:39:07 +02:00
|
|
|
}
|
2017-12-28 10:05:54 +01:00
|
|
|
|
|
|
|
if(col->fatal) {
|
2019-01-26 02:54:57 +01:00
|
|
|
delete_projectile(projlist, p);
|
2017-12-28 10:05:54 +01:00
|
|
|
}
|
2010-10-17 09:27:44 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_projectile(EntityInterface *ent) {
|
|
|
|
Projectile *proj = ENT_CAST(ent, Projectile);
|
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
r_blend(proj->blend);
|
2018-04-12 16:08:48 +02:00
|
|
|
r_shader_ptr(proj->shader);
|
2018-01-11 08:33:51 +01:00
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
#ifdef PROJ_DEBUG
|
|
|
|
static Projectile prev_state;
|
|
|
|
memcpy(&prev_state, proj, sizeof(Projectile));
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
proj->draw_rule(proj, global.frames - proj->birthtime);
|
2017-10-24 04:57:14 +02:00
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
if(memcmp(&prev_state, proj, sizeof(Projectile))) {
|
|
|
|
set_debug_info(&proj->debug);
|
|
|
|
log_fatal("Projectile modified its state in draw rule");
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
proj->draw_rule(proj, global.frames - proj->birthtime);
|
2017-11-10 21:49:16 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool projectile_in_viewport(Projectile *proj) {
|
2017-11-01 05:47:36 +01:00
|
|
|
double w, h;
|
2017-11-10 21:49:16 +01:00
|
|
|
int e = proj->max_viewport_dist;
|
2017-11-23 16:27:41 +01:00
|
|
|
projectile_size(proj, &w, &h);
|
2017-11-01 05:47:36 +01:00
|
|
|
|
|
|
|
return !(creal(proj->pos) + w/2 + e < 0 || creal(proj->pos) - w/2 - e > VIEWPORT_W
|
|
|
|
|| cimag(proj->pos) + h/2 + e < 0 || cimag(proj->pos) - h/2 - e > VIEWPORT_H);
|
2017-10-24 04:57:14 +02:00
|
|
|
}
|
|
|
|
|
2018-01-20 11:18:11 +01:00
|
|
|
Projectile* spawn_projectile_collision_effect(Projectile *proj) {
|
|
|
|
if(proj->flags & PFLAG_NOCOLLISIONEFFECT) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
2018-02-06 07:19:25 +01:00
|
|
|
if(proj->sprite == NULL) {
|
2018-01-06 10:24:46 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PARTICLE(
|
2018-02-06 07:19:25 +01:00
|
|
|
.sprite_ptr = proj->sprite,
|
2018-05-02 06:46:48 +02:00
|
|
|
.size = proj->size,
|
2018-01-06 10:24:46 +01:00
|
|
|
.pos = proj->pos,
|
2018-07-23 19:07:59 +02:00
|
|
|
.color = &proj->color,
|
2019-01-04 23:59:39 +01:00
|
|
|
.flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
|
2018-04-12 16:08:48 +02:00
|
|
|
.shader_ptr = proj->shader,
|
2018-05-02 06:46:48 +02:00
|
|
|
.rule = linear,
|
2018-01-20 11:18:11 +01:00
|
|
|
.draw_rule = DeathShrink,
|
2018-01-20 11:25:38 +01:00
|
|
|
.angle = proj->angle,
|
2018-05-02 06:46:48 +02:00
|
|
|
.args = { 5*cexp(I*proj->angle) },
|
|
|
|
.timeout = 10,
|
2018-01-06 10:24:46 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
static void really_clear_projectile(ProjectileList *projlist, Projectile *proj) {
|
|
|
|
Projectile *effect = spawn_projectile_clear_effect(proj);
|
|
|
|
Item *clear_item = NULL;
|
2018-08-05 19:58:50 +02:00
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
if(!(proj->flags & PFLAG_NOCLEARBONUS)) {
|
2019-02-22 00:56:03 +01:00
|
|
|
clear_item = create_clear_item(proj->pos, proj->clear_flags);
|
2018-01-06 10:24:46 +01:00
|
|
|
}
|
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
if(clear_item != NULL && effect != NULL) {
|
|
|
|
effect->args[0] = add_ref(clear_item);
|
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
delete_projectile(projlist, proj);
|
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
bool clear_projectile(Projectile *proj, uint flags) {
|
|
|
|
switch(proj->type) {
|
|
|
|
case PlrProj:
|
|
|
|
case Particle:
|
|
|
|
return false;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(flags & CLEAR_HAZARDS_FORCE) && !projectile_is_clearable(proj)) {
|
2019-01-26 02:54:57 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-01-06 10:29:14 +01:00
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
proj->type = DeadProj;
|
2019-02-22 00:56:03 +01:00
|
|
|
proj->clear_flags |= flags;
|
2018-01-06 10:24:46 +01:00
|
|
|
|
2018-01-06 19:23:38 +01:00
|
|
|
return true;
|
2018-01-06 10:24:46 +01:00
|
|
|
}
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
void process_projectiles(ProjectileList *projlist, bool collision) {
|
2017-12-28 10:05:54 +01:00
|
|
|
ProjCollisionResult col = { 0 };
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-05-13 19:03:02 +02:00
|
|
|
char killed = 0;
|
|
|
|
int action;
|
2019-02-22 00:56:03 +01:00
|
|
|
bool stage_cleared = stage_is_cleared();
|
2017-12-28 10:05:54 +01:00
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
for(Projectile *proj = projlist->first, *next; proj; proj = next) {
|
|
|
|
next = proj->next;
|
2018-05-13 15:08:58 +02:00
|
|
|
proj->prevpos = proj->pos;
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
if(stage_cleared) {
|
|
|
|
clear_projectile(proj, CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_FORCE);
|
|
|
|
}
|
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
action = proj_call_rule(proj, global.frames - proj->birthtime);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-13 15:08:58 +02:00
|
|
|
if(proj->graze_counter && proj->graze_counter_reset_timer - global.frames <= -90) {
|
|
|
|
proj->graze_counter--;
|
|
|
|
proj->graze_counter_reset_timer = global.frames;
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(proj->type == DeadProj && killed < 10 && !(proj->clear_flags & CLEAR_HAZARDS_NOW)) {
|
|
|
|
proj->clear_flags |= CLEAR_HAZARDS_NOW;
|
2011-05-13 19:03:02 +02:00
|
|
|
killed++;
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-03-28 17:25:55 +01:00
|
|
|
if(action == ACTION_DESTROY) {
|
|
|
|
memset(&col, 0, sizeof(col));
|
|
|
|
col.fatal = true;
|
|
|
|
} else if(collision) {
|
2017-12-28 10:05:54 +01:00
|
|
|
calc_projectile_collision(proj, &col);
|
|
|
|
|
2017-12-31 10:23:57 +01:00
|
|
|
if(col.fatal && col.type != PCOL_VOID) {
|
2018-01-06 10:24:46 +01:00
|
|
|
spawn_projectile_collision_effect(proj);
|
2017-12-28 10:05:54 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
memset(&col, 0, sizeof(col));
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
if(!projectile_in_viewport(proj)) {
|
|
|
|
col.fatal = true;
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-01-26 02:54:57 +01:00
|
|
|
apply_projectile_collision(projlist, proj, &col);
|
|
|
|
}
|
|
|
|
|
|
|
|
for(Projectile *proj = projlist->first, *next; proj; proj = next) {
|
|
|
|
next = proj->next;
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(proj->type == DeadProj && (proj->clear_flags & CLEAR_HAZARDS_NOW)) {
|
2019-01-26 02:54:57 +01:00
|
|
|
really_clear_projectile(projlist, proj);
|
|
|
|
}
|
2010-10-27 19:51:49 +02:00
|
|
|
}
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-12-28 11:40:40 +01:00
|
|
|
int trace_projectile(Projectile *p, ProjCollisionResult *out_col, ProjCollisionType stopflags, int timeofs) {
|
|
|
|
int t;
|
|
|
|
|
|
|
|
for(t = timeofs; p; ++t) {
|
2017-11-01 05:47:36 +01:00
|
|
|
int action = p->rule(p, t);
|
2017-12-28 10:05:54 +01:00
|
|
|
calc_projectile_collision(p, out_col);
|
2017-11-01 05:47:36 +01:00
|
|
|
|
2017-12-28 10:05:54 +01:00
|
|
|
if(out_col->type & stopflags || action == ACTION_DESTROY) {
|
2017-12-28 11:40:40 +01:00
|
|
|
return t;
|
2017-11-01 05:47:36 +01:00
|
|
|
}
|
|
|
|
}
|
2017-12-28 11:40:40 +01:00
|
|
|
|
|
|
|
return t;
|
2017-11-01 05:47:36 +01:00
|
|
|
}
|
2011-05-21 15:02:19 +02:00
|
|
|
|
2018-01-06 09:54:13 +01:00
|
|
|
bool projectile_is_clearable(Projectile *p) {
|
|
|
|
if(p->type == DeadProj) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-28 17:40:04 +01:00
|
|
|
if(p->type == EnemyProj) {
|
2018-01-06 09:54:13 +01:00
|
|
|
return (p->flags & PFLAG_NOCLEAR) != PFLAG_NOCLEAR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-07-07 14:34:30 +02:00
|
|
|
int linear(Projectile *p, int t) { // sure is physics in here; a[0]: velocity
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_DEATH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2011-05-06 17:09:43 +02:00
|
|
|
p->angle = carg(p->args[0]);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_BIRTH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2018-05-16 03:39:33 +02:00
|
|
|
p->pos = p->pos0 + p->args[0]*t;
|
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
return ACTION_NONE;
|
2011-05-13 19:03:02 +02:00
|
|
|
}
|
|
|
|
|
2011-06-27 13:36:35 +02:00
|
|
|
int accelerated(Projectile *p, int t) {
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_DEATH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2012-07-31 14:34:20 +02:00
|
|
|
p->angle = carg(p->args[0]);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_BIRTH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2012-07-31 14:34:20 +02:00
|
|
|
p->pos += p->args[0];
|
|
|
|
p->args[0] += p->args[1];
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-06-27 13:36:35 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-06-28 19:23:02 +02:00
|
|
|
int asymptotic(Projectile *p, int t) { // v = a[0]*(a[1] + 1); a[1] -> 0
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_DEATH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2011-08-27 15:56:02 +02:00
|
|
|
p->angle = carg(p->args[0]);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
if(t == EVENT_BIRTH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
2011-06-28 19:23:02 +02:00
|
|
|
p->args[1] *= 0.8;
|
|
|
|
p->pos += p->args[0]*(p->args[1] + 1);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-06-28 19:23:02 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static inline bool proj_uses_spawning_effect(Projectile *proj, ProjFlags effect_flag) {
|
2019-03-28 17:40:04 +01:00
|
|
|
if(proj->type != EnemyProj) {
|
2019-01-04 23:59:39 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-28 17:40:04 +01:00
|
|
|
if((proj->flags & effect_flag) == effect_flag) {
|
2019-01-04 23:59:39 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static float proj_spawn_effect_factor(Projectile *proj, int t) {
|
|
|
|
static const int maxt = 16;
|
|
|
|
|
|
|
|
if(t >= maxt || !proj_uses_spawning_effect(proj, PFLAG_NOSPAWNFADE)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t / (float)maxt;
|
|
|
|
}
|
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
static inline void apply_common_transforms(Projectile *proj, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_translate(creal(proj->pos), cimag(proj->pos), 0);
|
|
|
|
r_mat_rotate_deg(proj->angle*180/M_PI+90, 0, 0, 1);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
/*
|
|
|
|
float s = 0.75 + 0.25 * proj_spawn_effect_factor(proj, t);
|
|
|
|
|
|
|
|
if(s != 1) {
|
|
|
|
r_mat_scale(s, s, 1);
|
2012-04-06 15:54:30 +02:00
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
*/
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static void bullet_highlight_draw(Projectile *p, int t) {
|
|
|
|
float timefactor = t / p->timeout;
|
|
|
|
float sx = creal(p->args[0]);
|
|
|
|
float sy = cimag(p->args[0]);
|
|
|
|
|
|
|
|
float opacity = pow(1 - timefactor, 2);
|
|
|
|
opacity = min(1, 1.5 * opacity) * min(1, timefactor * 10);
|
|
|
|
|
|
|
|
r_mat_mode(MM_TEXTURE);
|
|
|
|
r_mat_push();
|
|
|
|
r_mat_translate(0.5, 0.5, 0);
|
|
|
|
r_mat_rotate(p->args[1], 0, 0, 1);
|
|
|
|
r_mat_translate(-0.5, -0.5, 0);
|
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = p->sprite,
|
|
|
|
.shader_ptr = p->shader,
|
|
|
|
.shader_params = &(ShaderCustomParams) {{ 1 - opacity }},
|
|
|
|
.color = &p->color,
|
|
|
|
.scale = { .x = sx, .y = sy },
|
|
|
|
.rotation.angle = p->angle + M_PI * 0.5,
|
|
|
|
.pos = { creal(p->pos), cimag(p->pos) }
|
|
|
|
});
|
|
|
|
|
|
|
|
r_mat_pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
static Projectile* spawn_projectile_highlight_effect_internal(Projectile *p, bool flare) {
|
|
|
|
if(!p->sprite) {
|
|
|
|
return NULL;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-04-20 18:37:46 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
Color clr = p->color;
|
|
|
|
clr.r = fmax(0.1, clr.r);
|
|
|
|
clr.g = fmax(0.1, clr.g);
|
|
|
|
clr.b = fmax(0.1, clr.b);
|
|
|
|
float h, s, l;
|
|
|
|
color_get_hsl(&clr, &h, &s, &l);
|
|
|
|
s = s > 0 ? 0.75 : 0;
|
|
|
|
l = 0.5;
|
|
|
|
color_hsla(&clr, h, s, l, 0.05);
|
|
|
|
|
|
|
|
float sx, sy;
|
|
|
|
|
|
|
|
if(flare) {
|
|
|
|
sx = pow(p->sprite->w, 0.7);
|
|
|
|
sy = pow(p->sprite->h, 0.7);
|
|
|
|
|
|
|
|
PARTICLE(
|
|
|
|
.sprite = "stardust_green",
|
|
|
|
.shader = "sprite_bullet",
|
|
|
|
.size = p->size * 4.5,
|
|
|
|
.layer = LAYER_PARTICLE_HIGH | 0x40,
|
|
|
|
.draw_rule = ScaleSquaredFade,
|
|
|
|
.args = { 0, 0, (0 + 2*I) * 0.1 * fmax(sx, sy) * (1 - 0.2 * frand()) },
|
|
|
|
.angle = frand() * M_PI * 2,
|
|
|
|
.pos = p->pos + frand() * 8 * cexp(I*M_PI*2*frand()),
|
|
|
|
.flags = PFLAG_NOREFLECT,
|
|
|
|
.timeout = 24 + 2 * nfrand(),
|
|
|
|
.color = &clr,
|
|
|
|
);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-04-20 18:37:46 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
sx = pow((1.5 * p->sprite->w + 0.5 * p->sprite->h) * 0.5, 0.65);
|
|
|
|
sy = pow((1.5 * p->sprite->h + 0.5 * p->sprite->w) * 0.5, 0.65);
|
|
|
|
clr.a = 0.2;
|
|
|
|
|
|
|
|
return PARTICLE(
|
|
|
|
.sprite = "bullet_cloud",
|
|
|
|
.size = p->size * 4.5,
|
|
|
|
.shader = "sprite_bullet",
|
|
|
|
.layer = LAYER_PARTICLE_HIGH | 0x80,
|
|
|
|
.draw_rule = bullet_highlight_draw,
|
|
|
|
.args = { 0.125 * (sx + I * sy), frand() * M_PI * 2 },
|
|
|
|
.angle = p->angle,
|
|
|
|
.pos = p->pos + frand() * 5 * cexp(I*M_PI*2*frand()),
|
|
|
|
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
|
|
|
|
.timeout = 32 + 2 * nfrand(),
|
|
|
|
.color = &clr,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Projectile* spawn_projectile_highlight_effect(Projectile *p) {
|
|
|
|
return spawn_projectile_highlight_effect_internal(p, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Projectile* spawn_bullet_spawning_effect(Projectile *p) {
|
|
|
|
if(proj_uses_spawning_effect(p, PFLAG_NOSPAWNFLARE)) {
|
|
|
|
return spawn_projectile_highlight_effect(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void projectile_clear_effect_draw(Projectile *p, int t) {
|
|
|
|
r_mat_push();
|
|
|
|
apply_common_transforms(p, t);
|
|
|
|
|
|
|
|
float timefactor = t / p->timeout;
|
|
|
|
float plrfactor = clamp(1 - (cabs(p->pos - global.plr.pos) - 64) / 128, 0, 1);
|
|
|
|
plrfactor *= clamp(timefactor * 10, 0, 1);
|
|
|
|
float f = 1 - (1 - timefactor) * (1 - plrfactor);
|
|
|
|
|
|
|
|
Sprite spr = *p->sprite;
|
|
|
|
Sprite *ispr = get_sprite("item/bullet_point");
|
|
|
|
spr.w = f * ispr->w + (1 - f) * spr.w;
|
|
|
|
spr.h = f * ispr->h + (1 - f) * spr.h;
|
|
|
|
|
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = &spr,
|
|
|
|
.color = RGBA(p->color.r, p->color.g, p->color.b, p->color.a * (1 - 0)),
|
|
|
|
.shader_params = &(ShaderCustomParams){{ f }},
|
|
|
|
// .scale.both = 1 + 0.5 * sqrt(tf),
|
|
|
|
});
|
|
|
|
|
|
|
|
r_mat_pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int projectile_clear_effect_logic(Projectile *p, int t) {
|
|
|
|
if(t == EVENT_DEATH) {
|
|
|
|
free_ref(p->args[0]);
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(t == EVENT_BIRTH) {
|
|
|
|
return ACTION_ACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((int)p->args[0] < 0) {
|
|
|
|
return ACTION_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
Item *i = REF(p->args[0]);
|
|
|
|
|
|
|
|
if(i != NULL) {
|
|
|
|
p->pos = i->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ACTION_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
Projectile* spawn_projectile_clear_effect(Projectile *proj) {
|
2019-02-22 00:56:03 +01:00
|
|
|
if((proj->flags & PFLAG_NOCLEAREFFECT) || proj->sprite == NULL) {
|
2019-01-04 23:59:39 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// spawn_projectile_highlight_effect_internal(proj, false);
|
|
|
|
|
|
|
|
ShaderProgram *shader = proj->shader;
|
|
|
|
uint32_t layer = LAYER_PARTICLE_BULLET_CLEAR;
|
|
|
|
|
|
|
|
if(proj->shader == defaults_proj.shader_ptr) {
|
|
|
|
// HACK
|
|
|
|
shader = r_shader_get("sprite_bullet_dead");
|
|
|
|
layer |= 0x1;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
return PARTICLE(
|
|
|
|
.sprite_ptr = proj->sprite,
|
|
|
|
.size = proj->size,
|
|
|
|
.pos = proj->pos,
|
|
|
|
.color = &proj->color,
|
|
|
|
.flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
|
|
|
|
.shader_ptr = shader,
|
|
|
|
.rule = projectile_clear_effect_logic,
|
|
|
|
.draw_rule = projectile_clear_effect_draw,
|
|
|
|
.angle = proj->angle,
|
|
|
|
.timeout = 24,
|
|
|
|
.args = { -1 },
|
|
|
|
.layer = layer,
|
|
|
|
);
|
2012-07-18 23:12:31 +02:00
|
|
|
}
|
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
void ProjDrawCore(Projectile *proj, const Color *c) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = proj->sprite,
|
|
|
|
.color = c,
|
2018-07-23 19:07:59 +02:00
|
|
|
.shader_params = &proj->shader_params,
|
2018-04-12 16:08:48 +02:00
|
|
|
});
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-11-10 21:49:16 +01:00
|
|
|
void ProjDraw(Projectile *proj, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(proj, t);
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
float eff = proj_spawn_effect_factor(proj, t);
|
|
|
|
|
|
|
|
/*
|
|
|
|
if(eff < 1 && proj->color.a > 0) {
|
|
|
|
Color c = proj->color;
|
|
|
|
c.a *= sqrt(eff);
|
|
|
|
ProjDrawCore(proj, &c);
|
|
|
|
} else {
|
|
|
|
ProjDrawCore(proj, &proj->color);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
if(eff < 1) {
|
|
|
|
float o = proj->shader_params.vector[0];
|
|
|
|
float a = min(1, eff * 2);
|
|
|
|
proj->shader_params.vector[0] = 1 - (1 - o) * a;
|
|
|
|
Color c = proj->color;
|
|
|
|
c.a *= eff;//sqrt(eff);
|
|
|
|
ProjDrawCore(proj, &c);
|
|
|
|
proj->shader_params.vector[0] = o;
|
|
|
|
} else {
|
|
|
|
ProjDrawCore(proj, &proj->color);
|
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2011-06-29 17:01:03 +02:00
|
|
|
}
|
2011-11-02 20:00:14 +01:00
|
|
|
|
2017-10-18 01:53:42 +02:00
|
|
|
void ProjNoDraw(Projectile *proj, int t) {
|
|
|
|
}
|
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
void Blast(Projectile *p, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
|
|
|
r_mat_translate(creal(p->pos), cimag(p->pos), 0);
|
|
|
|
r_mat_rotate_deg(creal(p->args[1]), cimag(p->args[1]), creal(p->args[2]), cimag(p->args[2]));
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
if(t != p->timeout && p->timeout != 0) {
|
|
|
|
r_mat_scale(t/(double)p->timeout, t/(double)p->timeout, 1);
|
|
|
|
}
|
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
float fade = 1.0 - t / (double)p->timeout;
|
|
|
|
r_color(RGBA_MUL_ALPHA(0.3, 0.6, 1.0, fade));
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
draw_sprite_batched_p(0,0,p->sprite);
|
|
|
|
r_mat_scale(0.5+creal(p->args[2]),0.5+creal(p->args[2]),1);
|
2018-07-23 19:07:59 +02:00
|
|
|
r_color4(0.3 * fade, 0.6 * fade, 1.0 * fade, 0);
|
2018-04-12 16:08:48 +02:00
|
|
|
draw_sprite_batched_p(0,0,p->sprite);
|
|
|
|
r_mat_pop();
|
2011-06-28 19:23:02 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
|
|
|
void Shrink(Projectile *p, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(p, t);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
float s = 2.0-t/(double)p->timeout*2;
|
2017-11-10 21:49:16 +01:00
|
|
|
if(s != 1) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_scale(s, s, 1);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, &p->color);
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2011-05-21 15:02:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void DeathShrink(Projectile *p, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(p, t);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
float s = 2.0-t/(double)p->timeout*2;
|
2017-11-10 21:49:16 +01:00
|
|
|
if(s != 1) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_scale(s, 1, 1);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, &p->color);
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2011-05-13 19:03:02 +02:00
|
|
|
}
|
|
|
|
|
2011-06-28 19:23:02 +02:00
|
|
|
void GrowFade(Projectile *p, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(p, t);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
set_debug_info(&p->debug);
|
|
|
|
assert(p->timeout != 0);
|
|
|
|
|
|
|
|
float s = t/(double)p->timeout*(1 + (creal(p->args[2])? p->args[2] : p->args[1]));
|
2017-11-10 21:49:16 +01:00
|
|
|
if(s != 1) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_scale(s, s, 1);
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), 1 - t/(double)p->timeout));
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2017-02-10 23:05:22 +01:00
|
|
|
}
|
2011-06-28 19:23:02 +02:00
|
|
|
|
2011-05-13 19:03:02 +02:00
|
|
|
void Fade(Projectile *p, int t) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(p, t);
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), 1 - t/(double)p->timeout));
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2011-05-13 19:03:02 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
static void ScaleFadeImpl(Projectile *p, int t, int fade_exponent) {
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_push();
|
2017-11-10 21:49:16 +01:00
|
|
|
apply_common_transforms(p, t);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2018-01-08 17:56:46 +01:00
|
|
|
double scale_min = creal(p->args[2]);
|
|
|
|
double scale_max = cimag(p->args[2]);
|
2018-05-02 06:46:48 +02:00
|
|
|
double timefactor = t / (double)p->timeout;
|
2018-01-08 17:56:46 +01:00
|
|
|
double scale = scale_min * (1 - timefactor) + scale_max * timefactor;
|
2019-01-04 23:59:39 +01:00
|
|
|
double alpha = pow(fabs(1 - timefactor), fade_exponent);
|
2018-01-08 17:56:46 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_scale(scale, scale, 1);
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), alpha));
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2012-08-01 17:32:11 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
void ScaleFade(Projectile *p, int t) {
|
|
|
|
ScaleFadeImpl(p, t, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScaleSquaredFade(Projectile *p, int t) {
|
|
|
|
ScaleFadeImpl(p, t, 2);
|
|
|
|
}
|
|
|
|
|
2011-08-28 10:57:45 +02:00
|
|
|
void Petal(Projectile *p, int t) {
|
|
|
|
float x = creal(p->args[2]);
|
|
|
|
float y = cimag(p->args[2]);
|
|
|
|
float z = creal(p->args[3]);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-03-24 14:53:57 +01:00
|
|
|
float r = sqrt(x*x+y*y+z*z);
|
|
|
|
x /= r; y /= r; z /= r;
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_disable(RCAP_CULL_FACE);
|
|
|
|
r_mat_push();
|
|
|
|
r_mat_translate(creal(p->pos), cimag(p->pos),0);
|
|
|
|
r_mat_rotate_deg(t*4.0 + cimag(p->args[3]), x, y, z);
|
2018-07-23 19:07:59 +02:00
|
|
|
ProjDrawCore(p, &p->color);
|
2018-04-12 16:08:48 +02:00
|
|
|
r_mat_pop();
|
2011-08-28 10:57:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void petal_explosion(int n, complex pos) {
|
2017-11-21 10:26:11 +01:00
|
|
|
for(int i = 0; i < n; i++) {
|
2017-03-24 14:53:57 +01:00
|
|
|
tsrand_fill(6);
|
|
|
|
float t = frand();
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
PARTICLE(
|
|
|
|
.sprite = "petal",
|
|
|
|
.pos = pos,
|
2019-01-04 23:59:39 +01:00
|
|
|
.color = RGBA(sin(5*t) * t, cos(5*t) * t, 0.5 * t, 0),
|
2018-05-02 06:46:48 +02:00
|
|
|
.rule = asymptotic,
|
2017-11-10 21:49:16 +01:00
|
|
|
.draw_rule = Petal,
|
|
|
|
.args = {
|
|
|
|
(3+5*afrand(2))*cexp(I*M_PI*2*afrand(3)),
|
|
|
|
5,
|
2017-11-21 10:26:11 +01:00
|
|
|
afrand(4) + afrand(5)*I,
|
|
|
|
afrand(1) + 360.0*I*afrand(0),
|
2017-11-10 21:49:16 +01:00
|
|
|
},
|
2018-04-12 16:08:48 +02:00
|
|
|
// TODO: maybe remove this noreflect, there shouldn't be a cull mode mess anymore
|
2019-01-04 23:59:39 +01:00
|
|
|
.flags = PFLAG_NOREFLECT | (n % 2 ? 0 : PFLAG_REQUIREDPARTICLE),
|
|
|
|
.layer = LAYER_PARTICLE_PETAL,
|
2017-11-10 21:49:16 +01:00
|
|
|
);
|
2011-08-28 10:57:45 +02:00
|
|
|
}
|
2012-07-18 12:33:37 +02:00
|
|
|
}
|
2017-03-11 04:41:57 +01:00
|
|
|
|
|
|
|
void projectiles_preload(void) {
|
2019-01-04 23:59:39 +01:00
|
|
|
const char *shaders[] = {
|
|
|
|
// This array defines a shader-based fallback draw order
|
|
|
|
"sprite_silhouette",
|
2018-04-12 16:08:48 +02:00
|
|
|
defaults_proj.shader,
|
|
|
|
defaults_part.shader,
|
2019-01-04 23:59:39 +01:00
|
|
|
"sprite_bullet_dead",
|
|
|
|
};
|
2018-04-12 16:08:48 +02:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
const uint num_shaders = sizeof(shaders)/sizeof(*shaders);
|
|
|
|
|
|
|
|
for(uint i = 0; i < num_shaders; ++i) {
|
|
|
|
preload_resource(RES_SHADER_PROGRAM, shaders[i], RESF_PERMANENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Why is this here?
|
2017-03-11 04:41:57 +01:00
|
|
|
preload_resources(RES_TEXTURE, RESF_PERMANENT,
|
2018-02-06 07:19:25 +01:00
|
|
|
"part/lasercurve",
|
|
|
|
NULL);
|
2017-03-20 07:38:07 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
// TODO: Maybe split this up into stage-specific preloads too?
|
|
|
|
// some of these are ubiquitous, but some only appear in very specific parts.
|
2018-02-06 07:19:25 +01:00
|
|
|
preload_resources(RES_SPRITE, RESF_PERMANENT,
|
2017-03-11 04:41:57 +01:00
|
|
|
"part/blast",
|
2019-01-04 23:59:39 +01:00
|
|
|
"part/bullet_cloud",
|
2017-03-11 04:41:57 +01:00
|
|
|
"part/flare",
|
2019-01-04 23:59:39 +01:00
|
|
|
"part/graze",
|
2017-03-20 07:38:07 +01:00
|
|
|
"part/lightning0",
|
|
|
|
"part/lightning1",
|
|
|
|
"part/lightningball",
|
2019-01-04 23:59:39 +01:00
|
|
|
"part/petal",
|
|
|
|
"part/smoke",
|
2018-02-06 07:19:25 +01:00
|
|
|
"part/smoothdot",
|
2019-01-04 23:59:39 +01:00
|
|
|
"part/stain",
|
|
|
|
"part/stardust_green",
|
2017-03-11 04:41:57 +01:00
|
|
|
NULL);
|
|
|
|
|
2017-09-30 20:11:10 +02:00
|
|
|
preload_resources(RES_SFX, RESF_PERMANENT,
|
|
|
|
"shot1",
|
2017-10-29 04:43:32 +01:00
|
|
|
"shot2",
|
2018-08-14 02:56:16 +02:00
|
|
|
"shot3",
|
2017-09-30 20:11:10 +02:00
|
|
|
"shot1_loop",
|
2017-10-22 21:11:15 +02:00
|
|
|
"shot_special1",
|
|
|
|
"redirect",
|
2017-09-30 20:11:10 +02:00
|
|
|
NULL);
|
2018-04-12 16:08:48 +02:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
#define PP(name) (_pp_##name).preload(&_pp_##name);
|
|
|
|
#include "projectile_prototypes/all.inc.h"
|
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
ht_create(&shader_sublayer_map);
|
|
|
|
|
|
|
|
for(uint i = 0; i < num_shaders; ++i) {
|
|
|
|
ht_set(&shader_sublayer_map, r_shader_get(shaders[i]), i + 1);
|
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
defaults_proj.shader_ptr = r_shader_get(defaults_proj.shader);
|
|
|
|
defaults_part.shader_ptr = r_shader_get(defaults_part.shader);
|
2017-03-11 04:41:57 +01:00
|
|
|
}
|
2019-01-04 23:59:39 +01:00
|
|
|
|
|
|
|
void projectiles_free(void) {
|
|
|
|
ht_destroy(&shader_sublayer_map);
|
|
|
|
}
|