2020-12-13 19:57:49 +00:00
|
|
|
|
/// Stage 5 Boss - SinGyoku
|
|
|
|
|
/// -----------------------
|
|
|
|
|
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#include <stddef.h>
|
2020-12-06 12:38:41 +00:00
|
|
|
|
#include "platform.h"
|
2022-06-18 16:18:56 +00:00
|
|
|
|
#include "decomp.hpp"
|
2020-12-17 20:28:17 +00:00
|
|
|
|
#include "pc98.h"
|
2022-06-18 15:34:08 +00:00
|
|
|
|
#include "master.hpp"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/v_colors.hpp"
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#include "th01/math/area.hpp"
|
2022-06-18 14:17:11 +00:00
|
|
|
|
#include "th01/math/clamp.hpp"
|
2022-06-18 19:14:43 +00:00
|
|
|
|
#include "th01/math/subpixel.hpp"
|
2022-06-18 16:18:56 +00:00
|
|
|
|
#include "th01/math/vector.hpp"
|
2022-06-18 14:17:11 +00:00
|
|
|
|
#include "th01/hardware/egc.h"
|
2022-06-18 16:18:56 +00:00
|
|
|
|
#include "th01/hardware/input.hpp"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
extern "C" {
|
|
|
|
|
#include "th01/hardware/palette.h"
|
2022-06-17 22:38:23 +00:00
|
|
|
|
#include "th01/snd/mdrv2.h"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/formats/grp.h"
|
|
|
|
|
}
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#include "th01/formats/pf.hpp"
|
2022-06-18 15:34:08 +00:00
|
|
|
|
#include "th01/sprites/pellet.h"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/main/particle.hpp"
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#include "th01/main/playfld.hpp"
|
2021-08-03 13:50:52 +00:00
|
|
|
|
#include "th01/main/vars.hpp"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/main/hud/hp.hpp"
|
2020-12-13 19:57:49 +00:00
|
|
|
|
#include "th01/main/boss/boss.hpp"
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#include "th01/main/boss/entity_a.hpp"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/main/boss/palette.hpp"
|
2022-06-18 16:18:56 +00:00
|
|
|
|
#include "th01/main/player/player.hpp"
|
2022-06-18 15:34:08 +00:00
|
|
|
|
#include "th01/main/bullet/pellet.hpp"
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#include "th01/main/stage/palette.hpp"
|
|
|
|
|
|
2022-06-18 16:18:56 +00:00
|
|
|
|
// Coordinates
|
|
|
|
|
// -----------
|
|
|
|
|
|
|
|
|
|
// SINGYOKU_W and SINGYOKU_H are defined in boss.hpp, as they are needed
|
|
|
|
|
// globally, by singyoku_defeat_animate_and_select_route() and as dummy default
|
|
|
|
|
// parameters for CBossEntity::pos_set().
|
|
|
|
|
|
|
|
|
|
static const screen_y_t BASE_CENTER_Y = (
|
|
|
|
|
PLAYFIELD_TOP + ((PLAYFIELD_H / 21) * 5)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const screen_y_t BASE_TOP = (BASE_CENTER_Y - (SINGYOKU_H / 2));
|
|
|
|
|
// -----------
|
|
|
|
|
|
2022-06-17 22:24:25 +00:00
|
|
|
|
// Always denotes the last phase that ends with that amount of HP.
|
|
|
|
|
enum singyoku_hp_t {
|
|
|
|
|
HP_TOTAL = 8,
|
|
|
|
|
PHASE_1_END_HP = 6,
|
|
|
|
|
PHASE_2_END_HP = 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// State that's suddenly no longer shared with other bosses
|
|
|
|
|
// --------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#define boss_hp singyoku_hp
|
|
|
|
|
#define boss_phase singyoku_phase
|
|
|
|
|
#define boss_phase_frame singyoku_phase_frame
|
|
|
|
|
extern int8_t boss_phase;
|
|
|
|
|
extern int boss_phase_frame;
|
|
|
|
|
extern int boss_hp;
|
|
|
|
|
// --------------------------------------------------------
|
2022-06-17 21:43:48 +00:00
|
|
|
|
|
|
|
|
|
// Entities
|
|
|
|
|
// --------
|
|
|
|
|
|
2022-06-18 17:47:01 +00:00
|
|
|
|
enum singyoku_form_t {
|
|
|
|
|
F_WOMAN = 0,
|
|
|
|
|
F_MAN = 1,
|
|
|
|
|
|
|
|
|
|
_singyoku_form_t_FORCE_INT16 = 0x7FFF,
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-17 21:43:48 +00:00
|
|
|
|
static const int SPHERE_CELS = 8;
|
|
|
|
|
|
2022-06-18 20:23:23 +00:00
|
|
|
|
// That's the position for the left hand, at least. The right hand would be a
|
|
|
|
|
// bit further in, but the game doesn't care.
|
|
|
|
|
static const pixel_t WOMAN_HAND_DISTANCE_FROM_EDGE = 16;
|
|
|
|
|
|
2022-06-18 17:47:01 +00:00
|
|
|
|
enum singyoku_flash_cel_t {
|
|
|
|
|
C_SPHERE = 0,
|
|
|
|
|
C_WOMAN = 1,
|
|
|
|
|
C_MAN = 2,
|
|
|
|
|
|
|
|
|
|
// Used for adding a singyoku_form_t on top.
|
|
|
|
|
C_FLASH_FORM = C_WOMAN,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum singyoku_person_cel_t {
|
|
|
|
|
C_WOMAN_STILL = 0,
|
|
|
|
|
C_WOMAN_ATTACK_1 = 1,
|
|
|
|
|
C_WOMAN_ATTACK_2 = 2,
|
|
|
|
|
C_MAN_STILL = 3,
|
|
|
|
|
C_MAN_ATTACK = 4,
|
|
|
|
|
|
|
|
|
|
// Used for multiplying with a singyoku_form_t.
|
|
|
|
|
C_PERSON_FORM = (C_MAN_STILL - C_WOMAN_STILL),
|
|
|
|
|
|
|
|
|
|
// Used for adding (C_PERSON_FORM * singyoku_form_t) on top.
|
|
|
|
|
C_STILL = C_WOMAN_STILL,
|
|
|
|
|
C_ATTACK_1 = C_WOMAN_ATTACK_1,
|
|
|
|
|
C_ATTACK_2 = C_WOMAN_ATTACK_2,
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-17 21:43:48 +00:00
|
|
|
|
#define ent_sphere \
|
|
|
|
|
reinterpret_cast<CBossEntitySized<SINGYOKU_W, SINGYOKU_H> &>( \
|
|
|
|
|
boss_entities[0] \
|
|
|
|
|
)
|
|
|
|
|
|
2022-06-17 22:24:25 +00:00
|
|
|
|
#define ent_flash boss_entities[1]
|
|
|
|
|
#define ent_person boss_entities[2]
|
|
|
|
|
|
|
|
|
|
inline void singyoku_ent_load(void) {
|
|
|
|
|
ent_sphere.load("boss1.bos", 0);
|
|
|
|
|
ent_flash.load("boss1_2.bos", 1);
|
|
|
|
|
ent_person.load("boss1_3.bos", 2);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 22:06:07 +00:00
|
|
|
|
inline void singyoku_ent_free(void) {
|
|
|
|
|
bos_entity_free(0);
|
|
|
|
|
bos_entity_free(1);
|
|
|
|
|
bos_entity_free(2);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 21:43:48 +00:00
|
|
|
|
// And that's how you avoid the entity position synchronization code that
|
|
|
|
|
// plagues Elis: By simply only using a single set of coordinates.
|
|
|
|
|
#define ent ent_sphere
|
|
|
|
|
|
|
|
|
|
#define ent_unput_and_put(ent_with_cel, cel) { \
|
|
|
|
|
ent_with_cel.unput_and_put_8(ent.cur_left, ent.cur_top, cel); \
|
|
|
|
|
}
|
|
|
|
|
// --------
|
2020-12-06 12:38:41 +00:00
|
|
|
|
|
2022-06-18 14:26:06 +00:00
|
|
|
|
// Patterns
|
|
|
|
|
// --------
|
|
|
|
|
|
|
|
|
|
#define pattern_state singyoku_pattern_state
|
|
|
|
|
extern union {
|
|
|
|
|
int pellet_count;
|
|
|
|
|
pixel_t speed_in_pixels;
|
|
|
|
|
subpixel_t speed_in_subpixels;
|
|
|
|
|
int unknown;
|
|
|
|
|
} pattern_state;
|
|
|
|
|
// --------
|
|
|
|
|
|
2020-12-17 20:28:17 +00:00
|
|
|
|
#define flash_colors singyoku_flash_colors
|
|
|
|
|
#define invincible singyoku_invincible
|
|
|
|
|
#define invincibility_frame singyoku_invincibility_frame
|
2020-12-09 20:42:10 +00:00
|
|
|
|
#define initial_hp_rendered singyoku_initial_hp_rendered
|
2020-12-17 20:28:17 +00:00
|
|
|
|
extern bool16 invincible;
|
|
|
|
|
extern int invincibility_frame;
|
2020-12-09 20:42:10 +00:00
|
|
|
|
extern bool16 initial_hp_rendered;
|
|
|
|
|
|
2022-06-17 22:24:25 +00:00
|
|
|
|
void singyoku_load(void)
|
|
|
|
|
{
|
|
|
|
|
int col;
|
|
|
|
|
int comp;
|
|
|
|
|
|
|
|
|
|
singyoku_ent_load();
|
|
|
|
|
|
|
|
|
|
grp_palette_load_show_sane("boss1.grp");
|
|
|
|
|
palette_copy(boss_post_defeat_palette, z_Palettes, col, comp);
|
|
|
|
|
stage_palette_set(boss_post_defeat_palette);
|
|
|
|
|
|
|
|
|
|
void singyoku_setup(void);
|
|
|
|
|
singyoku_setup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void singyoku_setup(void)
|
|
|
|
|
{
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
z_palette_set_all_show(z_Palettes);
|
|
|
|
|
|
|
|
|
|
ent.pos_set(PLAYFIELD_RIGHT, PLAYFIELD_TOP, 32);
|
|
|
|
|
ent.hitbox_set(
|
|
|
|
|
((SINGYOKU_W / 4) * 1), ((SINGYOKU_H / 4) * 1),
|
|
|
|
|
((SINGYOKU_W / 4) * 3), ((SINGYOKU_H / 4) * 3)
|
|
|
|
|
);
|
|
|
|
|
ent.hitbox_orb_inactive = false;
|
|
|
|
|
ent_sphere.set_image(0);
|
|
|
|
|
|
|
|
|
|
boss_hp = HP_TOTAL;
|
|
|
|
|
hud_hp_first_white = PHASE_1_END_HP;
|
|
|
|
|
hud_hp_first_redwhite = 2; // fully arbitrary, doesn't indicate anything
|
|
|
|
|
boss_phase = 0;
|
|
|
|
|
|
|
|
|
|
// (redundant, no particles are shown in this fight)
|
|
|
|
|
particles_unput_update_render(PO_INITIALIZE, V_WHITE);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 22:06:07 +00:00
|
|
|
|
void singyoku_free(void)
|
|
|
|
|
{
|
|
|
|
|
singyoku_ent_free();
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 21:43:48 +00:00
|
|
|
|
// Rotates the sphere by the given [cel_delta]. [interval] could be used to
|
|
|
|
|
// restrict this function to certain [boss_phase_frame] intervals, but it's
|
|
|
|
|
// always either 1 or -1 in the original game.
|
|
|
|
|
void sphere_rotate_and_render(int interval, int cel_delta)
|
|
|
|
|
{
|
|
|
|
|
if((boss_phase_frame % interval) != 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Yeah, why is the CBossEntity image variable 16 bits anywhere else to
|
|
|
|
|
// begin with?
|
|
|
|
|
int8_t image_new = (ent_sphere.image() + cel_delta);
|
|
|
|
|
if(image_new > (SPHERE_CELS - 1)) {
|
|
|
|
|
image_new = 0;
|
|
|
|
|
} else if(image_new < 0) {
|
|
|
|
|
image_new = (SPHERE_CELS - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ent_sphere.set_image(image_new);
|
|
|
|
|
ent_unput_and_put(ent_sphere, image_new);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-06 12:38:41 +00:00
|
|
|
|
#define select_for_rank singyoku_select_for_rank
|
|
|
|
|
#include "th01/main/select_r.cpp"
|
2022-06-17 22:38:23 +00:00
|
|
|
|
|
|
|
|
|
// Renders a frame of the sphere rotation, starting from a rotational speed of
|
|
|
|
|
// 0 and gradually speeding up.
|
|
|
|
|
void sphere_accelerate_rotation_and_render(int cel_delta)
|
|
|
|
|
{
|
|
|
|
|
if(boss_phase_frame < 50) {
|
|
|
|
|
ent_sphere.set_image(0);
|
|
|
|
|
if((boss_phase_frame % 4) == 0) {
|
|
|
|
|
ent_unput_and_put(ent_sphere, ent_sphere.image());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame == 50) {
|
|
|
|
|
mdrv2_se_play(8);
|
|
|
|
|
}
|
|
|
|
|
if((boss_phase_frame < 100) && ((boss_phase_frame % 4) == 0)) {
|
|
|
|
|
ent_unput_and_put(ent_sphere, ent_sphere.image());
|
|
|
|
|
|
|
|
|
|
// Only 60 and 68 are actually divisible by 4. The other conditions
|
|
|
|
|
// can never be true.
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame == 50) ||
|
|
|
|
|
(boss_phase_frame == 60) ||
|
|
|
|
|
(boss_phase_frame == 68) ||
|
|
|
|
|
(boss_phase_frame == 74) ||
|
|
|
|
|
(boss_phase_frame == 78) ||
|
|
|
|
|
(boss_phase_frame == 82) ||
|
|
|
|
|
(boss_phase_frame > 82)
|
|
|
|
|
) {
|
|
|
|
|
sphere_rotate_and_render(1, cel_delta);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-18 14:17:11 +00:00
|
|
|
|
|
|
|
|
|
void sphere_move_rotate_and_render(
|
|
|
|
|
pixel_t delta_x, pixel_t delta_y, int interval = 1, int cel_delta = 1
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
if(delta_y < 0) {
|
|
|
|
|
egc_copy_rect_1_to_0_16(
|
|
|
|
|
ent.cur_left,
|
|
|
|
|
(ent.cur_top + delta_y + SINGYOKU_H),
|
|
|
|
|
SINGYOKU_W,
|
|
|
|
|
-delta_y
|
|
|
|
|
);
|
|
|
|
|
} else if(delta_y > 0) {
|
|
|
|
|
egc_copy_rect_1_to_0_16(ent.cur_left, ent.cur_top, SINGYOKU_W, delta_y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZUN bug: Why implicitly limit [delta_x] to 8? (Which is actually at
|
|
|
|
|
// least 16, due to egc_copy_rect_1_to_0_16() rounding up to the next
|
|
|
|
|
// word.) The actual maximum value for [delta_x] that doesn't permanently
|
|
|
|
|
// leave sphere parts in VRAM is 23 – at 24, a byte-aligned sphere moves at
|
|
|
|
|
// a speed of 3 VRAM words every 2 frames, outrunning these unblitting
|
|
|
|
|
// calls which only span a single VRAM word every frame in that case.
|
|
|
|
|
if(delta_x > 0) {
|
|
|
|
|
egc_copy_rect_1_to_0_16(ent.cur_left, ent.cur_top, 8, SINGYOKU_H);
|
|
|
|
|
} else if(delta_x < 0) {
|
|
|
|
|
// ZUN bug: Should be (+ delta_x) instead of (- delta_y). While the
|
|
|
|
|
// latter is always positive whenever we get here, it can easily be
|
|
|
|
|
// smaller than [delta_x] if SinGyoku is moving over a large amount of
|
|
|
|
|
// horizontal space. In that case, [delta_y] is smaller, and word
|
|
|
|
|
// alignment doesn't just not consistently cancel out this bug, but
|
|
|
|
|
// in fact makes it worse: (ent.cur_left - delta_y) will then align to
|
|
|
|
|
// a different word than (ent.cur_left + delta_x) on at least a couple
|
|
|
|
|
// of frames during the animation, and the left edge of the unblitted
|
|
|
|
|
// area will be past the right edge of the sphere in the previous
|
|
|
|
|
// frame. As a result, not a single sphere pixel will be unblitted,
|
|
|
|
|
// and a small stripe of the sphere will be left in VRAM for one frame.
|
|
|
|
|
egc_copy_rect_1_to_0_16(
|
|
|
|
|
(ent.cur_left + SINGYOKU_W - delta_y), ent.cur_top, 8, SINGYOKU_H
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The calling site stops any positive Y movement as soon as SinGyoku's
|
|
|
|
|
// bottom coordinate has reached the bottom of the playfield, so we can get
|
|
|
|
|
// by without any clipping here.
|
|
|
|
|
screen_y_t new_top = (ent.cur_top + delta_y);
|
|
|
|
|
|
|
|
|
|
screen_x_t new_left = clamp_max_2(clamp_min_2(
|
|
|
|
|
(ent.cur_left + delta_x), 0), (PLAYFIELD_RIGHT - SINGYOKU_W)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ent.cur_left = new_left;
|
|
|
|
|
ent.cur_top = new_top;
|
|
|
|
|
|
|
|
|
|
// ZUN bug: We unblit the movement delta every frame, but only blit every
|
|
|
|
|
// second frame?! (Then again, this is what prevents sphere parts from
|
|
|
|
|
// remaining in VRAM at [delta_x] values between 17 and 23 inclusive.)
|
|
|
|
|
if((boss_phase_frame % 2) == 0) {
|
|
|
|
|
sphere_rotate_and_render(interval, cel_delta);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-18 15:34:08 +00:00
|
|
|
|
|
|
|
|
|
void pattern_halfcircle_spray_downwards(void)
|
|
|
|
|
{
|
|
|
|
|
enum {
|
|
|
|
|
KEYFRAME_FIRE = 100,
|
|
|
|
|
KEYFRAME_FIRE_DONE = 160,
|
|
|
|
|
|
|
|
|
|
FIRE_FRAMES = (KEYFRAME_FIRE_DONE - KEYFRAME_FIRE),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define angle pattern0_angle
|
|
|
|
|
#define direction pattern0_direction
|
|
|
|
|
|
|
|
|
|
extern unsigned char angle;
|
|
|
|
|
extern int8_t direction;
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame == 10) {
|
|
|
|
|
direction = ((rand() % 2) == 1) ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame < KEYFRAME_FIRE) {
|
|
|
|
|
sphere_accelerate_rotation_and_render(direction);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame == KEYFRAME_FIRE) {
|
|
|
|
|
select_for_rank(pattern_state.pellet_count, 10, 15, 20, 30);
|
|
|
|
|
angle = (direction == -1) ? 0x00 : 0x80;
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame < KEYFRAME_FIRE_DONE) {
|
|
|
|
|
sphere_rotate_and_render(direction, 1);
|
|
|
|
|
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame % (FIRE_FRAMES / pattern_state.pellet_count)) == 0
|
|
|
|
|
) {
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
angle,
|
|
|
|
|
to_sp(3.125f)
|
|
|
|
|
);
|
|
|
|
|
angle -= ((direction * 0x80) / pattern_state.pellet_count);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef direction
|
|
|
|
|
#undef angle
|
|
|
|
|
}
|
2022-06-18 16:18:56 +00:00
|
|
|
|
|
|
|
|
|
void pattern_slam_into_player_and_back_up(void)
|
|
|
|
|
{
|
|
|
|
|
#define velocity pattern1_velocity
|
|
|
|
|
|
|
|
|
|
extern point_t velocity;
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame < 100) {
|
|
|
|
|
sphere_accelerate_rotation_and_render(1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame == 100) {
|
|
|
|
|
// Could be a local variable.
|
|
|
|
|
select_for_rank(pattern_state.speed_in_pixels, 4, 4, 5, 6);
|
|
|
|
|
|
|
|
|
|
vector2_between(
|
|
|
|
|
(ent.cur_center_x() - (PLAYER_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PLAYER_H / 2)),
|
|
|
|
|
player_left,
|
|
|
|
|
player_top,
|
|
|
|
|
velocity.x,
|
|
|
|
|
velocity.y,
|
|
|
|
|
pattern_state.speed_in_pixels
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Leftover debug code?
|
|
|
|
|
if(velocity.x != -PIXEL_NONE) {
|
|
|
|
|
sphere_move_rotate_and_render(inhibit_Z3(velocity.x), velocity.y);
|
|
|
|
|
if(ent.cur_top > (PLAYFIELD_BOTTOM - SINGYOKU_H)) {
|
|
|
|
|
// Nope, it's in fact a way to differentiate the two subphases of
|
|
|
|
|
// this "pattern", and their completion conditions...
|
|
|
|
|
velocity.x = -PIXEL_NONE;
|
|
|
|
|
|
|
|
|
|
// ... except that this variable also fulfills that job.
|
|
|
|
|
velocity.y = -4;
|
|
|
|
|
}
|
|
|
|
|
} else if(velocity.y == -4) { // See?
|
|
|
|
|
sphere_move_rotate_and_render(0, velocity.y);
|
|
|
|
|
// < rather than <= and no clamping? That makes sure that SinGyoku will
|
|
|
|
|
// overshoot the base position.
|
|
|
|
|
if(ent.cur_top < BASE_TOP) {
|
|
|
|
|
velocity.y = 0;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A quadratic hitbox exactly covering all 96 pixels. Actually more lenient
|
|
|
|
|
// than a perfect circular one.
|
|
|
|
|
if(
|
|
|
|
|
!player_invincible &&
|
|
|
|
|
(ent.cur_left <= player_left) &&
|
|
|
|
|
((ent.cur_left + (SINGYOKU_W - PLAYER_W)) >= player_left) &&
|
|
|
|
|
(ent.cur_top >= (player_top - SINGYOKU_H))
|
|
|
|
|
) {
|
|
|
|
|
done = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef velocity
|
|
|
|
|
}
|
2022-06-18 17:47:01 +00:00
|
|
|
|
|
|
|
|
|
enum singyoku_transform_keyframe_t {
|
|
|
|
|
TKF_START = 100,
|
|
|
|
|
TKF_TO_PERSON = 105,
|
|
|
|
|
TKF_TO_PERSON_RERENDER = 110,
|
|
|
|
|
TKF_TO_PERSON_DONE = 115,
|
|
|
|
|
TKF_PERSON_ATTACK_1 = 135,
|
2022-06-18 20:35:33 +00:00
|
|
|
|
TKF_EXTERNAL_PATTERN_START = 140,
|
2022-06-18 17:47:01 +00:00
|
|
|
|
TKF_PERSON_ATTACK_2 = 160,
|
|
|
|
|
TKF_PERSON_STILL = 185,
|
2022-06-18 20:35:33 +00:00
|
|
|
|
TKF_EXTERNAL_PATTERN_DONE = 220,
|
2022-06-18 17:47:01 +00:00
|
|
|
|
TKF_TO_SPHERE = 240,
|
|
|
|
|
TKF_TO_SPHERE_RERENDER = 245,
|
|
|
|
|
TKF_TO_SPHERE_DONE = 250,
|
|
|
|
|
TKF_DONE = 260,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Ends its corresponding pattern at TKF_DONE.
|
|
|
|
|
void transform_to_person_and_back_to_sphere(
|
|
|
|
|
singyoku_form_t form,
|
|
|
|
|
void pascal on_attack_1() = boss_nop,
|
|
|
|
|
void pascal on_attack_2() = boss_nop,
|
|
|
|
|
void pascal on_still() = boss_nop
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
#define person_cel_for_form (C_PERSON_FORM * form)
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame < TKF_START) {
|
|
|
|
|
sphere_accelerate_rotation_and_render(1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame == TKF_START) {
|
|
|
|
|
ent_unput_and_put(ent_flash, C_SPHERE);
|
|
|
|
|
} else if(
|
|
|
|
|
(boss_phase_frame == TKF_TO_PERSON) ||
|
|
|
|
|
(boss_phase_frame == TKF_TO_PERSON_RERENDER)
|
|
|
|
|
) {
|
|
|
|
|
ent_unput_and_put(ent_flash, (C_FLASH_FORM + form));
|
|
|
|
|
} else if(boss_phase_frame == TKF_TO_PERSON_DONE) {
|
|
|
|
|
ent_unput_and_put(ent_person, (C_STILL + person_cel_for_form));
|
|
|
|
|
} else if(boss_phase_frame == TKF_PERSON_ATTACK_1) {
|
|
|
|
|
ent_person.set_image(C_ATTACK_1 + person_cel_for_form);
|
|
|
|
|
ent_unput_and_put(ent_person, (C_ATTACK_1 + person_cel_for_form));
|
|
|
|
|
on_attack_1();
|
|
|
|
|
} else if(boss_phase_frame == TKF_PERSON_ATTACK_2) {
|
|
|
|
|
// Suggests that there was a male version of C_ATTACK_2 during earlier
|
|
|
|
|
// stages of development?
|
|
|
|
|
ent_person.set_image(C_ATTACK_2 + person_cel_for_form);
|
|
|
|
|
|
|
|
|
|
if(form == F_WOMAN) {
|
|
|
|
|
ent_unput_and_put(ent_person, C_WOMAN_ATTACK_2);
|
|
|
|
|
} else {
|
|
|
|
|
ent_unput_and_put(ent_person, C_MAN_ATTACK);
|
|
|
|
|
}
|
|
|
|
|
on_attack_2();
|
|
|
|
|
} else if(boss_phase_frame == TKF_PERSON_STILL) {
|
|
|
|
|
ent_person.set_image(C_STILL + person_cel_for_form);
|
|
|
|
|
ent_unput_and_put(ent_person, (C_STILL + person_cel_for_form));
|
|
|
|
|
on_still();
|
|
|
|
|
} else if(
|
|
|
|
|
(boss_phase_frame == TKF_TO_SPHERE) ||
|
|
|
|
|
(boss_phase_frame == TKF_TO_SPHERE_RERENDER)
|
|
|
|
|
) {
|
|
|
|
|
ent_unput_and_put(ent_flash, (C_FLASH_FORM + form));
|
|
|
|
|
} else if(boss_phase_frame == TKF_TO_SPHERE_DONE) {
|
|
|
|
|
ent_unput_and_put(ent_flash, C_SPHERE);
|
|
|
|
|
} else if(boss_phase_frame == TKF_DONE) {
|
|
|
|
|
ent_unput_and_put(ent_sphere, 0);
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame > TKF_PERSON_ATTACK_1) &&
|
|
|
|
|
(boss_phase_frame < TKF_TO_SPHERE) &&
|
|
|
|
|
((boss_phase_frame % 4) == 0)
|
|
|
|
|
) {
|
|
|
|
|
ent_unput_and_put(ent_person, ent_person.image());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef person_cel_for_form
|
|
|
|
|
}
|
2022-06-18 18:51:11 +00:00
|
|
|
|
|
|
|
|
|
void pascal fire_chasing_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
subpixel_t chase_speed;
|
|
|
|
|
|
|
|
|
|
select_subpixel_for_rank(chase_speed, 3.4375f, 3.625f, 3.875f, 4.0625f);
|
|
|
|
|
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
(0x00 - 0x10),
|
|
|
|
|
to_sp(1.0f),
|
|
|
|
|
PM_CHASE,
|
|
|
|
|
chase_speed
|
|
|
|
|
);
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
(0x80 + 0x10),
|
|
|
|
|
to_sp(1.0f),
|
|
|
|
|
PM_CHASE,
|
|
|
|
|
chase_speed
|
|
|
|
|
);
|
|
|
|
|
};
|
2022-06-18 20:23:23 +00:00
|
|
|
|
|
|
|
|
|
void pascal fire_crossing_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
subpixel_t speed;
|
|
|
|
|
|
|
|
|
|
select_subpixel_for_rank(speed, 3.75f, 4.375f, 4.6875f, 5.0f);
|
|
|
|
|
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_left + WOMAN_HAND_DISTANCE_FROM_EDGE - (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
(0x40 - 0x10),
|
|
|
|
|
speed
|
|
|
|
|
);
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_right() - WOMAN_HAND_DISTANCE_FROM_EDGE + (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
(0x40 + 0x10),
|
|
|
|
|
speed
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-06-18 20:35:33 +00:00
|
|
|
|
|
|
|
|
|
void pattern_chasing_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
transform_to_person_and_back_to_sphere(F_WOMAN);
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame > TKF_EXTERNAL_PATTERN_START) &&
|
|
|
|
|
((boss_phase_frame % 8) == 0) &&
|
|
|
|
|
(boss_phase_frame <= TKF_EXTERNAL_PATTERN_DONE)
|
|
|
|
|
) {
|
|
|
|
|
fire_chasing_pellets();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pattern_crossing_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
transform_to_person_and_back_to_sphere(F_WOMAN);
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame > TKF_EXTERNAL_PATTERN_START) &&
|
|
|
|
|
((boss_phase_frame % 8) == 0)
|
|
|
|
|
) {
|
|
|
|
|
fire_crossing_pellets();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-18 22:57:50 +00:00
|
|
|
|
|
|
|
|
|
void pascal fire_random_downwards_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
// Could be a local variable.
|
|
|
|
|
select_subpixel_for_rank(pattern_state.speed_in_subpixels,
|
|
|
|
|
3.0f, 3.375f, 3.75f, 4.125f
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++) {
|
|
|
|
|
unsigned char angle = (rand() & (0x80 - 1));
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(ent.cur_center_y() - (PELLET_H / 2)),
|
|
|
|
|
angle,
|
|
|
|
|
pattern_state.speed_in_subpixels
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-18 23:14:52 +00:00
|
|
|
|
|
|
|
|
|
void pascal fire_random_sling_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
// Could be a local variable.
|
|
|
|
|
select_subpixel_for_rank(pattern_state.speed_in_subpixels,
|
|
|
|
|
3.0f, 4.0f, 5.0f, 6.0f
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++) {
|
|
|
|
|
pixel_t offset_x = (rand() % SINGYOKU_W);
|
|
|
|
|
pixel_t offset_y = (rand() % SINGYOKU_H);
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(ent.cur_left + offset_x),
|
|
|
|
|
(ent.cur_top + offset_y),
|
|
|
|
|
0,
|
|
|
|
|
0x00,
|
|
|
|
|
PM_SLING_AIMED,
|
|
|
|
|
pattern_state.speed_in_subpixels
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-18 23:19:45 +00:00
|
|
|
|
|
|
|
|
|
void pattern_random_downwards_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
transform_to_person_and_back_to_sphere(
|
|
|
|
|
F_MAN,
|
|
|
|
|
fire_random_downwards_pellets,
|
|
|
|
|
fire_random_downwards_pellets,
|
|
|
|
|
fire_random_downwards_pellets
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pattern_random_sling_pellets(void)
|
|
|
|
|
{
|
|
|
|
|
transform_to_person_and_back_to_sphere(
|
|
|
|
|
F_MAN,
|
|
|
|
|
fire_random_sling_pellets,
|
|
|
|
|
fire_random_sling_pellets,
|
|
|
|
|
fire_random_sling_pellets
|
|
|
|
|
);
|
|
|
|
|
}
|