/// Stage 1 Boss - Sara /// ------------------- #pragma option -zCB1_UPDATE_TEXT -zPmain_03 #include "th01/math/dir.hpp" #include "th02/formats/tile.hpp" #include "th04/formats/bb.h" #include "th04/math/randring.hpp" #include "th04/snd/snd.h" #include "th04/main/pattern.hpp" #include "th04/main/bg.hpp" #include "th04/main/frames.h" #include "th04/main/gather.hpp" #include "th04/main/homing.hpp" #include "th04/main/hud/hud.hpp" #include "th04/main/tile/bb.hpp" #include "th05/sprites/main_pat.h" #include "th05/main/boss/bosses.hpp" #include "th05/main/bullet/laser.hpp" // Constants // --------- static const int PHASE_2_PATTERN_START_FRAME = 16; static const int PHASE_3_PATTERN_START_FRAME = 32; enum sara_colors_t { COL_GATHER_1 = 9, COL_GATHER_2 = 8, }; // Always denotes the last phase that ends with that amount of HP. enum sara_hp_t { HP_TOTAL = 4650, HP_PHASE_2_END = 2550, HP_PHASE_3_END = 450, HP_PHASE_4_END = 0, }; // --------- // State // ----- union sara_state_t { struct { /* -------------------- */ int8_t _unused_1[9]; uint8_t fly_delay; uint8_t pattern_prev; /* -------------------- */ int8_t _unused_2; /* -------------------- */ int8_t _unused_3; unsigned char angle_stacks; unsigned char angle_clockwise; unsigned char angle_counterclockwise; } phase_2; struct { /* -------------------- */ int8_t _unused_1[9]; uint8_t pattern_duration; uint8_t pattern_prev; /* -------------------- */ int8_t _unused_2; /* -------------------- */ int8_t _unused_3; SubpixelLength8 ring_speed; unsigned char angle_counterclockwise; unsigned char angle_clockwise; } phase_3; struct { /* -------------------- */ int8_t _unused[13]; uint8_t laser_angle_interval; uint8_t random_ball_count; unsigned char spread_angle; } phase_4; }; #define state reinterpret_cast(boss_statebyte) #define phase_2_3_pattern sara_phase_2_3_pattern extern pattern_loop_func_t phase_2_3_pattern; // ----- static void near phase_2_with_pattern(void) { if(boss.phase_frame < PHASE_2_PATTERN_START_FRAME) { gather_add_only_3stack( (boss.phase_frame - 1), COL_GATHER_1, COL_GATHER_2 ); if(boss.phase_frame == 1) { snd_se_play(8); state->phase_2.angle_counterclockwise = 0x80; state->phase_2.angle_clockwise = 0x00; state->phase_2.angle_stacks = 0x08; } return; } if(boss_flystep_random(boss.phase_frame - PHASE_2_PATTERN_START_FRAME)) { boss.phase_frame = 0; boss.mode = 0; } phase_2_3_pattern(); } #define pattern_blue(pattern_angle, angle_delta) { \ if((boss.phase_frame % 2) == 0) { \ bullet_template.spawn_type = BST_CLOUD_FORWARDS; \ bullet_template.patnum = PAT_BULLET16_N_BALL_BLUE; \ bullet_template.group = BG_SINGLE; \ bullet_template.angle = pattern_angle; \ bullet_template.speed.set(1.5f); \ bullet_template_tune(); \ bullets_add_regular(); \ pattern_angle = (pattern_angle + angle_delta); \ } \ } void near pattern_blue_curve_counterclockwise(void) { pattern_blue(state->phase_2.angle_counterclockwise, -0x0A); } void near pattern_blue_curve_clockwise(void) { pattern_blue(state->phase_2.angle_clockwise, +0x0A); } void near pattern_aimed_red_spread_stack(void) { if(boss.phase_frame == (PHASE_2_PATTERN_START_FRAME + 16)) { bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN); bullet_template.patnum = PAT_BULLET16_N_BALL_RED; bullet_template.group = BG_SPREAD_STACK_AIMED; bullet_template.angle = 0; bullet_template.set_spread_stack(5, 0x10, 5, 0.4375f); bullet_template.speed.set(1.0f); bullet_template_tune(); bullets_add_regular(); snd_se_play(15); } } void near pattern_red_stacks(void) { if((boss.phase_frame % 8) == 0) { bullet_template.spawn_type = (BST_CLOUD_FORWARDS | BST_NO_SLOWDOWN); bullet_template.patnum = PAT_BULLET16_N_BALL_RED; bullet_template.group = BG_STACK; bullet_template.angle = ( boss.angle + state->phase_2.angle_stacks - 0x80 ); bullet_template.set_stack(8, 0.375f); bullet_template.speed.set(1.0f); bullet_template_tune(); bullets_add_regular(); state->phase_2.angle_stacks -= 0x08; snd_se_play(15); } } static void near phase_3_with_pattern(void) { if(boss.phase_frame < PHASE_3_PATTERN_START_FRAME) { gather_add_only_3stack( (boss.phase_frame - 16), COL_GATHER_1, COL_GATHER_2 ); if(boss.phase_frame == 16) { snd_se_play(8); boss.sprite = PAT_SARA_SPIN; state->phase_3.angle_clockwise = (0x40 - 0x08); state->phase_3.angle_counterclockwise = (0x40 + 0x08); state->phase_3.ring_speed.set(1.5f); } return; } if(boss.phase_frame < 64) { boss.sprite++; } else if(boss.phase_frame < 96) { if((boss.phase_frame % 2) == 0) { boss.sprite++; } } else if(boss.phase_frame < 128) { if((boss.phase_frame % 4) == 0) { boss.sprite++; } } else if((boss.phase_frame < 160)) { if((boss.phase_frame % 8) == 0) { boss.sprite++; } } if(boss.sprite >= (PAT_SARA_SPIN_last + 1)) { boss.sprite = PAT_SARA_SPIN; } phase_2_3_pattern(); if(boss.phase_frame >= state->phase_3.pattern_duration) { boss.phase_frame = 0; boss.mode = 0; boss.sprite = PAT_SARA_STAY; } } void near pattern_pellet_arcs_at_expanding_random_angles(void) { if((boss.phase_frame % 8) == 0) { bullet_template.spawn_type = BST_NO_SLOWDOWN; bullet_template.patnum = 0; bullet_template.group = BG_SPREAD; bullet_template.angle = ( randring2_next16_mod(boss.phase_frame * 2) - boss.phase_frame + 0x40 ); bullet_template.set_spread(3, 0x3); bullet_template.special_motion = BSM_EXACT_LINEAR; bullet_template.speed.v = randring2_next8_and_ge_lt_sp(1.0f, 3.0f); bullet_template_tune(); bullets_add_special(); bullet_template.spawn_type = BST_CLOUD_BACKWARDS; bullet_template.patnum = PAT_BULLET16_N_BALL_BLUE; bullet_template.group = BG_SINGLE; bullet_template.angle = ( randring2_next16_mod(boss.phase_frame * 2) - boss.phase_frame + 0x40 ); bullet_template.speed.v = randring2_next8_and_ge_lt_sp(1.5f, 3.5f); bullet_template_tune(); bullets_add_regular(); snd_se_play(3); } } void near pattern_random_red_rings(void) { if((boss.phase_frame % 16) == 0) { bullet_template.spawn_type = BST_CLOUD_FORWARDS; bullet_template.patnum = PAT_BULLET16_N_BALL_RED; bullet_template.group = BG_RING; bullet_template.angle = randring2_next16(); bullet_template.speed.set(2.0f); bullet_template.spread = 12; bullet_template_tune(); bullets_add_regular(); snd_se_play(3); } } #define pattern_accelerating_rings(pattern_angle, angle_delta) { \ if((boss.phase_frame % 8) == 0) { \ bullet_template.spawn_type = BST_NORMAL; \ bullet_template.group = BG_RING; \ bullet_template.spread = 3; \ bullet_template.patnum = PAT_BULLET16_N_BALL_BLUE; \ bullet_template.angle = pattern_angle; \ bullet_template.speed = state->phase_3.ring_speed; \ if((boss.phase_frame % 16) == 0) { \ bullet_template.speed.v /= 2; \ } \ bullet_template_tune(); \ bullets_add_regular(); \ snd_se_play(3); \ \ pattern_angle += angle_delta; \ state->phase_3.ring_speed.v += to_sp(0.25f); \ } \ } void near pattern_accelerating_spirals_clockwise(void) { pattern_accelerating_rings(state->phase_3.angle_clockwise, +0x06); } void near pattern_accelerating_spirals_counterclockwise(void) { pattern_accelerating_rings(state->phase_3.angle_counterclockwise, -0x06); } void near pattern_dense_spreads_and_random_balls_within_laser_walls(void) { enum { CORRIDOR_ANGLE = 0x14, // centered around 0x40 }; if(boss.phase_frame == 1) { boss.sprite = PAT_SARA_SPIN; } if(boss.phase_frame == 32) { laser_template.coords.origin = boss.pos.cur; laser_template.coords.angle = (-0x40 + 0x20); laser_template.col = 8; laser_template.coords.width.nonshrink = 8; laser_manual_fixed_spawn(X_RIGHT); laser_template.coords.angle = (-0x40 - 0x20); laser_manual_fixed_spawn(X_LEFT); state->phase_4.spread_angle = 0x00; state->phase_4.random_ball_count = 1; state->phase_4.laser_angle_interval = 1; return; } if(boss.phase_frame <= 32) { return; } if(boss.phase_frame == 64) { laser_manual_grow(X_RIGHT); laser_manual_grow(X_LEFT); } if( ((boss.phase_frame % state->phase_4.laser_angle_interval) == 0) && (lasers[X_LEFT].coords.angle > (0x40 + (CORRIDOR_ANGLE / 2))) ) { lasers[X_RIGHT].coords.angle += 0x01; lasers[X_LEFT].coords.angle -= 0x01; if( (lasers[X_LEFT].coords.angle == 0x80) || (lasers[X_LEFT].coords.angle == 0x60) || (lasers[X_LEFT].coords.angle == 0x58) || (lasers[X_LEFT].coords.angle == 0x50) ) { state->phase_4.laser_angle_interval++; } } if(stage_frame_mod16 == 0) { bullet_template.spawn_type = BST_NO_SLOWDOWN; bullet_template.angle = state->phase_4.spread_angle; bullet_template.group = BG_SPREAD; bullet_template.special_motion = BSM_EXACT_LINEAR; bullet_template.set_spread_for_rank( 5, 0x1, 6, 0x1, 7, 0x1, 8, 0x1 ); bullet_template.speed.set(2.0f); bullet_template.patnum = 0; bullets_add_special(); bullet_template.group = BG_RANDOM_ANGLE_AND_SPEED; bullet_template.speed.set(1.0f); bullet_template.spread = state->phase_4.random_ball_count; bullet_template.patnum = PAT_BULLET16_N_BALL_BLUE; bullets_add_regular(); state->phase_4.spread_angle += 0x0E; } if( ((boss.phase_frame % 64) == 0) && (state->phase_4.random_ball_count < 8) ) { state->phase_4.random_ball_count++; } } extern const pattern_loop_func_t SARA_PATTERNS_PHASE_2_3[2][4]; #define phase_2_3_wait_fly_and_select_pattern( \ pattern_prev, phase, fly_delay, patterns_max, timeout_label \ ) { \ if(boss_flystep_random(boss.phase_frame - fly_delay)) { \ boss.phase_frame = 0; \ boss.phase_state.patterns_seen++; \ \ /* Timeout condition */ \ if(boss.phase_state.patterns_seen >= patterns_max) { \ goto timeout_label; \ } \ do { \ boss.mode = randring2_next8_mod_ge_lt(1, 5); \ } while(boss.mode == pattern_prev); \ pattern_prev = boss.mode; \ phase_2_3_pattern = SARA_PATTERNS_PHASE_2_3[phase - 2][boss.mode - 1]; \ } \ } #pragma option -a2 void pascal sara_update(void) { homing_target.x = boss.pos.cur.x; homing_target.y = boss.pos.cur.y; boss.phase_frame++; bullet_template.spawn_type = BST_NORMAL; // ZUN bloat bullet_template.origin = boss.pos.cur; gather_template.center = boss.pos.cur; switch(boss.phase) { case PHASE_HP_FILL: enum { KEYFRAME_GATHER_SPAWN_START = (HUD_HP_FILL_FRAMES + 96), KEYFRAME_GATHER_SPAWN_END = (KEYFRAME_GATHER_SPAWN_START + 32), }; if(boss.phase_frame == 1) { boss.hp = HP_TOTAL; boss.phase_end_hp = HP_PHASE_2_END; } boss_hittest_shots_invincible(); if(boss.phase_frame >= KEYFRAME_GATHER_SPAWN_START) { if(boss.phase_frame == KEYFRAME_GATHER_SPAWN_START) { gather_template.center.x = boss.pos.cur.x; gather_template.center.y = boss.pos.cur.y; gather_template.radius.set(RES_X / 2.0f); gather_template.ring_points = 32; gather_template.angle_delta = 0x03; gather_template.col = COL_GATHER_1; } if((boss.phase_frame & 7) == 0) { gather_add_only(); } if(boss.phase_frame == KEYFRAME_GATHER_SPAWN_START) { gather_template.col = COL_GATHER_2; } } // Timeout condition if(boss.phase_frame >= KEYFRAME_GATHER_SPAWN_END) { // Next phase gather_template.radius.set(BOSS_W / 1.0f); gather_template.angle_delta = 0x02; gather_template.ring_points = 8; boss.phase++; boss.phase_frame = 0; snd_se_play(13); bg_render_bombing_func = sara_bg_render; } break; case PHASE_BOSS_ENTRANCE_BB: boss_hittest_shots_invincible(); // Timeout condition if(boss.phase_frame >= ( ENTRANCE_BB_TRANSITION_FRAMES_PER_CEL * TILES_BB_CELS )) { // Next phase boss.phase++; boss.phase_frame = 0; boss.mode = 0; boss.phase_state.patterns_seen = 0; state->phase_2.pattern_prev = -1; state->phase_2.fly_delay = 64; // ZUN bloat: Already set at the end of the previous phase, and // it's not set at any other place. bg_render_bombing_func = sara_bg_render; } break; case 2: switch(boss.mode) { case 0: phase_2_3_wait_fly_and_select_pattern( state->phase_2.pattern_prev, 2, state->phase_2.fly_delay, 32, phase_2_timed_out ); break; default: phase_2_with_pattern(); if((boss.phase_frame == 0) && (state->phase_2.fly_delay > 12)) { state->phase_2.fly_delay -= 12; } break; } if(!boss_hittest_shots()) { break; } // Next phase boss_score_bonus(5); phase_2_timed_out: boss_phase_next(ET_NW_SE, HP_PHASE_3_END); state->phase_3.pattern_duration = 80; break; case 3: switch(boss.mode) { case 0: phase_2_3_wait_fly_and_select_pattern( state->phase_3.pattern_prev, 3, 16, 24, phase_3_timed_out ); break; default: phase_3_with_pattern(); if( (boss.phase_frame == 0) && (state->phase_3.pattern_duration < 180) ) { state->phase_3.pattern_duration += 24; } break; } if(!boss_hittest_shots()) { break; } // Next phase boss_score_bonus(5); phase_3_timed_out: boss_phase_next(ET_SW_NE, HP_PHASE_4_END); break; case 4: pattern_dense_spreads_and_random_balls_within_laser_walls(); // Timeout condition if(boss.phase_frame >= 1300) { boss.phase_state.defeat_bonus = false; } else { // Boss defeated? if(!boss_hittest_shots()) { break; } boss.phase_state.defeat_bonus = true; } // Boss defeated laser_stop(0); laser_stop(1); boss.phase_frame = 0; boss.phase = PHASE_BOSS_EXPLODE_SMALL; break; default: boss_defeat_update(10); break; } hud_hp_update_and_render(boss.hp, HP_TOTAL); }