2020-12-09 20:42:10 +00:00
|
|
|
|
/// Makai Stage 10 Boss - YuugenMagan
|
|
|
|
|
/// ---------------------------------
|
|
|
|
|
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include <stddef.h>
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include <stdlib.h>
|
2020-12-09 20:42:10 +00:00
|
|
|
|
#include "platform.h"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "decomp.hpp"
|
2021-10-22 08:56:07 +00:00
|
|
|
|
#include "pc98.h"
|
|
|
|
|
#include "planar.h"
|
2022-07-18 18:50:36 +00:00
|
|
|
|
#include "master.hpp"
|
2022-08-15 07:42:57 +00:00
|
|
|
|
#include "th01/resident.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/v_colors.hpp"
|
|
|
|
|
#include "th01/math/area.hpp"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "th01/math/dir.hpp"
|
2022-07-19 16:56:08 +00:00
|
|
|
|
#include "th01/math/overlap.hpp"
|
2022-07-20 05:32:52 +00:00
|
|
|
|
#include "th01/math/polar.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/math/subpixel.hpp"
|
2022-07-19 18:41:02 +00:00
|
|
|
|
#include "th01/math/vector.hpp"
|
2022-07-19 17:57:13 +00:00
|
|
|
|
#include "th01/hardware/egc.h"
|
2022-07-19 16:56:08 +00:00
|
|
|
|
#include "th01/hardware/graph.h"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/hardware/palette.h"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "th01/snd/mdrv2.h"
|
2021-10-22 08:56:07 +00:00
|
|
|
|
#include "th01/main/vars.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/formats/grp.h"
|
|
|
|
|
#include "th01/formats/pf.hpp"
|
2021-10-22 08:56:07 +00:00
|
|
|
|
#include "th01/formats/ptn.hpp"
|
2022-07-19 17:57:13 +00:00
|
|
|
|
#include "th01/sprites/pellet.h"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/main/particle.hpp"
|
|
|
|
|
#include "th01/main/playfld.hpp"
|
2022-07-15 01:34:20 +00:00
|
|
|
|
#include "th01/main/player/orb.hpp"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "th01/main/boss/defeat.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/main/boss/boss.hpp"
|
|
|
|
|
#include "th01/main/boss/entity_a.hpp"
|
|
|
|
|
#include "th01/main/boss/palette.hpp"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "th01/main/bullet/laser_s.hpp"
|
2022-07-20 05:32:52 +00:00
|
|
|
|
#include "th01/main/bullet/line.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/main/bullet/missile.hpp"
|
2022-07-19 17:57:13 +00:00
|
|
|
|
#include "th01/main/bullet/pellet.hpp"
|
2022-06-21 17:39:02 +00:00
|
|
|
|
#include "th01/main/hud/hp.hpp"
|
2022-07-19 16:56:08 +00:00
|
|
|
|
#include "th01/main/player/player.hpp"
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#include "th01/main/stage/palette.hpp"
|
|
|
|
|
#include "th01/main/stage/stages.hpp"
|
2020-12-09 20:42:10 +00:00
|
|
|
|
|
2022-07-28 19:17:41 +00:00
|
|
|
|
// Coordinates
|
|
|
|
|
// -----------
|
|
|
|
|
|
|
|
|
|
static const pixel_t EYE_W = 64;
|
|
|
|
|
static const pixel_t EYE_H = 48;
|
2022-07-19 17:57:13 +00:00
|
|
|
|
static const pixel_t PUPIL_H = 12;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
static const pixel_t IRIS_H = 7;
|
2022-07-28 19:17:41 +00:00
|
|
|
|
|
|
|
|
|
static const pixel_t EYE_LATERAL_CENTER_DISTANCE_X = 224;
|
|
|
|
|
static const pixel_t EYE_SOUTH_CENTER_DISTANCE_X = 96;
|
|
|
|
|
static const pixel_t EYE_NORTH_LATERAL_DISTANCE_Y = 64;
|
|
|
|
|
static const pixel_t EYE_LATERAL_SOUTH_DISTANCE_Y = 48;
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Center of the kimono figure
|
|
|
|
|
static const screen_y_t PENTAGRAM_REGULAR_CENTER_Y = 189;
|
|
|
|
|
|
2022-07-28 19:17:41 +00:00
|
|
|
|
static const screen_x_t EYE_WEST_LEFT = (
|
|
|
|
|
PLAYFIELD_CENTER_X - EYE_LATERAL_CENTER_DISTANCE_X - (EYE_W / 2)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const screen_x_t EYE_EAST_LEFT = (
|
|
|
|
|
PLAYFIELD_CENTER_X + EYE_LATERAL_CENTER_DISTANCE_X - (EYE_W / 2)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const screen_x_t EYE_SOUTHWEST_LEFT = (
|
|
|
|
|
PLAYFIELD_CENTER_X - EYE_SOUTH_CENTER_DISTANCE_X - (EYE_W / 2)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const screen_x_t EYE_SOUTHEAST_LEFT = (
|
|
|
|
|
PLAYFIELD_CENTER_X + EYE_SOUTH_CENTER_DISTANCE_X - (EYE_W / 2)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const screen_x_t EYE_NORTH_LEFT = (PLAYFIELD_CENTER_X - (EYE_W / 2));
|
|
|
|
|
|
|
|
|
|
static const screen_y_t EYE_NORTH_TOP = PLAYFIELD_TOP;
|
|
|
|
|
static const screen_y_t EYE_LATERAL_TOP = (
|
|
|
|
|
EYE_NORTH_TOP + EYE_NORTH_LATERAL_DISTANCE_Y
|
|
|
|
|
);
|
|
|
|
|
static const screen_y_t EYE_SOUTH_TOP = (
|
|
|
|
|
EYE_LATERAL_TOP + EYE_LATERAL_SOUTH_DISTANCE_Y
|
|
|
|
|
);
|
|
|
|
|
// -----------
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
enum yuugenmagan_colors_t {
|
|
|
|
|
COL_YOKOSHIMA = 15, // The big 邪 in the background
|
|
|
|
|
};
|
2021-10-22 08:56:07 +00:00
|
|
|
|
|
2022-06-21 17:39:02 +00:00
|
|
|
|
// Always denotes the last phase that ends with that amount of HP.
|
|
|
|
|
enum yuugenmagan_hp_t {
|
|
|
|
|
HP_TOTAL = 16,
|
2022-07-27 20:19:30 +00:00
|
|
|
|
PHASE_1_END_HP = 15,
|
2022-06-21 17:39:02 +00:00
|
|
|
|
PHASE_3_END_HP = 12,
|
2022-07-27 20:19:30 +00:00
|
|
|
|
PHASE_5_END_HP = 10,
|
2022-06-21 17:39:02 +00:00
|
|
|
|
PHASE_7_END_HP = 8,
|
2022-07-27 20:19:30 +00:00
|
|
|
|
PHASE_9_END_HP = 2,
|
|
|
|
|
PHASE_13_END_HP = 0,
|
2022-06-21 17:39:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Entities
|
|
|
|
|
// --------
|
|
|
|
|
|
|
|
|
|
static const int EYE_COUNT = 5;
|
|
|
|
|
|
2022-07-22 15:02:55 +00:00
|
|
|
|
enum eye_cel_t {
|
2022-06-21 17:39:02 +00:00
|
|
|
|
C_HIDDEN = 0,
|
2022-07-22 15:02:55 +00:00
|
|
|
|
C_CLOSED = 1,
|
|
|
|
|
C_HALFOPEN = 2,
|
|
|
|
|
C_DOWN = 3,
|
|
|
|
|
C_LEFT = 4,
|
|
|
|
|
C_RIGHT = 5,
|
|
|
|
|
C_AHEAD = 6,
|
2022-06-21 17:39:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-07-20 17:19:23 +00:00
|
|
|
|
// Eye flags
|
|
|
|
|
typedef int8_t eye_flag_t;
|
|
|
|
|
|
|
|
|
|
// Code generation wants 16-bit constants, unfortunately
|
|
|
|
|
static const int EF_NONE = 0;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
static const int EF_WEST = (1 << 0);
|
|
|
|
|
static const int EF_EAST = (1 << 1);
|
|
|
|
|
static const int EF_SOUTHWEST = (1 << 2);
|
|
|
|
|
static const int EF_SOUTHEAST = (1 << 3);
|
|
|
|
|
static const int EF_NORTH = (1 << 4);
|
|
|
|
|
static const int EF_RESET = (1 << 5);
|
2022-07-20 17:19:23 +00:00
|
|
|
|
|
2022-07-19 16:56:08 +00:00
|
|
|
|
struct CEyeEntity : public CBossEntitySized<EYE_W, EYE_H> {
|
|
|
|
|
// Relative pupil and iris coordinates
|
|
|
|
|
// -----------------------------------
|
|
|
|
|
// These only apply to the player-facing cels (C_DOWN, C_LEFT, and C_RIGHT).
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: Doesn't really correspond to any precise feature of any eye
|
|
|
|
|
// cel. The best match is the center of the iris on C_DOWN, but even that
|
|
|
|
|
// would be off by 1 pixel – not to mention very wrong for every other cel.
|
|
|
|
|
screen_x_t offcenter_x(void) const {
|
|
|
|
|
return (cur_center_x() - 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Correct for C_LEFT and C_RIGHT, off by 1 pixel for C_DOWN.
|
|
|
|
|
screen_y_t iris_top(void) const {
|
|
|
|
|
return (cur_center_y() + 4);
|
|
|
|
|
}
|
2022-07-19 17:57:13 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Correct for C_LEFT and C_RIGHT, off by 1 pixel for C_DOWN.
|
|
|
|
|
screen_y_t iris_center_y(void) const {
|
|
|
|
|
return (iris_top() + (IRIS_H / 2) + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 17:57:13 +00:00
|
|
|
|
// Correct for C_LEFT and C_RIGHT, off by 1 pixel for C_DOWN.
|
|
|
|
|
screen_y_t pupil_bottom(void) const {
|
|
|
|
|
return (cur_center_y() + PUPIL_H);
|
|
|
|
|
}
|
2022-07-19 16:56:08 +00:00
|
|
|
|
// -----------------------------------
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
void track_player(void) {
|
|
|
|
|
if((cur_center_x() - player_left) > (EYE_W / 2)) {
|
|
|
|
|
set_image(C_LEFT);
|
|
|
|
|
} else if((player_left - cur_center_x()) > (EYE_W / 2)) {
|
|
|
|
|
set_image(C_RIGHT);
|
|
|
|
|
} else {
|
|
|
|
|
set_image(C_DOWN);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 16:56:08 +00:00
|
|
|
|
void downwards_laser_put(void) const {
|
|
|
|
|
graph_r_vline(
|
|
|
|
|
offcenter_x(), iris_top(), (PLAYFIELD_BOTTOM - 2), V_WHITE
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-19 17:57:13 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
void downwards_laser_unput(void) const {
|
|
|
|
|
graph_r_line_unput(
|
|
|
|
|
offcenter_x(), iris_top(), offcenter_x(), PLAYFIELD_BOTTOM
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void fire_from_iris_center(pellet_group_t group, float speed_base) const {
|
|
|
|
|
Pellets.add_group(
|
|
|
|
|
(cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(iris_center_y() - (PELLET_H / 2)),
|
|
|
|
|
group,
|
|
|
|
|
to_sp(speed_base)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 17:57:13 +00:00
|
|
|
|
void fire_from_bottom_center(pellet_group_t group, float speed_base) const {
|
|
|
|
|
Pellets.add_group(
|
|
|
|
|
(cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(pupil_bottom() - (PELLET_H / 2)),
|
|
|
|
|
group,
|
|
|
|
|
to_sp(speed_base)
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
void fire_from_bottom_center(
|
|
|
|
|
bool mirror_angle_horizontally,
|
|
|
|
|
const unsigned char &angle,
|
|
|
|
|
float speed_base,
|
|
|
|
|
pellet_motion_t motion_type = PM_REGULAR,
|
|
|
|
|
float speed_for_motion_fixed = 0.0f
|
|
|
|
|
) const {
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
(cur_center_x() - (PELLET_W / 2)),
|
|
|
|
|
(pupil_bottom() - (PELLET_H / 2)),
|
|
|
|
|
(mirror_angle_horizontally ? (0x80 - angle) : angle),
|
|
|
|
|
to_sp(speed_base),
|
|
|
|
|
motion_type,
|
|
|
|
|
to_sp(speed_for_motion_fixed)
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-19 16:56:08 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define eye_west reinterpret_cast<CEyeEntity &>(boss_entity_0)
|
|
|
|
|
#define eye_east reinterpret_cast<CEyeEntity &>(boss_entity_1)
|
|
|
|
|
#define eye_southwest reinterpret_cast<CEyeEntity &>(boss_entity_2)
|
|
|
|
|
#define eye_southeast reinterpret_cast<CEyeEntity &>(boss_entity_3)
|
|
|
|
|
#define eye_north reinterpret_cast<CEyeEntity &>(boss_entity_4)
|
2022-06-21 17:39:02 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// For [eye_predicate], pass a macro that takes a single eye_flag_t parameter.
|
|
|
|
|
#define eyes_foreach_if(eye_predicate, method) { \
|
|
|
|
|
if(eye_predicate( EF_WEST)) { eye_west.method(); } \
|
|
|
|
|
if(eye_predicate( EF_EAST)) { eye_east.method(); } \
|
|
|
|
|
if(eye_predicate(EF_SOUTHWEST)) { eye_southwest.method(); } \
|
|
|
|
|
if(eye_predicate(EF_SOUTHEAST)) { eye_southeast.method(); } \
|
|
|
|
|
if(eye_predicate( EF_NORTH)) { eye_north.method(); } \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void eyes_locked_unput_and_put_then_track(eye_flag_t eye_flag) {
|
|
|
|
|
#define eye_flag_is_set(bit) ( \
|
|
|
|
|
eye_flag & bit \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Unblitting/rendering first, and *then* tracking? Depending on when the
|
|
|
|
|
// eye's cel is updated within the entity's lock, this order introduces a
|
|
|
|
|
// variable tracking latency from 1 to 4 frames. Which isn't all too bad,
|
|
|
|
|
// and might even be considered somewhat realistic. Certainly not ZUN quirk
|
|
|
|
|
// territory.
|
|
|
|
|
eyes_foreach_if(eye_flag_is_set, locked_unput_and_put_8);
|
|
|
|
|
eyes_foreach_if(eye_flag_is_set, track_player);
|
|
|
|
|
|
|
|
|
|
#undef eye_flag_is_set
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-28 18:52:31 +00:00
|
|
|
|
#define eyes_set_image(eye_flag, cel) { \
|
|
|
|
|
if(eye_flag & EF_WEST) { eye_west.set_image(cel); } \
|
|
|
|
|
if(eye_flag & EF_EAST) { eye_east.set_image(cel); } \
|
|
|
|
|
if(eye_flag & EF_SOUTHWEST) { eye_southwest.set_image(cel); } \
|
|
|
|
|
if(eye_flag & EF_SOUTHEAST) { eye_southeast.set_image(cel); } \
|
|
|
|
|
if(eye_flag & EF_NORTH) { eye_north.set_image(cel); } \
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 17:39:02 +00:00
|
|
|
|
inline void yuugenmagan_ent_load(void) {
|
2022-07-29 18:33:37 +00:00
|
|
|
|
eye_west.load("boss2.bos", 0);
|
2022-08-06 20:47:32 +00:00
|
|
|
|
eye_east.metadata_assign(eye_west);
|
|
|
|
|
eye_southwest.metadata_assign(eye_west);
|
|
|
|
|
eye_southeast.metadata_assign(eye_west);
|
|
|
|
|
eye_north.metadata_assign(eye_west);
|
2022-06-21 17:39:02 +00:00
|
|
|
|
}
|
2022-07-18 18:56:01 +00:00
|
|
|
|
|
|
|
|
|
inline void yuugenmagan_ent_free(void) {
|
|
|
|
|
bos_entity_free(0);
|
|
|
|
|
}
|
2022-06-21 17:39:02 +00:00
|
|
|
|
// --------
|
|
|
|
|
|
2022-01-29 23:23:27 +00:00
|
|
|
|
// .PTN
|
|
|
|
|
// ----
|
2021-10-22 08:56:07 +00:00
|
|
|
|
|
|
|
|
|
static const main_ptn_slot_t PTN_SLOT_MISSILE = PTN_SLOT_BOSS_1;
|
2022-01-29 23:23:27 +00:00
|
|
|
|
// ----
|
2022-06-21 17:39:02 +00:00
|
|
|
|
|
2022-07-19 17:57:13 +00:00
|
|
|
|
// Patterns
|
|
|
|
|
// --------
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Yes! Finally a proper stateless version of this concept!
|
|
|
|
|
inline int select_for_rank(
|
|
|
|
|
int for_easy, int for_normal, int for_hard, int for_lunatic
|
|
|
|
|
) {
|
|
|
|
|
return (
|
|
|
|
|
(rank == RANK_EASY) ? for_easy :
|
|
|
|
|
(rank == RANK_NORMAL) ? for_normal :
|
|
|
|
|
(rank == RANK_HARD) ? for_hard :
|
|
|
|
|
(rank == RANK_LUNATIC) ? for_lunatic :
|
|
|
|
|
for_normal
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static int pattern_interval;
|
2022-07-19 17:57:13 +00:00
|
|
|
|
// --------
|
|
|
|
|
|
2022-06-21 17:39:02 +00:00
|
|
|
|
void yuugenmagan_load(void)
|
|
|
|
|
{
|
|
|
|
|
yuugenmagan_ent_load();
|
|
|
|
|
void yuugenmagan_setup(void);
|
|
|
|
|
yuugenmagan_setup();
|
|
|
|
|
Missiles.load(PTN_SLOT_MISSILE);
|
|
|
|
|
Missiles.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void yuugenmagan_setup(void)
|
|
|
|
|
{
|
|
|
|
|
int col;
|
|
|
|
|
int comp;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
grp_palette_load_show("boss2.grp");
|
2022-06-21 17:39:02 +00:00
|
|
|
|
boss_palette_snap();
|
|
|
|
|
|
2022-08-06 20:47:32 +00:00
|
|
|
|
eye_west .set_image(C_HIDDEN);
|
|
|
|
|
eye_east .set_image(C_HIDDEN);
|
|
|
|
|
eye_southwest.set_image(C_HIDDEN);
|
|
|
|
|
eye_southeast.set_image(C_HIDDEN);
|
|
|
|
|
eye_north .set_image(C_HIDDEN);
|
2022-06-21 17:39:02 +00:00
|
|
|
|
|
|
|
|
|
palette_copy(boss_post_defeat_palette, z_Palettes, col, comp);
|
|
|
|
|
|
|
|
|
|
// These exactly correspond to the yellow boxes in BOSS2.GRP.
|
2022-07-28 19:17:41 +00:00
|
|
|
|
eye_west .pos_set( EYE_WEST_LEFT, EYE_LATERAL_TOP);
|
|
|
|
|
eye_east .pos_set( EYE_EAST_LEFT, EYE_LATERAL_TOP);
|
|
|
|
|
eye_southwest.pos_set(EYE_SOUTHWEST_LEFT, EYE_SOUTH_TOP);
|
|
|
|
|
eye_southeast.pos_set(EYE_SOUTHEAST_LEFT, EYE_SOUTH_TOP);
|
|
|
|
|
eye_north .pos_set( EYE_NORTH_LEFT, EYE_NORTH_TOP);
|
2022-08-06 20:47:32 +00:00
|
|
|
|
|
|
|
|
|
eye_west .hitbox_orb_set(-4, -4, (EYE_W + 4), EYE_H);
|
|
|
|
|
eye_east .hitbox_orb_set(-4, -4, (EYE_W + 4), EYE_H);
|
|
|
|
|
eye_southwest.hitbox_orb_set(-4, -4, (EYE_W + 4), EYE_H);
|
|
|
|
|
eye_southeast.hitbox_orb_set(-4, -4, (EYE_W + 4), EYE_H);
|
|
|
|
|
eye_north .hitbox_orb_set(-4, -4, (EYE_W + 4), EYE_H);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
eye_west .hitbox_orb_deactivate();
|
|
|
|
|
eye_east .hitbox_orb_deactivate();
|
|
|
|
|
eye_southwest.hitbox_orb_deactivate();
|
|
|
|
|
eye_southeast.hitbox_orb_deactivate();
|
|
|
|
|
eye_north .hitbox_orb_deactivate();
|
2022-06-21 17:39:02 +00:00
|
|
|
|
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
boss_phase = 0;
|
|
|
|
|
boss_hp = HP_TOTAL;
|
|
|
|
|
hud_hp_first_white = PHASE_3_END_HP;
|
|
|
|
|
hud_hp_first_redwhite = PHASE_7_END_HP;
|
|
|
|
|
|
2022-08-15 17:15:40 +00:00
|
|
|
|
// ZUN bloat: Redundant, no particles are shown in this fight.
|
2022-06-21 17:39:02 +00:00
|
|
|
|
particles_unput_update_render(PO_INITIALIZE, V_WHITE);
|
|
|
|
|
}
|
2022-07-18 18:50:36 +00:00
|
|
|
|
|
|
|
|
|
void unused_formula(int a, int b, int& ret)
|
|
|
|
|
{
|
|
|
|
|
double delta = (b - a);
|
|
|
|
|
ret = ((delta * isqrt(3)) / 2.0f);
|
|
|
|
|
}
|
2022-07-18 18:56:01 +00:00
|
|
|
|
|
|
|
|
|
void yuugenmagan_free(void)
|
|
|
|
|
{
|
|
|
|
|
yuugenmagan_ent_free();
|
|
|
|
|
ptn_free(PTN_SLOT_MISSILE);
|
|
|
|
|
}
|
2022-07-19 16:56:08 +00:00
|
|
|
|
|
|
|
|
|
// Phases
|
|
|
|
|
// ------
|
|
|
|
|
|
|
|
|
|
static const int EYE_OPENING_FRAMES = 60;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
static const int EYE_TOGGLE_FRAMES = (EYE_OPENING_FRAMES + 10);
|
|
|
|
|
static const int SUBPHASE_PREPARE_FRAMES = 100;
|
|
|
|
|
static const int SUBPHASE_TIMEOUT_FRAMES = 200;
|
2022-07-19 16:56:08 +00:00
|
|
|
|
|
|
|
|
|
enum phase_0_keyframe_t {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
KEYFRAME_LATERAL_OPENING = SUBPHASE_PREPARE_FRAMES,
|
2022-07-19 16:56:08 +00:00
|
|
|
|
KEYFRAME_SOUTH_OPENING = 120,
|
|
|
|
|
KEYFRAME_NORTH_OPENING = 140,
|
|
|
|
|
KEYFRAME_LATERAL_OPEN = (KEYFRAME_LATERAL_OPENING + EYE_OPENING_FRAMES),
|
|
|
|
|
KEYFRAME_SOUTH_OPEN = (KEYFRAME_SOUTH_OPENING + EYE_OPENING_FRAMES),
|
|
|
|
|
KEYFRAME_NORTH_OPEN = (KEYFRAME_NORTH_OPENING + EYE_OPENING_FRAMES),
|
|
|
|
|
|
|
|
|
|
KEYFRAME_CLOSING = 240,
|
|
|
|
|
KEYFRAME_CLOSED = 260,
|
|
|
|
|
KEYFRAME_HIDDEN = 280,
|
|
|
|
|
KEYFRAME_LATERAL_LASER_DONE = 300,
|
|
|
|
|
KEYFRAME_SOUTH_LASER_DONE = 320,
|
|
|
|
|
KEYFRAME_NORTH_LASER_DONE = 330,
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// Matches the animation in eyes_toggle_and_yokoshima_recolor().
|
2022-07-27 20:19:30 +00:00
|
|
|
|
inline void phase_0_eye_open(CEyeEntity& eye, int frame, int frame_first) {
|
|
|
|
|
eye.set_image(
|
|
|
|
|
((frame - frame_first) < ((EYE_OPENING_FRAMES / 3) * 1)) ? C_CLOSED :
|
|
|
|
|
((frame - frame_first) < ((EYE_OPENING_FRAMES / 3) * 2)) ? C_HALFOPEN :
|
|
|
|
|
((frame - frame_first) < ((EYE_OPENING_FRAMES / 3) * 3)) ? C_AHEAD :
|
|
|
|
|
/* */ C_DOWN
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void phase_0_eye_close(CEyeEntity& eye, int frame) {
|
|
|
|
|
eye.set_image(
|
|
|
|
|
(frame == KEYFRAME_CLOSING) ? C_HALFOPEN :
|
|
|
|
|
(frame == KEYFRAME_CLOSED) ? C_CLOSED : C_HIDDEN
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void phase_0_eyes_open(int frame) {
|
|
|
|
|
if(
|
|
|
|
|
(frame >= KEYFRAME_LATERAL_OPENING) &&
|
|
|
|
|
(frame <= KEYFRAME_LATERAL_OPEN)
|
|
|
|
|
) {
|
|
|
|
|
phase_0_eye_open(eye_west, frame, KEYFRAME_LATERAL_OPENING);
|
|
|
|
|
phase_0_eye_open(eye_east, frame, KEYFRAME_LATERAL_OPENING);
|
|
|
|
|
}
|
|
|
|
|
if((frame >= KEYFRAME_SOUTH_OPENING) && (frame <= KEYFRAME_SOUTH_OPEN)) {
|
|
|
|
|
phase_0_eye_open(eye_southwest, frame, KEYFRAME_SOUTH_OPENING);
|
|
|
|
|
phase_0_eye_open(eye_southeast, frame, KEYFRAME_SOUTH_OPENING);
|
|
|
|
|
}
|
|
|
|
|
if((frame >= KEYFRAME_NORTH_OPENING) && (frame <= KEYFRAME_NORTH_OPEN)) {
|
|
|
|
|
phase_0_eye_open(eye_north, frame, KEYFRAME_NORTH_OPENING);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void phase_0_eyes_close(int frame) {
|
|
|
|
|
phase_0_eye_close(eye_west, frame);
|
|
|
|
|
phase_0_eye_close(eye_east, frame);
|
|
|
|
|
phase_0_eye_close(eye_southwest, frame);
|
|
|
|
|
phase_0_eye_close(eye_southeast, frame);
|
|
|
|
|
phase_0_eye_close(eye_north, frame);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 16:56:08 +00:00
|
|
|
|
void phase_0_downwards_lasers(void)
|
|
|
|
|
{
|
|
|
|
|
#define laser_hittest(eye, player_w) ( \
|
|
|
|
|
overlap_low_center_lt_gt( \
|
|
|
|
|
player_left, player_w, eye.offcenter_x(), (EYE_W - (EYE_W / 4)) \
|
|
|
|
|
) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame > KEYFRAME_LATERAL_OPEN) {
|
|
|
|
|
if(boss_phase_frame < KEYFRAME_LATERAL_LASER_DONE) {
|
|
|
|
|
eye_west.downwards_laser_put();
|
|
|
|
|
eye_east.downwards_laser_put();
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: The hitbox for the rightmost eye is much larger than
|
|
|
|
|
// the other ones?!
|
|
|
|
|
if(
|
|
|
|
|
laser_hittest(eye_west, PLAYER_W) ||
|
|
|
|
|
laser_hittest(eye_east, (PLAYER_W / 4))
|
|
|
|
|
) {
|
2022-08-14 03:15:31 +00:00
|
|
|
|
player_is_hit = true;
|
2022-07-19 16:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame > KEYFRAME_SOUTH_OPEN) {
|
|
|
|
|
if(boss_phase_frame < KEYFRAME_SOUTH_LASER_DONE) {
|
|
|
|
|
eye_southwest.downwards_laser_put();
|
|
|
|
|
eye_southeast.downwards_laser_put();
|
|
|
|
|
if(
|
|
|
|
|
laser_hittest(eye_southwest, PLAYER_W) ||
|
|
|
|
|
laser_hittest(eye_southeast, PLAYER_W)
|
|
|
|
|
) {
|
2022-08-14 03:15:31 +00:00
|
|
|
|
player_is_hit = true;
|
2022-07-19 16:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame > KEYFRAME_NORTH_OPEN) {
|
|
|
|
|
eye_north.downwards_laser_put();
|
|
|
|
|
if(laser_hittest(eye_north, PLAYER_W)) {
|
2022-08-14 03:15:31 +00:00
|
|
|
|
player_is_hit = true;
|
2022-07-19 16:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(player_invincible) {
|
2022-08-14 03:15:31 +00:00
|
|
|
|
player_is_hit = false;
|
2022-07-19 16:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef laser_hittest
|
|
|
|
|
}
|
2022-07-19 17:57:13 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
inline void phase_0_fire(CEyeEntity& eye) {
|
|
|
|
|
eye.fire_from_iris_center(PG_5_SPREAD_WIDE_AIMED, 3.375f);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 17:57:13 +00:00
|
|
|
|
inline void fire_from_lateral(pellet_group_t group, float speed_base) {
|
|
|
|
|
eye_west.fire_from_bottom_center(group, speed_base);
|
|
|
|
|
eye_east.fire_from_bottom_center(group, speed_base);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void phase_1_pellets_from_lateral()
|
|
|
|
|
{
|
|
|
|
|
if((boss_phase_frame % pattern_interval) == 10) {
|
|
|
|
|
fire_from_lateral(PG_2_SPREAD_WIDE_AIMED, 1.5f);
|
|
|
|
|
} else if((boss_phase_frame % pattern_interval) == 25) {
|
|
|
|
|
fire_from_lateral(PG_2_SPREAD_WIDE_AIMED, 1.75f);
|
|
|
|
|
} else if((boss_phase_frame % pattern_interval) == 40) {
|
|
|
|
|
fire_from_lateral(PG_2_SPREAD_WIDE_AIMED, 2.0f);
|
|
|
|
|
} else if((boss_phase_frame % pattern_interval) == 60) {
|
|
|
|
|
fire_from_lateral(PG_2_SPREAD_WIDE_AIMED, 2.75f);
|
|
|
|
|
} else if((boss_phase_frame % pattern_interval) == 80) {
|
|
|
|
|
fire_from_lateral(PG_2_SPREAD_WIDE_AIMED, 3.125f);
|
|
|
|
|
} else if((boss_phase_frame % pattern_interval) == 110) {
|
|
|
|
|
fire_from_lateral(PG_1_AIMED, 4.25f);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-19 16:56:08 +00:00
|
|
|
|
// ------
|
2022-07-19 18:41:02 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Aimed missiles
|
|
|
|
|
// --------------
|
|
|
|
|
|
|
|
|
|
static const int MISSILE_INTERVAL = 8;
|
|
|
|
|
|
|
|
|
|
// The original code generation of missile_angle() combines all constant parts
|
|
|
|
|
// into a single immediate argument, so these can't be defined in terms of
|
|
|
|
|
// CEyeEntity's cur_center_x() and cur_center_y() helpers.
|
|
|
|
|
|
2022-07-19 18:41:02 +00:00
|
|
|
|
// (cur_center_x() + 4) kind of corresponds to the right edge of C_DOWN.
|
|
|
|
|
static const pixel_t MISSILE_OFFSET_LEFT = ((EYE_W / 2) + 4 - (MISSILE_W / 2));
|
|
|
|
|
|
|
|
|
|
// (pupil_bottom() - (MISSILE_H / 2))
|
|
|
|
|
static const pixel_t MISSILE_OFFSET_TOP = (
|
|
|
|
|
(EYE_H / 2) + PUPIL_H - (MISSILE_H / 2)
|
|
|
|
|
);
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
inline unsigned char missile_angle(
|
|
|
|
|
const CEyeEntity& eye, const screen_x_t& target_x
|
|
|
|
|
) {
|
|
|
|
|
return iatan2(
|
|
|
|
|
(player_bottom() - eye.cur_top + MISSILE_OFFSET_TOP),
|
|
|
|
|
(target_x - eye.cur_left + MISSILE_OFFSET_LEFT)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 18:41:02 +00:00
|
|
|
|
void pascal near fire_missile_pair_from_south(
|
|
|
|
|
unsigned char angle_for_southwest, unsigned char angle_for_southeast
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
point_t velocity;
|
|
|
|
|
|
|
|
|
|
vector2(velocity.x, velocity.y, 8, angle_for_southwest);
|
|
|
|
|
Missiles.add(
|
|
|
|
|
(eye_southwest.cur_left + MISSILE_OFFSET_LEFT),
|
|
|
|
|
(eye_southwest.cur_top + MISSILE_OFFSET_TOP),
|
|
|
|
|
velocity.x,
|
|
|
|
|
velocity.y
|
|
|
|
|
);
|
|
|
|
|
vector2(velocity.x, velocity.y, 8, angle_for_southeast);
|
|
|
|
|
Missiles.add(
|
|
|
|
|
(eye_southeast.cur_left + MISSILE_OFFSET_LEFT),
|
|
|
|
|
(eye_southeast.cur_top + MISSILE_OFFSET_TOP),
|
|
|
|
|
velocity.x,
|
|
|
|
|
velocity.y
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-20 05:32:52 +00:00
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
enum missile_subphase_t {
|
|
|
|
|
MSP_PREPARE = 0,
|
|
|
|
|
MSP_AIM_AND_FIRE_AROUND = 1,
|
|
|
|
|
MSP_SHIFT_ANGLE_1 = 2,
|
|
|
|
|
MSP_SHIFT_ANGLE_2 = 3,
|
|
|
|
|
MSP_COUNT = 4,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define missile_subphase_variation(variation, msp) ( \
|
|
|
|
|
(variation * MSP_COUNT) + msp \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MODDERS: Simply replace with ((subphase / MSP_COUNT) == variation).
|
|
|
|
|
#define missile_subphase_variation_is( \
|
|
|
|
|
subphase, variation, msp, variations_max \
|
|
|
|
|
) ( \
|
|
|
|
|
(variations_max >= (variation + 1)) && \
|
|
|
|
|
(subphase == missile_subphase_variation(variation, msp)) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MODDERS: Simply replace with ((subphase % MSP_COUNT) == msp).
|
|
|
|
|
#define missile_subphase_is(subphase, msp, variations_max) ( \
|
|
|
|
|
missile_subphase_variation_is(subphase, 0, msp, variations_max) || \
|
|
|
|
|
missile_subphase_variation_is(subphase, 1, msp, variations_max) || \
|
|
|
|
|
missile_subphase_variation_is(subphase, 2, msp, variations_max) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
inline void missile_pairs_shift_angle_1_away(
|
|
|
|
|
const int& subphase,
|
|
|
|
|
unsigned char& angle_southwest,
|
|
|
|
|
unsigned char& angle_southeast
|
|
|
|
|
) {
|
|
|
|
|
angle_southwest -= 0x02;
|
|
|
|
|
angle_southeast += 0x02;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define missile_pairs_shift_angle_2_towards( \
|
|
|
|
|
subphase, angle_southwest, angle_southeast \
|
|
|
|
|
) { \
|
|
|
|
|
if(subphase == missile_subphase_variation(0, MSP_SHIFT_ANGLE_2)) { \
|
|
|
|
|
angle_southwest += 0x04; \
|
|
|
|
|
} else if(subphase == missile_subphase_variation(1, MSP_SHIFT_ANGLE_2)) { \
|
|
|
|
|
angle_southeast -= 0x04;\
|
|
|
|
|
} else if(subphase == missile_subphase_variation(2, MSP_SHIFT_ANGLE_2)) { \
|
|
|
|
|
angle_southwest += 0x02; \
|
|
|
|
|
angle_southeast -= 0x02; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void missile_pairs_shift_angle_1_clock(
|
|
|
|
|
const int& subphase,
|
|
|
|
|
unsigned char& angle_southwest,
|
|
|
|
|
unsigned char& angle_southeast
|
|
|
|
|
) {
|
|
|
|
|
if(subphase == missile_subphase_variation(0, MSP_SHIFT_ANGLE_1)) {
|
|
|
|
|
angle_southwest -= 0x02;
|
|
|
|
|
angle_southeast -= 0x02;
|
|
|
|
|
} else {
|
|
|
|
|
angle_southwest += 0x02;
|
|
|
|
|
angle_southeast += 0x02;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define missile_pairs_shift_angle_2_clock( \
|
|
|
|
|
subphase, angle_southwest, angle_southeast \
|
|
|
|
|
) { \
|
|
|
|
|
if(subphase == missile_subphase_variation(0, MSP_SHIFT_ANGLE_2)) { \
|
|
|
|
|
angle_southwest += 0x03; \
|
|
|
|
|
angle_southeast += 0x03; \
|
|
|
|
|
} else if(subphase == missile_subphase_variation(1, MSP_SHIFT_ANGLE_2)) { \
|
|
|
|
|
angle_southwest -= 0x03; \
|
|
|
|
|
angle_southeast -= 0x03; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Processes a single frame of the missile pair pattern. [subphase] cycles
|
|
|
|
|
// between the fields of missile_subphase_t, offset by randomly selected
|
|
|
|
|
// variations. These can be differentiated in [shift_angle_1_func] and
|
|
|
|
|
// [shift_angle_2_func].
|
|
|
|
|
#define pattern_missile_pairs_from_south( \
|
|
|
|
|
subphase, \
|
|
|
|
|
missile_pairs_fired_in_subphase, \
|
|
|
|
|
target_left, \
|
|
|
|
|
angle_southwest, \
|
|
|
|
|
angle_southeast, \
|
|
|
|
|
iterations_done, \
|
|
|
|
|
variations_max, \
|
|
|
|
|
shift_angle_1_func, \
|
|
|
|
|
shift_angle_2_func \
|
|
|
|
|
) { \
|
|
|
|
|
eyes_locked_unput_and_put_then_track(EF_SOUTHWEST | EF_SOUTHEAST); \
|
|
|
|
|
\
|
|
|
|
|
if(boss_phase_frame >= (SUBPHASE_PREPARE_FRAMES - 10)) { \
|
|
|
|
|
/** \
|
|
|
|
|
* Hardcoding two maximum variations? Doesn't matter though: \
|
|
|
|
|
* If we're in MSP_PREPARE, we're always in variation 0 (see below). \
|
|
|
|
|
*/ \
|
|
|
|
|
if(missile_subphase_is(subphase, MSP_PREPARE, 2)) { \
|
|
|
|
|
eye_southwest.set_image(C_CLOSED); \
|
|
|
|
|
eye_southeast.set_image(C_CLOSED); \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
if( \
|
|
|
|
|
(boss_phase_frame == SUBPHASE_PREPARE_FRAMES) || \
|
|
|
|
|
missile_subphase_is(subphase, MSP_AIM_AND_FIRE_AROUND, variations_max) \
|
|
|
|
|
) { \
|
|
|
|
|
if(missile_subphase_variation_is(subphase, 0, MSP_PREPARE, 1)) { \
|
|
|
|
|
subphase = missile_subphase_variation( \
|
|
|
|
|
(rand() % variations_max), MSP_AIM_AND_FIRE_AROUND \
|
|
|
|
|
); \
|
|
|
|
|
target_left = (player_left - (MISSILE_W / 2)); \
|
|
|
|
|
angle_southwest = missile_angle(eye_southwest, target_left); \
|
|
|
|
|
angle_southeast = missile_angle(eye_southeast, target_left); \
|
|
|
|
|
angle_southwest -= 0x10; \
|
|
|
|
|
angle_southeast += 0x10; \
|
|
|
|
|
} \
|
|
|
|
|
if((boss_phase_frame % MISSILE_INTERVAL) == 0) { \
|
|
|
|
|
fire_missile_pair_from_south(angle_southwest, angle_southeast); \
|
|
|
|
|
} \
|
|
|
|
|
if(boss_phase_frame == SUBPHASE_TIMEOUT_FRAMES) { \
|
|
|
|
|
subphase++; \
|
|
|
|
|
missile_pairs_fired_in_subphase = 0; \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
if(missile_subphase_is(subphase, MSP_SHIFT_ANGLE_1, variations_max)) { \
|
|
|
|
|
if((boss_phase_frame % MISSILE_INTERVAL) == 1) { \
|
|
|
|
|
missile_pairs_fired_in_subphase++; \
|
|
|
|
|
shift_angle_1_func(subphase, angle_southwest, angle_southeast); \
|
|
|
|
|
fire_missile_pair_from_south(angle_southwest, angle_southeast); \
|
|
|
|
|
if(missile_pairs_fired_in_subphase > 10) { \
|
|
|
|
|
subphase++; \
|
|
|
|
|
missile_pairs_fired_in_subphase = 0; \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
} else if( \
|
|
|
|
|
missile_subphase_is(subphase, MSP_SHIFT_ANGLE_2, variations_max) \
|
|
|
|
|
) { \
|
|
|
|
|
if((boss_phase_frame % MISSILE_INTERVAL) == 1) { \
|
|
|
|
|
missile_pairs_fired_in_subphase++; \
|
|
|
|
|
shift_angle_2_func(subphase, angle_southwest, angle_southeast); \
|
|
|
|
|
fire_missile_pair_from_south(angle_southwest, angle_southeast); \
|
|
|
|
|
if(missile_pairs_fired_in_subphase > 10) { \
|
|
|
|
|
/** \
|
|
|
|
|
* Yup, always resetting to variation 0, which will in turn \
|
|
|
|
|
* select a new random one. \
|
|
|
|
|
*/ \
|
|
|
|
|
subphase = missile_subphase_variation(0, MSP_PREPARE); \
|
|
|
|
|
\
|
|
|
|
|
missile_pairs_fired_in_subphase = 0; \
|
|
|
|
|
boss_phase_frame = 0; \
|
|
|
|
|
iterations_done++; \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
// --------------
|
|
|
|
|
|
|
|
|
|
// Lasers across the playfield
|
|
|
|
|
// ---------------------------
|
|
|
|
|
|
|
|
|
|
static const pixel_t LASER_W = 3;
|
|
|
|
|
static const pixel_t LASER_VELOCITY = 4;
|
|
|
|
|
static const int LASER_INTERVAL = 4;
|
|
|
|
|
static const pixel_t LASER_VELOCITY_STEP = (LASER_VELOCITY * LASER_INTERVAL);
|
|
|
|
|
static const dots8_t LASER_DOTS = (
|
|
|
|
|
((1 << LASER_W) - 1) << (BYTE_DOTS - LASER_W) // (***_____)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
enum laser_subphase_t {
|
|
|
|
|
LSP_PREPARE = 0,
|
|
|
|
|
LSP_ACTIVE = 1,
|
|
|
|
|
LSP_DONE = 2,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline int laser_subphase(int iteration, laser_subphase_t lsp) {
|
|
|
|
|
return ((iteration * LSP_DONE) + lsp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define laser_pattern_should_run(subphase, iteration) ( \
|
|
|
|
|
( \
|
|
|
|
|
(boss_phase_frame == 40) && \
|
|
|
|
|
(subphase == laser_subphase(iteration, LSP_PREPARE)) \
|
|
|
|
|
) || (subphase == laser_subphase(iteration, LSP_ACTIVE)) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
#define laser_eye eye_north
|
|
|
|
|
|
|
|
|
|
inline screen_x_t laser_left(void) {
|
|
|
|
|
return laser_eye.offcenter_x();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline screen_y_t laser_top(void) {
|
|
|
|
|
return laser_eye.iris_top();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline screen_x_t laser_right(const pixel_t& x_edge_offset, x_direction_t dir) {
|
|
|
|
|
return ((dir == X_LEFT)
|
|
|
|
|
? (PLAYFIELD_RIGHT - x_edge_offset)
|
|
|
|
|
: (PLAYFIELD_LEFT + x_edge_offset)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void laser_unput_render_hittest(
|
|
|
|
|
const pixel_t& x_edge_offset, x_direction_t dir
|
|
|
|
|
) {
|
|
|
|
|
screen_x_t cur_right = laser_right(x_edge_offset, dir);
|
|
|
|
|
screen_y_t cur_top = laser_top();
|
|
|
|
|
screen_y_t cur_left = laser_left();
|
|
|
|
|
|
|
|
|
|
screen_x_t prev_right = (laser_right(x_edge_offset, dir) - (
|
|
|
|
|
dir ? -LASER_VELOCITY_STEP : LASER_VELOCITY_STEP
|
|
|
|
|
));
|
|
|
|
|
screen_y_t prev_top = laser_top();
|
|
|
|
|
screen_y_t prev_left = laser_left();
|
|
|
|
|
|
|
|
|
|
linebullet_unput(prev_left, prev_top, prev_right, PLAYFIELD_BOTTOM);
|
|
|
|
|
linebullet_put_patterned_and_hittest(
|
|
|
|
|
cur_left, cur_top, cur_right, PLAYFIELD_BOTTOM, V_WHITE, LASER_DOTS
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void laser_unput(const pixel_t& x_edge_offset, x_direction_t dir) {
|
|
|
|
|
linebullet_unput(
|
|
|
|
|
laser_left(),
|
|
|
|
|
laser_top(),
|
|
|
|
|
laser_right(x_edge_offset, dir),
|
|
|
|
|
PLAYFIELD_BOTTOM
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Separate function to work around the `Condition is always true/false` and
|
|
|
|
|
// `Unreachable code` warnings. Should really be done unconditionally, though.
|
|
|
|
|
inline void conditionally_reset(pixel_t& x_edge_offset, bool cond) {
|
|
|
|
|
if(cond) {
|
|
|
|
|
x_edge_offset = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define pattern_single_laser_across_playfield( \
|
|
|
|
|
subphase, redundant_lvalue, x_edge_offset, iteration, right_to_left \
|
|
|
|
|
) \
|
|
|
|
|
if(subphase == laser_subphase(iteration, LSP_PREPARE)) { \
|
|
|
|
|
subphase = laser_subphase(iteration, LSP_ACTIVE); \
|
|
|
|
|
redundant_lvalue = 0; /* ZUN bloat */ \
|
|
|
|
|
conditionally_reset(x_edge_offset, right_to_left); \
|
|
|
|
|
} \
|
|
|
|
|
if((boss_phase_frame % LASER_INTERVAL) == 1) { \
|
|
|
|
|
/* Track the laser target */ \
|
|
|
|
|
if(x_edge_offset <= (PLAYFIELD_CENTER_X - EYE_W)) { \
|
|
|
|
|
laser_eye.set_image(right_to_left ? C_RIGHT : C_LEFT); \
|
|
|
|
|
} else if(x_edge_offset >= (PLAYFIELD_CENTER_X + EYE_W)) { \
|
|
|
|
|
laser_eye.set_image(right_to_left ? C_LEFT : C_RIGHT); \
|
|
|
|
|
} else { \
|
|
|
|
|
laser_eye.set_image(C_DOWN); \
|
|
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
laser_unput_render_hittest(x_edge_offset, right_to_left); \
|
|
|
|
|
if(x_edge_offset >= (PLAYFIELD_W - (PLAYER_W + (PLAYER_W / 2)))) { \
|
|
|
|
|
laser_unput(x_edge_offset, right_to_left); \
|
|
|
|
|
subphase = laser_subphase((iteration + 1), LSP_PREPARE); \
|
|
|
|
|
boss_phase_frame = 0; \
|
|
|
|
|
\
|
|
|
|
|
/**
|
|
|
|
|
* ZUN bloat: Done after LSP_PREPARE, where it's much more \
|
|
|
|
|
* appropriate. \
|
|
|
|
|
*/ \
|
|
|
|
|
x_edge_offset = 0; \
|
|
|
|
|
\
|
|
|
|
|
if(rank > RANK_NORMAL) { \
|
|
|
|
|
laser_eye.fire_from_iris_center( \
|
|
|
|
|
PG_3_SPREAD_NARROW_AIMED, 0.1875f \
|
|
|
|
|
); \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
x_edge_offset += LASER_VELOCITY_STEP; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define pattern_dual_lasers_across_playfield( \
|
|
|
|
|
subphase, redundant_lvalue, x_edge_offset, iteration \
|
|
|
|
|
) \
|
|
|
|
|
if(subphase == laser_subphase(iteration, LSP_PREPARE)) { \
|
|
|
|
|
subphase = laser_subphase(iteration, LSP_ACTIVE); \
|
|
|
|
|
redundant_lvalue = 0; /* ZUN bloat */ \
|
|
|
|
|
x_edge_offset = 0; \
|
|
|
|
|
} \
|
|
|
|
|
if((boss_phase_frame % LASER_INTERVAL) == 1) { \
|
|
|
|
|
laser_eye.set_image(C_AHEAD); \
|
|
|
|
|
\
|
|
|
|
|
/** \
|
|
|
|
|
* ZUN bug: The really should be both unblitted and *then* both \
|
|
|
|
|
* rendered. With graph_r_line_unput() unblitting 32 horizontal \
|
|
|
|
|
* pixels for every row, the top part of the X_RIGHT-moving laser \
|
|
|
|
|
* will never be fully visible. \
|
|
|
|
|
*/ \
|
|
|
|
|
laser_unput_render_hittest(x_edge_offset, X_RIGHT); \
|
|
|
|
|
laser_unput_render_hittest(x_edge_offset, X_LEFT); \
|
|
|
|
|
if(x_edge_offset >= (PLAYFIELD_CENTER_X - PLAYER_W)) { \
|
|
|
|
|
laser_unput(x_edge_offset, X_RIGHT); \
|
|
|
|
|
laser_unput(x_edge_offset, X_LEFT); \
|
|
|
|
|
subphase = laser_subphase(iteration, LSP_DONE); \
|
|
|
|
|
boss_phase_frame = 0; \
|
|
|
|
|
x_edge_offset = 0; \
|
|
|
|
|
if(rank == RANK_HARD) { \
|
|
|
|
|
laser_eye.fire_from_iris_center( \
|
|
|
|
|
PG_3_SPREAD_NARROW_AIMED, 0.1875f \
|
|
|
|
|
); \
|
|
|
|
|
} \
|
|
|
|
|
if(rank == RANK_LUNATIC) { \
|
|
|
|
|
laser_eye.fire_from_iris_center( \
|
|
|
|
|
PG_5_SPREAD_NARROW_AIMED, 0.25f \
|
|
|
|
|
); \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
x_edge_offset += LASER_VELOCITY_STEP; \
|
|
|
|
|
}
|
|
|
|
|
// ---------------------------
|
|
|
|
|
|
2022-07-20 05:32:52 +00:00
|
|
|
|
/// Pentagram
|
|
|
|
|
/// ---------
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
static const int PENTAGRAM_INTERVAL = 4;
|
2022-07-20 05:32:52 +00:00
|
|
|
|
static const int PENTAGRAM_POINTS = 5;
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static struct {
|
2022-07-20 05:32:52 +00:00
|
|
|
|
// Corners, arranged in counterclockwise order.
|
|
|
|
|
screen_x_t x[PENTAGRAM_POINTS];
|
|
|
|
|
screen_y_t y[PENTAGRAM_POINTS];
|
|
|
|
|
|
|
|
|
|
pixel_t radius;
|
|
|
|
|
screen_point_t center;
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// This reversed order ruins a potential PUSH optimization...
|
|
|
|
|
pixel_t velocity_y;
|
|
|
|
|
pixel_t velocity_x;
|
|
|
|
|
|
2022-07-20 05:32:52 +00:00
|
|
|
|
void unput(void) {
|
|
|
|
|
linebullet_unput(x[0], y[0], x[2], y[2]);
|
|
|
|
|
linebullet_unput(x[2], y[2], x[4], y[4]);
|
|
|
|
|
linebullet_unput(x[4], y[4], x[1], y[1]);
|
|
|
|
|
linebullet_unput(x[1], y[1], x[3], y[3]);
|
|
|
|
|
linebullet_unput(x[3], y[3], x[0], y[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void put_and_hittest(void) {
|
|
|
|
|
linebullet_put_and_hittest(x[0], y[0], x[2], y[2], V_WHITE);
|
|
|
|
|
linebullet_put_and_hittest(x[2], y[2], x[4], y[4], V_WHITE);
|
|
|
|
|
linebullet_put_and_hittest(x[4], y[4], x[1], y[1], V_WHITE);
|
|
|
|
|
linebullet_put_and_hittest(x[1], y[1], x[3], y[3], V_WHITE);
|
|
|
|
|
linebullet_put_and_hittest(x[3], y[3], x[0], y[0], V_WHITE);
|
|
|
|
|
}
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
void aim(const screen_x_t& target_x, screen_y_t target_y) {
|
|
|
|
|
vector2_between(
|
|
|
|
|
center.x, inhibit_Z3(center.y),
|
|
|
|
|
target_x, target_y,
|
|
|
|
|
velocity_x, velocity_y,
|
|
|
|
|
4
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void move(bool dumb_workaround_for_different_call_positions = true) {
|
|
|
|
|
if(dumb_workaround_for_different_call_positions) {
|
|
|
|
|
center.y += velocity_y;
|
|
|
|
|
center.x += velocity_x;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-20 05:32:52 +00:00
|
|
|
|
} pentagram;
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Initial position of the pentagram, with corners in eyes
|
|
|
|
|
// -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
inline void pentagram_between_eyes_put(
|
|
|
|
|
const CEyeEntity& eye_1, const CEyeEntity& eye_2
|
|
|
|
|
) {
|
|
|
|
|
linebullet_put_and_hittest(
|
|
|
|
|
eye_1.offcenter_x(), eye_1.iris_top(),
|
|
|
|
|
eye_2.offcenter_x(), eye_2.iris_top(), V_WHITE
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void pentagram_between_eyes_put(void) {
|
|
|
|
|
pentagram_between_eyes_put(eye_north, eye_southwest);
|
|
|
|
|
pentagram_between_eyes_put(eye_southwest, eye_east);
|
|
|
|
|
pentagram_between_eyes_put(eye_east, eye_west);
|
|
|
|
|
pentagram_between_eyes_put(eye_west, eye_southeast);
|
|
|
|
|
pentagram_between_eyes_put(eye_southeast, eye_north);
|
|
|
|
|
}
|
|
|
|
|
// -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// Shrink animation
|
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
|
|
// No weird fraction for once!
|
|
|
|
|
static const screen_y_t PENTAGRAM_SHRINK_TARGET_CENTER_Y = PLAYFIELD_CENTER_Y;
|
|
|
|
|
|
|
|
|
|
static const pixel_t PENTAGRAM_RADIUS_FINAL = 64;
|
|
|
|
|
static const unsigned char PENTAGRAM_ANGLE_INITIAL = 0xC0; // (-0x40)
|
|
|
|
|
|
|
|
|
|
// As the largest distance, we can define all other shrink distances in terms
|
|
|
|
|
// of this one. Technically, the radius factor should be
|
|
|
|
|
//
|
|
|
|
|
// cos(PENTAGRAM_ANGLE_INITIAL + (1 × (360° / PENTAGRAM_POINTS)))
|
|
|
|
|
//
|
|
|
|
|
// for the 1st clockwise point on the pentagram. But since that comes out to
|
|
|
|
|
// roughly ≈ 0.95 and thus *almost* 1, it's acceptable to just round up.
|
|
|
|
|
static const pixel_t PENTAGRAM_SHRINK_DISTANCE = (
|
|
|
|
|
EYE_LATERAL_CENTER_DISTANCE_X - (PENTAGRAM_RADIUS_FINAL * 1)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
inline screen_x_t pentagram_shrink_x(eye_flag_t eye, const pixel_t& distance) {
|
|
|
|
|
enum {
|
|
|
|
|
DISTANCE_SOUTH = static_cast<pixel_t>(
|
|
|
|
|
// cos(PENTAGRAM_ANGLE_INITIAL + (2 × (360° / PENTAGRAM_POINTS)))
|
|
|
|
|
EYE_SOUTH_CENTER_DISTANCE_X - (PENTAGRAM_RADIUS_FINAL * 0.587f)
|
|
|
|
|
),
|
|
|
|
|
// Integer arithmetic can never be precise for the distances required
|
|
|
|
|
// here. All these calculations are at least an attempt of deriving
|
|
|
|
|
// those magic shrink factors, even if we have to cheat by "rounding
|
|
|
|
|
// up" in the end.
|
|
|
|
|
DIVISOR_SOUTH = ((PENTAGRAM_SHRINK_DISTANCE / DISTANCE_SOUTH) + 1),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if(eye == EF_WEST) {
|
|
|
|
|
return ( eye_west.offcenter_x() + distance);
|
|
|
|
|
} else if(eye == EF_EAST) {
|
|
|
|
|
return ( eye_east.offcenter_x() - distance);
|
|
|
|
|
} else if(eye == EF_SOUTHWEST) {
|
|
|
|
|
return (eye_southwest.offcenter_x() + (distance / DIVISOR_SOUTH));
|
|
|
|
|
} else if(eye == EF_SOUTHEAST) {
|
|
|
|
|
return (eye_southeast.offcenter_x() - (distance / DIVISOR_SOUTH));
|
|
|
|
|
}
|
|
|
|
|
return eye_north.offcenter_x();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline screen_y_t pentagram_shrink_y(eye_flag_t eye, const pixel_t& distance) {
|
|
|
|
|
enum {
|
|
|
|
|
TARGET_CENTER_Y = PENTAGRAM_SHRINK_TARGET_CENTER_Y,
|
|
|
|
|
|
|
|
|
|
TARGET_NORTH = static_cast<pixel_t>(
|
|
|
|
|
// sin(PENTAGRAM_ANGLE_INITIAL + (1 × (360° / PENTAGRAM_POINTS)))
|
|
|
|
|
TARGET_CENTER_Y - (PENTAGRAM_RADIUS_FINAL * 1)
|
|
|
|
|
),
|
|
|
|
|
TARGET_LATERAL = static_cast<pixel_t>(
|
|
|
|
|
// sin(PENTAGRAM_ANGLE_INITIAL + (2 × (360° / PENTAGRAM_POINTS)))
|
|
|
|
|
TARGET_CENTER_Y + (PENTAGRAM_RADIUS_FINAL * -0.309f)
|
|
|
|
|
),
|
|
|
|
|
TARGET_SOUTH = static_cast<pixel_t>(
|
|
|
|
|
// sin(PENTAGRAM_ANGLE_INITIAL + (3 × (360° / PENTAGRAM_POINTS)))
|
|
|
|
|
TARGET_CENTER_Y + (PENTAGRAM_RADIUS_FINAL * +0.809f)
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
DISTANCE_NORTH = (TARGET_NORTH - (EYE_NORTH_TOP + (EYE_H / 2))),
|
|
|
|
|
DISTANCE_LATERAL = (TARGET_LATERAL - (EYE_LATERAL_TOP + (EYE_H / 2))),
|
|
|
|
|
DISTANCE_SOUTH = (TARGET_SOUTH - (EYE_LATERAL_TOP + (EYE_H / 2))),
|
|
|
|
|
|
|
|
|
|
// Integer arithmetic can never be precise for the distances required
|
|
|
|
|
// here. All these calculations are at least an attempt of deriving
|
|
|
|
|
// those magic shrink factors, even if we have to cheat by "rounding
|
|
|
|
|
// up" in the end.
|
|
|
|
|
DIVISOR_NORTH = (PENTAGRAM_SHRINK_DISTANCE / DISTANCE_NORTH),
|
|
|
|
|
DIVISOR_LATERAL = ((PENTAGRAM_SHRINK_DISTANCE / DISTANCE_LATERAL) + 1),
|
|
|
|
|
DIVISOR_SOUTH = ((PENTAGRAM_SHRINK_DISTANCE / DISTANCE_SOUTH) + 1),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if(eye == EF_WEST) {
|
|
|
|
|
return (eye_west.iris_top() + (distance / DIVISOR_LATERAL));
|
|
|
|
|
} else if(eye == EF_EAST) {
|
|
|
|
|
return (eye_east.iris_top() + (distance / DIVISOR_LATERAL));
|
|
|
|
|
} else if(eye == EF_SOUTHWEST) {
|
|
|
|
|
return (eye_southwest.iris_top() + (distance / DIVISOR_SOUTH));
|
|
|
|
|
} else if(eye == EF_SOUTHEAST) {
|
|
|
|
|
return (eye_southeast.iris_top() + (distance / DIVISOR_SOUTH));
|
|
|
|
|
}
|
|
|
|
|
return (eye_north.iris_top() + (distance / DIVISOR_NORTH));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void pentagram_shrink_unput(
|
|
|
|
|
eye_flag_t eye_1, eye_flag_t eye_2, const pixel_t& distance
|
|
|
|
|
) {
|
|
|
|
|
linebullet_unput(
|
|
|
|
|
pentagram_shrink_x(eye_1, distance),
|
|
|
|
|
pentagram_shrink_y(eye_1, distance),
|
|
|
|
|
pentagram_shrink_x(eye_2, distance),
|
|
|
|
|
pentagram_shrink_y(eye_2, distance)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void pentagram_shrink_put(
|
|
|
|
|
eye_flag_t eye_1, eye_flag_t eye_2, const pixel_t& distance
|
|
|
|
|
) {
|
|
|
|
|
linebullet_put_and_hittest(
|
|
|
|
|
pentagram_shrink_x(eye_1, distance),
|
|
|
|
|
pentagram_shrink_y(eye_1, distance),
|
|
|
|
|
pentagram_shrink_x(eye_2, distance),
|
|
|
|
|
pentagram_shrink_y(eye_2, distance),
|
|
|
|
|
V_WHITE
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define pentagram_shrink(func, distance) { \
|
|
|
|
|
func(EF_NORTH, EF_SOUTHWEST, distance); \
|
|
|
|
|
func(EF_SOUTHWEST, EF_EAST, distance); \
|
|
|
|
|
func(EF_EAST, EF_WEST, distance); \
|
|
|
|
|
func(EF_WEST, EF_SOUTHEAST, distance); \
|
|
|
|
|
func(EF_SOUTHEAST, EF_NORTH, distance); \
|
|
|
|
|
}
|
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
|
|
// Regular pentagram, with spin and slam phases
|
|
|
|
|
// --------------------------------------------
|
|
|
|
|
|
2022-07-20 05:32:52 +00:00
|
|
|
|
#define pentagram_corners_set_regular(i, angle_offset) { \
|
|
|
|
|
for(i = 0; i < PENTAGRAM_POINTS; i++) { \
|
|
|
|
|
pentagram.x[i] = polar_x( \
|
|
|
|
|
pentagram.center.x, \
|
|
|
|
|
pentagram.radius, \
|
|
|
|
|
(angle_offset + (i * (0x100 / PENTAGRAM_POINTS))) \
|
|
|
|
|
); \
|
|
|
|
|
pentagram.y[i] = polar_y( \
|
|
|
|
|
pentagram.center.y, \
|
|
|
|
|
pentagram.radius, \
|
|
|
|
|
(angle_offset + (i * (0x100 / PENTAGRAM_POINTS))) \
|
|
|
|
|
); \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 20:19:30 +00:00
|
|
|
|
// Not "subphases", as these run independent of [boss_phase] – that one does
|
|
|
|
|
// in fact loop back at the end of the *_SLAM_INTO_PLAYER_* phases.
|
|
|
|
|
enum pentagram_attack_phase_t {
|
|
|
|
|
PAP_PREPARE_1 = 0,
|
|
|
|
|
PAP_SPIN_CLOCKWISE_1 = 1,
|
|
|
|
|
PAP_SPIN_COUNTERCLOCKWISE_1 = 2,
|
|
|
|
|
PAP_SPIN_CLOCKWISE_2 = 3,
|
|
|
|
|
PAP_SPIN_COUNTERCLOCKWISE_2 = 4,
|
|
|
|
|
PAP_SPIN_CLOCKWISE_3 = 5,
|
|
|
|
|
PAP_SLAM_INTO_PLAYER_1 = 6,
|
|
|
|
|
PAP_PREPARE_2 = 7,
|
|
|
|
|
PAP_GROW = 8,
|
|
|
|
|
PAP_SLAM_INTO_PLAYER_2 = 9,
|
|
|
|
|
|
|
|
|
|
_pentagram_attack_phase_t_FORCE_INT16 = 0x7FFF
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define pentagram_in_slam_phase(phase) ( \
|
|
|
|
|
(phase == PAP_SLAM_INTO_PLAYER_1) || (phase == PAP_SLAM_INTO_PLAYER_2) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
#define pentagram_in_clockwise_spin_phase(phase) ( \
|
|
|
|
|
(phase == PAP_SPIN_CLOCKWISE_1) || \
|
|
|
|
|
(phase == PAP_SPIN_CLOCKWISE_2) || \
|
|
|
|
|
(phase == PAP_SPIN_CLOCKWISE_3) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
#define pentagram_in_counterclockwise_spin_phase(phase) ( \
|
|
|
|
|
(phase == PAP_SPIN_COUNTERCLOCKWISE_1) || \
|
|
|
|
|
(phase == PAP_SPIN_COUNTERCLOCKWISE_2) \
|
|
|
|
|
)
|
|
|
|
|
|
2022-07-20 05:32:52 +00:00
|
|
|
|
// Also performs collision detection.
|
|
|
|
|
void pascal near pentagram_regular_unput_update_render(
|
|
|
|
|
int angle_offset // ACTUAL TYPE: unsigned char
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
pentagram.unput();
|
|
|
|
|
pentagram_corners_set_regular(i, angle_offset);
|
|
|
|
|
pentagram.put_and_hittest();
|
|
|
|
|
}
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
// MODDERS: Merge into the other pentagram structure.
|
|
|
|
|
struct Pentagram {
|
|
|
|
|
pentagram_attack_phase_t phase;
|
|
|
|
|
int angle; // ACTUAL TYPE: unsigned char
|
|
|
|
|
|
|
|
|
|
void spin(clock_direction_t dir) {
|
|
|
|
|
if(dir == CLOCKWISE) {
|
|
|
|
|
angle += ((boss_phase_frame / 64) + 0x02);
|
|
|
|
|
} else {
|
|
|
|
|
angle -= ((boss_phase_frame / 64) + 0x02);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void unput_update_render_regular(void) const {
|
|
|
|
|
pentagram_regular_unput_update_render(angle);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ZUN bug: The pentagram is not rendered in the preparation phases, and can
|
|
|
|
|
// thus easily be unblitted by overlapping sprites, most notably player shots
|
|
|
|
|
// or HARRY UP pellets.
|
|
|
|
|
#define pentagram_prepare_update(pentagram_, phase_id) { \
|
|
|
|
|
if( \
|
|
|
|
|
(boss_phase_frame == SUBPHASE_PREPARE_FRAMES) && \
|
|
|
|
|
(pentagram_.phase == phase_id) \
|
|
|
|
|
) { \
|
|
|
|
|
pentagram_.phase = static_cast<pentagram_attack_phase_t>( \
|
|
|
|
|
phase_id + 1 \
|
|
|
|
|
); \
|
|
|
|
|
boss_phase_frame = 0; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The next two are forced to be used inside an `if` statement, returning a
|
|
|
|
|
// non-zero value if the phase is done.
|
|
|
|
|
#define pentagram_spin_unput_update_render(pentagram_, dir) \
|
|
|
|
|
(boss_phase_frame % PENTAGRAM_INTERVAL) == 0) { \
|
|
|
|
|
pentagram_.spin(dir); \
|
|
|
|
|
pentagram_.unput_update_render_regular(); \
|
|
|
|
|
} \
|
|
|
|
|
if(boss_phase_frame >= SUBPHASE_TIMEOUT_FRAMES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define pentagram_slam_unput_update_render( \
|
|
|
|
|
pentagram_, homing_threshold_from_bottom, move_first \
|
|
|
|
|
) \
|
|
|
|
|
(boss_phase_frame % PENTAGRAM_INTERVAL) == 0) { \
|
|
|
|
|
if(pentagram.center.y < ( \
|
|
|
|
|
PLAYFIELD_BOTTOM - (homing_threshold_from_bottom) \
|
|
|
|
|
)) { \
|
|
|
|
|
pentagram.aim(player_left, player_bottom()); \
|
|
|
|
|
} \
|
|
|
|
|
pentagram.move(move_first); \
|
|
|
|
|
pentagram_.spin(CLOCKWISE); \
|
|
|
|
|
pentagram.move(!move_first); \
|
|
|
|
|
pentagram_.unput_update_render_regular(); \
|
|
|
|
|
} \
|
|
|
|
|
if(pentagram.center.y >= (PLAYFIELD_BOTTOM + ((PLAYFIELD_H / 84) * 5))
|
|
|
|
|
// --------------------------------------------
|
2022-07-20 05:32:52 +00:00
|
|
|
|
/// ---------
|
2022-07-20 17:19:23 +00:00
|
|
|
|
|
|
|
|
|
// Function ordering fails
|
|
|
|
|
// -----------------------
|
|
|
|
|
|
|
|
|
|
// Opens and closes the given eyes, and changes the 邪 color in [stage_palette]
|
|
|
|
|
// (and, via transferring it to [z_Palettes], also the hardware color) by
|
|
|
|
|
// gradually incrementing and decrementing the given components on 9 out of 10
|
|
|
|
|
// frames. Takes ownership of [frame]. To keep the 邪 color unchanged, set
|
|
|
|
|
// [yokoshima_comp_inc] and [yokoshima_comp_dec] to a value ≥ COMPONENT_COUNT.
|
|
|
|
|
//
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// ZUN quirk: While the final [z_Palettes] values are clamped to 0x0 and 0xF,
|
|
|
|
|
// the [yokoshima_comp_inc] component in the [stage_palette] is *not* clamped
|
|
|
|
|
// to 0xF after incrementing. This overflow can (and does) affect the final
|
|
|
|
|
// color on successive recoloring operations.
|
|
|
|
|
//
|
2022-07-20 17:19:23 +00:00
|
|
|
|
// MODDERS: Make the component parameters unsigned.
|
|
|
|
|
void eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
eye_flag_t eyes_to_close,
|
|
|
|
|
eye_flag_t eyes_to_open,
|
|
|
|
|
int yokoshima_comp_dec,
|
|
|
|
|
int yokoshima_comp_inc,
|
|
|
|
|
int& frame
|
|
|
|
|
);
|
|
|
|
|
// -----------------------
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
// Separate function to work around the `Condition is always true/false` and
|
|
|
|
|
// `Unreachable code` warnings. (It's unnecessary anyway, though.)
|
|
|
|
|
inline void conditionally_reset_missiles(bool cond) {
|
|
|
|
|
if(cond) {
|
|
|
|
|
Missiles.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define yuugenmagan_defeat_if(cond, reset_missiles, tmp_i) { \
|
|
|
|
|
if(cond) { \
|
|
|
|
|
mdrv2_bgm_fade_out_nonblock(); \
|
|
|
|
|
Pellets.unput_and_reset(); \
|
|
|
|
|
conditionally_reset_missiles(reset_missiles); \
|
|
|
|
|
\
|
|
|
|
|
/* 5? Triply broken, since this fight doesn't even use lasers... */ \
|
|
|
|
|
shootout_lasers_unput_and_reset_broken(tmp_i, 5); \
|
|
|
|
|
boss_defeat_animate(); \
|
|
|
|
|
scene_init_and_load(3); \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void yuugenmagan_main(void)
|
|
|
|
|
{
|
2022-07-29 18:33:37 +00:00
|
|
|
|
const unsigned char flash_colors[2] = { 1, 11 };
|
2022-07-27 20:19:30 +00:00
|
|
|
|
int i;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static int invincibility_frame;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static union {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
int missile_pairs_fired_in_subphase;
|
|
|
|
|
int subphase_frame;
|
|
|
|
|
|
|
|
|
|
// Always increasing, even when going right-to-left.
|
|
|
|
|
pixel_t x_edge_offset;
|
|
|
|
|
|
|
|
|
|
pixel_t distance;
|
|
|
|
|
int unused;
|
|
|
|
|
} u1;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static union {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
int subphase;
|
|
|
|
|
int yokoshima_comp_dec;
|
|
|
|
|
int unused;
|
|
|
|
|
} u2;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static screen_x_t target_left;
|
|
|
|
|
static pixel_t unused_distance;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
// Compared to just reusing [invincibility_frame], this "copy" has the
|
|
|
|
|
// advantage of not being reset every 40 frames, and thus lasting the full
|
|
|
|
|
// EYE_TOGGLE_FRAMES.
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static int after_hit_frames;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static Pentagram pentagram_;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static union {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
int8_t iterations_done;
|
|
|
|
|
int8_t yokoshima_comp_inc;
|
|
|
|
|
eye_flag_t eyes_open;
|
|
|
|
|
int8_t unused;
|
|
|
|
|
} u3;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static struct {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
bool initial_hp_rendered;
|
|
|
|
|
|
|
|
|
|
void frame_common(void) {
|
|
|
|
|
boss_phase_frame++;
|
|
|
|
|
invincibility_frame++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void next(int phase_new) {
|
|
|
|
|
boss_phase = phase_new;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
invincibility_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void next(int phase_new, int& u2_element_to_reset) {
|
|
|
|
|
boss_phase = phase_new;
|
|
|
|
|
u2_element_to_reset = 0;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
invincibility_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void next(
|
|
|
|
|
int phase_new,
|
|
|
|
|
int& u2_element_to_reset,
|
|
|
|
|
int8_t& u3_element_to_reset,
|
|
|
|
|
int8_t new_value_for_u3 = 0
|
|
|
|
|
) {
|
|
|
|
|
boss_phase = phase_new;
|
|
|
|
|
u2_element_to_reset = 0;
|
|
|
|
|
u3_element_to_reset = new_value_for_u3;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
invincibility_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Respawns the pentagram.
|
|
|
|
|
void back_from_13_to_10(pentagram_attack_phase_t pentagram_phase_new) {
|
|
|
|
|
if(pentagram_phase_new == PAP_PREPARE_1) {
|
|
|
|
|
pentagram_.phase = pentagram_phase_new;
|
|
|
|
|
}
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
boss_phase = 10;
|
|
|
|
|
u2.yokoshima_comp_dec = COMPONENT_COUNT;
|
|
|
|
|
u3.yokoshima_comp_inc = COMPONENT_COUNT;
|
|
|
|
|
if(pentagram_phase_new == PAP_PREPARE_2) {
|
|
|
|
|
pentagram_.phase = pentagram_phase_new;
|
|
|
|
|
}
|
|
|
|
|
invincibility_frame = 0;
|
|
|
|
|
u1.unused = 0;
|
|
|
|
|
pentagram.unput();
|
|
|
|
|
}
|
|
|
|
|
} phase;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static union {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
unsigned char missile_southwest;
|
|
|
|
|
unsigned char pellet_east;
|
|
|
|
|
unsigned char tmp; // MODDERS: Turn into a scope-local variable
|
|
|
|
|
} angle;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static unsigned char angle_missile_southeast;
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
static struct {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
bool16 invincible;
|
|
|
|
|
|
2022-07-29 18:33:37 +00:00
|
|
|
|
void update_and_render(const unsigned char (&flash_colors)[2]) {
|
2022-07-27 20:19:30 +00:00
|
|
|
|
#define hittest(eye) ( \
|
|
|
|
|
(eye.hittest_orb() == true) && (eye.image() != C_HIDDEN) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
boss_hit_update_and_render(
|
|
|
|
|
invincibility_frame,
|
|
|
|
|
invincible,
|
|
|
|
|
boss_hp,
|
2022-07-29 18:33:37 +00:00
|
|
|
|
flash_colors,
|
2022-07-27 20:19:30 +00:00
|
|
|
|
sizeof(flash_colors),
|
|
|
|
|
5000,
|
|
|
|
|
boss_nop,
|
|
|
|
|
(
|
|
|
|
|
hittest(eye_west) ||
|
|
|
|
|
hittest(eye_east) ||
|
|
|
|
|
hittest(eye_southwest) ||
|
|
|
|
|
hittest(eye_southeast) ||
|
|
|
|
|
hittest(eye_north)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
#undef hittest
|
|
|
|
|
}
|
2022-07-29 18:33:37 +00:00
|
|
|
|
} hit = { false };
|
2022-07-27 20:19:30 +00:00
|
|
|
|
|
|
|
|
|
Missiles.unput_update_render();
|
|
|
|
|
|
|
|
|
|
if(boss_phase == 0) {
|
|
|
|
|
// Downwards lasers from every eye, in a symmetric sequence from the
|
|
|
|
|
// left and right edges of the playfield towards the center
|
|
|
|
|
|
|
|
|
|
hud_hp_increment_render(
|
|
|
|
|
phase.initial_hp_rendered, boss_hp, boss_phase_frame
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
boss_phase_frame++;
|
|
|
|
|
|
|
|
|
|
eye_west.locked_unput_and_put_8();
|
|
|
|
|
eye_east.locked_unput_and_put_8();
|
|
|
|
|
eye_southwest.locked_unput_and_put_8();
|
|
|
|
|
eye_southeast.locked_unput_and_put_8();
|
|
|
|
|
eye_north.locked_unput_and_put_8();
|
|
|
|
|
|
|
|
|
|
phase_0_downwards_lasers();
|
|
|
|
|
|
|
|
|
|
if((boss_phase_frame % 40) == 0) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// Target color: (13, 13, 5) ⇒ #DD5
|
2022-07-27 20:19:30 +00:00
|
|
|
|
z_Palettes[COL_YOKOSHIMA].c.r++; // ZUN bloat
|
|
|
|
|
z_Palettes[COL_YOKOSHIMA].c.g++; // ZUN bloat
|
|
|
|
|
stage_palette[COL_YOKOSHIMA].c.r++;
|
|
|
|
|
stage_palette[COL_YOKOSHIMA].c.g++;
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame == KEYFRAME_LATERAL_OPENING) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_LATERAL_OPENING);
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: Without a call to hit.update_and_render(), any hitbox
|
|
|
|
|
// manipulation in this phase is doubly redundant...
|
|
|
|
|
eye_west.hitbox_orb_activate();
|
|
|
|
|
eye_east.hitbox_orb_activate();
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_SOUTH_OPENING) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_SOUTH_OPENING);
|
|
|
|
|
eye_southwest.hitbox_orb_activate();
|
|
|
|
|
eye_southeast.hitbox_orb_activate();
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_NORTH_OPENING) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_NORTH_OPENING);
|
|
|
|
|
eye_north.hitbox_orb_activate();
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_LATERAL_OPEN) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_LATERAL_OPEN);
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_SOUTH_OPEN) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_SOUTH_OPEN);
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_NORTH_OPEN) {
|
|
|
|
|
phase_0_eyes_open(KEYFRAME_NORTH_OPEN);
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_CLOSING) {
|
|
|
|
|
phase_0_eyes_close(KEYFRAME_CLOSING);
|
|
|
|
|
eye_west.hitbox_orb_deactivate();
|
|
|
|
|
eye_east.hitbox_orb_deactivate();
|
|
|
|
|
eye_southwest.hitbox_orb_deactivate();
|
|
|
|
|
eye_southeast.hitbox_orb_deactivate();
|
|
|
|
|
eye_north.hitbox_orb_deactivate();
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: Did you mean ">= RANK_HARD"? Because...
|
|
|
|
|
if(rank == RANK_HARD) {
|
|
|
|
|
phase_0_fire(eye_north);
|
|
|
|
|
phase_0_fire(eye_southeast);
|
|
|
|
|
phase_0_fire(eye_southwest);
|
|
|
|
|
|
|
|
|
|
// ... this condition can now never be true, making the Lunatic
|
|
|
|
|
// version of this subpattern effectively unused.
|
|
|
|
|
if(rank == RANK_LUNATIC) {
|
|
|
|
|
phase_0_fire(eye_west);
|
|
|
|
|
phase_0_fire(eye_east);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_CLOSED) {
|
|
|
|
|
phase_0_eyes_close(KEYFRAME_CLOSED);
|
|
|
|
|
|
|
|
|
|
if(rank == RANK_LUNATIC) {
|
|
|
|
|
phase_0_fire(eye_north);
|
|
|
|
|
phase_0_fire(eye_southeast);
|
|
|
|
|
phase_0_fire(eye_southwest);
|
|
|
|
|
phase_0_fire(eye_west);
|
|
|
|
|
phase_0_fire(eye_east);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_HIDDEN) {
|
|
|
|
|
phase_0_eyes_close(KEYFRAME_HIDDEN);
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_LATERAL_LASER_DONE) {
|
|
|
|
|
eye_west.set_image(C_CLOSED);
|
|
|
|
|
eye_east.set_image(C_CLOSED);
|
|
|
|
|
|
|
|
|
|
eye_west.downwards_laser_unput();
|
|
|
|
|
eye_east.downwards_laser_unput();
|
|
|
|
|
} else if(boss_phase_frame == KEYFRAME_SOUTH_LASER_DONE) {
|
|
|
|
|
eye_west.set_image(C_HALFOPEN);
|
|
|
|
|
eye_east.set_image(C_HALFOPEN);
|
|
|
|
|
|
|
|
|
|
eye_west.hitbox_orb_activate();
|
|
|
|
|
eye_east.hitbox_orb_activate();
|
|
|
|
|
|
|
|
|
|
eye_southwest.downwards_laser_unput();
|
|
|
|
|
eye_southeast.downwards_laser_unput();
|
|
|
|
|
} else if(boss_phase_frame > KEYFRAME_NORTH_LASER_DONE) {
|
|
|
|
|
boss_phase = 1;
|
|
|
|
|
hit.invincible = false;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
phase.initial_hp_rendered = false; // ZUN bloat
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: These won't be visible at all, since we get here on
|
|
|
|
|
// the second frame with the entity lock active. On the next frame,
|
|
|
|
|
// these two eyes will immediately adjust themselves to the
|
|
|
|
|
// player's tracked position.
|
|
|
|
|
eye_west.set_image(C_LEFT);
|
|
|
|
|
eye_east.set_image(C_DOWN);
|
|
|
|
|
|
|
|
|
|
pattern_interval = select_for_rank(350, 300, 200, 130);
|
|
|
|
|
|
|
|
|
|
eye_north.downwards_laser_unput();
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 1) {
|
|
|
|
|
// Slow pellets from the lateral eyes
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
eyes_locked_unput_and_put_then_track(EF_WEST | EF_EAST);
|
|
|
|
|
|
|
|
|
|
phase_1_pellets_from_lateral();
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
if((boss_hp <= PHASE_1_END_HP) || (boss_phase_frame > 1100)) {
|
|
|
|
|
phase.next(2);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 2) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// (13, 13, 5) → (0, 13, 68) ⇒ #0DF (cyan)
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(EF_WEST | EF_EAST),
|
|
|
|
|
(EF_SOUTHWEST | EF_SOUTHEAST),
|
|
|
|
|
COMPONENT_R,
|
|
|
|
|
COMPONENT_B,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(3, u2.subphase, u3.iterations_done);
|
|
|
|
|
u1.missile_pairs_fired_in_subphase = 0;
|
|
|
|
|
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
pattern_interval = select_for_rank(8, 12, 16, 20); // (unused)
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 3) {
|
|
|
|
|
// Missiles from the southern eyes, whose angles first shift away from
|
|
|
|
|
// Reimu's tracked position and then towards it
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
pattern_missile_pairs_from_south(
|
|
|
|
|
u2.subphase,
|
|
|
|
|
u1.missile_pairs_fired_in_subphase,
|
|
|
|
|
target_left,
|
|
|
|
|
angle.missile_southwest,
|
|
|
|
|
angle_missile_southeast,
|
|
|
|
|
u3.iterations_done,
|
|
|
|
|
3,
|
|
|
|
|
missile_pairs_shift_angle_1_away,
|
|
|
|
|
missile_pairs_shift_angle_2_towards
|
|
|
|
|
);
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
if((boss_hp <= PHASE_3_END_HP) || (u3.iterations_done >= 5)) {
|
|
|
|
|
phase.next(4);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 4) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// (0, 13, 68) → (63, 0, 68) ⇒ #F0F (magenta)
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(EF_SOUTHWEST | EF_SOUTHEAST),
|
|
|
|
|
(EF_WEST | EF_EAST),
|
|
|
|
|
COMPONENT_G,
|
|
|
|
|
COMPONENT_R,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(5, u2.subphase, u3.iterations_done);
|
|
|
|
|
u1.subphase_frame = 0;
|
|
|
|
|
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
unused_distance = (player_bottom() - eye_west.cur_top);
|
|
|
|
|
pattern_interval = select_for_rank(12, 8, 4, 2);
|
|
|
|
|
angle.pellet_east = 0x00;
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 5) {
|
|
|
|
|
// Circular pellets sprayed from the lateral eyes
|
|
|
|
|
|
|
|
|
|
enum phase_5_subphase_t {
|
|
|
|
|
P5_PREPARE_1 = 0,
|
|
|
|
|
P5_SPRAY_1 = 1,
|
|
|
|
|
P5_PREPARE_2 = 2,
|
|
|
|
|
P5_SPRAY_2 = 3,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: Redundant and confusing. Just use the regular
|
|
|
|
|
// [boss_phase_frame] exclusively?
|
|
|
|
|
u1.subphase_frame++;
|
|
|
|
|
|
|
|
|
|
angle.pellet_east--;
|
|
|
|
|
|
|
|
|
|
eyes_locked_unput_and_put_then_track(EF_WEST | EF_EAST);
|
|
|
|
|
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame == SUBPHASE_PREPARE_FRAMES) &&
|
|
|
|
|
(u2.subphase == P5_PREPARE_1)
|
|
|
|
|
) {
|
|
|
|
|
u1.subphase_frame = 0;
|
|
|
|
|
u2.subphase = P5_SPRAY_1;
|
|
|
|
|
}
|
|
|
|
|
if(u2.subphase == P5_SPRAY_1) {
|
|
|
|
|
if((u1.subphase_frame % pattern_interval) == 0) {
|
|
|
|
|
eye_west.fire_from_bottom_center(
|
|
|
|
|
true, angle.pellet_east, 4.0f
|
|
|
|
|
);
|
|
|
|
|
eye_east.fire_from_bottom_center(
|
|
|
|
|
false, angle.pellet_east, 4.0f
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if((u1.subphase_frame / 5) >= 36) {
|
|
|
|
|
u2.subphase = P5_PREPARE_2;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(
|
|
|
|
|
(u2.subphase == P5_PREPARE_2) &&
|
|
|
|
|
(boss_phase_frame > (SUBPHASE_PREPARE_FRAMES / 2))
|
|
|
|
|
) {
|
|
|
|
|
u1.subphase_frame = -15;
|
|
|
|
|
u2.subphase = P5_SPRAY_2;
|
|
|
|
|
angle.pellet_east = 0x80;
|
|
|
|
|
}
|
|
|
|
|
if(u2.subphase == P5_SPRAY_2) {
|
|
|
|
|
if((u1.subphase_frame % pattern_interval) == 0) {
|
|
|
|
|
// Since these pellets don't reach the top of the playfield
|
|
|
|
|
// before this subphase ends, they don't actually move
|
|
|
|
|
// differently compared to the first subphase.
|
|
|
|
|
eye_west.fire_from_bottom_center(
|
|
|
|
|
true,
|
|
|
|
|
angle.pellet_east,
|
|
|
|
|
4.0f,
|
|
|
|
|
PM_FALL_STRAIGHT_FROM_TOP_THEN_REGULAR,
|
|
|
|
|
4.0f
|
|
|
|
|
);
|
|
|
|
|
eye_east.fire_from_bottom_center(
|
|
|
|
|
false,
|
|
|
|
|
angle.pellet_east,
|
|
|
|
|
4.0f,
|
|
|
|
|
PM_FALL_STRAIGHT_FROM_TOP_THEN_REGULAR,
|
|
|
|
|
4.0f
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if((u1.subphase_frame / 5) >= 20) {
|
|
|
|
|
u2.subphase = P5_PREPARE_1;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
u3.iterations_done++;
|
|
|
|
|
angle.pellet_east = 0x00;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
if((boss_hp <= PHASE_5_END_HP) || (u3.iterations_done > 4)) {
|
|
|
|
|
phase.next(6);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 6) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// (63, 0, 68) → (0, 0, 68) ⇒ #00F (blue)
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(EF_WEST | EF_EAST),
|
|
|
|
|
(EF_SOUTHWEST | EF_SOUTHEAST),
|
|
|
|
|
COMPONENT_R,
|
|
|
|
|
COMPONENT_COUNT,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(7, u2.subphase, u3.iterations_done);
|
|
|
|
|
u1.missile_pairs_fired_in_subphase = 0;
|
|
|
|
|
pentagram_.angle = 0x00; // ZUN bloat
|
|
|
|
|
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
pattern_interval = select_for_rank(10, 16, 20, 24); // (unused)
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 7) {
|
|
|
|
|
// Missiles from the southern eyes, with (counter)clockwise angle shifts
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
pattern_missile_pairs_from_south(
|
|
|
|
|
u2.subphase,
|
|
|
|
|
u1.missile_pairs_fired_in_subphase,
|
|
|
|
|
target_left,
|
|
|
|
|
angle.missile_southwest,
|
|
|
|
|
angle_missile_southeast,
|
|
|
|
|
u3.iterations_done,
|
|
|
|
|
2,
|
|
|
|
|
missile_pairs_shift_angle_1_clock,
|
|
|
|
|
missile_pairs_shift_angle_2_clock
|
|
|
|
|
);
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
if((boss_hp <= PHASE_7_END_HP) || (u3.iterations_done > 4)) {
|
|
|
|
|
phase.next(8);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 8) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// (0, 0, 68) → (0, 63, 5) ⇒ #0F5 (green, via cyan)
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(EF_SOUTHWEST | EF_SOUTHEAST),
|
|
|
|
|
EF_NORTH,
|
|
|
|
|
COMPONENT_B,
|
|
|
|
|
COMPONENT_G,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(9, u2.subphase);
|
|
|
|
|
u1.x_edge_offset = 0;
|
|
|
|
|
pentagram_.angle = 0x00;
|
|
|
|
|
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 9) {
|
|
|
|
|
// 3-pixel 3-laser sequence from the northern eye
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
laser_eye.locked_unput_and_put_8();
|
|
|
|
|
|
|
|
|
|
if((boss_phase_frame >= 30) && (
|
|
|
|
|
(u2.subphase == laser_subphase(0, LSP_PREPARE)) ||
|
|
|
|
|
(u2.subphase == laser_subphase(1, LSP_PREPARE)) ||
|
|
|
|
|
(u2.subphase == laser_subphase(2, LSP_PREPARE))
|
|
|
|
|
)) {
|
|
|
|
|
laser_eye.set_image(C_CLOSED);
|
|
|
|
|
}
|
|
|
|
|
if(laser_pattern_should_run(u2.subphase, 0)) {
|
|
|
|
|
pattern_single_laser_across_playfield(
|
|
|
|
|
u2.subphase, pentagram_.angle, u1.x_edge_offset, 0, X_RIGHT
|
|
|
|
|
);
|
|
|
|
|
} else if(laser_pattern_should_run(u2.subphase, 1)) {
|
|
|
|
|
pattern_single_laser_across_playfield(
|
|
|
|
|
u2.subphase, pentagram_.angle, u1.x_edge_offset, 1, X_LEFT
|
|
|
|
|
);
|
|
|
|
|
} else if(laser_pattern_should_run(u2.subphase, 2)) {
|
|
|
|
|
pattern_dual_lasers_across_playfield(
|
|
|
|
|
u2.subphase, pentagram_.angle, u1.x_edge_offset, 2
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
if(
|
|
|
|
|
(boss_hp <= PHASE_9_END_HP) ||
|
|
|
|
|
(u2.subphase == laser_subphase(2, LSP_DONE))
|
|
|
|
|
) {
|
|
|
|
|
// ZUN bug: We can get here with a laser still on screen,
|
|
|
|
|
// particularly in debug mode. There should at least be an
|
|
|
|
|
// unblitting call here.
|
|
|
|
|
|
|
|
|
|
boss_phase = 10;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
invincibility_frame = 0;
|
|
|
|
|
u2.yokoshima_comp_dec = COMPONENT_G;
|
|
|
|
|
u3.yokoshima_comp_inc = COMPONENT_R;
|
|
|
|
|
|
|
|
|
|
// Must be done here, as phase 13 loops back into phase 10 with
|
|
|
|
|
// this phase variable set to PAP_PREPARE_2.
|
|
|
|
|
pentagram_.phase = PAP_PREPARE_1;
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 10) {
|
|
|
|
|
// Prepare pentagram spawning by opening all eyes
|
|
|
|
|
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// First iteration: (0, 63, 5) → (63, 0, 5) ⇒ #F05 (red, via yellow)
|
|
|
|
|
// Later iterations don't change the color; see back_from_13_to_10().
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
EF_NONE,
|
|
|
|
|
(EF_WEST | EF_EAST | EF_SOUTHWEST | EF_SOUTHEAST | EF_NORTH),
|
|
|
|
|
u2.yokoshima_comp_dec,
|
|
|
|
|
u3.yokoshima_comp_inc,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(11, u2.subphase, u3.unused, 31 /* ZUN bloat */);
|
|
|
|
|
u1.distance = 0;
|
|
|
|
|
pentagram_.angle = 0x00; // Finally not redundant.
|
|
|
|
|
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 11) {
|
|
|
|
|
// Spawns the pentagram with one corner out of every eye, then
|
|
|
|
|
// gradually shrinks and moves it towards the center of the playfield
|
|
|
|
|
|
|
|
|
|
enum phase_11_subphase_t {
|
|
|
|
|
P11_PREPARE = 0,
|
|
|
|
|
P11_CORNERS_IN_EYES = 1,
|
|
|
|
|
P11_MOVE = 2,
|
|
|
|
|
P11_DONE = 3,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
eyes_locked_unput_and_put_then_track(
|
|
|
|
|
EF_WEST | EF_EAST | EF_SOUTHWEST | EF_SOUTHEAST | EF_NORTH
|
|
|
|
|
);
|
|
|
|
|
if(
|
|
|
|
|
(boss_phase_frame == SUBPHASE_PREPARE_FRAMES) ||
|
|
|
|
|
(u2.subphase == P11_CORNERS_IN_EYES)
|
|
|
|
|
) {
|
|
|
|
|
if(u2.subphase == P11_PREPARE) {
|
|
|
|
|
u2.subphase = P11_CORNERS_IN_EYES;
|
|
|
|
|
target_left = player_left; // unused
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Every frame, for a change? No unblitting glitches here, then. :P
|
|
|
|
|
pentagram_between_eyes_put();
|
|
|
|
|
|
|
|
|
|
if(boss_phase_frame == 140) {
|
|
|
|
|
u2.subphase = P11_MOVE;
|
|
|
|
|
u1.distance = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(
|
|
|
|
|
(u2.subphase == P11_MOVE) &&
|
|
|
|
|
((boss_phase_frame % PENTAGRAM_INTERVAL) == 1)
|
|
|
|
|
) {
|
|
|
|
|
// ZUN bug: Doing this after blitting the eyes is guaranteed to
|
|
|
|
|
// rip holes into them for at least one frame.
|
|
|
|
|
pentagram_shrink(pentagram_shrink_unput, u1.distance);
|
|
|
|
|
|
|
|
|
|
u1.distance += (PENTAGRAM_SHRINK_DISTANCE / 20);
|
|
|
|
|
pentagram_shrink(pentagram_shrink_put, u1.distance);
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: Should be >=, and ideally even be clamped to exactly
|
|
|
|
|
// that value. With >, the shrink animation lasts for one step
|
|
|
|
|
// longer than it should, leaving the pentagram both smaller and
|
|
|
|
|
// moved slightly off-center.
|
|
|
|
|
if(u1.distance > PENTAGRAM_SHRINK_DISTANCE) {
|
|
|
|
|
// ZUN quirk: This uses offcenter_x(), which makes sure that
|
|
|
|
|
// the regular pentagram will stay off-center even *if* the
|
|
|
|
|
// above condition were corrected.
|
|
|
|
|
pentagram.x[0] = pentagram_shrink_x(EF_NORTH, u1.distance);
|
|
|
|
|
pentagram.y[0] = pentagram_shrink_y(EF_NORTH, u1.distance);
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: All further coordinate assignments are unused and
|
|
|
|
|
// can be deleted. The values are never read in this phase, and
|
|
|
|
|
// phase 12 directly assigns regular polygon coordinates before
|
|
|
|
|
// rendering.
|
|
|
|
|
// Correct ones, too: This seems to be a relic from a time when
|
|
|
|
|
// ZUN stored pentagram corner coordinates in interleaved line
|
|
|
|
|
// order (north → southwest → east → west → southeast) rather
|
|
|
|
|
// than in (counter-)clockwise angle order. Therefore, these
|
|
|
|
|
// wouldn't even result in a pentagram with the rendering
|
|
|
|
|
// function that ended up in the final game.
|
|
|
|
|
pentagram.x[1] = pentagram_shrink_x(EF_SOUTHWEST, u1.distance);
|
|
|
|
|
pentagram.y[1] = pentagram_shrink_y(EF_SOUTHWEST, u1.distance);
|
|
|
|
|
pentagram.x[2] = pentagram_shrink_x(EF_EAST, u1.distance);
|
|
|
|
|
pentagram.y[2] = pentagram_shrink_y(EF_EAST, u1.distance);
|
|
|
|
|
pentagram.x[3] = pentagram_shrink_x(EF_WEST, u1.distance);
|
|
|
|
|
pentagram.y[3] = pentagram_shrink_y(EF_WEST, u1.distance);
|
|
|
|
|
pentagram.x[4] = pentagram_shrink_x(EF_SOUTHEAST, u1.distance);
|
|
|
|
|
pentagram.y[4] = pentagram_shrink_y(EF_SOUTHEAST, u1.distance);
|
|
|
|
|
|
|
|
|
|
u2.subphase = P11_DONE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
yuugenmagan_defeat_if((boss_hp <= PHASE_13_END_HP), false, i);
|
|
|
|
|
if(
|
|
|
|
|
((boss_hp <= PHASE_13_END_HP) || (u2.subphase == P11_DONE)) &&
|
|
|
|
|
!hit.invincible
|
|
|
|
|
) {
|
|
|
|
|
phase.next(12, u2.unused, u3.eyes_open, EF_WEST);
|
|
|
|
|
u1.unused = 0;
|
|
|
|
|
pentagram.center.y = (pentagram.y[0] + PENTAGRAM_RADIUS_FINAL);
|
|
|
|
|
pentagram.center.x = pentagram.x[0];
|
|
|
|
|
pentagram.radius = PENTAGRAM_RADIUS_FINAL;
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: Done for every rendering call in phase 12.
|
|
|
|
|
// (And also wrong, since PENTAGRAM_ANGLE_INITIAL is different.)
|
|
|
|
|
pentagram_corners_set_regular(i, 0x00);
|
|
|
|
|
|
|
|
|
|
// Work around the inaccuracies of 8-bit angles and make sure that
|
|
|
|
|
// at least the lateral corners perfectly line up vertically.
|
|
|
|
|
pentagram.y[1] = pentagram.y[4];
|
|
|
|
|
|
|
|
|
|
// Used in phase 13.
|
|
|
|
|
pattern_interval = select_for_rank(24, 14, 10, 8);
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 12) {
|
|
|
|
|
// Moves the pentagram on top of the kimono figure
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
// Fixing the 8 pixels we've overshot above. In fact, the whole
|
|
|
|
|
// reason for overshooting in the first place might have been to
|
|
|
|
|
// ensure that *this* distance cleanly divides by 3?!
|
|
|
|
|
DISTANCE_Y = ((
|
|
|
|
|
PENTAGRAM_SHRINK_TARGET_CENTER_Y - PENTAGRAM_REGULAR_CENTER_Y
|
|
|
|
|
) + 8),
|
|
|
|
|
|
|
|
|
|
STEPS = (EYE_TOGGLE_FRAMES / PENTAGRAM_INTERVAL),
|
|
|
|
|
Y_STEP = (DISTANCE_Y / STEPS),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: [boss_phase_frame] would have worked as well, and saved
|
|
|
|
|
// the addition.
|
|
|
|
|
invincibility_frame++;
|
|
|
|
|
|
2022-07-28 18:52:31 +00:00
|
|
|
|
// ZUN quirk: Stays on red for the first iteration of the phase, due to
|
|
|
|
|
// merely going
|
|
|
|
|
//
|
|
|
|
|
// (63, 0, 5) → (126, 0, 5) ⇒ #F05
|
|
|
|
|
//
|
|
|
|
|
// On the second iteration though, the same addition causes the red
|
|
|
|
|
// component to overflow into negative numbers, which are clamped to
|
|
|
|
|
// zero:
|
|
|
|
|
//
|
|
|
|
|
// (126, 0, 5) → (-67, 0, 5) ⇒ #005 (dark blue)
|
|
|
|
|
//
|
|
|
|
|
// The apparent pattern then repeats, leading to the color alternating
|
|
|
|
|
// between red and dark blue on every 2.03 iterations if the player
|
|
|
|
|
// manages to stall the fight long enough.
|
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(EF_EAST | EF_SOUTHWEST | EF_SOUTHEAST | EF_NORTH),
|
|
|
|
|
EF_WEST,
|
|
|
|
|
COMPONENT_G,
|
|
|
|
|
COMPONENT_R,
|
|
|
|
|
boss_phase_frame
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if((invincibility_frame % PENTAGRAM_INTERVAL) == 0) {
|
|
|
|
|
// ZUN bug: Missing an unblitting call for the final appearance of
|
|
|
|
|
// the shrunk pentagram from the previous phase. This would
|
|
|
|
|
// require:
|
|
|
|
|
// • storing the shrink animation coordinates inside the structure
|
|
|
|
|
// rather than recalculating them for both unblitting and
|
|
|
|
|
// blitting operations,
|
|
|
|
|
// • and not overwriting them with wrong ones at the end of the
|
|
|
|
|
// previous phase.
|
|
|
|
|
|
|
|
|
|
pentagram.center.y -= Y_STEP;
|
|
|
|
|
pentagram_regular_unput_update_render(PENTAGRAM_ANGLE_INITIAL);
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
phase.next(13, u2.unused, u3.eyes_open, EF_WEST);
|
|
|
|
|
u1.unused = 0;
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: Should *maybe* have been PENTAGRAM_ANGLE_INITIAL to
|
|
|
|
|
// achieve a seamless shift between moving up and spinning. Would
|
|
|
|
|
// change the final pattern, though.
|
|
|
|
|
pentagram_.angle = 0x00;
|
|
|
|
|
|
|
|
|
|
after_hit_frames = 0;
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
boss_palette_snap();
|
|
|
|
|
}
|
|
|
|
|
} else if(boss_phase == 13) {
|
|
|
|
|
enum {
|
|
|
|
|
GROW_RADIUS = (SUBPHASE_TIMEOUT_FRAMES / PENTAGRAM_INTERVAL),
|
|
|
|
|
GROWN_RADIUS = (PENTAGRAM_RADIUS_FINAL + GROW_RADIUS),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define eye_is_open(bit) ( \
|
|
|
|
|
u3.eyes_open & bit \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: Just a more pedantic version of the one above.
|
|
|
|
|
#define eye_is_open_pedantic(bit) ( \
|
|
|
|
|
eye_is_open(bit) == bit \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
phase.frame_common();
|
|
|
|
|
u1.unused++;
|
|
|
|
|
eyes_foreach_if(eye_is_open_pedantic, locked_unput_and_put_8);
|
|
|
|
|
if(hit.invincible && (after_hit_frames == 0)) {
|
|
|
|
|
u3.eyes_open <<= 1;
|
|
|
|
|
after_hit_frames = 1;
|
|
|
|
|
if(u3.eyes_open == EF_RESET) {
|
|
|
|
|
u3.eyes_open = EF_WEST;
|
|
|
|
|
}
|
|
|
|
|
} else if(after_hit_frames > 0) {
|
2022-07-28 18:52:31 +00:00
|
|
|
|
eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
(u3.eyes_open == EF_WEST) ? EF_NORTH : (u3.eyes_open >> 1),
|
|
|
|
|
u3.eyes_open,
|
|
|
|
|
COMPONENT_COUNT,
|
|
|
|
|
COMPONENT_COUNT,
|
|
|
|
|
after_hit_frames
|
|
|
|
|
);
|
2022-07-27 20:19:30 +00:00
|
|
|
|
if(after_hit_frames >= EYE_TOGGLE_FRAMES) {
|
|
|
|
|
after_hit_frames = 0;
|
|
|
|
|
u1.unused = 0;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
eyes_foreach_if(eye_is_open, track_player);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(
|
|
|
|
|
(u2.unused == 0) &&
|
|
|
|
|
((boss_phase_frame % pattern_interval) == 0) &&
|
|
|
|
|
!pentagram_in_slam_phase(pentagram_.phase)
|
|
|
|
|
) {
|
|
|
|
|
for(i = 0; i < PENTAGRAM_POINTS; i++) {
|
|
|
|
|
angle.tmp = iatan2(
|
|
|
|
|
(pentagram.y[i] - pentagram.center.y),
|
|
|
|
|
(pentagram.x[i] - pentagram.center.x)
|
|
|
|
|
);
|
|
|
|
|
Pellets.add_single(
|
|
|
|
|
pentagram.x[i], pentagram.y[i], angle.tmp, to_sp(3.0f)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pentagram_prepare_update(pentagram_, PAP_PREPARE_1);
|
|
|
|
|
pentagram_prepare_update(pentagram_, PAP_PREPARE_2);
|
|
|
|
|
if(pentagram_in_clockwise_spin_phase(pentagram_.phase)) {
|
|
|
|
|
if(pentagram_spin_unput_update_render(pentagram_, CLOCKWISE)) {
|
|
|
|
|
reinterpret_cast<int &>(pentagram_.phase)++;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(pentagram_in_counterclockwise_spin_phase(pentagram_.phase)) {
|
|
|
|
|
if(pentagram_spin_unput_update_render(
|
|
|
|
|
pentagram_, COUNTERCLOCKWISE
|
|
|
|
|
)) {
|
|
|
|
|
reinterpret_cast<int &>(pentagram_.phase)++;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
pentagram.aim(player_left, player_bottom());
|
|
|
|
|
}
|
|
|
|
|
} else if(pentagram_.phase == PAP_SLAM_INTO_PLAYER_1) {
|
|
|
|
|
if(pentagram_slam_unput_update_render(
|
|
|
|
|
pentagram_, (PLAYER_H + 4 + PENTAGRAM_RADIUS_FINAL), false
|
|
|
|
|
)) {
|
|
|
|
|
phase.back_from_13_to_10(PAP_PREPARE_2);
|
|
|
|
|
}
|
|
|
|
|
} else if(pentagram_.phase == PAP_GROW) {
|
|
|
|
|
if((boss_phase_frame % PENTAGRAM_INTERVAL) == 0) {
|
|
|
|
|
pentagram.radius++;
|
|
|
|
|
pentagram_.spin(CLOCKWISE);
|
|
|
|
|
pentagram_.unput_update_render_regular();
|
|
|
|
|
}
|
|
|
|
|
if(boss_phase_frame >= SUBPHASE_TIMEOUT_FRAMES) {
|
|
|
|
|
pentagram_.phase = PAP_SLAM_INTO_PLAYER_2;
|
|
|
|
|
boss_phase_frame = 0;
|
|
|
|
|
pentagram.aim(player_left, player_bottom());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(pentagram_.phase == PAP_SLAM_INTO_PLAYER_2) {
|
|
|
|
|
if(pentagram_slam_unput_update_render(
|
|
|
|
|
pentagram_, (GROWN_RADIUS + (PLAYER_H - 6)), true
|
|
|
|
|
)) {
|
|
|
|
|
phase.back_from_13_to_10(PAP_PREPARE_1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hit.update_and_render(flash_colors);
|
|
|
|
|
yuugenmagan_defeat_if((boss_hp <= PHASE_13_END_HP), true, i);
|
|
|
|
|
|
|
|
|
|
#undef eye_is_open_pedantic
|
|
|
|
|
#undef eye_is_open
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-28 18:52:31 +00:00
|
|
|
|
|
|
|
|
|
void eyes_toggle_and_yokoshima_recolor(
|
|
|
|
|
eye_flag_t eyes_to_close,
|
|
|
|
|
eye_flag_t eyes_to_open,
|
|
|
|
|
int yokoshima_comp_dec,
|
|
|
|
|
int yokoshima_comp_inc,
|
|
|
|
|
int& frame
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
#define eye_should_be_closed(bit) ( \
|
|
|
|
|
eyes_to_close & bit \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
#define eye_should_be_opened(bit) ( \
|
|
|
|
|
eyes_to_open & bit \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
#define eye_is_toggled(bit) ( \
|
|
|
|
|
((eyes_to_close & bit) == bit) || ((eyes_to_open & bit) == bit) \
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
eyes_foreach_if(eye_is_toggled, locked_unput_and_put_8);
|
|
|
|
|
frame++;
|
|
|
|
|
|
|
|
|
|
// ZUN quirk: That's a color change on every frame *except* the 10th one...
|
|
|
|
|
if((frame % 10) != 0) {
|
|
|
|
|
if(yokoshima_comp_dec < COMPONENT_COUNT) {
|
|
|
|
|
// This clamping condition is actually rather important to keep
|
|
|
|
|
// [stage_palette] from running into negative numbers.
|
|
|
|
|
if(z_Palettes[COL_YOKOSHIMA].v[yokoshima_comp_dec] > 0) {
|
|
|
|
|
z_Palettes[COL_YOKOSHIMA].v[yokoshima_comp_dec]--; // ZUN bloat
|
|
|
|
|
stage_palette[COL_YOKOSHIMA].v[yokoshima_comp_dec]--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(yokoshima_comp_inc < COMPONENT_COUNT) {
|
|
|
|
|
// ZUN quirk: No equivalent clamping condition here. The final
|
|
|
|
|
// color in [z_Palettes] is clamped to 0xF, but there's nothing to
|
|
|
|
|
// prevent the [stage_palette] version from running past 0xF.
|
|
|
|
|
// If a component that was previously incremented like this is then
|
|
|
|
|
// decremented in subsequent recoloring operations, this overflow
|
|
|
|
|
// manifests itself as a noticeable delay: The component will spend
|
|
|
|
|
// most of the recoloring time above 0xF, and only fall back into
|
|
|
|
|
// the valid color range at the *end* of the recoloring phase. If
|
|
|
|
|
// the previously component additionally started out with a non-0x0
|
|
|
|
|
// value (which is the case for the blue component in the original
|
|
|
|
|
// game), decrementing it for a constant amount of frames can only
|
|
|
|
|
// ever reach that brighter initial value again.
|
|
|
|
|
z_Palettes[COL_YOKOSHIMA].v[yokoshima_comp_inc]++; // ZUN bloat
|
|
|
|
|
stage_palette[COL_YOKOSHIMA].v[yokoshima_comp_inc]++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZUN bloat: z_palette_set_show(COL_YOKOSHIMA) is enough.
|
|
|
|
|
z_palette_set_all_show(stage_palette);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Matches the animation in phase_0_eye_open().
|
|
|
|
|
if(frame == ((EYE_OPENING_FRAMES / 3) * 1)) {
|
|
|
|
|
eyes_set_image(eyes_to_close, C_HALFOPEN);
|
|
|
|
|
eyes_set_image(eyes_to_open, C_CLOSED);
|
|
|
|
|
eyes_foreach_if(eye_should_be_opened, hitbox_orb_activate);
|
|
|
|
|
} else if(frame == ((EYE_OPENING_FRAMES / 3) * 2)) {
|
|
|
|
|
eyes_set_image(eyes_to_close, C_CLOSED);
|
|
|
|
|
eyes_set_image(eyes_to_open, C_HALFOPEN);
|
|
|
|
|
eyes_foreach_if(eye_should_be_closed, hitbox_orb_deactivate);
|
|
|
|
|
} else if(frame == ((EYE_OPENING_FRAMES / 3) * 3)) {
|
|
|
|
|
eyes_set_image(eyes_to_close, C_HIDDEN);
|
|
|
|
|
eyes_set_image(eyes_to_open, C_AHEAD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef eye_should_be_closed
|
|
|
|
|
#undef eye_should_be_opened
|
|
|
|
|
#undef eye_is_toggled
|
|
|
|
|
}
|