ReC98/th04/main/bullet/bullet.hpp

351 lines
10 KiB
C++
Raw Normal View History

/// Everything here needs to be kept in sync with the ASM versions in
/// bullet.inc!
#include "th04/sprites/cels.h"
/// Game-specific group and spawn types
/// -----------------------------------
#if GAME == 5
#include "th05/main/bullet/types.h"
#else
#include "th04/main/bullet/types.h"
#endif
/// -----------------------------------
/// States and modes
/// ----------------
static const int BMS_DECAY_FRAMES_PER_CEL = 4;
#define BSS_CLOUD_FRAMES (BULLET_CLOUD_CELS * 4)
#define BMS_DECAY_FRAMES (BULLET_DECAY_CELS * BMS_DECAY_FRAMES_PER_CEL)
// Regular bullets with a given speed below BMS_SLOWDOWN_THRESHOLD are set to
// BMS_SLOWDOWN. This fires them at BMS_SLOWDOWN_BASE_SPEED instead, and then
// gradually slows them down to their given speed over the next
// BMS_SLOWDOWN_FRAMES.
// • In TH04, this is not done for stacks.
// • In TH05, this is not done for any group with BST_NO_SLOWDOWN set.
#define BMS_SLOWDOWN_BASE_SPEED 4.5f
#define BMS_SLOWDOWN_THRESHOLD (BMS_SLOWDOWN_BASE_SPEED - 0.5f)
#define BMS_SLOWDOWN_FRAMES 32
enum bullet_spawn_state_t {
/// Hitbox is active
/// ----------------
BSS_GRAZEABLE = 0,
BSS_GRAZED = 1,
BSS_ACTIVE = 2,
/// ----------------
/// Delay "cloud", no hitbox
/// ------------------------
BSS_CLOUD_BACKWARDS = 3,
BSS_CLOUD_FORWARDS = 4,
BSS_CLOUD_END = (BSS_CLOUD_FORWARDS + BSS_CLOUD_FRAMES),
/// ------------------------
_bullet_spawn_state_t_FORCE_UINT8 = 0xFF
};
enum bullet_move_state_t {
/// Hitbox is active
/// ----------------
// Slows down from BMS_SLOWDOWN_BASE_SPEED to [final_speed]
BMS_SLOWDOWN = 0,
// Special processing according to [special_motion]
BMS_SPECIAL = 1,
// No special processing
BMS_REGULAR = 2,
/// ----------------
/// Decay, no hitbox
/// ----------------
BMS_DECAY = 4,
BMS_DECAY_END = (BMS_DECAY + BMS_DECAY_FRAMES),
/// ----------------
_bullet_move_state_t_FORCE_UINT8 = 0xFF
};
enum bullet_special_motion_t {
// This is so dumb.
_bullet_special_motion_t_offset = static_cast<int8_t>((GAME - 5) * 0x81),
// Slows down the bullet from its initial speed to 0, then aims to the
// player and resets to its initial speed.
// Affected by [bullet_special_motion.turns_max].
BSM_SLOWDOWN_THEN_TURN_AIMED,
// Slows down the bullet from its initial speed to 0, then increases its
// angle by [angle.turn_by] and resets to its initial speed.
// Affected by [bullet_special_motion.turns_max].
BSM_SLOWDOWN_THEN_TURN,
// Accelerates the speed of the bullet by
// [bullet_special_motion.speed_delta] every frame.
BSM_SPEEDUP,
// Slows down the bullet from its initial speed to 0 while turning it
// towards [angle.target]. Upon reaching a speed of 0, the bullet
// continues flying at that exact angle and resets to its initial speed.
BSM_SLOWDOWN_TO_ANGLE,
// Bounces the bullet into the opposite direction if it reaches the
// respective edge of the playfield.
// Affected by [bullet_special_motion.turns_max].
BSM_BOUNCE_LEFT_RIGHT,
BSM_BOUNCE_TOP_BOTTOM,
BSM_BOUNCE_LEFT_RIGHT_TOP_BOTTOM,
BSM_BOUNCE_LEFT_RIGHT_TOP,
// Accelerates the Y velocity of the bullet by
// [bullet_special_motion.speed_delta] every two frames.
BSM_GRAVITY,
#if (GAME == 5)
// Exact linear movement along a line; recalculates the bullet position
// based on the origin, angle, and distance every frame. Useful if
// regular incremental subpixel movement would introduce too much
// quantization noise.
BSM_EXACT_LINEAR,
#endif
BSM_NONE = 0xFF,
};
union bullet_special_angle_t {
unsigned char turn_by;
char target;
int8_t v;
};
/// ----------------
struct bullet_t {
unsigned char flag;
char age;
PlayfieldMotion pos;
unsigned char from_group; // ZUN bloat: Unused
int8_t unused; // ZUN bloat
SubpixelLength8 speed_cur;
unsigned char angle;
bullet_spawn_state_t spawn_state;
bullet_move_state_t move_state;
bullet_special_motion_t special_motion;
SubpixelLength8 speed_final;
union {
unsigned char slowdown_time; // with BMS_SLOWDOWN
unsigned char turns_done; // with BMS_SPECIAL
} u1;
union {
// Difference between [speed_final] and the BMS_SLOWDOWN_BASE_SPEED.
// Always positive for BMS_SLOWDOWN bullets.
SubpixelLength8 slowdown_speed_delta; // with BMS_SLOWDOWN
bullet_special_angle_t angle; // with BMS_SPECIAL
} u2;
int patnum;
#if GAME == 5
// Coordinates for BSM_EXACT_LINEAR
SPPoint origin;
Subpixel distance;
#endif
};
#define PELLET_W 8
#define PELLET_H 8
#define BULLET16_W 16
#define BULLET16_H 16
// Symmetrical around the center point of each bullet, and treated in relation
// to a 1×1 "hitbox" around the player's center point.
static const subpixel_t BULLET_KILLBOX_W = TO_SP(8);
static const subpixel_t BULLET_KILLBOX_H = TO_SP(8);
#ifdef BULLET_D_CELS
static const unsigned char ANGLE_PER_SPRITE = (0x80 / BULLET_D_CELS);
#endif
#if GAME == 5
#define PELLET_COUNT 180
#define BULLET16_COUNT 220
// Returns the sprite ID of a directional or vector bullet sprite that
// represents the given [angle], relative to [patnum_base]. While the function
// is technically not restricted to `main_patnum_t` in TH05 and can also be
// used for a general (angle / BULLET_D_CELS) division, it still assumes
// [patnum_base] to be that type in order to distinguish vector bullets.
extern "C++" unsigned char pascal near bullet_patnum_for_angle(
unsigned int patnum_base, unsigned char angle
);
// Turns every 4th bullet into a point item when zapping bullets.
extern bool bullet_zap_drop_point_items;
#else
#define PELLET_COUNT 240
#define BULLET16_COUNT 200
// Returns the offset for a directional bullet sprite that shows the given
// [angle].
unsigned char pascal near bullet_patnum_for_angle(unsigned char angle);
#endif
#define BULLET_COUNT (PELLET_COUNT + BULLET16_COUNT)
extern bullet_t bullets[BULLET_COUNT];
#define pellets (&bullets[0])
#define bullets16 (&bullets[PELLET_COUNT])
// Global parameters for special motion types.
extern union {
// Number of times a bullet can change its direction during various special
// motion types, before it changes back into a BMS_REGULAR bullet. A value
// of 0 will still allow a single direction change.
unsigned char turns_max;
SubpixelLength8 speed_delta;
} bullet_special_motion;
/// Template
/// --------
struct BulletTemplate {
uint8_t spawn_type;
unsigned char patnum; // TH05: 0 = pellet
PlayfieldPoint origin;
#if GAME == 5
bullet_group_t group;
bullet_special_motion_t special_motion;
unsigned char spread;
unsigned char spread_angle_delta;
unsigned char stack;
SubpixelLength8 stack_speed_delta;
unsigned char angle;
SubpixelLength8 speed;
private:
// MODDERS: Just assign the values regularly, and don't rely on the
// physical layout of the structure.
void set16(unsigned char& val, uint8_t b0, uint8_t b1) {
reinterpret_cast<uint16_t &>(val) = (b0 | (b1 << 8));
}
void set32(
unsigned char& val, uint8_t b0, uint8_t b1, uint32_t b2, uint32_t b3
) {
reinterpret_cast<uint32_t &>(val) = (
b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
);
}
#ifdef RANK_H
void set16_for_rank(
unsigned char& val,
uint8_t b0_e, uint8_t b1_e,
uint8_t b0_n, uint8_t b1_n,
uint8_t b0_h, uint8_t b1_h,
uint8_t b0_l, uint8_t b1_l
) {
reinterpret_cast<uint16_t &>(val) = select_for_rank(
(b0_e | (b1_e << 8)),
(b0_n | (b1_n << 8)),
(b0_h | (b1_h << 8)),
(b0_l | (b1_l << 8))
);
}
#endif
public:
void set_spread(unsigned char count, unsigned char angle_delta) {
set16(spread, count, angle_delta);
}
void set_spread_stack(
unsigned char spread, unsigned char spread_angle_delta,
unsigned char stack, float stack_speed_delta
) {
set32(this->spread,
spread, spread_angle_delta, stack, to_sp8(stack_speed_delta)
);
}
#ifdef RANK_H
void set_stack_for_rank(
unsigned char count_easy, float speed_delta_easy,
unsigned char count_normal, float speed_delta_normal,
unsigned char count_hard, float speed_delta_hard,
unsigned char count_lunatic, float speed_delta_lunatic
) {
set16_for_rank(stack,
count_easy, to_sp8(speed_delta_easy),
count_normal, to_sp8(speed_delta_normal),
count_hard, to_sp8(speed_delta_hard),
count_lunatic, to_sp8(speed_delta_lunatic)
);
}
#endif
#else
PlayfieldPoint velocity;
bullet_group_t group;
unsigned char angle;
SubpixelLength8 speed;
unsigned char count;
bullet_template_delta_t delta;
uint8_t unused_1; // ZUN bloat
bullet_special_motion_t special_motion;
uint8_t unused_2; // ZUN bloat
#endif
};
extern BulletTemplate bullet_template;
// Separate from the template, for some reason?
extern bullet_special_angle_t bullet_template_special_angle;
// Modifies [bullet_template] based on [playperf] and the respective
// difficulty. These don't modify the base [speed]; that is done by the spawn
// functions themselves, unless overridden via the _fixedspeed() variants.
void pascal near bullet_template_tune_easy(void);
void pascal near bullet_template_tune_normal(void);
void pascal near bullet_template_tune_hard(void);
void pascal near bullet_template_tune_lunatic(void);
extern nearfunc_t_near bullet_template_tune;
// The actual functions for spawning bullets based on the [bullet_template].
// Both TH04 and TH05 pointlessly use separate functions for spawning "regular"
// bullets (which receive a move state of BMS_SLOWDOWN or BMS_REGULAR) or
// "special" ones (which are BMS_SPECIAL).
#if (GAME == 5)
void near bullets_add_regular(void);
void near bullets_add_special(void);
// Only used for the revenge bullets fired from Stage 3 Alice's barrier.
void far bullets_add_regular_far(void);
#else
// TH04 additionally uses pointless per-difficulty wrappers around these
// spawn functions that don't actually do anything difficulty-specific.
void pascal near bullets_add_regular_easy(void);
void pascal near bullets_add_regular_normal(void);
void pascal near bullets_add_regular_hard_lunatic(void);
void pascal near bullets_add_special_easy(void);
void pascal near bullets_add_special_normal(void);
void pascal near bullets_add_special_hard_lunatic(void);
// Set to the version of the wrapper functions above that match the
// current difficulty.
extern nearfunc_t_near bullets_add_regular;
extern nearfunc_t_near bullets_add_special;
#endif
// Further wrappers around the spawn functions that bypass base [speed] tuning
// of the resulting bullets based on [playperf], and fire them at a constant
// speed instead.
void near bullets_add_regular_fixedspeed(void);
void near bullets_add_special_fixedspeed(void);
/// --------