mirror of https://github.com/nmlgc/ReC98.git
1882 lines
50 KiB
C++
1882 lines
50 KiB
C++
/// Jigoku Stage 20 Boss - Konngara
|
||
/// -------------------------------
|
||
|
||
extern "C" {
|
||
#include <stddef.h>
|
||
#include <stdio.h>
|
||
#include "platform.h"
|
||
#include "pc98.h"
|
||
#include "planar.h"
|
||
#include "master.hpp"
|
||
#include "th01/common.h"
|
||
#include "th01/math/area.hpp"
|
||
#include "th01/math/overlap.hpp"
|
||
#include "th01/math/subpixel.hpp"
|
||
#include "th01/math/vector.hpp"
|
||
#include "th01/hardware/frmdelay.h"
|
||
#include "th01/hardware/palette.h"
|
||
#include "th01/hardware/graph.h"
|
||
#include "th01/hardware/egc.h"
|
||
#include "th01/hardware/scrollup.hpp"
|
||
#include "th01/hardware/input.hpp"
|
||
#include "th01/snd/mdrv2.h"
|
||
#include "th01/main/playfld.hpp"
|
||
#include "th01/formats/grp.h"
|
||
#include "th01/formats/grz.h"
|
||
#include "th01/formats/pf.hpp"
|
||
#include "th01/formats/ptn.hpp"
|
||
#include "th01/formats/stagedat.hpp"
|
||
#include "th01/sprites/pellet.h"
|
||
#include "th01/sprites/shape8x8.hpp"
|
||
#include "th01/main/vars.hpp"
|
||
#include "th01/main/boss/entity_a.hpp"
|
||
#include "th01/main/stage/palette.hpp"
|
||
}
|
||
#include "th01/main/stage/stageobj.hpp"
|
||
#include "th01/main/shape.hpp"
|
||
#include "th01/main/player/player.hpp"
|
||
#include "th01/main/player/orb.hpp"
|
||
#include "th01/main/boss/boss.hpp"
|
||
#include "th01/main/boss/palette.hpp"
|
||
#include "th01/main/bullet/pellet.hpp"
|
||
#include "th01/main/bullet/laser_s.hpp"
|
||
#include "th01/main/hud/hp.hpp"
|
||
|
||
static const char* unused_redletters_maybe[] = { "ANGEL", "OF", "DEATH" };
|
||
|
||
// Coordinates
|
||
// -----------
|
||
|
||
static const screen_x_t HEAD_LEFT = 280;
|
||
static const screen_y_t HEAD_TOP = 80;
|
||
static const screen_x_t FACE_LEFT = 280;
|
||
static const screen_y_t FACE_TOP = 128;
|
||
static const screen_x_t CUP_LEFT = 296;
|
||
static const screen_y_t CUP_TOP = 188;
|
||
static const screen_x_t LEFT_SLEEVE_LEFT = 290;
|
||
static const screen_y_t LEFT_SLEEVE_TOP = 200;
|
||
static const screen_x_t EYE_CENTER_X = 316;
|
||
static const screen_y_t EYE_BOTTOM = 140;
|
||
|
||
static const screen_x_t HITBOX_LEFT = 288;
|
||
static const screen_y_t HITBOX_TOP = 120;
|
||
static const pixel_t HITBOX_W = 96;
|
||
static const pixel_t HITBOX_H = 40;
|
||
|
||
// Slash pattern spawners are moved on a triangle along these points.
|
||
static const screen_x_t SWORD_CENTER_X = 410;
|
||
static const screen_y_t SWORD_CENTER_Y = 70;
|
||
static const screen_x_t SLASH_4_CORNER_X = 432;
|
||
static const screen_y_t SLASH_4_CORNER_Y = 232;
|
||
static const screen_x_t SLASH_5_CORNER_X = 198;
|
||
static const screen_y_t SLASH_5_CORNER_Y = 198;
|
||
|
||
static const pixel_t CUP_W = 32;
|
||
|
||
static const screen_x_t CUP_RIGHT = (CUP_LEFT + CUP_W);
|
||
static const screen_x_t CUP_CENTER_X = (CUP_LEFT + (CUP_W / 2));
|
||
// -----------
|
||
|
||
// Other constants
|
||
// ----------------
|
||
|
||
static const pixel_t SLASH_DISTANCE_2_TO_4_X = (
|
||
SLASH_4_CORNER_X - SWORD_CENTER_X
|
||
);
|
||
static const pixel_t SLASH_DISTANCE_2_TO_4_Y = (
|
||
SLASH_4_CORNER_Y - SWORD_CENTER_Y
|
||
);
|
||
static const pixel_t SLASH_DISTANCE_5_TO_4_X = (
|
||
SLASH_5_CORNER_X - SLASH_4_CORNER_X // yes, backwards
|
||
);
|
||
static const pixel_t SLASH_DISTANCE_5_TO_4_Y = (
|
||
SLASH_5_CORNER_Y - SLASH_4_CORNER_Y // yes, backwards
|
||
);
|
||
static const pixel_t SLASH_DISTANCE_5_TO_6_X = (
|
||
SLASH_5_CORNER_X - SWORD_CENTER_X
|
||
);
|
||
static const pixel_t SLASH_DISTANCE_5_TO_6_Y = (
|
||
SLASH_5_CORNER_Y - SWORD_CENTER_Y
|
||
);
|
||
|
||
#define RAIN_G to_sp(0.0625f) /* Rain gravity */
|
||
// ----------------
|
||
|
||
static union {
|
||
int group; // pellet_group_t
|
||
int interval;
|
||
subpixel_t speed;
|
||
pixel_t delta_x;
|
||
int unused;
|
||
} pattern_state;
|
||
|
||
// Entities
|
||
// --------
|
||
|
||
enum face_direction_t {
|
||
FD_RIGHT = 0,
|
||
FD_LEFT = 1,
|
||
FD_CENTER = 2,
|
||
FD_COUNT = 3,
|
||
FD_UNINITIALIZED = 9, // :zunpet:
|
||
|
||
_face_direction_t_FORCE_INT16 = 0x7FFF
|
||
};
|
||
|
||
enum face_expression_t {
|
||
FE_NEUTRAL = 0,
|
||
FE_CLOSED = 1,
|
||
FE_GLARE = 2,
|
||
FE_AIM = 3,
|
||
|
||
_face_expression_t_FORCE_INT16 = 0x7FFF
|
||
};
|
||
|
||
static face_direction_t face_direction = FD_UNINITIALIZED;
|
||
static face_expression_t face_expression = FE_NEUTRAL;
|
||
static bool16 face_direction_can_change = true;
|
||
|
||
#define ent_head boss_entities[0]
|
||
#define ent_face_closed_or_glare boss_entities[1]
|
||
#define ent_face_aim boss_entities[2]
|
||
|
||
#define head_put(direction) \
|
||
ent_head.put_8(HEAD_LEFT, HEAD_TOP, direction);
|
||
|
||
#define face_aim_put(direction) \
|
||
ent_face_aim.put_8(FACE_LEFT, FACE_TOP, direction);
|
||
|
||
#define face_put(expression, direction) \
|
||
ent_face_closed_or_glare.put_8( \
|
||
FACE_LEFT, FACE_TOP, (((expression - FE_CLOSED) * FD_COUNT) + direction) \
|
||
);
|
||
// --------
|
||
|
||
// Snakes
|
||
// ------
|
||
|
||
static const int SNAKE_TRAIL_COUNT = 5;
|
||
|
||
inline screen_x_t snake_target_offset_left(const screen_x_t &to_left) {
|
||
return (to_left + (PLAYER_W / 2) - (DIAMOND_W / 2));
|
||
}
|
||
|
||
#define SNAKE_HOMING_THRESHOLD \
|
||
(PLAYFIELD_TOP + playfield_fraction_y(5, 7) - (DIAMOND_H / 2))
|
||
|
||
template <int SnakeCount> struct Snakes {
|
||
screen_x_t left[SnakeCount][SNAKE_TRAIL_COUNT];
|
||
screen_y_t top[SnakeCount][SNAKE_TRAIL_COUNT];
|
||
pixel_t velocity_x[SnakeCount];
|
||
pixel_t velocity_y[SnakeCount];
|
||
bool16 target_locked[SnakeCount];
|
||
screen_x_t target_left[SnakeCount];
|
||
|
||
int count() const {
|
||
return SnakeCount;
|
||
}
|
||
};
|
||
|
||
#define snakes_wobbly_aim(snakes, snake_i, to_left, speed, tmp_angle) \
|
||
tmp_angle = iatan2( \
|
||
(player_center_y() - snakes.top[snake_i][0]), \
|
||
(snake_target_offset_left(to_left) - snakes.left[snake_i][0]) \
|
||
); \
|
||
tmp_angle += ((rand() % 2) == 0) ? +0x28 : -0x28; \
|
||
vector2( \
|
||
(pixel_t far &)snakes.velocity_x[snake_i], \
|
||
(pixel_t far &)snakes.velocity_y[snake_i], \
|
||
speed, \
|
||
tmp_angle \
|
||
);
|
||
|
||
#define snakes_spawn_and_wobbly_aim( \
|
||
snakes, snake_i, origin_x, origin_y, tmp_i, tmp_angle \
|
||
) \
|
||
for(tmp_i = 0; tmp_i < SNAKE_TRAIL_COUNT; tmp_i++) { \
|
||
snakes.left[snake_i][tmp_i] = origin_x; \
|
||
snakes.top[snake_i][tmp_i] = origin_y; \
|
||
} \
|
||
snakes_wobbly_aim(snakes, snake_i, player_left, 6, tmp_angle)
|
||
|
||
#define snakes_unput_update_render(tmp_i, tmp_j, tmp_angle) \
|
||
for(tmp_i = 0; tmp_i < snakes.count(); tmp_i++) { \
|
||
/* Snake update */ \
|
||
if(snakes.left[i][0] == -PIXEL_NONE) { \
|
||
continue; \
|
||
} \
|
||
/* The last trail sprite is the only one we have to unblit here. */ \
|
||
/* Since we forward-copy the coordinates for the remaining trail */ \
|
||
/* segments, they're then drawn on top of previously blitted */ \
|
||
/* sprites anyway. Nifty! */ \
|
||
shape8x8_sloppy_unput( \
|
||
snakes.left[tmp_i][SNAKE_TRAIL_COUNT - 1], \
|
||
snakes.top[tmp_i][SNAKE_TRAIL_COUNT - 1] \
|
||
); \
|
||
\
|
||
/* Render…? Before update? */ \
|
||
for(tmp_j = (SNAKE_TRAIL_COUNT - 2); tmp_j >= 1; tmp_j--) { \
|
||
shape8x8_diamond_put( \
|
||
snakes.left[tmp_i][tmp_j], snakes.top[i][tmp_j], 9 \
|
||
); \
|
||
} \
|
||
shape8x8_diamond_put(snakes.left[tmp_i][0], snakes.top[tmp_i][0], 7); \
|
||
\
|
||
/* Update */ \
|
||
if(snakes.target_locked[tmp_i] == false) { \
|
||
snakes.target_left[tmp_i] = player_left; \
|
||
} \
|
||
snakes_wobbly_aim(snakes, i, snakes.target_left[tmp_i], 7, angle); \
|
||
if(snakes.top[tmp_i][0] > SNAKE_HOMING_THRESHOLD) { \
|
||
snakes.target_locked[tmp_i] = true; \
|
||
} \
|
||
\
|
||
/* Forward copy */ \
|
||
for(tmp_j = (SNAKE_TRAIL_COUNT - 1); tmp_j >= 1; tmp_j--) { \
|
||
snakes.left[tmp_i][tmp_j] = snakes.left[tmp_i][tmp_j - 1]; \
|
||
snakes.top[tmp_i][tmp_j] = snakes.top[tmp_i][tmp_j - 1]; \
|
||
} \
|
||
\
|
||
snakes.left[tmp_i][0] += snakes.velocity_x[tmp_i]; \
|
||
snakes.top[tmp_i][0] += snakes.velocity_y[tmp_i]; \
|
||
\
|
||
/* Yes, that's a 30×30 hitbox around the player's center point if */ \
|
||
/* the player is not sliding, only leaving out the edges. */ \
|
||
if(overlap_xy_ltrb_lt_gt( \
|
||
snakes.left[tmp_i][0], \
|
||
snakes.top[tmp_i][0], \
|
||
player_left, \
|
||
(player_top + (player_sliding * 10)), \
|
||
(player_right() - DIAMOND_W), \
|
||
(player_bottom() - DIAMOND_H) \
|
||
)) { \
|
||
if(!player_invincible) { \
|
||
done = true; \
|
||
} \
|
||
} \
|
||
}
|
||
|
||
#define snakes_unput_all(snakes, tmp_i, tmp_j) \
|
||
for(tmp_i = 0; tmp_i < snakes.count(); tmp_i++) { \
|
||
for(j = 0; j < SNAKE_TRAIL_COUNT; j++) { \
|
||
shape8x8_sloppy_unput(snakes.left[i][j], snakes.top[i][j]); \
|
||
} \
|
||
}
|
||
// ------
|
||
|
||
// File names
|
||
// ----------
|
||
|
||
#define SCROLL_BG_FN "boss7_d1.grp"
|
||
#include "th01/shiftjis/fns.hpp"
|
||
#define GRZ_FN "boss8.grz"
|
||
// ----------
|
||
|
||
#define select_for_rank konngara_select_for_rank
|
||
#include "th01/main/select_r.cpp"
|
||
|
||
void pellet_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;
|
||
if(col == 99) {
|
||
target_prev_x = -PIXEL_NONE;
|
||
target_prev_y = -PIXEL_NONE;
|
||
// Umm, shouldn't we unblit in this case?
|
||
return;
|
||
}
|
||
if(
|
||
(target_prev_x != -PIXEL_NONE) && (target_prev_y != -PIXEL_NONE) &&
|
||
(target_prev_x >= 0) && (target_prev_x < RES_X) &&
|
||
(target_prev_y >= 0) && (target_prev_y < RES_Y)
|
||
) {
|
||
graph_r_line_unput(origin_x, origin_y, target_prev_x, target_prev_y);
|
||
}
|
||
if(
|
||
(target_x >= 0) && (target_x < RES_X) &&
|
||
(target_y >= 0) && (target_y < RES_Y)
|
||
) {
|
||
graph_r_line(origin_x, origin_y, target_x, target_y, col);
|
||
}
|
||
target_prev_x = target_x;
|
||
target_prev_y = target_y;
|
||
}
|
||
|
||
// Siddhaṃ seed syllables
|
||
// ----------------------
|
||
|
||
#define SIDDHAM_COL 0x9
|
||
|
||
inline void siddham_col_white(void) {
|
||
z_palette_set_show(SIDDHAM_COL, 0xF, 0xF, 0xF);
|
||
}
|
||
|
||
#define siddham_col_white_in_step() \
|
||
if(z_Palettes[SIDDHAM_COL].c.r > 0x0) { \
|
||
z_Palettes[SIDDHAM_COL].c.r--; \
|
||
} \
|
||
if(z_Palettes[SIDDHAM_COL].c.g > 0x9) { \
|
||
z_Palettes[SIDDHAM_COL].c.g--; \
|
||
} \
|
||
if(z_Palettes[SIDDHAM_COL].c.b > 0xA) { \
|
||
z_Palettes[SIDDHAM_COL].c.b--; \
|
||
} \
|
||
z_palette_set_all_show(z_Palettes);
|
||
// ----------------------
|
||
|
||
void konngara_load_and_entrance(int8_t)
|
||
{
|
||
int i;
|
||
int j;
|
||
int in_quarter;
|
||
int ramp_col;
|
||
int ramp_comp;
|
||
int scroll_frame;
|
||
|
||
pellet_interlace = true;
|
||
|
||
text_fillca(' ', (TX_BLACK | TX_REVERSE));
|
||
|
||
// graph_accesspage_func(0);
|
||
grp_put_palette_show(SCROLL_BG_FN);
|
||
stage_palette_set(z_Palettes);
|
||
stageobjs_init_and_render(BOSS_STAGE);
|
||
|
||
graph_accesspage_func(1);
|
||
grp_put_palette_show("boss8_a1.grp");
|
||
|
||
graph_accesspage_func(0);
|
||
mdrv2_bgm_load("ALICE.MDT");
|
||
mdrv2_se_load(SE_FN);
|
||
mdrv2_bgm_play();
|
||
|
||
z_palette_set_black(j, i);
|
||
|
||
text_fillca(' ', TX_WHITE);
|
||
ent_head.load("boss8_1.bos", 0);
|
||
ent_face_closed_or_glare.load("boss8_e1.bos", 1);
|
||
ent_face_aim.load("boss8_e2.bos", 2);
|
||
|
||
// Decelerating scroll
|
||
// -------------------
|
||
|
||
#define quarters_remaining i
|
||
#define line_on_top j
|
||
|
||
line_on_top = 0;
|
||
quarters_remaining = 32; // Should be divisible by 4.
|
||
in_quarter = 0;
|
||
scroll_frame = 0;
|
||
|
||
do {
|
||
z_vsync_wait_and_scrollup(line_on_top);
|
||
line_on_top += quarters_remaining;
|
||
if((in_quarter == 0) && (line_on_top > ((RES_Y / 4) * 1))) {
|
||
in_quarter++;
|
||
quarters_remaining--;
|
||
}
|
||
if((in_quarter == 1) && (line_on_top > ((RES_Y / 4) * 2))) {
|
||
in_quarter++;
|
||
quarters_remaining--;
|
||
}
|
||
if((in_quarter == 2) && (line_on_top > ((RES_Y / 4) * 3))) {
|
||
in_quarter++;
|
||
quarters_remaining--;
|
||
}
|
||
if((in_quarter == 3) && (line_on_top > ((RES_Y / 4) * 4))) {
|
||
in_quarter = 0;
|
||
quarters_remaining--;
|
||
line_on_top -= RES_Y;
|
||
}
|
||
if(quarters_remaining <= 0) {
|
||
break;
|
||
}
|
||
if((scroll_frame % 8) == 0) {
|
||
z_palette_black_in_step_to(ramp_col, ramp_comp, grp_palette)
|
||
}
|
||
scroll_frame++;
|
||
frame_delay(1);
|
||
} while(1);
|
||
|
||
#undef line_on_top
|
||
#undef quarters_remaining
|
||
// -------------------
|
||
|
||
z_vsync_wait_and_scrollup(0);
|
||
grz_load_single(0, GRZ_FN, 0);
|
||
grz_load_single(1, GRZ_FN, 1);
|
||
grz_load_single(2, GRZ_FN, 2);
|
||
grz_load_single(3, GRZ_FN, 3);
|
||
grz_load_single(4, GRZ_FN, 4);
|
||
grz_load_single(5, GRZ_FN, 5);
|
||
grz_load_single(6, GRZ_FN, 6);
|
||
|
||
frame_delay(40);
|
||
|
||
// Shaking and panning
|
||
// -------------------
|
||
|
||
#define MAGNITUDE 16
|
||
#define frame i
|
||
#define line_on_top j
|
||
|
||
// Shake (below)
|
||
for(frame = 0; frame < 32; frame++) {
|
||
z_vsync_wait_and_scrollup(
|
||
(RES_Y + MAGNITUDE) - ((frame % 2) * (MAGNITUDE * 2))
|
||
);
|
||
if((frame % 8) == 0) {
|
||
mdrv2_se_play(9);
|
||
}
|
||
frame_delay(1);
|
||
}
|
||
|
||
// "Pan" up to Konngara
|
||
for(line_on_top = RES_Y; line_on_top >= 0; line_on_top -= (MAGNITUDE * 2)) {
|
||
z_vsync_wait_and_scrollup(line_on_top);
|
||
egc_copy_rows_1_to_0(line_on_top, (MAGNITUDE * 2));
|
||
frame_delay(1);
|
||
}
|
||
|
||
// Shake
|
||
for(frame = 0; frame < 32; frame++) {
|
||
z_vsync_wait_and_scrollup(
|
||
(RES_Y + MAGNITUDE) - ((frame % 2) * MAGNITUDE)
|
||
);
|
||
frame_delay(1);
|
||
}
|
||
|
||
#undef line_on_top
|
||
#undef frame
|
||
#undef MAGNITUDE
|
||
// -------------------
|
||
|
||
frame_delay(30);
|
||
|
||
// Flashing Siddhaṃ seed syllables
|
||
// -------------------------------
|
||
|
||
siddham_col_white();
|
||
grp_put_colorkey("boss8_d1.grp");
|
||
grp_put_colorkey("boss8_d2.grp");
|
||
grp_put_colorkey("boss8_d3.grp");
|
||
grp_put_colorkey("boss8_d4.grp");
|
||
|
||
for(j = 0; j < RGB4::Range; j++) {
|
||
siddham_col_white_in_step();
|
||
frame_delay(10);
|
||
}
|
||
graph_copy_page_back_to_front();
|
||
// -------------------------------
|
||
}
|
||
|
||
void konngara_init(void)
|
||
{
|
||
boss_palette_snap();
|
||
void konngara_setup();
|
||
konngara_setup();
|
||
}
|
||
|
||
void konngara_setup(void)
|
||
{
|
||
boss_hp = 18;
|
||
hud_hp_first_white = 16;
|
||
hud_hp_first_redwhite = 10;
|
||
boss_phase = 0;
|
||
boss_phase_frame = 0;
|
||
face_direction_can_change = true;
|
||
face_expression = FE_NEUTRAL;
|
||
face_direction = FD_CENTER;
|
||
}
|
||
|
||
// Happens to be entirely protected to double frees. Yes, this matters.
|
||
void konngara_free(void)
|
||
{
|
||
bos_entity_free(0);
|
||
bos_entity_free(1);
|
||
bos_entity_free(2);
|
||
for(int i = 0; i < 7; i++) {
|
||
grx_free(i);
|
||
}
|
||
}
|
||
|
||
void face_direction_set_and_put(face_direction_t fd_new)
|
||
{
|
||
if(!face_direction_can_change || (face_direction == fd_new)) {
|
||
return;
|
||
}
|
||
graph_accesspage_func(1); head_put(fd_new);
|
||
graph_accesspage_func(0); head_put(fd_new);
|
||
if(face_expression == FE_AIM) {
|
||
graph_accesspage_func(1); face_aim_put(fd_new);
|
||
graph_accesspage_func(0); face_aim_put(fd_new);
|
||
} else if(face_expression != FE_NEUTRAL) {
|
||
graph_accesspage_func(1); face_put(face_expression, fd_new);
|
||
graph_accesspage_func(0); face_put(face_expression, fd_new);
|
||
}
|
||
face_direction = fd_new;
|
||
}
|
||
|
||
void face_expression_set_and_put(face_expression_t fe_new)
|
||
{
|
||
if(face_expression == fe_new) {
|
||
return;
|
||
}
|
||
if(fe_new == FE_AIM) {
|
||
graph_accesspage_func(1); face_aim_put(face_direction);
|
||
graph_accesspage_func(0); face_aim_put(face_direction);
|
||
} else if(fe_new != FE_NEUTRAL) {
|
||
graph_accesspage_func(1); face_put(fe_new, face_direction);
|
||
graph_accesspage_func(0); face_put(fe_new, face_direction);
|
||
} else {
|
||
graph_accesspage_func(1); head_put(face_direction);
|
||
graph_accesspage_func(0); head_put(face_direction);
|
||
}
|
||
face_expression = fe_new;
|
||
}
|
||
|
||
void slash_put(int image)
|
||
{
|
||
graph_accesspage_func(1); grx_put(image);
|
||
graph_accesspage_func(0); grx_put(image);
|
||
}
|
||
|
||
void pattern_diamond_cross_to_edges_followed_by_rain(void)
|
||
{
|
||
#define DIAMOND_COUNT 4
|
||
#define DIAMOND_ORIGIN_X (PLAYFIELD_CENTER_X - (DIAMOND_W / 2))
|
||
#define DIAMOND_ORIGIN_Y (PLAYFIELD_CENTER_Y + (DIAMOND_H / 2))
|
||
|
||
int i;
|
||
|
||
static struct {
|
||
pixel_t velocity_bottomleft_x, velocity_topleft_x;
|
||
pixel_t velocity_bottomleft_y, velocity_topleft_y;
|
||
screen_x_t left[DIAMOND_COUNT];
|
||
screen_y_t top[DIAMOND_COUNT];
|
||
} diamonds;
|
||
static int frames_with_diamonds_at_edges;
|
||
|
||
#define diamonds_unput(i) \
|
||
for(i = 0; i < DIAMOND_COUNT; i++) { \
|
||
shape8x8_sloppy_unput(diamonds.left[i], diamonds.top[i]); \
|
||
}
|
||
|
||
#define diamonds_put(i) \
|
||
for(i = 0; i < DIAMOND_COUNT; i++) { \
|
||
shape8x8_diamond_put(diamonds.left[i], diamonds.top[i], 9); \
|
||
}
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
} else if(boss_phase_frame == 100) {
|
||
// MODDERS: Just use a local variable.
|
||
select_for_rank(pattern_state.group,
|
||
PG_2_SPREAD_NARROW_AIMED,
|
||
PG_3_SPREAD_NARROW_AIMED,
|
||
PG_5_SPREAD_WIDE_AIMED,
|
||
PG_5_SPREAD_NARROW_AIMED
|
||
);
|
||
|
||
vector2_between(
|
||
DIAMOND_ORIGIN_X, DIAMOND_ORIGIN_Y,
|
||
PLAYFIELD_LEFT, player_center_y(),
|
||
diamonds.velocity_bottomleft_x, diamonds.velocity_bottomleft_y,
|
||
7
|
||
);
|
||
vector2_between(
|
||
DIAMOND_ORIGIN_X, DIAMOND_ORIGIN_Y,
|
||
PLAYFIELD_LEFT, PLAYFIELD_TOP,
|
||
diamonds.velocity_topleft_x, diamonds.velocity_topleft_y,
|
||
7
|
||
);
|
||
|
||
for(i = 0; i < DIAMOND_COUNT; i++) {
|
||
diamonds.left[i] = DIAMOND_ORIGIN_X;
|
||
diamonds.top[i] = DIAMOND_ORIGIN_Y;
|
||
}
|
||
Pellets.add_group(
|
||
(PLAYFIELD_LEFT + (PLAYFIELD_W / 2) - PELLET_W),
|
||
(PLAYFIELD_TOP + playfield_fraction_y(8 / 21.0f) - (PELLET_H / 2)),
|
||
static_cast<pellet_group_t>(pattern_state.group),
|
||
to_sp(3.0f)
|
||
);
|
||
select_for_rank(pattern_state.interval, 18, 16, 14, 12);
|
||
mdrv2_se_play(12);
|
||
} else if(diamonds.left[0] > PLAYFIELD_LEFT) {
|
||
diamonds_unput(i);
|
||
diamonds.left[0] += diamonds.velocity_bottomleft_x;
|
||
diamonds.top[0] += diamonds.velocity_bottomleft_y;
|
||
diamonds.left[1] -= diamonds.velocity_bottomleft_x;
|
||
diamonds.top[1] += diamonds.velocity_bottomleft_y;
|
||
diamonds.left[2] += diamonds.velocity_topleft_x;
|
||
diamonds.top[2] += diamonds.velocity_topleft_y;
|
||
diamonds.left[3] -= diamonds.velocity_topleft_x;
|
||
diamonds.top[3] += diamonds.velocity_topleft_y;
|
||
if(diamonds.left[0] <= PLAYFIELD_LEFT) {
|
||
diamonds.left[0] = PLAYFIELD_LEFT;
|
||
diamonds.left[2] = PLAYFIELD_LEFT;
|
||
diamonds.left[1] = (PLAYFIELD_RIGHT - DIAMOND_W);
|
||
diamonds.left[3] = (PLAYFIELD_RIGHT - DIAMOND_W);
|
||
} else {
|
||
diamonds_put(i);
|
||
}
|
||
return;
|
||
} else if(diamonds.top[0] > PLAYFIELD_TOP) {
|
||
diamonds_unput(i);
|
||
diamonds.top[0] -= 3;
|
||
diamonds.top[1] -= 3;
|
||
diamonds.left[2] += 6;
|
||
diamonds.left[3] -= 6;
|
||
if(diamonds.top[0] <= PLAYFIELD_TOP) {
|
||
diamonds.top[0] = PLAYFIELD_TOP;
|
||
} else {
|
||
diamonds_put(i);
|
||
}
|
||
return;
|
||
} else if(frames_with_diamonds_at_edges < 200) {
|
||
frames_with_diamonds_at_edges++;
|
||
if((frames_with_diamonds_at_edges % pattern_state.interval) == 0) {
|
||
#define speed to_sp(2.5f)
|
||
screen_x_t from_left;
|
||
screen_y_t from_top;
|
||
screen_x_t to_left;
|
||
screen_y_t to_top;
|
||
unsigned char angle;
|
||
|
||
from_left = PLAYFIELD_LEFT;
|
||
from_top = (PLAYFIELD_TOP + playfield_rand_y(25 / 42.0f));
|
||
// Should actually be
|
||
// to_left = (PLAYFIELD_RIGHT - playfield_rand_x(5 / 8.0f));
|
||
to_left = (PLAYFIELD_LEFT +
|
||
playfield_rand_x(5 / 8.0f) + playfield_fraction_x(3 / 8.0f)
|
||
);
|
||
to_top = PLAYFIELD_BOTTOM;
|
||
angle = iatan2((to_top - from_top), (to_left - from_left));
|
||
Pellets.add_single(from_left, from_top, angle, speed, PM_NORMAL);
|
||
|
||
from_left = (PLAYFIELD_RIGHT - PELLET_W);
|
||
from_top = (PLAYFIELD_TOP + playfield_rand_y(25 / 42.0f));
|
||
to_left = (PLAYFIELD_LEFT + playfield_rand_x( 5 / 8.0f));
|
||
to_top = PLAYFIELD_BOTTOM;
|
||
angle = iatan2((to_top - from_top), (to_left - from_left));
|
||
Pellets.add_single(from_left, from_top, angle, speed, PM_NORMAL);
|
||
|
||
from_top = PLAYFIELD_TOP;
|
||
from_left = (PLAYFIELD_LEFT + playfield_rand_x());
|
||
to_top = PLAYFIELD_BOTTOM;
|
||
to_left = (PLAYFIELD_LEFT + playfield_rand_x());
|
||
angle = iatan2((to_top - from_top), (to_left - from_left));
|
||
Pellets.add_single(from_left, from_top, angle, speed, PM_NORMAL);
|
||
|
||
from_top = PLAYFIELD_TOP;
|
||
from_left = (PLAYFIELD_LEFT + playfield_rand_x());
|
||
to_top = PLAYFIELD_BOTTOM;
|
||
to_left = (PLAYFIELD_LEFT + playfield_rand_x());
|
||
angle = iatan2((to_top - from_top), (to_left - from_left));
|
||
Pellets.add_single(from_left, from_top, angle, speed, PM_NORMAL);
|
||
|
||
from_top = PLAYFIELD_TOP;
|
||
from_left = (PLAYFIELD_LEFT + playfield_rand_x());
|
||
Pellets.add_group(from_left, from_top, PG_1_AIMED, speed);
|
||
|
||
#undef speed
|
||
}
|
||
return;
|
||
} else {
|
||
boss_phase_frame = 0;
|
||
}
|
||
frames_with_diamonds_at_edges = 0;
|
||
|
||
#undef diamonds_put
|
||
#undef diamonds_unput
|
||
#undef diamonds
|
||
#undef DIAMOND_ORIGIN_Y
|
||
#undef DIAMOND_ORIGIN_X
|
||
#undef DIAMOND_COUNT
|
||
}
|
||
|
||
void pattern_symmetrical_from_cup_fire(unsigned char angle)
|
||
{
|
||
Pellets.add_single(
|
||
CUP_RIGHT, CUP_TOP, angle, pattern_state.speed, PM_NORMAL
|
||
);
|
||
Pellets.add_single(
|
||
CUP_LEFT, CUP_TOP, angle, pattern_state.speed, PM_NORMAL
|
||
);
|
||
}
|
||
|
||
void pattern_symmetrical_from_cup(void)
|
||
{
|
||
static unsigned char angle;
|
||
static int16_t unused;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_CLOSED);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
angle = 0x40;
|
||
unused = -1;
|
||
select_for_rank(pattern_state.speed,
|
||
to_sp(5.0f), to_sp(5.0f), to_sp(6.0f), to_sp(7.0f)
|
||
);
|
||
}
|
||
if((boss_phase_frame < 140) && ((boss_phase_frame % 8) == 0)) {
|
||
pattern_symmetrical_from_cup_fire(0x40);
|
||
return;
|
||
}
|
||
if((boss_phase_frame < 220) && ((boss_phase_frame % 8) == 0)) {
|
||
angle += 0x05;
|
||
pattern_symmetrical_from_cup_fire(angle);
|
||
pattern_symmetrical_from_cup_fire(0x80 - angle);
|
||
return;
|
||
}
|
||
if((boss_phase_frame < 300) && ((boss_phase_frame % 8) == 0)) {
|
||
angle -= 0x0C;
|
||
pattern_symmetrical_from_cup_fire(angle);
|
||
pattern_symmetrical_from_cup_fire(0x80 - angle);
|
||
}
|
||
if(boss_phase_frame >= 300) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void pattern_two_homing_snakes_and_semicircle_spreads(void)
|
||
{
|
||
static Snakes<2> snakes;
|
||
|
||
int i;
|
||
int j;
|
||
screen_x_t pellet_left;
|
||
screen_y_t pellet_top;
|
||
unsigned char angle;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_GLARE);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
snakes.target_locked[0] = false;
|
||
snakes.target_locked[1] = false;
|
||
snakes_spawn_and_wobbly_aim(snakes, 0, CUP_CENTER_X, CUP_TOP, i, angle);
|
||
snakes.left[1][0] = -PIXEL_NONE;
|
||
mdrv2_se_play(12);
|
||
return;
|
||
}
|
||
if(boss_phase_frame < 500) {
|
||
snakes_unput_update_render(i, j, angle)
|
||
if(boss_phase_frame == 150) {
|
||
snakes_spawn_and_wobbly_aim(
|
||
snakes, 1, CUP_CENTER_X, CUP_TOP, i, angle
|
||
);
|
||
mdrv2_se_play(12);
|
||
}
|
||
|
||
if(
|
||
(boss_phase_frame > (240 - (rank * 40))) &&
|
||
((boss_phase_frame % 40) == 0)
|
||
) {
|
||
enum {
|
||
SPREAD = 10,
|
||
};
|
||
pixel_t velocity_x;
|
||
pixel_t velocity_y;
|
||
Subpixel speed;
|
||
|
||
angle = (rand() % (0x80 / SPREAD));
|
||
pellet_left =
|
||
((boss_phase_frame % 120) == 0) ? SWORD_CENTER_X :
|
||
((boss_phase_frame % 120) == 40) ? EYE_CENTER_X :
|
||
/*boss_phase_frame % 120 == 80 */ CUP_CENTER_X;
|
||
pellet_top =
|
||
((boss_phase_frame % 120) == 0) ? SWORD_CENTER_Y :
|
||
((boss_phase_frame % 120) == 40) ? EYE_BOTTOM :
|
||
/*boss_phase_frame % 120 == 80 */ CUP_TOP;
|
||
|
||
for(i = 0; i < SPREAD; i++) {
|
||
speed.v = (to_sp(2.5f) + (
|
||
((i % 4) == 0) ? to_sp(0.0f) :
|
||
((i % 4) == 1) ? to_sp(1.0f) :
|
||
((i % 4) == 2) ? to_sp(0.0f) :
|
||
/*i % 4 == 3 */ to_sp(2.0f)
|
||
));
|
||
|
||
// That result is never used?
|
||
vector2(velocity_x, velocity_y, speed, angle);
|
||
|
||
Pellets.add_single(
|
||
pellet_left, pellet_top, (0x80 - angle), speed, PM_NORMAL
|
||
);
|
||
Pellets.add_single(
|
||
pellet_left, pellet_top, angle, speed, PM_NORMAL
|
||
);
|
||
angle += (0x80 / SPREAD);
|
||
}
|
||
mdrv2_se_play(7);
|
||
};
|
||
} else {
|
||
snakes_unput_all(snakes, i, j);
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void pattern_aimed_rows_from_top(void)
|
||
{
|
||
enum {
|
||
DIAMOND_SPEED = 8,
|
||
ROW_MARGIN = (PLAYFIELD_W / 10),
|
||
};
|
||
|
||
static point_t diamond_velocity;
|
||
|
||
// screen_point_t would generate too good ASM here
|
||
static screen_x_t diamond_left;
|
||
static screen_y_t diamond_top;
|
||
|
||
static enum {
|
||
RIGHT = 0,
|
||
LEFT = 1,
|
||
DOWN_START = 2,
|
||
DOWN_END = (DOWN_START + (ROW_MARGIN / DIAMOND_SPEED)),
|
||
TO_INITIAL_POSITION = 99,
|
||
|
||
_diamond_direction_t_FORCE_INT16 = 0x7FFF
|
||
} diamond_direction;
|
||
static Subpixel pellet_speed;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
vector2_between(
|
||
LEFT_SLEEVE_LEFT, LEFT_SLEEVE_TOP,
|
||
(PLAYFIELD_LEFT + ROW_MARGIN), PLAYFIELD_TOP,
|
||
diamond_velocity.x, diamond_velocity.y,
|
||
(DIAMOND_SPEED * 2)
|
||
);
|
||
diamond_left = LEFT_SLEEVE_LEFT;
|
||
diamond_top = LEFT_SLEEVE_TOP;
|
||
pellet_speed.set(3.0f);
|
||
mdrv2_se_play(12);
|
||
diamond_direction = TO_INITIAL_POSITION;
|
||
select_for_rank(pattern_state.interval, 12, 10, 8, 6);
|
||
return;
|
||
}
|
||
if(diamond_direction == TO_INITIAL_POSITION) {
|
||
shape8x8_sloppy_unput(diamond_left, diamond_top);
|
||
diamond_left += diamond_velocity.x;
|
||
diamond_top += diamond_velocity.y;
|
||
|
||
if(diamond_top <= PLAYFIELD_TOP) {
|
||
diamond_left = (PLAYFIELD_LEFT + ROW_MARGIN);
|
||
diamond_top = PLAYFIELD_TOP;
|
||
diamond_direction = RIGHT;
|
||
return;
|
||
}
|
||
shape8x8_diamond_put(diamond_left, diamond_top, 9);
|
||
} else if(diamond_direction < TO_INITIAL_POSITION) {
|
||
shape8x8_sloppy_unput(diamond_left, diamond_top);
|
||
|
||
// That's quite the roundabout way of saying "-8, 0, or +8"...
|
||
diamond_left += (DIAMOND_SPEED + (
|
||
(diamond_direction == RIGHT) ? 0 :
|
||
(diamond_direction == LEFT) ? (-DIAMOND_SPEED * 2) :
|
||
/* >= DOWN_START */ -DIAMOND_SPEED
|
||
));
|
||
if(diamond_direction >= DOWN_START) {
|
||
diamond_top += DIAMOND_SPEED;
|
||
reinterpret_cast<int &>(diamond_direction)++;
|
||
if(diamond_direction >= DOWN_END) {
|
||
diamond_direction = (diamond_left > PLAYFIELD_CENTER_X)
|
||
? LEFT
|
||
: RIGHT;
|
||
}
|
||
}
|
||
if(diamond_left > (PLAYFIELD_RIGHT - ROW_MARGIN)) {
|
||
diamond_direction = DOWN_START;
|
||
diamond_left = (PLAYFIELD_RIGHT - ROW_MARGIN);
|
||
} else if(diamond_left < (PLAYFIELD_LEFT + ROW_MARGIN)) {
|
||
diamond_direction = DOWN_START;
|
||
diamond_left = (PLAYFIELD_LEFT + ROW_MARGIN);
|
||
}
|
||
if(diamond_top >= (PLAYFIELD_TOP + (ROW_MARGIN * 3))) {
|
||
boss_phase_frame = 0;
|
||
return;
|
||
}
|
||
if(diamond_direction < DOWN_START) {
|
||
if((boss_phase_frame % pattern_state.interval) == 0) {
|
||
Pellets.add_group(
|
||
diamond_left, diamond_top, PG_1_AIMED, pellet_speed
|
||
);
|
||
pellet_speed += 0.125f;
|
||
}
|
||
}
|
||
shape8x8_diamond_put(diamond_left, diamond_top, 6);
|
||
}
|
||
}
|
||
|
||
void pattern_aimed_spray_from_cup(void)
|
||
{
|
||
static unsigned char spray_offset;
|
||
static unsigned char angle; // should be local
|
||
static int spray_delta; // should be unsigned char
|
||
static int frames_in_current_direction;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_CLOSED);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
spray_offset = 0x20;
|
||
spray_delta = -0x08;
|
||
frames_in_current_direction = 0;
|
||
select_for_rank(pattern_state.interval, 5, 4, 3, 2);
|
||
}
|
||
if((boss_phase_frame % pattern_state.interval) == 0) {
|
||
// Yes, the point from which these are aimed to the top-left player
|
||
// coordinate is quite a bit away from where they're actually fired,
|
||
// leading to some quite imperfect aiming. Probably done on purpose
|
||
// though, and largely mitigated by the spraying motion anyway.
|
||
angle = iatan2(
|
||
(player_top - (CUP_TOP - 4)), (player_left - (CUP_CENTER_X - 34))
|
||
);
|
||
angle += spray_offset;
|
||
spray_offset += spray_delta;
|
||
frames_in_current_direction++;
|
||
if(frames_in_current_direction > 8) {
|
||
spray_delta *= -1;
|
||
frames_in_current_direction = 0;
|
||
}
|
||
Pellets.add_single(
|
||
CUP_CENTER_X, CUP_TOP, angle, to_sp(3.0f), PM_NORMAL
|
||
);
|
||
}
|
||
if(boss_phase_frame >= 700) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
void pattern_four_homing_snakes(void)
|
||
{
|
||
static Snakes<4> snakes;
|
||
|
||
int i;
|
||
int j;
|
||
unsigned char angle;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_GLARE);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
for(i = 0; i < snakes.count(); i++) {
|
||
snakes.target_locked[i] = false;
|
||
}
|
||
snakes_spawn_and_wobbly_aim(snakes, 0, CUP_CENTER_X, CUP_TOP, i, angle);
|
||
for(i = 1; i < snakes.count(); i++) {
|
||
snakes.left[i][0] = -PIXEL_NONE;
|
||
}
|
||
konngara_select_for_rank(pattern_state.unused, 18, 16, 14, 12);
|
||
mdrv2_se_play(12);
|
||
return;
|
||
}
|
||
if(boss_phase_frame < 400) {
|
||
snakes_unput_update_render(i, j, angle);
|
||
if(boss_phase_frame == 150) {
|
||
snakes_spawn_and_wobbly_aim(
|
||
snakes, 1, CUP_CENTER_X, CUP_TOP, i, angle
|
||
);
|
||
mdrv2_se_play(12);
|
||
}
|
||
if(boss_phase_frame == 200) {
|
||
snakes_spawn_and_wobbly_aim(
|
||
snakes, 2, LEFT_SLEEVE_LEFT, LEFT_SLEEVE_TOP, i, angle
|
||
);
|
||
mdrv2_se_play(12);
|
||
}
|
||
if(boss_phase_frame == 250) {
|
||
snakes_spawn_and_wobbly_aim(
|
||
snakes, 3, LEFT_SLEEVE_LEFT, LEFT_SLEEVE_TOP, i, angle
|
||
);
|
||
mdrv2_se_play(12);
|
||
}
|
||
} else {
|
||
snakes_unput_all(snakes, i, j);
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
inline void swordray_unput_put_and_move(
|
||
screen_x_t& end_x, screen_x_t& end_y, screen_x_t delta_x, screen_y_t delta_y
|
||
) {
|
||
pellet_spawnray_unput_and_put(
|
||
SWORD_CENTER_X, SWORD_CENTER_Y, end_x, end_y, 6
|
||
);
|
||
// Gimme those original instructions!
|
||
if(delta_x < 0) { end_x -= -delta_x; } else { end_x += delta_x; }
|
||
if(delta_y < 0) { end_y -= -delta_y; } else { end_y += delta_y; }
|
||
}
|
||
|
||
inline void swordray_unput(const screen_x_t& end_x, const screen_x_t& end_y) {
|
||
graph_r_line_unput(SWORD_CENTER_X, SWORD_CENTER_Y, end_x, end_y);
|
||
}
|
||
|
||
void pattern_rain_from_edges(void)
|
||
{
|
||
static screen_x_t end_x;
|
||
static screen_y_t end_y;
|
||
static int unused;
|
||
|
||
enum {
|
||
SPAWNRAY_SPEED = 8,
|
||
FRAMES_VERTICAL = 25,
|
||
FRAMES_HORIZONTAL = (PLAYFIELD_W / SPAWNRAY_SPEED),
|
||
|
||
KEYFRAME_0 = 100,
|
||
KEYFRAME_1 = (KEYFRAME_0 + FRAMES_VERTICAL), // up
|
||
KEYFRAME_2 = (KEYFRAME_1 + FRAMES_HORIZONTAL), // right
|
||
KEYFRAME_3 = (KEYFRAME_2 + FRAMES_VERTICAL), // down
|
||
KEYFRAME_4 = (KEYFRAME_3 + FRAMES_VERTICAL), // up
|
||
KEYFRAME_5 = (KEYFRAME_4 + FRAMES_HORIZONTAL), // left
|
||
KEYFRAME_6 = (KEYFRAME_5 + FRAMES_VERTICAL), // down
|
||
};
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_AIM);
|
||
}
|
||
if(boss_phase_frame < KEYFRAME_0) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == KEYFRAME_0) {
|
||
end_x = PLAYFIELD_LEFT;
|
||
end_y = (PLAYFIELD_TOP + (SPAWNRAY_SPEED * FRAMES_VERTICAL));
|
||
unused = 1;
|
||
select_for_rank(pattern_state.interval, 5, 3, 2, 2);
|
||
}
|
||
if(boss_phase_frame < KEYFRAME_1) {
|
||
swordray_unput_put_and_move(end_x, end_y, 0, -SPAWNRAY_SPEED);
|
||
} else if(boss_phase_frame == KEYFRAME_1) {
|
||
end_x = PLAYFIELD_LEFT;
|
||
end_y = PLAYFIELD_TOP;
|
||
swordray_unput_put_and_move(end_x, end_y, +SPAWNRAY_SPEED, 0);
|
||
|
||
unused = 0;
|
||
} else if(boss_phase_frame < KEYFRAME_2) {
|
||
swordray_unput_put_and_move(end_x, end_y, +SPAWNRAY_SPEED, 0);
|
||
} else if(boss_phase_frame == KEYFRAME_2) {
|
||
end_x = (PLAYFIELD_RIGHT - 1);
|
||
end_y = PLAYFIELD_TOP;
|
||
swordray_unput_put_and_move(end_x, end_y, 0, +SPAWNRAY_SPEED);
|
||
|
||
unused = 2;
|
||
} else if(boss_phase_frame < KEYFRAME_3) {
|
||
swordray_unput_put_and_move(end_x, end_y, 0, +SPAWNRAY_SPEED);
|
||
} else if(boss_phase_frame == KEYFRAME_3) {
|
||
end_x = (PLAYFIELD_RIGHT - 1);
|
||
end_y = (PLAYFIELD_TOP + (SPAWNRAY_SPEED * FRAMES_VERTICAL));
|
||
swordray_unput_put_and_move(end_x, end_y, 0, -SPAWNRAY_SPEED);
|
||
|
||
unused = 2;
|
||
} else if(boss_phase_frame < KEYFRAME_4) {
|
||
swordray_unput_put_and_move(end_x, end_y, 0, -SPAWNRAY_SPEED);
|
||
} else if(boss_phase_frame == KEYFRAME_4) {
|
||
end_x = (PLAYFIELD_RIGHT - 1);
|
||
end_y = PLAYFIELD_TOP;
|
||
swordray_unput_put_and_move(end_x, end_y, -SPAWNRAY_SPEED, 0);
|
||
|
||
unused = 0;
|
||
} else if(boss_phase_frame < KEYFRAME_5) {
|
||
swordray_unput_put_and_move(end_x, end_y, -SPAWNRAY_SPEED, 0);
|
||
} else if(boss_phase_frame == KEYFRAME_5) {
|
||
end_x = PLAYFIELD_LEFT;
|
||
end_y = PLAYFIELD_TOP;
|
||
swordray_unput_put_and_move(end_x, end_y, 0, +SPAWNRAY_SPEED);
|
||
|
||
unused = 1;
|
||
} else if(boss_phase_frame < KEYFRAME_6) {
|
||
swordray_unput_put_and_move(end_x, end_y, 0, +SPAWNRAY_SPEED);
|
||
} else if(boss_phase_frame == KEYFRAME_6) {
|
||
// Wait, what, changing the end point of the ray immediately before
|
||
// unblitting?! Technically wrong, but since line unblitting uses
|
||
// 32-dot chunks anyway, this doesn't leave any visible glitches.
|
||
end_y -= SPAWNRAY_SPEED;
|
||
swordray_unput(end_x, end_y);
|
||
boss_phase_frame = 0;
|
||
}
|
||
if((boss_phase_frame % 10) == 0) {
|
||
mdrv2_se_play(6);
|
||
}
|
||
if((boss_phase_frame % pattern_state.interval) == 0) {
|
||
Pellets.add_single(
|
||
end_x, end_y, (rand() & 0x7F), to_sp(2.0f), PM_GRAVITY, RAIN_G
|
||
);
|
||
Pellets.add_single(
|
||
end_x, end_y, (rand() & 0x7F), to_sp(2.0f), PM_GRAVITY, RAIN_G
|
||
);
|
||
}
|
||
}
|
||
|
||
enum slash_cel_frame_t {
|
||
SLASH_0_FRAME = 50,
|
||
SLASH_1_FRAME = 60,
|
||
SLASH_2_FRAME = 100,
|
||
SLASH_3_FRAME = 120,
|
||
SLASH_4_FRAME = 140,
|
||
SLASH_4_5_FRAME = 150,
|
||
SLASH_5_FRAME = 160,
|
||
SLASH_6_FRAME = 170,
|
||
|
||
SLASH_FRAMES_FROM_2_TO_4 = (SLASH_4_FRAME - SLASH_2_FRAME),
|
||
// Yes, also backwards
|
||
SLASH_FRAMES_FROM_5_TO_4_5 = (SLASH_4_5_FRAME - SLASH_5_FRAME),
|
||
SLASH_FRAMES_FROM_4_5_TO_6 = (SLASH_6_FRAME - SLASH_4_5_FRAME),
|
||
};
|
||
|
||
void slash_animate(void)
|
||
{
|
||
// MODDERS: Just use a switch.
|
||
#define boss_phase_at_frame(frame) \
|
||
boss_phase_frame < frame) return; if(boss_phase_frame == frame
|
||
|
||
if(boss_phase_at_frame(SLASH_0_FRAME)) {
|
||
slash_put(0);
|
||
mdrv2_se_play(8);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_1_FRAME)) {
|
||
slash_put(1);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_2_FRAME)) {
|
||
slash_put(2);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_3_FRAME)) {
|
||
slash_put(3);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_4_FRAME)) {
|
||
slash_put(4);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_5_FRAME)) {
|
||
slash_put(5);
|
||
}
|
||
if(boss_phase_at_frame(SLASH_6_FRAME)) {
|
||
slash_put(6);
|
||
}
|
||
if(boss_phase_frame > 200) {
|
||
boss_phase_frame = 0;
|
||
face_direction_can_change = true;
|
||
}
|
||
|
||
#undef boss_phase_at_frame
|
||
}
|
||
|
||
#define slash_spawner_step_from_2_to_4(left, top, steps) \
|
||
left += (SLASH_DISTANCE_2_TO_4_X / (SLASH_FRAMES_FROM_2_TO_4 / steps)); \
|
||
top += (SLASH_DISTANCE_2_TO_4_Y / (SLASH_FRAMES_FROM_2_TO_4 / steps));
|
||
|
||
#define slash_spawner_step_from_4_to_4_5(left, top, steps) \
|
||
left -= (SLASH_DISTANCE_5_TO_4_X / (SLASH_FRAMES_FROM_5_TO_4_5 / steps)); \
|
||
top -= (SLASH_DISTANCE_5_TO_4_Y / (SLASH_FRAMES_FROM_5_TO_4_5 / steps));
|
||
|
||
#define slash_spawner_step_from_4_5_to_6(left, top, steps) \
|
||
left -= (SLASH_DISTANCE_5_TO_6_X / (SLASH_FRAMES_FROM_4_5_TO_6 / steps)); \
|
||
top -= (SLASH_DISTANCE_5_TO_6_Y / (SLASH_FRAMES_FROM_4_5_TO_6 / steps));
|
||
|
||
inline void slash_rain_fire(
|
||
const screen_x_t& left, const screen_y_t& top, subpixel_t speed
|
||
) {
|
||
Pellets.add_single(left, top, (rand() % 0x7F), speed, PM_GRAVITY, RAIN_G);
|
||
Pellets.add_single(left, top, (rand() % 0x7F), speed, PM_GRAVITY, RAIN_G);
|
||
}
|
||
|
||
void pattern_slash_rain(void)
|
||
{
|
||
static screen_x_t spawner_left;
|
||
static screen_y_t spawner_top;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_direction_set_and_put(FD_CENTER);
|
||
face_expression_set_and_put(FE_CLOSED);
|
||
face_direction_can_change = false;
|
||
spawner_left = SWORD_CENTER_X;
|
||
spawner_top = SWORD_CENTER_Y;
|
||
select_for_rank(pattern_state.interval, 5, 3, 2, 1);
|
||
}
|
||
|
||
slash_animate();
|
||
|
||
if(boss_phase_frame < SLASH_2_FRAME) {
|
||
return;
|
||
}
|
||
if(
|
||
(boss_phase_frame < SLASH_4_FRAME) &&
|
||
((boss_phase_frame % pattern_state.interval) == 0)
|
||
) {
|
||
slash_rain_fire(spawner_left, spawner_top, to_sp(0.0f));
|
||
slash_spawner_step_from_2_to_4(
|
||
spawner_left, spawner_top, pattern_state.interval
|
||
);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_FRAME) {
|
||
spawner_left = SLASH_4_CORNER_X;
|
||
spawner_top = SLASH_4_CORNER_Y;
|
||
// Originally meant to be the step interval between cels 4 and 5?
|
||
select_for_rank(pattern_state.unused, 3, 2, 2, 2);
|
||
}
|
||
if(boss_phase_frame < SLASH_4_FRAME) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
slash_rain_fire(spawner_left, spawner_top, to_sp(0.0f));
|
||
slash_spawner_step_from_4_to_4_5(spawner_left, spawner_top, 1);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_5_FRAME) {
|
||
spawner_left = SLASH_5_CORNER_X;
|
||
spawner_top = SLASH_5_CORNER_Y;
|
||
select_for_rank(pattern_state.interval, 3, 2, 1, 1);
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
return;
|
||
}
|
||
if(
|
||
(boss_phase_frame < SLASH_6_FRAME) &&
|
||
((boss_phase_frame % pattern_state.interval) == 0)
|
||
) {
|
||
slash_rain_fire(spawner_left, spawner_top, to_sp(0.0f));
|
||
slash_spawner_step_from_4_5_to_6(
|
||
spawner_left, spawner_top, pattern_state.interval
|
||
);
|
||
}
|
||
}
|
||
|
||
inline void slash_triangular_fire(
|
||
const screen_x_t& left, const screen_y_t& top, const subpixel_t& speed
|
||
) {
|
||
Pellets.add_single(left, top, 0x20, speed, PM_NORMAL);
|
||
Pellets.add_single(left, top, 0x60, speed, PM_NORMAL);
|
||
}
|
||
|
||
void pattern_slash_triangular(void)
|
||
{
|
||
static screen_x_t spawner_left;
|
||
static screen_y_t spawner_top;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_direction_set_and_put(FD_CENTER);
|
||
face_expression_set_and_put(FE_AIM);
|
||
face_direction_can_change = false;
|
||
spawner_left = SWORD_CENTER_X;
|
||
spawner_top = SWORD_CENTER_Y;
|
||
select_for_rank(
|
||
pattern_state.speed,
|
||
to_sp(2.0f), to_sp(3.0f), to_sp(4.0f), to_sp(4.5f)
|
||
);
|
||
}
|
||
|
||
slash_animate();
|
||
|
||
if(boss_phase_frame < SLASH_2_FRAME) {
|
||
return;
|
||
}
|
||
if((boss_phase_frame < SLASH_4_FRAME) && ((boss_phase_frame % 3) == 0)) {
|
||
slash_triangular_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_2_to_4(spawner_left, spawner_top, 3);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_FRAME) {
|
||
spawner_left = SLASH_4_CORNER_X;
|
||
spawner_top = SLASH_4_CORNER_Y;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_FRAME) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
slash_triangular_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_4_to_4_5(spawner_left, spawner_top, 1);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_5_FRAME) {
|
||
spawner_left = SLASH_5_CORNER_X;
|
||
spawner_top = SLASH_5_CORNER_Y;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
return;
|
||
}
|
||
if((boss_phase_frame < SLASH_6_FRAME) && ((boss_phase_frame % 2) == 0)) {
|
||
slash_triangular_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_4_5_to_6(spawner_left, spawner_top, 2);
|
||
}
|
||
}
|
||
|
||
void pattern_lasers_and_3_spread(void)
|
||
{
|
||
// These have no reason to be static.
|
||
static screen_x_t target_left;
|
||
static screen_y_t target_y;
|
||
|
||
static bool16 right_to_left;
|
||
|
||
enum {
|
||
INTERVAL = 10,
|
||
};
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_AIM);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame == 100) {
|
||
right_to_left = (rand() % 2);
|
||
|
||
// Divisor = number of lasers that are effectively fired.
|
||
select_for_rank(pattern_state.delta_x,
|
||
(PLAYFIELD_W / 5),
|
||
(PLAYFIELD_W / 6.66),
|
||
(PLAYFIELD_W / 8),
|
||
(PLAYFIELD_W / 10)
|
||
);
|
||
}
|
||
if((boss_phase_frame % INTERVAL) == 0) {
|
||
if(right_to_left == 0) {
|
||
target_left = (PLAYFIELD_LEFT + (
|
||
((boss_phase_frame - 100) / INTERVAL) * pattern_state.delta_x
|
||
));
|
||
} else {
|
||
target_left = (PLAYFIELD_RIGHT - (
|
||
((boss_phase_frame - 100) / INTERVAL) * pattern_state.delta_x
|
||
));
|
||
}
|
||
target_y = PLAYFIELD_BOTTOM;
|
||
|
||
// Quite a roundabout way of preventing a buffer overflow, but fine.
|
||
shootout_lasers[
|
||
(boss_phase_frame / INTERVAL) % SHOOTOUT_LASER_COUNT
|
||
].spawn(
|
||
SWORD_CENTER_X, SWORD_CENTER_Y,
|
||
target_left, target_y,
|
||
(to_sp(8.5f) / 2), 7, 30, 5
|
||
);
|
||
mdrv2_se_play(6);
|
||
|
||
if(
|
||
((right_to_left == false) && (target_left >= PLAYFIELD_RIGHT)) ||
|
||
((right_to_left == true) && (target_left <= PLAYFIELD_LEFT))
|
||
) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
|
||
Pellets.add_group(
|
||
SWORD_CENTER_X, SWORD_CENTER_Y, PG_3_SPREAD_WIDE_AIMED, to_sp(4.5f)
|
||
);
|
||
}
|
||
}
|
||
|
||
inline void slash_aimed_fire(
|
||
const screen_x_t& left, const screen_y_t& top, const subpixel_t& speed
|
||
) {
|
||
Pellets.add_group(left, top, PG_1_AIMED, speed);
|
||
}
|
||
|
||
void pattern_slash_aimed(void)
|
||
{
|
||
static screen_x_t spawner_left;
|
||
static screen_y_t spawner_top;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_direction_set_and_put(FD_CENTER);
|
||
face_expression_set_and_put(FE_AIM);
|
||
face_direction_can_change = false;
|
||
spawner_left = SWORD_CENTER_X;
|
||
spawner_top = SWORD_CENTER_Y;
|
||
konngara_select_for_rank(pattern_state.speed,
|
||
to_sp(4.0f), to_sp(5.0f), to_sp(5.5f), to_sp(6.0f)
|
||
);
|
||
}
|
||
|
||
slash_animate();
|
||
if(boss_phase_frame < SLASH_2_FRAME) {
|
||
return;
|
||
}
|
||
if((boss_phase_frame < SLASH_4_FRAME) && ((boss_phase_frame % 3) == 0)) {
|
||
slash_aimed_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_2_to_4(spawner_left, spawner_top, 3);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_FRAME) {
|
||
spawner_left = SLASH_4_CORNER_X;
|
||
spawner_top = SLASH_4_CORNER_Y;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_FRAME) {
|
||
return;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
slash_aimed_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_4_to_4_5(spawner_left, spawner_top, 1);
|
||
}
|
||
|
||
if(boss_phase_frame == SLASH_4_5_FRAME) {
|
||
spawner_left = SLASH_5_CORNER_X;
|
||
spawner_top = SLASH_5_CORNER_Y;
|
||
}
|
||
if(boss_phase_frame < SLASH_4_5_FRAME) {
|
||
return;
|
||
}
|
||
if((boss_phase_frame < SLASH_6_FRAME) && ((boss_phase_frame % 2) == 0)) {
|
||
slash_aimed_fire(spawner_left, spawner_top, pattern_state.speed);
|
||
slash_spawner_step_from_4_5_to_6(spawner_left, spawner_top, 2);
|
||
}
|
||
}
|
||
|
||
void pattern_semicircle_rain_from_sleeve(void)
|
||
{
|
||
enum {
|
||
SPREAD = 10,
|
||
};
|
||
int i;
|
||
unsigned char angle;
|
||
|
||
if(boss_phase_frame == 10) {
|
||
face_expression_set_and_put(FE_AIM);
|
||
}
|
||
if(boss_phase_frame < 100) {
|
||
return;
|
||
}
|
||
if((boss_phase_frame % 20) == 0) {
|
||
for(i = 0, angle = 0x00; i < SPREAD; i++, angle -= (0x80 / SPREAD)) {
|
||
Pellets.add_single(
|
||
LEFT_SLEEVE_LEFT, LEFT_SLEEVE_TOP, angle, to_sp(2.0f),
|
||
PM_GRAVITY, RAIN_G
|
||
);
|
||
}
|
||
mdrv2_se_play(7);
|
||
} else if((boss_phase_frame % 20) == 10) {
|
||
for(i = 0, angle = 0x00; i < SPREAD; i++, angle -= (0x80 / SPREAD)) {
|
||
Pellets.add_single(
|
||
LEFT_SLEEVE_LEFT, LEFT_SLEEVE_TOP, angle, to_sp(2.0f),
|
||
PM_FALL_STRAIGHT_FROM_TOP_THEN_NORMAL, to_sp(3.0f)
|
||
);
|
||
}
|
||
mdrv2_se_play(7);
|
||
}
|
||
if(boss_phase_frame >= 300) {
|
||
boss_phase_frame = 0;
|
||
}
|
||
}
|
||
|
||
enum kuji_flash_color_t {
|
||
BLACK,
|
||
WHITE
|
||
};
|
||
|
||
inline void kuji_flash(kuji_flash_color_t color) {
|
||
if(color == BLACK) {
|
||
z_palette_set_show(0, 0x0, 0x0, 0x0);
|
||
} else {
|
||
z_palette_set_show(0, 0xF, 0xF, 0xF);
|
||
}
|
||
}
|
||
|
||
inline void kuji_put(kuji_flash_color_t flash_color, int image) {
|
||
if(image == 7) {
|
||
kuji_flash(flash_color);
|
||
grz_load_single(0, GRZ_FN, image);
|
||
} else {
|
||
z_graph_clear();
|
||
grz_load_single(0, GRZ_FN, image);
|
||
kuji_flash(flash_color);
|
||
}
|
||
grx_put(0);
|
||
if(image == 15) {
|
||
grx_free(0);
|
||
}
|
||
frame_delay(10);
|
||
}
|
||
|
||
void konngara_main(void)
|
||
{
|
||
static struct {
|
||
bool16 invincible;
|
||
int invincibility_frame;
|
||
|
||
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,
|
||
overlap_xy_xywh_le_ge_2(
|
||
orb_cur_left, orb_cur_top,
|
||
HITBOX_LEFT, HITBOX_TOP, (HITBOX_W - ORB_W), HITBOX_H // ???
|
||
),
|
||
HITBOX_LEFT, HITBOX_TOP, HITBOX_W, HITBOX_H
|
||
);
|
||
}
|
||
} hit;
|
||
|
||
enum {
|
||
CHOOSE_NEW = 99,
|
||
};
|
||
|
||
// The IDs are associated with a different pattern in every phase.
|
||
static int pattern_prev;
|
||
static bool initial_hp_rendered;
|
||
|
||
static struct {
|
||
int pattern_cur;
|
||
int patterns_done;
|
||
|
||
void next(int8_t phase_new) {
|
||
boss_phase = phase_new;
|
||
boss_phase_frame = 0;
|
||
hit.invincibility_frame = 0;
|
||
pattern_cur = CHOOSE_NEW;
|
||
patterns_done = 0;
|
||
if(phase_new & 1) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
}
|
||
}
|
||
|
||
void frame_common(face_direction_t& fd_new) {
|
||
boss_phase_frame++;
|
||
hit.invincibility_frame++;
|
||
|
||
fd_new =
|
||
(player_left < 198) ? FD_LEFT :
|
||
(player_left > 396) ? FD_RIGHT : FD_CENTER;
|
||
face_direction_set_and_put(fd_new);
|
||
}
|
||
} phase = { 0, 0 };
|
||
|
||
int i;
|
||
int j;
|
||
int col;
|
||
int comp;
|
||
int scroll_frame;
|
||
face_direction_t fd_track;
|
||
const unsigned char flash_colors[3] = { 3, 4, 5 };
|
||
|
||
#define pattern_choose( \
|
||
phase, frame_min, count_on_first_try, count_on_second_try \
|
||
) { \
|
||
if(boss_phase_frame > frame_min) { \
|
||
boss_phase_frame = 1; \
|
||
phase.pattern_cur = (rand() % count_on_first_try); \
|
||
if(phase.pattern_cur == pattern_prev) { \
|
||
phase.pattern_cur = (rand() % count_on_second_try); \
|
||
} \
|
||
pattern_prev = phase.pattern_cur; \
|
||
phase.patterns_done++; \
|
||
} \
|
||
}
|
||
|
||
#define phase_frame_siddham_flash(phase, next_phase) { \
|
||
if(boss_phase_frame == 50) { \
|
||
siddham_col_white(); \
|
||
} \
|
||
if((boss_phase_frame > 50) && ((boss_phase_frame % 4) == 0)) { \
|
||
siddham_col_white_in_step(); \
|
||
} \
|
||
\
|
||
hit.update_and_render(flash_colors); \
|
||
if(!hit.invincible && (boss_phase_frame > 120)) { \
|
||
phase.next(next_phase); \
|
||
} \
|
||
}
|
||
|
||
if(boss_phase == 0) {
|
||
boss_phase = 1;
|
||
pattern_prev = CHOOSE_NEW;
|
||
phase.pattern_cur = CHOOSE_NEW;
|
||
phase.patterns_done = 0;
|
||
boss_phase_frame = 0;
|
||
hit.invincibility_frame = 0;
|
||
hit.invincible = false;
|
||
initial_hp_rendered = false;
|
||
boss_palette_snap();
|
||
random_seed = frame_rand;
|
||
} else if(boss_phase == 1) {
|
||
if(!initial_hp_rendered) {
|
||
initial_hp_rendered = hud_hp_increment(boss_hp, boss_phase_frame);
|
||
}
|
||
phase.frame_common(fd_track);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_diamond_cross_to_edges_followed_by_rain();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_symmetrical_from_cup();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_two_homing_snakes_and_semicircle_spreads();
|
||
} else if(phase.pattern_cur == CHOOSE_NEW) {
|
||
pattern_choose(phase, 120, 3, 3);
|
||
}
|
||
if(boss_phase_frame == 0) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
phase.pattern_cur = CHOOSE_NEW;
|
||
}
|
||
hit.update_and_render(flash_colors);
|
||
if(!hit.invincible && ((phase.patterns_done >= 7) || (boss_hp < 16))) {
|
||
if(phase.pattern_cur == CHOOSE_NEW) {
|
||
phase.next(2);
|
||
}
|
||
}
|
||
} else if(boss_phase == 2) {
|
||
phase.frame_common(fd_track);
|
||
phase_frame_siddham_flash(phase, 3);
|
||
} else if(boss_phase == 3) {
|
||
phase.frame_common(fd_track);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_aimed_rows_from_top();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_aimed_spray_from_cup();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_four_homing_snakes();
|
||
} else if(phase.pattern_cur == 3) {
|
||
pattern_rain_from_edges();
|
||
} else if(phase.pattern_cur == CHOOSE_NEW) {
|
||
pattern_choose(phase, 120, 4, 4);
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
phase.pattern_cur = CHOOSE_NEW;
|
||
}
|
||
|
||
hit.update_and_render(flash_colors);
|
||
if(!hit.invincible && ((phase.patterns_done >= 9) || (boss_hp < 13))) {
|
||
if(phase.pattern_cur == CHOOSE_NEW) {
|
||
phase.next(4);
|
||
}
|
||
}
|
||
} else if(boss_phase == 4) {
|
||
phase.frame_common(fd_track);
|
||
phase_frame_siddham_flash(phase, 5);
|
||
} else if(boss_phase == 5) {
|
||
phase.frame_common(fd_track);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_slash_rain();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_lasers_and_3_spread();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_slash_triangular();
|
||
} else if(phase.pattern_cur == CHOOSE_NEW) {
|
||
pattern_choose(phase, 120, 3, 2);
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
phase.pattern_cur = CHOOSE_NEW;
|
||
}
|
||
|
||
hit.update_and_render(flash_colors);
|
||
if(!hit.invincible && ((phase.patterns_done >= 6) || (boss_hp < 10))) {
|
||
if(phase.pattern_cur == CHOOSE_NEW) {
|
||
phase.next(6);
|
||
}
|
||
}
|
||
} else if(boss_phase == 6) {
|
||
phase.frame_common(fd_track);
|
||
phase_frame_siddham_flash(phase, 7);
|
||
} else if(boss_phase == 7) {
|
||
phase.frame_common(fd_track);
|
||
|
||
if(phase.pattern_cur == 0) {
|
||
pattern_diamond_cross_to_edges_followed_by_rain();
|
||
} else if(phase.pattern_cur == 1) {
|
||
pattern_symmetrical_from_cup();
|
||
} else if(phase.pattern_cur == 2) {
|
||
pattern_two_homing_snakes_and_semicircle_spreads();
|
||
} else if(phase.pattern_cur == 3) {
|
||
pattern_aimed_rows_from_top();
|
||
} else if(phase.pattern_cur == 4) {
|
||
pattern_aimed_spray_from_cup();
|
||
} else if(phase.pattern_cur == 5) {
|
||
pattern_four_homing_snakes();
|
||
} else if(phase.pattern_cur == 6) {
|
||
pattern_rain_from_edges();
|
||
} else if(phase.pattern_cur == 7) {
|
||
pattern_slash_rain();
|
||
} else if(phase.pattern_cur == 8) {
|
||
pattern_lasers_and_3_spread();
|
||
} else if(phase.pattern_cur == 9) {
|
||
pattern_slash_triangular();
|
||
} else if(phase.pattern_cur == 10) {
|
||
pattern_semicircle_rain_from_sleeve();
|
||
} else if(phase.pattern_cur == 11) {
|
||
pattern_slash_aimed();
|
||
} else if(phase.pattern_cur == CHOOSE_NEW) {
|
||
pattern_choose(phase, 5, 12, 2);
|
||
}
|
||
|
||
if(boss_phase_frame == 0) {
|
||
face_expression_set_and_put(FE_NEUTRAL);
|
||
phase.pattern_cur = CHOOSE_NEW;
|
||
}
|
||
|
||
hit.update_and_render(flash_colors);
|
||
if(boss_hp > 0) {
|
||
return;
|
||
}
|
||
|
||
// Defeat sequence (blocking)
|
||
// ==========================
|
||
|
||
printf("\x1B*"); // Clear text RAM
|
||
konngara_free();
|
||
z_graph_clear();
|
||
mdrv2_bgm_stop();
|
||
|
||
kuji_put(BLACK, 7);
|
||
kuji_put(WHITE, 8);
|
||
kuji_put(BLACK, 9);
|
||
kuji_put(WHITE, 10);
|
||
kuji_put(BLACK, 11);
|
||
kuji_put(WHITE, 12);
|
||
kuji_put(BLACK, 13);
|
||
kuji_put(WHITE, 14);
|
||
kuji_put(BLACK, 15);
|
||
|
||
boss_phase_frame = 0;
|
||
while(1) {
|
||
enum {
|
||
MAGNITUDE = -8
|
||
};
|
||
|
||
boss_phase_frame++;
|
||
if((boss_phase_frame % 4) == 0) {
|
||
z_palette_black_out_step(j, i);
|
||
mdrv2_se_play(7);
|
||
}
|
||
z_vsync_wait_and_scrollup(
|
||
(RES_Y - MAGNITUDE) - ((boss_phase_frame % 2) * (MAGNITUDE * 2))
|
||
);
|
||
if(boss_phase_frame > 64) {
|
||
break;
|
||
}
|
||
frame_delay(1);
|
||
}
|
||
|
||
// Blit the scroll background, while covering the blit with ridiculously
|
||
// slow text RAM clears to black and back to transparency.
|
||
//
|
||
// "Graph mode" (as opposed to "kanji mode") disables Shift-JIS decoding
|
||
// inside NEC's IO.SYS. This allows new half-width glyphs at the
|
||
// Shift-JIS lead byte codepoints, 0x81-0x9F and 0xE0-0xFF, to be
|
||
// accessed via regular INT 29h text output, and consequently, printf().
|
||
// Had to reverse-engineer that, only to find out that it has exactly
|
||
// zero effect when printing spaces...
|
||
// See https://github.com/joncampbell123/dosbox-x/pull/2547 for a
|
||
// reimplementation of the original functionality into DOSBox-X.
|
||
// --------------------------------------------------------------------
|
||
|
||
#define y i
|
||
#define x j
|
||
|
||
printf("\x1B)3"); // Enter graph mode
|
||
printf("\x1B[16;40m"); // Set black foreground and background text color
|
||
printf("\x1B[0;0H"); // Move text cursor to (0, 0)
|
||
|
||
for(y = 0; y < (RES_Y / GLYPH_H); y++) {
|
||
for(x = 0; x < (RES_X / GLYPH_HALF_W); x++) {
|
||
printf(" ");
|
||
}
|
||
}
|
||
|
||
grp_put_palette_show(SCROLL_BG_FN);
|
||
z_palette_set_black(j, i);
|
||
|
||
printf("\x1B)0"); // Back to regular kanji mode
|
||
printf("\x1B[0m"); // Reset text mode color
|
||
printf("\x1B[1;1H"); // Move text cursor to (0, 0)
|
||
// (yes, this escape sequence is actually 1-based)
|
||
|
||
for(y = 0; y < (RES_Y / GLYPH_H); y++) {
|
||
for(x = 0; x < (RES_X / GLYPH_HALF_W); x++) {
|
||
printf(" ");
|
||
}
|
||
}
|
||
|
||
#undef x
|
||
#undef y
|
||
// --------------------------------------------------------------------
|
||
|
||
// Final scroll
|
||
// ------------
|
||
|
||
#define scroll_speed i
|
||
#define line_on_top j
|
||
|
||
line_on_top = 0;
|
||
scroll_speed = 32;
|
||
scroll_frame = 0;
|
||
|
||
while(1) {
|
||
z_vsync_wait_and_scrollup(line_on_top);
|
||
line_on_top += scroll_speed;
|
||
if(line_on_top > RES_Y) {
|
||
line_on_top -= RES_Y;
|
||
}
|
||
if(scroll_frame < 150) {
|
||
if((scroll_frame % 8) == 0) {
|
||
z_palette_black_in_step_to(col, comp, grp_palette);
|
||
mdrv2_se_play(7);
|
||
}
|
||
} else if((scroll_frame % 8) == 0) {
|
||
if(scroll_frame == 192) {
|
||
// We aren't playing anything, though?
|
||
mdrv2_bgm_fade_out_nonblock();
|
||
}
|
||
z_palette_black_out_step_2(col, comp);
|
||
if(z_Palettes[7].c.r == 0) {
|
||
break;
|
||
}
|
||
if(scroll_frame < 220) {
|
||
mdrv2_se_play(7);
|
||
}
|
||
}
|
||
scroll_frame++;
|
||
frame_delay(1);
|
||
}
|
||
|
||
#undef line_on_top
|
||
#undef scroll_speed
|
||
// ------------
|
||
|
||
z_vsync_wait_and_scrollup(0);
|
||
|
||
// Jigoku clear! Grant 50,000 points in the fanciest way
|
||
for(j = 0; j < 5; j++) {
|
||
score += 10000;
|
||
}
|
||
konngara_free(); // Yes, we already did this above :zunpet:
|
||
game_cleared = true;
|
||
}
|
||
|
||
#undef phase_frame_siddham_flash
|
||
#undef pattern_choose
|
||
}
|