mirror of https://github.com/nmlgc/ReC98.git
604 lines
18 KiB
C++
604 lines
18 KiB
C++
#include <stddef.h>
|
||
#include "platform.h"
|
||
#include "x86real.h"
|
||
#include "pc98.h"
|
||
#include "planar.h"
|
||
#include "shiftjis.hpp"
|
||
#include "master.hpp"
|
||
#include "platform/array.hpp"
|
||
#include "libs/kaja/kaja.h"
|
||
#include "th02/common.h"
|
||
#include "th02/resident.hpp"
|
||
#include "th02/hardware/frmdelay.h"
|
||
extern "C" {
|
||
#include "th02/hardware/input.hpp"
|
||
#include "th02/hardware/pages.hpp"
|
||
#include "th02/snd/snd.h"
|
||
}
|
||
#include "th02/formats/dialog.hpp"
|
||
#include "th02/formats/tile.hpp"
|
||
#include "th02/formats/mpn.hpp"
|
||
#include "th02/main/playfld.hpp"
|
||
#include "th02/main/score.hpp"
|
||
#include "th02/main/scroll.hpp"
|
||
#include "th02/main/dialog/dialog.hpp"
|
||
#include "th02/main/hud/overlay.hpp"
|
||
#include "th02/main/stage/stage.hpp"
|
||
#include "th02/main/player/player.hpp"
|
||
#include "th02/main/tile/tile.hpp"
|
||
#include "th02/sprites/main_pat.h"
|
||
#include "th02/sprites/face.hpp"
|
||
|
||
// Coordinates
|
||
// -----------
|
||
|
||
static const pixel_t BOX_W = PLAYFIELD_W;
|
||
static const pixel_t BOX_H = (
|
||
(DIALOG_BOX_PART_H / 2) +
|
||
(DIALOG_BOX_LINES * GLYPH_H) +
|
||
(DIALOG_BOX_PART_H / 2)
|
||
);
|
||
static const screen_x_t BOX_LEFT = PLAYFIELD_LEFT;
|
||
static const screen_y_t BOX_TOP = (PLAYFIELD_BOTTOM - BOX_H);
|
||
|
||
static const pixel_t BOX_MIDDLE_W = (
|
||
BOX_W - DIALOG_BOX_LEFT_W - DIALOG_BOX_PART_W
|
||
);
|
||
|
||
static const pixel_t BOX_SLIDE_SPEED = (PLAYFIELD_W / 24);
|
||
|
||
static const screen_x_t FACE_LEFT = (BOX_LEFT + 8);
|
||
static const screen_y_t FACE_TOP = (BOX_TOP + 8);
|
||
static const tram_x_t TEXT_TRAM_LEFT = (
|
||
(BOX_LEFT + DIALOG_BOX_LEFT_W - (DIALOG_BOX_PART_W / 2)) / GLYPH_HALF_W
|
||
);
|
||
static const tram_x_t TEXT_TRAM_TOP = (
|
||
(BOX_TOP + (BOX_H / 2) - ((DIALOG_BOX_LINES * GLYPH_H) / 2)) / GLYPH_H
|
||
);
|
||
// -----------
|
||
|
||
// State
|
||
// -----
|
||
|
||
extern shiftjis_t dialog_text[64][DIALOG_BOX_LINES][DIALOG_LINE_SIZE];
|
||
extern bool restore_tile_mode_none_at_post;
|
||
// -----
|
||
|
||
// Core system
|
||
// -----------
|
||
|
||
void dialog_load_and_init(void)
|
||
{
|
||
extern char dialog_fn[];
|
||
char* fn = dialog_fn;
|
||
fn[5] = ('0' + stage_id);
|
||
|
||
file_ropen(fn);
|
||
|
||
// ZUN landmine: No check to ensure that the size is ≤ sizeof(dialog_text).
|
||
size_t size = file_size();
|
||
file_read(dialog_text, size);
|
||
|
||
file_close();
|
||
dialog_box_cur = 0;
|
||
}
|
||
|
||
// ZUN bloat: Turn into a single global inline function.
|
||
#define egc_start_copy static near egc_start_copy
|
||
#include "th01/hardware/egcstart.cpp"
|
||
#undef egc_start_copy
|
||
|
||
void near dialog_put_player(void)
|
||
{
|
||
// A function that shouldn't exist, and it contains no less than 3 bugs!
|
||
//
|
||
// • ZUN bug: This function is supposed to blit the player with an enforced
|
||
// PAT_PLAYCHAR_STILL sprite and keep this sprite visible under the text
|
||
// box during the slide-in animation. However, every sprite blitting call
|
||
// must be preceded by a matching invalidation to clear the sprite from
|
||
// its previous position in VRAM, which is missing from both call sites.
|
||
// The only area that is sure to be cleared when we get here is the text
|
||
// box area from (32, 320) to (416, 400) exclusive, so this only won't
|
||
// overlap a previously blitted PAT_PLAYCHAR_LEFT or PAT_PLAYCHAR_RIGHT
|
||
// sprite if it happens to be located entirely within that area.
|
||
//
|
||
// • ZUN bug: Also, this function is exclusively called within the very
|
||
// first function that runs after flipping hardware pages, long before
|
||
// any coordinates are updated. Therefore, [page_front] would have been
|
||
// the correct index into the option position array; [page_back] points
|
||
// to the previous position of the option.
|
||
//
|
||
// • ZUN bug: It also blits the option sprite for the A shot type,
|
||
// regardless of which one the player is actually using. This should
|
||
// really be done in a common function inside the player module, which
|
||
// can then refer to [player_patnum].
|
||
//
|
||
// (You could also count the bug that shifts up all sprites blitted in the
|
||
// last frame before the dialog by one pixel because the three here draw
|
||
// even more attention towards that one, but the fix for that one needs to
|
||
// be somewhere else.)
|
||
|
||
#define option_left_topleft player_option_left_topleft
|
||
|
||
vram_y_t top;
|
||
|
||
top = scroll_screen_y_to_vram(top, player_topleft.y);
|
||
super_roll_put(player_topleft.x, top, PAT_PLAYCHAR_STILL);
|
||
|
||
top = scroll_screen_y_to_vram(top, option_left_topleft[page_back].y);
|
||
super_roll_put_tiny(option_left_topleft[page_back].x, top, PAT_OPTION_A);
|
||
super_roll_put_tiny(
|
||
(option_left_topleft[page_back].x + PLAYER_OPTION_TO_OPTION_DISTANCE),
|
||
top,
|
||
PAT_OPTION_A
|
||
);
|
||
|
||
#undef option_left_topleft
|
||
}
|
||
|
||
void pascal near dialog_box_put_top_and_bottom_part(
|
||
screen_x_t& left,
|
||
vram_y_t top_top,
|
||
vram_y_t bottom_top,
|
||
int top_patnum // ACTUAL TYPE: main_patnum_t
|
||
)
|
||
{
|
||
enum {
|
||
PATNUM_TO_BOTTOM = (
|
||
PAT_DIALOG_BOX_LEFT_BOTTOM - PAT_DIALOG_BOX_LEFT_TOP
|
||
),
|
||
};
|
||
|
||
// Assuming the constant slide speed, these are the last coordinates where
|
||
// the box part wouldn't be fully covered by black TRAM cells.
|
||
// ZUN bloat: Using the playfield_clip_left*() and playfield_clip_right*()
|
||
// functions would have been cleaner and more consistent.
|
||
static_assert(BOX_SLIDE_SPEED == (DIALOG_BOX_PART_W / 2));
|
||
if(
|
||
(left >= (PLAYFIELD_LEFT - BOX_SLIDE_SPEED)) &&
|
||
(left <= (PLAYFIELD_RIGHT - BOX_SLIDE_SPEED))
|
||
) {
|
||
static_assert(BOX_H == (DIALOG_BOX_PART_H * 2));
|
||
static_assert(
|
||
(PAT_DIALOG_BOX_MIDDLE_BOTTOM - PAT_DIALOG_BOX_MIDDLE_TOP) ==
|
||
PATNUM_TO_BOTTOM
|
||
);
|
||
static_assert(
|
||
(PAT_DIALOG_BOX_RIGHT_BOTTOM - PAT_DIALOG_BOX_RIGHT_TOP) ==
|
||
PATNUM_TO_BOTTOM
|
||
);
|
||
super_roll_put(left, top_top, (top_patnum + 0));
|
||
super_roll_put(left, bottom_top, (top_patnum + PATNUM_TO_BOTTOM));
|
||
}
|
||
left += DIALOG_BOX_PART_W;
|
||
}
|
||
|
||
// ZUN bloat: Passing [left] by reference made sense in the function above, but
|
||
// here it doesn't.
|
||
void pascal near dialog_box_put(
|
||
screen_x_t& left, vram_y_t top_top, vram_y_t bottom_top
|
||
)
|
||
{
|
||
int i;
|
||
|
||
for(i = PAT_DIALOG_BOX_LEFT_TOP; i < PAT_DIALOG_BOX_MIDDLE_TOP; i++) {
|
||
dialog_box_put_top_and_bottom_part(left, top_top, bottom_top, i);
|
||
}
|
||
for(i = 0; i < (BOX_MIDDLE_W / DIALOG_BOX_PART_W); i++) {
|
||
dialog_box_put_top_and_bottom_part(
|
||
left, top_top, bottom_top, PAT_DIALOG_BOX_MIDDLE_TOP
|
||
);
|
||
}
|
||
dialog_box_put_top_and_bottom_part(
|
||
left, top_top, bottom_top, PAT_DIALOG_BOX_RIGHT_TOP
|
||
);
|
||
}
|
||
|
||
// ZUN bloat: Turn this and the function below into a single
|
||
// dialog_box_slide_animate() function.
|
||
#define dialog_box_slide_init(left, top_top, bottom_top, left_start) { \
|
||
top_top = scroll_screen_y_to_vram(top_top, BOX_TOP); \
|
||
scroll_add_scrolled(bottom_top, top_top, 0); \
|
||
left = left_start; \
|
||
}
|
||
|
||
#define dialog_box_slide_update_and_render( \
|
||
left, temp_left_mut, top_top, bottom_top \
|
||
) { \
|
||
left -= BOX_SLIDE_SPEED; \
|
||
temp_left_mut = left; \
|
||
frame_delay(1); \
|
||
\
|
||
egc_start_copy(); \
|
||
\
|
||
/* \
|
||
* ZUN bloat: Why not [BOX_H]? Reblitting the bottom margin of the \
|
||
* playfield has no effect. \
|
||
*/ \
|
||
tiles_invalidate_rect(BOX_LEFT, BOX_TOP, BOX_W, (RES_Y - BOX_TOP)); \
|
||
\
|
||
tiles_egc_render(); \
|
||
egc_off(); \
|
||
\
|
||
dialog_put_player(); \
|
||
dialog_box_put(temp_left_mut, top_top, bottom_top); \
|
||
}
|
||
|
||
void near dialog_pre(void)
|
||
{
|
||
// ZUN bloat: Deduplicate and move into dialog_box_slide_animate().
|
||
vram_y_t top_top;
|
||
vram_y_t bottom_top = DIALOG_BOX_PART_H;
|
||
|
||
overlay_wipe();
|
||
|
||
// ZUN quirk: The game doesn't reset [score_delta] before the next proper
|
||
// game frame calls score_update_and_render(), thus retaining the inherent
|
||
// quirk of this function.
|
||
score_grant_current_delta_as_bonus();
|
||
|
||
graph_scrollup(scroll_line);
|
||
palette_100();
|
||
graph_accesspage(page_front);
|
||
|
||
// ZUN bloat: Already did this above. TRAM is not double-buffered :P
|
||
overlay_wipe();
|
||
|
||
screen_x_t left_mut;
|
||
screen_x_t left;
|
||
dialog_box_slide_init(
|
||
left, top_top, bottom_top, (PLAYFIELD_RIGHT - BOX_SLIDE_SPEED)
|
||
);
|
||
|
||
// If we don't render any tiles, tiles_egc_render() should behave like a
|
||
// flood fill with hardware color 0 in order to actually unblit anything.
|
||
// ZUN bug: It would have been more robust to just snap the entire original
|
||
// [BOX_W]×[BOX_H] area and re-blit that to VRAM on every frame.
|
||
if(tile_mode == TM_NONE) {
|
||
restore_tile_mode_none_at_post = true;
|
||
tile_mode = TM_COL_0;
|
||
} else {
|
||
restore_tile_mode_none_at_post = false;
|
||
}
|
||
|
||
while(left > PLAYFIELD_LEFT) {
|
||
dialog_box_slide_update_and_render(left, left_mut, top_top, bottom_top);
|
||
}
|
||
}
|
||
|
||
void near dialog_post(void)
|
||
{
|
||
// ZUN bloat: Deduplicate and move into dialog_box_slide_animate().
|
||
vram_y_t top_top;
|
||
vram_y_t bottom_top = DIALOG_BOX_PART_H;
|
||
|
||
overlay_wipe();
|
||
|
||
screen_x_t left_mut;
|
||
screen_x_t left;
|
||
dialog_box_slide_init(left, top_top, bottom_top, PLAYFIELD_LEFT);
|
||
|
||
do {
|
||
dialog_box_slide_update_and_render(left, left_mut, top_top, bottom_top);
|
||
} while(left_mut >= (PLAYFIELD_LEFT + BOX_SLIDE_SPEED));
|
||
|
||
if(restore_tile_mode_none_at_post == true) {
|
||
restore_tile_mode_none_at_post = false;
|
||
tile_mode = TM_NONE;
|
||
}
|
||
|
||
graph_accesspage(page_back);
|
||
}
|
||
|
||
void pascal near dialog_face_put(
|
||
int topleft_id // ACTUAL TYPE: face_topleft_id_t
|
||
)
|
||
{
|
||
static_assert(FACE_TILES_X == 3);
|
||
static_assert(FACE_TILES_Y == 3);
|
||
|
||
vram_y_t top1;
|
||
vram_y_t top2 = 0;
|
||
vram_y_t top3 = 0;
|
||
|
||
top1 = scroll_screen_y_to_vram(top1, FACE_TOP);
|
||
scroll_add_scrolled(top2, top1, TILE_H);
|
||
scroll_add_scrolled(top3, top2, TILE_H);
|
||
|
||
if(topleft_id == FACE_COL_0) {
|
||
grcg_setcolor(GC_RMW, 0);
|
||
grcg_boxfill(
|
||
FACE_LEFT, top1, (FACE_LEFT + FACE_W - 1), (top1 + FACE_H - 1)
|
||
);
|
||
grcg_off();
|
||
return;
|
||
}
|
||
|
||
mpn_put_8((FACE_LEFT + (0 * TILE_W)), top1, face_tile_id(topleft_id, 0, 0));
|
||
mpn_put_8((FACE_LEFT + (1 * TILE_W)), top1, face_tile_id(topleft_id, 1, 0));
|
||
mpn_put_8((FACE_LEFT + (2 * TILE_W)), top1, face_tile_id(topleft_id, 2, 0));
|
||
mpn_put_8((FACE_LEFT + (0 * TILE_W)), top2, face_tile_id(topleft_id, 0, 1));
|
||
mpn_put_8((FACE_LEFT + (1 * TILE_W)), top2, face_tile_id(topleft_id, 1, 1));
|
||
mpn_put_8((FACE_LEFT + (2 * TILE_W)), top2, face_tile_id(topleft_id, 2, 1));
|
||
mpn_put_8((FACE_LEFT + (0 * TILE_W)), top3, face_tile_id(topleft_id, 0, 2));
|
||
mpn_put_8((FACE_LEFT + (1 * TILE_W)), top3, face_tile_id(topleft_id, 1, 2));
|
||
mpn_put_8((FACE_LEFT + (2 * TILE_W)), top3, face_tile_id(topleft_id, 2, 2));
|
||
}
|
||
|
||
void pascal near dialog_text_put(
|
||
tram_cell_amount_t line, const shiftjis_t* str, tram_atrb2 atrb, int n
|
||
)
|
||
{
|
||
// ZUN landmine: master.lib has text_putnsa() for this purpose, which works
|
||
// without copying the string and risking a buffer overflow in the process,
|
||
// or wasting 40 bytes of conventional RAM on \0 bytes.
|
||
extern const Array<shiftjis_t, DIALOG_LINE_SIZE> clear_bytes;
|
||
Array<shiftjis_t, DIALOG_LINE_SIZE> buf = clear_bytes;
|
||
for(int i = 0; i < n; i++) {
|
||
buf[i] = str[i];
|
||
}
|
||
|
||
text_putsa(TEXT_TRAM_LEFT, (TEXT_TRAM_TOP + line), buf.data(), atrb);
|
||
}
|
||
|
||
inline void near dialog_box_wipe(void) {
|
||
extern const char near* LINE_BLANK;
|
||
|
||
static_assert(DIALOG_BOX_LINES == 2);
|
||
text_putsa(TEXT_TRAM_LEFT, (TEXT_TRAM_TOP + 0), LINE_BLANK, TX_WHITE);
|
||
text_putsa(TEXT_TRAM_LEFT, (TEXT_TRAM_TOP + 1), LINE_BLANK, TX_WHITE);
|
||
}
|
||
|
||
// Shows a single dialog box in a blocking way, then advances [box_cur].
|
||
void pascal near dialog_box_animate_and_advance(
|
||
int face_topleft_id // ACTUAL TYPE: face_topleft_id_t
|
||
)
|
||
{
|
||
dialog_box_wipe();
|
||
|
||
// ZUN quirk: Assumes that the box starts with a 6-byte character name and
|
||
// colon, and prints that all at once in the first frame. This assumption
|
||
// breaks with "魔梨沙:" in Stage 3, which is 8 bytes.
|
||
shiftjis_ank_amount_t box_cursor = 6;
|
||
|
||
int loop_count = 0;
|
||
int delay_per_kanji;
|
||
int box = dialog_box_cur;
|
||
while(box_cursor <= ((DIALOG_BOX_LINES * DIALOG_LINE_LENGTH) + 8)) {
|
||
input_sense();
|
||
dialog_face_put(face_topleft_id); // ZUN bloat: Every frame?
|
||
|
||
static_assert(DIALOG_BOX_LINES == 2);
|
||
if(box_cursor <= (DIALOG_LINE_LENGTH * 1)) {
|
||
dialog_text_put(
|
||
0,
|
||
dialog_text[box][0],
|
||
TX_WHITE,
|
||
(box_cursor - (DIALOG_LINE_LENGTH * 0))
|
||
);
|
||
} else if(box_cursor <= (DIALOG_LINE_LENGTH * 2)) {
|
||
dialog_text_put(
|
||
1,
|
||
dialog_text[box][1],
|
||
TX_WHITE,
|
||
(box_cursor - (DIALOG_LINE_LENGTH * 1))
|
||
);
|
||
}
|
||
|
||
loop_count++;
|
||
if(key_det) {
|
||
delay_per_kanji = 1;
|
||
} else {
|
||
delay_per_kanji = 3;
|
||
frame_delay(1);
|
||
}
|
||
if((loop_count % delay_per_kanji) == 0) {
|
||
box_cursor += static_cast<int>(sizeof(shiftjis_kanji_t));
|
||
}
|
||
}
|
||
key_delay();
|
||
dialog_box_cur++;
|
||
}
|
||
// -----------
|
||
|
||
// Stage-specific hardcoded "scripts"
|
||
// ----------------------------------
|
||
// ZUN bloat: All face ID arrays in this section should have been `static
|
||
// const` to avoid the useless copy.
|
||
|
||
#define boxes_animate(face_id_array) { \
|
||
for(int i = 0; i < face_id_array.count(); i++) { \
|
||
dialog_box_animate_and_advance(face_id_array[i]); \
|
||
} \
|
||
}
|
||
|
||
void pascal near dialog_script_generic_part_animate(dialog_sequence_t sequence)
|
||
{
|
||
enum {
|
||
STAGE_SEQUENCE_COUNT = (TOTAL_STAGE_COUNT * DS_COUNT),
|
||
};
|
||
extern const uint8_t GENERIC_BOX_COUNTS[TOTAL_STAGE_COUNT][DS_COUNT];
|
||
extern const face_tile_topleft_t GENERIC_FACES[STAGE_SEQUENCE_COUNT][22];
|
||
|
||
int stage_and_sequence = ((stage_id * DS_COUNT) + sequence);
|
||
for(int i = 0; i < GENERIC_BOX_COUNTS[stage_id][sequence]; i++) {
|
||
dialog_box_animate_and_advance(GENERIC_FACES[stage_and_sequence][i]);
|
||
}
|
||
}
|
||
|
||
void near dialog_script_stage2_pre_intro_animate(void)
|
||
{
|
||
dialog_box_animate_and_advance(FACE_REIMU_NEUTRAL);
|
||
dialog_box_animate_and_advance(FACE_GENJII);
|
||
}
|
||
|
||
void near dialog_script_stage4_pre_intro_animate(void)
|
||
{
|
||
typedef Array<face_tile_topleft_t, 8> T1;
|
||
extern const T1 STAGE4_PREBOSS_INTRO_FACES;
|
||
const T1 FACES = STAGE4_PREBOSS_INTRO_FACES;
|
||
boxes_animate(FACES);
|
||
}
|
||
|
||
void near dialog_script_stage4_pre_marisa_animate(void)
|
||
{
|
||
typedef Array<face_tile_topleft_t, 11> T1;
|
||
extern const T1 STAGE4_PREBOSS_MARISA_FACES;
|
||
const T1 FACES = STAGE4_PREBOSS_MARISA_FACES;
|
||
boxes_animate(FACES);
|
||
dialog_box_wipe();
|
||
}
|
||
|
||
void near dialog_script_stage4_post_animate(void)
|
||
{
|
||
typedef Array<shiftjis_t near*, 10> T1;
|
||
typedef Array<int /* ACTUAL TYPE: face_tile_topleft_t */, 3> T2;
|
||
typedef Array<int /* ACTUAL TYPE: face_tile_topleft_t */, 5> T3;
|
||
|
||
extern const T1 STAGE4_POSTBOSS_CONTINUED_NUMERALS;
|
||
extern const T2 STAGE4_POSTBOSS_CONTINUED_BEFORE_FACES;
|
||
extern const T3 STAGE4_POSTBOSS_CONTINUED_AFTER_FACES;
|
||
extern const T3 STAGE4_POSTBOSS_NOTCONTINUED_FACES;
|
||
|
||
const T1 CONTINUED_NUMERALS = STAGE4_POSTBOSS_CONTINUED_NUMERALS;
|
||
const T2 CONTINUED_BEFORE_BOX_FACE = STAGE4_POSTBOSS_CONTINUED_BEFORE_FACES;
|
||
const T3 CONTINUED_AFTER_BOX_FACE = STAGE4_POSTBOSS_CONTINUED_AFTER_FACES;
|
||
const T3 NOTCONTINUED_BOX_FACE = STAGE4_POSTBOSS_NOTCONTINUED_FACES;
|
||
|
||
dialog_script_generic_part_animate(DS_POSTBOSS);
|
||
if(resident->continues_used) {
|
||
boxes_animate(CONTINUED_BEFORE_BOX_FACE);
|
||
|
||
// Insert number of continues into the next dialog box
|
||
int digit = (resident->continues_used / 10);
|
||
if(digit) {
|
||
dialog_text[dialog_box_cur][0][8] = CONTINUED_NUMERALS[digit][0];
|
||
dialog_text[dialog_box_cur][0][9] = CONTINUED_NUMERALS[digit][1];
|
||
}
|
||
digit = (resident->continues_used % 10);
|
||
dialog_text[dialog_box_cur][0][10] = CONTINUED_NUMERALS[digit][0];
|
||
dialog_text[dialog_box_cur][0][11] = CONTINUED_NUMERALS[digit][1];
|
||
|
||
// ZUN bloat: Same as boxes_animate().
|
||
for(digit = 0; digit < CONTINUED_AFTER_BOX_FACE.count(); digit++) {
|
||
dialog_box_animate_and_advance(CONTINUED_AFTER_BOX_FACE[digit]);
|
||
}
|
||
} else {
|
||
dialog_box_cur += (
|
||
CONTINUED_BEFORE_BOX_FACE.count() +
|
||
CONTINUED_AFTER_BOX_FACE.count()
|
||
);
|
||
boxes_animate(NOTCONTINUED_BOX_FACE);
|
||
}
|
||
}
|
||
|
||
void near dialog_script_stage5_pre_intro_animate(void)
|
||
{
|
||
dialog_box_animate_and_advance(FACE_REIMU_ANGRY);
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
}
|
||
|
||
void near dialog_script_stage5_pre_unsealed_animate(void)
|
||
{
|
||
typedef Array<face_tile_topleft_t, 22> T1;
|
||
typedef Array<face_tile_topleft_t, 17> T2;
|
||
|
||
extern const T1 STAGE5_PREBOSS_CONTINUED_FACES;
|
||
extern const T2 STAGE5_PREBOSS_NOTCONTINUED_FACES;
|
||
|
||
const T1 CONTINUED_FACES = STAGE5_PREBOSS_CONTINUED_FACES;
|
||
const T2 NOTCONTINUED_FACES = STAGE5_PREBOSS_NOTCONTINUED_FACES;
|
||
|
||
dialog_box_animate_and_advance(FACE_REIMU_ANGRY);
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
|
||
if(resident->continues_used) {
|
||
boxes_animate(CONTINUED_FACES);
|
||
dialog_box_cur += NOTCONTINUED_FACES.count();
|
||
} else {
|
||
dialog_box_cur += CONTINUED_FACES.count();
|
||
boxes_animate(NOTCONTINUED_FACES);
|
||
}
|
||
}
|
||
|
||
void near dialog_script_stage5_pre_winged_animate(void)
|
||
{
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
}
|
||
|
||
void near dialog_script_stage5_form1defeat_animate(void)
|
||
{
|
||
typedef Array<face_tile_topleft_t, 5> T1;
|
||
typedef Array<face_tile_topleft_t, 4> T2;
|
||
|
||
extern const T1 STAGE5_FORM1DEFEAT_CONTINUED_FACES;
|
||
extern const T2 STAGE5_FORM1DEFEAT_NOTCONTINUED_FACES;
|
||
|
||
const T1 CONTINUED_FACES = STAGE5_FORM1DEFEAT_CONTINUED_FACES;
|
||
const T2 NOTCONTINUED_FACES = STAGE5_FORM1DEFEAT_NOTCONTINUED_FACES;
|
||
|
||
if(resident->continues_used) {
|
||
boxes_animate(CONTINUED_FACES);
|
||
// MODDERS: Skipping over the boxes for the non-continued part would
|
||
// have been the sensible thing to do, but the game ends after this
|
||
// sequence anyway.
|
||
// dialog_box_cur += NOTCONTINUED_FACES.count();
|
||
} else {
|
||
dialog_box_cur += CONTINUED_FACES.count();
|
||
boxes_animate(NOTCONTINUED_FACES);
|
||
}
|
||
}
|
||
|
||
void near dialog_script_stage5_flash_animate(void)
|
||
{
|
||
palette_white_out(0);
|
||
snd_se_play_force(5);
|
||
palette_white_in(0);
|
||
}
|
||
|
||
void near dialog_script_stage5_post_animate(void)
|
||
{
|
||
overlay_wipe();
|
||
snd_kaja_func(KAJA_SONG_STOP, 0);
|
||
|
||
// ZUN quirk: These faces never show up on screen. This function is called
|
||
// straight from double-buffered game code with therefore different shown
|
||
// and accessed VRAM pages, without having called dialog_pre() to slide in
|
||
// the box and switch to single-buffered rendering. This could count as a
|
||
// bug if you look at this function in isolation, but the alternative of
|
||
// [FACE_COL_0] would be equally wrong since the face area is supposed to
|
||
// be transparent. In any case, the smiling expression just looks wrong
|
||
// compared to the original game, especially considering Mima's lines in
|
||
// this sequence.
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
dialog_script_stage5_flash_animate();
|
||
frame_delay(10);
|
||
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
frame_delay(30);
|
||
|
||
dialog_script_stage5_flash_animate();
|
||
frame_delay(20);
|
||
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
frame_delay(20);
|
||
|
||
dialog_box_animate_and_advance(FACE_MIMA_SMILE);
|
||
dialog_script_stage5_flash_animate();
|
||
frame_delay(20);
|
||
|
||
dialog_script_stage5_flash_animate();
|
||
frame_delay(20);
|
||
|
||
dialog_script_stage5_flash_animate();
|
||
palette_white_out(3);
|
||
}
|
||
|
||
void near dialog_script_extra_pre_intro_animate(void)
|
||
{
|
||
dialog_box_animate_and_advance(FACE_COL_0);
|
||
dialog_box_animate_and_advance(FACE_REIMU_NEUTRAL);
|
||
dialog_box_animate_and_advance(FACE_COL_0);
|
||
}
|
||
// ----------------------------------
|