/** * Behavior for bhvPlatformOnTrack and bhvTrackBall. * The platform spawns up to 5 track balls at a time, which then despawn * themselves as the platform moves past them. */ /** * Collision models for the different types of platforms. */ static void *sPlatformOnTrackCollisionModels[] = { /* PLATFORM_ON_TRACK_TYPE_CARPET */ rr_seg7_collision_07029038, /* PLATFORM_ON_TRACK_TYPE_SKI_LIFT */ ccm_seg7_collision_070163F8, /* PLATFORM_ON_TRACK_TYPE_CHECKERED */ checkerboard_platform_seg8_collision_0800D710, /* PLATFORM_ON_TRACK_TYPE_GRATE */ bitfs_seg7_collision_070157E0, }; /** * Paths for the different instances of these platforms. */ static void *sPlatformOnTrackPaths[] = { rr_seg7_trajectory_0702EC3C, rr_seg7_trajectory_0702ECC0, ccm_seg7_trajectory_0701669C, bitfs_seg7_trajectory_070159AC, hmc_seg7_trajectory_0702B86C, lll_seg7_trajectory_0702856C, lll_seg7_trajectory_07028660, rr_seg7_trajectory_0702ED9C, rr_seg7_trajectory_0702EEE0, }; /** * Despawn all track balls and enter the init action. */ static void platform_on_track_reset(void) { o->oAction = PLATFORM_ON_TRACK_ACT_INIT; // This will cause the track balls to all despawn o->oPlatformOnTrackBaseBallIndex += 99; } /** * If this platform is the kind that disappears, pause for a while, then * begin blinking, and finally reset. */ static void platform_on_track_mario_not_on_platform(void) { if (!((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_DONT_DISAPPEAR)) { // Once oTimer reaches 150, blink 40 times if (cur_obj_wait_then_blink(150, 40)) { platform_on_track_reset(); o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE; } } } /** * Init function for bhvPlatformOnTrack. */ void bhv_platform_on_track_init(void) { if (!(o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM)) { s16 pathIndex = (u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_MASK_PATH; o->oPlatformOnTrackType = ((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_MASK_TYPE) >> 4; o->oPlatformOnTrackIsNotSkiLift = o->oPlatformOnTrackType - PLATFORM_ON_TRACK_TYPE_SKI_LIFT; o->collisionData = segmented_to_virtual(sPlatformOnTrackCollisionModels[o->oPlatformOnTrackType]); o->oPlatformOnTrackStartWaypoint = segmented_to_virtual(sPlatformOnTrackPaths[pathIndex]); o->oPlatformOnTrackIsNotHMC = pathIndex - 4; o->oBehParams2ndByte = o->oMoveAngleYaw; // TODO: Weird? if (o->oPlatformOnTrackType == PLATFORM_ON_TRACK_TYPE_CHECKERED) { o->header.gfx.scale[1] = 2.0f; } } } /** * Move to the start waypoint, spawn the first track balls, and enter the * wait for mario action. */ static void platform_on_track_act_init(void) { s32 i; o->oPlatformOnTrackPrevWaypoint = o->oPlatformOnTrackStartWaypoint; o->oPlatformOnTrackPrevWaypointFlags = 0; o->oPlatformOnTrackBaseBallIndex = 0; o->oPosX = o->oHomeX = o->oPlatformOnTrackStartWaypoint->pos[0]; o->oPosY = o->oHomeY = o->oPlatformOnTrackStartWaypoint->pos[1]; o->oPosZ = o->oHomeZ = o->oPlatformOnTrackStartWaypoint->pos[2]; o->oFaceAngleYaw = o->oBehParams2ndByte; o->oForwardVel = o->oVelX = o->oVelY = o->oVelZ = o->oPlatformOnTrackDistMovedSinceLastBall = 0.0f; o->oPlatformOnTrackWasStoodOn = FALSE; if (o->oPlatformOnTrackIsNotSkiLift) { o->oFaceAngleRoll = 0; } // Spawn track balls for (i = 1; i < 6; i++) { platform_on_track_update_pos_or_spawn_ball(i, o->oHomeX, o->oHomeY, o->oHomeZ); } o->oAction = PLATFORM_ON_TRACK_ACT_WAIT_FOR_MARIO; } /** * Wait for mario to stand on the platform for 20 frames, then begin moving. */ static void platform_on_track_act_wait_for_mario(void) { if (gMarioObject->platform == o) { if (o->oTimer > 20) { o->oAction = PLATFORM_ON_TRACK_ACT_MOVE_ALONG_TRACK; } } else { if (o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM) { platform_on_track_reset(); } o->oTimer = 0; } } /** * Move along the track. After reaching the end, either start falling, * return to the init action, or continue moving back to the start waypoint. */ static void platform_on_track_act_move_along_track(void) { s16 initialAngle; if (!o->oPlatformOnTrackIsNotSkiLift) { cur_obj_play_sound_1(SOUND_ENV_ELEVATOR3); } else if (!o->oPlatformOnTrackIsNotHMC) { cur_obj_play_sound_1(SOUND_ENV_ELEVATOR1); } // Fall after reaching the last waypoint if desired if (o->oPlatformOnTrackPrevWaypointFlags == WAYPOINT_FLAGS_END && !((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_RETURN_TO_START)) { o->oAction = PLATFORM_ON_TRACK_ACT_FALL; } else { // The ski lift should pause or stop after reaching a special waypoint if (o->oPlatformOnTrackPrevWaypointFlags != 0 && !o->oPlatformOnTrackIsNotSkiLift) { if (o->oPlatformOnTrackPrevWaypointFlags == WAYPOINT_FLAGS_END || o->oPlatformOnTrackPrevWaypointFlags == WAYPOINT_FLAGS_PLATFORM_ON_TRACK_PAUSE) { cur_obj_play_sound_2(SOUND_GENERAL_UNKNOWN4_LOWPRIO); o->oForwardVel = 0.0f; if (o->oPlatformOnTrackPrevWaypointFlags == WAYPOINT_FLAGS_END) { o->oAction = PLATFORM_ON_TRACK_ACT_INIT; } else { o->oAction = PLATFORM_ON_TRACK_ACT_PAUSE_BRIEFLY; } } } else { // The ski lift accelerates, while the others instantly start if (!o->oPlatformOnTrackIsNotSkiLift) { obj_forward_vel_approach(10.0, 0.1f); } else { o->oForwardVel = 10.0f; } // Spawn a new track ball if necessary if (approach_f32_ptr(&o->oPlatformOnTrackDistMovedSinceLastBall, 300.0f, o->oForwardVel)) { o->oPlatformOnTrackDistMovedSinceLastBall -= 300.0f; o->oHomeX = o->oPosX; o->oHomeY = o->oPosY; o->oHomeZ = o->oPosZ; o->oPlatformOnTrackBaseBallIndex = (u16)(o->oPlatformOnTrackBaseBallIndex + 1); platform_on_track_update_pos_or_spawn_ball(5, o->oHomeX, o->oHomeY, o->oHomeZ); } } platform_on_track_update_pos_or_spawn_ball(0, o->oPosX, o->oPosY, o->oPosZ); o->oMoveAnglePitch = o->oPlatformOnTrackPitch; o->oMoveAngleYaw = o->oPlatformOnTrackYaw; //! Both oAngleVelYaw and oAngleVelRoll aren't reset until the platform // starts moving again, resulting in unexpected platform displacement // after reappearing // Turn face yaw and compute yaw vel if (!((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_DONT_TURN_YAW)) { s16 targetFaceYaw = o->oMoveAngleYaw + 0x4000; s16 yawSpeed = abs_angle_diff(targetFaceYaw, o->oFaceAngleYaw) / 20; initialAngle = o->oFaceAngleYaw; clamp_s16(&yawSpeed, 100, 500); obj_face_yaw_approach(targetFaceYaw, yawSpeed); o->oAngleVelYaw = (s16) o->oFaceAngleYaw - initialAngle; } // Turn face roll and compute roll vel if (((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_DONT_TURN_ROLL)) { s16 rollSpeed = abs_angle_diff(o->oMoveAnglePitch, o->oFaceAngleRoll) / 20; initialAngle = o->oFaceAngleRoll; clamp_s16(&rollSpeed, 100, 500); //! If the platform is moving counterclockwise upward or // clockwise downward, this will be backward obj_face_roll_approach(o->oMoveAnglePitch, rollSpeed); o->oAngleVelRoll = (s16) o->oFaceAngleRoll - initialAngle; } } if (gMarioObject->platform != o) { platform_on_track_mario_not_on_platform(); } else { o->oTimer = 0; o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE; } } /** * Wait 20 frames then continue moving. */ static void platform_on_track_act_pause_briefly(void) { if (o->oTimer > 20) { o->oAction = PLATFORM_ON_TRACK_ACT_MOVE_ALONG_TRACK; } } /** * Fall downward with no terminal velocity, stopping after reaching y = -12k * and eventually blinking and disappearing. */ static void platform_on_track_act_fall(void) { cur_obj_move_using_vel_and_gravity(); if (gMarioObject->platform != o) { platform_on_track_mario_not_on_platform(); } else { o->oTimer = 0; //! Doesn't ensure visibility } } /** * Control the rocking of the ski lift. */ static void platform_on_track_rock_ski_lift(void) { s32 targetRoll = 0; UNUSED s32 initialRoll = o->oFaceAngleRoll; o->oFaceAngleRoll += (s32) o->oPlatformOnTrackSkiLiftRollVel; // Tilt away from the moving direction and toward mario if (gMarioObject->platform == o) { targetRoll = o->oForwardVel * sins(o->oMoveAngleYaw) * -50.0f + (s32)(o->oDistanceToMario * sins(o->oAngleToMario - o->oFaceAngleYaw) * -4.0f); } oscillate_toward( /* value */ &o->oFaceAngleRoll, /* vel */ &o->oPlatformOnTrackSkiLiftRollVel, /* target */ targetRoll, /* velCloseToZero */ 5.0f, /* accel */ 6.0f, /* slowdown */ 1.5f); clamp_f32(&o->oPlatformOnTrackSkiLiftRollVel, -100.0f, 100.0f); } /** * Update function for bhvPlatformOnTrack. */ void bhv_platform_on_track_update(void) { switch (o->oAction) { case PLATFORM_ON_TRACK_ACT_INIT: platform_on_track_act_init(); break; case PLATFORM_ON_TRACK_ACT_WAIT_FOR_MARIO: platform_on_track_act_wait_for_mario(); break; case PLATFORM_ON_TRACK_ACT_MOVE_ALONG_TRACK: platform_on_track_act_move_along_track(); break; case PLATFORM_ON_TRACK_ACT_PAUSE_BRIEFLY: platform_on_track_act_pause_briefly(); break; case PLATFORM_ON_TRACK_ACT_FALL: platform_on_track_act_fall(); break; } if (!o->oPlatformOnTrackIsNotSkiLift) { platform_on_track_rock_ski_lift(); } else if (o->oPlatformOnTrackType == PLATFORM_ON_TRACK_TYPE_CARPET) { if (!o->oPlatformOnTrackWasStoodOn && gMarioObject->platform == o) { o->oPlatformOnTrackOffsetY = -8.0f; o->oPlatformOnTrackWasStoodOn = TRUE; } approach_f32_ptr(&o->oPlatformOnTrackOffsetY, 0.0f, 0.5f); o->oPosY += o->oPlatformOnTrackOffsetY; } } /** * Update function for bhvTrackBall. */ void bhv_track_ball_update(void) { // Despawn after the elevator passes this ball s16 relativeIndex = (s16) o->oBehParams2ndByte - (s16) o->parentObj->oPlatformOnTrackBaseBallIndex - 1; if (relativeIndex < 1 || relativeIndex > 5) { obj_mark_for_deletion(o); } }