/* * This software is licensed under the terms of the MIT License. * See COPYING for further information. * --- * Copyright (c) 2011-2024, Lukas Weber . * Copyright (c) 2012-2024, Andrei Alexeyev . */ #pragma once #include "taisei.h" #include "color.h" #include "coroutine/coevent.h" #include "entity.h" #include "move.h" #include "renderer/api.h" #include "resource/resource.h" #include "resource/shader_program.h" #include "resource/sprite.h" #include "util.h" #if defined(DEBUG) && !defined(RNG_API_CHECK) #define PROJ_DEBUG #endif #ifdef PROJ_DEBUG #define IF_PROJ_DEBUG(...) __VA_ARGS__ #else #define IF_PROJ_DEBUG(...) #endif enum { PROJ_DRAWRULE_NUMARGS = 4 }; typedef LIST_ANCHOR(Projectile) ProjectileList; typedef LIST_INTERFACE(Projectile) ProjectileListInterface; typedef int (*ProjRule)(Projectile *p, int t); typedef bool (*ProjPredicate)(Projectile *p); typedef union { float as_float[2]; cmplxf as_cmplx; void *as_ptr; } ProjDrawRuleArgs[PROJ_DRAWRULE_NUMARGS]; typedef struct ProjDrawRule { void (*func)(Projectile *p, int t, ProjDrawRuleArgs args); ProjDrawRuleArgs args; } ProjDrawRule; typedef enum { PROJ_INVALID, PROJ_ENEMY, // hazard, collides with player PROJ_DEAD, // no collision, will be cleared shortly PROJ_PARTICLE, // no collision, not a hazard PROJ_PLAYER, // collides with enemies and bosses } ProjType; typedef enum ProjFlags { // NOTE: Many of these flags are only meaningful for a specific type(s) of projectile. // The relevant ProjType enum(s) are specified in [square brackets], or [ALL] if all apply. PFLAG_NOSPAWNFLARE = (1 << 0), // [PROJ_ENEMY] Don't spawn the standard particle effect when spawned. PFLAG_NOSPAWNFADE = (1 << 1), // [PROJ_ENEMY] Don't fade in when spawned; assume 100% opacity immediately. PFLAG_NOGRAZE = (1 << 3), // [PROJ_ENEMY] Can't be grazed. PFLAG_NOCLEAR = (1 << 4), // [PROJ_ENEMY] Can't be cleared (unless forced). PFLAG_NOCLEAREFFECT = (1 << 5), // [PROJ_ENEMY, PROJ_DEAD] Don't spawn the standard particle effect when cleared. PFLAG_NOCOLLISIONEFFECT = (1 << 6), // [PROJ_ENEMY, PROJ_DEAD, PROJ_PLAYER] Don't spawn the standard particle effect on collision. PFLAG_NOCLEARBONUS = (1 << 7), // [PROJ_ENEMY, PROJ_DEAD] Don't spawn any bonus items on clear. PFLAG_NOMOVE = (1 << 8), // [ALL] Don't call move_update for this projectile. PFLAG_NOREFLECT = (1 << 9), // [ALL] Don't render a "reflection" of this on the Stage 1 water surface. PFLAG_REQUIREDPARTICLE = (1 << 10), // [PROJ_PARTICLE] Visible at "minimal" particles setting. PFLAG_PLRSPECIALPARTICLE = (1 << 11), // [PROJ_PARTICLE] Apply Power Surge effect to this particle, as if it was a PROJ_PLAYER. PFLAG_NOCOLLISION = (1 << 12), // [PROJ_ENEMY, PROJ_PLAYER] Disable collision detection. PFLAG_INTERNAL_DEAD = (1 << 13), // [ALL] Delete as soon as processed. (internal flag, do not use) PFLAG_MANUALANGLE = (1 << 14), // [ALL] Don't automatically update the angle. PFLAG_NOAUTOREMOVE = (1 << 15), // [ALL] Don't automatically remove when outside viewport. PFLAG_INDESTRUCTIBLE = (1 << 16), // [PROJ_ENEMY, PROJ_PLAYER] Projectile doesn't get destroyed on collision. 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; DEFINE_ENTITY_TYPE(Projectile, { cmplx pos; cmplx pos0; cmplx prevpos; // used to lerp trajectory for collision detection; set this to pos if you intend to "teleport" the projectile in the rule! cmplx size; // affects out-of-viewport culling and grazing cmplx collision_size; // affects collision with player (TODO: make this work for player projectiles too?) ShaderProgram *shader; Sprite *sprite; ProjPrototype *proto; ProjDrawRule draw_rule; /* * 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; Color color; BlendMode blend; int birthtime; float damage; float angle; float angle_delta; cmplx _cached_delta_pos; real _cached_angle; ProjType type; DamageType damage_type; int max_viewport_dist; ProjFlags flags; uint clear_flags; cmplxf scale; float opacity; // XXX: this is in frames of course, but needs to be float // to avoid subtle truncation and integer division gotchas. float timeout; int graze_counter_reset_timer; int graze_cooldown; short graze_counter; IF_PROJ_DEBUG( DebugInfo debug; ) }); typedef struct ProjArgs { ProjPrototype *proto; const Color *color; const char *sprite; Sprite *sprite_ptr; const char *shader; ShaderProgram *shader_ptr; ProjectileList *dest; ProjDrawRule draw_rule; cmplx pos; cmplx size; // affects default draw order, out-of-viewport culling, and grazing cmplx collision_size; // affects collision with player (TODO: make this work for player projectiles too?) MoveParams move; ProjType type; ProjFlags flags; BlendMode blend; float angle; float angle_delta; float damage; DamageType damage_type; int max_viewport_dist; drawlayer_t layer; cmplxf scale; float opacity; // XXX: this is in frames of course, but needs to be float // to avoid subtle truncation and integer division gotchas. float timeout; } attr_designated_init ProjArgs; struct ProjPrototype { void (*preload)(ProjPrototype *proto, ResourceGroup *rg); void (*process_args)(ProjPrototype *proto, ProjArgs *args); void (*reset)(ProjPrototype *proto); void (*init_projectile)(ProjPrototype *proto, Projectile *p); void (*deinit_projectile)(ProjPrototype *proto, Projectile *p); void *private; }; #define PP(name) \ extern ProjPrototype _pp_##name; \ extern ProjPrototype *pp_##name; \ #include "projectile_prototypes/all.inc.h" Projectile *create_projectile(ProjArgs *args) attr_nonnull_all attr_returns_nonnull attr_returns_max_aligned; Projectile *create_particle(ProjArgs *args) attr_nonnull_all attr_returns_nonnull attr_returns_max_aligned; #ifdef PROJ_DEBUG Projectile *_proj_attach_dbginfo(Projectile *p, DebugInfo *dbg, const char *callsite_str) attr_nonnull_all; #define _PROJ_WRAP_SPAWN(p) _proj_attach_dbginfo((p), _DEBUG_INFO_PTR_, #p) #else #define _PROJ_WRAP_SPAWN(p) (p) #endif #define _PROJ_GENERIC_SPAWN(constructor, ...) _PROJ_WRAP_SPAWN((constructor)((&(ProjArgs) { __VA_ARGS__ }))) #define PROJECTILE(...) _PROJ_GENERIC_SPAWN(create_projectile, __VA_ARGS__) #define PARTICLE(...) _PROJ_GENERIC_SPAWN(create_particle, __VA_ARGS__) void delete_projectiles(ProjectileList *projlist) attr_nonnull_all; void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) attr_nonnull_all; void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col) attr_nonnull_all; int trace_projectile(Projectile *p, ProjCollisionResult *out_col, ProjCollisionType stopflags, int timeofs) attr_nonnull_all; bool projectile_in_viewport(Projectile *proj) attr_nonnull_all; void process_projectiles(ProjectileList *projlist, bool collision) attr_hot attr_nonnull_all; bool projectile_is_clearable(Projectile *p) attr_nonnull_all; Projectile *spawn_projectile_collision_effect(Projectile *proj) attr_nonnull_all; Projectile *spawn_projectile_clear_effect(Projectile *proj) attr_nonnull_all; Projectile *spawn_projectile_highlight_effect(Projectile *proj) attr_nonnull_all; void projectile_set_prototype(Projectile *p, ProjPrototype *proto) attr_nonnull(1); void projectile_set_layer(Projectile *p, drawlayer_t layer) attr_nonnull_all; bool clear_projectile(Projectile *proj, uint flags) attr_nonnull_all; void kill_projectile(Projectile *proj) attr_nonnull_all; ProjDrawRule pdraw_basic(void); ProjDrawRule pdraw_timeout_scalefade_exp(cmplxf scale0, cmplxf scale1, float opacity0, float opacity1, float opacity_exp); ProjDrawRule pdraw_timeout_scalefade(cmplxf scale0, cmplxf scale1, float opacity0, float opacity1); ProjDrawRule pdraw_timeout_scale(cmplxf scale0, cmplxf scale1); ProjDrawRule pdraw_timeout_fade(float opacity0, float opacity1); ProjDrawRule pdraw_petal(float rot_angle, vec3 rot_axis); ProjDrawRule pdraw_petal_random(void); ProjDrawRule pdraw_blast(void); void petal_explosion(int n, cmplx pos); void projectiles_preload(ResourceGroup *rg); void projectiles_free(void); cmplx projectile_graze_size(Projectile *p); float projectile_timeout_factor(Projectile *p); int projectile_time(Projectile *p); SpriteParams projectile_sprite_params(Projectile *proj, SpriteParamsBuffer *spbuf);