ReC98/th01/main/boss/b20j.cpp

1882 lines
50 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// 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
}