mirror of https://github.com/nmlgc/ReC98.git
328 lines
8.2 KiB
C++
328 lines
8.2 KiB
C++
/// Jigoku Stage 10 Boss - Mima
|
||
/// ---------------------------
|
||
|
||
extern "C" {
|
||
#include <stddef.h>
|
||
#include "platform.h"
|
||
#include "pc98.h"
|
||
#include "planar.h"
|
||
#include "th01/v_colors.hpp"
|
||
#include "th01/math/area.hpp"
|
||
#include "th01/math/subpixel.hpp"
|
||
#include "th01/hardware/egc.h"
|
||
#include "th01/hardware/graph.h"
|
||
#include "th01/hardware/palette.h"
|
||
#include "th01/formats/grp.h"
|
||
#include "th01/formats/pf.hpp"
|
||
#include "th01/formats/ptn.hpp"
|
||
#include "th01/main/playfld.hpp"
|
||
#include "th01/main/vars.hpp"
|
||
#include "th01/main/boss/entity_a.hpp"
|
||
}
|
||
#include "th01/shiftjis/fns.hpp"
|
||
#undef MISSILE_FN
|
||
#define MISSILE_FN boss3_m_ptn_0
|
||
extern const char MISSILE_FN[];
|
||
#include "th01/main/particle.hpp"
|
||
#include "th01/main/boss/boss.hpp"
|
||
#include "th01/main/boss/palette.hpp"
|
||
#include "th01/main/bullet/missile.hpp"
|
||
#include "th01/main/hud/hp.hpp"
|
||
|
||
// Coordinates
|
||
// -----------
|
||
|
||
static const pixel_t MIMA_W = 128;
|
||
static const pixel_t MIMA_H = 160;
|
||
|
||
static const pixel_t MIMA_ANIM_TOP = 48; // relative to the sprite's top edge
|
||
static const pixel_t MIMA_ANIM_H = 64;
|
||
// -----------
|
||
|
||
#define meteor_active mima_meteor_active
|
||
#define spreadin_interval mima_spreadin_interval
|
||
#define spreadin_speed mima_spreadin_speed
|
||
#define flash_colors mima_flash_colors
|
||
#define invincibility_frame mima_invincibility_frame
|
||
#define invincible mima_invincible
|
||
#define initial_hp_rendered mima_initial_hp_rendered
|
||
extern int invincibility_frame;
|
||
extern bool16 invincible;
|
||
extern bool initial_hp_rendered;
|
||
|
||
extern bool meteor_active;
|
||
|
||
// Amount of frames between the individual steps of the spread-in transition
|
||
extern uint8_t spreadin_interval;
|
||
// Sprite pixels to spread in per frame, in one half of Mima's sprite
|
||
extern uint8_t spreadin_speed;
|
||
|
||
// File names
|
||
// ----------
|
||
|
||
extern const char boss3_1_bos[];
|
||
extern const char boss3_2_bos[];
|
||
extern const char boss3_grp_0[];
|
||
extern const char boss5_gr_grc[];
|
||
// ----------
|
||
|
||
// Entities
|
||
// --------
|
||
|
||
static const int METEOR_CELS = 4;
|
||
|
||
enum anim_cel_t {
|
||
C_CAST = 0,
|
||
C_METEOR = 1,
|
||
C_METEOR_last = (C_METEOR + METEOR_CELS - 1),
|
||
};
|
||
|
||
#define ent_still boss_entities[0]
|
||
#define ent_anim boss_entities[1]
|
||
|
||
inline void mima_ent_load(void) {
|
||
ent_still.load(boss3_1_bos, 0);
|
||
ent_anim.load(boss3_2_bos, 1);
|
||
}
|
||
|
||
inline void mima_ent_free(void) {
|
||
bos_entity_free(0);
|
||
bos_entity_free(1);
|
||
}
|
||
// --------
|
||
|
||
// .PTN
|
||
// ----
|
||
|
||
static const main_ptn_slot_t PTN_SLOT_BG_ENT = PTN_SLOT_BOSS_1;
|
||
static const main_ptn_slot_t PTN_SLOT_MISSILE = PTN_SLOT_BOSS_2;
|
||
|
||
// Three unused background .PTN IDs, for three unused 32×32 animations?
|
||
static const int BG_ENT_OFFSET = 3;
|
||
// ----
|
||
|
||
void mima_load(void)
|
||
{
|
||
int col;
|
||
int comp;
|
||
|
||
mima_ent_load();
|
||
grp_palette_load_show(boss3_grp_0);
|
||
palette_copy(boss_post_defeat_palette, z_Palettes, col, comp);
|
||
void mima_setup(void);
|
||
mima_setup();
|
||
ptn_new(
|
||
PTN_SLOT_BG_ENT,
|
||
(((MIMA_W / PTN_W) * (MIMA_H / PTN_H)) + BG_ENT_OFFSET + 1)
|
||
);
|
||
Missiles.load(PTN_SLOT_MISSILE);
|
||
Missiles.reset();
|
||
}
|
||
|
||
inline void ent_anim_sync_with_still(void) {
|
||
ent_anim.pos_cur_set(
|
||
ent_still.cur_left, (ent_still.cur_top + MIMA_ANIM_TOP)
|
||
);
|
||
}
|
||
|
||
#define ent_anim_sync_with_still_and_put_both(cel) { \
|
||
ent_anim_sync_with_still(); \
|
||
ent_anim.bos_image = cel; \
|
||
graph_accesspage_func(1); ent_anim.put_8(cel); \
|
||
graph_accesspage_func(0); ent_anim.put_8(cel); \
|
||
}
|
||
|
||
void meteor_put(void)
|
||
{
|
||
if(meteor_active && ((boss_phase_frame % 8) == 0)) {
|
||
ent_anim_sync_with_still();
|
||
ent_anim.set_image_unput_and_put_8(
|
||
(C_METEOR + ((boss_phase_frame / 8) % METEOR_CELS))
|
||
);
|
||
}
|
||
}
|
||
|
||
void mima_put_cast_both(void)
|
||
{
|
||
meteor_active = false;
|
||
ent_anim_sync_with_still_and_put_both(C_CAST);
|
||
}
|
||
|
||
void meteor_activate(void)
|
||
{
|
||
if(!meteor_active) {
|
||
meteor_active = true;
|
||
ent_anim_sync_with_still_and_put_both(C_METEOR);
|
||
}
|
||
}
|
||
|
||
void mima_put_still_both(void)
|
||
{
|
||
graph_accesspage_func(1); ent_still.put_8();
|
||
graph_accesspage_func(0); ent_still.put_8();
|
||
}
|
||
|
||
void mima_bg_snap(void)
|
||
{
|
||
int ptn_x;
|
||
int ptn_y;
|
||
screen_x_t left = ent_still.cur_left;
|
||
screen_y_t top = ent_still.cur_top;
|
||
int image = BG_ENT_OFFSET;
|
||
|
||
ptn_snap_rect_from_1_8(
|
||
left, top, MIMA_W, MIMA_H, PTN_SLOT_BG_ENT, image, ptn_x, ptn_y
|
||
);
|
||
}
|
||
|
||
void mima_unput(bool16 just_the_animated_part = false)
|
||
{
|
||
int ptn_x;
|
||
int image = BG_ENT_OFFSET;
|
||
screen_x_t left = ent_still.cur_left;
|
||
screen_y_t top = ent_still.cur_top;
|
||
|
||
if(!just_the_animated_part) {
|
||
int ptn_y;
|
||
ptn_put_rect_noalpha_8(
|
||
left, top, MIMA_W, MIMA_H, PTN_SLOT_BG_ENT, image, ptn_x, ptn_y
|
||
);
|
||
return;
|
||
}
|
||
|
||
// (The code below is never executed in the original game.)
|
||
|
||
// Advance to the .PTN background row that contains the background behind
|
||
// the animating part of Mima's sprite (i.e., the second one)
|
||
image = (BG_ENT_OFFSET + ((MIMA_ANIM_TOP / PTN_H) * (MIMA_W / PTN_W)));
|
||
|
||
// And since MIMA_ANIM_TOP is only a multiple of 16 and not 32, we have to
|
||
// first awkwardly unblit a MIMA_W×16 area...
|
||
for(ptn_x = 0; ptn_x < (MIMA_W / PTN_W); ptn_x++) {
|
||
ptn_put_quarter_noalpha_8(
|
||
(left + (0 * PTN_QUARTER_W) + (ptn_x * PTN_W)),
|
||
(top + MIMA_ANIM_TOP),
|
||
PTN_ID(PTN_SLOT_BG_ENT, image),
|
||
(((MIMA_ANIM_TOP % PTN_H) / PTN_QUARTER_H) * 2)
|
||
);
|
||
ptn_put_quarter_noalpha_8(
|
||
(left + (1 * PTN_QUARTER_W) + (ptn_x * PTN_W)),
|
||
(top + MIMA_ANIM_TOP),
|
||
PTN_ID(PTN_SLOT_BG_ENT, image),
|
||
((((MIMA_ANIM_TOP % PTN_H) / PTN_QUARTER_H) * 2) + 1)
|
||
);
|
||
image++;
|
||
}
|
||
|
||
// ZUN bug (?): Why is MIMA_ANIM_H assumed to be 48 (16 above + 32 here)?
|
||
// This might have even worked if the bottom 16 pixels of all [ent_anim]
|
||
// cels were identical, but they differ between C_CAST and C_METEOR.
|
||
//
|
||
// Note that this has nothing to do with Mima's infamous "third arm"
|
||
// (remember, the game never executes this code), but wouldn't exactly
|
||
// prevent it from happening either.
|
||
#define bug_top (top + MIMA_ANIM_TOP + PTN_QUARTER_H)
|
||
ptn_put_row_noalpha_8(left, bug_top, MIMA_W, PTN_SLOT_BG_ENT, image, ptn_x);
|
||
#undef bug_top
|
||
}
|
||
|
||
inline pixel_t spreadin_bottom_cur(void) {
|
||
return ((spreadin_speed / spreadin_interval) * (boss_phase_frame - 10));
|
||
}
|
||
|
||
void spreadin_unput_and_put(screen_x_t left, screen_y_t top)
|
||
{
|
||
pixel_t row;
|
||
pixel_t line_on_top;
|
||
|
||
if(boss_phase_frame < 10) {
|
||
return;
|
||
} else if(boss_phase_frame == 10) {
|
||
ent_still.pos_cur_set(left, top);
|
||
mima_bg_snap();
|
||
line_on_top = (top + (MIMA_H / 2));
|
||
return;
|
||
} else if((boss_phase_frame % spreadin_interval) != 0) {
|
||
return;
|
||
}
|
||
|
||
line_on_top = ((MIMA_H / 2) - spreadin_bottom_cur());
|
||
if(line_on_top < 0) {
|
||
boss_phase_frame = 0;
|
||
mima_put_still_both();
|
||
return;
|
||
}
|
||
for(row = 0; spreadin_bottom_cur() > row; row++) {
|
||
ent_still.unput_and_put_1line(
|
||
left, (top + line_on_top + row), ent_still.bos_image, row
|
||
);
|
||
ent_still.unput_and_put_1line(
|
||
left,
|
||
((top + MIMA_H) - line_on_top - row),
|
||
ent_still.bos_image,
|
||
((MIMA_H - 1) - row)
|
||
);
|
||
}
|
||
|
||
}
|
||
|
||
// Only called while Mima isn't visible anyway. But even apart from that, it
|
||
// barely would have any effect anywhere, as the Mima sprite is blitted to both
|
||
// VRAM pages. This *might* have been supposed to crossfade between various
|
||
// cels? …Nah, why would you do that by blitting whole lines.
|
||
void mima_vertical_sprite_transition_broken(void)
|
||
{
|
||
if((boss_phase_frame < 10) || ((boss_phase_frame % 4) != 0)) {
|
||
return;
|
||
}
|
||
pixel_t half_h = ((boss_phase_frame - 10) * 2);
|
||
if(half_h >= (MIMA_H / 2)) {
|
||
boss_phase_frame = 0;
|
||
return;
|
||
}
|
||
// And besides, *VRAM width*?! This is completely broken.
|
||
egc_copy_rect_1_to_0_16(
|
||
ent_still.cur_left, (ent_still.cur_top + half_h), ent_still.vram_w, 8
|
||
);
|
||
egc_copy_rect_1_to_0_16(
|
||
ent_still.cur_left,
|
||
(ent_still.cur_top + (MIMA_H - 8) - half_h),
|
||
ent_still.vram_w,
|
||
8
|
||
);
|
||
}
|
||
|
||
void mima_setup(void)
|
||
{
|
||
boss_palette_snap();
|
||
ent_still.bos_image = 0;
|
||
ent_anim.bos_image = C_METEOR;
|
||
z_palette_white_in();
|
||
ent_still.pos_set(
|
||
(PLAYFIELD_CENTER_X - (MIMA_W / 2)), PLAYFIELD_TOP, 48,
|
||
PLAYFIELD_LEFT, (PLAYFIELD_RIGHT + ((MIMA_W / 4) * 3)),
|
||
PLAYFIELD_TOP, (PLAYFIELD_BOTTOM - ((MIMA_H / 5) * 3))
|
||
);
|
||
ent_still.hitbox_set(
|
||
((MIMA_W / 4) * 1), ((MIMA_H / 5) * 1),
|
||
((MIMA_W / 4) * 3), ((MIMA_H / 5) * 4)
|
||
);
|
||
ent_still.hitbox_orb_inactive = false;
|
||
boss_phase_frame = 0;
|
||
boss_phase = 0;
|
||
boss_hp = 12;
|
||
hud_hp_first_white = 6;
|
||
hud_hp_first_redwhite = 2;
|
||
particles_unput_update_render(PO_INITIALIZE, V_WHITE);
|
||
}
|
||
|
||
void mima_free(void)
|
||
{
|
||
mima_ent_free();
|
||
ptn_free(PTN_SLOT_BG_ENT);
|
||
ptn_free(PTN_SLOT_MISSILE);
|
||
}
|
||
|
||
#define select_for_rank mima_select_for_rank
|
||
#include "th01/main/select_r.cpp"
|