mirror of https://github.com/nmlgc/ReC98.git
261 lines
8.0 KiB
C++
261 lines
8.0 KiB
C++
/// Constants
|
|
/// ---------
|
|
static const int PELLET_COUNT = 100;
|
|
|
|
static const unsigned char PELLET_SLING_DELTA_ANGLE = +0x08;
|
|
static const unsigned char PELLET_SPIN_DELTA_ANGLE = +0x04;
|
|
#define PELLET_SPIN_CIRCLE_RADIUS to_sp(16.0f)
|
|
#define PELLET_CHASE_TOP_MAX to_sp(PLAYFIELD_BOTTOM - 80.0f)
|
|
/// ---------
|
|
|
|
enum pellet_motion_t {
|
|
// No velocity change during regular pellet updates.
|
|
PM_NORMAL = 0,
|
|
|
|
// Accelerates the Y velocity of the pellet by its [speed] every frame.
|
|
PM_GRAVITY = 1,
|
|
|
|
// Slings the pellet in a (counter-)clockwise motion around its spawn
|
|
// point, with a sling radius of [speed] and a rotational speed of
|
|
// [PELLET_SLING_DELTA_ANGLE]° per frame. Once the pellet completed one
|
|
// full revolution, it is fired towards the player's position on that
|
|
// frame, in a straight line at the same [speed].
|
|
PM_SLING_AIMED = 2,
|
|
|
|
// Lets the pellet bounce off the top of the playfield once, negating its
|
|
// X velocity, and zeroing its Y velocity. The pellet then switches to
|
|
// PM_GRAVITY.
|
|
// Unused in the original game. Should have bounced the bullets off the
|
|
// left and right edge of the playfield as well, but that code doesn't
|
|
// actually work in ZUN's original code.
|
|
PM_BOUNCE_FROM_TOP_THEN_GRAVITY = 3,
|
|
|
|
// Lets the pellet bounce off the top of the playfield once, zeroing its
|
|
// X velocity, and setting its Y velocity to [speed]. The pellet then
|
|
// switches to PM_NORMAL.
|
|
PM_FALL_STRAIGHT_FROM_TOP_THEN_NORMAL = 4,
|
|
|
|
// Spins the pellet on a circle around a [spin_center] point, which moves
|
|
// at [spin_velocity], with [PELLET_SPIN_CIRCLE_RADIUS] and a rotational
|
|
// speed of [PELLET_SPIN_DELTA_ANGLE]° per frame. Since both the center of
|
|
// rotation and the angle are changed every frame, this will result in a
|
|
// swerving motion for individual pellets. Use this type for a ring of
|
|
// pellets to create moving multi-bullet circles, as seen in the Kikuri
|
|
// fight.
|
|
PM_SPIN = 5,
|
|
|
|
// For every frame this pellet is above [PELLET_CHASE_TOP_MAX], its
|
|
// velocity is adjusted by -1, 0, or +1 in both coordinates, depending on
|
|
// the location of the player relative to this pellet.
|
|
PM_CHASE = 6,
|
|
|
|
_pellet_motion_t_FORCE_INT16 = 0x7FFF
|
|
};
|
|
|
|
enum pellet_sling_direction_t {
|
|
PSD_NONE = 0,
|
|
PSD_CLOCKWISE = 1,
|
|
PSD_COUNTERCLOCKWISE = 2,
|
|
|
|
_pellet_sling_direction_t_FORCE_INT16 = 0x7FFF
|
|
};
|
|
|
|
// Types for predefined multi-pellet groups. In TH01, individual bullets of
|
|
// such a group only differ in their angle.
|
|
//
|
|
// The PG_?_ number indicates the number of pellets created by this group.
|
|
//
|
|
// All groups (including the random-angle ones) are symmetrical around
|
|
// • a (0, 1) vector, pointing downwards (for static groups)
|
|
// • a vector from the pellet's origin to the player (for aimed groups)
|
|
// Odd-numbered spreads always contain a pellet in the center, which moves
|
|
// along this axis of symmetry; even-numbered spreads don't.
|
|
//
|
|
// For aimed groups, this means that:
|
|
// • spreads with odd numbers of pellets are aimed *at* the player, while
|
|
// • spreads with even numbers of pellets are aimed *around* the player.
|
|
enum pellet_group_t {
|
|
// Does not actually work, due to a ZUN bug in group_velocity_set()!
|
|
PG_NONE = 0,
|
|
|
|
PG_1 = 1,
|
|
PG_1_AIMED = 12,
|
|
|
|
PG_2_SPREAD_WIDE,
|
|
PG_2_SPREAD_NARROW,
|
|
PG_3_SPREAD_WIDE,
|
|
PG_3_SPREAD_NARROW,
|
|
PG_4_SPREAD_WIDE,
|
|
PG_4_SPREAD_NARROW,
|
|
PG_5_SPREAD_WIDE,
|
|
PG_5_SPREAD_NARROW,
|
|
|
|
// Aimed versions of the n-way spreads above. Expected to have enum values
|
|
// >= this one!
|
|
PG_AIMED_SPREADS,
|
|
|
|
PG_2_SPREAD_WIDE_AIMED = PG_AIMED_SPREADS,
|
|
PG_2_SPREAD_NARROW_AIMED,
|
|
PG_3_SPREAD_WIDE_AIMED,
|
|
PG_3_SPREAD_NARROW_AIMED,
|
|
PG_4_SPREAD_WIDE_AIMED,
|
|
PG_4_SPREAD_NARROW_AIMED,
|
|
PG_5_SPREAD_WIDE_AIMED,
|
|
PG_5_SPREAD_NARROW_AIMED,
|
|
|
|
// -11.25 deg to +11.25 deg, around the player
|
|
PG_1_RANDOM_NARROW_AIMED = 29,
|
|
// -45 deg to +45 deg, facing down
|
|
PG_1_RANDOM_WIDE = 30,
|
|
|
|
_pellet_group_t_FORCE_INT16 = 0x7FFF
|
|
};
|
|
|
|
struct pellet_t {
|
|
protected:
|
|
friend class CPellets;
|
|
|
|
// Why, I don't even?!?
|
|
void sloppy_wide_unput(void) {
|
|
egc_copy_rect_1_to_0_16_word_w(
|
|
prev_left.to_pixel(), prev_top.to_pixel(), PELLET_W, PELLET_H
|
|
);
|
|
}
|
|
|
|
void sloppy_wide_unput_at_cur_pos(void) {
|
|
egc_copy_rect_1_to_0_16_word_w(
|
|
cur_left.to_pixel(), cur_top.to_pixel(), PELLET_W, PELLET_H
|
|
);
|
|
}
|
|
|
|
public:
|
|
bool moving;
|
|
unsigned char motion_type;
|
|
|
|
// Automatically calculated every frame for PM_SPIN
|
|
Subpixel cur_left;
|
|
Subpixel cur_top;
|
|
|
|
SPPoint spin_center; // only used for PM_SPIN
|
|
Subpixel prev_left;
|
|
Subpixel prev_top;
|
|
pellet_group_t from_group;
|
|
SPPoint velocity; // unused for PM_SPIN
|
|
SPPoint spin_velocity; // only used for PM_SPIN
|
|
|
|
// Used only for poor attempts at reducing flickering. Does not disable
|
|
// hit testing if true.
|
|
bool16 not_rendered;
|
|
|
|
int age;
|
|
Subpixel speed; // for recalculating velocities with certain motion types
|
|
int decay_frame;
|
|
int cloud_frame;
|
|
screen_x_t cloud_left; // Not subpixels!
|
|
screen_y_t cloud_top; // Not subpixels!
|
|
int angle; // for recalculating velocities with certain motion types
|
|
|
|
// Direction for PM_SLING_AIMED. Not reset when a pellet is destroyed -
|
|
// and therefore effectively only calculated once for every instance of
|
|
// this structure.
|
|
pellet_sling_direction_t sling_direction;
|
|
};
|
|
|
|
class CPellets {
|
|
pellet_t near pellets[PELLET_COUNT];
|
|
int alive_count; // only used for one single optimization
|
|
int unknown_zero[10];
|
|
public:
|
|
int unknown_seven;
|
|
|
|
// Rendering pellets at odd or even indices this frame?
|
|
bool16 interlace_field;
|
|
bool spawn_with_cloud;
|
|
|
|
protected:
|
|
pellet_t near* iteration_start(void) {
|
|
return static_cast<pellet_t near *>(pellets);
|
|
}
|
|
|
|
// Updates the velocity of the currently iterated pellet, depending on its
|
|
// [motion_type].
|
|
void motion_type_apply_for_cur(void);
|
|
|
|
void decay_tick_for_cur(void);
|
|
bool16 hittest_player_for_cur(void);
|
|
|
|
// Processes any collision between the currently iterated pellet, the Orb,
|
|
// or a deflecting player. (Regular, life-losing hit testing between
|
|
// pellets and the player is done elsewhere!)
|
|
// Returns true if the bullet remains visible.
|
|
//
|
|
// (And yes, this function does operate on the currently iterated pellet,
|
|
// despite taking separate position parameters!)
|
|
bool16 visible_after_hittests_for_cur(
|
|
screen_x_t pellet_left, screen_y_t pellet_top
|
|
);
|
|
|
|
void clouds_unput_update_render(void);
|
|
|
|
public:
|
|
CPellets(void);
|
|
|
|
// Spawns a number of bullets according to the given [group], with their
|
|
// corresponding velocities, at (left, top). [speed_base] is tuned
|
|
// according to the currently played difficulty and the resident
|
|
// [pellet_speed]. The [motion_type] for the new pellets is PM_NORMAL.
|
|
void add_group(
|
|
screen_x_t left,
|
|
screen_y_t top,
|
|
pellet_group_t group,
|
|
subpixel_t speed_base
|
|
);
|
|
|
|
// Spawns a single new pellet with a customizable [motion_type].
|
|
// [speed_base] is tuned according to the currently played difficulty and
|
|
// the resident [pellet_speed]; [speed_for_motion_fixed] is never tuned.
|
|
//
|
|
// [spin_center_x] and [spin_center_y] are only used with PM_SPIN,
|
|
// while [speed_base] is *ignored* for PM_SPIN.
|
|
void add_single(
|
|
screen_x_t left,
|
|
screen_y_t top,
|
|
unsigned char angle,
|
|
subpixel_t speed_base,
|
|
pellet_motion_t motion_type,
|
|
subpixel_t speed_for_motion_fixed = to_sp(0.0f),
|
|
screen_x_t spin_center_x = 0,
|
|
screen_y_t spin_center_y = 0
|
|
);
|
|
|
|
// Transitions all living pellets into their decay state, awarding points
|
|
// for each one.
|
|
void decay_all(void);
|
|
|
|
// Also calls Shots.unput_update_render()!
|
|
void unput_update_render(void);
|
|
|
|
void unput_and_reset_all(void);
|
|
void reset_all(void);
|
|
};
|
|
|
|
/// Globals
|
|
/// -------
|
|
extern CPellets Pellets;
|
|
|
|
// If true, CPellets::unput_update_render() performs
|
|
// • rendering,
|
|
// • and hit testing against the Orb and the player
|
|
// for only half of the [pellets] each frame, alternating between even and odd
|
|
// [pellets] array indices. However,
|
|
// • motion updates,
|
|
// • and hit testing against *player shots*
|
|
// are still done for all pellets every frame.
|
|
//
|
|
// Probably not really meant to save CPU and/or VRAM accesses, but rather to
|
|
// reduce flicker in busy boss battles, due to all the sloppy unblitting...
|
|
extern bool pellet_interlace;
|
|
|
|
extern unsigned int pellet_destroy_score_delta;
|
|
/// -------
|