mirror of https://github.com/nmlgc/ReC98.git
2985 lines
78 KiB
C++
2985 lines
78 KiB
C++
/// Makai Stage 20 Boss - Sariel
|
||
/// ----------------------------
|
||
|
||
#include <stdlib.h>
|
||
#include <dos.h>
|
||
#include "platform.h"
|
||
#include "decomp.hpp"
|
||
#include "pc98.h"
|
||
#include "planar.h"
|
||
#include "master.hpp"
|
||
#include "th01/common.h"
|
||
#include "th01/v_colors.hpp"
|
||
#include "th01/math/area.hpp"
|
||
#include "th01/math/dir.hpp"
|
||
#include "th01/math/overlap.hpp"
|
||
#include "th01/math/polar.hpp"
|
||
#include "th01/math/subpixel.hpp"
|
||
#include "th01/math/vector.hpp"
|
||
#include "th01/hardware/frmdelay.h"
|
||
#include "th01/hardware/graph.h"
|
||
#include "th01/hardware/grcg8x8m.hpp"
|
||
#include "th01/hardware/egc.h"
|
||
#include "th01/hardware/egcrows.hpp"
|
||
#include "th01/hardware/grpinv32.hpp"
|
||
#include "th01/hardware/palette.h"
|
||
#include "th01/hardware/pgtrans.hpp"
|
||
#include "th01/hardware/scrollup.hpp"
|
||
#include "th01/hardware/input.hpp"
|
||
#include "th01/snd/mdrv2.h"
|
||
#include "th01/formats/pf.hpp"
|
||
#include "th01/formats/grc.hpp"
|
||
#include "th01/formats/grp.h"
|
||
#include "th01/formats/ptn.hpp"
|
||
#include "th01/main/playfld.hpp"
|
||
#include "th01/formats/stagedat.hpp"
|
||
#include "th01/sprites/leaf.hpp"
|
||
#include "th01/sprites/pellet.h"
|
||
#include "th01/sprites/shape8x8.hpp"
|
||
#include "th01/main/entity.hpp"
|
||
#include "th01/main/spawnray.hpp"
|
||
#include "th01/main/vars.hpp"
|
||
#include "th01/main/player/player.hpp"
|
||
#include "th01/main/stage/palette.hpp"
|
||
#include "th01/main/shape.hpp"
|
||
#include "th01/main/stage/stageobj.hpp"
|
||
#include "th01/main/player/bomb.hpp"
|
||
#include "th01/main/player/orb.hpp"
|
||
#include "th01/main/player/shot.hpp"
|
||
#include "th01/main/boss/boss.hpp"
|
||
#include "th01/main/boss/entity_a.hpp"
|
||
#include "th01/main/boss/palette.hpp"
|
||
#include "th01/main/bullet/laser_s.hpp"
|
||
#include "th01/main/bullet/pellet.hpp"
|
||
#include "th01/main/hud/hp.hpp"
|
||
#include "th01/main/hud/hud.hpp"
|
||
|
||
static const char* unused_entrance_letters_maybe[] = { "ANGEL", "OF", "DEATH" };
|
||
bool game_cleared = false;
|
||
|
||
// Coordinates
|
||
// -----------
|
||
|
||
static const screen_x_t SHIELD_LEFT = 304;
|
||
static const screen_y_t SHIELD_TOP = 144;
|
||
static const screen_x_t DRESS_LEFT = 280;
|
||
static const screen_y_t DRESS_TOP = 192;
|
||
static const screen_x_t WAND_LEFT = 296;
|
||
static const screen_y_t WAND_TOP = 48;
|
||
|
||
static const screen_y_t SHIELD_CENTER_X = 320;
|
||
static const screen_y_t SHIELD_CENTER_Y = 164;
|
||
|
||
static const screen_x_t SEAL_CENTER_X = 320;
|
||
static const screen_x_t SEAL_CENTER_Y = 185;
|
||
static const pixel_t SEAL_RADIUS = 140;
|
||
|
||
// That's… not quite where the sphere is?
|
||
static const screen_x_t WAND_EMIT_LEFT = 340;
|
||
static const screen_y_t WAND_EMIT_TOP = 64;
|
||
|
||
// That's… not quite where the face is?
|
||
static const screen_x_t FACE_LEFT = 314;
|
||
static const screen_y_t FACE_TOP = 104;
|
||
static const pixel_t FACE_W = 64;
|
||
static const pixel_t FACE_H = 64;
|
||
static const screen_y_t FACE_CENTER_Y = (FACE_TOP + (FACE_H / 4));
|
||
|
||
// MODDERS: That's 32 more than BOSS6_2.BOS is wide? Reducing it to 96 works
|
||
// fine as well.
|
||
static const pixel_t WAND_W = 128;
|
||
|
||
static const pixel_t WAND_H = 96;
|
||
|
||
// BOSS6_A2.GRP
|
||
static const screen_x_t A2_ORBS_L_CENTER_X = 64;
|
||
static const screen_x_t A2_ORBS_R_CENTER_X = 568;
|
||
static const screen_y_t A2_ORBS_CENTER_Y = 160;
|
||
// -----------
|
||
|
||
enum sariel_colors_t {
|
||
COL_LASER = 4,
|
||
COL_PARTICLE2X2 = 4,
|
||
COL_DETONATION = 5,
|
||
COL_DETONATION_START = 6,
|
||
COL_FORM2_PULSE = 6,
|
||
COL_AIR = 12,
|
||
COL_BIRD = 15, // Yes, just a single one, changed by the background image.
|
||
};
|
||
|
||
static union {
|
||
int frame;
|
||
int speed_multiplied_by_8;
|
||
int interval;
|
||
int start_frame;
|
||
pixel_t velocity_x;
|
||
int pellet_count;
|
||
int unknown;
|
||
} pattern_state;
|
||
|
||
// File names
|
||
// ----------
|
||
|
||
#include "th01/shiftjis/fns.hpp"
|
||
|
||
const char* BG_IMAGES[4] = {
|
||
"BOSS6_A1.GRP", "BOSS6_A2.GRP", "BOSS6_A3.GRP", "BOSS6_A4.GRP"
|
||
};
|
||
// ----------
|
||
|
||
// Entities (and animations)
|
||
// --------
|
||
|
||
#define ent_shield boss_entity_0
|
||
#define anm_dress boss_anims[0]
|
||
#define anm_wand boss_anims[1]
|
||
|
||
inline void sariel_ent_load(void) {
|
||
ent_shield.load("boss6_1.bos", 0);
|
||
anm_wand.load("boss6_2.bos", 0);
|
||
anm_dress.load("boss6_3.bos", 1);
|
||
}
|
||
|
||
inline void sariel_ent_free(void) {
|
||
bos_entity_free(0);
|
||
bos_anim_free(0);
|
||
bos_anim_free(1);
|
||
}
|
||
// --------
|
||
|
||
/// .GRC entities
|
||
/// -------------
|
||
|
||
static const main_grc_slot_t GRC_SLOT_VORTEX_DEBRIS = GRC_SLOT_BOSS_1;
|
||
static const main_grc_slot_t GRC_SLOT_BIRD = GRC_SLOT_BOSS_2;
|
||
static const main_grc_slot_t GRC_SLOT_SPAWNCROSS = GRC_SLOT_BOSS_3;
|
||
static const main_grc_slot_t GRC_SLOT_LEAFSPLASH = GRC_SLOT_BOSS_4;
|
||
|
||
inline void sariel_grc_load(void) {
|
||
grc_load(GRC_SLOT_VORTEX_DEBRIS, "boss6gr1.grc");
|
||
grc_load(GRC_SLOT_BIRD, "boss6gr2.grc");
|
||
grc_load(GRC_SLOT_SPAWNCROSS, "boss6gr3.grc");
|
||
grc_load(GRC_SLOT_LEAFSPLASH, "boss6gr4.grc");
|
||
}
|
||
|
||
inline void sariel_grc_free(void) {
|
||
grc_free(GRC_SLOT_VORTEX_DEBRIS);
|
||
grc_free(GRC_SLOT_BIRD);
|
||
grc_free(GRC_SLOT_SPAWNCROSS);
|
||
grc_free(GRC_SLOT_LEAFSPLASH);
|
||
}
|
||
|
||
// Vortex and debris sprites (BOSS6GR1.GRC)
|
||
// ----------------------------------------
|
||
|
||
static const pixel_t VORTEX_W = 32;
|
||
static const pixel_t VORTEX_H = 32;
|
||
static const pixel_t DEBRIS_W = 32;
|
||
static const pixel_t DEBRIS_H = 32;
|
||
|
||
static const int VORTEX_COUNT = 2;
|
||
|
||
enum vortex_or_debris_cel_t {
|
||
VORTEX_CELS = 3,
|
||
DEBRIS_CELS = 3,
|
||
|
||
C_VORTEX = 0,
|
||
C_VORTEX_last = (C_VORTEX + VORTEX_CELS - 1),
|
||
C_DEBRIS,
|
||
C_DEBRIS_last = (C_DEBRIS + DEBRIS_CELS - 1),
|
||
|
||
_vortex_or_debris_cel_t_FORCE_INT16 = 0x7FFF
|
||
};
|
||
|
||
inline vortex_or_debris_cel_t debris_cel_next(vortex_or_debris_cel_t &cur) {
|
||
return (cur < C_DEBRIS_last)
|
||
? static_cast<vortex_or_debris_cel_t>(cur + 1)
|
||
: C_DEBRIS;
|
||
}
|
||
|
||
#define vortex_or_debris_put_8(left, top, cel) \
|
||
grc_put_8(left, top, GRC_SLOT_VORTEX_DEBRIS, cel, V_WHITE);
|
||
// ----------------------------------------
|
||
|
||
// Birds (BOSS6GR2.GRC)
|
||
// --------------------
|
||
|
||
static const pixel_t BIRD_W = 32;
|
||
static const pixel_t BIRD_H = 32;
|
||
|
||
static const int BIRD_COUNT = 30;
|
||
static const int BIRD_HATCH_CELS = 5;
|
||
static const int BIRD_FLY_CELS = 2;
|
||
|
||
enum bird_cel_t {
|
||
C_EGG = 0,
|
||
C_HATCH,
|
||
C_HATCH_last = (C_HATCH + BIRD_HATCH_CELS - 1),
|
||
C_FLY,
|
||
C_FLY_last = (C_FLY + BIRD_FLY_CELS - 1),
|
||
};
|
||
|
||
#define bird_put_8(left, top, cel) \
|
||
grc_put_8(left, top, GRC_SLOT_BIRD, cel, COL_BIRD);
|
||
// --------------------
|
||
|
||
// Spawn cross (BOSS6GR3.GRC)
|
||
// --------------------------
|
||
|
||
static const pixel_t SPAWNCROSS_W = 32;
|
||
static const pixel_t SPAWNCROSS_H = 32;
|
||
static const int SPAWNCROSS_CELS = 2;
|
||
// --------------------------
|
||
|
||
// Leaf splash animation (BOSS6GR4.GRC)
|
||
// ------------------------------------
|
||
// Never seen in the original game, due to a ZUN bug in the only pattern that
|
||
// uses it.
|
||
|
||
static const pixel_t LEAFSPLASH_W = 32;
|
||
static const pixel_t LEAFSPLASH_H = 32;
|
||
static const int LEAFSPLASH_CELS = 3;
|
||
// ------------------------------------
|
||
/// -------------
|
||
|
||
// .PTN
|
||
// ----
|
||
|
||
// The original wand sprite together with its background, as it appears in the
|
||
// .GRP images, snapped from VRAM.
|
||
static const main_ptn_slot_t PTN_SLOT_WAND_LOWERED = PTN_SLOT_BOSS_1;
|
||
// ----
|
||
|
||
// 2×2 particles (just "particles" was taken)
|
||
// -------------
|
||
|
||
static const pixel_t PARTICLE2X2_W = 2;
|
||
static const pixel_t PARTICLE2X2_H = 2;
|
||
static const int PARTICLE2X2_COUNT = 30;
|
||
static const dots8_t sPARTICLE2X2 = 0xC0; // (** )
|
||
|
||
#define particle2x2_linear_vram_offset(vo, first_bit, left, top) { \
|
||
vo = vram_offset_divmul_double(left, top); \
|
||
first_bit = (static_cast<screen_x_t>(left) % BYTE_DOTS); \
|
||
}
|
||
|
||
// Better.
|
||
#define particle2x2_snap(dots, vo, first_bit) { \
|
||
dots[0] = grcg_chunk_8(vo); \
|
||
dots[1] = grcg_chunk_8(vo + ROW_SIZE); \
|
||
dots[0] &= (sPARTICLE2X2 >> first_bit); \
|
||
dots[1] &= (sPARTICLE2X2 >> first_bit); \
|
||
}
|
||
|
||
// Worse.
|
||
#define particle2x2_snap_2(dots, vo, first_bit) { \
|
||
dots[0] = grcg_chunk(vo, 8); \
|
||
/* Parentheses omitted for code generation reasons */ \
|
||
dots[1] = grcg_chunk(vo + ROW_SIZE, 8); \
|
||
dots[0] &= (sPARTICLE2X2 >> first_bit); \
|
||
dots[1] &= (sPARTICLE2X2 >> first_bit); \
|
||
}
|
||
|
||
#define particle2x2_snap_left_right(dots_left, dots_right, vo, first_bit) { \
|
||
particle2x2_snap_2(dots_left, vo, first_bit); \
|
||
if(first_bit == ((BYTE_DOTS - PARTICLE2X2_W) + 1)) { \
|
||
/* Parentheses omitted for code generation reasons */ \
|
||
dots_right[0] = grcg_chunk(vo + 1, 8); \
|
||
dots_right[1] = grcg_chunk(vo + 1 + ROW_SIZE, 8); \
|
||
/* We only care about the leftmost dot, if anything */ \
|
||
dots_right[0] &= 0x80; \
|
||
dots_right[1] &= 0x80; \
|
||
} \
|
||
}
|
||
|
||
#define particle2x2_put(vo, first_bit, dots) { \
|
||
grcg_put_emptyopt(vo, dots[0], 8); \
|
||
/* Parentheses omitted for code generation reasons */ \
|
||
grcg_put_emptyopt(vo + ROW_SIZE, dots[1], 8); \
|
||
}
|
||
|
||
#define particle2x2_put_left_right(vo, first_bit, dots_left, dots_right) { \
|
||
particle2x2_put(vo, first_bit, dots_left); \
|
||
if(first_bit == ((BYTE_DOTS - PARTICLE2X2_W) + 1)) { \
|
||
particle2x2_put(vo + 1, first_bit, dots_right); \
|
||
} \
|
||
}
|
||
// -------------
|
||
|
||
#include "th01/main/select_r.cpp"
|
||
|
||
void sariel_entrance(int8_t)
|
||
{
|
||
pellet_interlace = true;
|
||
|
||
text_fillca(' ', (TX_BLACK | TX_REVERSE));
|
||
|
||
/* graph_accesspage_func(0); */ grp_put_palette_show("boss6_l.grp");
|
||
/**/graph_accesspage_func(1)/**/; grp_put_palette_show("boss6_h.grp");
|
||
|
||
graph_accesspage_func(0);
|
||
stageobjs_init_and_render(BOSS_STAGE);
|
||
mdrv2_bgm_load("TENSI.MDT");
|
||
mdrv2_se_load(SE_FN);
|
||
mdrv2_bgm_play();
|
||
|
||
text_fillca(' ', TX_WHITE);
|
||
|
||
// Scroll
|
||
// ------
|
||
|
||
for(pixel_t line_on_top = (RES_Y - 1); line_on_top >= 0; line_on_top--) {
|
||
z_vsync_wait_and_scrollup(line_on_top);
|
||
egc_copy_rows_1_to_0(line_on_top, 1);
|
||
frame_delay(1);
|
||
}
|
||
z_vsync_wait_and_scrollup(0);
|
||
// ------
|
||
|
||
graph_accesspage_func(1);
|
||
grp_put_palette_show(BG_IMAGES[0]);
|
||
graph_accesspage_func(0);
|
||
pagetrans_diagonal_8x8(40);
|
||
|
||
random_seed = frame_rand;
|
||
}
|
||
|
||
void sariel_load_and_init(void)
|
||
{
|
||
sariel_ent_load();
|
||
sariel_grc_load();
|
||
boss_palette_snap();
|
||
void sariel_setup(void);
|
||
sariel_setup();
|
||
ptn_new(
|
||
PTN_SLOT_WAND_LOWERED, (((WAND_W / PTN_W) * (WAND_H / PTN_H)) + 4)
|
||
); // ?
|
||
}
|
||
|
||
void sariel_setup(void)
|
||
{
|
||
ent_shield.pos_set(SHIELD_LEFT, SHIELD_TOP, 48, 0, RES_X, 0, RES_Y);
|
||
ent_shield.hitbox_orb_set(-16, -16, 64, 64);
|
||
boss_hp = 18;
|
||
hud_hp_first_white = 8;
|
||
hud_hp_first_redwhite = 2;
|
||
boss_phase = 0;
|
||
boss_phase_frame = 0;
|
||
ent_shield.hitbox_orb_inactive = false;
|
||
anm_dress.left = DRESS_LEFT;
|
||
anm_dress.top = DRESS_TOP;
|
||
anm_wand.left = WAND_LEFT;
|
||
anm_wand.top = WAND_TOP;
|
||
}
|
||
|
||
void near wand_lowered_snap(void)
|
||
{
|
||
int ptn_x;
|
||
int ptn_y;
|
||
screen_x_t left = anm_wand.left;
|
||
screen_y_t top = anm_wand.top;
|
||
int image = 0;
|
||
|
||
ptn_snap_rect_from_1_8(
|
||
left, top, WAND_W, WAND_H, PTN_SLOT_WAND_LOWERED, image, ptn_x, ptn_y
|
||
);
|
||
}
|
||
|
||
void near wand_lowered_put(void)
|
||
{
|
||
int ptn_x;
|
||
int ptn_y;
|
||
screen_x_t left;
|
||
screen_y_t top;
|
||
int image;
|
||
|
||
image = 0;
|
||
left = anm_wand.left;
|
||
top = anm_wand.top;
|
||
|
||
ptn_put_rect_noalpha_8(
|
||
left, top, WAND_W, WAND_H, PTN_SLOT_WAND_LOWERED, image, ptn_x, ptn_y
|
||
)
|
||
}
|
||
|
||
void sariel_free(void)
|
||
{
|
||
sariel_ent_free();
|
||
sariel_grc_free();
|
||
ptn_free(PTN_SLOT_WAND_LOWERED);
|
||
}
|
||
|
||
// Almost identical to Konngara's version. This one is worse.
|
||
void pascal near spawnray_unput_and_put(
|
||
screen_x_t origin_x, vram_y_t origin_y,
|
||
screen_x_t target_x, vram_y_t target_y,
|
||
int col
|
||
)
|
||
{
|
||
static screen_x_t target_prev_x = -PIXEL_NONE;
|
||
static vram_y_t target_prev_y = -PIXEL_NONE;
|
||
spawnray_unput_and_put_func(
|
||
target_prev_x, target_prev_y,
|
||
origin_x, origin_y, target_x, target_y, col, true
|
||
);
|
||
}
|
||
|
||
inline void spawnray_reset(void) {
|
||
spawnray_unput_and_put(0, 0, 0, 0, SPAWNRAY_RESET);
|
||
}
|
||
|
||
// Two symmetric spawn rays with debris
|
||
// ------------------------------------
|
||
|
||
template <
|
||
screen_x_t OriginX, screen_y_t OriginY, pixel_t DebrisOffsetX
|
||
> struct SymmetricSpawnraysWithDebris {
|
||
screen_x_t target_l_x; // Left ray target, used to calculate the right one.
|
||
screen_y_t target_y;
|
||
|
||
screen_x_t target_x(x_direction_t ray_id) const {
|
||
return (ray_id == X_LEFT)
|
||
? target_l_x
|
||
: ((PLAYFIELD_RIGHT - 1) - target_l_x);
|
||
}
|
||
|
||
screen_x_t debris_left(x_direction_t ray_id, pixel_t offset = 0) const {
|
||
return (ray_id == X_LEFT)
|
||
? ((target_l_x - (DEBRIS_W - DebrisOffsetX)) - offset)
|
||
: (((PLAYFIELD_RIGHT - DebrisOffsetX) - target_l_x) + offset);
|
||
}
|
||
|
||
// Almost a correct unblitting call, except for the top coordinate...
|
||
void debris_sloppy_unput(
|
||
x_direction_t ray_id, pixel_t offset = 0, pixel_t additional_h = 0
|
||
) const {
|
||
egc_copy_rect_1_to_0_16(
|
||
debris_left(ray_id),
|
||
((target_y - (DEBRIS_H / 4)) + offset),
|
||
DEBRIS_W,
|
||
(DEBRIS_H + additional_h)
|
||
);
|
||
}
|
||
|
||
// Worse.
|
||
void unput_and_put(
|
||
x_direction_t ray_id,
|
||
const vortex_or_debris_cel_t &debris_cel,
|
||
pixel_t velocity_x
|
||
) const {
|
||
// ZUN bug: Due to egc_copy_rect_1_to_0_16()'s lack of horizontal
|
||
// clamping, an initially negative X coordinate for the left debris
|
||
// sprite, and an extra sloppy 48-pixel width, this unblitting call
|
||
// will wrap around to the other side of the screen for *both* the left
|
||
// and right sprites, unblitting most of the other sprite as well.
|
||
// It will take until
|
||
//
|
||
// (target_l_x - (DEBRIS_W / 4) - (velocity_x * 2)) >= 32
|
||
//
|
||
// for both the left and right sprite coordinates to have diverged
|
||
// enough for these wraparounds to stop.
|
||
grc_sloppy_unput(
|
||
debris_left(ray_id, (velocity_x * 2)), (target_y - DEBRIS_H)
|
||
);
|
||
|
||
// Also, why only the top half?
|
||
vortex_or_debris_put_8(
|
||
debris_left(ray_id), (target_y - (DEBRIS_H / 2)), debris_cel
|
||
);
|
||
spawnray_unput_and_put(
|
||
OriginX, OriginY, target_x(ray_id), target_y, V_WHITE
|
||
);
|
||
}
|
||
|
||
// Better.
|
||
void unput_and_put(
|
||
x_direction_t ray_id, const vortex_or_debris_cel_t &debris_cel
|
||
) const {
|
||
debris_sloppy_unput(ray_id);
|
||
spawnray_unput_and_put(
|
||
OriginX, OriginY, target_x(ray_id), target_y, V_WHITE
|
||
);
|
||
vortex_or_debris_put_8(
|
||
debris_left(ray_id), (target_y - (DEBRIS_H / 2)), debris_cel
|
||
);
|
||
}
|
||
|
||
void ray_unput_right(pixel_t velocity_x) const {
|
||
// spawnray_unput_and_put() could have done this as well, no need to
|
||
// change APIs...
|
||
// Also, [target_l_x] was updated after the last time the right ray was
|
||
// rendered, so we have to undo this movement here.
|
||
graph_r_line_unput(
|
||
OriginX, OriginY, (target_x(X_RIGHT) + velocity_x), target_y
|
||
);
|
||
}
|
||
|
||
void unput_broken(pixel_t velocity_y) const {
|
||
debris_sloppy_unput(X_RIGHT, -velocity_y, velocity_y);
|
||
debris_sloppy_unput(X_LEFT, -velocity_y, (velocity_y + 10)); // ???
|
||
|
||
// ZUN bug: Should of course be [velocity_y] rather than 4. These calls
|
||
// therefore unblit lines at angles that completely differ from the
|
||
// intended spawn rays. It doesn't matter for the left one, which
|
||
// actually isn't visible when this code gets to run, but the right one
|
||
// does indeed stay visible after this function. You only barely notice
|
||
// it because the one pattern that calls this function then spawns
|
||
// pellets on top of both rays, and it's *their* sloppy unblitting code
|
||
// that ends up gradually unblitting the right ray over the next few
|
||
// frames, as a side effect.
|
||
// Again, spawnray_unput_and_put() would have just done the correct
|
||
// thing.
|
||
graph_r_line_unput(OriginX, OriginY, target_x(X_LEFT), (target_y + 4));
|
||
graph_r_line_unput(OriginX, OriginY, target_x(X_RIGHT), (target_y + 4));
|
||
}
|
||
|
||
void fire_random_to_bottom(
|
||
unsigned char &angle, x_direction_t from_dir, subpixel_t speed
|
||
) const {
|
||
angle = iatan2(
|
||
(PLAYFIELD_BOTTOM - target_y), (playfield_rand_x() - target_l_x)
|
||
);
|
||
if(from_dir == X_LEFT) {
|
||
Pellets.add_single(inhibit_Z3(target_l_x), target_y, angle, speed);
|
||
} else {
|
||
Pellets.add_single(target_x(from_dir), target_y, angle, speed);
|
||
}
|
||
}
|
||
};
|
||
// ------------------------------------
|
||
|
||
enum bird_pellet_group_t {
|
||
BPG_AIMED = 0,
|
||
BPG_6_RING = 1,
|
||
BPG_RANDOM_RAIN = 2,
|
||
};
|
||
|
||
static const int BF_UNPUT_UPDATE_RENDER = 0;
|
||
static const int BF_RESET = 1000;
|
||
static const int BF_FIRE = 1001;
|
||
|
||
void pascal near birds_reset_fire_spawn_unput_update_render(
|
||
double func_or_left,
|
||
double top = 0.0f,
|
||
double velocity_x = 0.0f,
|
||
double velocity_y = 0.0f,
|
||
int8_t unknown = 0
|
||
)
|
||
{
|
||
static char birds_alive[BIRD_COUNT] = { false }; // Should be bool.
|
||
static struct {
|
||
double left[BIRD_COUNT];
|
||
double top[BIRD_COUNT];
|
||
double velocity_x[BIRD_COUNT];
|
||
double velocity_y[BIRD_COUNT];
|
||
int8_t unknown[BIRD_COUNT];
|
||
char hatch_time[BIRD_COUNT];
|
||
char hatch_duration[BIRD_COUNT];
|
||
|
||
double pellet_left(int i) {
|
||
return (left[i] + ((BIRD_W / 2) - (PELLET_W / 2)));
|
||
}
|
||
|
||
double pellet_top(int i) {
|
||
return (top[i] + ((BIRD_H / 2) - (PELLET_H / 2)));
|
||
}
|
||
|
||
// Yes, this is a genuine ZUN abstraction, nothing I refactored.
|
||
// Why would you not just have a per-instance spawn() method?!
|
||
void set_velocity_and_hatch_time(
|
||
const double velocity_x, const double velocity_y,
|
||
double& out_velocity_x, double& out_velocity_y, char& hatch_time
|
||
) {
|
||
out_velocity_x = velocity_x;
|
||
out_velocity_y = velocity_y;
|
||
hatch_time = 25;
|
||
}
|
||
} birds;
|
||
|
||
if(func_or_left == BF_RESET) {
|
||
for(int i = 0; i < BIRD_COUNT; i++) {
|
||
birds_alive[i] = false;
|
||
}
|
||
} else if(func_or_left == (BF_FIRE + BPG_AIMED)) {
|
||
for(int i = 0; i < BIRD_COUNT; i++) {
|
||
if(!birds_alive[i] || birds.hatch_time[i]) {
|
||
continue;
|
||
}
|
||
Pellets.add_group(
|
||
birds.pellet_left(i),
|
||
birds.pellet_top(i),
|
||
PG_1_AIMED,
|
||
to_sp(3.0f)
|
||
);
|
||
}
|
||
} else if(func_or_left == (BF_FIRE + BPG_6_RING)) {
|
||
for(int i = 0; i < BIRD_COUNT; i++) {
|
||
if(!birds_alive[i] || birds.hatch_time[i]) {
|
||
continue;
|
||
}
|
||
for(int j = 0; j < 6; j++) {
|
||
Pellets.add_single(
|
||
birds.pellet_left(i),
|
||
birds.pellet_top(i),
|
||
(j * (0x100 / 6)),
|
||
to_sp(4.0f)
|
||
);
|
||
}
|
||
}
|
||
} else if(func_or_left == (BF_FIRE + BPG_RANDOM_RAIN)) {
|
||
for(int i = 0; i < BIRD_COUNT; i++) {
|
||
if(!birds_alive[i] || birds.hatch_time[i]) {
|
||
continue;
|
||
}
|
||
pellets_add_single_rain(
|
||
birds.pellet_left(i),
|
||
birds.pellet_top(i),
|
||
((rand() & 0x7F) + 0x80),
|
||
4.0f
|
||
);
|
||
}
|
||
} else if(func_or_left != BF_UNPUT_UPDATE_RENDER) {
|
||
// (Yes, this prevents birds from being spawned at [left] == 0.)
|
||
for(int i = 0; i < BIRD_COUNT; i++) {
|
||
if(birds_alive[i] != true) {
|
||
birds_alive[i] = true;
|
||
birds.set_velocity_and_hatch_time(
|
||
velocity_x,
|
||
velocity_y,
|
||
birds.velocity_x[i],
|
||
birds.velocity_y[i],
|
||
birds.hatch_time[i]
|
||
);
|
||
birds.left[i] = func_or_left;
|
||
birds.top[i] = top;
|
||
birds.unknown[i] = unknown;
|
||
birds.hatch_duration[i] = birds.hatch_time[i];
|
||
return;
|
||
}
|
||
}
|
||
} else {
|
||
int i;
|
||
for(i = 0; i < BIRD_COUNT; i++) {
|
||
if(!birds_alive[i]) {
|
||
continue;
|
||
}
|
||
if(birds.hatch_time[i] > 0) {
|
||
bird_put_8(birds.left[i], birds.top[i], ((
|
||
(birds.hatch_duration[i] - birds.hatch_time[i]) /
|
||
(birds.hatch_duration[i] / BIRD_HATCH_CELS)
|
||
) + C_HATCH));
|
||
birds.hatch_time[i]--;
|
||
} else {
|
||
// ZUN bug: Shouldn't these be unblitted unconditionally?
|
||
// Because they aren't, each cel of the hatch animation is
|
||
// blitted on top of the previous one...
|
||
grc_sloppy_unput(birds.left[i], birds.top[i]);
|
||
}
|
||
}
|
||
for(i = 0; i < BIRD_COUNT; i++) {
|
||
if(!birds_alive[i] || birds.hatch_time[i]) {
|
||
continue;
|
||
}
|
||
birds.left[i] += birds.velocity_x[i];
|
||
birds.top[i] += birds.velocity_y[i];
|
||
if(!overlap_xy_lrtb_le_ge(
|
||
birds.left[i], birds.top[i], 0, 0, (RES_X - 1), (RES_Y - 1)
|
||
)) {
|
||
grc_sloppy_unput(birds.left[i], birds.top[i]);
|
||
birds_alive[i] = false;
|
||
continue;
|
||
}
|
||
bird_put_8(birds.left[i], birds.top[i], (
|
||
C_FLY + ((boss_phase_frame / 5) % BIRD_FLY_CELS)
|
||
));
|
||
// 14×38 pixels… still unfair given the shape of such a bird.
|
||
if(
|
||
!player_invincible &&
|
||
(birds.left[i] > (player_left - (PLAYER_W / 4))) &&
|
||
(birds.left[i] < (player_left + (PLAYER_W / 4))) &&
|
||
(birds.top[i] > (player_top - (PLAYER_H / 2) - (BIRD_H / 4))) &&
|
||
(birds.top[i] < (player_top + (PLAYER_H / 2)))
|
||
) {
|
||
done = true;
|
||
delay(100); // ???
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
inline void birds_reset(void) {
|
||
birds_reset_fire_spawn_unput_update_render(BF_RESET);
|
||
}
|
||
|
||
#define birds_spawn(left, top, velocity_x, velocity_y) \
|
||
birds_reset_fire_spawn_unput_update_render( \
|
||
left, top, velocity_x, velocity_y, 1 \
|
||
);
|
||
|
||
#define birds_fire(pellet_group) \
|
||
birds_reset_fire_spawn_unput_update_render(BF_FIRE + pellet_group);
|
||
|
||
inline void birds_unput_update_render(void) {
|
||
birds_reset_fire_spawn_unput_update_render(BF_UNPUT_UPDATE_RENDER);
|
||
}
|
||
|
||
void near shield_render_both(void)
|
||
{
|
||
enum {
|
||
CELS = 4,
|
||
FRAMES_PER_CEL = 10,
|
||
};
|
||
if((boss_phase_frame % FRAMES_PER_CEL) != 0) {
|
||
return;
|
||
}
|
||
ent_shield.set_image(
|
||
(boss_phase_frame % (FRAMES_PER_CEL * CELS)) / FRAMES_PER_CEL
|
||
);
|
||
graph_accesspage_func(1); ent_shield.unlock_put_lock_8();
|
||
graph_accesspage_func(0); ent_shield.unlock_put_lock_8();
|
||
}
|
||
|
||
// Renders a frame of Sariel's wand raise animation on both VRAM pages, and
|
||
// returns `true` once the animation completed.
|
||
bool16 pascal near wand_render_raise_both(bool16 restart = false)
|
||
{
|
||
static int frames = 0;
|
||
|
||
if(restart == true) {
|
||
frames = 0;
|
||
} else if (frames == 0) {
|
||
frames = 1;
|
||
} else {
|
||
frames++;
|
||
if(frames == 2) {
|
||
anm_wand.bos_image = 0;
|
||
graph_accesspage_func(1); anm_wand.put_8();
|
||
graph_accesspage_func(0); anm_wand.put_8();
|
||
} else if(frames == 16) {
|
||
anm_wand.bos_image = 1;
|
||
graph_accesspage_func(1); anm_wand.put_8();
|
||
graph_accesspage_func(0); anm_wand.put_8();
|
||
frames = 0;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Should maybe return `false`, for consistency with wand_render_raise_both().
|
||
void near wand_lower_both(void)
|
||
{
|
||
graph_accesspage_func(1); wand_lowered_put();
|
||
graph_accesspage_func(0); wand_lowered_put();
|
||
}
|
||
|
||
void near dress_render_both(void)
|
||
{
|
||
enum {
|
||
CELS = 4,
|
||
FRAMES_PER_CEL = 15,
|
||
};
|
||
if((boss_phase_frame % FRAMES_PER_CEL) != 0) {
|
||
return;
|
||
}
|
||
int cel = ((boss_phase_frame % (FRAMES_PER_CEL * CELS)) / FRAMES_PER_CEL);
|
||
anm_dress.bos_image = cel;
|
||
graph_accesspage_func(1); anm_dress.put_8();
|
||
graph_accesspage_func(0); anm_dress.put_8();
|
||
}
|
||
|
||
static const subpixel_t VORTEX_PELLET_SPEED = TO_SP(7);
|
||
|
||
#define vortex_pellet_left(vortex_left) \
|
||
((vortex_left) + ((VORTEX_W / 2) - (PELLET_W / 2)))
|
||
|
||
#define vortex_pellet_top(vortex_top) \
|
||
((vortex_top) + ((VORTEX_H / 2) - (PELLET_H / 2)))
|
||
|
||
inline screen_y_t vortex_pellet_bottom(screen_y_t vortex_bottom) {
|
||
return (vortex_bottom - ((VORTEX_H / 2) - (PELLET_H / 2)));
|
||
}
|
||
|
||
// Shouldn't really take [angle] as a parameter...
|
||
void pascal near vortex_fire_3_spread(
|
||
const screen_x_t left[VORTEX_COUNT],
|
||
const vram_y_t top[VORTEX_COUNT],
|
||
int i,
|
||
unsigned char angle
|
||
)
|
||
{
|
||
Pellets.add_single(
|
||
vortex_pellet_left(left[i]),
|
||
vortex_pellet_top(top[i]),
|
||
0x40,
|
||
VORTEX_PELLET_SPEED
|
||
);
|
||
angle = iatan2(
|
||
(vortex_pellet_bottom(PLAYFIELD_BOTTOM) - top[i]),
|
||
(left[1 - i] - left[i])
|
||
);
|
||
Pellets.add_single(
|
||
vortex_pellet_left(left[i]),
|
||
vortex_pellet_top(top[i]),
|
||
angle,
|
||
VORTEX_PELLET_SPEED
|
||
);
|
||
Pellets.add_single(
|
||
vortex_pellet_left(left[i]),
|
||
vortex_pellet_top(top[i]),
|
||
(0x80 - angle),
|
||
VORTEX_PELLET_SPEED
|
||
);
|
||
}
|
||
|
||
void near pattern_vortices(void)
|
||
{
|
||
static bool16 wand_raise_animation_done = false;
|
||
static CEntities<VORTEX_COUNT> cur;
|
||
static CEntities<VORTEX_COUNT> prev;
|
||
static bool16 dir_first; // x_direction_t
|
||
static bool16 dir_second; // x_direction_t
|
||
|
||
#define vortex_unput_and_put_8(i) { \
|
||
grc_sloppy_unput(prev.left[i], prev.top[i]); \
|
||
vortex_or_debris_put_8( \
|
||
cur.left[i], \
|
||
cur.top[i], \
|
||
(C_VORTEX + (boss_phase_frame % VORTEX_CELS)) \
|
||
); \
|
||
}
|
||
|
||
#define vortex_fire_down(i) { \
|
||
Pellets.add_group( \
|
||
vortex_pellet_left(cur.left[i]), \
|
||
vortex_pellet_top(cur.top[i]), \
|
||
PG_1, \
|
||
VORTEX_PELLET_SPEED \
|
||
); \
|
||
}
|
||
|
||
#define vortex_unput_put_3_spread(i) { \
|
||
vortex_unput_and_put_8(i); \
|
||
if((boss_phase_frame % 4) == 0) { \
|
||
vortex_fire_3_spread(cur.left, cur.top, i, angle); \
|
||
} \
|
||
}
|
||
|
||
unsigned char angle;
|
||
|
||
if(wand_raise_animation_done == false) {
|
||
wand_raise_animation_done = wand_render_raise_both();
|
||
}
|
||
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
} else if(boss_phase_frame == 50) {
|
||
select_for_rank(pattern_state.frame, 140, 145, 150, 155);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
cur.left[i] = (i * (PLAYFIELD_RIGHT - VORTEX_W));
|
||
cur.top[i] = (PLAYFIELD_TOP + playfield_fraction_y(17 / 42.0f));
|
||
|
||
vortex_unput_and_put_8(i);
|
||
if((boss_phase_frame % 4) == 0) {
|
||
vortex_fire_down(i);
|
||
}
|
||
prev.left[i] = cur.left[i];
|
||
prev.top[i] = cur.top[i];
|
||
}
|
||
} else if(boss_phase_frame < pattern_state.frame) {
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
cur.left[i] += (i == 0) ? 5 : -5;
|
||
cur.top[i] -= 2;
|
||
|
||
vortex_unput_and_put_8(i);
|
||
if((boss_phase_frame % 4) == 0) {
|
||
vortex_fire_down(i);
|
||
angle = iatan2(
|
||
vortex_pellet_top(PLAYFIELD_BOTTOM - cur.top[i]),
|
||
vortex_pellet_left(cur.left[i] +
|
||
(i * ((PLAYFIELD_W / 8) * 2)) - (PLAYFIELD_W / 8) -
|
||
cur.left[i])
|
||
);
|
||
Pellets.add_single(
|
||
vortex_pellet_left(cur.left[i]),
|
||
vortex_pellet_top(cur.top[i]),
|
||
angle,
|
||
VORTEX_PELLET_SPEED
|
||
);
|
||
}
|
||
prev.left[i] = cur.left[i];
|
||
prev.top[i] = cur.top[i];
|
||
}
|
||
} else if(boss_phase_frame < 200) {
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
vortex_unput_put_3_spread(i);
|
||
}
|
||
} else if(boss_phase_frame < 240) {
|
||
if(boss_phase_frame == 200) {
|
||
static_cast<int>(dir_first) = (rand() % 2);
|
||
if(dir_first == X_LEFT) {
|
||
wand_lower_both();
|
||
}
|
||
}
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
cur.left[i] += (2 - (dir_first * 4));
|
||
vortex_unput_put_3_spread(i);
|
||
prev.left[i] = cur.left[i];
|
||
}
|
||
} else if(boss_phase_frame < 320) {
|
||
if(boss_phase_frame == 240) {
|
||
static_cast<int>(dir_second) = (rand() % 2);
|
||
if((dir_second == X_RIGHT) && (dir_second == dir_first)) {
|
||
wand_lower_both();
|
||
} else if((dir_second == X_LEFT) && (dir_second == dir_first)) {
|
||
wand_raise_animation_done = false;
|
||
}
|
||
}
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
if(dir_second != dir_first) {
|
||
cur.left[i] -= (2 - (dir_second * 4));
|
||
} else {
|
||
cur.left[i] -= (4 - (dir_second * 8));
|
||
}
|
||
vortex_unput_put_3_spread(i);
|
||
prev.left[i] = cur.left[i];
|
||
}
|
||
} else if(boss_phase_frame > 300) {
|
||
for(int i = 0; i < VORTEX_COUNT; i++) {
|
||
grc_sloppy_unput(cur.left[i], cur.top[i]);
|
||
}
|
||
wand_lower_both();
|
||
boss_phase_frame = 0;
|
||
wand_raise_animation_done = false;
|
||
}
|
||
|
||
#undef vortex_unput_put_3_spread
|
||
#undef vortex_fire_down
|
||
#undef vortex_unput_and_put_8
|
||
}
|
||
|
||
void near pattern_random_purple_lasers(void)
|
||
{
|
||
enum {
|
||
LASER_COUNT = 10,
|
||
INTERVAL = 10,
|
||
|
||
KEYFRAME_0 = 50,
|
||
KEYFRAME_1 = (KEYFRAME_0 + (LASER_COUNT * INTERVAL)),
|
||
KEYFRAME_2 = (KEYFRAME_1 + 50),
|
||
};
|
||
|
||
static screen_x_t spawner_x[LASER_COUNT];
|
||
static vram_y_t spawner_y[LASER_COUNT];
|
||
|
||
if(boss_phase_frame < KEYFRAME_0) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == KEYFRAME_0) {
|
||
select_laser_speed_for_rank(pattern_state.speed_multiplied_by_8,
|
||
8.5f, 9.0f, 9.5f, 10.0f
|
||
);
|
||
for(int i = 0; i < LASER_COUNT; i++) {
|
||
spawner_x[i] = playfield_rand_x(0.1875f, 0.8125f);
|
||
spawner_y[i] = playfield_rand_y((9 / 84.0f), (34 / 84.0f));
|
||
}
|
||
}
|
||
if(boss_phase_frame < KEYFRAME_1) {
|
||
int i = ((boss_phase_frame - KEYFRAME_0) / INTERVAL);
|
||
if((boss_phase_frame % INTERVAL) != (INTERVAL - 1)) {
|
||
int radius_cur = (64 - (( boss_phase_frame % INTERVAL) * 8));
|
||
int radius_prev = (64 - (((boss_phase_frame - 1) % INTERVAL) * 8));
|
||
if((boss_phase_frame % INTERVAL) != 0) {
|
||
shape_circle_sloppy_unput(
|
||
spawner_x[i], spawner_y[i], radius_prev, 1
|
||
);
|
||
}
|
||
if((boss_phase_frame % INTERVAL) != (INTERVAL - 2)) {
|
||
shape_circle_put(
|
||
spawner_x[i], spawner_y[i], radius_cur, COL_LASER, 1
|
||
);
|
||
}
|
||
} else {
|
||
mdrv2_se_play(6);
|
||
shootout_lasers[i].spawn(
|
||
spawner_x[i],
|
||
spawner_y[i],
|
||
playfield_rand_x(),
|
||
PLAYFIELD_BOTTOM,
|
||
pattern_state.speed_multiplied_by_8,
|
||
COL_LASER,
|
||
25,
|
||
5
|
||
);
|
||
}
|
||
} else if(boss_phase_frame > KEYFRAME_2) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pattern_birds_on_ellipse_arc(void)
|
||
{
|
||
static bool wand_raise_animation_done = false;
|
||
static bird_pellet_group_t pellet_group = BPG_AIMED;
|
||
static int eggs_alive = 0;
|
||
static CEntities<BIRD_COUNT> egg;
|
||
static Subpixel spawner_left;
|
||
static Subpixel spawner_top;
|
||
static Subpixel spawner_velocity_y;
|
||
static Subpixel spawner_velocity_x;
|
||
|
||
point_t velocity;
|
||
|
||
if(boss_phase_frame < 40) {
|
||
return;
|
||
}
|
||
|
||
if(wand_raise_animation_done == false) {
|
||
wand_raise_animation_done = wand_render_raise_both();
|
||
}
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
} else if(boss_phase_frame == 50) {
|
||
eggs_alive = 0;
|
||
spawner_left.v = to_sp(WAND_EMIT_LEFT);
|
||
spawner_top.v = to_sp(WAND_EMIT_TOP);
|
||
spawner_velocity_x.v = TO_SP(4 - ((rand() % 2) * 8));
|
||
spawner_velocity_y.v = TO_SP(2 - ((rand() % 2) * 4));
|
||
select_for_rank(pattern_state.interval, 20, 15, 10, 8);
|
||
mdrv2_se_play(8);
|
||
} else if(boss_phase_frame < 200) {
|
||
if(spawner_left.v < to_sp(SHIELD_CENTER_X)) {
|
||
spawner_velocity_x.v += to_sp(0.0625f);
|
||
} else {
|
||
spawner_velocity_x.v -= to_sp(0.0625f);
|
||
}
|
||
if(spawner_top.v < to_sp(SHIELD_CENTER_Y)) {
|
||
spawner_velocity_y.v += to_sp(0.0625f);
|
||
} else {
|
||
spawner_velocity_y.v -= to_sp(0.0625f);
|
||
}
|
||
grc_sloppy_unput(spawner_left.to_pixel(), spawner_top.to_pixel());
|
||
for(int i = (eggs_alive - 1); i >= 0; i--) {
|
||
bird_put_8(egg.left[i], egg.top[i], C_EGG);
|
||
}
|
||
spawner_left.v += spawner_velocity_x.v;
|
||
spawner_top.v += spawner_velocity_y.v;
|
||
if((boss_phase_frame % pattern_state.interval) == 0) {
|
||
if(overlap_xy_lrtb_le_ge(
|
||
spawner_left.v, spawner_top.v,
|
||
to_sp(0.0), to_sp(0.0f),
|
||
to_sp(RES_X - SPAWNCROSS_W), to_sp(RES_Y - SPAWNCROSS_H)
|
||
)) {
|
||
egg.left[eggs_alive] = spawner_left.to_pixel();
|
||
egg.top[eggs_alive] = spawner_top.to_pixel();
|
||
bird_put_8(
|
||
spawner_left.to_pixel(), spawner_top.to_pixel(), C_EGG
|
||
);
|
||
eggs_alive++;
|
||
}
|
||
}
|
||
grc_put_8(
|
||
spawner_left.to_pixel(),
|
||
spawner_top.to_pixel(),
|
||
GRC_SLOT_SPAWNCROSS,
|
||
(boss_phase_frame % SPAWNCROSS_CELS),
|
||
((boss_phase_frame % 4) + 2)
|
||
);
|
||
} else if(boss_phase_frame < 400) {
|
||
if(boss_phase_frame == 200) {
|
||
if(abs(spawner_velocity_y.v) < to_sp(0.25f)) {
|
||
spawner_velocity_y.v = (spawner_velocity_y.v < 0)
|
||
? to_sp(-0.25f)
|
||
: to_sp( 0.25f);
|
||
}
|
||
if(abs(spawner_velocity_x.v) < to_sp(0.25f)) {
|
||
spawner_velocity_x.v = (spawner_velocity_x.v < 0)
|
||
? to_sp(-0.25f)
|
||
: to_sp( 0.25f);
|
||
}
|
||
for(int i = (eggs_alive - 1); i >= 0; i--) {
|
||
vector2(velocity.x, velocity.y, 3, ((rand() & 0x7F) + 0x80));
|
||
birds_spawn(egg.left[i], egg.top[i], velocity.x, velocity.y);
|
||
}
|
||
wand_lower_both();
|
||
pellet_group = static_cast<bird_pellet_group_t>(
|
||
rand() % (BPG_6_RING + 1) // excluding random rain here
|
||
);
|
||
}
|
||
|
||
if(overlap_xy_lrtb_le_ge(
|
||
spawner_left, spawner_top,
|
||
to_sp(-SPAWNCROSS_W), to_sp(-SPAWNCROSS_H),
|
||
to_sp(RES_X), to_sp(RES_Y)
|
||
)) {
|
||
grc_sloppy_unput(spawner_left.to_pixel(), spawner_top.to_pixel());
|
||
spawner_left.v += spawner_velocity_x.v;
|
||
spawner_top.v += spawner_velocity_y.v;
|
||
|
||
// ZUN bug: ZUN suddenly forgot that [spawner] uses subpixels, not
|
||
// pixels. Comparing its position to the pixel equivalent of
|
||
// ((RES_X - SPAWNCROSS_W), (RES_Y - SPAWNCROSS_H))
|
||
// effectively clips the top-left coordinate of the spawner to a
|
||
// screen-space rectangle from (0, 0) to (38, 23). Since that's
|
||
// quite far from its actual position, the code below never gets
|
||
// executed.
|
||
if(overlap_xy_lrtb_le_ge(
|
||
spawner_left, spawner_top,
|
||
0, 0,
|
||
(RES_X - SPAWNCROSS_W), // should be subpixels
|
||
(RES_Y - SPAWNCROSS_H) // should be subpixels
|
||
)) {
|
||
// ZUN bug: Also, here. Quite ironic given that there's a
|
||
// correct version of this blitting call above… Copy-pasta
|
||
// confirmed.
|
||
grc_put_8(
|
||
spawner_left, // should be pixels
|
||
spawner_top, // should be pixels
|
||
GRC_SLOT_SPAWNCROSS,
|
||
(boss_phase_frame % SPAWNCROSS_CELS),
|
||
((boss_phase_frame % 4) + 2)
|
||
);
|
||
}
|
||
}
|
||
if(((boss_phase_frame % 16) == 0) || ((boss_phase_frame % 16) == 2)) {
|
||
birds_fire(pellet_group);
|
||
}
|
||
} else if(boss_phase_frame == 400) {
|
||
boss_phase_frame = 0;
|
||
wand_raise_animation_done = false;
|
||
}
|
||
}
|
||
|
||
void pascal near bg_transition(int image_id_new)
|
||
{
|
||
// Terminology:
|
||
// • Gust: The full animation drawn across the whole width of VRAM
|
||
// • Cell: Area drawn in a single loop iteration, randomly shifted
|
||
// • Stripe: 8×1 pixels drawn in the same color, followed by an 8-pixel gap
|
||
|
||
enum {
|
||
VELOCITY_X = 32,
|
||
ROW_SPACING = 4,
|
||
STRIPES_PER_CELL = 5,
|
||
STRIPE_PADDED_VRAM_W = 2,
|
||
};
|
||
|
||
static screen_x_t cell_x;
|
||
static vram_y_t cell_y;
|
||
static vram_offset_t cell_vo;
|
||
static int stripe_col_base;
|
||
static int gust_id;
|
||
|
||
int row;
|
||
vram_word_amount_t stripe_id;
|
||
vram_byte_amount_t cell_offset_right;
|
||
|
||
struct {
|
||
// Storing 8 dots would have been enough.
|
||
dots16_t put[STRIPES_PER_CELL];
|
||
dots16_t unput[STRIPES_PER_CELL];
|
||
|
||
// Yeah, unsafe, but this code is hardware-dependent anyway :P
|
||
dots16_t& operator [](int stripe_id) {
|
||
return put[stripe_id];
|
||
}
|
||
} cell_mask;
|
||
|
||
#define stripe_vram_offset(id_static, id_dynamic) \
|
||
/* The parentheses around the whole expression are omitted for */ \
|
||
/* code generation reasons, since this macro is used as an operand */ \
|
||
/* for pointer arithmetic. */ \
|
||
cell_vo + cell_offset_right - (id_static * STRIPE_PADDED_VRAM_W) - ( \
|
||
id_dynamic * STRIPE_PADDED_VRAM_W \
|
||
)
|
||
|
||
#define stripe_y(id_static, id_dynamic) \
|
||
((stripe_vram_offset(id_static, id_dynamic)) / ROW_SIZE)
|
||
|
||
#define stripe_vram(id_static, id_dynamic) \
|
||
grcg_chunk(stripe_vram_offset(id_static, id_dynamic), 16)
|
||
|
||
#define on_same_y_as_cell(id_static, id_dynamic) ( \
|
||
/* Note how (cell_vo / ROW_SIZE) == cell_y */ \
|
||
((cell_vo / ROW_SIZE) == stripe_y(id_static, id_dynamic)) && \
|
||
(stripe_vram_offset(id_static, id_dynamic) >= 0) \
|
||
)
|
||
|
||
if(boss_phase_frame == 20) {
|
||
gust_id = 0;
|
||
}
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
} else if(boss_phase_frame == 50) {
|
||
cell_x = 0;
|
||
cell_y = 0;
|
||
stripe_col_base = 2;
|
||
cell_vo = vram_offset_shift(0, 0);
|
||
mdrv2_se_play(10);
|
||
}
|
||
|
||
for(row = 0; row < (RES_Y / ROW_SPACING); row++) {
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
cell_offset_right = (rand() % 8);
|
||
|
||
graph_accesspage(1);
|
||
for(stripe_id = 0; stripe_id < (STRIPES_PER_CELL * 2); stripe_id++) {
|
||
if(stripe_y(0, 0) != stripe_y(0, stripe_id)) {
|
||
break;
|
||
}
|
||
if(on_same_y_as_cell(0, stripe_id)) {
|
||
cell_mask[stripe_id] = stripe_vram(0, stripe_id);
|
||
}
|
||
}
|
||
|
||
graph_accesspage(0);
|
||
for(stripe_id = 0; stripe_id < STRIPES_PER_CELL; stripe_id++) {
|
||
if(stripe_y(0, 0) != stripe_y(0, stripe_id)) {
|
||
break;
|
||
}
|
||
if(on_same_y_as_cell(0, stripe_id)) {
|
||
grcg_setcolor_rmw(
|
||
stripe_col_base + ((stripe_id > (STRIPES_PER_CELL / 2))
|
||
? ((STRIPES_PER_CELL - 1) - stripe_id)
|
||
: stripe_id
|
||
));
|
||
stripe_vram(0, stripe_id) = (cell_mask.put[stripe_id] & 0x00FF);
|
||
}
|
||
}
|
||
|
||
grcg_setcolor_rmw(COL_AIR);
|
||
for(stripe_id = 0; stripe_id < (gust_id + 2); stripe_id++) {
|
||
if(stripe_y(STRIPES_PER_CELL, stripe_id) != stripe_y(0, 0)) {
|
||
break;
|
||
}
|
||
if(on_same_y_as_cell(STRIPES_PER_CELL, stripe_id)) {
|
||
stripe_vram(STRIPES_PER_CELL, stripe_id) = (
|
||
cell_mask.unput[stripe_id] & 0x00FF
|
||
);
|
||
}
|
||
}
|
||
|
||
cell_y += ROW_SPACING;
|
||
cell_vo = vram_offset_shift(cell_x, cell_y);
|
||
// Yeah, this could have just been done unconditionally outside the
|
||
// loop.
|
||
if(cell_vo >= PLANE_SIZE) {
|
||
cell_y = 0;
|
||
cell_x += VELOCITY_X;
|
||
cell_vo = vram_offset_shift(cell_x, cell_y);
|
||
}
|
||
grcg_off_func();
|
||
}
|
||
|
||
if(cell_x > (RES_X - VELOCITY_X)) {
|
||
gust_id++;
|
||
boss_phase_frame = 49;
|
||
if(gust_id >= 3) {
|
||
z_vsync_wait_and_scrollup(0);
|
||
|
||
graph_accesspage_func(1);
|
||
grp_put_palette_show(BG_IMAGES[image_id_new]);
|
||
graph_copy_accessed_page_to_other();
|
||
stage_palette_set(z_Palettes);
|
||
|
||
graph_accesspage_func(0);
|
||
hud_rerender();
|
||
hud_hp_rerender(boss_hp);
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
#undef on_same_y_as_cell
|
||
#undef stripe_vram
|
||
#undef stripe_y
|
||
#undef stripe_vram_offset
|
||
}
|
||
|
||
void pascal near particles2x2_vertical_unput_update_render(bool16 from_bottom)
|
||
{
|
||
// Also indicates whether a particle is alive.
|
||
static uint4_t col[PARTICLE2X2_COUNT] = { 0 };
|
||
|
||
static double left[PARTICLE2X2_COUNT];
|
||
static double top[PARTICLE2X2_COUNT];
|
||
static double velocity_y[PARTICLE2X2_COUNT];
|
||
|
||
int i;
|
||
vram_offset_t vo;
|
||
int first_bit;
|
||
DotRect<dots8_t, PARTICLE2X2_H> dots;
|
||
|
||
if((boss_phase_frame % 7) == 0) {
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] != 0) {
|
||
continue;
|
||
}
|
||
left[i] = (rand() % RES_X);
|
||
top[i] = ((from_bottom == false) ? 0 : (RES_Y - 1 - PARTICLE2X2_H));
|
||
velocity_y[i] = ((from_bottom == false)
|
||
? (( rand() % 15) + 2)
|
||
: ((-rand() % 15) - 8)
|
||
);
|
||
col[i] = COL_PARTICLE2X2;
|
||
break;
|
||
}
|
||
}
|
||
if((boss_phase_frame % 2) != 0) {
|
||
return;
|
||
}
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] == 0) {
|
||
continue;
|
||
}
|
||
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
|
||
particle2x2_linear_vram_offset(vo, first_bit, left[i], top[i]);
|
||
|
||
// Lazy trick to avoid having to touch two adjacent VRAM bytes? Why
|
||
// though, you've got a 16-bit CPU. And why not just shift it to the
|
||
// next VRAM byte (= 1 pixel to the right) rather than 7 pixels to the
|
||
// left? And why is this done every frame?
|
||
if(first_bit == ((BYTE_DOTS - PARTICLE2X2_W) + 1)) {
|
||
first_bit = 0;
|
||
}
|
||
|
||
// Unblit
|
||
graph_accesspage_func(1); particle2x2_snap_2(dots, vo, first_bit);
|
||
grcg_setcolor_rmw(COL_AIR);
|
||
graph_accesspage_func(0); particle2x2_put(vo, first_bit, dots);
|
||
|
||
// Update
|
||
top[i] += velocity_y[i];
|
||
|
||
// Recalculate VRAM offset and clip
|
||
vo = vram_offset_divmul_double(left[i], top[i]);
|
||
if((vo >= (((RES_Y - PARTICLE2X2_H) + 1) * ROW_SIZE) || (vo < 0))) {
|
||
col[i] = 0;
|
||
continue;
|
||
}
|
||
|
||
// Render
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
graph_accesspage_func(1); particle2x2_snap_2(dots, vo, first_bit);
|
||
grcg_setcolor_rmw(col[i]);
|
||
graph_accesspage_func(0); particle2x2_put(vo, first_bit, dots);
|
||
}
|
||
grcg_off();
|
||
}
|
||
|
||
void near pattern_detonating_snowflake(void)
|
||
{
|
||
enum {
|
||
HITBOX_W = 96,
|
||
RADIUS_MAX = (HITBOX_W / 2),
|
||
STEP_OUTER = 0x08,
|
||
STEP_INNER = 0x06,
|
||
};
|
||
|
||
enum phase_t {
|
||
P_RESET = -1,
|
||
P_WAND_RAISE = 0,
|
||
P_FLAKE_SPAWN = 1,
|
||
P_FLAKE_MOVING = 2,
|
||
P_DETONATION_START = 3,
|
||
P_DETONATION_ACTIVE = 5,
|
||
|
||
_phase_t_FORCE_INT16 = 0x7FFF
|
||
};
|
||
|
||
static union {
|
||
phase_t phase;
|
||
int detonation_frame;
|
||
} state = { P_RESET };
|
||
static Subpixel left;
|
||
static screen_x_t star_left;
|
||
static Subpixel top;
|
||
static vram_y_t star_top;
|
||
static Subpixel velocity_y;
|
||
static Subpixel velocity_x;
|
||
static pixel_t radius_outer_1;
|
||
static pixel_t radius_outer_2;
|
||
static pixel_t radius_inner;
|
||
|
||
#define ellipse_put(radius_x, radius_y, col, angle_step) { \
|
||
shape_ellipse_arc_put( \
|
||
left.to_pixel(), player_center_y(), radius_x, radius_y, \
|
||
col, angle_step, 0x00, 0xFF \
|
||
); \
|
||
}
|
||
|
||
#define ellipse_sloppy_unput(radius_x, radius_y, angle_step) { \
|
||
shape_ellipse_arc_sloppy_unput( \
|
||
left.to_pixel(), player_center_y(), radius_x, radius_y, \
|
||
angle_step, 0x00, 0xFF \
|
||
); \
|
||
}
|
||
|
||
if(boss_phase_frame < 10) {
|
||
state.phase = P_RESET;
|
||
select_for_rank(pattern_state.start_frame, 160, 160, 160, 160);
|
||
}
|
||
|
||
if((boss_phase_frame % pattern_state.start_frame) == 0) {
|
||
state.phase = P_WAND_RAISE;
|
||
}
|
||
|
||
if(state.phase == P_WAND_RAISE) {
|
||
state.phase = static_cast<phase_t>(wand_render_raise_both());
|
||
}
|
||
if(state.phase == P_FLAKE_SPAWN) {
|
||
mdrv2_se_play(6);
|
||
left.v = to_sp(WAND_EMIT_LEFT);
|
||
top.v = to_sp(WAND_EMIT_TOP);
|
||
vector2_between(
|
||
WAND_EMIT_LEFT,
|
||
WAND_EMIT_TOP,
|
||
(player_left + (PLAYER_W / 2) - (FLAKE_W / 2)),
|
||
(player_top + (PLAYER_H / 2) - (FLAKE_H / 2)),
|
||
velocity_x.v,
|
||
velocity_y.v,
|
||
to_sp(7.0f)
|
||
);
|
||
state.phase = P_FLAKE_MOVING;
|
||
}
|
||
if(state.phase == P_FLAKE_MOVING) {
|
||
shape8x8_sloppy_unput(left.to_pixel(), top.to_pixel());
|
||
|
||
top.v += velocity_y.v;
|
||
if(top.v >= to_sp(PLAYFIELD_BOTTOM - (FLAKE_H / 2))) {
|
||
state.phase = P_DETONATION_START;
|
||
wand_lower_both();
|
||
return;
|
||
}
|
||
left.v += velocity_x.v;
|
||
shape8x8_flake_put(left.to_pixel(), top.to_pixel(), V_WHITE);
|
||
}
|
||
if(state.phase == P_DETONATION_START) {
|
||
ellipse_put(RADIUS_MAX, 16, COL_DETONATION_START, STEP_OUTER);
|
||
ellipse_put(16, RADIUS_MAX, COL_DETONATION_START, STEP_OUTER);
|
||
ellipse_put(24, 24, V_WHITE, STEP_INNER);
|
||
radius_outer_1 = RADIUS_MAX;
|
||
radius_outer_2 = 16;
|
||
radius_inner = 24;
|
||
// ZUN bug: Assigning a subpixel to a regular pixel. Will affect a
|
||
// single frame, until the X position is randomized again.
|
||
star_left = left.v;
|
||
star_top = (PLAYFIELD_BOTTOM - (rand() % RADIUS_MAX));
|
||
mdrv2_se_play(10);
|
||
}
|
||
if(state.phase >= P_DETONATION_START) {
|
||
state.detonation_frame++;
|
||
}
|
||
if(
|
||
(state.phase >= P_DETONATION_ACTIVE) &&
|
||
((state.detonation_frame % 8) == 0)
|
||
) {
|
||
shape8x8_sloppy_unput(star_left, star_top);
|
||
ellipse_sloppy_unput(radius_outer_1, radius_outer_2, STEP_OUTER);
|
||
ellipse_sloppy_unput(radius_outer_2, radius_outer_1, STEP_OUTER);
|
||
ellipse_sloppy_unput(radius_inner, radius_inner, STEP_INNER);
|
||
|
||
radius_outer_1 -= 6;
|
||
radius_outer_2 += 6;
|
||
radius_inner += 4;
|
||
star_left = ((rand() % ((HITBOX_W * 2) / 3)) + left.to_pixel() - (
|
||
((HITBOX_W * 2) / 6) - (FLAKE_W / 2)
|
||
));
|
||
star_top = (PLAYFIELD_BOTTOM - (rand() % RADIUS_MAX));
|
||
if(radius_outer_1 <= 8) {
|
||
state.phase = P_RESET;
|
||
return;
|
||
}
|
||
|
||
ellipse_put(radius_outer_1, radius_outer_2, COL_DETONATION, STEP_OUTER);
|
||
ellipse_put(radius_outer_2, radius_outer_1, COL_DETONATION, STEP_OUTER);
|
||
ellipse_put(radius_inner, radius_inner, V_WHITE, STEP_INNER);
|
||
shape8x8_star_put(star_left, star_top, V_WHITE);
|
||
}
|
||
|
||
if(
|
||
!player_invincible &&
|
||
((left.to_pixel() + ((HITBOX_W / 2) - (PLAYER_W / 2))) > player_left) &&
|
||
((left.to_pixel() - ((HITBOX_W / 2) + (PLAYER_W / 2))) < player_left) &&
|
||
(state.phase >= P_DETONATION_START)
|
||
) {
|
||
done = true;
|
||
}
|
||
|
||
#undef ellipse_put
|
||
#undef ellipse_sloppy_unput
|
||
}
|
||
|
||
void near pattern_2_rings_from_a2_orbs(void)
|
||
{
|
||
static unsigned char angle;
|
||
static int interval;
|
||
|
||
if(boss_phase_frame < 5) {
|
||
angle = 0x00;
|
||
select_for_rank(interval, 18, 12, 10, 8);
|
||
}
|
||
if((boss_phase_frame % interval) == 0) {
|
||
Pellets.spawn_with_cloud = true;
|
||
Pellets.add_single(
|
||
A2_ORBS_R_CENTER_X, A2_ORBS_CENTER_Y, (0x00 - angle), to_sp(3.5f)
|
||
);
|
||
Pellets.add_single(
|
||
A2_ORBS_R_CENTER_X, A2_ORBS_CENTER_Y, (0x80 - angle), to_sp(3.5f)
|
||
);
|
||
Pellets.add_single(
|
||
A2_ORBS_L_CENTER_X, A2_ORBS_CENTER_Y, (0x00 + angle), to_sp(3.5f)
|
||
);
|
||
Pellets.add_single(
|
||
A2_ORBS_L_CENTER_X, A2_ORBS_CENTER_Y, (0x80 + angle), to_sp(3.5f)
|
||
);
|
||
angle += interval;
|
||
}
|
||
if(boss_phase_frame > 479) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pattern_aimed_sling_clusters(void)
|
||
{
|
||
if(boss_phase_frame == 10) {
|
||
select_for_rank(pattern_state.interval, 4, 3, 2, 2);
|
||
}
|
||
if(
|
||
((boss_phase_frame % 200) >= 150) &&
|
||
((boss_phase_frame % pattern_state.interval) == 0)
|
||
) {
|
||
screen_x_t left;
|
||
vram_y_t top;
|
||
unsigned char angle = rand();
|
||
|
||
left = polar_y(PLAYFIELD_CENTER_X, (PLAYFIELD_W / 16), angle);
|
||
top = polar_x(
|
||
(SEAL_CENTER_Y - (PELLET_H / 2) - 1),
|
||
playfield_fraction_y(5 / 42.0f),
|
||
angle
|
||
);
|
||
Pellets.add_single(left, top, 0x00, 0, PM_SLING_AIMED, to_sp(4.5f));
|
||
}
|
||
if(boss_phase_frame >= 299) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near particles2x2_wavy_unput_update_render()
|
||
{
|
||
// Also indicates whether a particle is alive.
|
||
static uint4_t col[PARTICLE2X2_COUNT] = { 0 };
|
||
|
||
static screen_x_t left[PARTICLE2X2_COUNT];
|
||
static vram_y_t top[PARTICLE2X2_COUNT];
|
||
static pixel_t velocity_y[PARTICLE2X2_COUNT];
|
||
static int age[PARTICLE2X2_COUNT];
|
||
|
||
int i;
|
||
int first_bit;
|
||
vram_offset_t vo;
|
||
screen_x_t wave_left;
|
||
DotRect<dots8_t, PARTICLE2X2_H> dots;
|
||
|
||
if((boss_phase_frame % 7) == 0) {
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] != 0) {
|
||
continue;
|
||
}
|
||
left[i] = (rand() % RES_X);
|
||
top[i] = RES_Y;
|
||
velocity_y[i] = -1;
|
||
age[i] = 0;
|
||
col[i] = COL_PARTICLE2X2;
|
||
break;
|
||
}
|
||
}
|
||
if((boss_phase_frame % 2) != 0) {
|
||
return;
|
||
}
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] == 0) {
|
||
continue;
|
||
}
|
||
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
|
||
wave_left = polar_y(left[i], 16, age[i]);
|
||
vo = vram_offset_shift(wave_left, top[i]);
|
||
first_bit = (wave_left & BYTE_MASK);
|
||
|
||
// Unblit
|
||
graph_accesspage_func(1); particle2x2_snap(dots, vo, first_bit);
|
||
grcg_setcolor_rmw(COL_AIR);
|
||
graph_accesspage_func(0); particle2x2_put(vo, first_bit, dots);
|
||
|
||
// Update
|
||
top[i] += velocity_y[i];
|
||
age[i]++;
|
||
|
||
// Recalculate VRAM offset and clip
|
||
wave_left = polar_y(left[i], 16, age[i]);
|
||
vo = vram_offset_shift(wave_left, top[i]);
|
||
first_bit = (wave_left & BYTE_MASK);
|
||
if(age[i] >= 100) {
|
||
col[i] = 0;
|
||
continue;
|
||
}
|
||
|
||
// Render
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
graph_accesspage_func(1); particle2x2_snap(dots, vo, first_bit);
|
||
grcg_setcolor_rmw(col[i]);
|
||
graph_accesspage_func(0); particle2x2_put(vo, first_bit, dots);
|
||
}
|
||
grcg_off();
|
||
}
|
||
|
||
void near pattern_four_aimed_lasers(void)
|
||
{
|
||
#define ORIGIN_DISTANCE_X_1 (SEAL_CENTER_X - SEAL_RADIUS)
|
||
#define ORIGIN_Y_1 FACE_CENTER_Y
|
||
#define ORIGIN_Y_2 SHIELD_CENTER_Y
|
||
|
||
static screen_x_t origin_x;
|
||
static screen_y_t origin_y;
|
||
static struct {
|
||
pixel_t velocity_y;
|
||
pixel_t velocity_x;
|
||
|
||
void unput_update_render(void) {
|
||
spawnray_unput_and_put(
|
||
PLAYFIELD_CENTER_X, ORIGIN_Y_1, origin_x, origin_y, V_WHITE
|
||
);
|
||
origin_x += velocity_x;
|
||
origin_y += velocity_y;
|
||
}
|
||
} spawnray;
|
||
static screen_x_t target_first;
|
||
|
||
#define spawnray_init(origin_x_, origin_y_, target_left) { \
|
||
origin_x = origin_x_; \
|
||
origin_y = origin_y_; \
|
||
vector2_between( \
|
||
origin_x, \
|
||
origin_y, \
|
||
target_left, \
|
||
PLAYFIELD_BOTTOM, \
|
||
spawnray.velocity_x, \
|
||
spawnray.velocity_y, \
|
||
16 \
|
||
); \
|
||
}
|
||
|
||
#define fire(laser_id, origin_left, origin_top, target_left) { \
|
||
shootout_lasers[laser_id].spawn( \
|
||
origin_left, origin_top, target_left, PLAYFIELD_BOTTOM, \
|
||
pattern_state.speed_multiplied_by_8, V_WHITE, 50, 8 \
|
||
); \
|
||
mdrv2_se_play(6); \
|
||
}
|
||
|
||
if(boss_phase_frame < 5) {
|
||
spawnray_reset();
|
||
}
|
||
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
} else if(boss_phase_frame == 50) {
|
||
spawnray_init(
|
||
(PLAYFIELD_RIGHT - ORIGIN_DISTANCE_X_1), ORIGIN_Y_1, player_left
|
||
);
|
||
|
||
target_first = player_left;
|
||
select_laser_speed_for_rank(pattern_state.speed_multiplied_by_8,
|
||
7.5f, 8.0f, 8.5f, 9.0f
|
||
);
|
||
|
||
fire(0,
|
||
(PLAYFIELD_RIGHT - ORIGIN_DISTANCE_X_1), ORIGIN_Y_1, player_left
|
||
);
|
||
} else if(boss_phase_frame < 100) {
|
||
spawnray.unput_update_render();
|
||
} else if(boss_phase_frame == 100) {
|
||
spawnray_init(
|
||
(PLAYFIELD_LEFT + ORIGIN_DISTANCE_X_1),
|
||
ORIGIN_Y_1,
|
||
((PLAYFIELD_RIGHT - 1) - target_first)
|
||
);
|
||
fire(1, origin_x, origin_y, ((PLAYFIELD_RIGHT - 1) - target_first));
|
||
} else if(boss_phase_frame < 150) {
|
||
spawnray.unput_update_render();
|
||
} else if(boss_phase_frame == 150) {
|
||
spawnray_init(
|
||
(PLAYFIELD_RIGHT - playfield_fraction_x(4 / 80.0f)),
|
||
ORIGIN_Y_2,
|
||
player_left
|
||
);
|
||
fire(2, origin_x, origin_y, player_left);
|
||
} else if(boss_phase_frame < 200) {
|
||
spawnray.unput_update_render();
|
||
} else if(boss_phase_frame == 200) {
|
||
spawnray_init(
|
||
(PLAYFIELD_LEFT + playfield_fraction_x(3 / 80.0f)),
|
||
ORIGIN_Y_2,
|
||
player_left
|
||
);
|
||
fire(3, origin_x, origin_y, player_left);
|
||
} else if(boss_phase_frame < 250) {
|
||
spawnray.unput_update_render();
|
||
} else if(boss_phase_frame > 300) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
|
||
#undef fire
|
||
#undef spawnray_init
|
||
|
||
#undef ORIGIN_Y_2
|
||
#undef ORIGIN_Y_1
|
||
#undef ORIGIN_DISTANCE_X_1
|
||
}
|
||
|
||
void near shake_for_50_frames(void)
|
||
{
|
||
if(boss_phase_frame < 50) {
|
||
if((boss_phase_frame % 8) == 0) {
|
||
z_vsync_wait_and_scrollup(RES_Y - 4);
|
||
} else if((boss_phase_frame % 8) == 4) {
|
||
z_vsync_wait_and_scrollup(RES_Y + 4);
|
||
}
|
||
if((boss_phase_frame % 4) == 0) {
|
||
mdrv2_se_play(7);
|
||
}
|
||
} else if(boss_phase_frame == 50) {
|
||
z_vsync_wait_and_scrollup(0);
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pattern_rain_from_top(void)
|
||
{
|
||
if((boss_phase_frame % 8) != 0) {
|
||
return;
|
||
}
|
||
screen_x_t left = playfield_rand_x();
|
||
vram_y_t top = PLAYFIELD_TOP;
|
||
pellet_group_t group;
|
||
|
||
select_for_rank(
|
||
reinterpret_cast<int &>(group), PG_1, PG_1, PG_1, PG_1_RANDOM_WIDE
|
||
);
|
||
Pellets.add_group(left, top, group, to_sp(2.75f));
|
||
}
|
||
|
||
void near pattern_radial_stacks_and_lasers(void)
|
||
{
|
||
#define CENTER_X PLAYFIELD_CENTER_X
|
||
// Technically wrong as the ray's origin point, but who cares.
|
||
#define CENTER_Y (SHIELD_CENTER_Y - (PELLET_H / 2))
|
||
|
||
static unsigned char angle;
|
||
static unsigned char angle_velocity;
|
||
|
||
screen_x_t target_x;
|
||
screen_y_t target_y;
|
||
|
||
if(boss_phase_frame < 5) {
|
||
spawnray_reset();
|
||
}
|
||
if(boss_phase_frame < 150) {
|
||
return;
|
||
} else if(boss_phase_frame == 150) {
|
||
angle = 0x00;
|
||
angle_velocity = 0x01;
|
||
select_laser_speed_for_rank(pattern_state.speed_multiplied_by_8,
|
||
7.5f, 8.125f, 8.75f, 9.375f
|
||
);
|
||
mdrv2_se_play(6);
|
||
} else if(boss_phase_frame < 200) {
|
||
target_x = polar_x(CENTER_X, SEAL_RADIUS, angle);
|
||
target_y = polar_y(CENTER_Y, SEAL_RADIUS, angle);
|
||
spawnray_unput_and_put(CENTER_X, CENTER_Y, target_x, target_y, V_WHITE);
|
||
angle += angle_velocity;
|
||
angle_velocity++;
|
||
if((boss_phase_frame % 10) == 0) {
|
||
mdrv2_se_play(6);
|
||
}
|
||
} else if(boss_phase_frame == 200) {
|
||
target_x = -PIXEL_NONE;
|
||
target_y = -PIXEL_NONE;
|
||
spawnray_unput_and_put(CENTER_X, CENTER_Y, target_x, target_y, V_WHITE);
|
||
angle = 0x00;
|
||
} else if(boss_phase_frame <= 400) {
|
||
if((boss_phase_frame % 5) == 0) {
|
||
target_x = polar_x(CENTER_X, 600, angle);
|
||
target_y = polar_y(CENTER_Y, 600, angle);
|
||
mdrv2_se_play(7);
|
||
if((boss_phase_frame % 15) == 0) {
|
||
shootout_laser_safe((boss_phase_frame - 215) / 15).spawn(
|
||
CENTER_X, CENTER_Y, target_x, target_y,
|
||
pattern_state.speed_multiplied_by_8, V_WHITE, 20, 4
|
||
);
|
||
} else {
|
||
Pellets.add_single(CENTER_X, CENTER_Y, angle, to_sp(6.25f));
|
||
}
|
||
Pellets.add_single(CENTER_X, CENTER_Y, (angle - 0x04), to_sp(6.0f));
|
||
Pellets.add_single(CENTER_X, CENTER_Y, (angle + 0x04), to_sp(6.0f));
|
||
if(rank >= RANK_HARD) {
|
||
Pellets.add_single(
|
||
CENTER_X, CENTER_Y, (angle - 0x04), to_sp(4.0f)
|
||
);
|
||
Pellets.add_single(
|
||
CENTER_X, CENTER_Y, (angle + 0x04), to_sp(4.0f)
|
||
);
|
||
if(rank == RANK_LUNATIC) {
|
||
Pellets.add_single(CENTER_X, CENTER_Y, angle, to_sp(4.0f));
|
||
Pellets.add_single(CENTER_X, CENTER_Y, angle, to_sp(3.0f));
|
||
}
|
||
}
|
||
angle += 0x0C;
|
||
}
|
||
} else if(boss_phase_frame == 450) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
|
||
#undef CENTER_Y
|
||
#undef CENTER_X
|
||
}
|
||
|
||
void near pattern_symmetric_birds_from_bottom(void)
|
||
{
|
||
enum {
|
||
VELOCITY_X = 2,
|
||
};
|
||
|
||
static SymmetricSpawnraysWithDebris<
|
||
PLAYFIELD_CENTER_X, FACE_CENTER_Y, (DEBRIS_W / 4)
|
||
> rays;
|
||
static point_t velocity;
|
||
static vortex_or_debris_cel_t debris_cel;
|
||
|
||
double target_x; // double?!
|
||
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 50) {
|
||
select_for_rank(pattern_state.unknown, 6, 7, 8, 9);
|
||
rays.target_l_x = VELOCITY_X;
|
||
rays.target_y = (PLAYFIELD_BOTTOM - 1);
|
||
velocity.x = VELOCITY_X;
|
||
velocity.y = -2; // could have been a constant
|
||
debris_cel = C_DEBRIS;
|
||
return;
|
||
} else if(boss_phase_frame < 100) {
|
||
if((boss_phase_frame % 2) == 0) {
|
||
rays.unput_and_put(X_LEFT, debris_cel, VELOCITY_X);
|
||
}
|
||
if((boss_phase_frame % 2) == 1) {
|
||
rays.unput_and_put(X_RIGHT, debris_cel, VELOCITY_X);
|
||
debris_cel = debris_cel_next(debris_cel);
|
||
}
|
||
rays.target_l_x += VELOCITY_X;
|
||
if((boss_phase_frame % 10) == 0) {
|
||
mdrv2_se_play(6);
|
||
|
||
target_x = (((boss_phase_frame - 60) * 10) + rays.target_l_x);
|
||
vector2_between(
|
||
rays.target_x(X_LEFT), (rays.target_y - BIRD_H),
|
||
target_x, 0,
|
||
velocity.x, velocity.y,
|
||
4
|
||
);
|
||
birds_spawn(
|
||
rays.target_x(X_LEFT),
|
||
(rays.target_y - BIRD_H),
|
||
velocity.x,
|
||
velocity.y
|
||
);
|
||
birds_spawn(
|
||
rays.target_x(X_RIGHT),
|
||
(rays.target_y - BIRD_H),
|
||
-velocity.x,
|
||
velocity.y
|
||
);
|
||
}
|
||
if((boss_phase_frame % 6) == 0) {
|
||
birds_fire(BPG_RANDOM_RAIN);
|
||
}
|
||
return;
|
||
} else if(boss_phase_frame == 100) {
|
||
rays.ray_unput_right(VELOCITY_X);
|
||
return;
|
||
} else if((boss_phase_frame % 10) == 0) {
|
||
birds_fire(BPG_RANDOM_RAIN);
|
||
}
|
||
if(boss_phase_frame >= 300) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pattern_four_semicircle_spreads(void)
|
||
{
|
||
enum {
|
||
ORIGIN_X = SHIELD_CENTER_X,
|
||
ORIGIN_Y = (SHIELD_CENTER_Y - (PELLET_H / 2)),
|
||
};
|
||
|
||
int i;
|
||
unsigned char angle;
|
||
|
||
if(boss_phase_frame < 50) {
|
||
return;
|
||
} else if(boss_phase_frame == 50) {
|
||
for((angle = 0x00, i = 0); i < 20; (angle += 0x07, i++)) {
|
||
Pellets.add_single(ORIGIN_X, ORIGIN_Y, angle, to_sp(2.0f));
|
||
}
|
||
} else if(boss_phase_frame == 60) {
|
||
for((angle = 0x04, i = 0); i < 10; (angle += 0x0C, i++)) {
|
||
Pellets.add_single(ORIGIN_X, ORIGIN_Y, angle, to_sp(3.0f));
|
||
}
|
||
} else if(boss_phase_frame == 70) {
|
||
for((angle = 0x04, i = 0); i < 8; (angle += 0x0E, i++)) {
|
||
Pellets.add_single(ORIGIN_X, ORIGIN_Y, angle, to_sp(4.0f));
|
||
}
|
||
} else if(boss_phase_frame == 80) {
|
||
for((angle = 0x0E, i = 0); i < 10; (angle += 0x0A, i++)) {
|
||
Pellets.add_single(ORIGIN_X, ORIGIN_Y, angle, to_sp(3.0f));
|
||
}
|
||
} else if(boss_phase_frame > 150) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pattern_vertical_stacks_from_bottom_then_random_rain_from_top(void)
|
||
{
|
||
enum {
|
||
COLUMN_INTERVAL = 10,
|
||
COLUMN_COUNT = 20,
|
||
SPAWNRAY_VELOCITY_X = (PLAYFIELD_W / 80),
|
||
COLUMN_DISTANCE_X = (PLAYFIELD_W / COLUMN_COUNT),
|
||
|
||
KEYFRAME_0 = 50,
|
||
KEYFRAME_1 = (KEYFRAME_0 + (PLAYFIELD_W / SPAWNRAY_VELOCITY_X)),
|
||
KEYFRAME_2 = (KEYFRAME_1 + 20),
|
||
KEYFRAME_3 = (KEYFRAME_2 + (COLUMN_COUNT * COLUMN_INTERVAL)),
|
||
KEYFRAME_4 = (KEYFRAME_3 + (COLUMN_COUNT * COLUMN_INTERVAL)),
|
||
KEYFRAME_5 = (KEYFRAME_4 + 50),
|
||
};
|
||
|
||
static SymmetricSpawnraysWithDebris<
|
||
PLAYFIELD_CENTER_X, FACE_CENTER_Y, (DEBRIS_W / 4)
|
||
> rays;
|
||
|
||
// ZUN bug: Leaving this uninitalized indeed implies vortex sprites for the
|
||
// first 5 frames, until this actually reaches C_DEBRIS...
|
||
static vortex_or_debris_cel_t debris_cel;
|
||
|
||
unsigned char angle;
|
||
|
||
if(boss_phase_frame < KEYFRAME_0) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == KEYFRAME_0) {
|
||
// Note that the value for Lunatic is less than the COLUMN_INTERVAL.
|
||
// This will move the spawners into another VRAM row before the pattern
|
||
// ends.
|
||
select_for_rank(pattern_state.interval, 16, 13, 10, 8);
|
||
|
||
rays.target_l_x = 2;
|
||
rays.target_y = (PLAYFIELD_BOTTOM - 1);
|
||
} else if(boss_phase_frame < KEYFRAME_1) {
|
||
if((boss_phase_frame % 2) == 0) {
|
||
rays.unput_and_put(X_LEFT, debris_cel, SPAWNRAY_VELOCITY_X);
|
||
}
|
||
if((boss_phase_frame % 2) == 1) {
|
||
rays.unput_and_put(X_RIGHT, debris_cel, SPAWNRAY_VELOCITY_X);
|
||
debris_cel = debris_cel_next(debris_cel);
|
||
}
|
||
rays.target_l_x += SPAWNRAY_VELOCITY_X;
|
||
} else if(boss_phase_frame == KEYFRAME_1) {
|
||
rays.ray_unput_right(SPAWNRAY_VELOCITY_X);
|
||
rays.target_l_x = PLAYFIELD_LEFT;
|
||
} else if(
|
||
(boss_phase_frame > KEYFRAME_2) &&
|
||
(boss_phase_frame < KEYFRAME_3)
|
||
) {
|
||
if((boss_phase_frame % COLUMN_INTERVAL) == 0) {
|
||
for(subpixel_t i = to_sp(0.0f); i < to_sp(4.0f); i += to_sp(1.0f)) {
|
||
// How nice!
|
||
if(abs(player_center_x() - rays.target_x(X_LEFT)) > PLAYER_W) {
|
||
Pellets.add_single(
|
||
rays.target_x(X_LEFT),
|
||
(rays.target_y - PELLET_H),
|
||
-0x40,
|
||
(i + to_sp(3.0f))
|
||
);
|
||
}
|
||
if(abs(player_center_x() - rays.target_x(X_RIGHT)) > PLAYER_W) {
|
||
Pellets.add_single(
|
||
rays.target_x(X_RIGHT),
|
||
(rays.target_y - PELLET_H),
|
||
-0x40,
|
||
(i + to_sp(3.0f))
|
||
);
|
||
}
|
||
}
|
||
rays.target_l_x += COLUMN_DISTANCE_X;
|
||
mdrv2_se_play(7);
|
||
}
|
||
} else if(boss_phase_frame == KEYFRAME_3) {
|
||
rays.target_l_x = PLAYFIELD_LEFT;
|
||
rays.target_y = PLAYFIELD_TOP;
|
||
} else if(boss_phase_frame < KEYFRAME_4) {
|
||
if((boss_phase_frame % pattern_state.interval) == 0) {
|
||
for(int i = 0; i < 2; i++) {
|
||
rays.fire_random_to_bottom(angle, X_LEFT, to_sp(3.0f));
|
||
rays.fire_random_to_bottom(angle, X_RIGHT, to_sp(3.0f));
|
||
}
|
||
rays.target_l_x += COLUMN_DISTANCE_X;
|
||
}
|
||
} else if(boss_phase_frame > KEYFRAME_5) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void near pascal dottedcircle_unput_update_render(
|
||
screen_x_t center_x,
|
||
screen_y_t center_y,
|
||
int frame_1based,
|
||
int interval,
|
||
pixel_t radius_step,
|
||
int col,
|
||
pixel_t radius_initial,
|
||
int duration
|
||
)
|
||
{
|
||
static pixel_t radius_prev;
|
||
static bool16 active;
|
||
|
||
#define radius_cur \
|
||
(((frame_1based / interval) * radius_step) + radius_initial)
|
||
|
||
if(frame_1based == 1) {
|
||
active = true;
|
||
// Yup, no rendering call this frame.
|
||
} else {
|
||
if((active != true) || ((frame_1based % interval) != 0)) {
|
||
return;
|
||
}
|
||
shape_circle_sloppy_unput(center_x, center_y, radius_prev, 0x01);
|
||
if(frame_1based >= duration) {
|
||
active = false;
|
||
return;
|
||
}
|
||
shape_circle_put(center_x, center_y, radius_cur, col, 0x01);
|
||
}
|
||
radius_prev = radius_cur;
|
||
|
||
#undef radius_cur
|
||
}
|
||
|
||
void pascal near particles2x2_horizontal_unput_update_render(int frame)
|
||
{
|
||
// Also indicates whether a particle is alive.
|
||
static uint4_t col[PARTICLE2X2_COUNT] = { 0 };
|
||
|
||
static double left[PARTICLE2X2_COUNT];
|
||
static double top[PARTICLE2X2_COUNT];
|
||
static double velocity_x[PARTICLE2X2_COUNT];
|
||
|
||
int i;
|
||
int first_bit;
|
||
vram_offset_t vo;
|
||
DotRect<dots8_t, PARTICLE2X2_H> dots_right;
|
||
DotRect<dots8_t, PARTICLE2X2_H> dots_left;
|
||
|
||
if((frame % 7) == 0) {
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] != 0) {
|
||
continue;
|
||
}
|
||
left[i] = (rand() % RES_X);
|
||
top[i] = (rand() % RES_Y);
|
||
velocity_x[i] = ((rand() % 2) == 0) ? -6 : 6;
|
||
col[i] = COL_PARTICLE2X2;
|
||
break;
|
||
}
|
||
}
|
||
if((frame % 2) != 0) {
|
||
return;
|
||
}
|
||
for(i = 0; i < PARTICLE2X2_COUNT; i++) {
|
||
if(col[i] == 0) {
|
||
continue;
|
||
}
|
||
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
|
||
particle2x2_linear_vram_offset(vo, first_bit, left[i], top[i]);
|
||
|
||
// Unblit
|
||
graph_accesspage_func(1);
|
||
particle2x2_snap_left_right(dots_left, dots_right, vo, first_bit);
|
||
grcg_setcolor_rmw(COL_AIR);
|
||
graph_accesspage_func(0);
|
||
particle2x2_put_left_right(vo, first_bit, dots_left, dots_right);
|
||
|
||
// Update
|
||
left[i] += velocity_x[i];
|
||
velocity_x[i] += (velocity_x[i] < 0) ? 0.1 : -0.1;
|
||
|
||
// Recalculate VRAM offset and clip
|
||
particle2x2_linear_vram_offset(vo, first_bit, left[i], top[i]);
|
||
if((left[i] >= RES_X) || (left[i] < 0) || (abs(velocity_x[i]) < 0.5)) {
|
||
col[i] = 0;
|
||
continue;
|
||
}
|
||
|
||
// Render
|
||
grcg_setcolor_tcr(COL_AIR);
|
||
graph_accesspage_func(1);
|
||
particle2x2_snap_left_right(dots_left, dots_right, vo, first_bit);
|
||
|
||
grcg_setcolor_rmw(col[i]);
|
||
graph_accesspage_func(0);
|
||
particle2x2_put_left_right(vo, first_bit, dots_left, dots_right);
|
||
}
|
||
grcg_off();
|
||
}
|
||
|
||
struct CurvedSpray {
|
||
screen_point_t target;
|
||
Subpixel speed;
|
||
unsigned char angle; // should be local
|
||
int subpattern_id; // controlled by the pattern
|
||
|
||
void init(void) {
|
||
target.x = PLAYFIELD_LEFT;
|
||
target.y = PLAYFIELD_BOTTOM;
|
||
speed.set(7.0f);
|
||
subpattern_id = 0;
|
||
|
||
// Divisor = number of pellets effectively fired per direction.
|
||
select_for_rank(pattern_state.velocity_x,
|
||
(PLAYFIELD_W / 20),
|
||
(PLAYFIELD_W / 26.66),
|
||
(PLAYFIELD_W / 35.55),
|
||
(PLAYFIELD_W / 40)
|
||
);
|
||
}
|
||
|
||
void fire_and_advance(x_direction_t dir) {
|
||
angle = iatan2((target.y - SEAL_CENTER_Y), (target.x - SEAL_CENTER_X));
|
||
Pellets.add_single(SEAL_CENTER_X, SEAL_CENTER_Y, angle, speed);
|
||
speed -= 0.25f;
|
||
if(dir == X_RIGHT) {
|
||
target.x += pattern_state.velocity_x;
|
||
} else {
|
||
target.x -= pattern_state.velocity_x;
|
||
}
|
||
}
|
||
|
||
void reset(screen_x_t new_target) {
|
||
speed.set(7.0f);
|
||
target.x = new_target;
|
||
}
|
||
|
||
void reset_2(screen_x_t new_target) {
|
||
target.x = new_target;
|
||
speed.set(7.0f);
|
||
}
|
||
};
|
||
|
||
void pascal near pattern_curved_spray_leftright_once(int &frame)
|
||
{
|
||
static CurvedSpray spray;
|
||
|
||
if(frame < 50) {
|
||
return;
|
||
} else if(frame == 50) {
|
||
dottedcircle_unput_update_render(
|
||
SEAL_CENTER_X, SEAL_CENTER_Y, 1, 4, 16, V_WHITE, 32, 160
|
||
);
|
||
}
|
||
|
||
// The duration can be calculated as:
|
||
//
|
||
// target_radius = √((RES_X - SEAL_CENTER_X)² + (RES_Y - SEAL_CENTER_Y)²)
|
||
// duration = (((target_radius - radius_initial) / radius_step) * interval)
|
||
//
|
||
// Good luck doing that at compile time, but given the variables defined
|
||
// here, the result does come out as 88.38. So, close enough.
|
||
dottedcircle_unput_update_render(
|
||
SEAL_CENTER_X, SEAL_CENTER_Y, ((frame - 50) + 1), 4, 16, V_WHITE, 32, 90
|
||
);
|
||
|
||
if(frame < 100) {
|
||
return;
|
||
} else if(frame == 100) {
|
||
spray.init();
|
||
}
|
||
|
||
if(spray.subpattern_id == 0) {
|
||
spray.fire_and_advance(X_RIGHT);
|
||
if(spray.target.x >= PLAYFIELD_RIGHT) {
|
||
spray.subpattern_id = 1;
|
||
spray.reset(PLAYFIELD_RIGHT);
|
||
}
|
||
} else {
|
||
spray.fire_and_advance(X_LEFT);
|
||
if(spray.target.x < PLAYFIELD_LEFT) {
|
||
frame = 0;
|
||
}
|
||
}
|
||
|
||
#undef spray
|
||
}
|
||
|
||
void pascal near pattern_rain_from_seal_center(int &frame)
|
||
{
|
||
enum {
|
||
VELOCITY_Y = 8,
|
||
};
|
||
|
||
static struct SymmetricSpawnraysWithDebris<
|
||
SEAL_CENTER_X, SEAL_CENTER_Y, DEBRIS_W
|
||
> rays;
|
||
static vortex_or_debris_cel_t debris_cel_cur;
|
||
static vortex_or_debris_cel_t debris_cel_prev;
|
||
|
||
if(frame < 50) {
|
||
return;
|
||
} else if(frame == 50) {
|
||
rays.target_l_x = PLAYFIELD_LEFT;
|
||
rays.target_y = (PLAYFIELD_BOTTOM - 1);
|
||
debris_cel_cur = C_DEBRIS;
|
||
debris_cel_prev = C_DEBRIS;
|
||
select_for_rank(pattern_state.pellet_count, 30, 35, 40, 45);
|
||
}
|
||
|
||
if(rays.target_y > SEAL_CENTER_Y) {
|
||
if((frame % 2) == 0) {
|
||
rays.unput_and_put(X_LEFT, debris_cel_cur);
|
||
} else if((frame % 2) == 1) {
|
||
rays.unput_and_put(X_RIGHT, debris_cel_cur);
|
||
debris_cel_prev = debris_cel_cur;
|
||
debris_cel_cur = debris_cel_next(debris_cel_cur);
|
||
rays.target_y -= VELOCITY_Y;
|
||
}
|
||
if((frame % 10) == 0) {
|
||
mdrv2_se_play(6);
|
||
}
|
||
return;
|
||
}
|
||
|
||
mdrv2_se_play(3);
|
||
rays.unput_broken(VELOCITY_Y);
|
||
|
||
for(int i = 0; i < pattern_state.pellet_count; i++) {
|
||
pellets_add_single_rain(
|
||
(PLAYFIELD_LEFT + ((PLAYFIELD_W / pattern_state.pellet_count) * i)),
|
||
SEAL_CENTER_Y,
|
||
rand(),
|
||
1.0f
|
||
);
|
||
}
|
||
frame = 0;
|
||
}
|
||
|
||
void pascal near pattern_curved_spray_leftright_twice(int &frame)
|
||
{
|
||
static CurvedSpray spray;
|
||
|
||
if(frame < 80) {
|
||
return;
|
||
} else if(frame == 80) {
|
||
spray.init();
|
||
}
|
||
if((spray.subpattern_id % 2) == 0) {
|
||
spray.fire_and_advance(X_RIGHT);
|
||
if(spray.target.x >= PLAYFIELD_RIGHT) {
|
||
spray.subpattern_id++;
|
||
spray.reset(PLAYFIELD_RIGHT);
|
||
}
|
||
} else {
|
||
spray.fire_and_advance(X_LEFT);
|
||
if(spray.target.x < PLAYFIELD_LEFT) {
|
||
spray.subpattern_id++;
|
||
spray.reset_2(PLAYFIELD_LEFT);
|
||
if(spray.subpattern_id == 4) {
|
||
frame = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void pascal near pattern_swaying_leaves(int &frame, int spawn_interval_or_reset)
|
||
{
|
||
enum {
|
||
LEAF_COUNT = 30,
|
||
};
|
||
|
||
enum leaf_flag_t {
|
||
LF_FREE = 0,
|
||
LF_SPARK = 1,
|
||
LF_SPLASH = 2,
|
||
|
||
// Separate state because it assigns the velocity for the next one.
|
||
LF_SPLASH_DONE = (LF_SPLASH + LEAFSPLASH_CELS),
|
||
|
||
LF_LEAF,
|
||
|
||
_leaf_flag_t_FORCE_INT16 = 0x7FFF,
|
||
};
|
||
|
||
static leaf_flag_t flag[LEAF_COUNT] = { LF_FREE };
|
||
static DecimalSubpixel left[LEAF_COUNT];
|
||
static DecimalSubpixel top[LEAF_COUNT];
|
||
static DecimalSubpixel velocity_x[LEAF_COUNT];
|
||
static DecimalSubpixel velocity_y[LEAF_COUNT];
|
||
|
||
#define leaf_on_screen(i) \
|
||
overlap_xy_rltb_lt_ge( \
|
||
left[i].v, \
|
||
top[i].v, \
|
||
to_dsp(0.0f), \
|
||
to_dsp(0.0f), \
|
||
to_dsp(RES_X - LEAF_W), \
|
||
to_dsp(RES_Y - LEAF_H) \
|
||
)
|
||
|
||
#define leaf_put(tmp_vo, tmp_first_bit, i, sprite) { \
|
||
if(leaf_on_screen(i)) { \
|
||
tmp_vo = vram_offset_divmul( \
|
||
left[i].to_pixel(), top[i].to_pixel() \
|
||
); \
|
||
tmp_first_bit = (left[i].to_pixel() % BYTE_DOTS); \
|
||
grcg_put_8x8_mono(tmp_vo, tmp_first_bit, sprite[0].row, V_WHITE); \
|
||
} \
|
||
}
|
||
|
||
// Code generation needs them to be separate, unfortunately...
|
||
#include "th01/sprites/leaf_s.csp"
|
||
#include "th01/sprites/leaf_l.csp"
|
||
#include "th01/sprites/leaf_r.csp"
|
||
|
||
int i;
|
||
vram_offset_t vo;
|
||
int first_bit;
|
||
|
||
if(spawn_interval_or_reset == 999) {
|
||
for(i = 0; i < LEAF_COUNT; i++) {
|
||
flag[i] = LF_FREE;
|
||
}
|
||
return;
|
||
}
|
||
if((frame % spawn_interval_or_reset) == 0) {
|
||
for(i = 0; i < LEAF_COUNT; i++) {
|
||
if(flag[i] != LF_FREE) {
|
||
continue;
|
||
}
|
||
left[i].v = (
|
||
to_dsp(PLAYFIELD_LEFT) + (rand() % to_dsp(PLAYFIELD_W))
|
||
);
|
||
top[i].v = (
|
||
to_dsp(FACE_CENTER_Y) +
|
||
(rand() % to_dsp((PLAYFIELD_H * 25) / 84))
|
||
);
|
||
vector2_between(
|
||
left[i].to_pixel(),
|
||
top[i].to_pixel(),
|
||
playfield_rand_x(),
|
||
PLAYFIELD_TOP,
|
||
velocity_x[i].v,
|
||
velocity_y[i].v,
|
||
to_dsp(4.0f)
|
||
);
|
||
flag[i] = LF_SPARK;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if((frame % 2) != 0) {
|
||
return;
|
||
}
|
||
|
||
// Unblit and update
|
||
for(i = 0; i < LEAF_COUNT; i++) {
|
||
if(flag[i] == LF_FREE) {
|
||
continue;
|
||
}
|
||
if(flag[i] == LF_SPARK) {
|
||
if(leaf_on_screen(i)) {
|
||
egc_copy_rect_1_to_0_16_word_w(
|
||
left[i].to_pixel(), top[i].to_pixel(), LEAF_W, LEAF_H
|
||
);
|
||
}
|
||
left[i].v += velocity_x[i].v;
|
||
top[i].v += velocity_y[i].v;
|
||
if(top[i].v < to_dsp(PLAYFIELD_TOP)) {
|
||
flag[i] = LF_SPLASH;
|
||
}
|
||
} else if(flag[i] <= LF_SPLASH_DONE) {
|
||
if(leaf_on_screen(i)) {
|
||
grc_sloppy_unput(left[i].to_pixel(), top[i].to_pixel());
|
||
}
|
||
if(flag[i] == LF_SPLASH_DONE) {
|
||
flag[i] = LF_LEAF;
|
||
velocity_y[i].v = (to_dsp(0.3f) + (rand() % to_dsp(0.2f)));
|
||
velocity_x[i].v = to_dsp(0.2f);
|
||
}
|
||
} else if(flag[i] == LF_LEAF) {
|
||
if(leaf_on_screen(i)) {
|
||
egc_copy_rect_1_to_0_16_word_w(
|
||
left[i].to_pixel(), top[i].to_pixel(), LEAF_W, LEAF_H
|
||
);
|
||
}
|
||
left[i].v += velocity_x[i].v;
|
||
top[i].v += velocity_y[i].v;
|
||
velocity_y[i].v--;
|
||
if(velocity_y[i].v < to_dsp(-0.1f)) {
|
||
velocity_y[i].v = (to_dsp(3.0f) + (rand() % to_dsp(2.0f)));
|
||
velocity_x[i].v = (velocity_x[i].v < to_dsp(0.0f))
|
||
? to_dsp(+2.0f)
|
||
: to_dsp(-2.0f);
|
||
}
|
||
if(top[i].to_pixel() > PLAYFIELD_BOTTOM) {
|
||
flag[i] = LF_FREE;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Render and detect player collisions
|
||
for(i = 0; i < LEAF_COUNT; i++) {
|
||
if(flag[i] == LF_FREE) {
|
||
continue;
|
||
}
|
||
if(flag[i] == LF_SPARK) {
|
||
leaf_put(vo, first_bit, i, sSPARK);
|
||
} else if(flag[i] <= LF_SPLASH_DONE) {
|
||
if(leaf_on_screen(i)) {
|
||
// ZUN bug: Another missing conversion to screen pixels,
|
||
// resulting in the entire leaf splash animation never being
|
||
// actually rendered, and becoming effectively unused.
|
||
grc_put_8(
|
||
left[i].v, // should be pixels
|
||
top[i].v, // should be pixels
|
||
GRC_SLOT_LEAFSPLASH,
|
||
(flag[i] - LF_SPLASH),
|
||
V_WHITE
|
||
);
|
||
}
|
||
if((frame % 4) == 0) {
|
||
static_cast<int>(flag[i])++;
|
||
}
|
||
} else if(flag[i] == LF_LEAF) {
|
||
leaf_put(vo, first_bit, i, ((velocity_x[i].v < to_dsp(0.0f))
|
||
? sLEAF_LEFT
|
||
: sLEAF_RIGHT
|
||
));
|
||
if(
|
||
(top[i].v > to_dsp(player_top)) &&
|
||
(top[i].v < to_dsp(player_bottom() - LEAF_H)) &&
|
||
(left[i].to_pixel() > player_left) &&
|
||
(left[i].to_pixel() < (player_left + PLAYER_W - LEAF_W)) &&
|
||
!player_invincible
|
||
) {
|
||
done = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
#undef leaf_put
|
||
#undef leaf_on_screen
|
||
}
|
||
|
||
void sariel_main(void)
|
||
{
|
||
enum {
|
||
PHASE_FORM1_DEFEATED = 99,
|
||
PHASE_FORM2 = 100,
|
||
PHASE_FORM2_DEFEATED = 101,
|
||
|
||
MAGNITUDE = 16,
|
||
};
|
||
|
||
static bool16 invincible;
|
||
static int invincibility_frame;
|
||
static pixel_t entrance_ring_radius_base;
|
||
static bool initial_hp_rendered;
|
||
static struct {
|
||
int pattern_cur;
|
||
union {
|
||
int patterns_done;
|
||
int pulse_fade_direction;
|
||
} u1;
|
||
int patterns_until_next;
|
||
|
||
void next(int phase) {
|
||
boss_phase = phase;
|
||
boss_phase_frame = 0;
|
||
// Shouldn't this be bg_transition()'s job?
|
||
wand_lowered_snap();
|
||
}
|
||
|
||
void frame_common(bool form2) {
|
||
boss_phase_frame++;
|
||
invincibility_frame++;
|
||
if(!form2) {
|
||
shield_render_both();
|
||
dress_render_both();
|
||
}
|
||
}
|
||
|
||
void frame_bg_transition(int next_bg_id) {
|
||
boss_phase_frame++;
|
||
bg_transition(next_bg_id);
|
||
dress_render_both();
|
||
}
|
||
|
||
void pattern_next(int ring_min, int ring_max) {
|
||
if(ring_max == 1) {
|
||
pattern_cur = (1 - pattern_cur);
|
||
} else {
|
||
pattern_cur = (pattern_cur == ring_max)
|
||
? ring_min
|
||
: (pattern_cur + 1);
|
||
}
|
||
// Modifying this variable during the second form also causes the
|
||
// pulse effect to switch its fade direction. Too inconsequential
|
||
// to be really called a ZUN bug though, and it might have even
|
||
// been sort of intended.
|
||
u1.patterns_done++;
|
||
}
|
||
} phase = { 0, 0, 0 };
|
||
|
||
#define phase_form1_next_if_done(next_phase) { \
|
||
if(boss_hp <= 0) { \
|
||
boss_phase = PHASE_FORM1_DEFEATED; \
|
||
} \
|
||
if( \
|
||
(phase.u1.patterns_done >= phase.patterns_until_next) && \
|
||
!invincible \
|
||
) { \
|
||
phase.u1.patterns_done = 0; \
|
||
boss_phase = next_phase; \
|
||
phase.pattern_cur = 0; \
|
||
boss_phase_frame = 0; \
|
||
} \
|
||
}
|
||
|
||
unsigned int i;
|
||
const unsigned char flash_colors[3] = { 3, 4, 5 };
|
||
|
||
struct {
|
||
bool padding;
|
||
bool colliding_with_orb;
|
||
|
||
void update_and_render(const unsigned char (&flash_colors)[3]) {
|
||
boss_hit_update_and_render(
|
||
invincibility_frame,
|
||
invincible,
|
||
boss_hp,
|
||
flash_colors,
|
||
sizeof(flash_colors),
|
||
10000,
|
||
boss_nop,
|
||
colliding_with_orb,
|
||
shot_hitbox_t(
|
||
FACE_LEFT, FACE_TOP, ((FACE_W * 5) / 4), ((FACE_H * 3) / 4)
|
||
)
|
||
);
|
||
}
|
||
} hit;
|
||
|
||
hit.colliding_with_orb = overlap_xy_xywh_le_ge(
|
||
orb_cur_left, orb_cur_top,
|
||
FACE_LEFT, FACE_TOP, (FACE_W - ORB_W), (FACE_H - ORB_H)
|
||
) || ent_shield.hittest_orb();
|
||
|
||
if(boss_phase == 0) {
|
||
boss_phase_frame = -1;
|
||
invincibility_frame = 0;
|
||
invincible = false;
|
||
boss_palette_snap();
|
||
stage_palette_set(z_Palettes);
|
||
random_seed = frame_rand;
|
||
|
||
while(1) {
|
||
#define frame_half boss_phase_frame
|
||
|
||
unsigned int tmp;
|
||
|
||
frame_half++;
|
||
if(entrance_rings_update_and_render(
|
||
entrance_ring_radius_base, i, tmp, frame_half, 16, 1
|
||
)) {
|
||
boss_phase = 1;
|
||
phase.pattern_cur = 0;
|
||
phase.u1.patterns_done = 0;
|
||
phase.patterns_until_next = ((rand() % 6) + 1);
|
||
boss_phase_frame = 0;
|
||
initial_hp_rendered = 0;
|
||
boss_palette_show(); // Unnecessary.
|
||
ent_shield.pos_cur_set(SHIELD_LEFT, SHIELD_TOP);
|
||
wand_lowered_snap();
|
||
wand_render_raise_both(true);
|
||
birds_reset();
|
||
break;
|
||
}
|
||
entrance_rings_still_active:
|
||
if(frame_half % 2) { // That's why we've renamed the variable
|
||
frame_delay(1);
|
||
}
|
||
#undef frame_half
|
||
}
|
||
} else if(boss_phase == 1) {
|
||
hud_hp_increment_render(initial_hp_rendered, boss_hp, boss_phase_frame);
|
||
phase.frame_common(false);
|
||
birds_unput_update_render();
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_random_purple_lasers();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_vortices();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_birds_on_ellipse_arc();
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
phase.pattern_next(0, 2);
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
phase_form1_next_if_done(2);
|
||
} else if(boss_phase == 2) {
|
||
phase.frame_bg_transition(1);
|
||
if(boss_phase_frame == 0) {
|
||
phase.next(3);
|
||
// Assume that the palette didn't change between background ID 0
|
||
// and 1...
|
||
// boss_palette_snap();
|
||
phase.patterns_until_next = ((rand() % 5) + 1);
|
||
}
|
||
} else if(boss_phase == 3) {
|
||
phase.frame_common(false);
|
||
particles2x2_vertical_unput_update_render(false);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_detonating_snowflake();
|
||
pattern_2_rings_from_a2_orbs();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_aimed_sling_clusters();
|
||
pattern_2_rings_from_a2_orbs();
|
||
} else if(boss_phase_frame < 60) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
phase.pattern_next(0, 1);
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
phase_form1_next_if_done(4);
|
||
} else if(boss_phase == 4) {
|
||
phase.frame_bg_transition(2);
|
||
if(boss_phase_frame == 0) {
|
||
phase.next(5);
|
||
boss_palette_snap();
|
||
phase.patterns_until_next = ((rand() % 4) + 3);
|
||
}
|
||
} else if(boss_phase == 5) {
|
||
phase.frame_common(false);
|
||
particles2x2_wavy_unput_update_render();
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_four_aimed_lasers();
|
||
} else if(phase.pattern_cur == 1) {
|
||
shake_for_50_frames();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_four_aimed_lasers();
|
||
pattern_rain_from_top();
|
||
} else if(phase.pattern_cur == 3) {
|
||
pattern_radial_stacks_and_lasers();
|
||
} else if(phase.pattern_cur == 4) {
|
||
shake_for_50_frames();
|
||
} else if(phase.pattern_cur == 5) {
|
||
pattern_four_aimed_lasers();
|
||
pattern_rain_from_top();
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
phase.pattern_next(10, 5); // ???
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
phase_form1_next_if_done(6);
|
||
} else if(boss_phase == 6) {
|
||
phase.frame_bg_transition(3);
|
||
if(boss_phase_frame == 0) {
|
||
phase.next(7);
|
||
boss_palette_snap();
|
||
phase.patterns_until_next = ((rand() % 5) + 2);
|
||
}
|
||
} else if(boss_phase == 7) {
|
||
phase.frame_common(false);
|
||
particles2x2_vertical_unput_update_render(true);
|
||
birds_unput_update_render();
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_symmetric_birds_from_bottom();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_four_semicircle_spreads();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_vertical_stacks_from_bottom_then_random_rain_from_top();
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
phase.pattern_next(0, 2);
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
phase_form1_next_if_done(8);
|
||
} else if(boss_phase == 8) {
|
||
phase.frame_bg_transition(0);
|
||
if(boss_phase_frame == 0) {
|
||
phase.next(1);
|
||
boss_palette_snap();
|
||
phase.patterns_until_next = ((rand() % 6) + 1);
|
||
}
|
||
} else if(boss_phase == PHASE_FORM1_DEFEATED) {
|
||
boss_phase_frame = 0;
|
||
|
||
// Not that this variable is ever read from, before it's set back to 0
|
||
// at the end of the transition animation.
|
||
invincibility_frame = 399;
|
||
|
||
Shots.unput_and_reset();
|
||
Pellets.unput_and_reset();
|
||
shootout_lasers_unput_and_reset_broken(i, SHOOTOUT_LASER_COUNT);
|
||
|
||
// MODDERS: Move this to a common player reset function.
|
||
orb_cur_left = ORB_LEFT_START;
|
||
orb_cur_top = ORB_TOP_START;
|
||
player_left = PLAYER_LEFT_START;
|
||
orb_force = ORB_FORCE_START;
|
||
orb_force_frame = 0;
|
||
orb_velocity_x = OVX_4_LEFT;
|
||
player_deflecting = false;
|
||
bomb_damaging = false;
|
||
player_reset();
|
||
|
||
invincible = false;
|
||
random_seed = frame_rand;
|
||
mdrv2_bgm_fade_out_nonblock();
|
||
|
||
// boss6.grp is not part of the game? Might have been a defeat graphic.
|
||
graph_accesspage_func(1);
|
||
grp_put_palette_show("boss6.grp");
|
||
z_palette_set_show(0xF, 0x0, 0x0, 0x0);
|
||
boss_palette_snap();
|
||
graph_copy_accessed_page_to_other();
|
||
|
||
graph_accesspage_func(1);
|
||
grp_put("boss6_a5.grp");
|
||
graph_accesspage_func(0);
|
||
|
||
while(1) {
|
||
boss_phase_frame++;
|
||
if(boss_phase_frame < 200) {
|
||
if((boss_phase_frame % 2) == 0) {
|
||
z_palette_set_show(
|
||
0xF, RGB4::max(), RGB4::max(), RGB4::max()
|
||
);
|
||
z_vsync_wait_and_scrollup(RES_Y + MAGNITUDE);
|
||
}
|
||
if((boss_phase_frame % 2) == 1) {
|
||
boss_palette_show();
|
||
z_vsync_wait_and_scrollup(RES_Y - (MAGNITUDE - 2)); // ???
|
||
}
|
||
if((boss_phase_frame % 4) == 0) {
|
||
mdrv2_se_play(9);
|
||
}
|
||
}
|
||
if(boss_phase_frame == 200) {
|
||
boss_palette_show();
|
||
z_vsync_wait_and_scrollup(0);
|
||
}
|
||
if(boss_phase_frame > 200) {
|
||
for(i = 0; i < COMPONENT_COUNT; i++) {
|
||
grp_palette[0xF].v[i] = 0x0;
|
||
}
|
||
// The palette was loaded from boss6_a5.grp earlier.
|
||
pagetrans_diagonal_8x8_with_palette(0, z_Palettes, grp_palette);
|
||
graph_accesspage_func(1);
|
||
mdrv2_bgm_load("syugen.MDT");
|
||
mdrv2_bgm_play();
|
||
grp_put_palette_show("boss6_a6.grp");
|
||
z_palette_set_show(COL_FORM2_PULSE, 0x0, 0x0, 0x0);
|
||
graph_copy_accessed_page_to_other();
|
||
hud_rerender();
|
||
z_vsync_wait_and_scrollup(0);
|
||
boss_phase_frame = 0;
|
||
invincibility_frame = 0;
|
||
phase.pattern_cur = 0;
|
||
phase.u1.pulse_fade_direction = 0;
|
||
boss_phase = PHASE_FORM2;
|
||
player_invincibility_time = 0;
|
||
player_invincible = false;
|
||
boss_palette_snap();
|
||
stage_palette_set(z_Palettes);
|
||
|
||
boss_hp = 6;
|
||
hud_hp_first_white = 10;
|
||
hud_hp_first_redwhite = 3;
|
||
initial_hp_rendered = false;
|
||
pattern_swaying_leaves(boss_phase_frame, 999);
|
||
return;
|
||
} else {
|
||
frame_delay(1);
|
||
}
|
||
}
|
||
} else if(boss_phase == PHASE_FORM2) {
|
||
hud_hp_increment_render(initial_hp_rendered, boss_hp, boss_phase_frame);
|
||
phase.frame_common(true);
|
||
particles2x2_horizontal_unput_update_render(boss_phase_frame);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
// Hey, that variable is supposed to be off-limits to the main
|
||
// function! (And also completely pointless, since it could have
|
||
// just been done locally for pattern 4.)
|
||
select_for_rank(pattern_state.interval, 56, 32, 24, 20);
|
||
|
||
if(boss_phase_frame > 100) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_curved_spray_leftright_once(boss_phase_frame);
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_rain_from_seal_center(boss_phase_frame);
|
||
} else if(phase.pattern_cur == 3) {
|
||
pattern_curved_spray_leftright_twice(boss_phase_frame);
|
||
} else if(phase.pattern_cur == 4) {
|
||
pattern_swaying_leaves(boss_phase_frame, pattern_state.interval);
|
||
if(boss_phase_frame > 300) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
} else if(phase.pattern_cur == 5) {
|
||
pattern_swaying_leaves(boss_phase_frame, 500);
|
||
if(boss_phase_frame > 200) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
} else if(phase.pattern_cur == 6) {
|
||
pattern_swaying_leaves(boss_phase_frame, 500);
|
||
pattern_curved_spray_leftright_once(boss_phase_frame);
|
||
} else if(phase.pattern_cur == 7) {
|
||
pattern_swaying_leaves(boss_phase_frame, 500);
|
||
pattern_rain_from_seal_center(boss_phase_frame);
|
||
} else if(phase.pattern_cur == 8) {
|
||
pattern_swaying_leaves(boss_phase_frame, 32);
|
||
pattern_curved_spray_leftright_twice(boss_phase_frame);
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
phase.pattern_next(4, 8);
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
|
||
if((boss_phase_frame % 10) == 0) {
|
||
// Setting just the COL_FORM2_PULSE entry in this palette surely
|
||
// doesn't require resetting the hardware palette to the intended
|
||
// boss palette, modifying the one entry, and then capturing the
|
||
// palette again...
|
||
boss_palette_show();
|
||
if(phase.u1.pulse_fade_direction == 0) {
|
||
if(z_Palettes[COL_FORM2_PULSE].c.r < 0xA) {
|
||
z_Palettes[COL_FORM2_PULSE].c.r++;
|
||
} else {
|
||
phase.u1.pulse_fade_direction = 1;
|
||
}
|
||
if(z_Palettes[COL_FORM2_PULSE].c.g < 0xA) {
|
||
z_Palettes[COL_FORM2_PULSE].c.g++;
|
||
}
|
||
if(z_Palettes[COL_FORM2_PULSE].c.b < 0x6) {
|
||
z_Palettes[COL_FORM2_PULSE].c.b++;
|
||
}
|
||
} else {
|
||
if(z_Palettes[COL_FORM2_PULSE].c.r > 0x0) {
|
||
z_Palettes[COL_FORM2_PULSE].c.r--;
|
||
} else {
|
||
phase.u1.pulse_fade_direction = 0;
|
||
}
|
||
if(z_Palettes[COL_FORM2_PULSE].c.g > 0x0) {
|
||
z_Palettes[COL_FORM2_PULSE].c.g--;
|
||
}
|
||
if(z_Palettes[COL_FORM2_PULSE].c.b > 0x0) {
|
||
z_Palettes[COL_FORM2_PULSE].c.b--;
|
||
}
|
||
}
|
||
// Yeah, what a waste.
|
||
z_palette_set_all_show(z_Palettes);
|
||
boss_palette_snap();
|
||
}
|
||
|
||
if(boss_hp <= 0) {
|
||
mdrv2_bgm_stop();
|
||
boss_phase = PHASE_FORM2_DEFEATED;
|
||
boss_phase_frame = 0;
|
||
invincibility_frame = 0;
|
||
}
|
||
} else if(boss_phase == PHASE_FORM2_DEFEATED) {
|
||
graph_accesspage_func(1);
|
||
grp_put_palette_show("boss6_a6.grp");
|
||
|
||
// Actually a different color inside the .GRP! Would have been nicer to
|
||
// reuse the previous state of the color from above.
|
||
z_palette_set_show(COL_FORM2_PULSE, 0x0, 0x0, 0x0);
|
||
|
||
boss_palette_snap(); // No longer necessary.
|
||
|
||
graph_copy_accessed_page_to_other();
|
||
graph_accesspage_func(0);
|
||
|
||
while(1) {
|
||
boss_phase_frame++;
|
||
if(boss_phase_frame < 230) {
|
||
if((boss_phase_frame % 2) == 0) {
|
||
z_vsync_wait_and_scrollup(RES_Y + MAGNITUDE);
|
||
}
|
||
if((boss_phase_frame % 2) == 1) {
|
||
z_vsync_wait_and_scrollup(RES_Y - (MAGNITUDE - 2)); // ???
|
||
}
|
||
if((boss_phase_frame < 200) && ((boss_phase_frame % 4) == 0)) {
|
||
mdrv2_se_play(9);
|
||
}
|
||
}
|
||
if(boss_phase_frame == 170) {
|
||
mdrv2_bgm_fade_out_nonblock();
|
||
}
|
||
if(boss_phase_frame > 190) {
|
||
if((boss_phase_frame % 2) == 0) {
|
||
unsigned int comp;
|
||
z_palette_black_out_step_bugged(i, comp);
|
||
}
|
||
if(boss_phase_frame > 230) {
|
||
break;
|
||
}
|
||
}
|
||
frame_delay(2);
|
||
}
|
||
|
||
z_vsync_wait_and_scrollup(0);
|
||
sariel_free();
|
||
|
||
// Makai clear! Grant 50,000 score points in the fanciest way
|
||
for(i = 0; i < 5; i++) {
|
||
score += 10000;
|
||
}
|
||
game_cleared = true;
|
||
}
|
||
|
||
#undef phase_form1_next_if_done
|
||
}
|