projectile: add collision info interception via events

See comment in projectile.h for details
This commit is contained in:
Andrei Alexeyev 2021-02-18 20:06:16 +02:00
parent e3c3a28632
commit f25c88942a
No known key found for this signature in database
GPG key ID: 72D26128040B9690
2 changed files with 60 additions and 29 deletions

View file

@ -324,22 +324,29 @@ Projectile* _proj_attach_dbginfo(Projectile *p, DebugInfo *dbg, const char *call
}
#endif
static void *_delete_projectile(ListAnchor *projlist, List *proj, void *arg) {
Projectile *p = (Projectile*)proj;
static void signal_event_with_collision_result(Projectile *p, CoEvent *evt, ProjCollisionResult *col) {
assert(p->collision == NULL);
p->collision = col;
coevent_signal(evt);
assert(p->collision == col);
p->collision = NULL;
}
static void delete_projectile(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col) {
proj_call_rule(p, EVENT_DEATH);
coevent_signal_once(&p->events.killed);
signal_event_with_collision_result(p, &p->events.killed, col);
COEVENT_CANCEL_ARRAY(p->events);
ent_unregister(&p->ent);
objpool_release(stage_object_pools.projectiles, alist_unlink(projlist, proj));
objpool_release(stage_object_pools.projectiles, alist_unlink(projlist, p));
}
static void *foreach_delete_projectile(ListAnchor *projlist, List *proj, void *arg) {
delete_projectile((ProjectileList*)projlist, (Projectile*)proj, arg);
return NULL;
}
static void delete_projectile(ProjectileList *projlist, Projectile *proj) {
_delete_projectile((ListAnchor*)projlist, (List*)proj, NULL);
}
void delete_projectiles(ProjectileList *projlist) {
alist_foreach(projlist, _delete_projectile, NULL);
alist_foreach(projlist, foreach_delete_projectile, NULL);
}
void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) {
@ -425,6 +432,8 @@ skip_collision:
}
void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col) {
signal_event_with_collision_result(p, &p->events.collision, col);
switch(col->type) {
case PCOL_NONE:
case PCOL_VOID:
@ -450,7 +459,7 @@ void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCol
}
if(col->fatal) {
delete_projectile(projlist, p);
delete_projectile(projlist, p, col);
}
}
@ -484,7 +493,7 @@ bool projectile_in_viewport(Projectile *proj) {
|| cimag(proj->pos) + h/2 + e < 0 || cimag(proj->pos) - h/2 - e > VIEWPORT_H);
}
Projectile* spawn_projectile_collision_effect(Projectile *proj) {
Projectile *spawn_projectile_collision_effect(Projectile *proj) {
if(proj->flags & PFLAG_NOCOLLISIONEFFECT) {
return NULL;
}
@ -517,7 +526,8 @@ static void really_clear_projectile(ProjectileList *projlist, Projectile *proj)
create_clear_item(proj->pos, proj->clear_flags);
}
delete_projectile(projlist, proj);
// TODO: synthetic collision type for clears?
delete_projectile(projlist, proj, NULL);
}
bool clear_projectile(Projectile *proj, uint flags) {
@ -540,9 +550,10 @@ bool clear_projectile(Projectile *proj, uint flags) {
return true;
}
void kill_projectile(Projectile* proj) {
void kill_projectile(Projectile *proj) {
proj->flags |= PFLAG_INTERNAL_DEAD | PFLAG_NOCOLLISION | PFLAG_NOCLEAR;
proj->ent.draw_layer = LAYER_NODRAW;
assert(proj->collision == NULL);
// WARNING: must be done last, an event handler may cancel the task this function is running in!
coevent_signal_once(&proj->events.killed);
}
@ -558,7 +569,7 @@ void process_projectiles(ProjectileList *projlist, bool collision) {
proj->prevpos = proj->pos;
if(proj->flags & PFLAG_INTERNAL_DEAD) {
delete_projectile(projlist, proj);
delete_projectile(projlist, proj, NULL);
continue;
}

View file

@ -86,6 +86,21 @@ typedef enum ProjFlags {
PFLAG_NOSPAWNEFFECTS = PFLAG_NOSPAWNFADE | PFLAG_NOSPAWNFLARE,
} ProjFlags;
typedef enum ProjCollisionType {
PCOL_NONE = 0,
PCOL_ENTITY = (1 << 0),
PCOL_PLAYER_GRAZE = (1 << 1),
PCOL_VOID = (1 << 2),
} ProjCollisionType;
typedef struct ProjCollisionResult {
ProjCollisionType type;
bool fatal; // for the projectile
cmplx location;
DamageInfo damage;
EntityInterface *entity;
} ProjCollisionResult;
// FIXME: prototype stuff awkwardly shoved in this header because of dependency cycles.
typedef struct ProjPrototype ProjPrototype;
@ -101,8 +116,28 @@ DEFINE_ENTITY_TYPE(Projectile, {
ShaderProgram *shader;
Sprite *sprite;
ProjPrototype *proto;
/*
* This field is usually NULL except during handling of "collision" and "killed" events.
*
* "Collision" events are run before the collision result is applied, so they may modify this
* struct to affect the outcome.
*
* "Killed" events are run after the collision has happened, and thus shouldn't write to this
* field.
*
* Note that this may be NULL during "killed" events or cancelled "collision" events.
* In the former case this usually means the projectile was killed manually.
*
* Out of bounds auto-removals are considered collisions with "void" (PCOL_VOID).
*
* The pointer becomes invalid as soon as the event handler yields.
*/
ProjCollisionResult *collision;
MoveParams move;
COEVENTS_ARRAY(
collision,
cleared,
killed
) events;
@ -185,21 +220,6 @@ struct ProjPrototype {
#define PARTICLE_ADDITIVE_SUBLAYER (1 << 3)
typedef enum ProjCollisionType {
PCOL_NONE = 0,
PCOL_ENTITY = (1 << 0),
PCOL_PLAYER_GRAZE = (1 << 1),
PCOL_VOID = (1 << 2),
} ProjCollisionType;
typedef struct ProjCollisionResult {
ProjCollisionType type;
bool fatal; // for the projectile
cmplx location;
DamageInfo damage;
EntityInterface *entity;
} ProjCollisionResult;
Projectile* create_projectile(ProjArgs *args);
Projectile* create_particle(ProjArgs *args);