mirror of https://github.com/nmlgc/ReC98.git
446 lines
15 KiB
C++
446 lines
15 KiB
C++
/// Entities
|
||
/// --------
|
||
|
||
// Slot count for unique .BOS files associated with CBossEntity instances.
|
||
// *Not* CBossEntity instances themselves!
|
||
#define BOS_ENTITY_SLOT_COUNT 4
|
||
|
||
// An individual entity rendered with sprites from a .BOS file. May or may not
|
||
// be animated, and may or may not have a hitbox for collision with the Orb.
|
||
class CBossEntity {
|
||
public:
|
||
screen_x_t cur_left;
|
||
screen_y_t cur_top;
|
||
screen_x_t prev_left;
|
||
screen_y_t prev_top;
|
||
vram_byte_amount_t vram_w;
|
||
pixel_t h;
|
||
Area<screen_x_t, screen_y_t> move_clamp; // Relative to VRAM
|
||
Area<pixel_t, pixel_t> hitbox_orb; // Relative to [cur_left] and [cur_top]
|
||
|
||
// Never actually read outside of the functions that set them...
|
||
pixel_t prev_delta_y;
|
||
pixel_t prev_delta_x;
|
||
|
||
int bos_image_count;
|
||
|
||
protected:
|
||
int zero_1;
|
||
int bos_image;
|
||
int unknown;
|
||
|
||
public:
|
||
bool16 hitbox_orb_inactive;
|
||
bool16 loading;
|
||
|
||
// Locks both movement and rendering via the locked_*() methods if nonzero.
|
||
int lock_frame;
|
||
|
||
int zero_2;
|
||
char zero_3;
|
||
unsigned char bos_slot;
|
||
|
||
CBossEntity() {
|
||
loading = false;
|
||
zero_3 = 0;
|
||
}
|
||
|
||
// Even Turbo C++ 4.0J implements copy constructors for trivially
|
||
// constructible types via an equivalent of memcpy() by default. This
|
||
// constructor downgrades that to dumb single-member assignment, and is
|
||
// undeniably worse. MODDERS: Delete.
|
||
CBossEntity(const CBossEntity near &other) {
|
||
cur_left = other.cur_left;
|
||
cur_top = other.cur_top;
|
||
prev_left = other.prev_left;
|
||
prev_top = other.prev_top;
|
||
vram_w = other.vram_w;
|
||
h = other.h;
|
||
move_clamp.left = other.move_clamp.left;
|
||
move_clamp.right = other.move_clamp.right;
|
||
move_clamp.top = other.move_clamp.top;
|
||
move_clamp.bottom = other.move_clamp.bottom;
|
||
hitbox_orb.left = other.hitbox_orb.left;
|
||
hitbox_orb.right = other.hitbox_orb.right;
|
||
hitbox_orb.top = other.hitbox_orb.top;
|
||
hitbox_orb.bottom = other.hitbox_orb.bottom;
|
||
prev_delta_y = other.prev_delta_y;
|
||
prev_delta_x = other.prev_delta_x;
|
||
bos_image_count = other.bos_image_count;
|
||
zero_1 = other.zero_1;
|
||
bos_image = other.bos_image;
|
||
unknown = other.unknown;
|
||
hitbox_orb_inactive = other.hitbox_orb_inactive;
|
||
loading = other.loading;
|
||
lock_frame = other.lock_frame;
|
||
zero_2 = other.zero_2;
|
||
zero_3 = other.zero_3;
|
||
bos_slot = other.bos_slot;
|
||
}
|
||
|
||
// Loads all images from the .BOS file with the given [fn] inside the
|
||
// currently active packfile into the given CBossEntity .BOS [slot], and
|
||
// keeps the .BOS metadata in this CBossEntity instance. Always returns 0.
|
||
void load(const char fn[PF_FN_LEN], int slot) {
|
||
loading = true;
|
||
load_inner(fn, slot);
|
||
loading = false;
|
||
}
|
||
|
||
int load_inner(const char fn[PF_FN_LEN], int slot);
|
||
|
||
// Copies the .BOS header data of this instance to the given variables. In
|
||
// practice, only used to copy these values from one CBossEntity to
|
||
// another, via metadata_assign().
|
||
void metadata_get(
|
||
int &image_count,
|
||
unsigned char &slot,
|
||
vram_byte_amount_t &vram_w,
|
||
pixel_t &h
|
||
) const;
|
||
|
||
// Copies the .BOS header data of [src] to this instance.
|
||
void metadata_assign(const CBossEntity near &src) {
|
||
// MODDERS: Delete this unnecessary and wasteful temporary copy.
|
||
CBossEntity tmp(src);
|
||
|
||
tmp.metadata_get(bos_image_count, bos_slot, vram_w, h);
|
||
}
|
||
|
||
int image(void) const {
|
||
return bos_image;
|
||
}
|
||
|
||
void set_image(int image) {
|
||
bos_image = image;
|
||
}
|
||
|
||
pixel_t w_aligned(void) const {
|
||
return ((vram_w * BYTE_DOTS) + 16);
|
||
}
|
||
|
||
/// Blitting
|
||
/// --------
|
||
// All functions with an [image] parameter use that image from [bos_slot],
|
||
// *not* the [bos_image] of this instance.
|
||
// Well, OK, except for the next one, with...
|
||
|
||
// Sets [bos_image] and blits the image to the entity's current position.
|
||
// Additionally clips at the bottom edge of VRAM.
|
||
void set_image_unput_and_put_8(int image) {
|
||
bos_image = image;
|
||
unput_and_put_8(cur_left, cur_top, bos_image);
|
||
}
|
||
|
||
// Like CPlayerAnim, all of these also make an attempt at clipping the
|
||
// sprite at the left and right edges of VRAM. This only really works if
|
||
// [left] is a multiple of 16 and inside the [-RES_X, RES_X[ range,
|
||
// though, and is pretty much broken otherwise.
|
||
|
||
// Blits [image] to the entity's current position. Additionally clips at
|
||
// the bottom edge of VRAM.
|
||
// (This function really shouldn't exist, as it can introduce a disparity
|
||
// between [bos_image] and the one on screen.)
|
||
void put_8(int image) const {
|
||
put_8(cur_left, cur_top, image);
|
||
}
|
||
|
||
// Blits [bos_image] to the entity's current position.
|
||
// Additionally clips at the bottom edge of VRAM.
|
||
void put_8(void) const {
|
||
put_8(cur_left, cur_top, bos_image);
|
||
}
|
||
|
||
// Blits [image] to (⌊left/8⌋*8, top).
|
||
// Additionally clips at the bottom edge of VRAM.
|
||
void put_8(screen_x_t left, vram_y_t top, int image) const;
|
||
|
||
// Precisely restores pixels according to the alpha mask of [image] from
|
||
// VRAM page 1, starting at (⌊left/8⌋*8, top).
|
||
// Additionally clips at the top and bottom edges of VRAM.
|
||
void unput_8(screen_x_t left, vram_y_t top, int image) const;
|
||
|
||
// Like put_8(), but restores all pixels in the blitted sprite
|
||
// rectangle from VRAM page 1 prior to blitting.
|
||
// Additionally clips at the top and bottom edges of VRAM.
|
||
void unput_and_put_8(screen_x_t left, vram_y_t top, int image) const;
|
||
|
||
// Blits line #[row] of [image] to (left, top).
|
||
// Additionally clips at the bottom edge of VRAM.
|
||
void put_1line(screen_x_t left, vram_y_t y, int image, pixel_t row) const;
|
||
|
||
// Like put_1line(), but restores all pixels along the line from VRAM page
|
||
// 1 prior to blitting the line.
|
||
void unput_and_put_1line(
|
||
screen_x_t left, vram_y_t y, int image, pixel_t row
|
||
) const;
|
||
|
||
// Blits [image] with a wave function applied to the starting X coordinate
|
||
// for each row, based at the given (left, top) point. Used for Elis'
|
||
// entrance animation.
|
||
// Calls put_1line() for each row, and clips the sprite accordingly.
|
||
void wave_put(
|
||
screen_x_t left,
|
||
vram_y_t top,
|
||
int image,
|
||
int len,
|
||
pixel_t amp,
|
||
int phase
|
||
) const;
|
||
|
||
// Like wave_put(), but calls unput_and_put_1line() for each line instead.
|
||
// For a sloppy, EGC-accelerated unblitter function, see egc_wave_unput().
|
||
void wave_unput_and_put(
|
||
screen_x_t left,
|
||
vram_y_t top,
|
||
int image,
|
||
int len,
|
||
pixel_t amp,
|
||
int phase
|
||
) const;
|
||
|
||
// Tries to unblit the two sprites at (left_1, top) and (left_2, top) that
|
||
// were previously blitted with the given wave function using the EGC, but
|
||
// fails.
|
||
void egc_sloppy_wave_unput_double_broken(
|
||
screen_x_t left_1, vram_y_t top, int unused,
|
||
int len_1, pixel_t amp_1, int phase_1,
|
||
screen_x_t left_2,
|
||
int len_2, pixel_t amp_2, int phase_2
|
||
) const;
|
||
|
||
// Blits the 16×8 pixels of [bos_image] in [bos_slot] starting at
|
||
// (bos_left, bos_top), relative to the top-left corner of the sprite, to
|
||
// ((⌊cur_left/8⌋ + bos_left/8⌋) * 8, (cur_top + bos_top))
|
||
// after precisely restoring pixels according to the alpha mask of the
|
||
// pixels to be blitted from VRAM page 1.
|
||
// Additionally clips at the top and bottom edges of VRAM.
|
||
void unput_and_put_16x8_8(pixel_t bos_left, pixel_t bos_top) const;
|
||
|
||
// Restores the pixels inside the entire ([vram_w]*8)×[h] rectangle
|
||
// starting at (cur_left, cur_top) from VRAM page 1.
|
||
void sloppy_unput(void) const;
|
||
/// --------
|
||
|
||
/// Movement
|
||
/// --------
|
||
protected:
|
||
void move(const pixel_t &delta_x, const pixel_t &delta_y) {
|
||
prev_delta_x = delta_x;
|
||
prev_delta_y = delta_y;
|
||
prev_left = cur_left;
|
||
prev_top = cur_top;
|
||
|
||
cur_left += prev_delta_x;
|
||
if(move_clamp.left > cur_left) {
|
||
cur_left = move_clamp.left;
|
||
}
|
||
if(move_clamp.right < cur_left) {
|
||
cur_left = move_clamp.right;
|
||
}
|
||
|
||
cur_top += prev_delta_y;
|
||
if(cur_top < move_clamp.top) {
|
||
cur_top = move_clamp.top;
|
||
}
|
||
if(cur_top > move_clamp.bottom) {
|
||
cur_top = move_clamp.bottom;
|
||
}
|
||
}
|
||
|
||
public:
|
||
void pos_cur_set(screen_x_t left, screen_x_t top) {
|
||
cur_left = left;
|
||
cur_top = top;
|
||
}
|
||
|
||
// Sets [cur_left], [cur_top], [unknown], and the [move_clamp] area.
|
||
//
|
||
// Kikuri is the only boss who relies on move clamping. All others ignore
|
||
// the feature by just directly partying on [cur_left] and [cur_top], and
|
||
// can ignore the last 4 parameters. ZUN is very likely to just have
|
||
// copy-pasted their [move_clamp_right] and [move_clamp_bottom] values from
|
||
// SinGyoku, as they don't make a lot of sense in context of the entity
|
||
// widths of other bosses.
|
||
void pos_set(
|
||
screen_x_t left,
|
||
screen_y_t top,
|
||
int unknown = 48,
|
||
screen_x_t move_clamp_left = PLAYFIELD_LEFT,
|
||
screen_x_t move_clamp_right = (PLAYFIELD_RIGHT + SINGYOKU_W),
|
||
screen_y_t move_clamp_top = PLAYFIELD_TOP,
|
||
screen_y_t move_clamp_bottom = (PLAYFIELD_BOTTOM - SINGYOKU_H)
|
||
);
|
||
|
||
// (Just read the actual function code, it's impossible to summarize these
|
||
// without spelling out every single line here. Most notably though, it
|
||
// only actually moves or renders the entity if [lock_frame] is 0.)
|
||
void locked_move_and_put_8(
|
||
int unused, pixel_t delta_x, pixel_t delta_y, int lock_frames
|
||
);
|
||
void locked_move_unput_and_put_8(
|
||
int unused, pixel_t delta_x, pixel_t delta_y, int lock_frames
|
||
);
|
||
|
||
// ZUN bug: The lock concept should really not apply to blitting. Only
|
||
// unblitting and reblitting an entity every 3 frames allows other
|
||
// overlapping sprites (i.e., bullets, player shots, or the Orb) to rip
|
||
// holes into it as part of their respective unblitting calls during these
|
||
// 3 frames. That's what causes the flickering during most of YuugenMagan's
|
||
// attack phases. This method can only be interpreted as a desperate
|
||
// attempt at improving performance, pointing out how much this game would
|
||
// have benefitted from a proper sprite system.
|
||
void locked_unput_and_put_8(void) {
|
||
locked_move_unput_and_put_8(0, 0, 0, 3);
|
||
}
|
||
|
||
void unlock_put_lock_8(void) {
|
||
lock_frame = 0;
|
||
locked_move_and_put_8(0, 0, 0, 3);
|
||
}
|
||
|
||
void unlock_put_image_lock_8(int image) {
|
||
lock_frame = 0;
|
||
bos_image = image;
|
||
locked_move_and_put_8(0, 0, 0, 3);
|
||
}
|
||
|
||
void unlock_unput_put_image_lock_8(int image) {
|
||
lock_frame = 0;
|
||
bos_image = image;
|
||
locked_unput_and_put_8();
|
||
}
|
||
/// --------
|
||
|
||
/// Collision detection
|
||
/// -------------------
|
||
|
||
// Sets [hitbox_orb] to the given coordinates, relative to the top-left
|
||
// corner of the image. The area corresponds to the one that the Orb's
|
||
// centered hitbox has to fully overlap. Relative to the entity's sprite:
|
||
//
|
||
// [this->cur_top]
|
||
// ┌──────────────────────────────────────────┐
|
||
// │ Orb top │
|
||
// │ ┌─────────┐ │
|
||
// │ │ [top] │
|
||
// │ Orb left │ ╔═══════════════╗ │
|
||
// │ │ ║ ║ │
|
||
// │ └─║ ║ [right] │
|
||
// [this->cur_left] │ ║ ║ │
|
||
// │ [left] ║ ║─┐ │
|
||
// │ ║ ║ │ │
|
||
// │ ╚═══════════════╝ │ Orb right │
|
||
// │ [bottom] │ │
|
||
// │ └─────────┘ │
|
||
// │ Orb bottom │
|
||
// └──────────────────────────────────────────┘
|
||
void hitbox_orb_set(
|
||
pixel_t left, pixel_t top, pixel_t right, pixel_t bottom
|
||
) {
|
||
hitbox_orb.left = (left + ORB_HITBOX_W);
|
||
hitbox_orb.right = (right - ORB_HITBOX_W);
|
||
hitbox_orb.top = (top + ORB_HITBOX_H);
|
||
hitbox_orb.bottom = (bottom - ORB_HITBOX_H);
|
||
}
|
||
|
||
void hitbox_orb_activate() {
|
||
hitbox_orb_inactive = false;
|
||
}
|
||
|
||
void hitbox_orb_deactivate() {
|
||
hitbox_orb_inactive = true;
|
||
}
|
||
|
||
// Simply returns whether the orb collided with this entity on the last
|
||
// frame. (TODO: Last frame???)
|
||
bool16 hittest_orb(void) const;
|
||
/// -------------------
|
||
};
|
||
|
||
// Despite CBossEntity's own width and height fields, ZUN still likes to
|
||
// statically hardcode the intended sprite sizes when calculating offsets
|
||
// within a .BOS sprite. Since bosses #define more readable names for each
|
||
// instance of [boss_entities], we might as well use the opportunity to lift up
|
||
// these static sizes into the type system, and avoid the need for per-boss
|
||
// boilerplate coordinate functions.
|
||
//
|
||
// (Due to CBossEntity unfortunately having a non-inlined default constructor,
|
||
// we can't ever directly declare instance of this template without emitting
|
||
// another constructor for this class.)
|
||
template <pixel_t W, pixel_t H> struct CBossEntitySized : public CBossEntity {
|
||
pixel_t w_static() const {
|
||
return W;
|
||
}
|
||
|
||
pixel_t h_static() const {
|
||
return H;
|
||
}
|
||
|
||
screen_x_t cur_center_x(void) const {
|
||
return (cur_left + (W / 2));
|
||
}
|
||
|
||
screen_x_t cur_center_y(void) const {
|
||
return (cur_top + (H / 2));
|
||
}
|
||
|
||
screen_x_t cur_right(void) const {
|
||
return (cur_left + W);
|
||
}
|
||
};
|
||
|
||
// Frees all images in the given [slot].
|
||
void bos_entity_free(int slot);
|
||
|
||
// MODDERS: The static initialization code in the original game proves that ZUN
|
||
// declared these as 5 individual variables rather than a single 5-element
|
||
// array. This fact explains why especially YuugenMagan and Elis ended up with
|
||
// highly redundant code. There are definitely better and cleaner ways of
|
||
// handling these entities.
|
||
extern CBossEntity boss_entity_0;
|
||
extern CBossEntity boss_entity_1;
|
||
extern CBossEntity boss_entity_2;
|
||
extern CBossEntity boss_entity_3;
|
||
extern CBossEntity boss_entity_4;
|
||
/// --------
|
||
|
||
/// Non-entity animations
|
||
/// ---------------------
|
||
|
||
// Slot count for unique .BOS files associated with CBossAnim instances.
|
||
// *Not* CBossAnim instances themselves!
|
||
#define BOS_ANIM_SLOT_COUNT 2
|
||
|
||
// Stripped-down version of CBossEntity, with just animation support. These
|
||
// only have functions for direct byte-aligned blitting onto page 0, in
|
||
// exchange for the alpha plane being pre-negated at load time? No idea why.
|
||
// That 1-instruction negation is certainly not what makes the original code
|
||
// slow.
|
||
class CBossAnim {
|
||
public:
|
||
screen_x_t left;
|
||
screen_y_t top;
|
||
vram_byte_amount_t vram_w;
|
||
pixel_t h;
|
||
unsigned char bos_image_count;
|
||
unsigned char bos_image;
|
||
unsigned char bos_slot;
|
||
|
||
// Loads all images from the .BOS file with the given [fn] inside the
|
||
// currently active packfile into the given CBossAnim .BOS [slot], and
|
||
// keeps the .BOS metadata in this CBossEntity instance. Always returns 0.
|
||
// Identical to CBossEntity::load() with an added alpha negation loop.
|
||
int load(const char fn[PF_FN_LEN], int slot);
|
||
|
||
// Blits [bos_image] to (⌊left/8⌋*8, top).
|
||
// Additionally clips at the bottom edge of VRAM.
|
||
void put_8(void) const;
|
||
};
|
||
|
||
// Frees all images in the given [slot].
|
||
void bos_anim_free(int slot);
|
||
|
||
extern CBossAnim boss_anims[2];
|
||
/// ---------------------
|