sm64pc/src/game/behaviors/koopa.inc.c

840 lines
28 KiB
C

/**
* Behavior for bhvKoopa and bhvKoopaRaceEndpoint.
* bhvKoopa includes normal, unshelled, tiny, and Koopa the Quick.
* When the race begins, koopa the quick sets his parent to bhvKoopaRaceEndpoint
* which assists in determining the state of the race. It is positioned at the
* flag.
*/
/**
* Hitbox for koopa - this is used for every form except Koopa the Quick, which
* uses a hardcoded soft hitbox.
*/
static struct ObjectHitbox sKoopaHitbox = {
/* interactType: */ INTERACT_KOOPA,
/* downOffset: */ 0,
/* damageOrCoinValue: */ 0,
/* health: */ 0,
/* numLootCoins: */ -1,
/* radius: */ 60,
/* height: */ 40,
/* hurtboxRadius: */ 40,
/* hurtboxHeight: */ 30,
};
/**
* Attack handlers for unshelled koopa and tiny shelled koopa.
*/
static u8 sKoopaUnshelledAttackHandlers[] = {
/* ATTACK_PUNCH: */ ATTACK_HANDLER_KNOCKBACK,
/* ATTACK_KICK_OR_TRIP: */ ATTACK_HANDLER_KNOCKBACK,
/* ATTACK_FROM_ABOVE: */ ATTACK_HANDLER_SQUISHED,
/* ATTACK_GROUND_POUND_OR_TWIRL: */ ATTACK_HANDLER_SQUISHED,
/* ATTACK_FAST_ATTACK: */ ATTACK_HANDLER_KNOCKBACK,
/* ATTACK_FROM_BELOW: */ ATTACK_HANDLER_KNOCKBACK,
};
/**
* Attack handlers for regular sized shelled koopa.
*/
static u8 sKoopaShelledAttackHandlers[] = {
/* ATTACK_PUNCH: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
/* ATTACK_KICK_OR_TRIP: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
/* ATTACK_FROM_ABOVE: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
/* ATTACK_GROUND_POUND_OR_TWIRL: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
/* ATTACK_FAST_ATTACK: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
/* ATTACK_FROM_BELOW: */ ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL,
};
/**
* Data to control the behavior of each instance of Koopa the Quick.
*/
struct KoopaTheQuickProperties {
s16 initText;
s16 winText;
void *path;
Vec3s starPos;
};
/**
* Properties for the BoB race and the THI race.
*/
static struct KoopaTheQuickProperties sKoopaTheQuickProperties[] = {
{ DIALOG_005, DIALOG_007, bob_seg7_trajectory_koopa, { 3030, 4500, -4600 } },
{ DIALOG_009, DIALOG_031, thi_seg7_trajectory_koopa, { 7100, -1300, -6000 } }
};
/**
* Initialization function.
*/
void bhv_koopa_init(void) {
if ((o->oKoopaMovementType = o->oBehParams2ndByte) == KOOPA_BP_TINY) {
// Tiny koopa in THI
o->oKoopaMovementType = KOOPA_BP_NORMAL;
o->oKoopaAgility = 1.6f / 3.0f;
o->oDrawingDistance = 1500.0f;
cur_obj_scale(0.8f);
o->oGravity = -6.4f / 3.0f;
} else if (o->oKoopaMovementType >= KOOPA_BP_KOOPA_THE_QUICK_BASE) {
// Koopa the Quick. Race index is 0 for BoB and 1 for THI
o->oKoopaTheQuickRaceIndex = o->oKoopaMovementType - KOOPA_BP_KOOPA_THE_QUICK_BASE;
o->oKoopaAgility = 4.0f;
cur_obj_scale(3.0f);
} else {
o->oKoopaAgility = 1.0f;
}
}
/**
* Play the appropriate footstep sound on the two provided animation frames.
*/
static void koopa_play_footstep_sound(s8 animFrame1, s8 animFrame2) {
s32 sound;
if (o->header.gfx.scale[0] > 1.5f) {
sound = SOUND_OBJ_KOOPA_THE_QUICK_WALK;
} else {
sound = SOUND_OBJ_KOOPA_WALK;
}
cur_obj_play_sound_at_anim_range(animFrame1, animFrame2, sound);
}
/**
* If mario is close to koopa, and koopa is facing toward mario, then begin
* running away.
*/
static s32 koopa_check_run_from_mario(void) {
if (o->oKoopaDistanceToMario < 300.0f
&& abs_angle_diff(o->oKoopaAngleToMario, o->oMoveAngleYaw) < 0x3000) {
o->oAction = KOOPA_SHELLED_ACT_RUN_FROM_MARIO;
return TRUE;
}
return FALSE;
}
/**
* Stay still for a while, then change target yaw by 45 degrees and begin
* walking.
*/
static void koopa_shelled_act_stopped(void) {
o->oForwardVel = 0.0f;
if (cur_obj_init_anim_and_check_if_end(7)) {
o->oAction = KOOPA_SHELLED_ACT_WALK;
o->oKoopaTargetYaw = o->oMoveAngleYaw + 0x2000 * (s16) random_sign();
}
}
/**
* Begin walking, then increment sub-action.
*/
static void koopa_walk_start(void) {
obj_forward_vel_approach(3.0f * o->oKoopaAgility, 0.3f * o->oKoopaAgility);
if (cur_obj_init_anim_and_check_if_end(11)) {
o->oSubAction += 1;
o->oKoopaCountdown = random_linear_offset(30, 100);
}
}
/**
* Walk until oKoopaCountdown hits zero, then increment sub-action.
*/
static void koopa_walk(void) {
cur_obj_init_animation_with_sound(9);
koopa_play_footstep_sound(2, 17);
if (o->oKoopaCountdown != 0) {
o->oKoopaCountdown -= 1;
} else if (cur_obj_check_if_near_animation_end()) {
o->oSubAction += 1;
}
}
/**
* Stop walking, then enter action KOOPA_SHELLED_ACT_STOPPED.
*/
static void koopa_walk_stop(void) {
obj_forward_vel_approach(0.0f, 1.0f * o->oKoopaAgility);
if (cur_obj_init_anim_and_check_if_end(10)) {
o->oAction = KOOPA_SHELLED_ACT_STOPPED;
}
}
/**
* Walk for a while, then come to a stop. During this time, turn toward the
* target yaw.
*/
static void koopa_shelled_act_walk(void) {
if (o->oKoopaTurningAwayFromWall) {
o->oKoopaTurningAwayFromWall = obj_resolve_collisions_and_turn(o->oKoopaTargetYaw, 0x200);
} else {
// If far from home, then begin turning toward home
if (o->oDistanceToMario >= 25000.0f) {
o->oKoopaTargetYaw = o->oAngleToMario;
}
o->oKoopaTurningAwayFromWall = obj_bounce_off_walls_edges_objects(&o->oKoopaTargetYaw);
cur_obj_rotate_yaw_toward(o->oKoopaTargetYaw, 0x200);
}
switch (o->oSubAction) {
case KOOPA_SHELLED_SUB_ACT_START_WALK:
koopa_walk_start();
break;
case KOOPA_SHELLED_SUB_ACT_WALK:
koopa_walk();
break;
case KOOPA_SHELLED_SUB_ACT_STOP_WALK:
koopa_walk_stop();
break;
}
koopa_check_run_from_mario();
}
/**
* Run while turning away from mario. Come to a stop once mario is far enough
* away.
*/
static void koopa_shelled_act_run_from_mario(void) {
cur_obj_init_animation_with_sound(1);
koopa_play_footstep_sound(0, 11);
// If far from home, run toward it
if (o->oDistanceToMario >= 25000.0f) {
o->oAngleToMario += 0x8000;
o->oDistanceToMario = 0.0f;
}
if (o->oTimer > 30 && o->oDistanceToMario > 800.0f) {
if (obj_forward_vel_approach(0.0f, 1.0f)) {
o->oAction = KOOPA_SHELLED_ACT_STOPPED;
}
} else {
cur_obj_rotate_yaw_toward(o->oAngleToMario + 0x8000, 0x400);
obj_forward_vel_approach(17.0f, 1.0f);
}
}
/**
* If on the ground, decelerate. Generate dust if moving fast enough.
*/
static void koopa_dive_update_speed(f32 decel) {
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
obj_forward_vel_approach(0.0f, decel);
if (o->oForwardVel > 5.0f) {
if (!(o->oTimer % 4)) {
spawn_object_with_scale(o, MODEL_SMOKE, bhvWhitePuffSmoke2, 1.0f);
}
}
}
}
/**
* Slide on the ground and then come to a stop.
*/
static void koopa_shelled_act_lying(void) {
if (o->oForwardVel != 0.0f) {
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
o->oMoveAngleYaw = cur_obj_reflect_move_angle_off_wall();
}
cur_obj_init_anim_extend(5);
koopa_dive_update_speed(0.3f);
} else if (o->oKoopaCountdown != 0) {
o->oKoopaCountdown -= 1;
cur_obj_extend_animation_if_at_end();
} else if (cur_obj_init_anim_and_check_if_end(6)) {
o->oAction = KOOPA_SHELLED_ACT_STOPPED;
}
}
/**
* Attack handler for regular-sized shelled koopa.
* Lose shell and enter lying action.
*/
void shelled_koopa_attack_handler(s32 attackType) {
if (o->header.gfx.scale[0] > 0.8f) {
cur_obj_play_sound_2(SOUND_OBJ_KOOPA_DAMAGE);
o->oKoopaMovementType = KOOPA_BP_UNSHELLED;
o->oAction = KOOPA_UNSHELLED_ACT_LYING;
o->oForwardVel = 20.0f;
// If attacked from the side, get knocked away from mario
if (attackType != ATTACK_FROM_ABOVE && attackType != ATTACK_GROUND_POUND_OR_TWIRL) {
o->oMoveAngleYaw = obj_angle_to_object(gMarioObject, o);
}
cur_obj_set_model(MODEL_KOOPA_WITHOUT_SHELL);
spawn_object(o, MODEL_KOOPA_SHELL, bhvKoopaShell);
//! Because bob-ombs/corkboxes come after koopa in processing order,
// they can interact with the koopa on the same frame that this
// happens. This causes the koopa to die immediately.
cur_obj_become_intangible();
} else {
// Die if tiny koopa
obj_die_if_health_non_positive();
}
}
/**
* Update function for both regular and tiny shelled koopa.
*/
static void koopa_shelled_update(void) {
cur_obj_update_floor_and_walls();
obj_update_blinking(&o->oKoopaBlinkTimer, 20, 50, 4);
switch (o->oAction) {
case KOOPA_SHELLED_ACT_STOPPED:
koopa_shelled_act_stopped();
koopa_check_run_from_mario();
break;
case KOOPA_SHELLED_ACT_WALK:
koopa_shelled_act_walk();
break;
case KOOPA_SHELLED_ACT_RUN_FROM_MARIO:
koopa_shelled_act_run_from_mario();
break;
case KOOPA_SHELLED_ACT_LYING:
koopa_shelled_act_lying();
break;
}
if (o->header.gfx.scale[0] > 0.8f) {
obj_handle_attacks(&sKoopaHitbox, o->oAction, sKoopaShelledAttackHandlers);
} else {
// If tiny koopa, die after attacking mario.
obj_handle_attacks(&sKoopaHitbox, KOOPA_SHELLED_ACT_DIE, sKoopaUnshelledAttackHandlers);
if (o->oAction == KOOPA_SHELLED_ACT_DIE) {
obj_die_if_health_non_positive();
}
}
cur_obj_move_standard(-78);
}
/**
* Attempt to run toward the shell if it exists while still running away from
* mario. If the shell doesn't exist, run around randomly.
* When close enough to the shell and with good angle and speed, enter the dive
* action.
*/
static void koopa_unshelled_act_run(void) {
f32 distToShell = 99999.0f;
struct Object *shell;
cur_obj_init_animation_with_sound(3);
koopa_play_footstep_sound(0, 6);
if (o->oKoopaTurningAwayFromWall) {
o->oKoopaTurningAwayFromWall = obj_resolve_collisions_and_turn(o->oKoopaTargetYaw, 0x600);
} else {
// If far from home, then turn toward home
if (o->oDistanceToMario >= 25000.0f) {
o->oKoopaTargetYaw = o->oAngleToMario;
}
// If shell exists, then turn toward shell
shell = cur_obj_find_nearest_object_with_behavior(bhvKoopaShell, &distToShell);
if (shell != NULL) {
//! This overrides turning toward home
o->oKoopaTargetYaw = obj_angle_to_object(o, shell);
} else if (!(o->oKoopaTurningAwayFromWall =
obj_bounce_off_walls_edges_objects(&o->oKoopaTargetYaw))) {
// Otherwise run around randomly
if (o->oKoopaUnshelledTimeUntilTurn != 0) {
o->oKoopaUnshelledTimeUntilTurn -= 1;
} else {
o->oKoopaTargetYaw = obj_random_fixed_turn(0x2000);
}
}
// If mario is far away, or our running away from mario coincides with
// running toward the shell
if (o->oDistanceToMario > 800.0f
|| (shell != NULL
&& abs_angle_diff(o->oKoopaTargetYaw, o->oAngleToMario + 0x8000) < 0x2000)) {
// then turn toward the shell
cur_obj_rotate_yaw_toward(o->oKoopaTargetYaw, 0x600);
} else {
// otherwise continue running from mario
cur_obj_rotate_yaw_toward(o->oAngleToMario + 0x8000, 0x600);
}
}
// If we think we have a shot, dive for the shell
if (obj_forward_vel_approach(20.0f, 1.0f) && distToShell < 600.0f
&& abs_angle_diff(o->oKoopaTargetYaw, o->oMoveAngleYaw) < 0xC00) {
o->oMoveAngleYaw = o->oKoopaTargetYaw;
o->oAction = KOOPA_UNSHELLED_ACT_DIVE;
o->oForwardVel *= 1.2f;
o->oVelY = distToShell / 20.0f;
o->oKoopaCountdown = 20;
}
}
/**
* Dive and slide along the ground. If close enough to the shell, pick it up,
* and otherwise enter the running action.
*/
static void koopa_unshelled_act_dive(void) {
struct Object *shell;
f32 distToShell;
if (o->oTimer > 10) {
cur_obj_become_tangible();
}
if (o->oTimer > 10) {
shell = cur_obj_find_nearest_object_with_behavior(bhvKoopaShell, &distToShell);
// If we got the shell and mario didn't, put on the shell
//! The shell comes after koopa in processing order, and the shell is
// responsible for positioning itself under mario.
// If mario has more than 200 speed and is riding the shell, then
// from the perspective of the koopa, the shell is always lagging 200
// units behind mario.
// Using this, we can get the koopa to pick up and despawn its shell
// while mario is riding it.
if (shell != NULL && dist_between_objects(shell, gMarioObject) > 200.0f
&& distToShell < 50.0f) {
o->oKoopaMovementType = KOOPA_BP_NORMAL;
o->oAction = KOOPA_SHELLED_ACT_LYING;
o->oForwardVel *= 0.5f;
cur_obj_set_model(MODEL_KOOPA_WITH_SHELL);
obj_mark_for_deletion(shell);
goto end;
}
}
if (o->oForwardVel != 0.0f) {
if (o->oAction == KOOPA_UNSHELLED_ACT_LYING) {
o->oAnimState = 1;
cur_obj_init_anim_extend(2);
} else {
cur_obj_init_anim_extend(5);
}
koopa_dive_update_speed(0.5f);
} else if (o->oKoopaCountdown != 0) {
o->oKoopaCountdown -= 1;
cur_obj_extend_animation_if_at_end();
} else if (cur_obj_init_anim_and_check_if_end(6)) {
o->oAction = KOOPA_UNSHELLED_ACT_RUN;
}
end:;
}
/**
* Unused action function.
*/
static void koopa_unshelled_act_unused3(void) {
cur_obj_init_anim_extend(0);
}
/**
* Update function for koopa after losing his shell.
*/
static void koopa_unshelled_update(void) {
cur_obj_update_floor_and_walls();
obj_update_blinking(&o->oKoopaBlinkTimer, 10, 15, 3);
switch (o->oAction) {
case KOOPA_UNSHELLED_ACT_RUN:
koopa_unshelled_act_run();
break;
case KOOPA_UNSHELLED_ACT_DIVE:
case KOOPA_UNSHELLED_ACT_LYING:
koopa_unshelled_act_dive();
break;
case KOOPA_UNSHELLED_ACT_UNUSED3:
koopa_unshelled_act_unused3();
break;
}
obj_handle_attacks(&sKoopaHitbox, o->oAction, sKoopaUnshelledAttackHandlers);
cur_obj_move_standard(-78);
}
/**
* Wait 50 frames, then play the race starting sound, disable time stop, and
* optionally begin the timer.
*/
s32 obj_begin_race(s32 noTimer) {
if (o->oTimer == 50) {
cur_obj_play_sound_2(SOUND_GENERAL_RACE_GUN_SHOT);
if (!noTimer) {
play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, SEQ_LEVEL_SLIDE), 0);
level_control_timer(TIMER_CONTROL_SHOW);
level_control_timer(TIMER_CONTROL_START);
o->parentObj->oKoopaRaceEndpointRaceBegun = TRUE;
}
// Unfreeze mario and disable time stop to begin the race
set_mario_npc_dialog(0);
disable_time_stop_including_mario();
} else if (o->oTimer > 50) {
return TRUE;
}
return FALSE;
}
/**
* Wait for mario to approach, and then enter the show init text action.
*/
static void koopa_the_quick_act_wait_before_race(void) {
koopa_shelled_act_stopped();
if (o->oKoopaTheQuickInitTextboxCooldown != 0) {
o->oKoopaTheQuickInitTextboxCooldown -= 1;
} else if (cur_obj_can_mario_activate_textbox_2(400.0f, 400.0f)) {
//! The next action doesn't execute until next frame, giving mario one
// frame where he can jump, and thus no longer be ready to speak.
// (On J, he has two frames and doing this enables time stop - see
// cur_obj_update_dialog_with_cutscene for that glitch)
o->oAction = KOOPA_THE_QUICK_ACT_SHOW_INIT_TEXT;
o->oForwardVel = 0.0f;
cur_obj_init_animation_with_sound(7);
}
}
/**
* Display the dialog asking mario if he wants to race. Begin the race or
* return to the waiting action.
*/
static void koopa_the_quick_act_show_init_text(void) {
s32 response = obj_update_race_proposition_dialog(
sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].initText);
UNUSED s32 unused;
if (response == 1) {
gMarioShotFromCannon = FALSE;
o->oAction = KOOPA_THE_QUICK_ACT_RACE;
o->oForwardVel = 0.0f;
o->parentObj = cur_obj_nearest_object_with_behavior(bhvKoopaRaceEndpoint);
o->oPathedStartWaypoint = o->oPathedPrevWaypoint =
segmented_to_virtual(sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].path);
o->oKoopaTurningAwayFromWall = FALSE;
o->oFlags |= OBJ_FLAG_ACTIVE_FROM_AFAR;
;
} else if (response == 2) {
o->oAction = KOOPA_THE_QUICK_ACT_WAIT_BEFORE_RACE;
o->oKoopaTheQuickInitTextboxCooldown = 60;
}
}
/**
* If there is a bowling ball nearby, either slow down, or return +/-1 to
* indicate that the ball is likely to collide.
*/
static s32 koopa_the_quick_detect_bowling_ball(void) {
struct Object *ball;
f32 distToBall;
s16 angleToBall;
f32 ballSpeedInKoopaRunDir;
ball = cur_obj_find_nearest_object_with_behavior(bhvBowlingBall, &distToBall);
if (ball != NULL) {
angleToBall = obj_turn_toward_object(o, ball, O_MOVE_ANGLE_YAW_INDEX, 0);
ballSpeedInKoopaRunDir = ball->oForwardVel * coss(ball->oMoveAngleYaw - o->oMoveAngleYaw);
if (abs_angle_diff(o->oMoveAngleYaw, angleToBall) < 0x4000) {
// The ball is in front of ktq
if (distToBall < 400.0f) {
if (ballSpeedInKoopaRunDir < o->oForwardVel * 0.7f) {
// The ball is moving slowly or toward him
return 1;
} else {
// The ball is moving relatively quickly, so we'll just
// slow down a bit
//! This can go negative and is unbounded. If placed next to
// oob, ktq can get PU speed.
o->oForwardVel -= 2.0f;
}
}
} else if (distToBall < 300.0f && ballSpeedInKoopaRunDir > o->oForwardVel) {
// The ball is coming at ktq from behind, and it's moving faster
// than him
return -1;
}
}
return 0;
}
/**
* Play the running animation at a speed proportional to ktq's forward velocity.
* If he is moving backward, then the animation will play backward.
*/
static void koopa_the_quick_animate_footsteps(void) {
//! With high negative speed (using the bowling ball deceleration), we can
// index out of the animation's bounds
cur_obj_init_animation_with_accel_and_sound(9, o->oForwardVel * 0.09f);
koopa_play_footstep_sound(2, 17);
}
/**
* Begin the race, then follow the race path. Avoid bowling balls by slowing
* down or jumping. After finishing the race, enter the decelerate action.
*/
static void koopa_the_quick_act_race(void) {
f32 downhillSteepness;
s32 bowlingBallStatus;
if (obj_begin_race(FALSE)) {
// Hitbox is slightly larger while racing
cur_obj_push_mario_away_from_cylinder(180.0f, 300.0f);
if (cur_obj_follow_path(0) == PATH_REACHED_END) {
o->oAction = KOOPA_THE_QUICK_ACT_DECELERATE;
} else {
downhillSteepness = 1.0f + sins((s16)(f32) o->oPathedTargetPitch);
cur_obj_rotate_yaw_toward(o->oPathedTargetYaw, (s32)(o->oKoopaAgility * 150.0f));
switch (o->oSubAction) {
case KOOPA_THE_QUICK_SUB_ACT_START_RUN:
koopa_walk_start();
break;
case KOOPA_THE_QUICK_SUB_ACT_RUN:
koopa_the_quick_animate_footsteps();
if (o->parentObj->oKoopaRaceEndpointRaceStatus != 0 && o->oDistanceToMario > 1500.0f
&& (o->oPathedPrevWaypointFlags & WAYPOINT_MASK_00FF) < 28) {
// Move faster if mario has already finished the race or
// cheated by shooting from cannon
o->oKoopaAgility = 8.0f;
} else if (o->oKoopaTheQuickRaceIndex != KOOPA_THE_QUICK_BOB_INDEX) {
o->oKoopaAgility = 6.0f;
} else {
o->oKoopaAgility = 4.0f;
}
obj_forward_vel_approach(o->oKoopaAgility * 6.0f * downhillSteepness,
o->oKoopaAgility * 0.1f);
// Move upward if we hit a wall, to climb it
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
o->oVelY = 20.0f;
}
// If we're about to collide with a bowling ball or we hit an
// edge, jump
bowlingBallStatus = koopa_the_quick_detect_bowling_ball();
if (bowlingBallStatus != 0 || (o->oMoveFlags & OBJ_MOVE_HIT_EDGE)) {
// If the ball is coming at us from behind, then set speed
// to zero to let it move under and past us
if (bowlingBallStatus < 0) {
o->oForwardVel = 0.0f;
}
if (bowlingBallStatus != 0
|| (o->oPathedPrevWaypointFlags & WAYPOINT_MASK_00FF) >= 8) {
o->oVelY = 80.0f;
} else {
o->oVelY = 40.0f;
}
o->oGravity = -6.0f;
o->oSubAction = 2;
o->oMoveFlags = 0;
cur_obj_init_animation_with_sound(12);
}
break;
case KOOPA_THE_QUICK_SUB_ACT_JUMP:
// We could perform a goomba double jump if we could deactivate
// ktq
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
if (cur_obj_init_anim_and_check_if_end(13)) {
o->oSubAction -= 1;
}
koopa_the_quick_detect_bowling_ball();
}
}
}
}
}
/**
* Decelerate and then enter the stop action.
*/
static void koopa_the_quick_act_decelerate(void) {
obj_forward_vel_approach(3.0f, 1.0f);
cur_obj_init_animation_with_accel_and_sound(9, 0.99f);
if (cur_obj_check_if_near_animation_end()) {
o->oAction = KOOPA_THE_QUICK_ACT_STOP;
o->oForwardVel = 3.0f;
}
}
/**
* Stop and then enter the after race action.
*/
static void koopa_the_quick_act_stop(void) {
koopa_walk_stop();
// koopa_walk_stop() was written for shelled koopa, so it enters the
// KOOPA_SHELLED_ACT_STOPPED at the end
if (o->oAction == KOOPA_SHELLED_ACT_STOPPED) {
o->oAction = KOOPA_THE_QUICK_ACT_AFTER_RACE;
}
}
/**
* Wait for mario to approach, then show text indicating the status of the race.
* If mario got to the finish line first and didn't use the cannon, then spawn
* the star.
*/
static void koopa_the_quick_act_after_race(void) {
cur_obj_init_animation_with_sound(7);
if (o->parentObj->oKoopaRaceEndpointUnk100 == 0) {
if (cur_obj_can_mario_activate_textbox_2(400.0f, 400.0f)) {
stop_background_music(SEQUENCE_ARGS(4, SEQ_LEVEL_SLIDE));
// Determine which text to display
if (o->parentObj->oKoopaRaceEndpointRaceStatus != 0) {
if (o->parentObj->oKoopaRaceEndpointRaceStatus < 0) {
// Mario cheated
o->parentObj->oKoopaRaceEndpointRaceStatus = 0;
o->parentObj->oKoopaRaceEndpointUnk100 = DIALOG_006;
} else {
// Mario won
o->parentObj->oKoopaRaceEndpointUnk100 =
sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].winText;
}
} else {
// KtQ won
o->parentObj->oKoopaRaceEndpointUnk100 = DIALOG_041;
}
o->oFlags &= ~OBJ_FLAG_ACTIVE_FROM_AFAR;
}
} else if (o->parentObj->oKoopaRaceEndpointUnk100 > 0) {
s32 dialogResponse = cur_obj_update_dialog_with_cutscene(2, 1, CUTSCENE_DIALOG, o->parentObj->oKoopaRaceEndpointUnk100);
if (dialogResponse != 0) {
o->parentObj->oKoopaRaceEndpointUnk100 = -1;
o->oTimer = 0;
}
} else if (o->parentObj->oKoopaRaceEndpointRaceStatus != 0) {
spawn_default_star(sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].starPos[0],
sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].starPos[1],
sKoopaTheQuickProperties[o->oKoopaTheQuickRaceIndex].starPos[2]);
o->parentObj->oKoopaRaceEndpointRaceStatus = 0;
}
}
/**
* Update function for koopa the quick.
*/
static void koopa_the_quick_update(void) {
cur_obj_update_floor_and_walls();
obj_update_blinking(&o->oKoopaBlinkTimer, 10, 15, 3);
switch (o->oAction) {
case KOOPA_THE_QUICK_ACT_WAIT_BEFORE_RACE:
case KOOPA_THE_QUICK_ACT_UNUSED1:
koopa_the_quick_act_wait_before_race();
break;
case KOOPA_THE_QUICK_ACT_SHOW_INIT_TEXT:
koopa_the_quick_act_show_init_text();
break;
case KOOPA_THE_QUICK_ACT_RACE:
koopa_the_quick_act_race();
break;
case KOOPA_THE_QUICK_ACT_DECELERATE:
koopa_the_quick_act_decelerate();
break;
case KOOPA_THE_QUICK_ACT_STOP:
koopa_the_quick_act_stop();
break;
case KOOPA_THE_QUICK_ACT_AFTER_RACE:
koopa_the_quick_act_after_race();
break;
}
if (o->parentObj != o) {
if (dist_between_objects(o, o->parentObj) < 400.0f) {
o->parentObj->oKoopaRaceEndpointKoopaFinished = TRUE;
}
}
cur_obj_push_mario_away_from_cylinder(140.0f, 300.0f);
cur_obj_move_standard(-78);
}
/**
* Update function.
*/
void bhv_koopa_update(void) {
// PARTIAL_UPDATE
o->oDeathSound = SOUND_OBJ_KOOPA_FLYGUY_DEATH;
if (o->oKoopaMovementType >= KOOPA_BP_KOOPA_THE_QUICK_BASE) {
koopa_the_quick_update();
} else if (obj_update_standard_actions(o->oKoopaAgility * 1.5f)) {
o->oKoopaDistanceToMario = o->oDistanceToMario;
o->oKoopaAngleToMario = o->oAngleToMario;
treat_far_home_as_mario(1000.0f);
switch (o->oKoopaMovementType) {
case KOOPA_BP_UNSHELLED:
koopa_unshelled_update();
break;
case KOOPA_BP_NORMAL:
koopa_shelled_update();
break;
case KOOPA_BP_KOOPA_THE_QUICK_BOB:
case KOOPA_BP_KOOPA_THE_QUICK_THI:
koopa_the_quick_update();
break;
}
} else {
o->oAnimState = 1;
}
obj_face_yaw_approach(o->oMoveAngleYaw, 0x600);
}
/**
* Update function for bhvKoopaRaceEndpoint.
*/
void bhv_koopa_race_endpoint_update(void) {
if (o->oKoopaRaceEndpointRaceBegun && !o->oKoopaRaceEndpointRaceEnded) {
if (o->oKoopaRaceEndpointKoopaFinished || o->oDistanceToMario < 400.0f) {
o->oKoopaRaceEndpointRaceEnded = TRUE;
level_control_timer(TIMER_CONTROL_STOP);
if (!o->oKoopaRaceEndpointKoopaFinished) {
play_race_fanfare();
if (gMarioShotFromCannon) {
o->oKoopaRaceEndpointRaceStatus = -1;
} else {
o->oKoopaRaceEndpointRaceStatus = 1;
}
}
}
}
}