ReC98/th05/main/boss/b6.cpp

637 lines
18 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.

/// Stage 6 Boss - Shinki
/// ---------------------
#include "platform.h"
#include "pc98.h"
#include "master.hpp"
#include "th01/math/area.hpp"
#include "th01/math/subpixel.hpp"
#include "th03/hardware/palette.hpp"
#include "th04/main/frames.h"
#include "th04/main/pattern.hpp"
#include "th04/math/motion.hpp"
extern "C" {
#include "th04/math/randring.hpp"
#include "th04/math/vector.hpp"
#include "th04/snd/snd.h"
#include "th04/main/rank.hpp"
#include "th04/main/playfld.hpp"
#include "th04/main/bullet/bullet.hpp"
#include "th04/main/gather.hpp"
}
#include "th05/main/custom.h"
#include "th05/main/bullet/b6ball.hpp"
#include "th05/main/bullet/cheeto.hpp"
#include "th05/main/bullet/laser.hpp"
#include "th05/main/boss/boss.hpp"
#include "th05/main/player/player.hpp"
#include "th05/sprites/main_pat.h"
// Constants
// ---------
static const int PHASE_2_3_PATTERN_START_FRAME = 32;
// ---------
// Coordinates
// -----------
inline subpixel_t shinki_wing_random_x(void) {
return (
randring2_next16_mod(to_sp(SHINKI_WING_W)) +
boss.pos.cur.x.v -
to_sp(SHINKI_WING_W / 2)
);
}
// Limited to the top 3/2rds of the wing area.
inline subpixel_t shinki_wing_random_y(void) {
return (
boss.pos.cur.y.v -
randring2_next16_mod((to_sp(SHINKI_WING_H) / 3) * 2) +
to_sp((SHINKI_WING_H - BOSS_H) / 2)
);
}
// -----------
// State
// -----
#define phase_2_3_pattern shinki_phase_2_3_pattern
extern pattern_oneshot_func_t phase_2_3_pattern;
// -----
bool near pattern_curved_rings(void)
{
#define delta_angle static_cast<unsigned char>(boss_statebyte[15])
if(boss.phase_frame == PHASE_2_3_PATTERN_START_FRAME) {
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_N_OUTLINED_BALL_BLUE;
bullet_template.speed.set(2.0f);
bullet_template.group = BG_RING;
bullet_template.special_motion = BSM_EXACT_LINEAR;
bullet_template.angle = randring2_next16();
bullet_template.spread = 16;
delta_angle = randring2_next16_and(1) ? 0x02 : -0x02;
bullet_template_tune();
snd_se_play(15);
}
if((boss.phase_frame % 4) == 0) {
bullets_add_special();
bullet_template.speed.v = (bullet_template.speed + 0.5f);
bullet_template.angle += delta_angle;
}
return (boss.phase_frame == (PHASE_2_3_PATTERN_START_FRAME + 28));
#undef delta_angle
}
bool near pattern_dualspeed_rings(void)
{
#define interval boss_statebyte[15]
if(boss.phase_frame == PHASE_2_3_PATTERN_START_FRAME) {
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_N_BLUE;
bullet_template.group = BG_RING;
bullet_template.angle = randring2_next16();
bullet_template.spread = 16;
interval = select_for_rank(16, 12, 8, 4);
}
if((boss.phase_frame % interval) == 0) {
vector2_at(
bullet_template.origin,
boss.pos.cur.x,
boss.pos.cur.y,
randring2_next16_mod(to_sp(32.0f)),
bullet_template.angle
);
bullet_template.speed.set(2.0f);
bullets_add_regular();
bullet_template.speed.set(4.0f);
bullet_template.angle += 0x08;
bullets_add_regular();
snd_se_play(3);
}
return (boss.phase_frame == (PHASE_2_3_PATTERN_START_FRAME + 64));
#undef interval
}
void near gather_then_phase_2_3_pattern(void)
{
if(boss.phase_frame < PHASE_2_3_PATTERN_START_FRAME) {
gather_add_only_3stack((boss.phase_frame - 16), 7, 6);
if(boss.phase_frame == 2) {
boss.sprite = PAT_SHINKI_CAST;
// What's this, part of an unused pattern? Actually, it's just
// copy-pasted from a similar function in Yumeko's fight, which
// does fire bullets based on that template.
bullet_template.spawn_type = (
BST_CLOUD_BACKWARDS | BST_NO_SLOWDOWN
);
bullet_template.patnum = PAT_BULLET16_N_RED;
bullet_template.group = BG_RING;
bullet_template.speed.set(3.75f);
bullet_template.spread = 16;
bullet_template_tune();
snd_se_play(8);
}
} else if(phase_2_3_pattern()) {
boss.sprite = PAT_SHINKI_STILL;
boss.phase_frame = 0;
boss.mode = 0;
}
}
bool near pattern_random_directional_and_kunai(void)
{
if(boss.phase_frame == PHASE_2_3_PATTERN_START_FRAME) {
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.speed.set(1.75f);
bullet_template.group = BG_RANDOM_ANGLE_AND_SPEED;
bullet_template.spread = 3;
bullet_template_tune();
snd_se_play(15);
}
if((boss.phase_frame % 2) == 0) {
bullet_template.patnum = PAT_BULLET16_D_BLUE;
bullet_template.origin.x -= (BOSS_W / 4.0f);
bullets_add_regular();
bullet_template.patnum = PAT_BULLET16_V_BLUE;
bullet_template.origin.x += ((BOSS_W / 4.0f) * 2.0f);
bullets_add_regular();
}
return boss_flystep_random(
boss.phase_frame - PHASE_2_3_PATTERN_START_FRAME - 32
);
}
bool near pattern_dense_blue_stacks(void)
{
if(boss.phase_frame == PHASE_2_3_PATTERN_START_FRAME) {
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_N_BLUE;
bullet_template.group = BG_SPREAD_STACK_AIMED;
bullet_template.angle = 0x00;
bullet_template.set_spread(2, 0x04);
bullet_template.set_stack_for_rank(
12, 12, 13, 13,
to_sp8(0.3125f), to_sp8(0.375f), to_sp8(0.375f), to_sp8(0.4375f)
);
bullet_template.speed.set(2.0f);
}
if((boss.phase_frame % 4) == 0) {
bullets_add_regular();
snd_se_play(15);
}
return boss_flystep_random(
boss.phase_frame - PHASE_2_3_PATTERN_START_FRAME - 32
);
}
bool near pattern_wing_preparation(void)
{
enum {
LASERS_USED = 6,
};
#define tone boss_statebyte[14]
#define wing_frames boss_statebyte[15]
int i;
if(boss.phase_frame == 16) {
// Still assumed to be set to this value by Yumeko. Original gameplay
// relies on this value to be < 8 to ensure that the lasers will kill
// the player if they move to the side of the wings. (Lasers only kill
// if they finished growing, they grow by 2 pixels every 2 frames, and
// the ones in this pattern are kept alive for 8 frames.)
// MODDERS: Uncomment to clearly define the original behavior.
// laser_template.coords.width = 6;
laser_template.coords.angle = 0x50; laser_manual_fixed_spawn(0);
laser_template.coords.angle = 0x48; laser_manual_fixed_spawn(1);
laser_template.coords.angle = 0x40; laser_manual_fixed_spawn(2);
laser_template.coords.angle = 0x40; laser_manual_fixed_spawn(3);
laser_template.coords.angle = 0x38; laser_manual_fixed_spawn(4);
laser_template.coords.angle = 0x30; laser_manual_fixed_spawn(5);
snd_se_play(8);
boss.sprite = PAT_SHINKI_CAST;
wing_frames = 0;
tone = 100;
}
if(boss.phase_frame <= 16) {
return false;
}
if(lasers[2].coords.angle < 0x80) {
if(stage_frame_mod2) {
lasers[0].coords.angle += 0x01;
lasers[1].coords.angle += 0x01;
lasers[2].coords.angle += 0x01;
lasers[3].coords.angle -= 0x01;
lasers[4].coords.angle -= 0x01;
lasers[5].coords.angle -= 0x01;
palette_settone_deferred(tone);
tone++;
}
return false;
}
if(wing_frames == 0) {
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_N_BLUE;
bullet_template.group = BG_SINGLE;
bullet_template_tune();
for(i = 0; i < 50; i++) {
bullet_template.origin.x.v = shinki_wing_random_x();
bullet_template.origin.y.v = shinki_wing_random_y();
bullet_template.angle = randring2_next8_ge_lt(0x10, 0x70);
bullet_template.speed.v = randring2_next8_and_ge_lt_sp(1.5f, 5.5f);
bullets_add_regular();
}
boss.sprite = PAT_SHINKI_WINGS_WHITE;
for(i = 0; i < LASERS_USED; i++) {
laser_manual_grow(i);
}
snd_se_play(15);
playfield_shake_anim_time = 8;
} else {
if(stage_frame_mod2) {
palette_settone_deferred(150);
} else {
palette_settone_deferred(100);
}
if(wing_frames >= 8) {
palette_settone_deferred(100);
for(i = 0; i < LASERS_USED; i++) {
laser_stop(i);
}
return true;
}
}
wing_frames++;
return false;
#undef wing_frames
#undef tone
}
void near pattern_random_rain_and_spreads_from_wings(void)
{
if(boss.phase_frame <= 128) {
return;
}
if((boss.phase_frame % 8) == 0) {
b6ball_template.origin.x.v = randring2_next16_mod(to_sp(PLAYFIELD_W));
b6ball_template.origin.y.v = randring2_next16_ge_lt_sp(
((2 / 23.0f) * PLAYFIELD_H), ((6 / 23.0f) * PLAYFIELD_H)
);
b6ball_template.angle = 0x40;
b6ball_template.speed.v = randring2_next8_and_ge_lt_sp(3.0f, 5.0f);
b6ball_template.patnum_tiny = PAT_B6BALL_BLUE_1;
b6balls_add();
snd_se_play(3);
}
if((boss.phase_frame % 24) == 0) {
bullet_template.origin.x.v = shinki_wing_random_x();
bullet_template.origin.y.v = (
boss.pos.cur.y.v - randring2_next16_mod(to_sp(BOSS_H))
);
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_N_BLUE;
bullet_template.group = BG_SPREAD_AIMED;
bullet_template.spread = 5;
bullet_template.spread_angle_delta = select_for_rank(
0x10, 0x0C, 0x0A, 0x08
);
bullet_template.angle = 0x00;
bullet_template.speed.set(3.0f);
bullet_template_tune();
bullets_add_regular();
}
}
void near pattern_cheetos_within_spread_walls(void)
{
#define interval boss_statebyte[14]
#define unused boss_statebyte[15]
if(boss.phase_frame < 128) {
return;
}
if(boss.phase_frame == 128) {
// Expected to be a multiple of 8.
interval = select_for_rank(128, 48, 32, 24);
unused = select_for_rank(32, 40, 48, 56);
}
int frame_in_cycle = (boss.phase_frame % interval);
if((frame_in_cycle & 7) == 0) {
bullet_template.patnum = PAT_BULLET16_N_OUTLINED_BALL_BLUE;
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.group = BG_SPREAD;
bullet_template.speed.v = randring2_next8_and_ge_lt_sp(3.0f, 5.0f);
bullet_template.set_spread(6, 0x08);
bullet_template.angle = 0x68;
bullets_add_regular();
bullet_template.angle = 0x18;
bullets_add_regular();
if(frame_in_cycle == 0) {
cheeto_template.col = 11;
cheeto_template.speed.set(4.0f);
// Firing either right, down, or left.
cheeto_template.angle = (randring2_next16_mod(3) * 0x40);
cheetos_add();
snd_se_play(3);
}
}
#undef unused
#undef interval
}
bool near pattern_wings_to_purple(void)
{
if(boss.phase_frame < 160) {
return false;
} else if(boss.phase_frame < 192) {
if(boss.phase_frame == 128) {
snd_se_play(8);
}
if(boss.phase_frame & 1) {
boss.pos.cur.y += 2.0f;
} else {
boss.pos.cur.y -= 2.0f;
}
} else if(boss.phase_frame == 192) {
b6ball_template.patnum_tiny = PAT_B6BALL_PURPLE;
for(int i = 0; i < 16; i++) {
b6ball_template.origin.x.v = shinki_wing_random_x();
b6ball_template.origin.y.v = shinki_wing_random_y();
b6ball_template.angle = randring2_next8_ge_lt(0x20, 0x60);
b6ball_template.speed.v = randring2_next8_and_ge_lt_sp(2.0f, 6.0f);
b6balls_add();
}
boss.sprite = PAT_SHINKI_WINGS_PURPLE;
snd_se_play(15);
playfield_shake_anim_time = 8;
}
return (boss.phase_frame == 200);
}
void near pattern_aimed_b6balls_and_symmetric_spreads(void)
{
#define b6ball_interval boss_statebyte[15]
if(boss.phase_frame <= 128) {
return;
}
if(boss.phase_frame == 129) {
b6ball_interval = select_for_rank(96, 32, 28, 24);
}
int spread_cycle = (boss.phase_frame % (0x40 * 2));
if((boss.phase_frame % b6ball_interval) == 0) {
b6ball_template.angle = player_angle_from(b6ball_template.origin);
b6ball_template.speed.set(4.0f);
b6ball_template.patnum_tiny = PAT_B6BALL_PURPLE;
b6balls_add();
}
if((boss.phase_frame % 4) == 0) {
bullet_template.origin.x -= (SHINKI_WING_W / 2);
bullet_template.group = BG_SPREAD;
bullet_template.special_motion = BSM_EXACT_LINEAR;
bullet_template.set_spread(3, 0x02);
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.speed.set(2.5f);
bullet_template.patnum = PAT_BULLET16_V_RED;
if(spread_cycle < 0x40) {
bullet_template.angle = (spread_cycle * 3);
} else {
bullet_template.angle = (0x40 - (spread_cycle * 3));
}
bullets_add_special();
bullet_template.angle = (0x80 - bullet_template.angle);
bullet_template.origin.x += SHINKI_WING_W;
bullets_add_special();
snd_se_play(15);
}
#undef b6ball_interval
}
void near pattern_devil(void)
{
enum {
CLOCKWISE = 0,
COUNTERCLOCKWISE = 1,
COLUMN_COUNT = 4,
SHINKI_TO_LAST_COLUMN_DISTANCE = (
(SHINKI_WING_W / 2) - (SHINKI_WING_W / 8)
),
COLUMN_W = (SHINKI_WING_W / 4),
};
// Since the columns are symmetrical, it's only necessary to track the
// inner and outer angles for spread bullets and lasers.
#define laser_direction boss_statebyte[7]
#define laser_angle_inner boss_statebyte[8]
#define laser_angle_outer boss_statebyte[9]
#define lasers_active static_cast<bool>(boss_statebyte[10])
#define bullet_direction boss_statebyte[11]
#define bullet_intro_done static_cast<bool>(boss_statebyte[12])
#define b6ball_interval boss_statebyte[13]
#define bullet_angle_inner boss_statebyte[14]
#define bullet_angle_outer boss_statebyte[15]
#define laser_grow_delay shinki_devil_laser_grow_delay
extern int laser_grow_delay;
if(boss.phase_frame < 192) {
return;
}
int phase_frame_minus_startup_delay = (boss.phase_frame - 192);
// Laser activation
if((boss.hp <= 5600) || (boss.phase_frame >= 1800)) {
if(laser_grow_delay == 0) {
laser_template.coords.width = 6;
laser_template.coords.angle = 0x40;
laser_template.col = 0xE;
// Assign laser IDs from right to left
laser_template.coords.origin.x += SHINKI_TO_LAST_COLUMN_DISTANCE;
laser_manual_fixed_spawn(0);
laser_template.coords.origin.x -= COLUMN_W;
laser_manual_fixed_spawn(1);
laser_template.coords.origin.x -= COLUMN_W;
laser_manual_fixed_spawn(2);
laser_template.coords.origin.x -= COLUMN_W;
laser_manual_fixed_spawn(3);
laser_grow_delay++;
boss_explode_small(ET_CIRCLE);
}
}
// Spread bullets
if((boss.phase_frame % 4) == 0) {
if(phase_frame_minus_startup_delay == 0) {
bullet_angle_inner = 0x20;
bullet_angle_outer = 0x00;
b6ball_interval = select_for_rank(64, 40, 32, 28);
bullet_intro_done = false;
bullet_direction = CLOCKWISE;
lasers_active = false;
laser_angle_inner = 0x40;
laser_angle_outer = 0x40;
laser_direction = CLOCKWISE;
}
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
bullet_template.patnum = PAT_BULLET16_V_RED;
bullet_template.group = BG_SPREAD;
if((bullet_angle_outer == 0x40) && (bullet_intro_done == 0)) {
bullet_intro_done++;
}
if(!bullet_intro_done) {
bullet_template.spread = 1;
} else {
bullet_template.spread = 3;
}
bullet_template.spread_angle_delta = 0x30;
bullet_template.speed.set(7.0f);
bullet_template.origin.x += SHINKI_TO_LAST_COLUMN_DISTANCE;
bullet_template.angle = bullet_angle_outer;
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W;
bullet_template.angle = bullet_angle_inner;
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W;
bullet_template.angle = (0x80 - bullet_angle_inner);
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W;
bullet_template.angle = (0x80 - bullet_angle_outer);
bullets_add_regular_fixedspeed();
if(!bullet_intro_done) {
bullet_angle_outer += 0x04;
bullet_angle_inner += 0x02;
} else {
// "Decorative" pellets aimed to the top of the playfield
bullet_template.patnum = 0;
bullet_template.spawn_type = BST_NO_SLOWDOWN;
bullet_template.set_spread(3, 0x30); // technically redundant
bullet_template.angle = (0x80 + bullet_angle_outer);
bullet_template.origin.x += (COLUMN_W * (COLUMN_COUNT - 1)),
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W,
bullet_template.angle = (0x80 + bullet_angle_inner);
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W,
bullet_template.angle = (0x00 - bullet_angle_inner);
bullets_add_regular_fixedspeed();
bullet_template.origin.x -= COLUMN_W,
bullet_template.angle = (0x00 - bullet_angle_outer);
bullets_add_regular_fixedspeed();
if(bullet_direction == CLOCKWISE) {
bullet_angle_outer -= 0x02;
bullet_angle_inner--;
if(bullet_angle_outer == 0x30) {
bullet_direction++; // = COUNTERCLOCKWISE;
}
} else /* if(bullet_direction == COUNTERCLOCKWISE) */ {
bullet_angle_outer += 0x02;
bullet_angle_inner++;
if(bullet_angle_outer == 0x40) {
bullet_direction--; // = CLOCKWISE;
}
}
}
// Aimed 32×32 balls
if(bullet_intro_done && ((boss.phase_frame % b6ball_interval) == 0)) {
b6ball_template.origin.x.v = shinki_wing_random_x();
b6ball_template.origin.y.v = shinki_wing_random_y();
b6ball_template.angle = player_angle_from(b6ball_template.origin);
b6ball_template.speed.set(3.75f);
b6ball_template.patnum_tiny = PAT_B6BALL_PURPLE;
b6balls_add();
}
snd_se_play(3);
}
// Lasers
if(laser_grow_delay == 0) {
return;
} else if(laser_grow_delay < 64) {
laser_grow_delay++;
} else if(laser_grow_delay == 64) {
// MODDERS: Turn into a loop
laser_manual_grow(0);
laser_manual_grow(1);
laser_manual_grow(2);
laser_manual_grow(3);
laser_grow_delay++;
} else if(!lasers_active) {
if(bullet_angle_outer == 0x30) {
lasers_active++;
}
} else if((boss.phase_frame % 8) == 0) {
lasers[0].coords.angle = laser_angle_outer;
lasers[1].coords.angle = laser_angle_inner;
lasers[2].coords.angle = (0x80 - lasers[1].coords.angle);
lasers[3].coords.angle = (0x80 - lasers[0].coords.angle);
if(laser_direction == CLOCKWISE) {
laser_angle_outer++;
laser_angle_inner--;
if(laser_angle_inner == 0x38) {
laser_direction++; // = COUNTERCLOCKWISE;
}
} else /* if(laser_direction == COUNTERCLOCKWISE) */ {
laser_angle_outer--;
laser_angle_inner++;
if(laser_angle_inner == 0x41) {
laser_direction--; // = CLOCKWISE;
}
}
if((boss.hp <= 3800) || (boss.phase_frame >= 2500)) {
if((laser_grow_delay++) == 65) {
b6ball_interval = select_for_rank(52, 20, 16, 12);
boss_explode_small(ET_CIRCLE);
}
}
}
#undef laser_grow_delay
#undef bullet_angle_outer
#undef bullet_angle_inner
#undef b6ball_interval
#undef bullet_intro_done
#undef bullet_direction
#undef lasers_active
#undef laser_angle_outer
#undef laser_angle_inner
#undef laser_direction
}