mirror of https://github.com/nmlgc/ReC98.git
1026 lines
27 KiB
C++
1026 lines
27 KiB
C++
/* ReC98
|
|
* -----
|
|
* Code segment #1 of TH01's REIIDEN.EXE
|
|
*/
|
|
|
|
#include <float.h>
|
|
#include <new.h>
|
|
#include <process.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "platform.h"
|
|
#include "x86real.h"
|
|
#include "decomp.hpp"
|
|
#include "pc98.h"
|
|
#include "planar.h"
|
|
#include "master.hpp"
|
|
#include "pc98kbd.h"
|
|
#include "shiftjis.hpp"
|
|
#include "th01/rank.h"
|
|
#include "th01/resident.hpp"
|
|
#include "th01/v_colors.hpp"
|
|
#include "th01/core/initexit.hpp"
|
|
#include "th01/core/resstuff.hpp"
|
|
#include "th01/math/area.hpp"
|
|
#include "th01/math/subpixel.hpp"
|
|
#include "th01/hardware/egc.h"
|
|
#include "th01/hardware/frmdelay.h"
|
|
#include "th01/hardware/graph.h"
|
|
#include "th01/hardware/grp_text.hpp"
|
|
#include "th01/hardware/palette.h"
|
|
#include "th01/hardware/text.h"
|
|
#include "th01/hardware/tram_x16.hpp"
|
|
#include "th01/hardware/vplanset.h"
|
|
#include "th01/hardware/ztext.hpp"
|
|
#include "th01/snd/mdrv2.h"
|
|
#include "th01/formats/cfg.hpp"
|
|
#include "th01/formats/grp.h"
|
|
#include "th01/formats/pf.hpp"
|
|
#include "th01/formats/ptn.hpp"
|
|
#include "th01/formats/scoredat.hpp"
|
|
#include "th01/formats/stagedat.hpp"
|
|
#include "th01/hiscore/regist.hpp"
|
|
#include "th01/main/bonus.hpp"
|
|
#include "th01/main/debug.hpp"
|
|
#include "th01/main/entity.hpp"
|
|
#include "th01/main/playfld.hpp"
|
|
#include "th01/main/player/anim.hpp"
|
|
#include "th01/main/player/bomb.hpp"
|
|
#include "th01/main/player/orb.hpp"
|
|
#include "th01/main/player/player.hpp"
|
|
#include "th01/main/player/shot.hpp"
|
|
#include "th01/main/boss/boss.hpp"
|
|
#include "th01/main/boss/entity_a.hpp"
|
|
#include "th01/main/bullet/laser_s.hpp"
|
|
#include "th01/main/bullet/pellet.hpp"
|
|
#include "th01/main/stage/card.hpp"
|
|
#include "th01/main/stage/item.hpp"
|
|
#include "th01/main/stage/palette.hpp"
|
|
#include "th01/main/stage/stages.hpp"
|
|
#include "th01/main/stage/stageobj.hpp"
|
|
#include "th01/main/stage/timer.hpp"
|
|
#include "th01/main/hud/hud.hpp"
|
|
#include "th01/shiftjis/debug.hpp"
|
|
#include "th01/shiftjis/entrance.hpp"
|
|
#include "th01/shiftjis/fns.hpp"
|
|
#include "th01/shiftjis/scoredat.hpp"
|
|
|
|
// Random state that mostly doesn't belong here
|
|
// --------------------------------------------
|
|
|
|
int8_t rank = CFG_RANK_DEFAULT;
|
|
bgm_mode_t bgm_mode = CFG_BGM_MODE_DEFAULT;
|
|
int8_t rem_bombs = CFG_CREDIT_BOMBS_DEFAULT;
|
|
int8_t credit_lives_extra = CFG_CREDIT_LIVES_EXTRA_DEFAULT;
|
|
|
|
int8_t stage_num = 0;
|
|
bool bgm_change_blocked = false;
|
|
static int8_t unused_1 = 0; // ZUN bloat
|
|
const shiftjis_t* RANKS[RANK_COUNT] = RANKS_CAPS;
|
|
bool timer_initialized = false;
|
|
static int8_t unused_2 = 0; // ZUN bloat
|
|
bool first_stage_in_scene = true;
|
|
|
|
#include "th01/hardware/input_mf.cpp"
|
|
|
|
static int8_t unused_3 = 0; // ZUN bloat
|
|
bool player_deflecting = false;
|
|
bool bomb_damaging = false;
|
|
bool player_sliding = false;
|
|
uscore_t score = 0;
|
|
uscore_t score_bonus = 0;
|
|
unsigned long bomb_frames = 0;
|
|
long continues_total = 0;
|
|
static int16_t unused_4 = 0; // ZUN bloat
|
|
bool16 mode_test = false;
|
|
int bomb_doubletap_frames = 0;
|
|
int bomb_doubletap_frames_unused = 0; // ZUN bloat
|
|
bool16 test_damage = false;
|
|
static int unused_5 = 0; // ZUN bloat
|
|
static int unused_6 = 0; // ZUN bloat
|
|
bool16 player_invincible = false;
|
|
static int unused_7 = 0; // ZUN bloat
|
|
orb_velocity_x_t orb_velocity_x = OVX_0;
|
|
int orb_rotation_frame = 0;
|
|
int rem_lives = 4;
|
|
bool16 stage_cleared = false;
|
|
|
|
int8_t bombs_extra_per_life_lost;
|
|
int8_t player_swing_deflection_frames;
|
|
unsigned long frame_rand;
|
|
uint32_t coreleft_prev;
|
|
bool stage_wait_for_shot_to_begin;
|
|
|
|
bool mode_debug;
|
|
unsigned long frames_since_start_of_binary;
|
|
|
|
int player_invincibility_time;
|
|
screen_x_t player_left;
|
|
int cardcombo_cur = 0;
|
|
bool16 orb_in_portal = false;
|
|
int cardcombo_max = 0;
|
|
int extend_next = 1;
|
|
int unnecessary_copy_of_the_initial_value_of_extend_next = 1;
|
|
|
|
screen_x_t orb_cur_left;
|
|
screen_y_t orb_cur_top;
|
|
screen_x_t orb_prev_left = ORB_LEFT_START;
|
|
screen_y_t orb_prev_top = ORB_TOP_START;
|
|
int orb_frames_outside_portal = 0;
|
|
double orb_velocity_y = 0.0;
|
|
double orb_force = 0.0;
|
|
int orb_force_frame;
|
|
|
|
stage_t scene_stage[STAGES_PER_SCENE];
|
|
CPlayerAnim player_48x48;
|
|
CPlayerAnim player_48x32;
|
|
CPellets Pellets;
|
|
CShots Shots;
|
|
static int32_t unused_8; // ZUN bloat
|
|
|
|
struct {
|
|
// Specifies whether PTN_SLOT_STG contains the full set of sprites required
|
|
// for card-flipping stages (`false`), or the trimmed-down version for boss
|
|
// stages (`true`).
|
|
bool has_reduced_sprites;
|
|
|
|
void switch_to_full(void) {
|
|
if(has_reduced_sprites) {
|
|
ptn_free(PTN_SLOT_STG);
|
|
ptn_load(PTN_SLOT_STG, PTN_STG_CARDFLIP_FN);
|
|
has_reduced_sprites = false;
|
|
}
|
|
}
|
|
|
|
void switch_to_reduced(void) {
|
|
if(!has_reduced_sprites) {
|
|
ptn_free(PTN_SLOT_STG);
|
|
ptn_load(PTN_SLOT_STG, PTN_STG_BOSS_FN);
|
|
has_reduced_sprites = true;
|
|
}
|
|
}
|
|
} ptn_slot_stg = { false };
|
|
// --------------------------------------------
|
|
|
|
inline void bomb_doubletap_update(uint8_t& pressed, uint8_t& other) {
|
|
if(bomb_doubletap_frames < BOMB_DOUBLETAP_WINDOW) {
|
|
pressed++;
|
|
} else {
|
|
bomb_doubletap_frames = 0;
|
|
pressed = 1;
|
|
other = 0;
|
|
}
|
|
}
|
|
|
|
void input_sense(bool16 reset_repeat)
|
|
{
|
|
static uint8_t input_prev[16];
|
|
int group_1, group_2, group_3, group_4;
|
|
|
|
if(reset_repeat == true) {
|
|
input_prev[0] = 0;
|
|
input_prev[1] = 0;
|
|
input_prev[2] = 0;
|
|
input_prev[3] = 0;
|
|
input_prev[8] = 0;
|
|
input_prev[9] = 0;
|
|
input_prev[10] = 0;
|
|
input_prev[11] = 0;
|
|
input_prev[4] = 0;
|
|
input_prev[5] = 0;
|
|
input_prev[6] = 0;
|
|
input_prev[7] = 0;
|
|
input_prev[12] = 0;
|
|
input_prev[13] = 0;
|
|
// Yup, no reset for 14 or 15.
|
|
input_bomb = 0;
|
|
return;
|
|
}
|
|
#define bomb_doubletap_shot input_prev[12]
|
|
#define bomb_doubletap_strike input_prev[13]
|
|
|
|
group_1 = key_sense(7);
|
|
group_2 = key_sense(5);
|
|
group_3 = key_sense(8);
|
|
group_4 = key_sense(9);
|
|
group_1 |= key_sense(7);
|
|
group_2 |= key_sense(5);
|
|
group_3 |= key_sense(8);
|
|
group_4 |= key_sense(9);
|
|
|
|
input_onchange_bool_2(0, 8,
|
|
input_up, (group_1 & K7_ARROW_UP), (group_3 & K8_NUM_8)
|
|
);
|
|
input_onchange_bool_2(1, 9,
|
|
input_down, (group_1 & K7_ARROW_DOWN), (group_4 & K9_NUM_2)
|
|
);
|
|
input_onchange_flag_2(2, 10,
|
|
input_lr, INPUT_LEFT, (group_1 & K7_ARROW_LEFT), (group_3 & K8_NUM_4)
|
|
);
|
|
input_onchange_flag_2(3, 11,
|
|
input_lr, INPUT_RIGHT, (group_1 & K7_ARROW_RIGHT), (group_4 & K9_NUM_6)
|
|
);
|
|
input_onchange(4, (group_2 & K5_Z), {
|
|
input_shot = true;
|
|
bomb_doubletap_update(bomb_doubletap_shot, bomb_doubletap_strike);
|
|
} else {
|
|
input_shot = false;
|
|
});
|
|
input_onchange(5, (group_2 & K5_X), {
|
|
input_strike = true;
|
|
bomb_doubletap_update(bomb_doubletap_strike, bomb_doubletap_shot);
|
|
} else {
|
|
input_strike = false;
|
|
});
|
|
|
|
input_pause_ok_sense(6, 7, group_1, group_2);
|
|
|
|
if(
|
|
(bomb_doubletap_strike >= 2 && bomb_doubletap_shot >= 2) ||
|
|
((input_lr == INPUT_LEFT_RIGHT) && input_shot)
|
|
) {
|
|
input_bomb = true;
|
|
bomb_doubletap_strike = 0;
|
|
bomb_doubletap_shot = 0;
|
|
}
|
|
|
|
if(mode_test == true) {
|
|
group_1 = key_sense(6);
|
|
group_1 |= key_sense(6);
|
|
|
|
// ZUN bug: debug_mem() itself renders a sub-screen in a blocking way,
|
|
// and senses input after a 3-frame delay, thus recursing back into
|
|
// this function. Therefore, debug_mem() will also be recursively
|
|
// called for every 3 frames you're holding this key.
|
|
// [input_prev[14]], which is supposed to prevent that, isn't set
|
|
// until debug_mem() returns, making this variable entirely pointless.
|
|
input_onchange(14, (group_1 & K6_ROLL_UP), {
|
|
input_mem_enter = true;
|
|
debug_mem();
|
|
} else {
|
|
input_mem_enter = false;
|
|
});
|
|
// And since this works as intended and only sets [input_mem_leave] to
|
|
// true on the first non-repeated key down event, you need to actually
|
|
// press and release this key once for every call to debug_mem() to get
|
|
// back into the game - even though debug_show_game() will make it
|
|
// appear as if you're already back in the game.
|
|
input_onchange(15, (group_1 & K6_ROLL_DOWN), {
|
|
input_mem_leave = true;
|
|
debug_show_game();
|
|
} else {
|
|
input_mem_leave = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
#include "th01/hardware/input_rs.cpp"
|
|
#include "th01/hardware/tram_x16.cpp"
|
|
|
|
// Largely copy-pasted from harryup_animate().
|
|
void pascal stage_num_animate(unsigned int stage_num)
|
|
{
|
|
ushiftjis_kanji_amount_t x;
|
|
upixel_t glyph_y;
|
|
TRAMx16Row<dots_t(GLYPH_HALF_W)> row;
|
|
TRAMCursor tram_cursor;
|
|
unsigned int stage_num_local = stage_num;
|
|
unsigned int i;
|
|
REGS in;
|
|
StupidBytewiseWrapperAround<font_glyph_ank_8x16_t> glyphs[7];
|
|
|
|
fontrom_get(in, glyphs[0].t, 'S');
|
|
fontrom_get(in, glyphs[1].t, 'T');
|
|
fontrom_get(in, glyphs[2].t, 'A');
|
|
fontrom_get(in, glyphs[3].t, 'G');
|
|
fontrom_get(in, glyphs[4].t, 'E');
|
|
// Yes, these are technically fontrom_get() calls as well, and were just
|
|
// inlined for code generation reasons.
|
|
int18h_14h(in, glyphs[5].t, (0x8000 + '0' + (stage_num_local / 10)));
|
|
int18h_14h(in, glyphs[6].t, (0x8000 + '0' + (stage_num_local % 10)));
|
|
|
|
tram_cursor.rewind_to_topleft();
|
|
tram_cursor.putkanji_for_5_rows(' ', TX_BLACK);
|
|
|
|
glyph_y = offsetof(font_glyph_ank_8x16_t, dots);
|
|
while(glyph_y <= (sizeof(font_glyph_ank_8x16_t) - 1)) {
|
|
for(i = 0; i < 5; i++) {
|
|
tram_x16_row_put_red(row, tram_cursor, x, glyphs[i].byte[glyph_y]);
|
|
}
|
|
// 5 halfwidth glyphs scaled by a factor of 16 just happen to exactly
|
|
// fit into one TRAM row, so we're already at the next one here.
|
|
glyph_y++;
|
|
}
|
|
tram_cursor.putkanji_until_end(' ', TX_BLACK);
|
|
|
|
frame_delay(35);
|
|
|
|
tram_cursor.rewind_to_topleft();
|
|
tram_cursor.putkanji_for_5_rows(' ', TX_BLACK);
|
|
|
|
glyph_y = offsetof(font_glyph_ank_8x16_t, dots);
|
|
while(glyph_y <= (sizeof(font_glyph_ank_8x16_t) - 1)) {
|
|
tram_x16_put_center_margin(tram_cursor, x, TX_BLACK);
|
|
for(i = 5; i < 7; i++) {
|
|
tram_x16_row_put_red(row, tram_cursor, x, glyphs[i].byte[glyph_y]);
|
|
}
|
|
tram_x16_put_center_margin(tram_cursor, x, TX_BLACK);
|
|
glyph_y++;
|
|
}
|
|
tram_cursor.putkanji_until_end(' ', TX_BLACK);
|
|
|
|
frame_delay(35);
|
|
z_text_clear_inlined();
|
|
}
|
|
|
|
void load_and_init_stuff_used_in_all_stages(void)
|
|
{
|
|
int i;
|
|
|
|
scoredat_load_hiscore();
|
|
hud_bg_load("mask.grf");
|
|
player_48x48.load("miko_ac.bos");
|
|
player_48x32.load("miko_ac2.bos");
|
|
ptn_load(PTN_SLOT_STG, PTN_STG_CARDFLIP_FN);
|
|
ptn_load(PTN_SLOT_MIKO, "miko.ptn");
|
|
ptn_new(PTN_SLOT_BG_HUD, ((PTN_BG_last - PTN_BG_first) + 1));
|
|
bomb_kuji_load();
|
|
shootout_lasers_init(i);
|
|
ptn_slot_stg.has_reduced_sprites = false;
|
|
}
|
|
|
|
void stage_entrance(int stage_id, const char* bg_fn, bool16 clear_vram_page_0)
|
|
{
|
|
int x;
|
|
int y;
|
|
|
|
if(first_stage_in_scene == true) {
|
|
text_fill_black(x, y);
|
|
text_color_reset();
|
|
|
|
if(strcmp(bg_fn, "empty.grf")) {
|
|
grp_put_palette_show(bg_fn);
|
|
}
|
|
stage_palette_set(z_Palettes);
|
|
|
|
// Copy the raw background image to page 1, so that
|
|
// stageobjs_init_and_render() can snap the correct backgrounds.
|
|
graph_copy_accessed_page_to_other();
|
|
} else {
|
|
graph_accesspage_func(1);
|
|
graph_copy_accessed_page_to_other();
|
|
graph_accesspage_func(0);
|
|
|
|
// Keep the player on screen during stage_num_animate()
|
|
player_put_default();
|
|
}
|
|
|
|
stageobjs_init_and_render(stage_id); // rendered to page 0
|
|
|
|
if(first_stage_in_scene == true) {
|
|
graph_copy_accessed_page_to_other(); // 0 → 1, with new stage objects
|
|
} else if(first_stage_in_scene == false) {
|
|
// ZUN bloat: This entire function would not have been necessary if ZUN
|
|
// just rendered the stage objects to page 1 and then always copied the
|
|
// entire page, not just if [first_stage_in_scene] is true.
|
|
stageobjs_copy_0_to_1(stage_id);
|
|
|
|
// ZUN bloat: Already did this above.
|
|
graph_accesspage_func(0);
|
|
graph_accesspage_func(1);
|
|
graph_accesspage_func(0);
|
|
player_put_default();
|
|
|
|
items_bomb_render();
|
|
items_point_render();
|
|
}
|
|
if(clear_vram_page_0) {
|
|
z_graph_clear();
|
|
}
|
|
if(stage_resets_game_state(stage_id)) {
|
|
touhou_reiiden_animate();
|
|
}
|
|
stage_num_animate(stage_num);
|
|
}
|
|
|
|
#include "th01/main/player/bomb.cpp"
|
|
#include "th01/main/stage/palette.cpp"
|
|
#include "th01/main/player/inv_spr.cpp"
|
|
#include "th01/main/player/orb.cpp"
|
|
#include "th01/main/hud/menu.cpp"
|
|
#include "th01/main/player/gameover.cpp"
|
|
#include "th01/main/extend.cpp"
|
|
#include "th01/main/debug.cpp"
|
|
|
|
bool16 stageobj_bgs_free_wrap(void)
|
|
{
|
|
return stageobj_bgs_free();
|
|
}
|
|
|
|
// ZUN bloat: This function is only ever (meaningfully) called before process
|
|
// termination when the standard library heap is destroyed anyway. You might
|
|
// argue that it's cleaner to free all memory, but then, why doesn't it free
|
|
// PTN_SLOT_MIKO?
|
|
void graphics_free_redundant_and_incomplete(void)
|
|
{
|
|
stageobj_bgs_free();
|
|
for(int i = PTN_SLOT_BOSS_1; i < PTN_SLOT_COUNT; i++) {
|
|
ptn_free(static_cast<main_ptn_slot_t>(i));
|
|
if(i < BOS_ENTITY_SLOT_COUNT) {
|
|
bos_entity_free(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void error_resident_invalid(void)
|
|
{
|
|
printf(ERROR_RESIDENT_INVALID);
|
|
}
|
|
|
|
void pellet_destroy_score_delta_commit(void)
|
|
{
|
|
score += pellet_destroy_score_delta;
|
|
hud_score_and_cardcombo_render();
|
|
pellet_destroy_score_delta = 0;
|
|
}
|
|
|
|
int8_t boss_id = BID_NONE; // ACTUAL TYPE: boss_id_t
|
|
|
|
void boss_free(void)
|
|
{
|
|
switch(boss_id) {
|
|
case BID_SINGYOKU: singyoku_free(); break;
|
|
case BID_YUUGENMAGAN: yuugenmagan_free(); break;
|
|
case BID_MIMA: mima_free(); break;
|
|
case BID_KIKURI: kikuri_free(); break;
|
|
case BID_ELIS: elis_free(); break;
|
|
case BID_SARIEL: sariel_free(); break;
|
|
case BID_KONNGARA: konngara_free(); break;
|
|
}
|
|
}
|
|
|
|
inline void debug_startup_delay(const char* str) {
|
|
puts(str);
|
|
debug_vars();
|
|
frame_delay(40);
|
|
}
|
|
|
|
inline void debug_startup_delay() {
|
|
debug_vars();
|
|
frame_delay(40);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
extern bool stage_wait_for_shot_to_begin;
|
|
|
|
int stage_id = 0;
|
|
int bgm_reload_and_play_if_0 = 0;
|
|
bool16 clear_vram_page_0;
|
|
bool16 quit = false;
|
|
int scene_id;
|
|
char* grp_fn;
|
|
int pellet_speed_raise_cycle;
|
|
int stage_id_copy;
|
|
char bgm_fn[16];
|
|
|
|
if(!mdrv2_resident()) {
|
|
error_resident_invalid();
|
|
return 1;
|
|
}
|
|
if(resident_stuff_get(
|
|
rank,
|
|
bgm_mode,
|
|
bombs_extra_per_life_lost, // ZUN bloat: Supposed to be [rem_bombs]...
|
|
credit_lives_extra,
|
|
frame_rand,
|
|
continues_total,
|
|
stage_id
|
|
) == 1) {
|
|
error_resident_invalid();
|
|
return 1;
|
|
}
|
|
|
|
// ZUN bloat: ...and the variable is hardcoded here anyway.
|
|
bombs_extra_per_life_lost = 1;
|
|
|
|
score = resident->score;
|
|
extend_next = ((resident->score / SCORE_PER_EXTEND) + 1);
|
|
srand(frame_rand);
|
|
game_init();
|
|
key_start();
|
|
|
|
// Disable all FPU exceptions
|
|
_control87(MCW_EM, MCW_EM);
|
|
|
|
route = resident->route;
|
|
if(resident->debug_mode != DM_OFF) {
|
|
if(resident->stage_id == 0) {
|
|
puts(STAGESELECT_TITLE "\n");
|
|
|
|
// ZUN quirk: Does not prevent negative values from being entered.
|
|
// Might be seen as a bug as well, but the resulting glitch stages
|
|
// have a consistent appearance, and can therefore be considered
|
|
// canon...
|
|
puts(STAGESELECT_STAGE);
|
|
scanf("%d", &stage_id);
|
|
|
|
// ZUN landmine: Reinterprets the 8-bit pointer &[route] as a 16-bit
|
|
// pointer, which will overwrite the byte that follows in memory.
|
|
// Luckily, it just hits the low byte of SinGyoku's phase frame,
|
|
// which is set to 0 in that fight's entrance animation.
|
|
puts(STAGESELECT_ROUTE);
|
|
scanf("%d", &route);
|
|
|
|
if(!stage_on_route(stage_id)) {
|
|
route = ROUTE_MAKAI;
|
|
} else {
|
|
if(route > ROUTE_JIGOKU) {
|
|
route = ROUTE_JIGOKU;
|
|
} else if(route < ROUTE_MAKAI) {
|
|
route = ROUTE_MAKAI;
|
|
}
|
|
}
|
|
}
|
|
if(resident->debug_mode == DM_TEST) {
|
|
puts(DM_TEST_STARTING "\n");
|
|
mode_test = true;
|
|
}
|
|
if(resident->debug_mode == DM_FULL) {
|
|
puts(DM_FULL_STARTING "\n");
|
|
mode_debug = true;
|
|
mode_test = true;
|
|
}
|
|
}
|
|
|
|
// ZUN bloat: Unnecessary cast.
|
|
if(static_cast<int>(bgm_mode) == BGM_MODE_MDRV2) {
|
|
mdrv2_enable_if_board_installed();
|
|
}
|
|
scene_id = (
|
|
(stage_id < (1 * STAGES_PER_SCENE)) ? 0 :
|
|
(stage_id < (2 * STAGES_PER_SCENE)) ? ((route == ROUTE_MAKAI) ? 1 : 2) :
|
|
(stage_id < (3 * STAGES_PER_SCENE)) ? ((route == ROUTE_MAKAI) ? 3 : 4) :
|
|
(stage_id < (4 * STAGES_PER_SCENE)) ? ((route == ROUTE_MAKAI) ? 5 : 6) :
|
|
|
|
// Stages 21-25 confirmed to have been planned as an Extra Stage?
|
|
// Seems way too specific to just be meant as a fallback.
|
|
7
|
|
);
|
|
set_new_handler(out_of_memory_exit);
|
|
arc_key = ARC_KEY;
|
|
arc_load(ARC_FN);
|
|
vram_planes_set();
|
|
scene_init_and_load(scene_id);
|
|
|
|
if(mode_debug == true) {
|
|
debug_startup_delay();
|
|
}
|
|
|
|
// ZUN bloat: We just started the program, these are still empty!
|
|
graphics_free_redundant_and_incomplete();
|
|
|
|
rem_lives = resident->rem_lives;
|
|
rem_bombs = resident->rem_bombs;
|
|
player_left = PLAYER_LEFT_START;
|
|
|
|
if((bgm_mode != BGM_MODE_OFF) && resident->snd_need_init) {
|
|
mdrv2_bgm_load("init.mdt");
|
|
mdrv2_bgm_play();
|
|
mdrv2_se_load(SE_FN);
|
|
// Kind of pointless if the flag isn't reset?
|
|
}
|
|
|
|
if(mode_debug == true) {
|
|
debug_startup_delay("2 :");
|
|
}
|
|
|
|
coreleft_prev = coreleft();
|
|
load_and_init_stuff_used_in_all_stages();
|
|
z_graph_init();
|
|
graph_accesspage_func(0);
|
|
z_graph_clear();
|
|
card_flip_cycle = (rand() % CARD_FLIP_CYCLE_INITIAL_MAX);
|
|
first_stage_in_scene = true;
|
|
stage_wait_for_shot_to_begin = true;
|
|
bgm_reload_and_play_if_0 = (!resident->snd_need_init ? 1 : 0);
|
|
|
|
// Stage loop
|
|
while(1) {
|
|
resident->stage_id = stage_id;
|
|
resident->route = route;
|
|
|
|
resident->score = score;
|
|
resident->continues_total = continues_total;
|
|
Pellets.unput_and_reset_nonclouds();
|
|
Shots.unput_and_reset();
|
|
|
|
grp_fn = default_grp_fn;
|
|
|
|
boss_id = BID_NONE;
|
|
unused_boss_stage_flag = false;
|
|
player_invincible = false;
|
|
player_invincibility_time = 0;
|
|
|
|
switch(stage_id) {
|
|
case ((0 * STAGES_PER_SCENE) + BOSS_STAGE):
|
|
boss_id = BID_SINGYOKU;
|
|
unused_boss_stage_flag = true;
|
|
strcpy(grp_fn, "boss1.grp");
|
|
strcpy(bgm_fn, "positive.mdt");
|
|
clear_vram_page_0 = false;
|
|
singyoku_load();
|
|
Pellets.unknown_seven = 7;
|
|
break;
|
|
|
|
case ((1 * STAGES_PER_SCENE) + BOSS_STAGE):
|
|
boss_id = (BID_YUUGENMAGAN + route);
|
|
unused_boss_stage_flag = true;
|
|
if(route == ROUTE_MAKAI) {
|
|
strcpy(bgm_fn, "LEGEND.mdt");
|
|
strcpy(grp_fn, "boss2.grp");
|
|
yuugenmagan_load();
|
|
} else {
|
|
strcpy(bgm_fn, "LEGEND.mdt");
|
|
strcpy(grp_fn, "boss3.grp");
|
|
mima_load();
|
|
}
|
|
clear_vram_page_0 = false;
|
|
Pellets.unknown_seven = 7;
|
|
break;
|
|
|
|
case ((2 * STAGES_PER_SCENE) + BOSS_STAGE):
|
|
boss_id = (BID_KIKURI + (ROUTE_JIGOKU - route));
|
|
unused_boss_stage_flag = true;
|
|
if(route == ROUTE_JIGOKU) {
|
|
strcpy(bgm_fn, "kami.mdt");
|
|
strcpy(grp_fn, "boss4.grp");
|
|
kikuri_load();
|
|
clear_vram_page_0 = true;
|
|
Pellets.unknown_seven = 7;
|
|
} else {
|
|
strcpy(bgm_fn, "kami2.mdt");
|
|
strcpy(grp_fn, "boss5.grp");
|
|
elis_load();
|
|
clear_vram_page_0 = false;
|
|
Pellets.unknown_seven = 7;
|
|
}
|
|
break;
|
|
|
|
case ((3 * STAGES_PER_SCENE) + BOSS_STAGE):
|
|
boss_id = (BID_SARIEL + route);
|
|
unused_boss_stage_flag = true;
|
|
|
|
// ZUN bloat: Both bosses have their own BGM playback calls.
|
|
if(route == ROUTE_MAKAI) {
|
|
strcpy(bgm_fn, "tensi.mdt");
|
|
sariel_load_and_init();
|
|
clear_vram_page_0 = true;
|
|
Pellets.unknown_seven = 7;
|
|
} else {
|
|
strcpy(bgm_fn, "alice.mdt");
|
|
konngara_init();
|
|
clear_vram_page_0 = false;
|
|
Pellets.unknown_seven = 7;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
strcpy(bgm_fn, default_bgm_fn);
|
|
clear_vram_page_0 = false;
|
|
break;
|
|
}
|
|
|
|
if(boss_id != BID_NONE) {
|
|
bgm_reload_and_play_if_0 = 0;
|
|
first_stage_in_scene = true;
|
|
items_reset();
|
|
ptn_slot_stg.switch_to_reduced();
|
|
} else {
|
|
ptn_slot_stg.switch_to_full();
|
|
}
|
|
|
|
if(mode_debug == true) {
|
|
debug_startup_delay("3 :");
|
|
}
|
|
|
|
stage_num = (stage_id + 1);
|
|
|
|
if((boss_id != BID_SARIEL) && (boss_id != BID_KONNGARA)) {
|
|
stage_entrance(
|
|
(stage_id % STAGES_PER_SCENE), grp_fn, clear_vram_page_0
|
|
);
|
|
} else if(boss_id == BID_KONNGARA) {
|
|
konngara_load_and_entrance(0);
|
|
} else if(boss_id == BID_SARIEL) {
|
|
sariel_entrance(0);
|
|
}
|
|
unnecessary_copy_of_the_initial_value_of_extend_next = extend_next;
|
|
hud_bg_snap_and_put();
|
|
|
|
cardcombo_max = 0;
|
|
orb_in_portal = false;
|
|
bomb_frames = 0;
|
|
Pellets.unknown_seven = 7;
|
|
|
|
if(mode_debug == true) {
|
|
debug_startup_delay();
|
|
}
|
|
|
|
frames_since_start_of_binary = 0;
|
|
orb_cur_left = ORB_LEFT_START;
|
|
orb_cur_top = ORB_TOP_START;
|
|
|
|
if(stage_resets_game_state(stage_id)) {
|
|
player_left = PLAYER_LEFT_START;
|
|
}
|
|
orb_force = ORB_FORCE_START;
|
|
orb_force_frame = 0;
|
|
orb_velocity_x = ORB_VELOCITY_X_START;
|
|
orb_prev_left = ORB_LEFT_START;
|
|
orb_prev_top = ORB_TOP_START;
|
|
player_deflecting = false;
|
|
bomb_damaging = false;
|
|
cardcombo_cur = 0;
|
|
|
|
// Life loop
|
|
while(1) {
|
|
player_reset();
|
|
player_put_default();
|
|
orb_put_default();
|
|
unused_5 = 0;
|
|
input_lr = INPUT_NONE;
|
|
input_shot = false;
|
|
input_ok = false;
|
|
paused = false;
|
|
hud_score_and_cardcombo_render();
|
|
bomb_doubletap_frames = (BOMB_DOUBLETAP_WINDOW * 3);
|
|
bomb_doubletap_frames_unused = (BOMB_DOUBLETAP_WINDOW * 3);
|
|
obstacles_update_and_render(true);
|
|
|
|
// Play stage BGM. Why inside this loop though? The code would be
|
|
// much simpler if that was part of the stage loop...
|
|
if(
|
|
(boss_id != BID_SARIEL) &&
|
|
(boss_id != BID_KONNGARA) &&
|
|
(bgm_change_blocked == false) &&
|
|
(bgm_reload_and_play_if_0 == 0)
|
|
) {
|
|
mdrv2_bgm_load(bgm_fn);
|
|
mdrv2_bgm_play();
|
|
}
|
|
|
|
input_reset_sense();
|
|
|
|
if(player_invincibility_time > 1) {
|
|
player_invincible = true;
|
|
}
|
|
if(boss_id != BID_NONE) {
|
|
// ZUN bloat: Moving these select entrance animations outside
|
|
// the main gameplay loop has no discernible effect.
|
|
switch(boss_id) {
|
|
case BID_SINGYOKU:
|
|
singyoku_main();
|
|
break;
|
|
|
|
case BID_MIMA:
|
|
mima_main();
|
|
break;
|
|
|
|
case BID_KIKURI:
|
|
// ZUN bloat: Kikuri has a blocking entrance animation as
|
|
// well, during which Reimu isn't even visible? And once
|
|
// it's done, this value is immediately re-sensed with the
|
|
// actual key state before Kikuri's code gets to execute,
|
|
// as this assignment completely bypasses the [input_prev]
|
|
// mechanism.
|
|
input_strike = false;
|
|
break;
|
|
}
|
|
} else if(stage_wait_for_shot_to_begin == true) {
|
|
while(!input_shot) {
|
|
input_sense(false);
|
|
|
|
// Allow the player to move before starting the stage
|
|
player_unput_update_render();
|
|
|
|
frame_delay(1);
|
|
bomb_frames++;
|
|
}
|
|
}
|
|
|
|
stage_wait_for_shot_to_begin = false;
|
|
input_shot = false;
|
|
timer_initialized = true;
|
|
srand(frame_rand);
|
|
bomb_doubletap_frames = BOMB_DOUBLETAP_WINDOW;
|
|
first_stage_in_scene = false;
|
|
pellet_speed_raise_cycle = 3000; // ZUN bloat: Reassigned below
|
|
|
|
// Main gameplay loop
|
|
while(!player_is_hit) {
|
|
frame_rand++;
|
|
pellet_speed_raise_cycle = (
|
|
1800 - (rem_lives * 200) - (rem_bombs * 50)
|
|
);
|
|
if((frame_rand % pellet_speed_raise_cycle) == 0) {
|
|
pellet_speed_raise(0.025f);
|
|
}
|
|
input_sense(false);
|
|
|
|
if(player_invincibility_time > 1) {
|
|
player_invincibility_time--;
|
|
player_invincible = true;
|
|
} else if(player_invincibility_time == 1) {
|
|
player_invincible = false;
|
|
player_invincibility_time = 0;
|
|
}
|
|
|
|
player_unput_update_render();
|
|
items_unput_update_render();
|
|
frames_since_start_of_binary++;
|
|
orb_force_frame++;
|
|
bomb_frames++;
|
|
bomb_doubletap_frames++;
|
|
if((frame_rand & 3) == 0) {
|
|
timer_tick_and_put();
|
|
}
|
|
|
|
if(mode_test == true) {
|
|
if(input_ok) {
|
|
test_damage = true;
|
|
}
|
|
if(input_down) {
|
|
player_is_hit = true;
|
|
rem_lives = 0;
|
|
}
|
|
}
|
|
|
|
invincibility_sprites_update_and_render(player_invincible);
|
|
|
|
switch(boss_id) {
|
|
case BID_SINGYOKU: singyoku_main(); break;
|
|
case BID_YUUGENMAGAN: yuugenmagan_main(); break;
|
|
case BID_MIMA: mima_main(); break;
|
|
case BID_KIKURI: kikuri_main(); break;
|
|
case BID_ELIS: elis_main(); break;
|
|
case BID_SARIEL: sariel_main(); break;
|
|
case BID_KONNGARA: konngara_main(); break;
|
|
}
|
|
|
|
// ZUN bloat: Why no loop?
|
|
static_assert(SHOOTOUT_LASER_COUNT == 10);
|
|
shootout_lasers[0].update_hittest_and_render();
|
|
shootout_lasers[1].update_hittest_and_render();
|
|
shootout_lasers[2].update_hittest_and_render();
|
|
shootout_lasers[3].update_hittest_and_render();
|
|
shootout_lasers[4].update_hittest_and_render();
|
|
shootout_lasers[5].update_hittest_and_render();
|
|
shootout_lasers[6].update_hittest_and_render();
|
|
shootout_lasers[7].update_hittest_and_render();
|
|
shootout_lasers[8].update_hittest_and_render();
|
|
shootout_lasers[9].update_hittest_and_render();
|
|
|
|
orb_and_pellets_and_stage_unput_update_render__vsync_wait(
|
|
stage_id
|
|
);
|
|
|
|
if(paused == true) {
|
|
quit = pause_menu();
|
|
}
|
|
if(quit == true) {
|
|
goto op;
|
|
}
|
|
|
|
score_extend_update_and_render();
|
|
|
|
if(mode_debug == true) {
|
|
debug_vars();
|
|
}
|
|
|
|
if(game_cleared == true) {
|
|
graphics_free_redundant_and_incomplete();
|
|
resident->end_flag = static_cast<end_sequence_t>(
|
|
ES_MAKAI + route
|
|
);
|
|
resident->score = score;
|
|
if(score > resident->score_highest) {
|
|
resident->score_highest = score;
|
|
}
|
|
frame_delay(120);
|
|
game_switch_binary();
|
|
execl(BINARY_END, BINARY_END, nullptr);
|
|
}
|
|
|
|
// ZUN quirk: Placing this after the [game_cleared] branch robs
|
|
// the player of the pellet destroy points gained on the last
|
|
// frame of a final boss.
|
|
if(pellet_destroy_score_delta) {
|
|
pellet_destroy_score_delta_commit();
|
|
}
|
|
}
|
|
// At this point, the player either lost a life or cleared a
|
|
// non-final stage.
|
|
timer_initialized = false;
|
|
z_vsync_wait_and_scrollup(0);
|
|
resident->rand = frame_rand;
|
|
test_damage = false;
|
|
bomb_frames = 200;
|
|
if((rem_lives <= 0) || stage_cleared) {
|
|
break;
|
|
}
|
|
|
|
// Regular, non-Game Over life loss is the only remaining option
|
|
// here. Perform the necessary updates and animations and restart
|
|
// the life loop.
|
|
mdrv2_se_play(5);
|
|
resident->rem_lives--;
|
|
rem_lives--;
|
|
player_miss_animate_and_update();
|
|
player_is_hit = false;
|
|
bgm_reload_and_play_if_0++;
|
|
player_invincibility_time = PLAYER_MISS_INVINCIBILITY_FRAMES;
|
|
}
|
|
// At this point, the player either cleared a non-final stage or
|
|
// game-overed.
|
|
if(stage_cleared == true) {
|
|
stage_cleared = false;
|
|
player_is_hit = false;
|
|
if(boss_id != BID_NONE) {
|
|
boss_free();
|
|
stage_wait_for_shot_to_begin = true;
|
|
first_stage_in_scene = true;
|
|
}
|
|
bgm_reload_and_play_if_0++;
|
|
stage_id++;
|
|
resident->stage_id = stage_id;
|
|
if(boss_id != BID_NONE) {
|
|
bgm_reload_and_play_if_0 = 0;
|
|
totle_animate(stage_id);
|
|
} else {
|
|
stagebonus_animate(stage_id);
|
|
}
|
|
if(resident->pellet_speed < 0) {
|
|
resident->pellet_speed = 0;
|
|
}
|
|
|
|
// Since [stage_id] already corresponds to the next stage, both
|
|
// checks are necessary to handle switching into and out of a boss.
|
|
if(stage_is_boss(stage_id) || (boss_id != BID_NONE)) {
|
|
resident->score = score;
|
|
resident->rem_lives = rem_lives;
|
|
resident->snd_need_init = true;
|
|
resident->route = route;
|
|
mdrv2_bgm_fade_out_nonblock();
|
|
resident->rem_bombs = rem_bombs;
|
|
game_switch_binary();
|
|
execl(BINARY_MAIN, BINARY_MAIN, nullptr);
|
|
}
|
|
orb_in_portal = false;
|
|
if(boss_id == BID_NONE) {
|
|
graph_accesspage_func(1);
|
|
stageobj_bgs_put_all();
|
|
graph_accesspage_func(0);
|
|
}
|
|
stageobj_bgs_free_wrap();
|
|
cards.free();
|
|
obstacles.free();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Game Over
|
|
cards.free();
|
|
obstacles.free();
|
|
bgm_reload_and_play_if_0++; // ZUN bloat
|
|
player_gameover_animate();
|
|
Shots.unput_and_reset();
|
|
Pellets.unput_and_reset_nonclouds();
|
|
stageobj_bgs_free_wrap();
|
|
extend_next = 1;
|
|
if(boss_id != BID_NONE) {
|
|
boss_free();
|
|
first_stage_in_scene = true; // ZUN bloat
|
|
}
|
|
resident->score = score;
|
|
|
|
regist_menu(score, (stage_id + 1), (
|
|
!stage_on_route(stage_id) ? SCOREDAT_ROUTE_SHRINE :
|
|
(route == ROUTE_MAKAI) ? SCOREDAT_ROUTE_MAKAI : SCOREDAT_ROUTE_JIGOKU
|
|
));
|
|
|
|
resident->stage_id = stage_id;
|
|
continue_menu();
|
|
|
|
op:
|
|
graphics_free_redundant_and_incomplete();
|
|
boss_free();
|
|
game_switch_binary();
|
|
key_end();
|
|
arc_free();
|
|
execl(BINARY_OP, BINARY_OP, nullptr);
|
|
return 0;
|
|
}
|