mirror of https://github.com/nmlgc/ReC98.git
1077 lines
28 KiB
C++
1077 lines
28 KiB
C++
#pragma option -zPmain_03
|
||
|
||
/// Stage 6 Boss - Shinki
|
||
/// ---------------------
|
||
|
||
#include "platform.h"
|
||
#include "decomp.hpp"
|
||
#include "pc98.h"
|
||
#include "master.hpp"
|
||
#include "th01/math/area.hpp"
|
||
#include "th01/math/dir.hpp"
|
||
#include "th01/math/subpixel.hpp"
|
||
#include "th02/main/tile/tile.hpp"
|
||
#include "th02/main/player/bomb.hpp"
|
||
#include "th03/hardware/palette.hpp"
|
||
#include "th04/formats/bb.h"
|
||
#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/bg.hpp"
|
||
#include "th04/main/homing.hpp"
|
||
#include "th04/main/rank.hpp"
|
||
#include "th04/main/playfld.hpp"
|
||
#include "th04/main/bullet/bullet.hpp"
|
||
#include "th04/main/gather.hpp"
|
||
}
|
||
#include "th04/main/phase.hpp"
|
||
#include "th04/main/hud/hud.hpp"
|
||
#include "th04/main/tile/bb.hpp"
|
||
#include "th05/playchar.h"
|
||
#include "th05/main/bullet/cheeto.hpp"
|
||
#include "th05/main/bullet/laser.hpp"
|
||
#include "th05/main/boss/boss.hpp"
|
||
#include "th05/main/boss/bosses.hpp"
|
||
#include "th05/main/player/player.hpp"
|
||
#include "th05/sprites/main_pat.h"
|
||
|
||
// Constants
|
||
// ---------
|
||
|
||
static const int PHASE_2_3_PATTERN_START_FRAME = 32;
|
||
|
||
// Always denotes the last phase that ends with that amount of HP.
|
||
enum shinki_hp_t {
|
||
HP_TOTAL = 22800,
|
||
PHASE_2_END_HP = 20600,
|
||
PHASE_3_END_HP = 18400,
|
||
PHASE_6_END_HP = 14600,
|
||
PHASE_7_END_HP = 11600,
|
||
PHASE_9_END_HP = 8600,
|
||
PHASE_10_DEVIL_LASER_HP = 5600,
|
||
PHASE_10_DEVIL_FAST_HP = 3800,
|
||
PHASE_10_END_HP = 2800,
|
||
PHASE_12_END_HP = 0,
|
||
};
|
||
// ---------
|
||
|
||
// 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
|
||
#define wing_pattern shinki_wing_pattern
|
||
extern pattern_oneshot_func_t phase_2_3_pattern;
|
||
extern pattern_loop_func_t wing_pattern;
|
||
// -----
|
||
|
||
#include "th05/main/bullet/b6ball.cpp"
|
||
|
||
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 {
|
||
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]
|
||
|
||
// Careful, collides with [phase_relative] in shinki_update()! *Must* be
|
||
// set to 1 before ending the pattern to ensure that the remaining phases
|
||
// run in their expected order. Guaranteed in the original code by setting
|
||
// PHASE_10_DEVIL_LASER_HP a much higher value than PHASE_10_END_HP.
|
||
#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 <= PHASE_10_DEVIL_LASER_HP) || (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) {
|
||
// Pattern can now be safely ended
|
||
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 <= PHASE_10_DEVIL_FAST_HP) || (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
|
||
}
|
||
|
||
void near pattern_circles_and_alternating_spirals(void)
|
||
{
|
||
#define pellet_angle boss_statebyte[14]
|
||
#define red_angle boss_statebyte[15]
|
||
|
||
int frame_in_cycle;
|
||
|
||
if(boss.phase_frame < 128) {
|
||
return;
|
||
}
|
||
if(boss.phase_frame == 128) {
|
||
b6ball_template.angle = 0x00;
|
||
red_angle = 0x00;
|
||
pellet_angle = 0x00;
|
||
}
|
||
if((boss.phase_frame % 8) == 0) {
|
||
// Still assumed to be set to this value from pattern_devil().
|
||
// MODDERS: Uncomment.
|
||
// b6ball_template.speed.set(3.75f);
|
||
b6ball_template.patnum_tiny = PAT_B6BALL_BLUE_1;
|
||
vector2_at(
|
||
b6ball_template.origin,
|
||
boss.pos.cur.x,
|
||
boss.pos.cur.y,
|
||
to_sp(BOSS_W),
|
||
b6ball_template.angle
|
||
);
|
||
b6balls_add();
|
||
|
||
b6ball_template.angle += 0x80;
|
||
vector2_at(
|
||
b6ball_template.origin,
|
||
boss.pos.cur.x,
|
||
boss.pos.cur.y,
|
||
to_sp(BOSS_W),
|
||
b6ball_template.angle
|
||
);
|
||
b6balls_add();
|
||
|
||
b6ball_template.angle -= 0x78;
|
||
snd_se_play(3);
|
||
}
|
||
if(boss.phase_frame >= 256) {
|
||
frame_in_cycle = (boss.phase_frame % 256);
|
||
if(((boss.phase_frame % 8) == 0) && (frame_in_cycle < 128)) {
|
||
bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN);
|
||
bullet_template.group = BG_RING;
|
||
bullet_template.angle = red_angle;
|
||
bullet_template.patnum = PAT_BULLET16_V_RED;
|
||
bullet_template.speed.set(2.0f);
|
||
bullet_template.spread = 12;
|
||
bullets_add_regular();
|
||
|
||
red_angle -= 0x02;
|
||
}
|
||
if(boss.phase_frame >= 512) {
|
||
if(((boss.phase_frame % 8) == 0) && (frame_in_cycle >= 128)) {
|
||
bullet_template.spawn_type = (
|
||
BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN
|
||
);
|
||
bullet_template.group = BG_RING;
|
||
bullet_template.angle = pellet_angle;
|
||
bullet_template.patnum = 0;
|
||
bullet_template.speed.set(2.0f);
|
||
bullet_template.spread = 12;
|
||
bullets_add_regular();
|
||
|
||
pellet_angle += 0x04;
|
||
}
|
||
}
|
||
}
|
||
if(boss.phase_frame >= 720) {
|
||
frame_in_cycle = ((boss.phase_frame - 720) % 128);
|
||
boss_flystep_random(frame_in_cycle - 96);
|
||
}
|
||
|
||
#undef red_angle
|
||
#undef pellet_angle
|
||
}
|
||
|
||
void near shinki_move_float(void)
|
||
{
|
||
#define float_direction shinki_float_direction
|
||
extern bool float_direction;
|
||
|
||
if(stage_frame_mod4 != 0) {
|
||
return;
|
||
}
|
||
if(float_direction == 0) {
|
||
boss.pos.velocity.y += 0.0625f;
|
||
if(boss.pos.velocity.y.v >= to_sp(1.0f)) {
|
||
float_direction++;
|
||
}
|
||
} else {
|
||
boss.pos.velocity.y -= 0.0625f;
|
||
if(boss.pos.velocity.y.v <= to_sp(-1.0f)) {
|
||
float_direction--;
|
||
}
|
||
}
|
||
boss.pos.cur.y.v += boss.pos.velocity.y.v;
|
||
|
||
#undef float_direction
|
||
}
|
||
|
||
#pragma option -a2
|
||
|
||
void pascal shinki_update(void)
|
||
{
|
||
// A completely unnecessary variable to differentiate between multiple
|
||
// phases that share the same `switch` body, because [boss.phase] itself
|
||
// is not good enough?
|
||
// Careful, collides with [lasers_active] in pattern_devil()!
|
||
// MODDERS: Just use [boss.phase].
|
||
#define phase_relative boss_statebyte[10]
|
||
|
||
enum {
|
||
PHASE_11_FRAMES = 16,
|
||
};
|
||
|
||
bool16 phase_done;
|
||
|
||
homing_target = boss.pos.cur;
|
||
bullet_template.origin = boss.pos.cur;
|
||
gather_template.center = boss.pos.cur;
|
||
laser_template.coords.origin = boss.pos.cur;
|
||
b6ball_template.origin = boss.pos.cur;
|
||
|
||
boss.phase_frame++;
|
||
|
||
switch(boss.phase) {
|
||
case PHASE_HP_FILL:
|
||
if(boss.phase_frame == 1) {
|
||
boss.hp = HP_TOTAL;
|
||
boss.phase_end_hp = PHASE_2_END_HP;
|
||
gather_template_init();
|
||
boss.sprite = PAT_SHINKI_STILL;
|
||
|
||
// Redundant here, only needs to be part of the phase 5→6
|
||
// transition for completeness.
|
||
boss.pos.velocity.y.set(0.0f);
|
||
|
||
boss_sprite_left = PAT_SHINKI_LEFT;
|
||
boss_sprite_right = PAT_SHINKI_RIGHT;
|
||
boss_sprite_stay = PAT_SHINKI_STILL;
|
||
b6balls_load();
|
||
}
|
||
boss_hittest_shots_invincible();
|
||
|
||
// Timeout condition
|
||
if(boss.phase_frame >= (HUD_HP_FILL_FRAMES + 64)) {
|
||
// Next phase
|
||
boss.phase_frame = 0;
|
||
boss.phase++;
|
||
snd_se_play(13);
|
||
set_nearfunc_ptr_to_farfunc(
|
||
bg_render_bombing_func, shinki_bg_render
|
||
);
|
||
}
|
||
break;
|
||
|
||
case PHASE_BOSS_ENTRANCE_BB:
|
||
boss_hittest_shots_invincible();
|
||
if(boss.phase_frame == (
|
||
ENTRANCE_BB_FRAMES_PER_CEL * (TILES_BB_CELS / 2)
|
||
)) {
|
||
Palettes[0].set(0x0, 0x0, 0x0);
|
||
palette_changed = true;
|
||
}
|
||
|
||
// Timeout condition
|
||
if(boss.phase_frame >= (ENTRANCE_BB_FRAMES_PER_CEL * TILES_BB_CELS)) {
|
||
// Next phase
|
||
boss.phase++;
|
||
boss.mode = 1;
|
||
boss.phase_frame = 0;
|
||
phase_2_3_pattern = pattern_curved_rings;
|
||
phase_relative = 0;
|
||
}
|
||
break;
|
||
|
||
case 2:
|
||
case 3:
|
||
switch(boss.mode) {
|
||
case 0:
|
||
if(boss_flystep_random(
|
||
boss.phase_frame - PHASE_2_3_PATTERN_START_FRAME
|
||
)) {
|
||
#define patterns shinki_patterns_phase_2_3
|
||
extern pattern_oneshot_func_t patterns[2][2];
|
||
|
||
boss.phase_frame = 0;
|
||
boss.phase_state.patterns_seen++;
|
||
boss.mode++;
|
||
phase_2_3_pattern = patterns[phase_relative][
|
||
boss.phase_state.patterns_seen & 1
|
||
];
|
||
|
||
// Timeout condition
|
||
if(boss.phase_state.patterns_seen >= 16) {
|
||
goto phase_2_3_timed_out;
|
||
}
|
||
|
||
#undef patterns
|
||
}
|
||
break;
|
||
case 1:
|
||
gather_then_phase_2_3_pattern();
|
||
break;
|
||
}
|
||
if(!boss_hittest_shots()) {
|
||
break;
|
||
}
|
||
boss_score_bonus(10);
|
||
|
||
phase_2_3_timed_out:
|
||
// Next phase
|
||
if(phase_relative == 0) {
|
||
boss_phase_next(ET_NW_SE, PHASE_3_END_HP);
|
||
boss.mode = 1;
|
||
phase_2_3_pattern = pattern_random_directional_and_kunai;
|
||
} else {
|
||
boss_phase_next(ET_NW_SE, PHASE_6_END_HP);
|
||
}
|
||
phase_relative++;
|
||
break;
|
||
|
||
case 4:
|
||
boss_hittest_shots();
|
||
|
||
// Timeout condition
|
||
if(boss_flystep_towards(
|
||
to_sp(PLAYFIELD_W / 2), to_sp(playfield_fraction_y(5 / 23.0f))
|
||
)) {
|
||
// Next phase
|
||
boss.phase++;
|
||
boss.phase_frame = 0;
|
||
}
|
||
break;
|
||
|
||
case 5:
|
||
boss_hittest_shots();
|
||
|
||
// Timeout condition
|
||
if(pattern_wing_preparation()) {
|
||
// Next phase
|
||
boss.phase++;
|
||
boss.phase_frame = 0;
|
||
boss.pos.velocity.y.set(0.0f);
|
||
set_nearfunc_ptr_to_farfunc(
|
||
boss_custombullets_render, shinki_custombullets_render
|
||
);
|
||
// How generous for Yuuka players!
|
||
boss_hitbox_radius.x.v = select_for_playchar(
|
||
to_sp(50.0f), to_sp(35.0f), to_sp(45.0f), to_sp(50.0f)
|
||
);
|
||
wing_pattern = pattern_random_rain_and_spreads_from_wings;
|
||
phase_relative = 0;
|
||
}
|
||
break;
|
||
|
||
case 6:
|
||
case 7:
|
||
if(Palettes[0].c.b < 0x80) {
|
||
if(stage_frame_mod16 == 0) {
|
||
Palettes[0].c.b++;
|
||
}
|
||
palette_changed = true;
|
||
}
|
||
shinki_move_float();
|
||
wing_pattern();
|
||
|
||
// Timeout condition
|
||
if(boss.phase_frame < 3000) {
|
||
if(boss_hittest_shots()) {
|
||
boss_score_bonus(25);
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Next phase
|
||
if(phase_relative == 0) {
|
||
boss_phase_next(ET_SW_NE, PHASE_7_END_HP);
|
||
wing_pattern = pattern_cheetos_within_spread_walls;
|
||
boss.phase_frame = 0; // redundant
|
||
phase_relative++;
|
||
} else {
|
||
boss_phase_next(ET_HORIZONTAL, PHASE_9_END_HP);
|
||
}
|
||
break;
|
||
|
||
case 8:
|
||
if(Palettes[0].c.b > 0x00) {
|
||
if(stage_frame_mod8 == 0) {
|
||
Palettes[0].c.b--;
|
||
}
|
||
palette_changed = true;
|
||
}
|
||
boss_hittest_shots();
|
||
|
||
// Timeout condition
|
||
if(pattern_wings_to_purple()) {
|
||
// Next phase
|
||
boss.phase++;
|
||
boss.phase_frame = 0;
|
||
shinki_wing_pattern = pattern_aimed_b6balls_and_symmetric_spreads;
|
||
phase_relative = 0;
|
||
}
|
||
break;
|
||
|
||
case 9:
|
||
case 10:
|
||
if(Palettes[0].c.b > 0x00) {
|
||
if(stage_frame_mod8 == 0) {
|
||
Palettes[0].c.b--;
|
||
}
|
||
palette_changed = true;
|
||
} else if(Palettes[0].c.r < 0x80) {
|
||
if(stage_frame_mod16 == 0) {
|
||
Palettes[0].c.r++;
|
||
}
|
||
palette_changed = true;
|
||
}
|
||
shinki_move_float();
|
||
wing_pattern();
|
||
|
||
// Timeout condition
|
||
if(boss.phase_frame < 3000) {
|
||
phase_done = boss_hittest_shots();
|
||
// Reduce per-frame damage to 1 HP during bombs
|
||
if(bombing) {
|
||
boss.hp += (boss.damage_this_frame - 1);
|
||
phase_done = false;
|
||
}
|
||
if(!phase_done) {
|
||
break;
|
||
}
|
||
boss_score_bonus(25);
|
||
}
|
||
|
||
// Next phase
|
||
if(phase_relative == 0) {
|
||
boss_phase_next(ET_SW_NE, PHASE_10_END_HP);
|
||
shinki_wing_pattern = pattern_devil;
|
||
boss.phase_frame = 0; // redundant
|
||
phase_relative++;
|
||
} else {
|
||
boss_phase_next(ET_HORIZONTAL, PHASE_12_END_HP);
|
||
playfield_shake_anim_time = PHASE_11_FRAMES;
|
||
// The pattern_devil() lasers. Should ideally be stopped inside the
|
||
// pattern, but since it's a looping one without a clear end,
|
||
// there's no easy way with the original architecture.
|
||
laser_stop(0);
|
||
laser_stop(1);
|
||
laser_stop(2);
|
||
laser_stop(3);
|
||
}
|
||
break;
|
||
|
||
case 11:
|
||
boss_hittest_shots();
|
||
if((boss.phase_frame % 2) == 0) {
|
||
palette_settone_deferred(150);
|
||
} else {
|
||
palette_settone_deferred(100);
|
||
}
|
||
|
||
// Timeout condition
|
||
if(boss.phase_frame > PHASE_11_FRAMES) {
|
||
// Next phase
|
||
boss.phase++;
|
||
boss.sprite = PAT_SHINKI_STILL;
|
||
boss_hitbox_radius.x.v = to_sp(BOSS_HITBOX_DEFAULT_W);
|
||
Palettes[0].c.r = 0x60;
|
||
Palettes[0].c.b = 0x00;
|
||
snd_se_play(13);
|
||
}
|
||
break;
|
||
|
||
case 12:
|
||
pattern_circles_and_alternating_spirals();
|
||
// Timeout condition
|
||
if(boss.phase_frame < 3000) {
|
||
// Boss defeated?
|
||
if(!boss_hittest_shots()) {
|
||
break;
|
||
}
|
||
boss.phase_state.defeat_bonus = true;
|
||
}
|
||
// Boss defeated
|
||
boss_explode_small(ET_VERTICAL);
|
||
boss.phase_frame = 0;
|
||
boss.phase = PHASE_BOSS_EXPLODE_SMALL;
|
||
break;
|
||
|
||
default:
|
||
boss_defeat_update(65);
|
||
break;
|
||
}
|
||
b6balls_update();
|
||
cheetos_update();
|
||
hud_hp_update_and_render(boss.hp, HP_TOTAL);
|
||
|
||
#undef phase_relative
|
||
}
|