diff --git a/Makefile b/Makefile index 1f74791573..6d14b8485d 100644 --- a/Makefile +++ b/Makefile @@ -383,7 +383,8 @@ LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h))) VNL_ACTRS_DIRS := $(patsubst actors/vanilla_actors/%,%,$(dir $(wildcard actors/vanilla_actors/*/header.h))) # Directories containing source files -SRC_DIRS += src src/boot src/game src/engine src/audio src/menu src/buffers actors levels bin data assets asm lib sound +CAMERA_DIRS += src/camera src/camera/level_specific src/camera/cutscenes src/camera/modes +SRC_DIRS += src src/boot src/game $(CAMERA_DIRS) src/engine src/audio src/menu src/buffers actors levels bin data assets asm lib sound LIBZ_SRC_DIRS := src/libz GODDARD_SRC_DIRS := src/goddard src/goddard/dynlists BIN_DIRS := bin bin/$(VERSION) diff --git a/sm64.ld b/sm64.ld index 43f3590928..022a07eba3 100755 --- a/sm64.ld +++ b/sm64.ld @@ -270,18 +270,22 @@ SECTIONS BUILD_DIR/src/engine/surface_collision.o(.text*); BUILD_DIR/src/engine/surface_load.o(.text*); BUILD_DIR/src/engine/graph_node.o(.text*); + BUILD_DIR/src/camera*.o(.text*); BUILD_DIR/src/engine*.o(.text*); _engineSegmentTextEnd = .; /* data */ BUILD_DIR/src/game*.o(.data*); + BUILD_DIR/src/camera*.o(.*data*); BUILD_DIR/src/engine*.o(.data*); BUILD_DIR/src/usb*.o(.data*); /* sdata */ BUILD_DIR/src/game*.o(.sdata*); + BUILD_DIR/src/camera*.o(.sdata*); BUILD_DIR/src/engine*.o(.sdata*); BUILD_DIR/src/usb*.o(.data*); /* rodata */ BUILD_DIR/src/game*.o(.rodata*); + BUILD_DIR/src/camera*.o(.rodata*); BUILD_DIR/src/engine*.o(.rodata*); BUILD_DIR/src/usb*.o(.rodata*); . = ALIGN(0x10); @@ -290,6 +294,7 @@ SECTIONS BEGIN_NOLOAD(engine) { BUILD_DIR/src/game*.o(.*bss*); + BUILD_DIR/src/camera*.o(.*bss*); BUILD_DIR/src/engine*.o(.bss*); . = ALIGN(0x40); } diff --git a/src/camera/camera_geo.c b/src/camera/camera_geo.c new file mode 100644 index 0000000000..8ebbfb9556 --- /dev/null +++ b/src/camera/camera_geo.c @@ -0,0 +1,248 @@ +#include + +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/level_update.h" +#include "game/rendering_graph_node.h" + +/** + * Add a cyclic offset to the camera's field of view based on a cosine wave + */ +static void shake_camera_fov(struct GraphNodePerspective *perspective) { + if (sFOVState.shakeAmplitude != 0.f) { + sFOVState.fovOffset = coss(sFOVState.shakePhase) * sFOVState.shakeAmplitude / 0x100; + sFOVState.shakePhase += sFOVState.shakeSpeed; + camera_approach_f32_symmetric_bool(&sFOVState.shakeAmplitude, 0.f, sFOVState.decay); + perspective->fov += sFOVState.fovOffset; + } else { + sFOVState.shakePhase = 0; + } +} + +static void set_fov_30(UNUSED struct MarioState *m) { + sFOVState.fov = 30.f; +} + +static void approach_fov_20(UNUSED struct MarioState *m) { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 20.f, 0.3f); +} + +static void set_fov_45(UNUSED struct MarioState *m) { + sFOVState.fov = 45.f; +} + +static void set_fov_29(UNUSED struct MarioState *m) { + sFOVState.fov = 29.f; +} + +static void zoom_fov_30(UNUSED struct MarioState *m) { + // Pretty sure approach_f32_asymptotic_bool would do a much nicer job here, but you do you, + // Nintendo. + camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, (30.f - sFOVState.fov) / 60.f); +} + +/** + * This is the default fov function. It makes fov approach 45 degrees, and it handles zooming in when + * Mario falls a sleep. + */ +static void fov_default(struct MarioState *m) { + sStatusFlags &= ~CAM_FLAG_SLEEPING; + + if ((m->action == ACT_SLEEPING) || (m->action == ACT_START_SLEEPING)) { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, (30.f - sFOVState.fov) / 30.f); + sStatusFlags |= CAM_FLAG_SLEEPING; + } else { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 45.f, (45.f - sFOVState.fov) / 30.f); + sFOVState.unusedIsSleeping = 0; + } + if (m->area->camera->cutscene == CUTSCENE_0F_UNUSED) { + sFOVState.fov = 45.f; + } +} + +static void approach_fov_30(UNUSED struct MarioState *m) { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, 1.f); +} + +static void approach_fov_60(UNUSED struct MarioState *m) { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 60.f, 1.f); +} + +static void approach_fov_45(struct MarioState *m) { + f32 targetFoV = sFOVState.fov; + + if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) { + targetFoV = 45.f; + } else { + targetFoV = 45.f; + } + + sFOVState.fov = approach_f32(sFOVState.fov, targetFoV, 2.f, 2.f); +} + +static void approach_fov_80(UNUSED struct MarioState *m) { + camera_approach_f32_symmetric_bool(&sFOVState.fov, 80.f, 3.5f); +} + +/** + * Sets the fov in BBH. + * If there's a cutscene, sets fov to 45. Otherwise sets fov to 60. + */ +static void set_fov_bbh(struct MarioState *m) { + f32 targetFoV = sFOVState.fov; + + if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) { + targetFoV = 60.f; + } else { + targetFoV = 45.f; + } + + sFOVState.fov = approach_f32(sFOVState.fov, targetFoV, 2.f, 2.f); +} + +/** + * Sets the field of view for the GraphNodeCamera + */ +Gfx *geo_camera_fov(s32 callContext, struct GraphNode *g, UNUSED void *context) { + struct GraphNodePerspective *perspective = (struct GraphNodePerspective *) g; + struct MarioState *marioState = &gMarioStates[0]; + u8 fovFunc = sFOVState.fovFunc; + + if (callContext == GEO_CONTEXT_RENDER) { + switch (fovFunc) { + case CAM_FOV_SET_45: + set_fov_45(marioState); + break; + case CAM_FOV_SET_29: + set_fov_29(marioState); + break; + case CAM_FOV_ZOOM_30: + zoom_fov_30(marioState); + break; + case CAM_FOV_DEFAULT: + fov_default(marioState); + break; + case CAM_FOV_BBH: + set_fov_bbh(marioState); + break; + case CAM_FOV_APP_45: + approach_fov_45(marioState); + break; + case CAM_FOV_SET_30: + set_fov_30(marioState); + break; + case CAM_FOV_APP_20: + approach_fov_20(marioState); + break; + case CAM_FOV_APP_80: + approach_fov_80(marioState); + break; + case CAM_FOV_APP_30: + approach_fov_30(marioState); + break; + case CAM_FOV_APP_60: + approach_fov_60(marioState); + break; + default: + set_fov_45(marioState); + break; + } + } + + perspective->fov = sFOVState.fov; + shake_camera_fov(perspective); + return NULL; +} + +/** + * Allocate the GraphNodeCamera's config.camera, and copy `c`'s focus to the Camera's area center point. + */ +static void create_camera(struct GraphNodeCamera *gc, struct AllocOnlyPool *pool) { +#ifdef FORCED_CAMERA_MODE + gc->config.mode = FORCED_CAMERA_MODE; +#endif + s16 mode = gc->config.mode; + struct Camera *c = alloc_only_pool_alloc(pool, sizeof(struct Camera)); + + gc->config.camera = c; + c->mode = mode; + c->defMode = mode; + c->cutscene = CUTSCENE_NONE; + c->doorStatus = DOOR_DEFAULT; + c->areaCenX = gc->focus[0]; + c->areaCenY = gc->focus[1]; + c->areaCenZ = gc->focus[2]; + c->yaw = 0; + vec3f_copy(c->pos, gc->pos); + vec3f_copy(c->focus, gc->focus); +} + +/** + * Zooms out the camera if paused and the level is 'outside', as determined by sZoomOutAreaMasks. + * + * Because gCurrLevelArea is assigned gCurrLevelNum * 16 + gCurrentArea->index, + * dividing by 32 maps 2 levels to one index. + * + * areaBit definition: + * (gCurrLevelArea & 0x10) / 4): + * This adds 4 to the shift if the level is an odd multiple of 16 + * + * ((gCurrLevelArea & 0xF) - 1) & 3): + * This isolates the lower 16 'area' bits, subtracts 1 because areas are 1-indexed, and effectively + * modulo-4's the result, because each 8-bit mask only has 4 area bits for each level + */ +static void zoom_out_if_paused_and_outside(struct GraphNodeCamera *camera) { + s16 yaw; + s32 areaMaskIndex = gCurrLevelArea / 32; + s32 areaBit = 1 << (((gCurrLevelArea & 0x10) / 4) + (((gCurrLevelArea & 0xF) - 1) & 3)); + + if (areaMaskIndex >= LEVEL_MAX / 2) { + areaMaskIndex = 0; + areaBit = 0; + } + if (gCameraMovementFlags & CAM_MOVE_PAUSE_SCREEN) { + if (sFramesPaused >= 2) { + if (sZoomOutAreaMasks[areaMaskIndex] & areaBit) { + + camera->focus[0] = gCamera->areaCenX; + camera->focus[1] = (sMarioCamState->pos[1] + gCamera->areaCenY) / 2; + camera->focus[2] = gCamera->areaCenZ; + vec3f_get_yaw(camera->focus, sMarioCamState->pos, &yaw); + vec3f_set_dist_and_angle(sMarioCamState->pos, camera->pos, 6000.f, 0x1000, yaw); +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelNum != LEVEL_THI) { + find_in_bounds_yaw_wdw_bob_thi(camera->pos, camera->focus, 0); + } +#endif + } + } else { + sFramesPaused++; + } + } else { + sFramesPaused = 0; + } +} + +/** + * Copy Lakitu's pos and foc into `gc` + */ +static void update_graph_node_camera(struct GraphNodeCamera *gc) { + gc->rollScreen = gLakituState.roll; + vec3f_copy(gc->pos, gLakituState.pos); + vec3f_copy(gc->focus, gLakituState.focus); + zoom_out_if_paused_and_outside(gc); +} + +Gfx *geo_camera_main(s32 callContext, struct GraphNode *g, void *context) { + struct GraphNodeCamera *gc = (struct GraphNodeCamera *) g; + switch (callContext) { + case GEO_CONTEXT_CREATE: + create_camera(gc, context); + break; + case GEO_CONTEXT_RENDER: + update_graph_node_camera(gc); + break; + } + return NULL; +} diff --git a/src/camera/camera_geo.h b/src/camera/camera_geo.h new file mode 100644 index 0000000000..4c1978dd20 --- /dev/null +++ b/src/camera/camera_geo.h @@ -0,0 +1,8 @@ +#pragma once + +#include "types.h" +#include "game/camera.h" + +Gfx *geo_camera_fov(s32 callContext, struct GraphNode *g, UNUSED void *context); +Gfx *geo_camera_main(s32 callContext, struct GraphNode *g, void *context); + diff --git a/src/camera/camera_math.c b/src/camera/camera_math.c new file mode 100644 index 0000000000..a8f3732f44 --- /dev/null +++ b/src/camera/camera_math.c @@ -0,0 +1,867 @@ +#include "types.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/level_update.h" + +/** + * Rotates the offset `to` according to the pitch and yaw values in `rotation`. + * Adds `from` to the rotated offset, and stores the result in `dst`. + * + * @warning Flips the Z axis, so that relative to `rotation`, -Z moves forwards and +Z moves backwards. + */ +void offset_rotated(Vec3f dst, Vec3f from, Vec3f to, Vec3s rotation) { + Vec3f pitchRotated; + + // First rotate the direction by rotation's pitch + //! The Z axis is flipped here. + pitchRotated[2] = -(to[2] * coss(rotation[0]) - to[1] * sins(rotation[0])); + pitchRotated[1] = to[2] * sins(rotation[0]) + to[1] * coss(rotation[0]); + pitchRotated[0] = to[0]; + + // Rotate again by rotation's yaw + dst[0] = from[0] + pitchRotated[2] * sins(rotation[1]) + pitchRotated[0] * coss(rotation[1]); + dst[1] = from[1] + pitchRotated[1]; + dst[2] = from[2] + pitchRotated[2] * coss(rotation[1]) - pitchRotated[0] * sins(rotation[1]); +} + +/** + * Rotates the offset defined by (`xTo`, `yTo`, `zTo`) according to the pitch and yaw values in `rotation`. + * Adds `from` to the rotated offset, and stores the result in `dst`. + * + * @warning Flips the Z axis, so that relative to `rotation`, -Z moves forwards and +Z moves backwards. + */ +void offset_rotated_coords(Vec3f dst, Vec3f from, Vec3s rotation, f32 xTo, f32 yTo, f32 zTo) { + Vec3f to; + + vec3f_set(to, xTo, yTo, zTo); + offset_rotated(dst, from, to, rotation); +} + + +void object_pos_to_vec3f(Vec3f dst, struct Object *obj) { + dst[0] = obj->oPosX; + dst[1] = obj->oPosY; + dst[2] = obj->oPosZ; +} + +void vec3f_to_object_pos(struct Object *obj, Vec3f src) { + obj->oPosX = src[0]; + obj->oPosY = src[1]; + obj->oPosZ = src[2]; +} + +/** + * Compare a vector to a position, return TRUE if they match. + */ +s32 vec3f_compare(Vec3f pos, f32 posX, f32 posY, f32 posZ) { + return pos[0] == posX + && pos[1] == posY + && pos[2] == posZ; +} + +void clamp_pitch(Vec3f from, Vec3f to, s16 maxPitch, s16 minPitch) { + s16 pitch; + s16 yaw; + f32 dist; + + vec3f_get_dist_and_angle(from, to, &dist, &pitch, &yaw); + pitch = CLAMP(pitch, minPitch, maxPitch); + vec3f_set_dist_and_angle(from, to, dist, pitch, yaw); +} + +s32 is_within_100_units_of_mario(f32 posX, f32 posY, f32 posZ) { + Vec3f pos; + vec3f_set(pos, posX, posY, posZ); + + return calc_abs_dist_squared(sMarioCamState->pos, pos) < sqr(100.f); +} + +s32 set_or_approach_f32_asymptotic(f32 *dst, f32 goal, f32 scale) { + if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { + approach_f32_asymptotic_bool(dst, goal, scale); + } else { + *dst = goal; + } + if (*dst == goal) { + return FALSE; + } else { + return TRUE; + } +} + +/** + * Applies the approach_f32_asymptotic_bool function to each of the X, Y, & Z components of the given + * vector. + */ +void approach_vec3f_asymptotic(Vec3f current, Vec3f target, f32 xMul, f32 yMul, f32 zMul) { + approach_f32_asymptotic_bool(¤t[0], target[0], xMul); + approach_f32_asymptotic_bool(¤t[1], target[1], yMul); + approach_f32_asymptotic_bool(¤t[2], target[2], zMul); +} + +/** + * Applies the set_or_approach_f32_asymptotic_bool function to each of the X, Y, & Z components of the + * given vector. + */ +void set_or_approach_vec3f_asymptotic(Vec3f dst, Vec3f goal, f32 xMul, f32 yMul, f32 zMul) { + set_or_approach_f32_asymptotic(&dst[0], goal[0], xMul); + set_or_approach_f32_asymptotic(&dst[1], goal[1], yMul); + set_or_approach_f32_asymptotic(&dst[2], goal[2], zMul); +} + +/** + * Applies the approach_s32_asymptotic function to each of the X, Y, & Z components of the given + * vector. + */ +void approach_vec3s_asymptotic(Vec3s current, Vec3s target, s16 xMul, s16 yMul, s16 zMul) { + approach_s16_asymptotic_bool(¤t[0], target[0], xMul); + approach_s16_asymptotic_bool(¤t[1], target[1], yMul); + approach_s16_asymptotic_bool(¤t[2], target[2], zMul); +} + +s32 camera_approach_s16_symmetric_bool(s16 *current, s16 target, s16 increment) { + s16 dist = target - *current; + + if (increment < 0) { + increment = -1 * increment; + } + if (dist > 0) { + dist -= increment; + if (dist >= 0) { + *current = target - dist; + } else { + *current = target; + } + } else { + dist += increment; + if (dist <= 0) { + *current = target - dist; + } else { + *current = target; + } + } + if (*current == target) { + return FALSE; + } else { + return TRUE; + } +} + +s32 camera_approach_s16_symmetric(s16 current, s16 target, s16 increment) { + s16 dist = target - current; + + if (increment < 0) { + increment = -1 * increment; + } + if (dist > 0) { + dist -= increment; + if (dist >= 0) { + current = target - dist; + } else { + current = target; + } + } else { + dist += increment; + if (dist <= 0) { + current = target - dist; + } else { + current = target; + } + } + return current; +} + +s32 set_or_approach_s16_symmetric(s16 *current, s16 target, s16 increment) { + if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { + return camera_approach_s16_symmetric_bool(current, target, increment); + } else { + *current = target; + } + if (*current == target) { + return FALSE; + } else { + return TRUE; + } +} + +/** + * Approaches a value by a given increment, returns FALSE if the target is reached. + * Appears to be a strange way of implementing approach_f32_symmetric from object_helpers.c. + * It could possibly be an older version of the function + */ +s32 camera_approach_f32_symmetric_bool(f32 *current, f32 target, f32 increment) { + f32 dist = target - *current; + + if (increment < 0) { + increment = -1 * increment; + } + if (dist > 0) { + dist -= increment; + if (dist > 0) { + *current = target - dist; + } else { + *current = target; + } + } else { + dist += increment; + if (dist < 0) { + *current = target - dist; + } else { + *current = target; + } + } + if (*current == target) { + return FALSE; + } else { + return TRUE; + } +} + +/** + * Nearly the same as the above function, this one returns the new value in place of a bool. + */ +f32 camera_approach_f32_symmetric(f32 current, f32 target, f32 increment) { + f32 dist = target - current; + + if (increment < 0) { + increment = -1 * increment; + } + if (dist > 0) { + dist -= increment; + if (dist > 0) { + current = target - dist; + } else { + current = target; + } + } else { + dist += increment; + if (dist < 0) { + current = target - dist; + } else { + current = target; + } + } + return current; +} + +/** + * Generate a vector with all three values about zero. The + * three ranges determine how wide the range about zero. + */ +void random_vec3s(Vec3s dst, s16 xRange, s16 yRange, s16 zRange) { + f32 randomFloat; + f32 tempXRange; + f32 tempYRange; + f32 tempZRange; + + randomFloat = random_float(); + tempXRange = xRange; + dst[0] = randomFloat * tempXRange - tempXRange / 2; + + randomFloat = random_float(); + tempYRange = yRange; + dst[1] = randomFloat * tempYRange - tempYRange / 2; + + randomFloat = random_float(); + tempZRange = zRange; + dst[2] = randomFloat * tempZRange - tempZRange / 2; +} + +/** + * Decrease value by multiplying it by the distance from (`posX`, `posY`, `posZ`) to + * the camera divided by `maxDist` + * + * @return the reduced value + */ +s16 reduce_by_dist_from_camera(s16 value, f32 maxDist, f32 posX, f32 posY, f32 posZ) { + Vec3f pos; + f32 dist; + s16 pitch, yaw; + s16 goalPitch, goalYaw; + s16 result = 0; + // Direction from pos to (Lakitu's) goalPos + f32 goalDX = gLakituState.goalPos[0] - posX; + f32 goalDY = gLakituState.goalPos[1] - posY; + f32 goalDZ = gLakituState.goalPos[2] - posZ; + + dist = sqrtf(goalDX * goalDX + goalDY * goalDY + goalDZ * goalDZ); + if (maxDist > dist) { + pos[0] = posX; + pos[1] = posY; + pos[2] = posZ; + vec3f_get_dist_and_angle(gLakituState.goalPos, pos, &dist, &pitch, &yaw); + if (dist < maxDist) { + calculate_angles(gLakituState.goalPos, gLakituState.goalFocus, &goalPitch, &goalYaw); + pitch -= goalPitch; + yaw -= goalYaw; + dist -= 2000.f; + if (dist < 0.f) { + dist = 0.f; + } + maxDist -= 2000.f; + if (maxDist < 2000.f) { + maxDist = 2000.f; + } + result = value * (1.f - dist / maxDist); + if (pitch < -0x1800 || pitch > 0x400 || + yaw < -0x1800 || yaw > 0x1800) { + result /= 2; + } + } + } + return result; +} + +s32 clamp_positions_and_find_yaw(Vec3f pos, Vec3f origin, f32 xMax, f32 xMin, f32 zMax, f32 zMin) { + s16 yaw = gCamera->nextYaw; + + if (pos[0] >= xMax) { + pos[0] = xMax; + } + if (pos[0] <= xMin) { + pos[0] = xMin; + } + if (pos[2] >= zMax) { + pos[2] = zMax; + } + if (pos[2] <= zMin) { + pos[2] = zMin; + } + yaw = calculate_yaw(origin, pos); + return yaw; +} + +/** + * The yaw passed here is the yaw of the direction FROM Mario TO Lakitu. + * + * wallYaw always has 90 degrees added to it before this is called -- it's parallel to the wall. + * + * @return the new yaw from Mario to rotate towards. + * + * @warning this is jank. It actually returns the yaw that will rotate further INTO the wall. So, the + * developers just add 180 degrees to the result. + */ +s32 calc_avoid_yaw(s16 yawFromMario, s16 wallYaw) { + s16 yawDiff; + yawDiff = wallYaw - yawFromMario + DEGREES(90); + + if (yawDiff < 0) { + // Deflect to the right + yawFromMario = wallYaw; + } else { + // Note: this favors the left side if the wall is exactly perpendicular to the camera. + // Deflect to the left + yawFromMario = wallYaw + DEGREES(180); + } + return yawFromMario; +} + + +/** + * Checks if `surf` is within the rect prism defined by xMax, yMax, and zMax + * + * @param surf surface to check + * @param xMax absolute-value max size in x, set to -1 to ignore + * @param yMax absolute-value max size in y, set to -1 to ignore + * @param zMax absolute-value max size in z, set to -1 to ignore + */ +s32 is_surf_within_bounding_box(struct Surface *surf, f32 xMax, f32 yMax, f32 zMax) { + // Surface vertex coordinates + Vec3s sx, sy, sz; + // Max delta between x, y, and z + s16 dxMax = 0; + s16 dyMax = 0; + s16 dzMax = 0; + // Current deltas between x, y, and z + f32 dx, dy, dz; + s32 i, j; + // result + s32 smaller = FALSE; + + sx[0] = surf->vertex1[0]; + sx[1] = surf->vertex2[0]; + sx[2] = surf->vertex3[0]; + sy[0] = surf->vertex1[1]; + sy[1] = surf->vertex2[1]; + sy[2] = surf->vertex3[1]; + sz[0] = surf->vertex1[2]; + sz[1] = surf->vertex2[2]; + sz[2] = surf->vertex3[2]; + + for (i = 0; i < 3; i++) { + j = i + 1; + if (j >= 3) { + j = 0; + } + dx = abss(sx[i] - sx[j]); + if (dx > dxMax) { + dxMax = dx; + } + dy = abss(sy[i] - sy[j]); + if (dy > dyMax) { + dyMax = dy; + } + dz = abss(sz[i] - sz[j]); + if (dz > dzMax) { + dzMax = dz; + } + } + if (yMax != -1.f) { + if (dyMax < yMax) { + smaller = TRUE; + } + } + if (xMax != -1.f && zMax != -1.f) { + if (dxMax < xMax && dzMax < zMax) { + smaller = TRUE; + } + } + return smaller; +} + +/** + * Checks if `pos` is behind the surface, using the dot product. + * + * Because the function only uses `surf`s first vertex, some surfaces can shadow others. + */ +s32 is_behind_surface(Vec3f pos, struct Surface *surf) { + s32 behindSurface = 0; + // Surface normal + f32 normX = (surf->vertex2[1] - surf->vertex1[1]) * (surf->vertex3[2] - surf->vertex2[2]) - + (surf->vertex3[1] - surf->vertex2[1]) * (surf->vertex2[2] - surf->vertex1[2]); + f32 normY = (surf->vertex2[2] - surf->vertex1[2]) * (surf->vertex3[0] - surf->vertex2[0]) - + (surf->vertex3[2] - surf->vertex2[2]) * (surf->vertex2[0] - surf->vertex1[0]); + f32 normZ = (surf->vertex2[0] - surf->vertex1[0]) * (surf->vertex3[1] - surf->vertex2[1]) - + (surf->vertex3[0] - surf->vertex2[0]) * (surf->vertex2[1] - surf->vertex1[1]); + f32 dirX = surf->vertex1[0] - pos[0]; + f32 dirY = surf->vertex1[1] - pos[1]; + f32 dirZ = surf->vertex1[2] - pos[2]; + + if (dirX * normX + dirY * normY + dirZ * normZ < 0) { + behindSurface = 1; + } + return behindSurface; +} + +/** + * Checks if the whole circular sector is behind the surface. + */ +s32 is_range_behind_surface(Vec3f from, Vec3f to, struct Surface *surf, s16 range, s16 surfType) { + s32 behindSurface = TRUE; + s32 leftBehind = 0; + s32 rightBehind = 0; + f32 checkDist; + s16 checkPitch; + s16 checkYaw; + Vec3f checkPos; + + if (surf != NULL) { + if (surfType == SURFACE_NULL || surf->type != surfType) { + if (range == 0) { + behindSurface = is_behind_surface(to, surf); + } else { + vec3f_get_dist_and_angle(from, to, &checkDist, &checkPitch, &checkYaw); + vec3f_set_dist_and_angle(from, checkPos, checkDist, checkPitch, checkYaw + range); + leftBehind = is_behind_surface(checkPos, surf); + vec3f_set_dist_and_angle(from, checkPos, checkDist, checkPitch, checkYaw - range); + rightBehind = is_behind_surface(checkPos, surf); + behindSurface = leftBehind * rightBehind; + } + } + } + return behindSurface; +} + +s32 is_mario_behind_surface(UNUSED struct Camera *c, struct Surface *surf) { + s32 behindSurface = is_behind_surface(sMarioCamState->pos, surf); + + return behindSurface; +} + +/** + * Calculates the distance between two points and sets a vector to a point + * scaled along a line between them. Typically, somewhere in the middle. + */ +void scale_along_line(Vec3f dst, Vec3f from, Vec3f to, f32 scale) { + dst[0] = (to[0] - from[0]) * scale + from[0]; + dst[1] = (to[1] - from[1]) * scale + from[1]; + dst[2] = (to[2] - from[2]) * scale + from[2]; +} +/** + * Effectively created a rectangular prism defined by a vector starting at the center + * and extending to the corners. If the position is in this box, the function returns true. + */ +s32 is_pos_in_bounds(Vec3f pos, Vec3f center, Vec3f bounds, s16 boundsYaw) { + Vec3f rel; + vec3_diff(rel, center, pos); + + rotate_in_xz(rel, rel, boundsYaw); + + return (-bounds[0] < rel[0] && rel[0] < bounds[0] && + -bounds[1] < rel[1] && rel[1] < bounds[1] && + -bounds[2] < rel[2] && rel[2] < bounds[2]); +} + +s16 calculate_pitch(Vec3f from, Vec3f to) { + f32 dx = to[0] - from[0]; + f32 dy = to[1] - from[1]; + f32 dz = to[2] - from[2]; + s16 pitch = atan2s(sqrtf(dx * dx + dz * dz), dy); + + return pitch; +} + +s16 calculate_yaw(Vec3f from, Vec3f to) { + f32 dx = to[0] - from[0]; + // UNUSED f32 dy = to[1] - from[1]; + f32 dz = to[2] - from[2]; + + return atan2s(dz, dx); +} + +/** + * Calculates the pitch and yaw between two vectors. + */ +void calculate_angles(Vec3f from, Vec3f to, s16 *pitch, s16 *yaw) { + f32 dx = to[0] - from[0]; + f32 dy = to[1] - from[1]; + f32 dz = to[2] - from[2]; + + *pitch = atan2s(sqrtf(sqr(dx) + sqr(dz)), dy); + *yaw = atan2s(dz, dx); +} + +/** + * Finds the distance between two vectors. + */ +f32 calc_abs_dist(Vec3f a, Vec3f b) { + register f32 distX = b[0] - a[0]; + register f32 distY = b[1] - a[1]; + register f32 distZ = b[2] - a[2]; + + return sqrtf(sqr(distX) + sqr(distY) + sqr(distZ)); +} + +f32 calc_abs_dist_squared(Vec3f a, Vec3f b) { + register f32 distX = b[0] - a[0]; + register f32 distY = b[1] - a[1]; + register f32 distZ = b[2] - a[2]; + + return (sqr(distX) + sqr(distY) + sqr(distZ)); +} + +/** + * Finds the horizontal distance between two vectors. + */ +f32 calc_hor_dist(Vec3f a, Vec3f b) { + register f32 distX = b[0] - a[0]; + register f32 distZ = b[2] - a[2]; + + return sqrtf(sqr(distX) + sqr(distZ)); +} + +/** + * Rotates a vector in the horizontal plane and copies it to a new vector. + */ +void rotate_in_xz(Vec3f dst, Vec3f src, s16 yaw) { + register f32 x = src[0]; + register f32 z = src[2]; + register f32 sy = sins(yaw); + register f32 cy = coss(yaw); + + dst[0] = z * sy + x * cy; + dst[1] = src[1]; + dst[2] = z * cy - x * sy; +} + +/** + * Rotates a vector in the YZ plane and copies it to a new vector. + * + * Note: This function also flips the Z axis, so +Z moves forward, not backward like it would in world + * space. If possible, use vec3f_set_dist_and_angle() + */ +void rotate_in_yz(Vec3f dst, Vec3f src, s16 pitch) { + dst[2] = -(src[2] * coss(pitch) - src[1] * sins(pitch)); + dst[1] = src[2] * sins(pitch) + src[1] * coss(pitch); + dst[0] = src[0]; +} + +/** + * Start shaking the camera's pitch (up and down) + */ +void set_camera_pitch_shake(s16 mag, s16 decay, s16 inc) { + if (gLakituState.shakeMagnitude[0] < mag) { + gLakituState.shakeMagnitude[0] = mag; + gLakituState.shakePitchDecay = decay; + gLakituState.shakePitchVel = inc; + } +} + +/** + * Start shaking the camera's yaw (side to side) + */ +void set_camera_yaw_shake(s16 mag, s16 decay, s16 inc) { + if (abss(mag) > abss(gLakituState.shakeMagnitude[1])) { + gLakituState.shakeMagnitude[1] = mag; + gLakituState.shakeYawDecay = decay; + gLakituState.shakeYawVel = inc; + } +} + +/** + * Start shaking the camera's roll (rotate screen clockwise and counterclockwise) + */ +void set_camera_roll_shake(s16 mag, s16 decay, s16 inc) { + if (gLakituState.shakeMagnitude[2] < mag) { + gLakituState.shakeMagnitude[2] = mag; + gLakituState.shakeRollDecay = decay; + gLakituState.shakeRollVel = inc; + } +} + +/** + * Start shaking the camera's pitch, but reduce `mag` by it's distance from the camera + */ +void set_pitch_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ) { + mag = reduce_by_dist_from_camera(mag, maxDist, posX, posY, posZ); + if (mag != 0) { + set_camera_pitch_shake(mag, decay, inc); + } +} + +/** + * Start shaking the camera's yaw, but reduce `mag` by it's distance from the camera + */ +void set_yaw_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ) { + mag = reduce_by_dist_from_camera(mag, maxDist, posX, posY, posZ); + if (mag != 0) { + set_camera_yaw_shake(mag, decay, inc); + } +} + +/** + * Update the shake offset by `increment` + */ +void increment_shake_offset(s16 *offset, s16 increment) { + if (increment == -0x8000) { + *offset = (*offset & 0x8000) + 0xC000; + } else { + *offset += increment; + } +} + +/** + * Apply a vertical shake to the camera by adjusting its pitch + */ +void shake_camera_pitch(Vec3f pos, Vec3f focus) { + f32 dist; + s16 pitch, yaw; + + if (gLakituState.shakeMagnitude[0] | gLakituState.shakeMagnitude[1]) { + vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); + pitch += gLakituState.shakeMagnitude[0] * sins(gLakituState.shakePitchPhase); + vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); + increment_shake_offset(&gLakituState.shakePitchPhase, gLakituState.shakePitchVel); + if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[0], 0, + gLakituState.shakePitchDecay) == 0) { + gLakituState.shakePitchPhase = 0; + } + } +} + +/** + * Apply a horizontal shake to the camera by adjusting its yaw + */ +void shake_camera_yaw(Vec3f pos, Vec3f focus) { + f32 dist; + s16 pitch, yaw; + + if (gLakituState.shakeMagnitude[1] != 0) { + vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); + yaw += gLakituState.shakeMagnitude[1] * sins(gLakituState.shakeYawPhase); + vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); + increment_shake_offset(&gLakituState.shakeYawPhase, gLakituState.shakeYawVel); + if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[1], 0, + gLakituState.shakeYawDecay) == 0) { + gLakituState.shakeYawPhase = 0; + } + } +} + +/** + * Apply a rotational shake to the camera by adjusting its roll + */ +void shake_camera_roll(s16 *roll) { + if (gLakituState.shakeMagnitude[2] != 0) { + increment_shake_offset(&gLakituState.shakeRollPhase, gLakituState.shakeRollVel); + *roll += gLakituState.shakeMagnitude[2] * sins(gLakituState.shakeRollPhase); + if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[2], 0, + gLakituState.shakeRollDecay) == 0) { + gLakituState.shakeRollPhase = 0; + } + } +} + +/** + * Add an offset to the camera's yaw, used in levels that are inside a rectangular building, like the + * pyramid or TTC. + */ +s32 offset_yaw_outward_radial(struct Camera *c, s16 areaYaw) { + s16 yawGoal = DEGREES(60); + s16 yaw = sModeOffsetYaw; + Vec3f areaCenter; + s16 dYaw; + switch (gCurrLevelArea) { + case AREA_TTC: + areaCenter[0] = c->areaCenX; + areaCenter[1] = sMarioCamState->pos[1]; + areaCenter[2] = c->areaCenZ; + if (sqr(800.f) > calc_abs_dist_squared(areaCenter, sMarioCamState->pos)) { + yawGoal = 0x3800; + } + break; + case AREA_SSL_PYRAMID: + // This mask splits the 360 degrees of yaw into 4 corners. It adds 45 degrees so that the yaw + // offset at the corner will be 0, but the yaw offset near the center will face more towards + // the direction Mario is running in. + yawGoal = (areaYaw & 0xC000) - areaYaw + DEGREES(45); + if (yawGoal < 0) { + yawGoal = -yawGoal; + } + yawGoal = yawGoal / 32 * 48; + break; + case AREA_LLL_OUTSIDE: + yawGoal = 0; + break; + } + dYaw = gMarioStates[0].forwardVel / 32.f * 128.f; + + if (sAreaYawChange < 0) { + camera_approach_s16_symmetric_bool(&yaw, -yawGoal, dYaw); + } + if (sAreaYawChange > 0) { + camera_approach_s16_symmetric_bool(&yaw, yawGoal, dYaw); + } + // When the final yaw is out of [-60,60] degrees, approach yawGoal faster than dYaw will ever be, + // making the camera lock in one direction until yawGoal drops below 60 (or Mario presses a C button) + if (yaw < -DEGREES(60)) { + //! Maybe they meant to reverse yawGoal's sign? + camera_approach_s16_symmetric_bool(&yaw, -yawGoal, 0x200); + } + if (yaw > DEGREES(60)) { + //! Maybe they meant to reverse yawGoal's sign? + camera_approach_s16_symmetric_bool(&yaw, yawGoal, 0x200); + } + return yaw; +} + +/*************************** SPLINES ***************************/ + +/** + * Produces values using a cubic b-spline curve. Basically Q is the used output, + * u is a value between 0 and 1 that represents the position along the spline, + * and a0-a3 are parameters that define the spline. + * + * The spline is described at www2.cs.uregina.ca/~anima/408/Notes/Interpolation/UniformBSpline.htm + */ +void evaluate_cubic_spline(f32 u, Vec3f Q, Vec3f spline1, Vec3f spline2, Vec3f spline3, Vec3f spline4) { + f32 B[4]; + if (u > 1.0f) u = 1.0f; + + register f32 nu = 1.0f - u; + register f32 su = sqr(u); + register f32 hcu = (su * u) / 2.0f; + + B[0] = (nu * nu * nu) / 6.0f; + B[1] = hcu - su + (2.0f / 3.0f); + B[2] = -hcu + (su / 2.0f) + (u / 2.0f) + (1.0f / 6.0f); + B[3] = hcu / 3.0f; + + Q[0] = (B[0] * spline1[0]) + (B[1] * spline2[0]) + (B[2] * spline3[0]) + (B[3] * spline4[0]); + Q[1] = (B[0] * spline1[1]) + (B[1] * spline2[1]) + (B[2] * spline3[1]) + (B[3] * spline4[1]); + Q[2] = (B[0] * spline1[2]) + (B[1] * spline2[2]) + (B[2] * spline3[2]) + (B[3] * spline4[2]); +} + +/** + * Computes the point that is `progress` percent of the way through segment `splineSegment` of `spline`, + * and stores the result in `p`. `progress` and `splineSegment` are updated if `progress` becomes >= 1.0. + * + * When neither of the next two points' speeds == 0, the number of frames is between 1 and 255. Otherwise + * it's infinite. + * + * To calculate the number of frames it will take to progress through a spline segment: + * If the next two speeds are the same and nonzero, it's 1.0 / firstSpeed. + * + * s1 and s2 are short hand for first/secondSpeed. The progress at any frame n is defined by a recurrency relation: + * p(n+1) = (s2 - s1 + 1) * p(n) + s1 + * Which can be written as + * p(n) = (s2 * ((s2 - s1 + 1)^(n) - 1)) / (s2 - s1) + * + * Solving for the number of frames: + * n = log(((s2 - s1) / s1) + 1) / log(s2 - s1 + 1) + * + * @return 1 if the point has reached the end of the spline, when `progress` reaches 1.0 or greater, and + * the 4th CutsceneSplinePoint in the current segment away from spline[splineSegment] has an index of -1. + */ +s32 move_point_along_spline(Vec3f p, struct CutsceneSplinePoint spline[], s16 *splineSegment, f32 *progress) { + s32 finished = FALSE; + Vec3f controlPoints[4]; + s32 i = 0; + f32 u = *progress; + f32 progressChange; + f32 firstSpeed = 0; + f32 secondSpeed = 0; + s32 segment = *splineSegment; + + if (*splineSegment < 0) { + segment = 0; + u = 0; + } + if (spline[segment].index == -1 || spline[segment + 1].index == -1 || spline[segment + 2].index == -1) { + return 1; + } + + for (i = 0; i < 4; i++) { + controlPoints[i][0] = spline[segment + i].point[0]; + controlPoints[i][1] = spline[segment + i].point[1]; + controlPoints[i][2] = spline[segment + i].point[2]; + } + evaluate_cubic_spline(u, p, controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3]); + + if (spline[*splineSegment + 1].speed != 0) { + firstSpeed = 1.0f / spline[*splineSegment + 1].speed; + } + if (spline[*splineSegment + 2].speed != 0) { + secondSpeed = 1.0f / spline[*splineSegment + 2].speed; + } + progressChange = (secondSpeed - firstSpeed) * *progress + firstSpeed; + + if (1 <= (*progress += progressChange)) { + (*splineSegment)++; + if (spline[*splineSegment + 3].index == -1) { + *splineSegment = 0; + finished = 1; + } + (*progress)--; + } + return finished; +} + +/** + * Change the spherical coordinates of `to` relative to `from` by `incDist`, `incPitch`, and `incYaw` + * + * @param from the base position + * @param[out] to the destination position + */ +void rotate_and_move_vec3f(Vec3f to, Vec3f from, f32 incDist, s16 incPitch, s16 incYaw) { + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(from, to, &dist, &pitch, &yaw); + pitch += incPitch; + yaw += incYaw; + dist += incDist; + vec3f_set_dist_and_angle(from, to, dist, pitch, yaw); +} + diff --git a/src/camera/camera_math.h b/src/camera/camera_math.h new file mode 100644 index 0000000000..f3d48769f1 --- /dev/null +++ b/src/camera/camera_math.h @@ -0,0 +1,55 @@ +#pragma once + +#include "types.h" +#include "game/camera.h" + +s16 reduce_by_dist_from_camera(s16 value, f32 maxDist, f32 posX, f32 posY, f32 posZ); + +s32 vec3f_compare(Vec3f pos, f32 posX, f32 posY, f32 posZ); +s32 is_within_100_units_of_mario(f32 posX, f32 posY, f32 posZ); +s32 set_or_approach_f32_asymptotic(f32 *dst, f32 goal, f32 scale); +s32 camera_approach_s16_symmetric_bool(s16 *current, s16 target, s16 increment); +s32 camera_approach_s16_symmetric(s16 current, s16 target, s16 increment); +s32 set_or_approach_s16_symmetric(s16 *current, s16 target, s16 increment); +s32 camera_approach_f32_symmetric_bool(f32 *current, f32 target, f32 increment); +s32 clamp_positions_and_find_yaw(Vec3f pos, Vec3f origin, f32 xMax, f32 xMin, f32 zMax, f32 zMin); +s32 calc_avoid_yaw(s16 yawFromMario, s16 wallYaw); +s32 is_surf_within_bounding_box(struct Surface *surf, f32 xMax, f32 yMax, f32 zMax); +s32 is_behind_surface(Vec3f pos, struct Surface *surf); +s32 is_range_behind_surface(Vec3f from, Vec3f to, struct Surface *surf, s16 range, s16 surfType); +s32 is_mario_behind_surface(UNUSED struct Camera *c, struct Surface *surf); +s32 is_pos_in_bounds(Vec3f pos, Vec3f center, Vec3f bounds, s16 boundsYaw); +s32 offset_yaw_outward_radial(struct Camera *c, s16 areaYaw); +s32 move_point_along_spline(Vec3f p, struct CutsceneSplinePoint spline[], s16 *splineSegment, f32 *progress); + +f32 camera_approach_f32_symmetric(f32 current, f32 target, f32 increment); +s16 calculate_pitch(Vec3f from, Vec3f to); +s16 calculate_yaw(Vec3f from, Vec3f to); +f32 calc_abs_dist(Vec3f a, Vec3f b); +f32 calc_abs_dist_squared(Vec3f a, Vec3f b); +f32 calc_hor_dist(Vec3f a, Vec3f b); + +void object_pos_to_vec3f(Vec3f dst, struct Object *obj); +void vec3f_to_object_pos(struct Object *obj, Vec3f src); +void clamp_pitch(Vec3f from, Vec3f to, s16 maxPitch, s16 minPitch); +void approach_vec3f_asymptotic(Vec3f current, Vec3f target, f32 xMul, f32 yMul, f32 zMul); +void set_or_approach_vec3f_asymptotic(Vec3f dst, Vec3f goal, f32 xMul, f32 yMul, f32 zMul); +void approach_vec3s_asymptotic(Vec3s current, Vec3s target, s16 xMul, s16 yMul, s16 zMul); +void random_vec3s(Vec3s dst, s16 xRange, s16 yRange, s16 zRange); +void scale_along_line(Vec3f dst, Vec3f from, Vec3f to, f32 scale); +void calculate_angles(Vec3f from, Vec3f to, s16 *pitch, s16 *yaw); +void rotate_in_xz(Vec3f dst, Vec3f src, s16 yaw); +void rotate_in_yz(Vec3f dst, Vec3f src, s16 pitch); +void set_camera_pitch_shake(s16 mag, s16 decay, s16 inc); +void set_camera_yaw_shake(s16 mag, s16 decay, s16 inc); +void set_camera_roll_shake(s16 mag, s16 decay, s16 inc); +void set_pitch_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ); +void set_yaw_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ); +void increment_shake_offset(s16 *offset, s16 increment); +void shake_camera_pitch(Vec3f pos, Vec3f focus); +void shake_camera_yaw(Vec3f pos, Vec3f focus); +void shake_camera_roll(s16 *roll); +void evaluate_cubic_spline(f32 u, Vec3f Q, Vec3f spline1, Vec3f spline2, Vec3f spline3, Vec3f spline4); +void rotate_and_move_vec3f(Vec3f to, Vec3f from, f32 incDist, s16 incPitch, s16 incYaw); +void offset_rotated(Vec3f dst, Vec3f from, Vec3f to, Vec3s rotation); +void offset_rotated_coords(Vec3f dst, Vec3f from, Vec3s rotation, f32 xTo, f32 yTo, f32 zTo); diff --git a/src/camera/camera_modes.c b/src/camera/camera_modes.c new file mode 100644 index 0000000000..1312c854a8 --- /dev/null +++ b/src/camera/camera_modes.c @@ -0,0 +1,316 @@ +#include "types.h" +#include "camera_math.h" +#include "camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" +#include "game/game_init.h" + +void focus_on_mario(Vec3f focus, Vec3f pos, f32 posYOff, f32 focYOff, f32 dist, s16 pitch, s16 yaw) { + Vec3f marioPos; + + marioPos[0] = sMarioCamState->pos[0]; + marioPos[1] = sMarioCamState->pos[1] + posYOff; + marioPos[2] = sMarioCamState->pos[2]; + + vec3f_set_dist_and_angle(marioPos, pos, dist, pitch + sLakituPitch, yaw); + + focus[0] = sMarioCamState->pos[0]; + focus[1] = sMarioCamState->pos[1] + focYOff; + focus[2] = sMarioCamState->pos[2]; +} + +/** + * Updates the camera based on which C buttons are pressed this frame + */ +void handle_c_button_movement(struct Camera *c) { + s16 cSideYaw; + + // Zoom in + if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { + if (c->mode != CAMERA_MODE_FIXED && (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT)) { + gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; + play_sound_cbutton_up(); + } else { + set_mode_c_up(c); + if (sZeroZoomDist > gCameraZoomDist) { + sZoomAmount = -gCameraZoomDist; + } else { + sZoomAmount = gCameraZoomDist; + } + } + } + if (c->mode != CAMERA_MODE_FIXED) { + // Zoom out + if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + gCameraMovementFlags |= CAM_MOVE_ALREADY_ZOOMED_OUT; + sZoomAmount = gCameraZoomDist + 400.f; + play_camera_buzz_if_cdown(); + } else { + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; + sZoomAmount = gCameraZoomDist + 400.f; + play_sound_cbutton_down(); + } + } + + // Rotate left or right + cSideYaw = 0x1000; + if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { + if (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) { + gCameraMovementFlags &= ~CAM_MOVE_ROTATE_LEFT; + } else { + gCameraMovementFlags |= CAM_MOVE_ROTATE_RIGHT; + if (sCSideButtonYaw == 0) { + play_sound_cbutton_side(); + } + sCSideButtonYaw = -cSideYaw; + } + } + if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { + if (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) { + gCameraMovementFlags &= ~CAM_MOVE_ROTATE_RIGHT; + } else { + gCameraMovementFlags |= CAM_MOVE_ROTATE_LEFT; + if (sCSideButtonYaw == 0) { + play_sound_cbutton_side(); + } + sCSideButtonYaw = cSideYaw; + } + } + } +} + +/** + * Checks for any walls obstructing Mario from view, and calculates a new yaw that the camera should + * rotate towards. + * + * @param[out] avoidYaw the angle (from Mario) that the camera should rotate towards to avoid the wall. + * The camera then approaches avoidYaw until Mario is no longer obstructed. + * avoidYaw is always parallel to the wall. + * @param yawRange how wide of an arc to check for walls obscuring Mario. + * + * @return 3 if a wall is covering Mario, 1 if a wall is only near the camera. + */ +s32 rotate_camera_around_walls(UNUSED struct Camera *c, Vec3f cPos, s16 *avoidYaw, s16 yawRange) { + struct WallCollisionData colData; + struct Surface *wall; + f32 dummyDist, checkDist; + f32 coarseRadius; + f32 fineRadius; + s16 wallYaw, horWallNorm; + s16 dummyPitch; + // The yaw of the vector from Mario to the camera. + s16 yawFromMario; + s32 status = 0; + /// The current iteration. The algorithm takes 8 equal steps from Mario back to the camera. + s32 step = 0; + + vec3f_get_dist_and_angle(sMarioCamState->pos, cPos, &dummyDist, &dummyPitch, &yawFromMario); + sStatusFlags &= ~CAM_FLAG_CAM_NEAR_WALL; + colData.offsetY = 100.0f; + // The distance from Mario to Lakitu + checkDist = 0.0f; + /// The radius used to find potential walls to avoid. + /// @bug Increases to 250.f, but the max collision radius is 200.f + coarseRadius = 150.0f; + /// This only increases when there is a wall collision found in the coarse pass + fineRadius = 100.0f; + + for (step = 0; step < 8; step++) { + // Start at Mario, move backwards to Lakitu's position + colData.x = sMarioCamState->pos[0] + ((cPos[0] - sMarioCamState->pos[0]) * checkDist); + colData.y = sMarioCamState->pos[1] + ((cPos[1] - sMarioCamState->pos[1]) * checkDist); + colData.z = sMarioCamState->pos[2] + ((cPos[2] - sMarioCamState->pos[2]) * checkDist); + colData.radius = coarseRadius; + // Increase the coarse check radius + camera_approach_f32_symmetric_bool(&coarseRadius, 250.f, 30.f); + + if (find_wall_collisions(&colData) != 0) { + wall = colData.walls[colData.numWalls - 1]; + + // If we're over halfway from Mario to Lakitu, then there's a wall near the camera, but + // not necessarily obstructing Mario + if (step >= 5) { + sStatusFlags |= CAM_FLAG_CAM_NEAR_WALL; + if (status <= 0) { + status = 1; + wall = colData.walls[colData.numWalls - 1]; + // wallYaw is parallel to the wall, not perpendicular + wallYaw = atan2s(wall->normal.z, wall->normal.x) + DEGREES(90); + // Calculate the avoid direction. The function returns the opposite direction so add 180 + // degrees. + *avoidYaw = calc_avoid_yaw(yawFromMario, wallYaw) + DEGREES(180); + } + } + + colData.x = sMarioCamState->pos[0] + ((cPos[0] - sMarioCamState->pos[0]) * checkDist); + colData.y = sMarioCamState->pos[1] + ((cPos[1] - sMarioCamState->pos[1]) * checkDist); + colData.z = sMarioCamState->pos[2] + ((cPos[2] - sMarioCamState->pos[2]) * checkDist); + colData.radius = fineRadius; + // Increase the fine check radius + camera_approach_f32_symmetric_bool(&fineRadius, 200.f, 20.f); + + if (find_wall_collisions(&colData) != 0) { + wall = colData.walls[colData.numWalls - 1]; + horWallNorm = atan2s(wall->normal.z, wall->normal.x); + wallYaw = horWallNorm + DEGREES(90); + // If Mario would be blocked by the surface, then avoid it + if ((is_range_behind_surface(sMarioCamState->pos, cPos, wall, yawRange, SURFACE_WALL_MISC) == 0) + && (is_mario_behind_surface(c, wall) == TRUE) + // Also check if the wall is tall enough to cover Mario + && (is_surf_within_bounding_box(wall, -1.f, 150.f, -1.f) == FALSE)) { + // Calculate the avoid direction. The function returns the opposite direction so add 180 + // degrees. + *avoidYaw = calc_avoid_yaw(yawFromMario, wallYaw) + DEGREES(180); + camera_approach_s16_symmetric_bool(avoidYaw, horWallNorm, yawRange); + status = 3; + step = 8; + } + } + } + checkDist += 0.125f; + } + + return status; +} + +/** + * Set the camera's y coordinate to goalHeight, respecting floors and ceilings in the way + */ +void set_camera_height(struct Camera *c, f32 goalHeight) { + struct Surface *surface; + f32 marioFloorHeight, marioCeilHeight, camFloorHeight; + f32 baseOff = 125.f; + f32 camCeilHeight = find_ceil(c->pos[0], gLakituState.goalPos[1] - 50.f, c->pos[2], &surface); +#ifdef FAST_VERTICAL_CAMERA_MOVEMENT + f32 approachRate = 20.0f; +#endif + + if (sMarioCamState->action & ACT_FLAG_HANGING) { + marioCeilHeight = sMarioGeometry.currCeilHeight; + marioFloorHeight = sMarioGeometry.currFloorHeight; + + if (marioFloorHeight < marioCeilHeight - 400.f) { + marioFloorHeight = marioCeilHeight - 400.f; + } + + goalHeight = marioFloorHeight + (marioCeilHeight - marioFloorHeight) * 0.4f; + + if (sMarioCamState->pos[1] - 400 > goalHeight) { + goalHeight = sMarioCamState->pos[1] - 400; + } + + approach_camera_height(c, goalHeight, 5.f); + } else { + camFloorHeight = find_floor(c->pos[0], c->pos[1] + 100.f, c->pos[2], &surface) + baseOff; + marioFloorHeight = baseOff + sMarioGeometry.currFloorHeight; + + if (camFloorHeight < marioFloorHeight) { + camFloorHeight = marioFloorHeight; + } + if (goalHeight < camFloorHeight) { + goalHeight = camFloorHeight; + c->pos[1] = goalHeight; + } + // Warp camera to goalHeight if further than 1000 and Mario is stuck in the ground + if (sMarioCamState->action == ACT_BUTT_STUCK_IN_GROUND || + sMarioCamState->action == ACT_HEAD_STUCK_IN_GROUND || + sMarioCamState->action == ACT_FEET_STUCK_IN_GROUND) { + if (absf(c->pos[1] - goalHeight) > 1000.0f) { + c->pos[1] = goalHeight; + } + } + +#ifdef FAST_VERTICAL_CAMERA_MOVEMENT + approachRate += ABS(c->pos[1] - goalHeight) / 20; + approach_camera_height(c, goalHeight, approachRate); +#else + approach_camera_height(c, goalHeight, 20.f); +#endif + + if (camCeilHeight != CELL_HEIGHT_LIMIT) { + camCeilHeight -= baseOff; + if ((c->pos[1] > camCeilHeight && sMarioGeometry.currFloorHeight + baseOff < camCeilHeight) + || (sMarioGeometry.currCeilHeight != CELL_HEIGHT_LIMIT + && sMarioGeometry.currCeilHeight > camCeilHeight && c->pos[1] > camCeilHeight)) { + c->pos[1] = camCeilHeight; + } + } + } +} + +/** + * Pitch the camera down when the camera is facing down a slope + */ +s16 look_down_slopes(s16 camYaw) { + struct Surface *floor; + // Default pitch + s16 pitch = 0x05B0; + // x and z offsets towards the camera + f32 xOff = sMarioCamState->pos[0] + sins(camYaw) * 40.f; + f32 zOff = sMarioCamState->pos[2] + coss(camYaw) * 40.f; + + f32 floorDY = find_floor(xOff, sMarioCamState->pos[1], zOff, &floor) - sMarioCamState->pos[1]; + + if (floor != NULL) { + if (floor->type != SURFACE_WALL_MISC && floorDY > 0) { + if (floor->normal.z == 0.f && floorDY < 100.f) { + pitch = 0x05B0; + } else { + // Add the slope's angle of declination to the pitch + pitch += atan2s(40.f, floorDY); + } + } + } + + return pitch; +} + +/** + * Look ahead to the left or right in the direction the player is facing + * The calculation for pan[0] could be simplified to: + * yaw = -yaw; + * pan[0] = sins(sMarioCamState->faceAngle[1] + yaw) * sins(0xC00) * dist; + * Perhaps, early in development, the pan used to be calculated for both the x and z directions + * + * Since this function only affects the camera's focus, Mario's movement direction isn't affected. + */ +void pan_ahead_of_player(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + Vec3f pan = { 0, 0, 0 }; + + // Get distance and angle from camera to Mario. + vec3f_get_dist_and_angle(c->pos, sMarioCamState->pos, &dist, &pitch, &yaw); + + // The camera will pan ahead up to about 30% of the camera's distance to Mario. + pan[2] = sins(0xC00) * dist; + + rotate_in_xz(pan, pan, sMarioCamState->faceAngle[1]); + // rotate in the opposite direction + yaw = -yaw; + rotate_in_xz(pan, pan, yaw); + // Only pan left or right + pan[2] = 0.f; + + // If Mario is long jumping, or on a flag pole (but not at the top), then pan in the opposite direction + if (sMarioCamState->action == ACT_LONG_JUMP || + (sMarioCamState->action != ACT_TOP_OF_POLE && (sMarioCamState->action & ACT_FLAG_ON_POLE))) { + pan[0] = -pan[0]; + } + + // Slowly make the actual pan, sPanDistance, approach the calculated pan + // If Mario is sleeping, then don't pan + if (sStatusFlags & CAM_FLAG_SLEEPING) { + approach_f32_asymptotic_bool(&sPanDistance, 0.f, 0.025f); + } else { + approach_f32_asymptotic_bool(&sPanDistance, pan[0], 0.025f); + } + + // Now apply the pan. It's a dir vector to the left or right, rotated by the camera's yaw to Mario + pan[0] = sPanDistance; + yaw = -yaw; + rotate_in_xz(pan, pan, yaw); + vec3f_add(c->focus, pan); +} diff --git a/src/camera/camera_modes.h b/src/camera/camera_modes.h new file mode 100644 index 0000000000..7027527173 --- /dev/null +++ b/src/camera/camera_modes.h @@ -0,0 +1,58 @@ +#pragma once + +#include "config/config_game.h" +#include "game/camera.h" + +/** + * A point in a path used by update_parallel_tracking_camera + */ +struct ParallelTrackingPoint { + /// Whether this point is the start of a path + s16 startOfPath; + /// Point used to define a line segment to follow + Vec3f pos; + /// The distance Mario can move along the line before the camera should move + f32 distThresh; + /// The percentage that the camera should move from the line to Mario + f32 zoom; +}; + + +// mode-specific export variables and functions +extern s16 sSpiralStairsYawOffset; +void move_mario_head_c_up(UNUSED struct Camera *c); + +void exit_c_up(struct Camera *c); +void radial_camera_input(struct Camera *c); +void radial_camera_move(struct Camera *c); +void radial_camera_input_default(struct Camera *c); +void update_yaw_and_dist_from_c_up(UNUSED struct Camera *c); +void parallel_tracking_init(struct Camera *c, struct ParallelTrackingPoint *path); + +void mode_8_directions_camera(struct Camera *c); +void mode_behind_mario_camera(struct Camera *c); +void mode_boss_fight_camera(struct Camera *c); +void mode_c_up_camera(struct Camera *c); +void mode_cannon_camera(struct Camera *c); +void mode_default_camera(struct Camera *c); +void mode_fixed_camera(struct Camera *c); +void mode_lakitu_camera(struct Camera *c); +void mode_mario_camera(struct Camera *c); +void mode_outward_radial_camera(struct Camera *c); +void mode_parallel_tracking_camera(struct Camera *c); +void mode_radial_camera(struct Camera *c); +void mode_slide_camera(struct Camera *c); +void mode_spiral_stairs_camera(struct Camera *c); +void mode_water_surface_camera(struct Camera *c); +s32 mode_behind_mario(struct Camera *c); + + + +void set_camera_height(struct Camera *c, f32 goalHeight); +void handle_c_button_movement(struct Camera *c); +void lakitu_zoom(f32 rangeDist, s16 rangePitch); +s16 look_down_slopes(s16 camYaw); +void pan_ahead_of_player(struct Camera *c); +void focus_on_mario(Vec3f focus, Vec3f pos, f32 posYOff, f32 focYOff, f32 dist, s16 pitch, s16 yaw); +s32 rotate_camera_around_walls(struct Camera *c, Vec3f cPos, s16 *avoidYaw, s16 yawRange); +void calc_y_to_curr_floor(f32 *posOff, f32 posMul, f32 posBound, f32 *focOff, f32 focMul, f32 focBound); diff --git a/src/camera/cutscene_helpers.c b/src/camera/cutscene_helpers.c new file mode 100644 index 0000000000..2498530f6f --- /dev/null +++ b/src/camera/cutscene_helpers.c @@ -0,0 +1,685 @@ +#include "audio/external.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" +#include "game/game_init.h" +#include "game/level_update.h" +#include "cutscene_helpers.h" +#include "camera_math.h" + +/** + * A copy of gDialogID, the dialog displayed during the cutscene. + */ +s16 sCutsceneDialogID; + +/** + * Mario's response to a dialog. + * 0 = No response yet + * 1 = Yes + * 2 = No + * 3 = Dialog doesn't have a response + */ +u8 sCutsceneDialogResponse = DIALOG_RESPONSE_NONE; + +/** + * A cutscene that plays when the player interacts with an object + */ +u8 sObjectCutscene = CUTSCENE_NONE; + +struct Object *gCutsceneFocus = NULL; + +/** + * The ID of the cutscene that ended. It's set to 0 if no cutscene ended less than 8 frames ago. + * + * It is only used to prevent the same cutscene from playing twice before 8 frames have passed. + */ +u8 gRecentCutscene = CUTSCENE_NONE; + +/** + * The current frame of the cutscene shot. + */ +s16 gCutsceneTimer; + +/** + * The currently playing shot in the cutscene. + */ +s16 sCutsceneShot; + +/** + * These structs are used by the cutscenes. Most of the fields are unused, and some (all?) of the used + * ones have multiple uses. + * Check the cutscene_start functions for documentation on the cvars used by a specific cutscene. + */ +struct CutsceneVariable sCutsceneVars[10]; + +/** + * The progress (from 0 to 1) through the current spline segment. + * When it becomes >= 1, 1.0 is subtracted from it and sCutsceneSplineSegment is increased. + */ +f32 sCutsceneSplineSegmentProgress; + +/** + * The current segment of the CutsceneSplinePoint[] being used. + */ +s16 sCutsceneSplineSegment; + +/** + * Direction controlled by player 2, moves the focus during the credits. + */ +Vec3f sPlayer2FocusOffset; +/** + * The pitch used for the credits easter egg. + */ +s16 sCreditsPlayer2Pitch; +/** + * The yaw used for the credits easter egg. + */ +s16 sCreditsPlayer2Yaw; + +void init_cutscene_vars() { + sCreditsPlayer2Pitch = 0; + sCreditsPlayer2Yaw = 0; + vec3_zero(sPlayer2FocusOffset); +} + +void reset_cutscene_vars() { + gCutsceneTimer = 0; + sCutsceneShot = 0; + gCutsceneObjSpawn = CUTSCENE_OBJ_NONE; + gObjCutsceneDone = FALSE; + gCutsceneFocus = NULL; + sObjectCutscene = CUTSCENE_NONE; + gRecentCutscene = CUTSCENE_NONE; +} + +/** + * If the camera's yaw is out of the range of `absYaw` +- `yawMax`, then set the yaw to `absYaw` + */ +void star_dance_bound_yaw(struct Camera *c, s16 absYaw, s16 yawMax) { + s16 yaw; + + vec3f_get_yaw(sMarioCamState->pos, c->pos, &yaw); + s16 yawFromAbs = yaw - absYaw; + + // Because angles are s16, this checks if yaw is negative + if ((yawFromAbs & 0x8000) != 0) { + yawFromAbs = -yawFromAbs; + } + if (yawFromAbs > yawMax) { + yaw = absYaw; + c->nextYaw = yaw; + c->yaw = yaw; + } +} + +/** + * Easter egg: the player 2 controller can move the camera's focus in the ending and credits. + */ +void player2_rotate_cam(struct Camera *c, s16 minPitch, s16 maxPitch, s16 minYaw, s16 maxYaw) { + f32 distCamToFocus; + s16 pitch, yaw, pitchCap; + + // Change the camera rotation to match the 2nd player's stick + approach_s16_asymptotic_bool(&sCreditsPlayer2Yaw, -(s16)(gPlayer2Controller->stickX * 250.f), 4); + approach_s16_asymptotic_bool(&sCreditsPlayer2Pitch, -(s16)(gPlayer2Controller->stickY * 265.f), 4); + vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &pitch, &yaw); + + pitchCap = 0x3800 - pitch; + if (pitchCap < 0) { + pitchCap = 0; + } + if (maxPitch > pitchCap) { + maxPitch = pitchCap; + } + + pitchCap = -0x3800 - pitch; + if (pitchCap > 0) { + pitchCap = 0; + } + if (minPitch < pitchCap) { + minPitch = pitchCap; + } + + if (sCreditsPlayer2Pitch > maxPitch) { + sCreditsPlayer2Pitch = maxPitch; + } + if (sCreditsPlayer2Pitch < minPitch) { + sCreditsPlayer2Pitch = minPitch; + } + + if (sCreditsPlayer2Yaw > maxYaw) { + sCreditsPlayer2Yaw = maxYaw; + } + if (sCreditsPlayer2Yaw < minYaw) { + sCreditsPlayer2Yaw = minYaw; + } + + pitch += sCreditsPlayer2Pitch; + yaw += sCreditsPlayer2Yaw; + vec3f_set_dist_and_angle(c->pos, sPlayer2FocusOffset, distCamToFocus, pitch, yaw); + vec3f_sub(sPlayer2FocusOffset, c->focus); +} + +/** + * Start a cutscene focusing on an object + * This will play if nothing else happened in the same frame, like exiting or warping. + */ +void start_object_cutscene(u8 cutscene, struct Object *obj) { + sObjectCutscene = cutscene; + gRecentCutscene = CUTSCENE_NONE; + gCutsceneFocus = obj; + gObjCutsceneDone = FALSE; +} + +/** + * Start a low-priority cutscene without focusing on an object + * This will play if nothing else happened in the same frame, like exiting or warping. + */ +void start_object_cutscene_without_focus(u8 cutscene) { + sObjectCutscene = cutscene; + sCutsceneDialogResponse = DIALOG_RESPONSE_NONE; +} + +UNUSED s32 unused_dialog_cutscene_response(u8 cutscene) { + // if not in a cutscene, start this one + if ((gCamera->cutscene == 0) && (sObjectCutscene == 0)) { + sObjectCutscene = cutscene; + } + + // if playing this cutscene and Mario responded, return the response + if ((gCamera->cutscene == cutscene) && (sCutsceneDialogResponse)) { + return sCutsceneDialogResponse; + } else { + return 0; + } +} + +s16 cutscene_object_with_dialog(u8 cutscene, struct Object *obj, s16 dialogID) { + s16 response = DIALOG_RESPONSE_NONE; + + if ((gCamera->cutscene == CUTSCENE_NONE) && (sObjectCutscene == CUTSCENE_NONE)) { + if (gRecentCutscene != cutscene) { + start_object_cutscene(cutscene, obj); + if (dialogID != DIALOG_NONE) { + sCutsceneDialogID = dialogID; + } else { + sCutsceneDialogID = DIALOG_001; + } + } else { + response = sCutsceneDialogResponse; + } + + gRecentCutscene = CUTSCENE_NONE; + } + return response; +} + +s16 cutscene_object_without_dialog(u8 cutscene, struct Object *obj) { + return cutscene_object_with_dialog(cutscene, obj, DIALOG_NONE); +} + +/** + * @return 0 if not started, 1 if started, and -1 if finished + */ +s16 cutscene_object(u8 cutscene, struct Object *obj) { + s16 status = 0; + + if ((gCamera->cutscene == 0) && (sObjectCutscene == 0)) { + if (gRecentCutscene != cutscene) { + start_object_cutscene(cutscene, obj); + status = 1; + } else { + status = -1; + } + } + return status; +} + +/** + * Starts a cutscene dialog. Only has an effect when `trigger` is 1 + */ +void trigger_cutscene_dialog(s32 trigger) { + if (trigger == 1) start_object_cutscene_without_focus(CUTSCENE_READ_MESSAGE); +} + +void reset_pan_distance(UNUSED struct Camera *c) { + sPanDistance = 0; +} + +/** + * Triggers Mario to enter a dialog state. This is used to make Mario look at the focus of a cutscene, + * for example, bowser. + * @param state 0 = stop, 1 = start, 2 = start and look up, and 3 = start and look down + * + * @return if Mario left the dialog state, return CUTSCENE_LOOP, else return gCutsceneTimer + */ +s16 cutscene_common_set_dialog_state(s32 state) { + s16 timer = gCutsceneTimer; + // If the dialog ended, return CUTSCENE_LOOP, which would end the cutscene shot + if (set_mario_npc_dialog(state) == MARIO_DIALOG_STATUS_SPEAK) { + timer = CUTSCENE_LOOP; + } + return timer; +} + +void cutscene_stop_dialog(UNUSED struct Camera *c) { + cutscene_common_set_dialog_state(MARIO_DIALOG_STOP); +} + +/** + * Cutscene helpers for Mario to enter the dialog state and look in a direction + */ +void cutscene_mario_dialog_look_down(UNUSED struct Camera *c) { + gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_DOWN); +} + +void cutscene_mario_dialog_look_front(UNUSED struct Camera *c) { + gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_FRONT); +} + +void cutscene_mario_dialog_look_up(UNUSED struct Camera *c) { + gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_UP); +} + +void set_flag_post_door(struct Camera *c) { + sStatusFlags |= CAM_FLAG_BEHIND_MARIO_POST_DOOR; + sCameraYawAfterDoorCutscene = calculate_yaw(c->focus, c->pos); +} + +/** + * Ends the double door cutscene. + */ +void cutscene_double_doors_end(struct Camera *c) { + set_flag_post_door(c); + c->cutscene = 0; + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; +} + +/** + * End the cutscene, used by cutscenes that play when Mario exits a course to castle grounds. + */ +void cutscene_exit_to_castle_grounds_end(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + cutscene_update_camera_yaw(c); +} + +/** + * Start a preset fov shake. Used in cutscenes + */ +void cutscene_set_fov_shake_preset(u8 preset) { + switch (preset) { + case 1: + set_fov_shake(0x100, 0x30, 0x8000); + break; + case 2: + set_fov_shake(0x400, 0x20, 0x4000); + break; + } +} + +void cutscene_shake_explosion(UNUSED struct Camera *c) { + set_environmental_camera_shake(SHAKE_ENV_EXPLOSION); + cutscene_set_fov_shake_preset(1); +} + +/** + * Update the camera's yaw and nextYaw. This is called from cutscenes to ignore the camera mode's yaw. + */ +void cutscene_update_camera_yaw(struct Camera *c) { + c->nextYaw = calculate_yaw(c->focus, c->pos); + c->yaw = c->nextYaw; +} + +void cutscene_soften_music(UNUSED struct Camera *c) { + seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); +} + +void cutscene_unsoften_music(UNUSED struct Camera *c) { + seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60); +} + +void cutscene_reset_spline(void) { + sCutsceneSplineSegment = 0; + sCutsceneSplineSegmentProgress = 0; +} + +void stop_cutscene_and_retrieve_stored_info(struct Camera *c) { + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + vec3f_copy(c->focus, sCameraStoreCutscene.focus); + vec3f_copy(c->pos, sCameraStoreCutscene.pos); +} + +/** + * Store camera info for the cannon opening cutscene + */ +void store_info_cannon(struct Camera *c) { + vec3f_copy(sCameraStoreCutscene.pos, c->pos); + vec3f_copy(sCameraStoreCutscene.focus, c->focus); + sCameraStoreCutscene.panDist = sPanDistance; + sCameraStoreCutscene.cannonYOffset = sCannonYOffset; +} + +/** + * Retrieve camera info for the cannon opening cutscene + */ +void retrieve_info_cannon(struct Camera *c) { + vec3f_copy(c->pos, sCameraStoreCutscene.pos); + vec3f_copy(c->focus, sCameraStoreCutscene.focus); + sPanDistance = sCameraStoreCutscene.panDist; + sCannonYOffset = sCameraStoreCutscene.cannonYOffset; +} + +/** + * Store camera info for the star spawn cutscene + */ +void store_info_star(struct Camera *c) { + reset_pan_distance(c); + vec3f_copy(sCameraStoreCutscene.pos, c->pos); + sCameraStoreCutscene.focus[0] = sMarioCamState->pos[0]; + sCameraStoreCutscene.focus[1] = c->focus[1]; + sCameraStoreCutscene.focus[2] = sMarioCamState->pos[2]; +} + +/** + * Retrieve camera info for the star spawn cutscene + */ +void retrieve_info_star(struct Camera *c) { + vec3f_copy(c->pos, sCameraStoreCutscene.pos); + vec3f_copy(c->focus, sCameraStoreCutscene.focus); +} + +/** + * Calculates Mario's distance to the floor, or the water level if it is above the floor. Then: + * `posOff` is set to the distance multiplied by posMul and bounded to [-posBound, posBound] + * `focOff` is set to the distance multiplied by focMul and bounded to [-focBound, focBound] + * + * Notes: + * posMul is always 1.0f, focMul is always 0.9f + * both ranges are always 200.0f + * Since focMul is 0.9, `focOff` is closer to the floor than `posOff` + * posOff and focOff are sometimes the same address, which just ignores the pos calculation + */ +void calc_y_to_curr_floor(f32 *posOff, f32 posMul, f32 posBound, f32 *focOff, f32 focMul, f32 focBound) { + f32 floorHeight = sMarioGeometry.currFloorHeight; + f32 waterHeight; + + if (!(sMarioCamState->action & ACT_FLAG_METAL_WATER)) { + //! @bug this should use sMarioGeometry.waterHeight + if (floorHeight < (waterHeight = find_water_level(sMarioCamState->pos[0], sMarioCamState->pos[2]))) { + floorHeight = waterHeight; + } + } + + if (sMarioCamState->action & ACT_FLAG_ON_POLE) { + if (sMarioGeometry.currFloorHeight >= gMarioStates[0].usedObj->oPosY && sMarioCamState->pos[1] + < 0.7f * gMarioStates[0].usedObj->hitboxHeight + gMarioStates[0].usedObj->oPosY) { + posBound = 1200; + } + } + + *posOff = (floorHeight - sMarioCamState->pos[1]) * posMul; + + if (*posOff > posBound) { + *posOff = posBound; + } + + if (*posOff < -posBound) { + *posOff = -posBound; + } + + *focOff = (floorHeight - sMarioCamState->pos[1]) * focMul; + + if (*focOff > focBound) { + *focOff = focBound; + } + + if (*focOff < -focBound) { + *focOff = -focBound; + } +} + +/** + * Set the camera's focus to Mario's position, and add several relative offsets. + * + * @param leftRight offset to Mario's left/right, relative to his faceAngle + * @param yOff y offset + * @param forwBack offset to Mario's front/back, relative to his faceAngle + * @param yawOff offset to Mario's faceAngle, changes the direction of `leftRight` and `forwBack` + */ +void set_focus_rel_mario(struct Camera *c, f32 leftRight, f32 yOff, f32 forwBack, s16 yawOff) { + s16 yaw; + f32 focFloorYOff; + + calc_y_to_curr_floor(&focFloorYOff, 1.f, 200.f, &focFloorYOff, 0.9f, 200.f); + yaw = sMarioCamState->faceAngle[1] + yawOff; + c->focus[2] = sMarioCamState->pos[2] + forwBack * coss(yaw) - leftRight * sins(yaw); + c->focus[0] = sMarioCamState->pos[0] + forwBack * sins(yaw) + leftRight * coss(yaw); + c->focus[1] = sMarioCamState->pos[1] + yOff + focFloorYOff; +} + +/** + * Adjust the camera focus towards a point `dist` units in front of Mario. + * @param dist distance in Mario's forward direction. Note that this is relative to Mario, so a negative + * distance will focus in front of Mario, and a positive distance will focus behind him. + */ +void focus_in_front_of_mario(struct Camera *c, f32 dist, f32 speed) { + Vec3f goalFocus, offset; + + offset[0] = 0.f; + offset[2] = dist; + offset[1] = 100.f; + + offset_rotated(goalFocus, sMarioCamState->pos, offset, sMarioCamState->faceAngle); + approach_vec3f_asymptotic(c->focus, goalFocus, speed, speed, speed); +} + +/** + * Call the event while `start` <= gCutsceneTimer <= `end` + * If `end` is -1, call for the rest of the shot. + */ +void cutscene_event(CameraEvent event, struct Camera *c, s16 start, s16 end) { + if (start <= gCutsceneTimer) { + if (end == -1 || end >= gCutsceneTimer) { + event(c); + } + } +} + +/** + * Set gCutsceneObjSpawn when gCutsceneTimer == `frame`. + * + * @see intro_scene.inc.c for details on which objects are spawned. + */ +void cutscene_spawn_obj(u32 obj, s16 frame) { + if (frame == gCutsceneTimer) { + gCutsceneObjSpawn = obj; + } +} + +/** + * Moves the camera towards the cutscene's focus, stored in sCutsceneVars[3].point + * + * sCutsceneVars[3].point is used as the target point + * sCutsceneVars[0].point is used as the current camera focus during the transition + * + * @param rotPitch constant pitch offset to add to the camera's focus + * @param rotYaw constant yaw offset to add to the camera's focus + */ +void cutscene_goto_cvar_pos(struct Camera *c, f32 goalDist, s16 goalPitch, s16 rotPitch, s16 rotYaw) { + f32 nextDist; + s16 nextPitch, nextYaw; + // The next 2 polar coord points are only used in CUTSCENE_PREPARE_CANNON + f32 cannonDist; + s16 cannonPitch, cannonYaw; + f32 curDist; + s16 curPitch, curYaw; + + vec3f_get_dist_and_angle(sCutsceneVars[3].point, c->pos, &nextDist, &nextPitch, &nextYaw); + + // If over 8000 units away from the cannon, just teleport there + if ((nextDist > 8000.f) && (c->cutscene == CUTSCENE_PREPARE_CANNON)) { + nextDist = goalDist * 4.f; + nextPitch = goalPitch; + vec3f_copy(sCutsceneVars[0].point, sCutsceneVars[3].point); + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelNum == LEVEL_TTM) { + nextYaw = atan2s(sCutsceneVars[3].point[2] - c->areaCenZ, + sCutsceneVars[3].point[0] - c->areaCenX); + } +#endif + } else { + if (c->cutscene == CUTSCENE_PREPARE_CANNON) { + vec3f_get_dist_and_angle(c->pos, sCutsceneVars[0].point, &curDist, &curPitch, &curYaw); + vec3f_get_dist_and_angle(c->pos, sCutsceneVars[3].point, &cannonDist, &cannonPitch, &cannonYaw); + approach_f32_asymptotic_bool(&curDist, cannonDist, 0.1f); + approach_s16_asymptotic_bool(&curPitch, cannonPitch, 15); + approach_s16_asymptotic_bool(&curYaw, cannonYaw, 15); + // Move the current focus, sCutsceneVars[0].point, in the direction towards the cannon + vec3f_set_dist_and_angle(c->pos, sCutsceneVars[0].point, curDist, curPitch, curYaw); + } else { + approach_vec3f_asymptotic(sCutsceneVars[0].point, sCutsceneVars[3].point, 0.1f, 0.1f, 0.1f); + } + } + + approach_f32_asymptotic_bool(&nextDist, goalDist, 0.05f); + approach_s16_asymptotic_bool(&nextPitch, goalPitch, 0x20); + + vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, nextDist, nextPitch, nextYaw); + vec3f_copy(c->focus, sCutsceneVars[0].point); + + // Apply the constant rotation given + pan_camera(c, rotPitch, rotYaw); + vec3f_get_dist_and_angle(c->pos, c->focus, &nextDist, &nextPitch, &nextYaw); + + if (nextPitch < -0x3000) { + nextPitch = -0x3000; + } + if (nextPitch > 0x3000) { + nextPitch = 0x3000; + } + + vec3f_set_dist_and_angle(c->pos, c->focus, nextDist, nextPitch, nextYaw); +} + +/** + * Zero the 10 cvars. + */ +void clear_cutscene_vars(UNUSED struct Camera *c) { + s32 i; + + for (i = 0; i < 10; i++) { + sCutsceneVars[i].unused1 = 0; + vec3_zero(sCutsceneVars[i].point); + vec3_zero(sCutsceneVars[i].unusedPoint); + vec3_zero(sCutsceneVars[i].angle); + sCutsceneVars[i].unused2 = 0; + } +} + +/** + * Start the cutscene, `cutscene`, if it is not already playing. + */ +void start_cutscene(struct Camera *c, u8 cutscene) { + if (c->cutscene != cutscene) { + c->cutscene = cutscene; + clear_cutscene_vars(c); + } +} + +/** + * Play the current cutscene until either gCutsceneTimer reaches the max time, or c->cutscene is set to 0 + * + * Note that CAM_FLAG_SMOOTH_MOVEMENT is cleared while a cutscene is playing, so cutscenes set it for + * the duration they want the flag to be active. + */ +void play_cutscene(struct Camera *c) { + s16 cutsceneDuration; + u8 oldCutscene = c->cutscene; + + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; + +#define CUTSCENE(id, cutscene) \ + case id: \ + cutsceneDuration = cutscene[sCutsceneShot].duration; \ + cutscene[sCutsceneShot].shot(c); \ + break; + + switch (c->cutscene) { + CUTSCENE(CUTSCENE_STAR_SPAWN, sCutsceneStarSpawn) + CUTSCENE(CUTSCENE_RED_COIN_STAR_SPAWN, sCutsceneRedCoinStarSpawn) + CUTSCENE(CUTSCENE_ENDING, sCutsceneEnding) + CUTSCENE(CUTSCENE_GRAND_STAR, sCutsceneGrandStar) + CUTSCENE(CUTSCENE_DOOR_WARP, sCutsceneDoorWarp) + CUTSCENE(CUTSCENE_DOOR_PULL, sCutsceneDoorPull) + CUTSCENE(CUTSCENE_DOOR_PUSH, sCutsceneDoorPush) + CUTSCENE(CUTSCENE_DOOR_PULL_MODE, sCutsceneDoorPullMode) + CUTSCENE(CUTSCENE_DOOR_PUSH_MODE, sCutsceneDoorPushMode) + CUTSCENE(CUTSCENE_ENTER_CANNON, sCutsceneEnterCannon) + CUTSCENE(CUTSCENE_ENTER_PAINTING, sCutsceneEnterPainting) + CUTSCENE(CUTSCENE_DEATH_EXIT, sCutsceneDeathExit) + CUTSCENE(CUTSCENE_EXIT_PAINTING_SUCC, sCutsceneExitPaintingSuccess) + CUTSCENE(CUTSCENE_UNUSED_EXIT, sCutsceneUnusedExit) + CUTSCENE(CUTSCENE_INTRO_PEACH, sCutsceneIntroPeach) + CUTSCENE(CUTSCENE_ENTER_BOWSER_ARENA, sCutsceneEnterBowserArena) + CUTSCENE(CUTSCENE_DANCE_ROTATE, sCutsceneDanceDefaultRotate) + CUTSCENE(CUTSCENE_DANCE_DEFAULT, sCutsceneDanceDefaultRotate) + CUTSCENE(CUTSCENE_DANCE_FLY_AWAY, sCutsceneDanceFlyAway) + CUTSCENE(CUTSCENE_DANCE_CLOSEUP, sCutsceneDanceCloseup) + CUTSCENE(CUTSCENE_KEY_DANCE, sCutsceneKeyDance) + CUTSCENE(CUTSCENE_0F_UNUSED, sCutsceneUnused) + CUTSCENE(CUTSCENE_END_WAVING, sCutsceneEndWaving) + CUTSCENE(CUTSCENE_CREDITS, sCutsceneCredits) + CUTSCENE(CUTSCENE_CAP_SWITCH_PRESS, sCutsceneCapSwitchPress) + CUTSCENE(CUTSCENE_SLIDING_DOORS_OPEN, sCutsceneSlidingDoorsOpen) + CUTSCENE(CUTSCENE_PREPARE_CANNON, sCutscenePrepareCannon) + CUTSCENE(CUTSCENE_UNLOCK_KEY_DOOR, sCutsceneUnlockKeyDoor) + CUTSCENE(CUTSCENE_STANDING_DEATH, sCutsceneStandingDeath) + CUTSCENE(CUTSCENE_ENTER_POOL, sCutsceneEnterPool) + CUTSCENE(CUTSCENE_DEATH_ON_STOMACH, sCutsceneDeathStomach) + CUTSCENE(CUTSCENE_DEATH_ON_BACK, sCutsceneDeathOnBack) + CUTSCENE(CUTSCENE_QUICKSAND_DEATH, sCutsceneQuicksandDeath) + CUTSCENE(CUTSCENE_SUFFOCATION_DEATH, sCutsceneSuffocation) + CUTSCENE(CUTSCENE_EXIT_BOWSER_SUCC, sCutsceneExitBowserSuccess) + CUTSCENE(CUTSCENE_EXIT_BOWSER_DEATH, sCutsceneExitBowserDeath) + CUTSCENE(CUTSCENE_EXIT_SPECIAL_SUCC, sCutsceneExitSpecialSuccess) + CUTSCENE(CUTSCENE_EXIT_WATERFALL, sCutsceneExitWaterfall) + CUTSCENE(CUTSCENE_EXIT_FALL_WMOTR, sCutsceneFallToCastleGrounds) + CUTSCENE(CUTSCENE_NONPAINTING_DEATH, sCutsceneNonPaintingDeath) + CUTSCENE(CUTSCENE_DIALOG, sCutsceneDialog) + CUTSCENE(CUTSCENE_READ_MESSAGE, sCutsceneReadMessage) + CUTSCENE(CUTSCENE_RACE_DIALOG, sCutsceneDialog) + CUTSCENE(CUTSCENE_ENTER_PYRAMID_TOP, sCutsceneEnterPyramidTop) + CUTSCENE(CUTSCENE_SSL_PYRAMID_EXPLODE, sCutscenePyramidTopExplode) + } + +#undef CUTSCENE + + if ((cutsceneDuration != 0) && !(gCutsceneTimer & CUTSCENE_STOP)) { + if (gCutsceneTimer < CUTSCENE_LOOP) { + gCutsceneTimer++; + } + //! Because gCutsceneTimer is often set to 0x7FFF (CUTSCENE_LOOP), this conditional can only + //! check for == due to overflow + if (gCutsceneTimer == cutsceneDuration) { + sCutsceneShot++; + gCutsceneTimer = 0; + } + } else { + sMarioCamState->cameraEvent = CAM_EVENT_NONE; + sCutsceneShot = 0; + gCutsceneTimer = 0; + } + + sAreaYawChange = 0; + + // The cutscene just ended + if ((c->cutscene == CUTSCENE_NONE) && (oldCutscene != 0)) { + gRecentCutscene = oldCutscene; + } +} + diff --git a/src/camera/cutscene_helpers.h b/src/camera/cutscene_helpers.h new file mode 100644 index 0000000000..10d9497765 --- /dev/null +++ b/src/camera/cutscene_helpers.h @@ -0,0 +1,136 @@ +#pragma once + +#include "config/config_game.h" +#include "game/camera.h" +#include "game/save_file.h" + +/** + * The same type as a CameraEvent, but because these are generally longer, and happen in sequential + * order, they're are called "shots," a term taken from cinematography. + * + * To further tell the difference: CutsceneShots usually call multiple CameraEvents at once, but only + * one CutsceneShot is ever called on a given frame. + */ +typedef CameraEvent CutsceneShot; + +/** + * A camera shot that is active for a number of frames. + * Together, a sequence of shots makes up a cutscene. + */ +struct Cutscene { + /// The function that gets called. + CutsceneShot shot; + /// How long the shot lasts. + s16 duration; +}; + +extern u8 sCutsceneDialogResponse; +extern u8 sObjectCutscene; +extern u8 gRecentCutscene; +extern u8 sCutsceneDialogResponse; +extern s16 sCutsceneDialogID; +extern s16 gCutsceneTimer; +extern s16 sCutsceneShot; +extern s16 sCutsceneSplineSegment; +extern s32 gObjCutsceneDone; +extern u32 gCutsceneObjSpawn; +extern f32 sCutsceneSplineSegmentProgress; +extern Vec3f sPlayer2FocusOffset; +extern struct CutsceneVariable sCutsceneVars[10]; +extern struct Object *gCutsceneFocus; + +// Cutscene Interface +void init_cutscene_vars(); +void reset_cutscene_vars(); +void start_cutscene(struct Camera *c, u8 cutscene); +void play_cutscene(struct Camera *c); +void cutscene_reset_spline(void); +void init_current_credits_spline(); + +// Cutscene Helpers +void stop_cutscene_and_retrieve_stored_info(struct Camera *c); +void cutscene_goto_cvar_pos(struct Camera *c, f32 goalDist, s16 goalPitch, s16 rotPitch, s16 rotYaw); +void cutscene_soften_music(UNUSED struct Camera *c); +void cutscene_unsoften_music(UNUSED struct Camera *c); +void cutscene_update_camera_yaw(struct Camera *c); +s16 cutscene_common_set_dialog_state(s32 state); +void cutscene_stop_dialog(UNUSED struct Camera *c); +void cutscene_shake_explosion(UNUSED struct Camera *c); +void focus_in_front_of_mario(struct Camera *c, f32 dist, f32 speed); +void set_focus_rel_mario(struct Camera *c, f32 leftRight, f32 yOff, f32 forwBack, s16 yawOff); +void store_info_star(struct Camera *c); +void retrieve_info_star(struct Camera *c); +void store_info_cannon(struct Camera *c); +void retrieve_info_cannon(struct Camera *c); +void cutscene_mario_dialog_look_up(UNUSED struct Camera *c); +void cutscene_mario_dialog_look_front(UNUSED struct Camera *c); +void cutscene_mario_dialog_look_down(UNUSED struct Camera *c); +void set_flag_post_door(struct Camera *c); +void reset_pan_distance(UNUSED struct Camera *c); +s32 move_point_along_spline(Vec3f p, struct CutsceneSplinePoint spline[], s16 *splineSegment, f32 *progress); +void trigger_cutscene_dialog(s32 trigger); +u8 get_cutscene_from_mario_status(struct Camera *c); +void cutscene_event(CameraEvent event, struct Camera * c, s16 start, s16 end); +void star_dance_bound_yaw(struct Camera *c, s16 absYaw, s16 yawMax); +void cutscene_set_fov_shake_preset(u8 preset); +void player2_rotate_cam(struct Camera *c, s16 minPitch, s16 maxPitch, s16 minYaw, s16 maxYaw); + +// Object cutscene functions +s16 cutscene_object(u8 cutscene, struct Object *obj); +s16 cutscene_object_with_dialog(u8 cutscene, struct Object *obj, s16 dialogID); +s16 cutscene_object_without_dialog(u8 cutscene, struct Object *obj); +void start_object_cutscene_without_focus(u8 cutscene); +void cutscene_spawn_obj(u32 obj, s16 frame); + +// Shared cutscene functions +void cutscene_dance_move_to_mario(struct Camera *c); +void cutscene_death_stomach_start(struct Camera *c); +void water_death_move_to_mario_side(struct Camera *c); +void cutscene_double_doors_end(struct Camera *c); +void cutscene_quicksand_death(struct Camera *c); +void cutscene_exit_to_castle_grounds_end(struct Camera *c); + +extern struct Cutscene sCutsceneCapSwitchPress[]; +extern struct Cutscene sCutsceneCredits[]; +extern struct Cutscene sCutsceneDanceCloseup[]; +extern struct Cutscene sCutsceneDanceDefaultRotate[]; +extern struct Cutscene sCutsceneDanceFlyAway[]; +extern struct Cutscene sCutsceneDeathOnBack[]; +extern struct Cutscene sCutsceneStandingDeath[]; +extern struct Cutscene sCutsceneDeathStomach[]; +extern struct Cutscene sCutsceneDialog[]; +extern struct Cutscene sCutsceneDoorPull[]; +extern struct Cutscene sCutsceneDoorPullMode[]; +extern struct Cutscene sCutsceneDoorPush[]; +extern struct Cutscene sCutsceneDoorPushMode[]; +extern struct Cutscene sCutsceneDoorWarp[]; +extern struct Cutscene sCutsceneEndWaving[]; +extern struct Cutscene sCutsceneEnding[]; +extern struct Cutscene sCutsceneEnterBowserArena[]; +extern struct Cutscene sCutsceneEnterCannon[]; +extern struct Cutscene sCutsceneEnterPainting[]; +extern struct Cutscene sCutsceneEnterPool[]; +extern struct Cutscene sCutsceneEnterPyramidTop[]; +extern struct Cutscene sCutsceneGrandStar[]; +extern struct Cutscene sCutsceneIntroPeach[]; +extern struct Cutscene sCutsceneKeyDance[]; +extern struct Cutscene sCutsceneDeathExit[]; +extern struct Cutscene sCutsceneExitBowserDeath[]; +extern struct Cutscene sCutsceneExitBowserSuccess[]; +extern struct Cutscene sCutsceneExitPaintingSuccess[]; +extern struct Cutscene sCutsceneExitSpecialSuccess[]; +extern struct Cutscene sCutsceneExitWaterfall[]; +extern struct Cutscene sCutsceneFallToCastleGrounds[]; +extern struct Cutscene sCutsceneNonPaintingDeath[]; +extern struct Cutscene sCutsceneUnusedExit[]; +extern struct Cutscene sCutscenePrepareCannon[]; +extern struct Cutscene sCutscenePyramidTopExplode[]; +extern struct Cutscene sCutsceneQuicksandDeath[]; +extern struct Cutscene sCutsceneReadMessage[]; +extern struct Cutscene sCutsceneRedCoinStarSpawn[]; +extern struct Cutscene sCutsceneSlidingDoorsOpen[]; +extern struct Cutscene sCutsceneStarSpawn[]; +extern struct Cutscene sCutsceneSuffocation[]; +extern struct Cutscene sCutsceneUnlockKeyDoor[]; +extern struct Cutscene sCutsceneUnused[]; +extern struct Cutscene sCutsceneWaterDeath[]; diff --git a/src/camera/cutscenes/cap_switch_press.c b/src/camera/cutscenes/cap_switch_press.c new file mode 100644 index 0000000000..be26edd2e2 --- /dev/null +++ b/src/camera/cutscenes/cap_switch_press.c @@ -0,0 +1,132 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/save_file.h" + +/** + * Cutscene that plays when Mario presses a cap switch. + */ + +void cap_switch_save(UNUSED s16 param) { + save_file_do_save(gCurrSaveFileNum - 1); +} + +/** + * Set cvars: + * cvar3 is an offset applied to the camera's rotation around Mario. It starts at 0x1200 + * cvar 1 is more complicated: + * First the yaw from Mario to the camera is calculated. cvar1 is the high byte of the difference + * between that yaw and Mario's faceAngle plus 0x1200. The reason for taking the high byte is + * because cvar1 rotates until is reaches 0, so it's important that it's a multiple of 0x100. + */ +void cutscene_cap_switch_press_start(struct Camera *c) { + store_info_star(c); + s16 yaw = calculate_yaw(sMarioCamState->pos, c->pos); + sCutsceneVars[3].angle[1] = 0x1200; + // Basically the amount of rotation to get from behind Mario to in front of Mario + sCutsceneVars[1].angle[1] = (yaw - (sMarioCamState->faceAngle[1] + sCutsceneVars[3].angle[1])) & 0xFF00; +} + +/** + * Rotate around Mario. As each cvar stops updating, the rotation slows until the camera ends up in + * front of Mario. + */ +void cutscene_cap_switch_press_rotate_around_mario(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + + // cvar3 wraps around until it reaches 0x1000 + if (sCutsceneVars[3].angle[1] != 0x1000) { + sCutsceneVars[3].angle[1] += 0x100; + } + + // cvar1 wraps until 0 + if (sCutsceneVars[1].angle[1] != 0) { + sCutsceneVars[1].angle[1] += 0x100; + } + + yaw = sMarioCamState->faceAngle[1] + sCutsceneVars[3].angle[1] + sCutsceneVars[1].angle[1]; + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Move the camera slightly downwards. + */ +void cutscene_cap_switch_press_lower_cam(struct Camera *c) { + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, -0x20, 0); +} + +/** + * Move the camera closer to Mario. + */ +void cutscene_cap_switch_press_approach_mario(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 195.f, 0.2f); + approach_s16_asymptotic_bool(&pitch, 0, 0x10); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); + + approach_f32_asymptotic_bool(&c->focus[0], sMarioCamState->pos[0], 0.1f); + approach_f32_asymptotic_bool(&c->focus[1], sMarioCamState->pos[1] + 110.f, 0.1f); + approach_f32_asymptotic_bool(&c->focus[2], sMarioCamState->pos[2], 0.1f); +} + +/** + * Pan the camera left so that Mario is on the right side of the screen when the camera stops spinning. + */ +void cutscene_cap_switch_press_pan_left(struct Camera *c) { + vec3f_copy(c->focus, sMarioCamState->pos); + c->focus[1] += 110.f; + camera_approach_s16_symmetric_bool(&sCutsceneVars[0].angle[1], 0x800, 0x20); + pan_camera(c, sCutsceneVars[0].angle[0], sCutsceneVars[0].angle[1]); +} + +/** + * Create a dialog box with the cap switch's text. + */ +void cutscene_cap_switch_press_create_dialog(UNUSED struct Camera *c) { + create_dialog_box_with_response(gCutsceneFocus->oBehParams2ndByte + DIALOG_010); +} + +static UNUSED void unused_cap_switch_retrieve_info(struct Camera *c) { + retrieve_info_star(c); + transition_next_state(c, 30); +} + +void cutscene_cap_switch_press(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + + cutscene_event(cutscene_cap_switch_press_start, c, 0, 0); + cutscene_event(cutscene_cap_switch_press_approach_mario, c, 0, 30); + cutscene_event(cutscene_cap_switch_press_pan_left, c, 0, -1); + cutscene_event(cutscene_cap_switch_press_rotate_around_mario, c, 30, -1); + cutscene_event(cutscene_cap_switch_press_lower_cam, c, 10, 70); + cutscene_event(cutscene_cap_switch_press_create_dialog, c, 10, 10); + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + + if (gDialogResponse != DIALOG_RESPONSE_NONE) { + sCutsceneVars[4].angle[0] = gDialogResponse; + } + + if ((get_dialog_id() == DIALOG_NONE) && (sCutsceneVars[4].angle[0] != 0)) { + sCutsceneDialogResponse = sCutsceneVars[4].angle[0]; + if (sCutsceneVars[4].angle[0] == 1) { + cap_switch_save(gCutsceneFocus->oBehParams2ndByte); + } + stop_cutscene_and_retrieve_stored_info(c); + transition_next_state(c, 30); + } +} + +struct Cutscene sCutsceneCapSwitchPress[] = { + { cutscene_cap_switch_press, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/credits.c b/src/camera/cutscenes/credits.c new file mode 100644 index 0000000000..b636d2b24b --- /dev/null +++ b/src/camera/cutscenes/credits.c @@ -0,0 +1,496 @@ +/** + * The game's credits. + */ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/save_file.h" + +/** + * The current spline that controls the camera's position during the credits. + */ +static struct CutsceneSplinePoint sCurCreditsSplinePos[32]; + +/** + * The current spline that controls the camera's focus during the credits. + */ +static struct CutsceneSplinePoint sCurCreditsSplineFocus[32]; + + +void init_current_credits_spline() { + for (int i = 0; i < 32; i++) { + sCurCreditsSplinePos[i].index = -1; + sCurCreditsSplineFocus[i].index = -1; + } +} + +/* + * credits spline paths. + * TODO: Separate these into their own file(s) + */ +struct CutsceneSplinePoint sBobCreditsSplinePositions[] = { + { 1, 0, { 5984, 3255, 4975 } }, + { 2, 0, { 4423, 3315, 1888 } }, + { 3, 0, { 776, 2740, -1825 } }, + { 4, 0, { -146, 3894, -3167 } }, + { -1, 0, { 741, 4387, -5474 } } +}; + +struct CutsceneSplinePoint sBobCreditsSplineFocus[] = { + { 0, 30, { 5817, 3306, 4507 } }, + { 0, 40, { 4025, 3378, 1593 } }, + { 0, 50, { 1088, 2652, -2205 } }, + { 0, 60, { 205, 3959, -3517 } }, + { -1, 60, { 1231, 4400, -5649 } } +}; + +struct CutsceneSplinePoint sWfCreditsSplinePositions[] = { + { 0, 0, { -301, 1399, 2643 } }, + { 0, 0, { -182, 2374, 4572 } }, + { 0, 0, { 4696, 3864, 413 } }, + { 0, 0, { 1738, 4891, -1516 } }, + { -1, 0, { 1783, 4891, -1516 } } +}; + +struct CutsceneSplinePoint sWfCreditsSplineFocus[] = { + { 1, 30, { -249, 1484, 2153 } }, + { 2, 40, { -200, 2470, 4082 } }, + { 3, 40, { 4200, 3916, 370 } }, + { 4, 40, { 1523, 4976, -1072 } }, + { -1, 40, { 1523, 4976, -1072 } } +}; + +struct CutsceneSplinePoint sJrbCreditsSplinePositions[] = { + { 0, 0, { 5538, -4272, 2376 } }, + { 0, 0, { 5997, -3303, 2261 } }, + { 0, 0, { 6345, -3255, 2179 } }, + { 0, 0, { 6345, -3255, 2179 } }, + { -1, 0, { 6694, -3203, 2116 } } +}; + +struct CutsceneSplinePoint sJrbCreditsSplineFocus[] = { + { 0, 50, { 5261, -4683, 2443 } }, + { 0, 50, { 5726, -3675, 2456 } }, + { 0, 50, { 6268, -2817, 2409 } }, + { 0, 50, { 6596, -2866, 2369 } }, + { -1, 50, { 7186, -3153, 2041 } } +}; + +struct CutsceneSplinePoint sCcmSlideCreditsSplinePositions[] = { + { 0, 0, { -6324, 6745, -5626 } }, + { 1, 0, { -6324, 6745, -5626 } }, + { 2, 0, { -6108, 6762, -5770 } }, + { 3, 0, { -5771, 6787, -5962 } }, + { -1, 0, { -5672, 6790, -5979 } } +}; + +struct CutsceneSplinePoint sCcmSlideCreditsSplineFocus[] = { + { 0, 50, { -5911, 6758, -5908 } }, + { 1, 50, { -5911, 6758, -5908 } }, + { 2, 50, { -5652, 6814, -5968 } }, + { 3, 50, { -5277, 6801, -6043 } }, + { -1, 50, { -5179, 6804, -6060 } } +}; + +struct CutsceneSplinePoint sBbhCreditsSplinePositions[] = { + { 1, 0, { 1088, 341, 2447 } }, + { 2, 0, { 1338, 610, 2808 } }, + { 3, 0, { 2267, 1612, 2966 } }, + { -1, 0, { 2296, 1913, 2990 } } +}; + +struct CutsceneSplinePoint sBbhCreditsSplineFocus[] = { + { 1, 50, { 1160, 263, 1958 } }, + { 2, 50, { 1034, 472, 2436 } }, + { 3, 50, { 1915, 1833, 2688 } }, + { -1, 50, { 2134, 2316, 2742 } } +}; + +struct CutsceneSplinePoint sHmcCreditsSplinePositions[] = { + { 1, 0, { -5952, 1807, -5882 } }, + { 2, 0, { -5623, 1749, -4863 } }, + { 3, 0, { -5472, 1955, -2520 } }, + { 4, 0, { -5544, 1187, -1085 } }, + { -1, 0, { -5547, 391, -721 } } +}; + +struct CutsceneSplinePoint sHmcCreditsSplineFocus[] = { + { 1, 210, { -5952, 1884, -6376 } }, + { 2, 58, { -5891, 1711, -5283 } }, + { 3, 30, { -5595, 1699, -2108 } }, + { 4, 31, { -5546, 794, -777 } }, + { -1, 31, { -5548, -85, -572 } } +}; + +struct CutsceneSplinePoint sThiWigglerCreditsSplinePositions[] = { + { 1, 0, { -1411, 2474, -1276 } }, + { 2, 0, { -1606, 2479, -434 } }, + { -1, 0, { -1170, 2122, 1337 } } +}; + +struct CutsceneSplinePoint sThiWigglerCreditsSplineFocus[] = { + { 1, 50, { -1053, 2512, -928 } }, + { 2, 50, { -1234, 2377, -114 } }, + { -1, 50, { -758, 2147, 1054 } } +}; + +struct CutsceneSplinePoint sVolcanoCreditsSplinePositions[] = { + { 0, 0, { -1445, 1094, 1617 } }, + { 0, 0, { -1509, 649, 871 } }, + { 0, 0, { -1133, 420, -248 } }, + { 0, 0, { -778, 359, -1052 } }, + { 0, 0, { -565, 260, -1730 } }, + { -1, 0, { 1274, 473, -275 } } +}; + +struct CutsceneSplinePoint sVolcanoCreditsSplineFocus[] = { + { 0, 50, { -1500, 757, 1251 } }, + { 0, 50, { -1401, 439, 431 } }, + { 0, 50, { -749, 270, -532 } }, + { 0, 50, { -396, 270, -1363 } }, + { 0, 50, { -321, 143, -2151 } }, + { -1, 50, { 1002, 460, -694 } } +}; + +struct CutsceneSplinePoint sSslCreditsSplinePositions[] = { + { 0, 0, { -4262, 4658, -5015 } }, + { 0, 0, { -3274, 2963, -4661 } }, + { 0, 0, { -2568, 812, -6528 } }, + { 0, 0, { -414, 660, -7232 } }, + { 0, 0, { 1466, 660, -6898 } }, + { -1, 0, { 2724, 660, -6298 } } +}; + +struct CutsceneSplinePoint sSslCreditsSplineFocus[] = { + { 0, 50, { -4083, 4277, -4745 } }, + { 0, 50, { -2975, 2574, -4759 } }, + { 0, 50, { -2343, 736, -6088 } }, + { 0, 50, { -535, 572, -6755 } }, + { 0, 50, { 1311, 597, -6427 } }, + { -1, 50, { 2448, 612, -5884 } } +}; + +struct CutsceneSplinePoint sDddCreditsSplinePositions[] = { + { 0, 0, { -874, -4933, 366 } }, + { 0, 0, { -1463, -4782, 963 } }, + { 0, 0, { -1893, -4684, 1303 } }, + { 0, 0, { -2818, -4503, 1583 } }, + { 0, 0, { -4095, -2924, 730 } }, + { 0, 0, { -4737, -1594, -63 } }, + { -1, 0, { -4681, -1084, -623 } } +}; + +struct CutsceneSplinePoint sDddCreditsSplineFocus[] = { + { 0, 50, { -1276, -4683, 622 } }, + { 0, 50, { -1858, -4407, 1097 } }, + { 0, 50, { -2324, -4332, 1318 } }, + { 0, 50, { -3138, -4048, 1434 } }, + { 0, 50, { -4353, -2444, 533 } }, + { 0, 50, { -4807, -1169, -436 } }, + { -1, 50, { -4665, -664, -1007 } } +}; + +struct CutsceneSplinePoint sSlCreditsSplinePositions[] = { + { 0, 0, { 939, 6654, 6196 } }, + { 0, 0, { 1873, 5160, 3714 } }, + { 0, 0, { 3120, 3564, 1314 } }, + { -1, 0, { 2881, 4231, 573 } } +}; + +struct CutsceneSplinePoint sSlCreditsSplineFocus[] = { + { 0, 50, { 875, 6411, 5763 } }, + { 0, 50, { 1659, 4951, 3313 } }, + { 0, 50, { 2630, 3565, 1215 } }, + { -1, 50, { 2417, 4056, 639 } } +}; + +struct CutsceneSplinePoint sWdwCreditsSplinePositions[] = { + { 0, 0, { 3927, 2573, 3685 } }, + { 0, 0, { 2389, 2054, 1210 } }, + { 0, 0, { 2309, 2069, 22 } }, + { -1, 0, { 2122, 2271, -979 } } +}; + +struct CutsceneSplinePoint sWdwCreditsSplineFocus[] = { + { 0, 50, { 3637, 2460, 3294 } }, + { 0, 50, { 1984, 2067, 918 } }, + { 0, 50, { 1941, 2255, -261 } }, + { -1, 50, { 1779, 2587, -1158 } } +}; + +struct CutsceneSplinePoint sTtmCreditsSplinePositions[] = { + { 0, 0, { 386, 2535, 644 } }, + { 0, 0, { 1105, 2576, 918 } }, + { 0, 0, { 3565, 2261, 2098 } }, + { 0, 0, { 6715, -2791, 4554 } }, + { 0, 0, { 3917, -3130, 3656 } }, + { -1, 0, { 3917, -3130, 3656 } } +}; + +struct CutsceneSplinePoint sTtmCreditsSplineFocus[] = { + { 1, 50, { 751, 2434, 318 } }, + { 2, 50, { 768, 2382, 603 } }, + { 3, 60, { 3115, 2086, 1969 } }, + { 4, 30, { 6370, -3108, 4727 } }, + { 5, 50, { 4172, -3385, 4001 } }, + { -1, 50, { 4172, -3385, 4001 } } +}; + +struct CutsceneSplinePoint sThiHugeCreditsSplinePositions[] = { + { 0, 0, { 6990, -1000, -4858 } }, + { 0, 0, { 7886, -1055, 2878 } }, + { 0, 0, { 1952, -1481, 10920 } }, + { 0, 0, { -1684, -219, 2819 } }, + { 0, 0, { -2427, -131, 2755 } }, + { 0, 0, { -3246, 416, 3286 } }, + { -1, 0, { -3246, 416, 3286 } } +}; + +struct CutsceneSplinePoint sThiHugeCreditsSplineFocus[] = { + { 1, 70, { 7022, -965, -5356 } }, + { 2, 40, { 7799, -915, 2405 } }, + { 3, 60, { 1878, -1137, 10568 } }, + { 4, 50, { -1931, -308, 2394 } }, + { 5, 50, { -2066, -386, 2521 } }, + { 6, 50, { -2875, 182, 3045 } }, + { -1, 50, { -2875, 182, 3045 } } +}; + +struct CutsceneSplinePoint sTtcCreditsSplinePositions[] = { + { 1, 0, { -1724, 277, -994 } }, + { 2, 0, { -1720, 456, -995 } }, + { 3, 0, { -1655, 810, -1014 } }, + { -1, 0, { -1753, 883, -1009 } } +}; + +struct CutsceneSplinePoint sTtcCreditsSplineFocus[] = { + { 1, 50, { -1554, 742, -1063 } }, + { 2, 50, { -1245, 571, -1102 } }, + { 3, 50, { -1220, 603, -1151 } }, + { -1, 50, { -1412, 520, -1053 } } +}; + +struct CutsceneSplinePoint sRrCreditsSplinePositions[] = { + { 0, 0, { -1818, 4036, 97 } }, + { 0, 0, { -575, 3460, -505 } }, + { 0, 0, { 1191, 3611, -1134 } }, + { -1, 0, { 2701, 3777, -3686 } } +}; + +struct CutsceneSplinePoint sRrCreditsSplineFocus[] = { + { 0, 50, { -1376, 3885, -81 } }, + { 0, 50, { -146, 3343, -734 } }, + { 0, 50, { 1570, 3446, -1415 } }, + { -1, 50, { 2794, 3627, -3218 } } +}; + +struct CutsceneSplinePoint sSaCreditsSplinePositions[] = { + { 0, 0, { -295, -396, -585 } }, + { 1, 0, { -295, -396, -585 } }, + { 2, 0, { -292, -856, -573 } }, + { 3, 0, { -312, -856, -541 } }, + { -1, 0, { 175, -856, -654 } } +}; + +struct CutsceneSplinePoint sSaCreditsSplineFocus[] = { + { 0, 50, { -175, -594, -142 } }, + { 1, 50, { -175, -594, -142 } }, + { 2, 50, { -195, -956, -92 } }, + { 3, 50, { -572, -956, -150 } }, + { -1, 50, { -307, -956, -537 } } +}; + +struct CutsceneSplinePoint sCotmcCreditsSplinePositions[] = { + { 0, 0, { -296, 495, 1607 } }, + { 0, 0, { -430, 541, 654 } }, + { 0, 0, { -466, 601, -359 } }, + { 0, 0, { -217, 433, -1549 } }, + { -1, 0, { -95, 366, -2922 } } +}; + +struct CutsceneSplinePoint sCotmcCreditsSplineFocus[] = { + { 0, 50, { -176, 483, 2092 } }, + { 0, 50, { -122, 392, 1019 } }, + { 0, 50, { -268, 450, -792 } }, + { 0, 50, { -172, 399, -2046 } }, + { -1, 50, { -51, 355, -3420 } } +}; + +struct CutsceneSplinePoint sDddSubCreditsSplinePositions[] = { + { 0, 0, { 4656, 2171, 5028 } }, + { 0, 0, { 4548, 1182, 4596 } }, + { 0, 0, { 5007, 813, 3257 } }, + { 0, 0, { 5681, 648, 1060 } }, + { -1, 0, { 4644, 774, 113 } } +}; + +struct CutsceneSplinePoint sDddSubCreditsSplineFocus[] = { + { 0, 50, { 4512, 2183, 4549 } }, + { 0, 50, { 4327, 838, 4308 } }, + { 0, 50, { 4774, 749, 2819 } }, + { 0, 50, { 5279, 660, 763 } }, + { -1, 50, { 4194, 885, -75 } } +}; + +struct CutsceneSplinePoint sCcmOutsideCreditsSplinePositions[] = { + { 1, 0, { 1427, -1387, 5409 } }, + { 2, 0, { -1646, -1536, 4526 } }, + { 3, 0, { -3852, -1448, 3913 } }, + { -1, 0, { -5199, -1366, 1886 } } +}; + +struct CutsceneSplinePoint sCcmOutsideCreditsSplineFocus[] = { + { 1, 50, { 958, -1481, 5262 } }, + { 2, 50, { -2123, -1600, 4391 } }, + { 3, 50, { -3957, -1401, 3426 } }, + { -1, 50, { -4730, -1215, 1795 } } +}; + +void init_spline_point(struct CutsceneSplinePoint *splinePoint, s8 index, u8 speed, Vec3s point) { + splinePoint->index = index; + splinePoint->speed = speed; + vec3s_copy(splinePoint->point, point); +} + +// TODO: (Scrub C) +void copy_spline_segment(struct CutsceneSplinePoint dst[], struct CutsceneSplinePoint src[]) { + s32 j = 0; + s32 i = 0; + + init_spline_point(&dst[i], src[j].index, src[j].speed, src[j].point); + i++; + do { + do { + init_spline_point(&dst[i], src[j].index, src[j].speed, src[j].point); + i++; + j++; + } while (src[j].index != -1); + } while (j > 16); + + // Create the end of the spline by duplicating the last point + init_spline_point(&dst[i + 0], 0, src[j].speed, src[j].point); + init_spline_point(&dst[i + 1], 0, 0, src[j].point); + init_spline_point(&dst[i + 2], 0, 0, src[j].point); + init_spline_point(&dst[i + 3], -1, 0, src[j].point); +} + +/** + * Called on the first frame of the credits. Resets the spline progress. + */ +void cutscene_credits_reset_spline(UNUSED struct Camera *c) { + cutscene_reset_spline(); +} + +/** + * Follow splines through the courses of the game. + */ +void cutscene_credits(struct Camera *c) { + struct CutsceneSplinePoint *focus, *pos; + + cutscene_event(cutscene_credits_reset_spline, c, 0, 0); + + switch (gCurrLevelArea) { + case AREA_BOB: + pos = sBobCreditsSplinePositions; + focus = sBobCreditsSplineFocus; + break; + case AREA_WF: + pos = sWfCreditsSplinePositions; + focus = sWfCreditsSplineFocus; + break; + case AREA_JRB_MAIN: + pos = sJrbCreditsSplinePositions; + focus = sJrbCreditsSplineFocus; + break; + case AREA_CCM_SLIDE: + pos = sCcmSlideCreditsSplinePositions; + focus = sCcmSlideCreditsSplineFocus; + break; + case AREA_BBH: + pos = sBbhCreditsSplinePositions; + focus = sBbhCreditsSplineFocus; + break; + case AREA_HMC: + pos = sHmcCreditsSplinePositions; + focus = sHmcCreditsSplineFocus; + break; + case AREA_THI_WIGGLER: + pos = sThiWigglerCreditsSplinePositions; + focus = sThiWigglerCreditsSplineFocus; + break; + case AREA_LLL_VOLCANO: + pos = sVolcanoCreditsSplinePositions; + focus = sVolcanoCreditsSplineFocus; + break; + case AREA_SSL_OUTSIDE: + pos = sSslCreditsSplinePositions; + focus = sSslCreditsSplineFocus; + break; + case AREA_DDD_WHIRLPOOL: + pos = sDddCreditsSplinePositions; + focus = sDddCreditsSplineFocus; + break; + case AREA_SL_OUTSIDE: + pos = sSlCreditsSplinePositions; + focus = sSlCreditsSplineFocus; + break; + case AREA_WDW_MAIN: + pos = sWdwCreditsSplinePositions; + focus = sWdwCreditsSplineFocus; + break; + case AREA_TTM_OUTSIDE: + pos = sTtmCreditsSplinePositions; + focus = sTtmCreditsSplineFocus; + break; + case AREA_THI_HUGE: + pos = sThiHugeCreditsSplinePositions; + focus = sThiHugeCreditsSplineFocus; + break; + case AREA_TTC: + pos = sTtcCreditsSplinePositions; + focus = sTtcCreditsSplineFocus; + break; + case AREA_RR: + pos = sRrCreditsSplinePositions; + focus = sRrCreditsSplineFocus; + break; + case AREA_SA: + pos = sSaCreditsSplinePositions; + focus = sSaCreditsSplineFocus; + break; + case AREA_COTMC: + pos = sCotmcCreditsSplinePositions; + focus = sCotmcCreditsSplineFocus; + break; + case AREA_DDD_SUB: + pos = sDddSubCreditsSplinePositions; + focus = sDddSubCreditsSplineFocus; + break; + case AREA_CCM_OUTSIDE: + //! Checks if the "Snowman's Lost His Head" star was collected. The credits likely would + //! have avoided the snowman if the player didn't collect that star, but in the end the + //! developers decided against it. + if (save_file_get_star_flags(gCurrSaveFileNum - 1, COURSE_NUM_TO_INDEX(gCurrCourseNum)) & (1 << 4)) { + pos = sCcmOutsideCreditsSplinePositions; + focus = sCcmOutsideCreditsSplineFocus; + } else { + pos = sCcmOutsideCreditsSplinePositions; + focus = sCcmOutsideCreditsSplineFocus; + } + break; + default: + pos = sCcmOutsideCreditsSplinePositions; + focus = sCcmOutsideCreditsSplineFocus; + } + + copy_spline_segment(sCurCreditsSplinePos, pos); + copy_spline_segment(sCurCreditsSplineFocus, focus); + move_point_along_spline(c->pos, sCurCreditsSplinePos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + move_point_along_spline(c->focus, sCurCreditsSplineFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + player2_rotate_cam(c, -0x2000, 0x2000, -0x4000, 0x4000); +} + +struct Cutscene sCutsceneCredits[] = { + { cutscene_credits, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/dance_closeup.c b/src/camera/cutscenes/dance_closeup.c new file mode 100644 index 0000000000..08f23a0d01 --- /dev/null +++ b/src/camera/cutscenes/dance_closeup.c @@ -0,0 +1,119 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Star dance cutscene. + * The camera moves in for a closeup on Mario. Used in tight spaces and underwater. + */ + +/** + * Start the closeup dance cutscene by restricting the camera's yaw in certain areas. + * Store the camera's focus in cvar9. + */ +void cutscene_dance_closeup_start(struct Camera *c) { +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if ((gLastCompletedStarNum == 4) && (gCurrCourseNum == COURSE_JRB)) { + star_dance_bound_yaw(c, 0x0, 0x4000); + } + if ((gLastCompletedStarNum == 1) && (gCurrCourseNum == COURSE_DDD)) { + star_dance_bound_yaw(c, 0x8000, 0x5000); + } + if ((gLastCompletedStarNum == 5) && (gCurrCourseNum == COURSE_WDW)) { + star_dance_bound_yaw(c, 0x8000, 0x800); + } +#endif + + vec3f_copy(sCutsceneVars[9].point, c->focus); + //! cvar8 is unused in the closeup cutscene + sCutsceneVars[8].angle[0] = 0x2000; +} + +/** + * Focus the camera on Mario eye height. + */ +void cutscene_dance_closeup_focus_mario(struct Camera *c) { + Vec3f marioPos; + + vec3f_set(marioPos, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); + approach_vec3f_asymptotic(sCutsceneVars[9].point, marioPos, 0.2f, 0.2f, 0.2f); + vec3f_copy(c->focus, sCutsceneVars[9].point); +} + +/** + * Fly above Mario, looking down. + */ +void cutscene_dance_closeup_fly_above(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + s16 goalPitch = 0x1800; +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if ((gLastCompletedStarNum == 6 && gCurrCourseNum == COURSE_SL) || + (gLastCompletedStarNum == 4 && gCurrCourseNum == COURSE_TTC)) { + goalPitch = 0x800; + } +#endif + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 800.f, 0.05f); + approach_s16_asymptotic_bool(&pitch, goalPitch, 16); + approach_s16_asymptotic_bool(&yaw, c->yaw, 8); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Fly closer right when Mario gives the peace sign. + */ +void cutscene_dance_closeup_fly_closer(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 240.f, 0.4f); + approach_s16_asymptotic_bool(&yaw, c->yaw, 8); + approach_s16_asymptotic_bool(&pitch, 0x1000, 5); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Zoom in by increasing fov to 80 degrees. Most dramatic zoom in the game. + */ +void cutscene_dance_closeup_zoom(UNUSED struct Camera *c) { + set_fov_function(CAM_FOV_APP_80); +} + +/** + * Shake fov, starts on the first frame Mario has the peace sign up. + */ +void cutscene_dance_closeup_shake_fov(UNUSED struct Camera *c) { + set_fov_shake(0x300, 0x30, 0x8000); +} + +/** + * The camera moves in for a closeup on Mario. Used for stars that are underwater or in tight places. + */ +void cutscene_dance_closeup(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + + if (sMarioCamState->action == ACT_STAR_DANCE_WATER) { + cutscene_event(cutscene_dance_closeup_start, c, 0, 0); + cutscene_event(cutscene_dance_closeup_focus_mario, c, 0, -1); + cutscene_event(cutscene_dance_closeup_fly_above, c, 0, 62); + cutscene_event(cutscene_dance_closeup_fly_closer, c, 63, -1); + cutscene_event(cutscene_dance_closeup_zoom, c, 63, 63); + cutscene_event(cutscene_dance_closeup_shake_fov, c, 70, 70); + } else { + cutscene_event(cutscene_dance_closeup_start, c, 0, 0); + cutscene_event(cutscene_dance_closeup_focus_mario, c, 0, -1); + // Almost twice as fast as under water + cutscene_event(cutscene_dance_closeup_fly_above, c, 0, 32); + cutscene_event(cutscene_dance_closeup_fly_closer, c, 33, -1); + cutscene_event(cutscene_dance_closeup_zoom, c, 33, 33); + cutscene_event(cutscene_dance_closeup_shake_fov, c, 40, 40); + } + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +struct Cutscene sCutsceneDanceCloseup[] = { + { cutscene_dance_closeup, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/dance_default_rotate.c b/src/camera/cutscenes/dance_default_rotate.c new file mode 100644 index 0000000000..92e9a5d7db --- /dev/null +++ b/src/camera/cutscenes/dance_default_rotate.c @@ -0,0 +1,97 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Star dance cutscene. + * For the default dance, the camera moves closer to Mario, then stays in place. + * For the rotate dance, the camera moves closer and rotates clockwise around Mario. + */ + +/** + * Approach Mario and look up. Since Mario faces the camera when he collects the star, there's no need + * to worry about the camera's yaw. + */ +void cutscene_dance_move_to_mario(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 600.f, 0.3f); + approach_s16_asymptotic_bool(&pitch, 0x1000, 0x10); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +void cutscene_dance_rotate(struct Camera *c) { + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, 0, 0x200); +} + +void cutscene_dance_rotate_move_back(struct Camera *c) { + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, -15.f, 0, 0); +} + +void cutscene_dance_rotate_move_towards_mario(struct Camera *c) { + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 20.f, 0, 0); +} + +/** + * Speculated to be dance-related due to its proximity to the other dance functions + */ +UNUSED static void cutscene_dance_unused(UNUSED struct Camera *c) { +} + +/** + * Slowly turn to the point 100 units in front of Mario + */ +void cutscene_dance_default_focus_mario(struct Camera *c) { + focus_in_front_of_mario(c, -100.f, 0.2f); +} + +/** + * Focus twice as far away as default dance, and move faster. + */ +void cutscene_dance_rotate_focus_mario(struct Camera *c) { + focus_in_front_of_mario(c, -200.f, 0.03f); +} + +void cutscene_dance_shake_fov(UNUSED struct Camera *c) { + set_fov_shake(0x200, 0x28, 0x8000); +} + +/** + * Handles both the default and rotate dance cutscenes. + * In the default dance: the camera moves closer to Mario, then stays in place. + * In the rotate dance: the camera moves closer and rotates clockwise around Mario. + */ +void cutscene_dance_default_rotate(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sYawSpeed = 0; + set_fov_function(CAM_FOV_DEFAULT); + cutscene_event(cutscene_dance_default_focus_mario, c, 0, 20); + cutscene_event(cutscene_dance_move_to_mario, c, 0, 39); + // Shake the camera on the 4th beat of the music, when Mario gives the peace sign. + cutscene_event(cutscene_dance_shake_fov, c, 40, 40); + + if (c->cutscene != CUTSCENE_DANCE_DEFAULT) { // CUTSCENE_DANCE_ROTATE + cutscene_event(cutscene_dance_rotate_focus_mario, c, 75, 102); + cutscene_event(cutscene_dance_rotate, c, 50, -1); + // These two functions move the camera away and then towards Mario. + cutscene_event(cutscene_dance_rotate_move_back, c, 50, 80); + cutscene_event(cutscene_dance_rotate_move_towards_mario, c, 70, 90); + } else { + // secret star, 100 coin star, or bowser red coin star. + if ((sMarioCamState->action != ACT_STAR_DANCE_NO_EXIT) + && (sMarioCamState->action != ACT_STAR_DANCE_WATER) + && (sMarioCamState->action != ACT_STAR_DANCE_EXIT)) { + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + transition_next_state(c, 20); + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + } + } +} + +struct Cutscene sCutsceneDanceDefaultRotate[] = { + { cutscene_dance_default_rotate, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/dance_fly_away.c b/src/camera/cutscenes/dance_fly_away.c new file mode 100644 index 0000000000..2bbbd085a4 --- /dev/null +++ b/src/camera/cutscenes/dance_fly_away.c @@ -0,0 +1,109 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Star dance cutscene. + * The camera moves closer and rotates clockwise around Mario. + */ + +/** + * cvar8.point[2] is the amount to increase distance from Mario + */ +void cutscene_dance_fly_away_start(struct Camera *c) { + Vec3f areaCenter; + + vec3f_copy(sCutsceneVars[9].point, c->focus); + sCutsceneVars[8].point[2] = 65.f; + + if (c->mode == CAMERA_MODE_RADIAL) { + vec3f_set(areaCenter, c->areaCenX, c->areaCenY, c->areaCenZ); + c->yaw = calculate_yaw(areaCenter, c->pos); + c->nextYaw = c->yaw; + } + +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + // Restrict the camera yaw in tight spaces + if ((gLastCompletedStarNum == 6) && (gCurrCourseNum == COURSE_CCM)) { + star_dance_bound_yaw(c, 0x5600, 0x800); + } + if ((gLastCompletedStarNum == 2) && (gCurrCourseNum == COURSE_TTM)) { + star_dance_bound_yaw(c, 0x0, 0x800); + } + if ((gLastCompletedStarNum == 1) && (gCurrCourseNum == COURSE_SL)) { + star_dance_bound_yaw(c, 0x2000, 0x800); + } + if ((gLastCompletedStarNum == 3) && (gCurrCourseNum == COURSE_RR)) { + star_dance_bound_yaw(c, 0x0, 0x800); + } +#endif +} + +void cutscene_dance_fly_away_approach_mario(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 600.f, 0.3f); + approach_s16_asymptotic_bool(&pitch, 0x1000, 16); + approach_s16_asymptotic_bool(&yaw, c->yaw, 8); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +void cutscene_dance_fly_away_focus_mario(struct Camera *c) { + Vec3f marioPos; + + vec3f_set(marioPos, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); + approach_vec3f_asymptotic(sCutsceneVars[9].point, marioPos, 0.2f, 0.2f, 0.2f); + vec3f_copy(c->focus, sCutsceneVars[9].point); +} + +/** + * Slowly pan the camera downwards and to the camera's right, using cvar9's angle. + */ +void cutscene_pan_cvar9(struct Camera *c) { + vec3f_copy(c->focus, sCutsceneVars[9].point); + sCutsceneVars[9].angle[0] -= 29; + sCutsceneVars[9].angle[1] += 29; + pan_camera(c, sCutsceneVars[9].angle[0], sCutsceneVars[9].angle[1]); +} + +/** + * Move backwards and rotate slowly around Mario. + */ +void cutscene_dance_fly_rotate_around_mario(struct Camera *c) { + cutscene_pan_cvar9(c); + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, sCutsceneVars[8].point[2], 0, 0); +} + +/** + * Rotate quickly while Lakitu flies up. + */ +void cutscene_dance_fly_away_rotate_while_flying(struct Camera *c) { + rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, 0, 0x80); +} + +void cutscene_dance_fly_away_shake_fov(UNUSED struct Camera *c) { + set_fov_shake(0x400, 0x30, 0x8000); +} + +/** + * After collecting the star, Lakitu flies upwards out of the course. + */ +void cutscene_dance_fly_away(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + cutscene_event(cutscene_dance_fly_away_start, c, 0, 0); + cutscene_event(cutscene_dance_fly_away_focus_mario, c, 0, 30); + cutscene_event(cutscene_dance_fly_away_approach_mario, c, 0, 30); + cutscene_event(cutscene_dance_fly_rotate_around_mario, c, 55, 124); + cutscene_event(cutscene_dance_fly_away_rotate_while_flying, c, 55, 124); + cutscene_event(cutscene_dance_fly_away_shake_fov, c, 40, 40); + set_fov_function(CAM_FOV_DEFAULT); + set_handheld_shake(HAND_CAM_SHAKE_STAR_DANCE); +} + + +struct Cutscene sCutsceneDanceFlyAway[] = { + { cutscene_dance_fly_away, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/death_on_back.c b/src/camera/cutscenes/death_on_back.c new file mode 100644 index 0000000000..6762ff441d --- /dev/null +++ b/src/camera/cutscenes/death_on_back.c @@ -0,0 +1,33 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario dies on his back. + */ + +void cutscene_bbh_death_start(struct Camera *c) { + Vec3f dir = { 0, 40.f, 60.f }; + + offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, dir, sMarioCamState->faceAngle); + vec3f_copy(sCutsceneVars[0].point, c->focus); +} + +void cutscene_bbh_death_goto_mario(struct Camera *c) { + cutscene_goto_cvar_pos(c, 400.f, 0x1800, 0, 0x400); +} + +/** + * Cutscene that plays when Mario dies in BBH. + */ +void cutscene_bbh_death(struct Camera *c) { + cutscene_event(cutscene_bbh_death_start, c, 0, 0); + cutscene_event(cutscene_bbh_death_goto_mario, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +struct Cutscene sCutsceneDeathOnBack[] = { + { cutscene_bbh_death, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/death_standing.c b/src/camera/cutscenes/death_standing.c new file mode 100644 index 0000000000..756ea0ab43 --- /dev/null +++ b/src/camera/cutscenes/death_standing.c @@ -0,0 +1,35 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario dies while standing, or from electrocution. + */ + +void cutscene_death_standing_start(struct Camera *c) { + vec3f_copy(sCutsceneVars[0].point, c->focus); + vec3f_copy(sCutsceneVars[3].point, sMarioCamState->pos); + sCutsceneVars[3].point[1] += 70.f; +} + +/** + * Fly to Mario and turn on handheld shake. + */ +void cutscene_death_standing_goto_mario(struct Camera *c) { + cutscene_goto_cvar_pos(c, 400.f, 0x1000, 0x300, 0); + set_handheld_shake(HAND_CAM_SHAKE_HIGH); +} + +/** + * Cutscene that plays when Mario dies while standing. + */ +void cutscene_death_standing(struct Camera *c) { + cutscene_event(cutscene_death_standing_start, c, 0, 0); + cutscene_event(cutscene_death_standing_goto_mario, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; +} + +struct Cutscene sCutsceneStandingDeath[] = { + { cutscene_death_standing, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/death_stomach.c b/src/camera/cutscenes/death_stomach.c new file mode 100644 index 0000000000..ae6cda0563 --- /dev/null +++ b/src/camera/cutscenes/death_stomach.c @@ -0,0 +1,33 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario dies on his stomach. + */ + +void cutscene_death_stomach_start(struct Camera *c) { + Vec3f offset = { 0, 40.f, -60.f }; + + offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, offset, sMarioCamState->faceAngle); + vec3f_copy(sCutsceneVars[0].point, c->focus); +} + +void cutscene_death_stomach_goto_mario(struct Camera *c) { + cutscene_goto_cvar_pos(c, 400.f, 0x1800, 0, -0x400); +} + +/** + * Cutscene that plays when Mario dies on his stomach. + */ +void cutscene_death_stomach(struct Camera *c) { + cutscene_event(cutscene_death_stomach_start, c, 0, 0); + cutscene_event(cutscene_death_stomach_goto_mario, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +struct Cutscene sCutsceneDeathStomach[] = { + { cutscene_death_stomach, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/dialog.c b/src/camera/cutscenes/dialog.c new file mode 100644 index 0000000000..1320430715 --- /dev/null +++ b/src/camera/cutscenes/dialog.c @@ -0,0 +1,143 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/object_list_processor.h" + +/** + * Cutscene that plays when Mario talks to a creature. + */ + +/** + * cvar8 is Mario's position and faceAngle + * + * cvar9.point is gCutsceneFocus's position + * cvar9.angle[1] is the yaw between Mario and the gCutsceneFocus + */ +void cutscene_dialog_start(struct Camera *c) { + s16 yaw; + + cutscene_soften_music(c); + set_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); + + if (c->mode == CAMERA_MODE_BOSS_FIGHT) { + vec3f_copy(sCameraStoreCutscene.focus, c->focus); + vec3f_copy(sCameraStoreCutscene.pos, c->pos); + } else { + store_info_star(c); + } + + // Store Mario's position and faceAngle + sCutsceneVars[8].angle[0] = 0; + vec3f_copy(sCutsceneVars[8].point, sMarioCamState->pos); + sCutsceneVars[8].point[1] += 125.f; + + // Store gCutsceneFocus's position and yaw + object_pos_to_vec3f(sCutsceneVars[9].point, gCutsceneFocus); + sCutsceneVars[9].point[1] += gCutsceneFocus->hitboxHeight + 200.f; + sCutsceneVars[9].angle[1] = calculate_yaw(sCutsceneVars[8].point, sCutsceneVars[9].point); + + yaw = calculate_yaw(sMarioCamState->pos, gLakituState.curPos); + if ((yaw - sCutsceneVars[9].angle[1]) & 0x8000) { + sCutsceneVars[9].angle[1] -= 0x6000; + } else { + sCutsceneVars[9].angle[1] += 0x6000; + } +} + +/** + * Move closer to Mario and the object, adjusting to their difference in height. + * The camera's generally ends up looking over Mario's shoulder. + */ +void cutscene_dialog_move_mario_shoulder(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + Vec3f focus, pos; + + scale_along_line(focus, sCutsceneVars[9].point, sMarioCamState->pos, 0.7f); + vec3f_get_dist_and_angle(c->pos, focus, &dist, &pitch, &yaw); + pitch = calculate_pitch(c->pos, sCutsceneVars[9].point); + vec3f_set_dist_and_angle(c->pos, pos, dist, pitch, yaw); + + focus[1] = focus[1] + (sCutsceneVars[9].point[1] - focus[1]) * 0.1f; + approach_vec3f_asymptotic(c->focus, focus, 0.2f, 0.2f, 0.2f); + + vec3f_copy(pos, c->pos); + + // Set y pos to cvar8's y (top of focus object) + pos[1] = sCutsceneVars[8].point[1]; + vec3f_get_dist_and_angle(sCutsceneVars[8].point, pos, &dist, &pitch, &yaw); + approach_s16_asymptotic_bool(&yaw, sCutsceneVars[9].angle[1], 0x10); + approach_f32_asymptotic_bool(&dist, 180.f, 0.05f); + vec3f_set_dist_and_angle(sCutsceneVars[8].point, pos, dist, pitch, yaw); + + // Move up if Mario is below the focus object, down is Mario is above + pos[1] = sCutsceneVars[8].point[1] + + sins(calculate_pitch(sCutsceneVars[9].point, sCutsceneVars[8].point)) * 100.f; + + approach_f32_asymptotic_bool(&c->pos[1], pos[1], 0.05f); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; +} + +/** + * Create the dialog with sCutsceneDialogID + */ +void cutscene_dialog_create_dialog_box(struct Camera *c) { + if (c->cutscene == CUTSCENE_RACE_DIALOG) { + create_dialog_box_with_response(sCutsceneDialogID); + } else { + create_dialog_box(sCutsceneDialogID); + } + + //! Unused. This may have been used before sCutsceneDialogResponse was implemented. + sCutsceneVars[8].angle[0] = DIALOG_RESPONSE_NOT_DEFINED; +} + +/** + * Cutscene that plays when Mario talks to an object. + */ +void cutscene_dialog(struct Camera *c) { + cutscene_event(cutscene_dialog_start, c, 0, 0); + cutscene_event(cutscene_dialog_move_mario_shoulder, c, 0, -1); + cutscene_event(cutscene_dialog_create_dialog_box, c, 10, 10); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + + if (gDialogResponse != DIALOG_RESPONSE_NONE) { + sCutsceneDialogResponse = gDialogResponse; + } + + if ((get_dialog_id() == DIALOG_NONE) && (sCutsceneVars[8].angle[0] != 0)) { + if (c->cutscene != CUTSCENE_RACE_DIALOG) { + sCutsceneDialogResponse = DIALOG_RESPONSE_NOT_DEFINED; + } + + gCutsceneTimer = CUTSCENE_LOOP; + retrieve_info_star(c); + transition_next_state(c, 15); + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + cutscene_unsoften_music(c); + } +} + +/** + * Sets the CAM_FLAG_UNUSED_CUTSCENE_ACTIVE flag, which does nothing. + */ +void cutscene_dialog_set_flag(UNUSED struct Camera *c) { + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +/** + * Ends the dialog cutscene. + */ +void cutscene_dialog_end(struct Camera *c) { + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + c->cutscene = 0; + clear_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); +} + +struct Cutscene sCutsceneDialog[] = { + { cutscene_dialog, CUTSCENE_LOOP }, + { cutscene_dialog_set_flag, 12 }, + { cutscene_dialog_end, 0 } +}; diff --git a/src/camera/cutscenes/door.c b/src/camera/cutscenes/door.c new file mode 100644 index 0000000000..779643c7f0 --- /dev/null +++ b/src/camera/cutscenes/door.c @@ -0,0 +1,178 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +void determine_pushing_or_pulling_door(s16 *rotation) { + if (sMarioCamState->action == ACT_PULLING_DOOR) { + *rotation = 0; + } else { + *rotation = DEGREES(-180); + } +} + +/** + * Store the camera's pos and focus for the door cutscene + */ +void cutscene_door_start(struct Camera *c) { + vec3f_copy(sCutsceneVars[0].point, c->pos); + vec3f_copy(sCutsceneVars[1].point, c->focus); +} + +/** + * Fix the camera in place while the door opens. + */ +void cutscene_door_fix_cam(struct Camera *c) { + vec3f_copy(c->pos, sCutsceneVars[0].point); + vec3f_copy(c->focus, sCutsceneVars[1].point); +} + +/** + * Loop until Mario is no longer using the door. + */ +void cutscene_door_loop(struct Camera *c) { + //! bitwise AND instead of boolean + if ((sMarioCamState->action != ACT_PULLING_DOOR) & (sMarioCamState->action != ACT_PUSHING_DOOR)) { + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + } +} + +/** + * Warp the camera behind Mario. + */ +void cutscene_door_move_behind_mario(struct Camera *c) { + Vec3f camOffset; + s16 doorRotation; + + reset_pan_distance(c); + determine_pushing_or_pulling_door(&doorRotation); + set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); + vec3s_set(sCutsceneVars[0].angle, 0, sMarioCamState->faceAngle[1] + doorRotation, 0); + vec3f_set(camOffset, 0.f, 125.f, 250.f); + + offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); +} + +/** + * Follow Mario through the door. + */ +void cutscene_door_follow_mario(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); + vec3f_get_dist_and_angle(c->focus, c->pos, &dist, &pitch, &yaw); + camera_approach_f32_symmetric_bool(&dist, 150.f, 7.f); + vec3f_set_dist_and_angle(c->focus, c->pos, dist, pitch, yaw); + cutscene_update_camera_yaw(c); +} + +/** + * Ends the door cutscene. Sets the camera mode to close mode unless the default is free roam. + */ +void cutscene_door_end(struct Camera *c) { +#ifdef USE_COURSE_DEFAULT_MODE + c->mode = c->defMode; +#else + c->mode = c->defMode == CAMERA_MODE_FREE_ROAM ? CAMERA_MODE_FREE_ROAM : CAMERA_MODE_CLOSE; +#endif + + c->cutscene = CUTSCENE_NONE; + gCutsceneTimer = CUTSCENE_STOP; + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + set_flag_post_door(c); + cutscene_update_camera_yaw(c); +} + +/** + * Used for entering a room that uses a specific camera mode, like the castle lobby or BBH + */ +void cutscene_door_mode(struct Camera *c) { + reset_pan_distance(c); +#ifdef USE_COURSE_DEFAULT_MODE + c->mode = c->defMode; +#else + camera_course_processing(c); + + if (c->mode == CAMERA_MODE_FIXED) { + c->nextYaw = update_fixed_camera(c, c->focus, c->pos); + } + if (c->mode == CAMERA_MODE_PARALLEL_TRACKING) { + c->nextYaw = update_parallel_tracking_camera(c, c->focus, c->pos); + } +#endif + + c->yaw = c->nextYaw; + + // Loop until Mario is no longer using the door + if (sMarioCamState->action != ACT_ENTERING_STAR_DOOR && + sMarioCamState->action != ACT_PULLING_DOOR && + sMarioCamState->action != ACT_PUSHING_DOOR) { + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + } +} + +/** + * Cutscene that plays when Mario pulls open a door. + */ +struct Cutscene sCutsceneDoorPull[] = { +// HackerSM64 TODO: Properly transition when moving through doors +#ifndef FORCED_CAMERA_MODE + { cutscene_door_start, 1 }, + { cutscene_door_fix_cam, 30 }, + { cutscene_door_move_behind_mario, 1 }, + { cutscene_door_follow_mario, 50 }, +#endif + { cutscene_door_end, 0 } +}; + +/** + * Cutscene that plays when Mario pulls open a door that has some special mode requirement on the other + * side. + */ +struct Cutscene sCutsceneDoorPullMode[] = { +// HackerSM64 TODO: Properly transition when moving through doors +#ifndef FORCED_CAMERA_MODE + { cutscene_door_start, 1 }, + { cutscene_door_fix_cam, 30 }, +#endif + { cutscene_door_mode, CUTSCENE_LOOP } +}; + +/** + * Cutscene that plays when Mario pushes open a door. + */ +struct Cutscene sCutsceneDoorPush[] = { +// HackerSM64 TODO: Properly transition when moving through doors +#ifndef FORCED_CAMERA_MODE + { cutscene_door_start, 1 }, + { cutscene_door_fix_cam, 20 }, + { cutscene_door_move_behind_mario, 1 }, + { cutscene_door_follow_mario, 50 }, +#endif + { cutscene_door_end, 0 } +}; + +/** + * Cutscene that plays when Mario pushes open a door that has some special mode requirement on the other + * side. + */ +struct Cutscene sCutsceneDoorPushMode[] = { +// HackerSM64 TODO: Properly transition when moving through doors +#ifndef FORCED_CAMERA_MODE + { cutscene_door_start, 1 }, + { cutscene_door_fix_cam, 20 }, +#endif + { cutscene_door_mode, CUTSCENE_LOOP } +}; + +/** + * Cutscene that plays when Mario enters a door that warps to another area. + */ +struct Cutscene sCutsceneDoorWarp[] = { + { cutscene_door_start, 1 }, + { cutscene_door_loop, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/end_waving.c b/src/camera/cutscenes/end_waving.c new file mode 100644 index 0000000000..da88efdf71 --- /dev/null +++ b/src/camera/cutscenes/end_waving.c @@ -0,0 +1,37 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays after the credits, when Lakitu is flying away from the castle. + */ + +/** + * Reset the spline progress. + */ +void cutscene_end_waving_start(UNUSED struct Camera *c) { + cutscene_reset_spline(); +} + +void cutscene_end_waving(struct Camera *c) { + static struct CutsceneSplinePoint sEndWavingPos[] = { + { 0, 0, { -5, 975, -917 } }, { 0, 0, { -5, 975, -917 } }, { 0, 0, { -5, 975, -917 } }, + { 0, 0, { -76, 1067, 742 } }, { 0, 0, { -105, 1576, 3240 } }, { 0, 0, { -177, 1709, 5586 } }, + { 0, 0, { -177, 1709, 5586 } }, { 0, 0, { -177, 1709, 5586 } }, { 0, 0, { -177, 1709, 5586 } } + }; + static struct CutsceneSplinePoint sEndWavingFocus[] = { + { 0, 50, { 18, 1013, -1415 } }, { 0, 100, { 17, 1037, -1412 } }, { 0, 100, { 16, 1061, -1408 } }, + { 0, 100, { -54, 1053, 243 } }, { 0, 100, { -84, 1575, 2740 } }, { 0, 50, { -156, 1718, 5086 } }, + { 0, 0, { -156, 1718, 5086 } }, { 0, 0, { -156, 1718, 5086 } }, { 0, 0, { -156, 1718, 5086 } } + }; + + cutscene_event(cutscene_end_waving_start, c, 0, 0); + move_point_along_spline(c->pos, sEndWavingPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + move_point_along_spline(c->focus, sEndWavingFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + cutscene_spawn_obj(CUTSCENE_OBJ_BEGINNING_LAKITU, 120); +} + +struct Cutscene sCutsceneEndWaving[] = { + { cutscene_end_waving, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/ending.c b/src/camera/cutscenes/ending.c new file mode 100644 index 0000000000..0d04f996a2 --- /dev/null +++ b/src/camera/cutscenes/ending.c @@ -0,0 +1,341 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario beats the game. + */ + +/** + * Set the camera position and focus for when Mario falls from the sky. + */ +void cutscene_ending_mario_fall_start(struct Camera *c) { + vec3f_set(c->focus, -26.f, 0.f, -137.f); + vec3f_set(c->pos, 165.f, 4725.f, 324.f); +} + +/** + * Focus on Mario when he's falling from the sky. + */ +void cutscene_ending_mario_fall_focus_mario(struct Camera *c) { + Vec3f offset; + vec3f_set(offset, 0.f, 80.f, 0.f); + + offset[2] = ABS(sMarioCamState->pos[1] - c->pos[1]) * -0.1f; + if (offset[2] > -100.f) { + offset[2] = -100.f; + } + + offset_rotated(c->focus, sMarioCamState->pos, offset, sMarioCamState->faceAngle); +} + +/** + * Mario falls from the sky after the grand star cutscene. + */ +void cutscene_ending_mario_fall(struct Camera *c) { + cutscene_event(cutscene_ending_mario_fall_start, c, 0, 0); + cutscene_event(cutscene_ending_mario_fall_focus_mario, c, 0, -1); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Closeup of Mario as the wing cap fades and Mario looks up. + */ +void cutscene_ending_mario_land_closeup(struct Camera *c) { + vec3f_set(c->focus, 85.f, 826.f, 250.f); + vec3f_set(c->pos, -51.f, 988.f, -202.f); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Reset the spline progress and cvar9. + */ +void cutscene_ending_reset_spline(UNUSED struct Camera *c) { + sCutsceneVars[9].point[0] = 0.f; + cutscene_reset_spline(); +} + +/** + * Follow sEndingFlyToWindowPos/Focus up to the window. + */ +static struct CutsceneSplinePoint sEndingFlyToWindowPos[] = { + { 0, 0, { -86, 876, 640 } }, { 1, 0, { -86, 876, 610 } }, { 2, 0, { -66, 945, 393 } }, + { 3, 0, { -80, 976, 272 } }, { 4, 0, { -66, 1306, -36 } }, { 5, 0, { -70, 1869, -149 } }, + { 6, 0, { -10, 2093, -146 } }, { 7, 0, { -10, 2530, -248 } }, { 8, 0, { -10, 2530, -263 } }, + { 9, 0, { -10, 2530, -273 } } +}; +static struct CutsceneSplinePoint sEndingFlyToWindowFocus[] = { + { 0, 50, { -33, 889, -7 } }, { 1, 35, { -33, 889, -7 } }, { 2, 31, { -17, 1070, -193 } }, + { 3, 25, { -65, 1182, -272 } }, { 4, 20, { -64, 1559, -542 } }, { 5, 25, { -68, 2029, -677 } }, + { 6, 25, { -9, 2204, -673 } }, { 7, 25, { -8, 2529, -772 } }, { 8, 0, { -8, 2529, -772 } }, + { 9, 0, { -8, 2529, -772 } }, { -1, 0, { -8, 2529, -772 } } +}; +void cutscene_ending_fly_up_to_window(struct Camera *c) { + + move_point_along_spline(c->pos, sEndingFlyToWindowPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + move_point_along_spline(c->focus, sEndingFlyToWindowFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); +} + +/** + * Move the camera up to the window as the star power frees peach. + */ +void cutscene_ending_stars_free_peach(struct Camera *c) { + cutscene_event(cutscene_ending_reset_spline, c, 0, 0); + cutscene_event(cutscene_ending_fly_up_to_window, c, 0, -1); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Move the camera to the ground as Mario lands. + */ +void cutscene_ending_mario_land(struct Camera *c) { + vec3f_set(c->focus, sEndingFlyToWindowFocus[0].point[0], sEndingFlyToWindowFocus[0].point[1] + 80.f, sEndingFlyToWindowFocus[0].point[2]); + vec3f_set(c->pos, sEndingFlyToWindowPos[0].point[0], sEndingFlyToWindowPos[0].point[1], sEndingFlyToWindowPos[0].point[2] + 150.f); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); +} + +/** + * Move the camera closer to peach appearing. + */ +void cutscene_ending_peach_appear_closeup(struct Camera *c) { + vec3f_set(c->pos, 179.f, 2463.f, -1216.f); + c->pos[1] = gCutsceneFocus->oPosY + 35.f; + vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); +} + +/** + * Peach fades in, the camera focuses on her. + */ +void cutscene_ending_peach_appears(struct Camera *c) { + cutscene_event(cutscene_ending_peach_appear_closeup, c, 0, 0); + approach_f32_asymptotic_bool(&c->pos[1], gCutsceneFocus->oPosY + 35.f, 0.02f); + approach_f32_asymptotic_bool(&c->focus[1], gCutsceneFocus->oPosY + 125.f, 0.15f); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Reset spline progress, set cvar2 y offset. + */ +void cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { + cutscene_reset_spline(); + sCutsceneVars[2].point[1] = 150.f; +} + +static struct CutsceneSplinePoint sEndingPeachDescentCamPos[] = { + { 0, 50, { 1, 120, -1150 } }, { 1, 50, { 1, 120, -1150 } }, { 2, 40, { 118, 121, -1199 } }, + { 3, 40, { 147, 74, -1306 } }, { 4, 40, { 162, 95, -1416 } }, { 5, 40, { 25, 111, -1555 } }, + { 6, 40, { -188, 154, -1439 } }, { 7, 40, { -203, 181, -1242 } }, { 8, 40, { 7, 191, -1057 } }, + { 9, 40, { 262, 273, -1326 } }, { 0, 40, { -4, 272, -1627 } }, { 1, 35, { -331, 206, -1287 } }, + { 2, 30, { -65, 219, -877 } }, { 3, 25, { 6, 216, -569 } }, { 4, 25, { -8, 157, 40 } }, + { 5, 25, { -4, 106, 200 } }, { 6, 25, { -6, 72, 574 } }, { 7, 0, { -6, 72, 574 } }, + { 8, 0, { -6, 72, 574 } }, { -1, 0, { -6, 72, 574 } } +}; + +/** + * Follow the sEndingPeachDescentCamPos spline, which rotates around peach. + */ +void cutscene_ending_follow_peach_descent(struct Camera *c) { + move_point_along_spline(c->pos, sEndingPeachDescentCamPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + c->pos[1] += gCutsceneFocus->oPosY + sCutsceneVars[3].point[1]; +} + +/** + * Decrease cvar2's y offset while the camera flies backwards to Mario. + */ +void cutscene_ending_peach_descent_lower_focus(UNUSED struct Camera *c) { + camera_approach_f32_symmetric_bool(&(sCutsceneVars[2].point[1]), 90.f, 0.5f); +} + +/** + * Keep following the sEndingPeachDescentCamPos spline, which leads back to Mario. + */ +void cutscene_ending_peach_descent_back_to_mario(struct Camera *c) { + Vec3f pos; + + move_point_along_spline(pos, sEndingPeachDescentCamPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; + approach_f32_asymptotic_bool(&c->pos[1], (pos[1] += gCutsceneFocus->oPosY), 0.07f); +} + +/** + * Peach starts floating to the ground. Rotate the camera around her, then fly backwards to Mario when + * she lands. + */ +void cutscene_ending_peach_descends(struct Camera *c) { + cutscene_event(cutscene_ending_peach_descends_start, c, 0, 0); + cutscene_event(cutscene_ending_follow_peach_descent, c, 0, 299); + cutscene_event(cutscene_ending_peach_descent_back_to_mario, c, 300, -1); + cutscene_event(cutscene_ending_peach_descent_lower_focus, c, 300, -1); + vec3f_set(c->focus, gCutsceneFocus->oPosX, sCutsceneVars[2].point[1] + gCutsceneFocus->oPosY, + gCutsceneFocus->oPosZ); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Mario runs across the bridge to peach, and takes off his cap. + * Follow the sEndingMarioToPeach* splines while Mario runs across. + */ +void cutscene_ending_mario_to_peach(struct Camera *c) { + static struct CutsceneSplinePoint sEndingMarioToPeachPos[] = { + { 0, 0, { -130, 1111, -1815 } }, { 1, 0, { -131, 1052, -1820 } }, { 2, 0, { -271, 1008, -1651 } }, + { 3, 0, { -439, 1043, -1398 } }, { 4, 0, { -433, 1040, -1120 } }, { 5, 0, { -417, 1040, -1076 } }, + { 6, 0, { -417, 1040, -1076 } }, { 7, 0, { -417, 1040, -1076 } }, { -1, 0, { -417, 1040, -1076 } } + }; + static struct CutsceneSplinePoint sEndingMarioToPeachFocus[] = { + { 0, 50, { -37, 1020, -1332 } }, { 1, 20, { -36, 1012, -1330 } }, { 2, 20, { -24, 1006, -1215 } }, + { 3, 20, { 28, 1002, -1224 } }, { 4, 24, { 45, 1013, -1262 } }, { 5, 35, { 34, 1000, -1287 } }, + { 6, 0, { 34, 1000, -1287 } }, { 7, 0, { 34, 1000, -1287 } }, { -1, 0, { 34, 1000, -1287 } } + }; + + cutscene_event(cutscene_ending_reset_spline, c, 0, 0); + move_point_along_spline(c->pos, sEndingMarioToPeachPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + move_point_along_spline(c->focus, sEndingMarioToPeachFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); +} + +/** + * Make the focus follow the sEndingLookUpAtCastle spline. + */ +void cutscene_ending_look_up_at_castle(UNUSED struct Camera *c) { + static struct CutsceneSplinePoint sEndingLookUpAtCastle[] = { + { 0, 50, { 200, 1066, -1414 } }, { 0, 50, { 200, 1066, -1414 } }, { 0, 30, { 198, 1078, -1412 } }, + { 0, 33, { 15, 1231, -1474 } }, { 0, 39, { -94, 1381, -1368 } }, { 0, 0, { -92, 1374, -1379 } }, + { 0, 0, { -92, 1374, -1379 } }, { -1, 0, { -92, 1374, -1379 } } + }; + + move_point_along_spline(c->focus, sEndingLookUpAtCastle, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); +} + +/** + * Peach opens her eyes and the camera looks at the castle window again. + */ +void cutscene_ending_peach_wakeup(struct Camera *c) { + cutscene_event(cutscene_ending_reset_spline, c, 0, 0); + cutscene_event(cutscene_ending_look_up_at_castle, c, 0, 0); +#ifdef VERSION_EU + cutscene_event(cutscene_ending_look_up_at_castle, c, 265, -1); + cutscene_spawn_obj(7, 315); + cutscene_spawn_obj(9, 355); +#else + cutscene_event(cutscene_ending_look_up_at_castle, c, 250, -1); + cutscene_spawn_obj(7, 300); + cutscene_spawn_obj(9, 340); +#endif + vec3f_set(c->pos, -163.f, 978.f, -1082.f); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); +} + +/** + * Side view of peach and Mario. Peach thanks Mario for saving her. + */ +void cutscene_ending_dialog(struct Camera *c) { + vec3f_set(c->focus, 11.f, 983.f, -1273.f); + vec3f_set(c->pos, -473.f, 970.f, -1152.f); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); +} + +/** + * Zoom in and move the camera close to Mario and peach. + */ +void cutscene_ending_kiss_closeup(struct Camera *c) { + set_fov_function(CAM_FOV_SET_29); + vec3f_set(c->focus, 350.f, 1034.f, -1216.f); + vec3f_set(c->pos, -149.f, 1021.f, -1216.f); +} + +/** + * Fly back and zoom out for Mario's spin after the kiss. + */ +void cutscene_ending_kiss_here_we_go(struct Camera *c) { + Vec3f pos, foc; + + set_fov_function(CAM_FOV_DEFAULT); + vec3f_set(foc, 233.f, 1068.f, -1298.f); + vec3f_set(pos, -250.f, 966.f, -1111.f); + approach_vec3f_asymptotic(c->pos, pos, 0.2f, 0.1f, 0.2f); + approach_vec3f_asymptotic(c->focus, foc, 0.2f, 0.1f, 0.2f); +} + +/** + * Peach kisses Mario on the nose. + */ +void cutscene_ending_kiss(struct Camera *c) { + cutscene_event(cutscene_ending_kiss_closeup, c, 0, 0); + cutscene_event(cutscene_ending_kiss_here_we_go, c, 155, -1); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); +} + +/** + * Make the focus follow sEndingLookAtSkyFocus. + */ +void cutscene_ending_look_at_sky(struct Camera *c) { + static struct CutsceneSplinePoint sEndingLookAtSkyFocus[] = { +#ifdef VERSION_EU + { 0, 50, { 484, 1368, -868 } }, { 0, 72, { 479, 1372, -872 } }, { 0, 50, { 351, 1817, -918 } }, +#else + { 0, 50, { 484, 1368, -888 } }, { 0, 72, { 479, 1372, -892 } }, { 0, 50, { 351, 1817, -918 } }, +#endif + { 0, 50, { 351, 1922, -598 } }, { 0, 0, { 636, 2027, -415 } }, { 0, 0, { 636, 2027, -415 } }, + { -1, 0, { 636, 2027, -415 } } + }; + + move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + vec3f_set(c->pos, 699.f, 1680.f, -703.f); +} + +/** + * Zoom in the fov. The fovFunc was just set to default, so it wants to approach 45. But while this is + * called, it will stay at about 37.26f + */ +void cutscene_ending_zoom_fov(UNUSED struct Camera *c) { + sFOVState.fov = 37.f; +} + +/** + * Peach suggests baking a cake for Mario. Mario looks back at the camera before going inside the castle. + */ +void cutscene_ending_cake_for_mario(struct Camera *c) { + cutscene_event(cutscene_ending_reset_spline, c, 0, 0); + cutscene_event(cutscene_ending_look_at_sky, c, 0, 0); + cutscene_event(cutscene_ending_zoom_fov, c, 0, 499); + cutscene_event(cutscene_ending_look_at_sky, c, 500, -1); + cutscene_spawn_obj(8, 600); + cutscene_spawn_obj(8, 608); + cutscene_spawn_obj(8, 624); + cutscene_spawn_obj(8, 710); +} + +/** + * Stop the ending cutscene, reset the fov. + */ +void cutscene_ending_stop(struct Camera *c) { + set_fov_function(CAM_FOV_SET_45); + c->cutscene = 0; + gCutsceneTimer = CUTSCENE_STOP; +} + +struct Cutscene sCutsceneEnding[] = { + { cutscene_ending_mario_fall, 170 }, + { cutscene_ending_mario_land, 70 }, + { cutscene_ending_mario_land_closeup, 75 }, +#ifdef VERSION_SH + { cutscene_ending_stars_free_peach, 431 }, +#else + { cutscene_ending_stars_free_peach, 386 }, +#endif + { cutscene_ending_peach_appears, 139 }, + { cutscene_ending_peach_descends, 590 }, + { cutscene_ending_mario_to_peach, 95 }, +#ifdef VERSION_SH + { cutscene_ending_peach_wakeup, 455 }, + { cutscene_ending_dialog, 286 }, +#else + { cutscene_ending_peach_wakeup, 425 }, + { cutscene_ending_dialog, 236 }, +#endif + { cutscene_ending_kiss, 245 }, + { cutscene_ending_cake_for_mario, CUTSCENE_LOOP }, + { cutscene_ending_stop, 0 } +}; diff --git a/src/camera/cutscenes/enter_bowser_arena.c b/src/camera/cutscenes/enter_bowser_arena.c new file mode 100644 index 0000000000..1ff2ffdd7b --- /dev/null +++ b/src/camera/cutscenes/enter_bowser_arena.c @@ -0,0 +1,164 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when entering bowser's arenas. + */ + +void cutscene_bowser_area_shake_fov(UNUSED struct Camera *c) { + cutscene_set_fov_shake_preset(2); +} + +/** + * Set oBowserCamAct to 1, which causes bowser to start walking. + */ +void cutscene_bowser_area_start_bowser_walking(UNUSED struct Camera *c) { + gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_WALK; +} + +/** + * Offset the camera from bowser using cvar2 and cvar3 + * @bug cvar2.point is (0,0,0) on the first frame, but because of the warp transition, this behavior + * isn't seen. After the first frame, cvar2.point is bowser's position. + */ +void cutscene_bowser_arena_set_pos(struct Camera *c) { + vec3f_set_dist_and_angle(sCutsceneVars[2].point, c->pos, sCutsceneVars[3].point[2], + sCutsceneVars[3].angle[0], sCutsceneVars[3].angle[1]); + vec3f_set(sCutsceneVars[2].point, gSecondCameraFocus->oPosX, gSecondCameraFocus->oPosY, + gSecondCameraFocus->oPosZ); +} + +/** + * Apply a sine wave to the focus's y coordinate. + * The y offset starts at 120, then decreases to 0 before reaching ~240 on the last frame. + */ +void cutscene_bowser_arena_focus_sine(UNUSED struct Camera *c) { + // cvar4 was zeroed when the cutscene started. + f32 yOff = sins(sCutsceneVars[4].angle[1]) * 120.0f + 120.0f; + sCutsceneVars[4].angle[1] -= 0x200; + approach_f32_asymptotic_bool(&sCutsceneVars[0].point[1], yOff, 0.5f); +} + +/** + * Set the camera focus according to cvar0 and cvar2. + */ +void cutscene_bowser_arena_set_focus(struct Camera *c) { + offset_rotated(c->focus, sCutsceneVars[2].point, sCutsceneVars[0].point, sCutsceneVars[2].angle); +} + +/** + * Adjust the cvar offsets, making the camera look up, move slightly further back, and focus a little + * further in front of bowser. + */ +void cutscene_bowser_arena_adjust_offsets(UNUSED struct Camera *c) { + approach_s16_asymptotic_bool(&sCutsceneVars[3].angle[0], 0x6C8, 30); + approach_f32_asymptotic_bool(&sCutsceneVars[0].point[2], -200.f, 0.02f); + approach_f32_asymptotic_bool(&sCutsceneVars[3].point[2], 550.f, 0.02f); +} + +/** + * Decrease cvar0's z offset, making the camera focus pan left towards bowser. + */ +void cutscene_bowser_arena_pan_left(UNUSED struct Camera *c) { + approach_f32_asymptotic_bool(&sCutsceneVars[0].point[2], 0.f, 0.05f); +} + +/** + * Active for the first 5 frames of the cutscene. + * cvar3 is the camera's polar offset from bowser + * cvar2.angle is bowser's move angle + * + * cvar0 is the focus offset from bowser + */ +void cutscene_bowser_arena_start(struct Camera *c) { + sCutsceneVars[3].point[2] = 430.f; + sCutsceneVars[3].angle[1] = gSecondCameraFocus->oMoveAngleYaw - DEGREES(45); + sCutsceneVars[3].angle[0] = 0xD90; + + //! Tricky math: Bowser starts at (0, 307, -1000), with a moveAngle of (0,0,0). A sane person would + //! expect this offset to move the focus to (0, 427, -1800). + //! BUT because offset_rotated() flips the Z direction (to match sm64's coordinate system), this + //! offset actually moves the focus to (0, 427, -200) + vec3f_set(sCutsceneVars[0].point, 0.f, 120.f, -800.f); + vec3s_set(sCutsceneVars[2].angle, gSecondCameraFocus->oMoveAnglePitch, + gSecondCameraFocus->oMoveAngleYaw, gSecondCameraFocus->oMoveAngleRoll); + + // Set the camera's position and focus. + cutscene_bowser_arena_set_pos(c); + cutscene_bowser_arena_set_focus(c); +} + +/** + * Create the dialog box depending on which bowser fight Mario is in. + */ +void bowser_fight_intro_dialog(UNUSED struct Camera *c) { + s16 dialog; + + switch (gCurrLevelNum) { + case LEVEL_BOWSER_1: + dialog = DIALOG_067; + break; + case LEVEL_BOWSER_2: + dialog = DIALOG_092; + break; + default: // LEVEL_BOWSER_3 + dialog = DIALOG_093; + } + + create_dialog_box(dialog); +} + +/** + * Create the dialog box and wait until it's gone. + */ +void cutscene_bowser_arena_dialog(struct Camera *c) { + cutscene_event(bowser_fight_intro_dialog, c, 0, 0); + + if (get_dialog_id() == DIALOG_NONE) { + gCutsceneTimer = CUTSCENE_LOOP; + } +} + +/** + * End the bowser arena cutscene. + */ +void cutscene_bowser_arena_end(struct Camera *c) { + cutscene_stop_dialog(c); + c->cutscene = 0; + transition_next_state(c, 20); + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + sModeOffsetYaw = sMarioCamState->faceAngle[1] + DEGREES(90); + gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_END; +} + +/** + * Cutscene that plays when Mario enters a bowser fight. + */ +void cutscene_bowser_arena(struct Camera *c) { + //! This does nothing, but may have been used in development + cutscene_spawn_obj(CUTSCENE_OBJ_UNUSED_2, 0); + + if (gSecondCameraFocus != NULL) { + cutscene_event(cutscene_mario_dialog_look_front, c, 0, -1); + cutscene_event(cutscene_bowser_arena_start, c, 0, 5); + cutscene_event(cutscene_bowser_area_start_bowser_walking, c, 40, 40); + cutscene_event(cutscene_bowser_area_shake_fov, c, 145, 145); + cutscene_event(cutscene_bowser_arena_set_pos, c, 40, -1); + cutscene_event(cutscene_bowser_arena_pan_left, c, 40, 99); + cutscene_event(cutscene_bowser_arena_adjust_offsets, c, 100, -1); + cutscene_event(cutscene_bowser_arena_focus_sine, c, 40, 140); + cutscene_event(cutscene_bowser_arena_set_focus, c, 40, -1); + cutscene_event(cutscene_shake_explosion, c, 60, 60); + cutscene_event(cutscene_shake_explosion, c, 82, 82); + cutscene_event(cutscene_shake_explosion, c, 109, 109); + cutscene_event(cutscene_shake_explosion, c, 127, 127); + } +} + +struct Cutscene sCutsceneEnterBowserArena[] = { + { cutscene_bowser_arena, 180 }, + { cutscene_bowser_arena_dialog, CUTSCENE_LOOP }, + { cutscene_bowser_arena_end, 0 } +}; diff --git a/src/camera/cutscenes/enter_cannon.c b/src/camera/cutscenes/enter_cannon.c new file mode 100644 index 0000000000..651f994a3c --- /dev/null +++ b/src/camera/cutscenes/enter_cannon.c @@ -0,0 +1,97 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario enters the cannon and it rises out of the hole. + */ + +/** + * End the cutscene, starting cannon mode. + */ +void cutscene_enter_cannon_end(struct Camera *c) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_INSIDE_CANNON; + c->cutscene = CUTSCENE_NONE; + sCannonYOffset = 800.f; +} + +/** + * Rotate around the cannon as it rises out of the hole. + */ +void cutscene_enter_cannon_raise(struct Camera *c) { + struct Object *obj; + f32 floorHeight; + struct Surface *floor; + Vec3f cannonFocus; + Vec3s cannonAngle; + + // Shake the camera when the cannon is fully raised + cutscene_event(cutscene_shake_explosion, c, 70, 70); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + camera_approach_s16_symmetric_bool(&sCutsceneVars[1].angle[0], 0, 0x80); + camera_approach_s16_symmetric_bool(&sCutsceneVars[2].angle[0], 0, 0x80); + // Move the camera around the cannon, gradually rotating and moving closer + vec3f_set_dist_and_angle(sCutsceneVars[0].point, c->pos, sCutsceneVars[1].point[2], sCutsceneVars[1].angle[0], + sCutsceneVars[1].angle[1]); + sCutsceneVars[1].point[2] = approach_f32(sCutsceneVars[1].point[2], 400.f, 5.f, 5.f); + sCutsceneVars[1].angle[1] += 0x40; + sCutsceneVars[3].point[1] += 2.f; + c->pos[1] += sCutsceneVars[3].point[1]; + + if ((obj = sMarioCamState->usedObj) != NULL) { + sCutsceneVars[0].point[1] = obj->oPosY; + cannonAngle[0] = obj->oMoveAnglePitch; + cannonAngle[1] = obj->oMoveAngleYaw; + cannonAngle[2] = obj->oMoveAngleRoll; + c->focus[0] = obj->oPosX; + c->focus[1] = obj->oPosY; + c->focus[2] = obj->oPosZ; + cannonFocus[0] = 0.f; + cannonFocus[1] = 100.f; + cannonFocus[2] = 0.f; + offset_rotated(c->focus, c->focus, cannonFocus, cannonAngle); + } + + floorHeight = find_floor(c->pos[0], c->pos[1] + 500.f, c->pos[2], &floor) + 100.f; + + if (c->pos[1] < floorHeight) { + c->pos[1] = floorHeight; + } +} + +/** + * Start the cannon entering cutscene + */ +void cutscene_enter_cannon_start(struct Camera *c) { + struct Object *obj; + + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sMarioCamState->cameraEvent = 0; + + // Store the cannon's position and angle in cvar0 + if ((obj = sMarioCamState->usedObj) != NULL) { + sCutsceneVars[0].point[0] = obj->oPosX; + sCutsceneVars[0].point[1] = obj->oPosY; + sCutsceneVars[0].point[2] = obj->oPosZ; + sCutsceneVars[0].angle[0] = obj->oMoveAnglePitch; + sCutsceneVars[0].angle[1] = obj->oMoveAngleYaw; + sCutsceneVars[0].angle[2] = obj->oMoveAngleRoll; + } + + // Store the camera's polar offset from the cannon in cvar1 + vec3f_get_dist_and_angle(sCutsceneVars[0].point, c->pos, &sCutsceneVars[1].point[2], + &sCutsceneVars[1].angle[0], &sCutsceneVars[1].angle[1]); + sCutsceneVars[3].point[1] = 0.f; + //! cvar4 is unused in this cutscene + sCutsceneVars[4].point[1] = 0.f; +} + +struct Cutscene sCutsceneEnterCannon[] = { + { cutscene_enter_cannon_start, 1 }, + { cutscene_enter_cannon_raise, 121 }, + { cutscene_enter_cannon_end, 0 } +}; diff --git a/src/camera/cutscenes/enter_painting.c b/src/camera/cutscenes/enter_painting.c new file mode 100644 index 0000000000..36e9ea0841 --- /dev/null +++ b/src/camera/cutscenes/enter_painting.c @@ -0,0 +1,68 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" +#include "game/paintings.h" + +/** + * Cutscene that plays when Mario enters a course painting. + */ + +/** + * Plays when Mario enters a painting. The camera flies up to the painting's center, then it slowly + * zooms in until the star select screen appears. + */ +void cutscene_enter_painting(struct Camera *c) { + struct Surface *floor, *highFloor; + Vec3f paintingPos, focus, focusOffset; + Vec3s paintingAngle; + f32 floorHeight; + + // Zoom in + set_fov_function(CAM_FOV_APP_20); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + + if (gRipplingPainting != NULL) { + paintingAngle[0] = 0; + paintingAngle[1] = (s32)((gRipplingPainting->yaw / 360.f) * 65536.f); // convert degrees to IAU + paintingAngle[2] = 0; + + focusOffset[0] = gRipplingPainting->size / 2; + focusOffset[1] = focusOffset[0]; + focusOffset[2] = 0; + + paintingPos[0] = gRipplingPainting->posX; + paintingPos[1] = gRipplingPainting->posY; + paintingPos[2] = gRipplingPainting->posZ; + + offset_rotated(focus, paintingPos, focusOffset, paintingAngle); + approach_vec3f_asymptotic(c->focus, focus, 0.1f, 0.1f, 0.1f); + focusOffset[2] = -(((gRipplingPainting->size * 1000.f) / 2) / 307.f); + offset_rotated(focus, paintingPos, focusOffset, paintingAngle); + floorHeight = find_floor(focus[0], focus[1] + 500.f, focus[2], &highFloor) + 125.f; + + if (focus[1] < floorHeight) { + focus[1] = floorHeight; + } + + if (c->cutscene == CUTSCENE_ENTER_PAINTING) { + approach_vec3f_asymptotic(c->pos, focus, 0.2f, 0.1f, 0.2f); + } else { + approach_vec3f_asymptotic(c->pos, focus, 0.9f, 0.9f, 0.9f); + } + + find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 50.f, sMarioCamState->pos[2], &floor); + + if ((floor->type < SURFACE_PAINTING_WOBBLE_A6) || (floor->type > SURFACE_PAINTING_WARP_F9)) { + c->cutscene = 0; + gCutsceneTimer = CUTSCENE_STOP; + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + } + } + c->mode = CAMERA_MODE_CLOSE; +} + +struct Cutscene sCutsceneEnterPainting[] = { + { cutscene_enter_painting, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/enter_pool.c b/src/camera/cutscenes/enter_pool.c new file mode 100644 index 0000000000..c518385f6a --- /dev/null +++ b/src/camera/cutscenes/enter_pool.c @@ -0,0 +1,38 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario enters HMC or CotMC. + */ + +void cutscene_enter_pool_start(struct Camera *c) { + vec3f_copy(sCutsceneVars[3].point, sMarioCamState->pos); + +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelNum == LEVEL_CASTLE) { // entering HMC + vec3f_set(sCutsceneVars[3].point, 2485.f, -1589.f, -2659.f); + } + if (gCurrLevelNum == LEVEL_HMC) { // entering CotMC + vec3f_set(sCutsceneVars[3].point, 3350.f, -4589.f, 4800.f); + } +#endif + + vec3f_copy(sCutsceneVars[0].point, c->focus); +} + +void cutscene_enter_pool_loop(struct Camera *c) { + cutscene_goto_cvar_pos(c, 1200.f, 0x2000, 0x200, 0); +} + +void cutscene_enter_pool(struct Camera *c) { + cutscene_event(cutscene_enter_pool_start, c, 0, 0); + cutscene_event(cutscene_enter_pool_loop, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; +} + +struct Cutscene sCutsceneEnterPool[] = { + { cutscene_enter_pool, 100 }, + { cutscene_exit_to_castle_grounds_end, 0 } +}; diff --git a/src/camera/cutscenes/enter_pyramid_top.c b/src/camera/cutscenes/enter_pyramid_top.c new file mode 100644 index 0000000000..cc5031fce7 --- /dev/null +++ b/src/camera/cutscenes/enter_pyramid_top.c @@ -0,0 +1,38 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario enters the pyramid through the hole at the top. + */ + +/** + * Store the camera focus in cvar0, and store the top of the pyramid in cvar3. + */ +void cutscene_enter_pyramid_top_start(struct Camera *c) { + vec3f_copy(sCutsceneVars[0].point, c->focus); + vec3f_set(sCutsceneVars[3].point, c->areaCenX, 1280.f, c->areaCenZ); +} + +/** + * Cutscene that plays when Mario enters the top of the pyramid. + */ +void cutscene_enter_pyramid_top(struct Camera *c) { + cutscene_event(cutscene_enter_pyramid_top_start, c, 0, 0); + // Move to cvar3 + cutscene_goto_cvar_pos(c, 200.f, 0x3000, 0, 0); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); + + if (sMarioCamState->pos[1] > 1250.f) { + // End the cutscene early if Mario ledge-grabbed. + // This only works because of the janky way that ledge-grabbing is implemented. + cutscene_exit_to_castle_grounds_end(c); + } +} + +struct Cutscene sCutsceneEnterPyramidTop[] = { + { cutscene_enter_pyramid_top, 90 }, + { cutscene_exit_to_castle_grounds_end, 0 } +}; diff --git a/src/camera/cutscenes/grand_star.c b/src/camera/cutscenes/grand_star.c new file mode 100644 index 0000000000..15de4f5830 --- /dev/null +++ b/src/camera/cutscenes/grand_star.c @@ -0,0 +1,181 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario collects the grand star from bowser. + */ + +/** + * Start the grand star cutscene. + * cvar0 is a relative offset from Mario. + * cvar1 is the is the camera's goal position. + */ +void cutscene_grand_star_start(UNUSED struct Camera *c) { + vec3f_set(sCutsceneVars[0].point, 0.f, 150.f, -600.f); + offset_rotated(sCutsceneVars[1].point, sMarioCamState->pos, sCutsceneVars[0].point, sMarioCamState->faceAngle); + sCutsceneVars[1].point[1] = 457.f; +} + +/** + * Make the camera fly to the front of Mario. + */ +void cutscene_grand_star_front_of_mario(struct Camera *c) { + f32 goalDist; + s16 goalPitch, goalYaw; + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(sMarioCamState->pos, sCutsceneVars[1].point, &goalDist, &goalPitch, &goalYaw); + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, goalDist, 0.1f); + approach_s16_asymptotic_bool(&pitch, goalPitch, 32); + approach_s16_asymptotic_bool(&yaw, goalYaw + 0x1200, 20); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Started shortly after Mario starts the triple jump. Stores Mario's face angle and zeros cvar2. + */ +void cutscene_grand_star_mario_jump(UNUSED struct Camera *c) { + vec3s_set(sCutsceneVars[0].angle, 0, sMarioCamState->faceAngle[1], 0); + vec3_zero(sCutsceneVars[2].point); +} + +/** + * Accelerate cvar2 to point back and to the left (relative to the camera). + */ +void cutscene_grand_star_accel_cvar2(UNUSED struct Camera *c) { + camera_approach_f32_symmetric_bool(&sCutsceneVars[2].point[2], -40.f, 2.0f); + sCutsceneVars[2].point[0] = 5.0f; +} + +/** + * Decrease cvar2 offset, follow Mario by directly updating the camera's pos. + */ +void cutscene_grand_star_approach_mario(struct Camera *c) { + camera_approach_f32_symmetric_bool(&sCutsceneVars[2].point[2], 0.f, 2.f); + sCutsceneVars[2].point[0] = 0.f; + approach_f32_asymptotic_bool(&c->pos[0], sMarioCamState->pos[0], 0.01f); + approach_f32_asymptotic_bool(&c->pos[2], sMarioCamState->pos[2], 0.01f); +} + +/** + * Offset the camera's position by cvar2. Before Mario triple jumps, this moves back and to the left. + * After the triple jump, cvar2 decelerates to 0. + */ +void cutscene_grand_star_move_cvar2(struct Camera *c) { + offset_rotated(c->pos, c->pos, sCutsceneVars[2].point, sCutsceneVars[0].angle); +} + +void cutscene_grand_star_focus_mario(struct Camera *c) { + Vec3f foc; + + vec3f_set(foc, sMarioCamState->pos[0], (sMarioCamState->pos[1] - 307.f) * 0.5f + 407.f, sMarioCamState->pos[2]); + approach_vec3f_asymptotic(c->focus, foc, 0.5f, 0.8f, 0.5f); +} + +/** + * The first part of the grand star cutscene, after Mario has collected the grand star. + */ +void cutscene_grand_star(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + cutscene_event(cutscene_grand_star_start, c, 0, 0); + cutscene_event(cutscene_grand_star_front_of_mario, c, 0, 109); + cutscene_event(cutscene_grand_star_focus_mario, c, 0, -1); + cutscene_event(cutscene_grand_star_mario_jump, c, 110, 110); + cutscene_event(cutscene_grand_star_accel_cvar2, c, 110, 159); + cutscene_event(cutscene_grand_star_approach_mario, c, 160, -1); + cutscene_event(cutscene_grand_star_move_cvar2, c, 110, -1); +} + +/** + * Zero the cvars that are used when Mario is flying. + */ +void cutscene_grand_star_fly_start(struct Camera *c) { + //! cvar7 is unused in grand star + vec3f_set(sCutsceneVars[7].point, 0.5f, 0.5f, 0.5f); + //! cvar6 is unused in grand star + vec3f_set(sCutsceneVars[6].point, 0.01f, 0.01f, 0.01f); + vec3f_set(sCutsceneVars[4].point, 0.f, 0.f, 0.f); + vec3f_set(sCutsceneVars[5].point, 0.f, c->focus[1] - sMarioCamState->pos[1], 0.f); + sCutsceneVars[8].point[2] = 0.f; + sCutsceneVars[8].point[0] = 0.f; +} + +/** + * Decrease the cvar offsets so that Lakitu flies closer to Mario. + */ +void cutscene_grand_star_fly_move_to_mario(UNUSED struct Camera *c) { + Vec3f posOff; + + vec3f_set(posOff, -600.f, 0.f, -400.f); + approach_vec3f_asymptotic(sCutsceneVars[4].point, posOff, 0.05f, 0.05f, 0.05f); + camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[1], 0.f, 2.f); + camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[2], -200.f, 6.f); +} + +/** + * Gradually increase the cvar offsets so Lakitu flies away. Mario flies offscreen to the right. + * + * cvar4 is the position offset from Mario. + * cvar5 is the focus offset from Mario. + * cvar8.point[0] is the approach velocity. + */ +void cutscene_grand_star_fly_mario_offscreen(UNUSED struct Camera *c) { + camera_approach_f32_symmetric_bool(&sCutsceneVars[8].point[0], 15.f, 0.1f); + + camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[0], -2000.f, sCutsceneVars[8].point[0]); + camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[1], 1200.f, sCutsceneVars[8].point[0] / 10.f); + camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[2], 1000.f, sCutsceneVars[8].point[0] / 10.f); + + camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[0], 0.f, sCutsceneVars[8].point[0]); + camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[1], 1200.f, sCutsceneVars[8].point[0] / 2); + camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[2], 1000.f, sCutsceneVars[8].point[0] / 1.5f); +} + +/** + * Make Lakitu approach the cvars. + * cvar4 is the position offset. + * cvar5 is the focus offset. + */ +void cutscene_grand_star_fly_app_cvars(struct Camera *c) { + Vec3f goalPos, goalFoc; + f32 dist; + s16 pitch, yaw; + + camera_approach_f32_symmetric_bool(&sCutsceneVars[8].point[2], 90.f, 2.5f); + offset_rotated(goalPos, sMarioCamState->pos, sCutsceneVars[4].point, sMarioCamState->faceAngle); + offset_rotated(goalFoc, sMarioCamState->pos, sCutsceneVars[5].point, sMarioCamState->faceAngle); + + // Move towards goalPos by cvar8's Z speed + vec3f_get_dist_and_angle(goalPos, c->pos, &dist, &pitch, &yaw); + camera_approach_f32_symmetric_bool(&dist, 0, sCutsceneVars[8].point[2]); + vec3f_set_dist_and_angle(goalPos, c->pos, dist, pitch, yaw); + + approach_vec3f_asymptotic(c->pos, goalPos, 0.01f, 0.01f, 0.01f); + approach_vec3f_asymptotic(c->focus, goalFoc, 0.5f, 0.8f, 0.5f); +} + +/** + * Part of the grand star cutscene, starts after Mario is flying. + * + * cvar4 and cvar5 are directions, relative to Mario: + * cvar4 is used as the camera position's offset from Mario. + * cvar5 is used as the camera focus's offset from Mario. + * + * cvar8.point[2] is Lakitu's speed. + */ +void cutscene_grand_star_fly(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + cutscene_event(cutscene_grand_star_fly_start, c, 0, 0); + cutscene_event(cutscene_grand_star_fly_move_to_mario, c, 0, 140); + cutscene_event(cutscene_grand_star_fly_mario_offscreen, c, 141, -1); + cutscene_event(cutscene_grand_star_fly_app_cvars, c, 0, -1); +} + +struct Cutscene sCutsceneGrandStar[] = { + { cutscene_grand_star, 360 }, + { cutscene_grand_star_fly, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/intro_peach.c b/src/camera/cutscenes/intro_peach.c new file mode 100644 index 0000000000..35f1ccb055 --- /dev/null +++ b/src/camera/cutscenes/intro_peach.c @@ -0,0 +1,293 @@ +#include "seq_ids.h" +#include "audio/external.h" +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * The intro of the game. Peach reads her letter and Lakitu flies down to Mario's warp pipe. + */ + +/** + * Plays the background music that starts while peach reads the intro message. + */ +void cutscene_intro_peach_play_message_music(void) { + play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, SEQ_EVENT_PEACH_MESSAGE), 0); +} + +/** + * Plays the music that starts after peach fades and Lakitu appears. + */ +void cutscene_intro_peach_play_lakitu_flying_music(void) { + play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(15, SEQ_EVENT_CUTSCENE_INTRO), 0); +} + +/** + * Lower the volume (US only) and start the peach letter background music + */ +void cutscene_intro_peach_start_letter_music(UNUSED struct Camera *c) { +#if defined(VERSION_US) || defined(VERSION_SH) + seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); +#endif + cutscene_intro_peach_play_message_music(); +} + +/** + * Raise the volume (not in JP) and start the flying music. + */ +void cutscene_intro_peach_start_flying_music(UNUSED struct Camera *c) { + seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60); + cutscene_intro_peach_play_lakitu_flying_music(); +} + +#ifdef VERSION_EU +/** + * Lower the volume for the letter background music. In US, this happens on the same frame as the music + * starts. + */ +void cutscene_intro_peach_eu_lower_volume(UNUSED struct Camera *c) { + seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); +} +#endif + +/** + * Move the camera along `positionSpline` and point its focus at the corresponding point along + * `focusSpline`. sCutsceneSplineSegmentProgress is updated after pos and focus are calculated. + */ +s32 intro_peach_move_camera_start_to_pipe(struct Camera *c, struct CutsceneSplinePoint positionSpline[], + struct CutsceneSplinePoint focusSpline[]) { + Vec3f offset; + s32 posReturn = 0; + s32 focusReturn = 0; + + /** + * The position spline's speed parameters are all 0, so sCutsceneSplineSegmentProgress doesn't get + * updated. Otherwise position would move two frames ahead, and c->focus would always be one frame + * further along the spline than c->pos. + */ + posReturn = move_point_along_spline(c->pos, positionSpline, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + focusReturn = move_point_along_spline(c->focus, focusSpline, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + + // The two splines used by this function are reflected in the horizontal plane for some reason, + // so they are rotated every frame. Why do this, Nintendo? + rotate_in_xz(c->focus, c->focus, DEGREES(-180)); + rotate_in_xz(c->pos, c->pos, DEGREES(-180)); + + vec3f_set(offset, -1328.f, 260.f, 4664.f); + vec3f_add(c->focus, offset); + vec3f_add(c->pos, offset); + + posReturn += focusReturn; // Unused + return focusReturn; +} + +/** + * Create a dialog box with the letter text + */ +void peach_letter_text(UNUSED struct Camera *c) { + create_dialog_box(DIALOG_020); +} + +#ifndef VERSION_JP +void play_sound_peach_reading_letter(UNUSED struct Camera *c) { + play_sound(SOUND_PEACH_DEAR_MARIO, gGlobalSoundSource); +} +#endif + +/** + * Move the camera from peach reading the letter all the way to Mario's warp pipe. Follow the + * sIntroStartToPipe splines. + */ +void cutscene_intro_peach_start_to_pipe_spline(struct Camera *c) { + static struct CutsceneSplinePoint sIntroStartToPipePosition[] = { + { 0, 0, { 2122, 8762, 9114 } }, { 0, 0, { 2122, 8762, 9114 } }, { 1, 0, { 2122, 7916, 9114 } }, + { 1, 0, { 2122, 7916, 9114 } }, { 2, 0, { 957, 5166, 8613 } }, { 3, 0, { 589, 4338, 7727 } }, + { 4, 0, { 690, 3366, 6267 } }, { 5, 0, { -1600, 2151, 4955 } }, { 6, 0, { -1557, 232, 1283 } }, + { 7, 0, { -6962, -295, 2729 } }, { 8, 0, { -6979, 131, 3246 } }, { 9, 0, { -6360, -283, 4044 } }, + { 0, 0, { -5695, -334, 5264 } }, { 1, 0, { -5568, -319, 7933 } }, { 2, 0, { -3848, -200, 6278 } }, + { 3, 0, { -965, -263, 6092 } }, { 4, 0, { 1607, 2465, 6329 } }, { 5, 0, { 2824, 180, 3548 } }, + { 6, 0, { 1236, 136, 945 } }, { 0, 0, { 448, 136, 564 } }, { 0, 0, { 448, 136, 564 } }, + { 0, 0, { 448, 136, 564 } }, { -1, 0, { 448, 136, 564 } } + }; + static struct CutsceneSplinePoint sIntroStartToPipeFocus[] = { + { 0, 50, { 1753, 29800, 8999 } }, { 0, 50, { 1753, 29800, 8999 } }, + { 1, 50, { 1753, 8580, 8999 } }, { 1, 100, { 1753, 8580, 8999 } }, + { 2, 50, { 520, 5400, 8674 } }, { 3, 50, { 122, 4437, 7875 } }, + { 4, 50, { 316, 3333, 6538 } }, { 5, 36, { -1526, 2189, 5448 } }, + { 6, 50, { -1517, 452, 1731 } }, { 7, 50, { -6659, -181, 3109 } }, + { 8, 17, { -6649, 183, 3618 } }, { 9, 20, { -6009, -214, 4395 } }, + { 0, 50, { -5258, -175, 5449 } }, { 1, 36, { -5158, -266, 7651 } }, + { 2, 26, { -3351, -192, 6222 } }, { 3, 25, { -483, -137, 6060 } }, + { 4, 100, { 1833, 2211, 5962 } }, { 5, 26, { 3022, 207, 3090 } }, + { 6, 20, { 1250, 197, 449 } }, { 7, 50, { 248, 191, 227 } }, + { 7, 0, { 48, 191, 227 } }, { 7, 0, { 48, 191, 227 } }, + { -1, 0, { 48, 191, 227 } } + }; + + if (intro_peach_move_camera_start_to_pipe(c, sIntroStartToPipePosition, sIntroStartToPipeFocus) != 0) { + gCameraMovementFlags &= ~CAM_MOVE_C_UP_MODE; + gCutsceneTimer = CUTSCENE_LOOP; + } +} + +/** + * Loop the cutscene until Mario exits the dialog. + */ +void cutscene_intro_peach_dialog(struct Camera *c) { + if (get_dialog_id() == DIALOG_NONE) { + vec3f_copy(gLakituState.goalPos, c->pos); + vec3f_copy(gLakituState.goalFocus, c->focus); + sStatusFlags |= (CAM_FLAG_SMOOTH_MOVEMENT | CAM_FLAG_UNUSED_CUTSCENE_ACTIVE); + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + } +} + +/** + * Describes the spline the camera follows, starting when the camera jumps to Lakitu and ending after + * Mario jumps out of the pipe when the first dialog opens. + */ +void cutscene_intro_peach_follow_pipe_spline(struct Camera *c) { + static struct CutsceneSplinePoint sIntroPipeToDialogPosition[] = { + { 0, 0, { -785, 625, 4527 } }, { 1, 0, { -785, 625, 4527 } }, { 2, 0, { -1286, 644, 4376 } }, + { 3, 0, { -1286, 623, 4387 } }, { 4, 0, { -1286, 388, 3963 } }, { 5, 0, { -1286, 358, 4093 } }, + { 6, 0, { -1386, 354, 4159 } }, { 7, 0, { -1477, 306, 4223 } }, { 8, 0, { -1540, 299, 4378 } }, + { 9, 0, { -1473, 316, 4574 } }, { 0, 0, { -1328, 485, 5017 } }, { 0, 0, { -1328, 485, 5017 } }, + { 0, 0, { -1328, 485, 5017 } }, { -1, 0, { -1328, 485, 5017 } } + }; + +#ifdef VERSION_EU + static struct CutsceneSplinePoint sIntroPipeToDialogFocus[] = { + { 0, 25, { -1248, 450, 4596 } }, { 1, 71, { -1258, 485, 4606 } }, { 2, 71, { -1379, 344, 4769 } }, + { 3, 22, { -1335, 366, 4815 } }, { 4, 23, { -1315, 370, 4450 } }, { 5, 40, { -1322, 333, 4591 } }, + { 6, 25, { -1185, 329, 4616 } }, { 7, 21, { -1059, 380, 4487 } }, { 8, 14, { -1086, 421, 4206 } }, + { 9, 21, { -1321, 346, 4098 } }, { 0, 0, { -1328, 385, 4354 } }, { 0, 0, { -1328, 385, 4354 } }, + { 0, 0, { -1328, 385, 4354 } }, { -1, 0, { -1328, 385, 4354 } } + }; +#else + static struct CutsceneSplinePoint sIntroPipeToDialogFocus[] = { + { 0, 20, { -1248, 450, 4596 } }, { 1, 59, { -1258, 485, 4606 } }, { 2, 59, { -1379, 344, 4769 } }, + { 3, 20, { -1335, 366, 4815 } }, { 4, 23, { -1315, 370, 4450 } }, { 5, 40, { -1322, 333, 4591 } }, + { 6, 25, { -1185, 329, 4616 } }, { 7, 21, { -1059, 380, 4487 } }, { 8, 14, { -1086, 421, 4206 } }, + { 9, 21, { -1321, 346, 4098 } }, { 0, 0, { -1328, 385, 4354 } }, { 0, 0, { -1328, 385, 4354 } }, + { 0, 0, { -1328, 385, 4354 } }, { -1, 0, { -1328, 385, 4354 } } + }; +#endif + + move_point_along_spline(c->pos, sIntroPipeToDialogPosition, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + move_point_along_spline(c->focus, sIntroPipeToDialogFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); +} + +void cutscene_intro_peach_clear_cutscene_status(UNUSED struct Camera *c) { + sMarioCamState->cameraEvent = CAM_EVENT_NONE; +} + +/** + * Set fov to 8 degrees, then zoom out to 30. + */ +void cutscene_intro_peach_zoom_fov(UNUSED struct Camera *c) { + sFOVState.fov = 8.f; + set_fov_function(CAM_FOV_ZOOM_30); +} + +/** + * Reset the spline progress, turn on handheld shake. + */ +void cutscene_intro_peach_reset_spline(UNUSED struct Camera *c) { + sCutsceneSplineSegment = 0; + sCutsceneSplineSegmentProgress = 0.1f; + //! @bug since this event is only called for one frame, this handheld shake is turned off on the + //! next frame. + set_handheld_shake(HAND_CAM_SHAKE_HIGH); +} + +/** + * Turn off handheld shake. This was likely written before handheld shake was changed to turn off every + * frame, as it's the only instance of HAND_CAM_SHAKE_OFF. + */ +void cutscene_intro_peach_handheld_shake_off(UNUSED struct Camera *c) { + set_handheld_shake(HAND_CAM_SHAKE_OFF); +} + +void intro_pipe_exit_text(UNUSED struct Camera *c) { + create_dialog_box(DIALOG_033); +} + +void play_sound_intro_turn_on_hud(UNUSED struct Camera *c) { + play_sound_rbutton_changed(); +} + +/** + * Fly to the pipe. Near the end, the camera jumps to Lakitu's position and the hud turns on. + */ +void cutscene_intro_peach_fly_to_pipe(struct Camera *c) { + cutscene_event(play_sound_intro_turn_on_hud, c, 818, 818); + cutscene_spawn_obj(6, 1); + cutscene_event(cutscene_intro_peach_start_flying_music, c, 0, 0); + cutscene_event(cutscene_intro_peach_start_to_pipe_spline, c, 0, -1); + cutscene_event(cutscene_intro_peach_clear_cutscene_status, c, 717, 717); + clamp_pitch(c->pos, c->focus, 0x3B00, -0x3B00); + sCutsceneVars[1].point[1] = 400.f; +} + +/** + * Lakitu flies around the warp pipe, then Mario jumps out. + */ +void cutscene_intro_peach_mario_appears(struct Camera *c) { + sMarioCamState->cameraEvent = 0; + cutscene_event(cutscene_intro_peach_reset_spline, c, 0, 0); + cutscene_event(cutscene_intro_peach_follow_pipe_spline, c, 0, -1); + cutscene_event(cutscene_intro_peach_handheld_shake_off, c, 70, 70); + cutscene_event(intro_pipe_exit_text, c, 250, 250); + + approach_f32_asymptotic_bool(&sCutsceneVars[1].point[1], 80.f + sMarioGeometry.currFloorHeight + + (sMarioCamState->pos[1] - sMarioGeometry.currFloorHeight) * 1.1f, 0.4f); + + // Make the camera look up as Mario jumps out of the pipe + if (c->focus[1] < sCutsceneVars[1].point[1]) { + c->focus[1] = sCutsceneVars[1].point[1]; + } + + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +/** + * Reset the fov. This gives the effect of peach zooming out as she fades. + */ +void cutscene_intro_peach_reset_fov(UNUSED struct Camera *c) { + set_fov_function(CAM_FOV_DEFAULT); +} + +/** + * Peach reads the letter to Mario. + */ +void cutscene_intro_peach_letter(struct Camera *c) { + cutscene_spawn_obj(CUTSCENE_OBJ_BEGINNING_PEACH, 0); + cutscene_event(cutscene_intro_peach_zoom_fov, c, 0, 0); + cutscene_event(cutscene_intro_peach_start_letter_music, c, 65, 65); + cutscene_event(cutscene_intro_peach_start_to_pipe_spline, c, 0, 0); + cutscene_event(peach_letter_text, c, 65, 65); +#ifndef VERSION_JP + cutscene_event(play_sound_peach_reading_letter, c, 83, 83); +#endif + + if ((gCutsceneTimer > 120) && (get_dialog_id() == DIALOG_NONE)) { + // Start the next scene + gCutsceneTimer = CUTSCENE_LOOP; + } + + clamp_pitch(c->pos, c->focus, 0x3B00, -0x3B00); +} + +struct Cutscene sCutsceneIntroPeach[] = { + { cutscene_intro_peach_letter, CUTSCENE_LOOP }, + { cutscene_intro_peach_reset_fov, 35 }, +#ifdef VERSION_EU + { cutscene_intro_peach_fly_to_pipe, 675 }, +#else + { cutscene_intro_peach_fly_to_pipe, 820 }, +#endif + { cutscene_intro_peach_mario_appears, 270 }, + { cutscene_intro_peach_dialog, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/key_dance.c b/src/camera/cutscenes/key_dance.c new file mode 100644 index 0000000000..26f7b10d89 --- /dev/null +++ b/src/camera/cutscenes/key_dance.c @@ -0,0 +1,84 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Star dance cutscene. + * The camera moves closer and rotates clockwise around Mario. + */ + +/** + * Jump the camera pos and focus to cvar 8 and 7. + * Called every frame, starting after 10, so when these cvars are updated, the camera will jump. + */ +void cutscene_key_dance_jump_cvar(struct Camera *c) { + offset_rotated(c->pos, sMarioCamState->pos, sCutsceneVars[8].point, sMarioCamState->faceAngle); + offset_rotated(c->focus, sMarioCamState->pos, sCutsceneVars[7].point, sMarioCamState->faceAngle); +} + +/** + * Jump to a closeup view of Mario and the key. + */ +void cutscene_key_dance_jump_closeup(UNUSED struct Camera *c) { + vec3f_set(sCutsceneVars[8].point, 38.f, 171.f, -248.f); + vec3f_set(sCutsceneVars[7].point, -57.f, 51.f, 187.f); +} + +/** + * Jump to a view from the lower left (Mario's right). + */ +void cutscene_key_dance_jump_lower_left(UNUSED struct Camera *c) { + vec3f_set(sCutsceneVars[8].point, -178.f, 62.f, -132.f); + vec3f_set(sCutsceneVars[7].point, 299.f, 91.f, 58.f); +} + +/** + * Jump to a rotated view from above. + */ +void cutscene_key_dance_jump_above(UNUSED struct Camera *c) { + gLakituState.keyDanceRoll = 0x2800; + vec3f_set(sCutsceneVars[8].point, 89.f, 373.f, -304.f); + vec3f_set(sCutsceneVars[7].point, 0.f, 127.f, 0.f); +} + +/** + * Finally, jump to a further view, slightly to Mario's left. + */ +void cutscene_key_dance_jump_last(UNUSED struct Camera *c) { + gLakituState.keyDanceRoll = 0; + vec3f_set(sCutsceneVars[8].point, 135.f, 158.f, -673.f); + vec3f_set(sCutsceneVars[7].point, -20.f, 135.f, -198.f); +} + +void cutscene_key_dance_shake_fov(UNUSED struct Camera *c) { + set_fov_shake(0x180, 0x30, 0x8000); +} + +void cutscene_key_dance_handheld_shake(UNUSED struct Camera *c) { + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +void cutscene_key_dance_focus_mario(struct Camera *c) { + focus_in_front_of_mario(c, 0, 0.2f); +} + +/** + * Cutscene that plays when Mario collects a key from bowser. It's basically a sequence of four jump + * cuts. + */ +void cutscene_key_dance(struct Camera *c) { + cutscene_event(cutscene_dance_move_to_mario, c, 0, 10); + cutscene_event(cutscene_key_dance_focus_mario, c, 0, 10); + cutscene_event(cutscene_key_dance_jump_closeup, c, 0, 0); + cutscene_event(cutscene_key_dance_jump_lower_left, c, 20, 20); + cutscene_event(cutscene_key_dance_jump_above, c, 35, 35); + cutscene_event(cutscene_key_dance_jump_last, c, 52, 52); + cutscene_event(cutscene_key_dance_jump_cvar, c, 11, -1); + cutscene_event(cutscene_key_dance_shake_fov, c, 54, 54); + cutscene_event(cutscene_key_dance_handheld_shake, c, 52, -1); +} + +struct Cutscene sCutsceneKeyDance[] = { + { cutscene_key_dance, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/level_exit.c b/src/camera/cutscenes/level_exit.c new file mode 100644 index 0000000000..6e500fa977 --- /dev/null +++ b/src/camera/cutscenes/level_exit.c @@ -0,0 +1,437 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + +/** + * Set cvar7 to Mario's pos and faceAngle + * Set cvar6 to the focus offset from Mario. + * set cvar5 to the pos offset from Mario. (This is always overwritten) + */ +void cutscene_non_painting_death_start(UNUSED struct Camera *c) { + vec3f_copy(sCutsceneVars[7].point, sMarioCamState->pos); + vec3s_copy(sCutsceneVars[7].angle, sMarioCamState->faceAngle); + vec3f_set(sCutsceneVars[6].point, -42.f, 350.f, 727.f); + // This is always overwritten, except in the unused cutscene_exit_bowser_death() + vec3f_set(sCutsceneVars[5].point, 107.f, 226.f, 1187.f); +} + +/** + * Set the camera pos depending on which level Mario exited. + */ +void cutscene_non_painting_set_cam_pos(struct Camera *c) { + struct Surface *floor; + + switch (gPrevLevel) { + case LEVEL_HMC: + vec3f_set(c->pos, 3465.f, -1008.f, -2961.f); + break; + + case LEVEL_COTMC: + vec3f_set(c->pos, 3465.f, -1008.f, -2961.f); + break; + + case LEVEL_RR: + vec3f_set(c->pos, -3741.f, 3151.f, 6065.f); + break; + + case LEVEL_WMOTR: + vec3f_set(c->pos, 1972.f, 3230.f, 5891.f); + break; + + default: + offset_rotated(c->pos, sCutsceneVars[7].point, sCutsceneVars[5].point, sCutsceneVars[7].angle); + c->pos[1] = find_floor(c->pos[0], c->pos[1] + 1000.f, c->pos[2], &floor) + 125.f; + break; + } +} + +/** + * Update the camera focus depending on which level Mario exited. + */ +void cutscene_non_painting_set_cam_focus(struct Camera *c) { + offset_rotated(c->focus, sCutsceneVars[7].point, sCutsceneVars[6].point, sCutsceneVars[7].angle); + + if ((gPrevLevel == LEVEL_COTMC) || (gPrevLevel == LEVEL_HMC) || (gPrevLevel == LEVEL_RR) + || (gPrevLevel == LEVEL_WMOTR)) { + c->focus[0] = c->pos[0] + (sMarioCamState->pos[0] - c->pos[0]) * 0.7f; + c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] - c->pos[1]) * 0.4f; + c->focus[2] = c->pos[2] + (sMarioCamState->pos[2] - c->pos[2]) * 0.7f; + } else { + c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] - c->pos[1]) * 0.2f; + } +} + +/** + * End a non-painting exit cutscene. Used by BBH and bowser courses. + */ +void cutscene_non_painting_end(struct Camera *c) { + c->cutscene = 0; + +#ifdef USE_COURSE_DEFAULT_MODE + c->mode = c->defMode; +#else + if (c->defMode == CAMERA_MODE_CLOSE) { + c->mode = CAMERA_MODE_CLOSE; + } else { + c->mode = CAMERA_MODE_FREE_ROAM; + } +#endif + + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + transition_next_state(c, 60); + cutscene_update_camera_yaw(c); +} + +/** + * Warp the camera to Mario, then use his faceAngle to calculate the right relative position. + * + * cvar0.point is Mario's position + * cvar0.angle is Mario's faceAngle + * + * cvar1 is the camera's position relative to Mario + * cvar2 is the camera's focus relative to Mario + */ +void cutscene_exit_painting_start(struct Camera *c) { + struct Surface *floor; + f32 floorHeight; + + vec3f_set(sCutsceneVars[2].point, 258.f, -352.f, 1189.f); + vec3f_set(sCutsceneVars[1].point, 65.f, -155.f, 444.f); + + if (gPrevLevel == LEVEL_TTM) { + sCutsceneVars[1].point[1] = 0.f; + sCutsceneVars[1].point[2] = 0.f; + } + vec3f_copy(sCutsceneVars[0].point, sMarioCamState->pos); + sCutsceneVars[0].angle[0] = 0; + sCutsceneVars[0].angle[1] = sMarioCamState->faceAngle[1]; + sCutsceneVars[0].angle[2] = 0; + offset_rotated(c->focus, sCutsceneVars[0].point, sCutsceneVars[1].point, sCutsceneVars[0].angle); + offset_rotated(c->pos, sCutsceneVars[0].point, sCutsceneVars[2].point, sCutsceneVars[0].angle); + floorHeight = find_floor(c->pos[0], c->pos[1] + 10.f, c->pos[2], &floor); + + if (floorHeight != FLOOR_LOWER_LIMIT) { + if (c->pos[1] < (floorHeight += 60.f)) { + c->pos[1] = floorHeight; + } + } +} + +/** + * Decrease cvar2's x and z offset, moving closer to Mario. + */ +void cutscene_exit_painting_move_to_mario(struct Camera *c) { + Vec3f pos; + + //! Tricky math: Since offset_rotated() flips Z offsets, you'd expect a positive Z offset to move + //! the camera into the wall. However, Mario's faceAngle always points into the painting, so a + //! positive Z offset moves the camera "behind" Mario, away from the painting. + //! + //! In the success cutscene, when Mario jumps out face-first, only his gfx angle is updated. His + //! actual face angle isn't updated until after the cutscene. + approach_f32_asymptotic_bool(&sCutsceneVars[2].point[0], 178.f, 0.05f); + approach_f32_asymptotic_bool(&sCutsceneVars[2].point[2], 889.f, 0.05f); + offset_rotated(pos, sCutsceneVars[0].point, sCutsceneVars[2].point, sCutsceneVars[0].angle); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; +} + +/** + * Move the camera down to the floor Mario lands on. + */ +void cutscene_exit_painting_move_to_floor(struct Camera *c) { + struct Surface *floor; + Vec3f floorHeight; + + vec3f_copy(floorHeight, sMarioCamState->pos); + floorHeight[1] = find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 10.f, sMarioCamState->pos[2], &floor); + + if (floor != NULL) { + floorHeight[1] = floorHeight[1] + (sMarioCamState->pos[1] - floorHeight[1]) * 0.7f + 125.f; + approach_vec3f_asymptotic(c->focus, floorHeight, 0.2f, 0.2f, 0.2f); + + if (floorHeight[1] < c->pos[1]) { + approach_f32_asymptotic_bool(&c->pos[1], floorHeight[1], 0.05f); + } + } +} + +/** + * Cutscene played when Mario leaves a painting, either due to death or collecting a star. + */ +void cutscene_exit_painting(struct Camera *c) { + cutscene_event(cutscene_exit_painting_start, c, 0, 0); + cutscene_event(cutscene_exit_painting_move_to_mario, c, 5, -1); + cutscene_event(cutscene_exit_painting_move_to_floor, c, 5, -1); + + //! Hardcoded position. TTM's painting is close to an opposite wall, so just fix the pos. + if (gPrevLevel == LEVEL_TTM) { + vec3f_set(c->pos, -296.f, 1261.f, 3521.f); + } + + cutscene_update_camera_yaw(c); +} + +/** + * Give control back to the player. + */ +void cutscene_exit_painting_end(struct Camera *c) { +#ifdef USE_COURSE_DEFAULT_MODE + c->mode = c->defMode; +#else + c->mode = CAMERA_MODE_CLOSE; +#endif + c->cutscene = CUTSCENE_NONE; + gCutsceneTimer = CUTSCENE_STOP; + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + cutscene_update_camera_yaw(c); +} + +/** + * This cutscene is the same as non_painting_death, but the camera is closer to Mario and lower. + * Because it it doesn't call cutscene_non_painting_death_override_offset, the value from + * cutscene_non_painting_death_start is used. + * + * This cutscene is unused, dying in bowser's arena spawns Mario near the warp pipe, not back in the + * hub. + */ +void cutscene_exit_bowser_death(struct Camera *c) { + cutscene_event(cutscene_non_painting_death_start, c, 0, 0); + cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); + cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); +} + +/** + * Set cvars: + * cvar7 is Mario's pos and angle + * cvar6 is the focus offset + * cvar5 is the position offset + */ +void cutscene_exit_succ_start(UNUSED struct Camera *c) { + vec3f_copy(sCutsceneVars[7].point, sMarioCamState->pos); + vec3s_copy(sCutsceneVars[7].angle, sMarioCamState->faceAngle); + vec3f_set(sCutsceneVars[6].point, 6.f, 363.f, 543.f); + vec3f_set(sCutsceneVars[5].point, 137.f, 226.f, 995.f); +} + +/** + * Focus slightly left of Mario. Perhaps to keep the bowser painting in view? + */ +void cutscene_exit_bowser_succ_focus_left(UNUSED struct Camera *c) { + approach_f32_asymptotic_bool(&sCutsceneVars[6].point[0], -24.f, 0.05f); +} + +/** + * Instead of focusing on the key, just start a pitch shake. Clever! + * The shake lasts 32 frames. + */ +void cutscene_exit_bowser_key_toss_shake(struct Camera *c) { + //! Unnecessary check. + if (c->cutscene == CUTSCENE_EXIT_BOWSER_SUCC) { + set_camera_pitch_shake(0x800, 0x40, 0x800); + } +} + +/** + * Start a camera shake when Mario lands on the ground. + */ +void cutscene_exit_succ_shake_landing(UNUSED struct Camera *c) { + set_environmental_camera_shake(SHAKE_ENV_EXPLOSION); +} + +/** + * Cutscene that plays when Mario beats bowser and exits the level. + */ +void cutscene_exit_bowser_succ(struct Camera *c) { + cutscene_event(cutscene_exit_succ_start, c, 0, 0); + cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); + cutscene_event(cutscene_exit_bowser_succ_focus_left, c, 18, -1); + cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); + cutscene_event(cutscene_exit_bowser_key_toss_shake, c, 125, 125); + cutscene_event(cutscene_exit_succ_shake_landing, c, 41, 41); +} + +/** + * Override the position offset. + */ +void cutscene_exit_non_painting_succ_override_cvar(UNUSED struct Camera *c) { + vec3f_set(sCutsceneVars[5].point, 137.f, 246.f, 1115.f); +} + +/** + * Cutscene that plays when Mario collects a star and leaves a non-painting course, like HMC or BBH. + */ +void cutscene_exit_non_painting_succ(struct Camera *c) { + cutscene_event(cutscene_exit_succ_start, c, 0, 0); + cutscene_event(cutscene_exit_non_painting_succ_override_cvar, c, 0, 0); + cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); + cutscene_event(cutscene_exit_bowser_succ_focus_left, c, 18, -1); + cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); + cutscene_event(cutscene_exit_succ_shake_landing, c, 41, 41); + cutscene_update_camera_yaw(c); +} + +/** + * Set the offset from Mario depending on the course Mario exited. + * This overrides cutscene_non_painting_death_start() + */ +void cutscene_non_painting_death_override_offset(UNUSED struct Camera *c) { + switch (gPrevLevel) { + case LEVEL_HMC: + vec3f_set(sCutsceneVars[5].point, 187.f, 369.f, -197.f); + break; + case LEVEL_COTMC: + vec3f_set(sCutsceneVars[5].point, 187.f, 369.f, -197.f); + break; + default: + vec3f_set(sCutsceneVars[5].point, 107.f, 246.f, 1307.f); + break; + } +} + +/** + * Cutscene played when Mario dies in a non-painting course, like HMC or BBH. + */ +void cutscene_non_painting_death(struct Camera *c) { + cutscene_event(cutscene_non_painting_death_start, c, 0, 0); + cutscene_event(cutscene_non_painting_death_override_offset, c, 0, 0); + cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); + cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +void cutscene_exit_waterfall_warp(struct Camera *c) { + //! hardcoded position + vec3f_set(c->pos, -3899.f, 39.f, -5671.f); +} + +/** + * Look at Mario, used by cutscenes that play when Mario exits a course to castle grounds. + */ +void cutscene_exit_to_castle_grounds_focus_mario(struct Camera *c) { + vec3f_copy(c->focus, sMarioCamState->pos); + c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] + 125.f - c->pos[1]) * 0.5f; + approach_vec3f_asymptotic(c->focus, sMarioCamState->pos, 0.05f, 0.4f, 0.05f); +} + +/** + * Cutscene that plays when Mario leaves CotMC through the waterfall. + */ +void cutscene_exit_waterfall(struct Camera *c) { + cutscene_event(cutscene_exit_waterfall_warp, c, 0, 0); + cutscene_event(cutscene_exit_to_castle_grounds_focus_mario, c, 0, -1); + cutscene_update_camera_yaw(c); +} + +void cutscene_exit_fall_to_castle_grounds_warp(struct Camera *c) { + //! hardcoded position + vec3f_set(c->pos, 5830.f, 32.f, 3985.f); +} + +/** + * Cutscene that plays when Mario falls from WMOTR. + */ +void cutscene_exit_fall_to_castle_grounds(struct Camera *c) { + cutscene_event(cutscene_exit_fall_to_castle_grounds_warp, c, 0, 0); + cutscene_event(cutscene_exit_to_castle_grounds_focus_mario, c, 0, -1); + cutscene_update_camera_yaw(c); +} + +/** + * Unused. Warp the camera to Mario. + */ +void cutscene_unused_exit_start(struct Camera *c) { + Vec3f offset; + Vec3s marioAngle; + + vec3f_set(offset, 200.f, 300.f, 200.f); + vec3s_set(marioAngle, 0, sMarioCamState->faceAngle[1], 0); + offset_rotated(c->pos, sMarioCamState->pos, offset, marioAngle); + set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); +} + +/** + * Unused. Focus on Mario as he exits. + */ +void cutscene_unused_exit_focus_mario(struct Camera *c) { + Vec3f focus; + + vec3f_set(focus, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); + set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); + approach_vec3f_asymptotic(c->focus, focus, 0.02f, 0.001f, 0.02f); + cutscene_update_camera_yaw(c); +} + +/** + * Cutscene that plays when Mario dies and warps back to the castle. + */ +struct Cutscene sCutsceneDeathExit[] = { + { cutscene_exit_painting, 118 }, + { cutscene_exit_painting_end, 0 } +}; + +/** + * Unused cutscene for when Mario dies in bowser's arena. Instead, Mario just respawns at the warp pipe. + */ +struct Cutscene sCutsceneExitBowserDeath[] = { + { cutscene_exit_bowser_death, 120 }, + { cutscene_non_painting_end, 0 } +}; + +/** + * Cutscene that plays when Mario exits bowser's arena after getting the key. + */ +struct Cutscene sCutsceneExitBowserSuccess[] = { + { cutscene_exit_bowser_succ, 190 }, + { cutscene_non_painting_end, 0 } +}; + +/** + * Cutscene that plays when Mario warps to the castle after collecting a star. + */ +struct Cutscene sCutsceneExitPaintingSuccess[] = { + { cutscene_exit_painting, 180 }, + { cutscene_exit_painting_end, 0 } +}; + +/** + * Cutscene that plays when Mario exits a non-painting course, like HMC. + */ +struct Cutscene sCutsceneExitSpecialSuccess[] = { + { cutscene_exit_non_painting_succ, 163 }, + { cutscene_non_painting_end, 0 } +}; + +/** + * Cutscene that plays when Mario enters the castle grounds after leaving CotMC through the waterfall. + */ +struct Cutscene sCutsceneExitWaterfall[] = { + { cutscene_exit_waterfall, 52 }, + { cutscene_exit_to_castle_grounds_end, 0 } +}; + +/** + * Cutscene that plays when Mario falls from WMOTR. + */ +struct Cutscene sCutsceneFallToCastleGrounds[] = { + { cutscene_exit_fall_to_castle_grounds, 73 }, + { cutscene_exit_to_castle_grounds_end, 0 } +}; + +/** + * Cutscene that plays when Mario exits from dying in a non-painting course, like HMC. + */ +struct Cutscene sCutsceneNonPaintingDeath[] = { + { cutscene_non_painting_death, 120 }, + { cutscene_non_painting_end, 0 } +}; + +struct Cutscene sCutsceneUnusedExit[] = { + { cutscene_unused_exit_start, 1 }, + { cutscene_unused_exit_focus_mario, 60 }, + { cutscene_exit_painting_end, 0 } +}; diff --git a/src/camera/cutscenes/prepare_cannon.c b/src/camera/cutscenes/prepare_cannon.c new file mode 100644 index 0000000000..10a2bf0c3f --- /dev/null +++ b/src/camera/cutscenes/prepare_cannon.c @@ -0,0 +1,86 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when a cannon door is opened. + */ + +/** + * Store the camera's pos and focus, and copy the cannon's position to cvars. + */ +void cutscene_prepare_cannon_start(struct Camera *c) { + store_info_cannon(c); + vec3f_copy(sCutsceneVars[0].point, c->focus); + sCutsceneVars[2].point[0] = 30.f; + // Store the cannon door's position in sCutsceneVars[3]'s point + object_pos_to_vec3f(sCutsceneVars[3].point, gCutsceneFocus); + vec3s_set(sCutsceneVars[5].angle, 0, 0, 0); +} + +/** + * Fly towards the cannon door. + */ +void cutscene_prepare_cannon_fly_to_cannon(struct Camera *c) { + cutscene_goto_cvar_pos(c, 300.f, 0x2000, 0, sCutsceneVars[5].angle[1]); + camera_approach_s16_symmetric_bool(&sCutsceneVars[5].angle[1], 0x400, 17); + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +/** + * Used in the cannon opening cutscene to fly back to the camera's last position and focus + */ +void cannon_approach_prev(f32 *value, f32 target) { + f32 inc = absf(target - *value) / sCutsceneVars[2].point[0]; + camera_approach_f32_symmetric_bool(value, target, inc); +} + +/** + * Fly or warp back to the previous pos and focus, stored in sCameraStoreCutscene. + */ +void cutscene_prepare_cannon_fly_back(struct Camera *c) { + f32 distToPrevPos = calc_abs_dist_squared(c->pos, sCameraStoreCutscene.pos); + + if (distToPrevPos < sqr(8000.f)) { + cannon_approach_prev(&c->pos[0], sCameraStoreCutscene.pos[0]); + cannon_approach_prev(&c->pos[1], sCameraStoreCutscene.pos[1]); + cannon_approach_prev(&c->pos[2], sCameraStoreCutscene.pos[2]); + cannon_approach_prev(&c->focus[0], sCameraStoreCutscene.focus[0]); + cannon_approach_prev(&c->focus[1], sCameraStoreCutscene.focus[1]); + cannon_approach_prev(&c->focus[2], sCameraStoreCutscene.focus[2]); + } else { + // If too far away, just warp back + vec3f_copy(c->focus, sCameraStoreCutscene.focus); + vec3f_copy(c->pos, sCameraStoreCutscene.pos); + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + } + if (sCutsceneVars[2].point[0] > 1.f) { + sCutsceneVars[2].point[0] -= 1.f; + } +} + +/** + * Cutscene that plays when the cannon is opened. + */ +void cutscene_prepare_cannon(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + cutscene_event(cutscene_prepare_cannon_start, c, 0, 0); + cutscene_event(cutscene_prepare_cannon_fly_to_cannon, c, 0, 140); + cutscene_event(cutscene_prepare_cannon_fly_back, c, 141, -1); +} + +/** + * Stop the cannon opening cutscene. + */ +void cutscene_prepare_cannon_end(struct Camera *c) { + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + retrieve_info_cannon(c); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; +} + +struct Cutscene sCutscenePrepareCannon[] = { + { cutscene_prepare_cannon, 170 }, + { cutscene_prepare_cannon_end, 0 } +}; diff --git a/src/camera/cutscenes/pyramid_top_explode.c b/src/camera/cutscenes/pyramid_top_explode.c new file mode 100644 index 0000000000..9ebdc292e7 --- /dev/null +++ b/src/camera/cutscenes/pyramid_top_explode.c @@ -0,0 +1,116 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Unused cutscene for when the pyramid explodes. + */ + +/** + * Store the camera focus in cvar1. + * Store the area's center position (which happens to be the pyramid, in SSL) in cvar3. + */ +void cutscene_pyramid_top_explode_start(struct Camera *c) { + reset_pan_distance(c); + store_info_cannon(c); + + vec3f_copy(sCutsceneVars[1].point, c->focus); + vec3f_set(sCutsceneVars[3].point, c->areaCenX, 1280.f, c->areaCenZ); +} + +/** + * Zoom in on the pyramid. + */ +void cutscene_pyramid_top_explode_zoom_in(UNUSED struct Camera *c) { + set_fov_function(CAM_FOV_APP_30); +} + +/** + * Look at the pyramid top. + */ +void cutscene_pyramid_top_explode_focus(struct Camera *c) { + approach_vec3f_asymptotic(c->focus, sCutsceneVars[3].point, 0.02f, 0.02f, 0.02f); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; +} + +/** + * Store the old pos and focus, then warp to the pyramid top. + */ +void cutscene_pyramid_top_explode_warp(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + set_fov_function(CAM_FOV_DEFAULT); + sFOVState.fov = 45.f; + + vec3f_copy(sCutsceneVars[4].point, c->pos); + vec3f_copy(sCutsceneVars[5].point, c->focus); + vec3f_copy(c->focus, sCutsceneVars[3].point); + + vec3f_get_dist_and_angle(sCutsceneVars[3].point, sMarioCamState[0].pos, &dist, &pitch, &yaw); + vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, 2000.f, 0, yaw); + c->pos[1] += 500.f; +} + +/** + * Close up view of the spinning pyramid top as it rises. + */ +void cutscene_pyramid_top_explode_closeup(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + vec3f_get_dist_and_angle(sCutsceneVars[3].point, c->pos, &dist, &pitch, &yaw); + approach_f32_asymptotic_bool(&dist, 2000.f, 0.1f); + vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, dist, pitch, yaw); + + c->focus[1] += 4.f; + c->pos[1] -= 5.f; + sFOVState.fov = 45.f; + set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); +} + +/** + * Shake the camera during the closeup. + */ +void cutscene_pyramid_top_explode_cam_shake(UNUSED struct Camera *c) { + set_environmental_camera_shake(SHAKE_ENV_PYRAMID_EXPLODE); +} + +/** + * Warp back to the old position, and start a heavy camera shake. + */ +void cutscene_pyramid_top_explode_warp_back(struct Camera *c) { + vec3f_copy(c->pos, sCutsceneVars[4].point); + vec3f_copy(c->focus, sCutsceneVars[5].point); + set_environmental_camera_shake(SHAKE_ENV_BOWSER_JUMP); +} + +/** + * An unused cutscene for when the pyramid explodes. + */ +void cutscene_pyramid_top_explode(struct Camera *c) { + cutscene_event(cutscene_pyramid_top_explode_start, c, 0, 0); + cutscene_event(cutscene_pyramid_top_explode_focus, c, 0, 30); + cutscene_event(cutscene_pyramid_top_explode_warp, c, 31, 31); + cutscene_event(cutscene_pyramid_top_explode_closeup, c, 31, 139); + cutscene_event(cutscene_pyramid_top_explode_zoom_in, c, 23, 23); + cutscene_event(cutscene_pyramid_top_explode_warp_back, c, 140, 140); + cutscene_event(cutscene_pyramid_top_explode_cam_shake, c, 31, 139); +} + +/** + * End the pyramid top explosion cutscene. + */ +void cutscene_pyramid_top_explode_end(struct Camera *c) { + cutscene_stop_dialog(c); + stop_cutscene_and_retrieve_stored_info(c); + // Move the camera back to Mario + transition_next_state(c, 30); +} + +struct Cutscene sCutscenePyramidTopExplode[] = { + { cutscene_mario_dialog_look_front, CUTSCENE_LOOP }, + { cutscene_pyramid_top_explode, 150 }, + { cutscene_pyramid_top_explode_end, 0 } +}; diff --git a/src/camera/cutscenes/quicksand_death.c b/src/camera/cutscenes/quicksand_death.c new file mode 100644 index 0000000000..12a51c0926 --- /dev/null +++ b/src/camera/cutscenes/quicksand_death.c @@ -0,0 +1,44 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario dies in quicksand. + */ + +/** + * Copy the camera's focus to cvar0 + */ +void cutscene_quicksand_death_start(struct Camera *c) { + vec3f_copy(sCutsceneVars[0].point, c->focus); +} + +/** + * Fly closer to Mario. In WATER_DEATH, move to Mario's side. + */ +void cutscene_quicksand_death_goto_mario(struct Camera *c) { + cutscene_goto_cvar_pos(c, 400.f, 0x2800, 0x200, 0); + + if (c->cutscene == CUTSCENE_WATER_DEATH) { + water_death_move_to_mario_side(c); + } +} + +/** + * Cutscene that plays when Mario dies in quicksand. + */ +void cutscene_quicksand_death(struct Camera *c) { + sCutsceneVars[3].point[0] = sMarioCamState->pos[0]; + sCutsceneVars[3].point[1] = sMarioCamState->pos[1] + 20.f; + sCutsceneVars[3].point[2] = sMarioCamState->pos[2]; + + cutscene_event(cutscene_quicksand_death_start, c, 0, 0); + cutscene_event(cutscene_quicksand_death_goto_mario, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + set_handheld_shake(HAND_CAM_SHAKE_HIGH); +} + +struct Cutscene sCutsceneQuicksandDeath[] = { + { cutscene_quicksand_death, CUTSCENE_LOOP }, +}; diff --git a/src/camera/cutscenes/read_message.c b/src/camera/cutscenes/read_message.c new file mode 100644 index 0000000000..24484ee31d --- /dev/null +++ b/src/camera/cutscenes/read_message.c @@ -0,0 +1,94 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_modes.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" +#include "game/object_list_processor.h" + +/** + * Cutscene that plays when Mario reads a sign or message. + */ + +/** + * Soften the music, clear cvar0 + * + * In this cutscene, cvar0.angle[0] is used as a state variable. + */ +void cutscene_read_message_start(struct Camera *c) { + cutscene_soften_music(c); + transition_next_state(c, 30); + reset_pan_distance(c); + store_info_star(c); + + sCutsceneVars[1].angle[0] = sCUpCameraPitch; + sCutsceneVars[1].angle[1] = sModeOffsetYaw; + sCUpCameraPitch = -0x830; + sModeOffsetYaw = 0; + sCutsceneVars[0].angle[0] = 0; +} + +UNUSED static void unused_cam_to_mario(struct Camera *c) { + Vec3s dir; + + vec3s_set(dir, 0, sMarioCamState->faceAngle[1], 0); + offset_rotated_coords(c->pos, sMarioCamState->pos, dir, 0, 100.f, 190.f); + offset_rotated_coords(c->focus, sMarioCamState->pos, dir, 0, 70.f, -20.f); +} + +/** + * Cutscene that plays when Mario is reading a message (a sign or message on the wall) + */ +void cutscene_read_message(struct Camera *c) { + cutscene_event(cutscene_read_message_start, c, 0, 0); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + + switch (sCutsceneVars[0].angle[0]) { + // Do nothing until message is gone. + case 0: + if (get_dialog_id() != DIALOG_NONE) { + sCutsceneVars[0].angle[0]++; + set_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); + } + break; + // Leave the dialog. + case 1: + move_mario_head_c_up(c); + update_c_up(c, c->focus, c->pos); + + // This could cause softlocks. If a message starts one frame after another one closes, the + // cutscene will never end. + if (get_dialog_id() == DIALOG_NONE) { + gCutsceneTimer = CUTSCENE_LOOP; + retrieve_info_star(c); + transition_next_state(c, 15); + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + clear_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); + // Retrieve previous state + sCUpCameraPitch = sCutsceneVars[1].angle[0]; + sModeOffsetYaw = sCutsceneVars[1].angle[1]; + cutscene_unsoften_music(c); + } + } + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +/** + * Set CAM_FLAG_UNUSED_CUTSCENE_ACTIVE, which does nothing. + */ +void cutscene_read_message_set_flag(UNUSED struct Camera *c) { + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +/** + * End the message cutscene. + */ +void cutscene_read_message_end(struct Camera *c) { + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; + c->cutscene = 0; +} + +struct Cutscene sCutsceneReadMessage[] = { + { cutscene_read_message, CUTSCENE_LOOP }, + { cutscene_read_message_set_flag, 15 }, + { cutscene_read_message_end, 0 } +}; diff --git a/src/camera/cutscenes/red_coin_star_spawn.c b/src/camera/cutscenes/red_coin_star_spawn.c new file mode 100644 index 0000000000..eafc3b8c97 --- /dev/null +++ b/src/camera/cutscenes/red_coin_star_spawn.c @@ -0,0 +1,102 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene for the red coin star spawning. Compared to a regular star, this cutscene can warp long + * distances. + */ + +/** + * Start the red coin star spawning cutscene. + */ +void cutscene_red_coin_star_start(struct Camera *c) { + object_pos_to_vec3f(sCutsceneVars[1].point, gCutsceneFocus); + store_info_star(c); + // Store the default fov for after the cutscene + sCutsceneVars[2].point[2] = sFOVState.fov; +} + +/** + * Look towards the star's x and z position + */ +void cutscene_red_coin_star_focus_xz(struct Camera *c) { + approach_f32_asymptotic_bool(&c->focus[0], gCutsceneFocus->oPosX, 0.15f); + approach_f32_asymptotic_bool(&c->focus[2], gCutsceneFocus->oPosZ, 0.15f); +} + +/** + * Look towards the star's y position. Only active before the camera warp. + */ +void cutscene_red_coin_star_focus_y(struct Camera *c) { + approach_f32_asymptotic_bool(&c->focus[1], gCutsceneFocus->oPosY, 0.1f); +} + +/** + * Look 80% up towards the star. Only active after the camera warp. + */ +void cutscene_red_coin_star_look_up_at_star(struct Camera *c) { + c->focus[1] = sCutsceneVars[1].point[1] + (gCutsceneFocus->oPosY - sCutsceneVars[1].point[1]) * 0.8f; +} + +/** + * Warp the camera near the star's spawn point + */ +void cutscene_red_coin_star_warp(struct Camera *c) { + f32 dist; + s16 pitch, yaw, posYaw; + struct Object *obj = gCutsceneFocus; + + vec3f_set(sCutsceneVars[1].point, obj->oHomeX, obj->oHomeY, obj->oHomeZ); + vec3f_get_dist_and_angle(sCutsceneVars[1].point, c->pos, &dist, &pitch, &yaw); + posYaw = calculate_yaw(sCutsceneVars[1].point, c->pos); + yaw = calculate_yaw(sCutsceneVars[1].point, sMarioCamState->pos); + + if (ABS(yaw - posYaw + DEGREES(90)) < ABS(yaw - posYaw - DEGREES(90))) { + yaw += DEGREES(90); + } else { + yaw -= DEGREES(90); + } + + vec3f_set_dist_and_angle(sCutsceneVars[1].point, c->pos, 400.f, 0x1000, yaw); + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; +} + +/** + * Zoom out while looking at the star. + */ +void cutscene_red_coin_star_set_fov(UNUSED struct Camera *c) { + sFOVState.fov = 60.f; +} + +void cutscene_red_coin_star(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + cutscene_event(cutscene_red_coin_star_start, c, 0, 0); + cutscene_event(cutscene_red_coin_star_warp, c, 30, 30); + cutscene_event(cutscene_red_coin_star_focus_xz, c, 0, -1); + cutscene_event(cutscene_red_coin_star_focus_y, c, 0, 29); + cutscene_event(cutscene_red_coin_star_look_up_at_star, c, 30, -1); + cutscene_event(cutscene_red_coin_star_set_fov, c, 30, -1); + + if (gObjCutsceneDone) { + // Set the timer to CUTSCENE_LOOP, which start the next shot. + gCutsceneTimer = CUTSCENE_LOOP; + } +} + +/** + * End the red coin star spawning cutscene + */ +void cutscene_red_coin_star_end(struct Camera *c) { + retrieve_info_star(c); + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; + // Restore the default fov + sFOVState.fov = sCutsceneVars[2].point[2]; +} + +struct Cutscene sCutsceneRedCoinStarSpawn[] = { + { cutscene_red_coin_star, CUTSCENE_LOOP }, + { cutscene_red_coin_star_end, 0 } +}; diff --git a/src/camera/cutscenes/sliding_doors_open.c b/src/camera/cutscenes/sliding_doors_open.c new file mode 100644 index 0000000000..52461aace0 --- /dev/null +++ b/src/camera/cutscenes/sliding_doors_open.c @@ -0,0 +1,96 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario opens a sliding star door. + */ + +/** + * Set the camera pos relative to Mario. + */ +void cutscene_sliding_doors_open_start(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + + // If the camera is too close, warp it backwards set it to a better angle. + if (dist < 500.f) { + dist = 500.f; + yaw = sMarioCamState->faceAngle[1] + 0x8800; + pitch = 0x800; + } + + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * cvar1: Mario's position + * cvar0.angle: Mario's angle + * cvar0.point: offset from Mario + */ +void cutscene_sliding_doors_open_set_cvars(UNUSED struct Camera *c) { + vec3f_copy(sCutsceneVars[1].point, sMarioCamState->pos); + vec3s_copy(sCutsceneVars[0].angle, sMarioCamState->faceAngle); + vec3f_set(sCutsceneVars[0].point, 80.f, 325.f, 200.f); +} + +/** + * Decrease the cvar0 y offset to 75, which would simulate Lakitu flying under the doorway. + * However, the initial y offset is too high for Lakitu to reach 75 in time. + */ +void cutscene_sliding_doors_go_under_doorway(UNUSED struct Camera *c) { + camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[1], 75.f, 10.f); +} + +/** + * Approach a y offset of 125 again. + */ +void cutscene_sliding_doors_fly_back_up(UNUSED struct Camera *c) { + camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[1], 125.f, 10.f); +} + +/** + * Follow Mario through the door, by approaching cvar1.point. + */ +void cutscene_sliding_doors_follow_mario(struct Camera *c) { + Vec3f pos; + vec3f_copy(pos, c->pos); + + // Update cvar1 with Mario's position (the y value doesn't change) + sCutsceneVars[1].point[0] = sMarioCamState->pos[0]; + sCutsceneVars[1].point[2] = sMarioCamState->pos[2]; + + // Decrease cvar0's offsets, moving the camera behind Mario at his eye height. + approach_f32_asymptotic_bool(&sCutsceneVars[0].point[0], 0, 0.1f); + camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[2], 125.f, 50.f); + // Update cvar0's angle + approach_vec3s_asymptotic(sCutsceneVars[0].angle, sMarioCamState->faceAngle, 16, 16, 16); + + // Apply the offset to the camera's position + offset_rotated(pos, sCutsceneVars[1].point, sCutsceneVars[0].point, sCutsceneVars[0].angle); + approach_vec3f_asymptotic(c->pos, pos, 0.15f, 0.05f, 0.15f); + + // Focus on Mario's eye height + set_focus_rel_mario(c, 0, 125.f, 0, 0); +} + +/** + * Plays when Mario opens the sliding doors. + * Note: the star door unlocking event is not a cutscene, it's handled by Mario separately. + */ +void cutscene_sliding_doors_open(struct Camera *c) { + reset_pan_distance(c); + cutscene_event(cutscene_sliding_doors_open_start, c, 0, 8); + cutscene_event(cutscene_sliding_doors_open_set_cvars, c, 8, 8); + cutscene_event(cutscene_sliding_doors_go_under_doorway, c, 8, 28); + cutscene_event(cutscene_sliding_doors_fly_back_up, c, 29, -1); + cutscene_event(cutscene_sliding_doors_follow_mario, c, 8, -1); +} + +struct Cutscene sCutsceneSlidingDoorsOpen[] = { + { cutscene_sliding_doors_open, 50 }, + { cutscene_double_doors_end, 0 } +}; diff --git a/src/camera/cutscenes/star_spawn.c b/src/camera/cutscenes/star_spawn.c new file mode 100644 index 0000000000..7cdd9ccc48 --- /dev/null +++ b/src/camera/cutscenes/star_spawn.c @@ -0,0 +1,84 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when a star spawns from ie a box or after a boss fight. + */ + +void cutscene_star_spawn_store_info(struct Camera *c) { + store_info_star(c); +} + +/** + * Focus on the top of the star. + */ +void cutscene_star_spawn_focus_star(struct Camera *c) { + Vec3f starPos; + + if (gCutsceneFocus != NULL) { + object_pos_to_vec3f(starPos, gCutsceneFocus); + starPos[1] += gCutsceneFocus->hitboxHeight; + approach_vec3f_asymptotic(c->focus, starPos, 0.1f, 0.1f, 0.1f); + } +} + +/** + * Use boss fight mode's update function to move the focus back. + */ +void cutscene_star_spawn_update_boss_fight(struct Camera *c) { + Vec3f pos, focus; + + update_boss_fight_camera(c, focus, pos); + approach_vec3f_asymptotic(c->focus, focus, 0.2f, 0.2f, 0.2f); + approach_vec3f_asymptotic(c->pos, pos, 0.2f, 0.2f, 0.2f); +} + +/** + * Fly back to the camera's previous pos and focus. + */ +void cutscene_star_spawn_fly_back(struct Camera *c) { + retrieve_info_star(c); + transition_next_state(c, 15); +} + +/** + * Plays when a star spawns (ie from a box). + */ +void cutscene_star_spawn(struct Camera *c) { + cutscene_event(cutscene_star_spawn_store_info, c, 0, 0); + cutscene_event(cutscene_star_spawn_focus_star, c, 0, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + + if (gObjCutsceneDone) { + // Set the timer to CUTSCENE_LOOP, which start the next shot. + gCutsceneTimer = CUTSCENE_LOOP; + } +} + +/** + * Move the camera back to Mario. + */ +void cutscene_star_spawn_back(struct Camera *c) { + if ((c->mode == CAMERA_MODE_BOSS_FIGHT) && (set_cam_angle(0) == CAM_ANGLE_LAKITU)) { + cutscene_event(cutscene_star_spawn_update_boss_fight, c, 0, -1); + } else { + cutscene_event(cutscene_star_spawn_fly_back, c, 0, 0); + } + + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; +} + +void cutscene_star_spawn_end(struct Camera *c) { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + gCutsceneTimer = CUTSCENE_STOP; + c->cutscene = 0; +} + +struct Cutscene sCutsceneStarSpawn[] = { + { cutscene_star_spawn, CUTSCENE_LOOP }, + { cutscene_star_spawn_back, 15 }, + { cutscene_star_spawn_end, 0 } +}; diff --git a/src/camera/cutscenes/suffocation.c b/src/camera/cutscenes/suffocation.c new file mode 100644 index 0000000000..f0931018bb --- /dev/null +++ b/src/camera/cutscenes/suffocation.c @@ -0,0 +1,62 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario suffocates. + */ + +/** + * Fly away from Mario near the end of the cutscene. + */ +void cutscene_suffocation_fly_away(UNUSED struct Camera *c) { + Vec3f target; + Vec3f offset = { 0, 20.f, 120.f }; + + offset_rotated(target, sMarioCamState->pos, offset, sMarioCamState->faceAngle); + approach_vec3f_asymptotic(sCutsceneVars[3].point, target, 0.1f, 0.1f, 0.1f); +} + +/** + * Keep Lakitu above the gas level. + */ +void cutscene_suffocation_stay_above_gas(struct Camera *c) { + cutscene_goto_cvar_pos(c, 400.f, 0x2800, 0x200, 0); + f32 gasLevel = find_poison_gas_level(sMarioCamState->pos[0], sMarioCamState->pos[2]); + + if (gasLevel != FLOOR_LOWER_LIMIT) { + if ((gasLevel += 130.f) > c->pos[1]) { + c->pos[1] = gasLevel; + } + } +} + +/** + * Quickly rotate around Mario. + */ +void cutscene_suffocation_rotate(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + yaw += 0x100; + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Cutscene that plays when Mario dies from suffocation (ie due to HMC gas). + */ +void cutscene_suffocation(struct Camera *c) { + cutscene_event(cutscene_death_stomach_start, c, 0, 0); + cutscene_event(cutscene_suffocation_rotate, c, 0, -1); + cutscene_event(cutscene_suffocation_stay_above_gas, c, 0, -1); + cutscene_event(cutscene_suffocation_fly_away, c, 50, -1); + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + set_handheld_shake(HAND_CAM_SHAKE_HIGH); +} + +struct Cutscene sCutsceneSuffocation[] = { + { cutscene_suffocation, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/unlock_key_door.c b/src/camera/cutscenes/unlock_key_door.c new file mode 100644 index 0000000000..d1f14e4acc --- /dev/null +++ b/src/camera/cutscenes/unlock_key_door.c @@ -0,0 +1,77 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Cutscene that plays when Mario unlocks the basement or upstairs key door. + */ + +/** + * Sets cvars: + * cvar0 is the camera's position + * cvar1 is the camera's focus + * cvar2 is the goal position + * cvar3 is the goal focus + */ +void cutscene_unlock_key_door_start(struct Camera *c) { + Vec3f posOff, focusOff; + + vec3f_copy(sCutsceneVars[0].point, c->pos); + vec3f_copy(sCutsceneVars[1].point, c->focus); + vec3f_set(posOff, -206.f, 108.f, 234.f); + vec3f_set(focusOff, 48.f, 104.f, -193.f); + offset_rotated(sCutsceneVars[2].point, sMarioCamState->pos, posOff, sMarioCamState->faceAngle); + offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, focusOff, sMarioCamState->faceAngle); +} + +/** + * Move the camera to the cvars position and focus, closer to Mario. + * Gives a better view of the key. + */ +void cutscene_unlock_key_door_approach_mario(struct Camera *c) { + approach_vec3f_asymptotic(c->pos, sCutsceneVars[2].point, 0.1f, 0.1f, 0.1f); + approach_vec3f_asymptotic(c->focus, sCutsceneVars[3].point, 0.1f, 0.1f, 0.1f); +} + +/** + * Move the camera focus up a bit, focusing on the key in the lock. + */ +void cutscene_unlock_key_door_focus_lock(UNUSED struct Camera *c) { + approach_f32_asymptotic_bool(&sCutsceneVars[3].point[1], sMarioCamState->pos[1] + 140.f, 0.07f); +} + +void cutscene_unlock_key_door_stub(UNUSED struct Camera *c) { +} + +/** + * Move back to the previous pos and focus, stored in cvar0 and cvar1. + */ +void cutscene_unlock_key_door_fly_back(struct Camera *c) { + approach_vec3f_asymptotic(c->pos, sCutsceneVars[0].point, 0.1f, 0.1f, 0.1f); + approach_vec3f_asymptotic(c->focus, sCutsceneVars[1].point, 0.1f, 0.1f, 0.1f); +} + +/** + * Shake the camera's fov when the key is put in the lock. + */ +void cutscene_unlock_key_door_fov_shake(UNUSED struct Camera *c) { + cutscene_set_fov_shake_preset(1); +} + +/** + * Cutscene that plays when Mario unlocks a key door. + */ +void cutscene_unlock_key_door(UNUSED struct Camera *c) { + cutscene_event(cutscene_unlock_key_door_start, c, 0, 0); + cutscene_event(cutscene_unlock_key_door_approach_mario, c, 0, 123); + cutscene_event(cutscene_unlock_key_door_fly_back, c, 124, -1); + cutscene_event(cutscene_unlock_key_door_fov_shake, c, 79, 79); + cutscene_event(cutscene_unlock_key_door_focus_lock, c, 70, 110); + cutscene_event(cutscene_unlock_key_door_stub, c, 112, 112); +} + +struct Cutscene sCutsceneUnlockKeyDoor[] = { + { cutscene_unlock_key_door, 200 }, + { cutscene_double_doors_end, 0 } +}; diff --git a/src/camera/cutscenes/unused.c b/src/camera/cutscenes/unused.c new file mode 100644 index 0000000000..cc417aa845 --- /dev/null +++ b/src/camera/cutscenes/unused.c @@ -0,0 +1,16 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + + +void cutscene_unused_start(UNUSED struct Camera *c) { +} + +void cutscene_unused_loop(UNUSED struct Camera *c) { +} + +struct Cutscene sCutsceneUnused[] = { + { cutscene_unused_start, 1 }, + { cutscene_unused_loop, CUTSCENE_LOOP } +}; diff --git a/src/camera/cutscenes/water_death.c b/src/camera/cutscenes/water_death.c new file mode 100644 index 0000000000..21866f444d --- /dev/null +++ b/src/camera/cutscenes/water_death.c @@ -0,0 +1,26 @@ +#include "camera/cutscene_helpers.h" +#include "camera/camera_math.h" +#include "engine/math_util.h" +#include "game/camera.h" + + +/** + * Moves the camera to Mario's side when Mario starts ACT_WATER_DEATH + * Note that ACT_WATER_DEATH only starts when Mario gets hit by an enemy under water. It does not start + * when Mario drowns. + */ +void water_death_move_to_mario_side(struct Camera *c) { + f32 dist; + s16 pitch, yaw; + + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + approach_s16_asymptotic_bool(&yaw, (sMarioCamState->faceAngle[1] - 0x3000), 8); + vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); +} + +/** + * Unused cutscene for ACT_WATER_DEATH, which happens when Mario gets hit by an enemy under water. + */ +struct Cutscene sCutsceneWaterDeath[] = { + { cutscene_quicksand_death, CUTSCENE_LOOP } +}; diff --git a/src/camera/level_specific/README.md b/src/camera/level_specific/README.md new file mode 100644 index 0000000000..7099e8348b --- /dev/null +++ b/src/camera/level_specific/README.md @@ -0,0 +1,5 @@ +# Level-Specific Camera Processing + +Some vanilla levels in SM64 extend the camera functionality by use of volumes. When Mario enters these areas, a function is called that can change the camera mode, correct camera movement through tight spaces, etc. If you aren't making a vanilla edit, these functions at best litter your game, and at worst make camera.c unreadable. + +While it would make sense to also place camera trigger definitions here (as is done with cutscenes), this would require a change to Fast64. diff --git a/src/camera/level_specific/bbh.c b/src/camera/level_specific/bbh.c new file mode 100644 index 0000000000..7f3d167e4d --- /dev/null +++ b/src/camera/level_specific/bbh.c @@ -0,0 +1,174 @@ +#include "engine/math_util.h" +#include "camera/camera_modes.h" +#include "game/camera.h" + +void cam_bbh_room_6(struct Camera *c) { + /** + * Table that dictates camera movement in bookend room. + * Due to only the X being varied in the table, this only moves along the X axis linearly. + * Third entry is seemingly unused. + */ + static struct ParallelTrackingPoint sBBHLibraryParTrackPath[] = { + { 1, { -929.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f }, + { 0, { -2118.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f }, + { 0, { 0.0f, 0.0f, 0.0f }, 0.0f, 0.0f }, + }; + + parallel_tracking_init(c, sBBHLibraryParTrackPath); +} + +void cam_bbh_fall_off_roof(struct Camera *c) { + set_camera_mode_close_cam(&c->mode); +} + +void cam_bbh_fall_into_pool(struct Camera *c) { + Vec3f dir; + set_camera_mode_close_cam(&c->mode); + vec3f_set(dir, 0.f, 0.f, 300.f); + offset_rotated(gLakituState.goalPos, sMarioCamState->pos, dir, sMarioCamState->faceAngle); + gLakituState.goalPos[1] = -2300.f; + vec3f_copy(c->pos, gLakituState.goalPos); + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; +} + +void cam_bbh_room_1(struct Camera *c) { + set_camera_mode_fixed(c, 956, 440, 1994); +} + +void cam_bbh_leave_front_door(struct Camera *c) { + c->doorStatus = DOOR_LEAVING_SPECIAL; + cam_bbh_room_1(c); +} + +void cam_bbh_room_2_lower(struct Camera *c) { + set_camera_mode_fixed(c, 2591, 400, 1284); +} + +void cam_bbh_room_4(struct Camera *c) { + set_camera_mode_fixed(c, 3529, 340, -1384); +} + +void cam_bbh_room_8(struct Camera *c) { + set_camera_mode_fixed(c, -500, 740, -1306); +} + +/** + * In BBH's room 5's library (the first floor room with the vanish cap/boo painting) + * set the camera mode to fixed and position to (-2172, 200, 675) + */ +void cam_bbh_room_5_library(struct Camera *c) { + set_camera_mode_fixed(c, -2172, 200, 675); +} + +/** + * In BBH's room 5 (the first floor room with the vanish cap/boo painting) + * set the camera mode to to the hidden room's position + * if coming from the library. + */ +void cam_bbh_room_5_library_to_hidden_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, -2172, 200, 675) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_5_hidden_to_library_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, -1542, 320, -307) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_5_hidden(struct Camera *c) { + c->doorStatus = DOOR_LEAVING_SPECIAL; + set_camera_mode_fixed(c, -1542, 320, -307); +} + +void cam_bbh_room_3(struct Camera *c) { + set_camera_mode_fixed(c, -1893, 320, 2327); +} + +void cam_bbh_room_7_mr_i(struct Camera *c) { + set_camera_mode_fixed(c, 1371, 360, -1302); +} + +void cam_bbh_room_7_mr_i_to_coffins_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, 1371, 360, -1302) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_7_coffins_to_mr_i_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, 2115, 260, -772) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_elevator_room_lower(struct Camera *c) { + c->doorStatus = DOOR_LEAVING_SPECIAL; + set_camera_mode_close_cam(&c->mode); +} + +void cam_bbh_room_0_back_entrance(struct Camera *c) { + set_camera_mode_close_cam(&c->mode); +} + +void cam_bbh_elevator(struct Camera *c) { + if (c->mode == CAMERA_MODE_FIXED) { + set_camera_mode_close_cam(&c->mode); + c->pos[1] = -405.f; + gLakituState.goalPos[1] = -405.f; + } +} + +void cam_bbh_room_12_upper(struct Camera *c) { + c->doorStatus = DOOR_LEAVING_SPECIAL; + set_camera_mode_fixed(c, -2932, 296, 4429); +} + +void cam_bbh_enter_front_door(struct Camera *c) { + set_camera_mode_close_cam(&c->mode); +} + +void cam_bbh_room_2_library(struct Camera *c) { + set_camera_mode_fixed(c, 3493, 440, 617); +} + +void cam_bbh_room_2_library_to_trapdoor_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, 3493, 440, 617) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_2_trapdoor(struct Camera *c) { + set_camera_mode_fixed(c, 3502, 440, 1217); +} + +void cam_bbh_room_2_trapdoor_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, 3502, 440, 1217) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_9_attic(struct Camera *c) { + set_camera_mode_fixed(c, -670, 460, 372); +} + +void cam_bbh_room_9_attic_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, -670, 460, 372) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_9_mr_i_transition(struct Camera *c) { + if (set_camera_mode_fixed(c, 131, 380, -263) == 1) { + transition_next_state(c, 20); + } +} + +void cam_bbh_room_13_balcony(struct Camera *c) { + set_camera_mode_fixed(c, 210, 420, 3109); +} + +void cam_bbh_room_0(struct Camera *c) { + c->doorStatus = DOOR_LEAVING_SPECIAL; + set_camera_mode_fixed(c, -204, 807, 204); +} diff --git a/src/camera/level_specific/bob.c b/src/camera/level_specific/bob.c new file mode 100644 index 0000000000..039456f002 --- /dev/null +++ b/src/camera/level_specific/bob.c @@ -0,0 +1,24 @@ +#include "game/camera.h" + + +/** + * Unused. Changes the camera to radial mode when Mario is on the tower. + * + * @see sCamBOB for bounds. + */ +void cam_bob_tower(struct Camera *c) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; + transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 90); +} + +/** + * Unused. Changes the camera to free roam mode when Mario is not climbing the tower. + * + * This is the only CameraTrigger event that uses the area == -1 feature: + * If this was used, it would be called by default in BoB. + * + * @see sCamBOB + */ +void cam_bob_default_free_roam(struct Camera *c) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); +} diff --git a/src/camera/level_specific/castle.c b/src/camera/level_specific/castle.c new file mode 100644 index 0000000000..ae363be8c5 --- /dev/null +++ b/src/camera/level_specific/castle.c @@ -0,0 +1,106 @@ +#include "camera/cutscene_helpers.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + +/** + * Starts the pool entrance cutscene if Mario is not exiting the pool. + * Used in both the castle and HMC. + */ +void cam_castle_hmc_start_pool_cutscene(struct Camera *c) { + if ((sMarioCamState->action != ACT_SPECIAL_DEATH_EXIT) + && (sMarioCamState->action != ACT_SPECIAL_EXIT_AIRBORNE)) { + start_cutscene(c, CUTSCENE_ENTER_POOL); + } +} + +/** + * Sets the fixed mode pos offset so that the camera faces the doorway when Mario is near the entrance + * to the castle lobby + */ +void cam_castle_lobby_entrance(UNUSED struct Camera *c) { + vec3f_set(sCastleEntranceOffset, -813.f - sFixedModeBasePosition[0], + 378.f - sFixedModeBasePosition[1], 1103.f - sFixedModeBasePosition[2]); +} + +/** + * Make the camera look up the stairs from the 2nd to 3rd floor of the castle + */ +void cam_castle_look_upstairs(struct Camera *c) { + struct Surface *floor; + f32 floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); + + // If Mario is on the first few steps, fix the camera pos, making it look up + if ((sMarioGeometry.currFloorHeight > 1229.f) && (floorHeight < 1229.f) + && (sCSideButtonYaw == 0)) { + vec3f_set(c->pos, -227.f, 1425.f, 1533.f); + } +} + +/** + * Make the camera look down the stairs towards the basement star door + */ +void cam_castle_basement_look_downstairs(struct Camera *c) { + struct Surface *floor; + f32 floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); + + // Fix the camera pos, making it look downwards. Only active on the top few steps + if ((floorHeight > -110.f) && (sCSideButtonYaw == 0)) { + vec3f_set(c->pos, -980.f, 249.f, -1398.f); + } +} + +/** + * Enter the fixed-mode castle lobby. A trigger for this is placed in every entrance so that the camera + * changes to fixed mode. + */ +void cam_castle_enter_lobby(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + set_fixed_cam_axis_sa_lobby(c->mode); + c->mode = CAMERA_MODE_FIXED; + } +} + +/** + * Starts spiral stairs mode. + */ +void cam_castle_enter_spiral_stairs(struct Camera *c) { + transition_to_camera_mode(c, CAMERA_MODE_SPIRAL_STAIRS, 20); +} + +/** + * unused, starts close mode if the camera is in spiral stairs mode. + * This was replaced with cam_castle_close_mode + */ +static UNUSED void cam_castle_leave_spiral_stairs(struct Camera *c) { + if (c->mode == CAMERA_MODE_SPIRAL_STAIRS) { + transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 30); + } else { + set_camera_mode_close_cam(&c->mode); + } +} + +/** + * The default mode when outside of the lobby and spiral staircase. A trigger for this is placed at + * every door leaving the lobby and spiral staircase. + */ +void cam_castle_close_mode(struct Camera *c) { + set_camera_mode_close_cam(&c->mode); +} + +/** + * Functions the same as cam_castle_close_mode, but sets doorStatus so that the camera will enter + * fixed-mode when Mario leaves the room. + */ +void cam_castle_leave_lobby_sliding_door(struct Camera *c) { + cam_castle_close_mode(c); + c->doorStatus = DOOR_ENTER_LOBBY; +} + +/** + * Just calls cam_castle_enter_lobby + */ +void cam_castle_enter_lobby_sliding_door(struct Camera *c) { + cam_castle_enter_lobby(c); +} diff --git a/src/camera/level_specific/ccm.c b/src/camera/level_specific/ccm.c new file mode 100644 index 0000000000..de955530ef --- /dev/null +++ b/src/camera/level_specific/ccm.c @@ -0,0 +1,9 @@ +#include "game/camera.h" + +void cam_ccm_enter_slide_shortcut(UNUSED struct Camera *c) { + sStatusFlags |= CAM_FLAG_CCM_SLIDE_SHORTCUT; +} + +void cam_ccm_leave_slide_shortcut(UNUSED struct Camera *c) { + sStatusFlags &= ~CAM_FLAG_CCM_SLIDE_SHORTCUT; +} diff --git a/src/camera/level_specific/cotmc.c b/src/camera/level_specific/cotmc.c new file mode 100644 index 0000000000..0eb920364b --- /dev/null +++ b/src/camera/level_specific/cotmc.c @@ -0,0 +1,8 @@ +#include "game/camera.h" + +/** + * Fix the camera in place as Mario gets exits out the MC cave into the waterfall. + */ +void cam_cotmc_exit_waterfall(UNUSED struct Camera *c) { + gCameraMovementFlags |= CAM_MOVE_FIX_IN_PLACE; +} diff --git a/src/camera/level_specific/hmc.c b/src/camera/level_specific/hmc.c new file mode 100644 index 0000000000..74a9ac360d --- /dev/null +++ b/src/camera/level_specific/hmc.c @@ -0,0 +1,44 @@ +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Warps the camera underneath the floor, used in HMC to move under the elevator platforms + */ +void move_camera_through_floor_while_descending(struct Camera *c, f32 height) { + if ((sMarioGeometry.currFloorHeight < height - 100.f) + && (sMarioGeometry.prevFloorHeight > sMarioGeometry.currFloorHeight)) { + c->pos[1] = height - 400.f; + gLakituState.curPos[1] = height - 400.f; + gLakituState.goalPos[1] = height - 400.f; + } +} + +void cam_hmc_enter_maze(struct Camera *c) { + s16 pitch, yaw; + f32 dist; + + if (c->pos[1] > -102.f) { + vec3f_get_dist_and_angle(c->focus, gLakituState.goalPos, &dist, &pitch, &yaw); + vec3f_set_dist_and_angle(c->focus, gLakituState.goalPos, 300.f, pitch, yaw); + gLakituState.goalPos[1] = -800.f; + c->pos[1] = gLakituState.goalPos[1]; + gLakituState.curPos[1] = gLakituState.goalPos[1]; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + } +} + +void cam_hmc_elevator_black_hole(struct Camera *c) { + move_camera_through_floor_while_descending(c, 1536.f); +} + +void cam_hmc_elevator_maze_emergency_exit(struct Camera *c) { + move_camera_through_floor_while_descending(c, 2355.f); +} + +void cam_hmc_elevator_lake(struct Camera *c) { + move_camera_through_floor_while_descending(c, 1843.f); +} + +void cam_hmc_elevator_maze(struct Camera *c) { + move_camera_through_floor_while_descending(c, 1843.f); +} diff --git a/src/camera/level_specific/rr.c b/src/camera/level_specific/rr.c new file mode 100644 index 0000000000..41e63ced8c --- /dev/null +++ b/src/camera/level_specific/rr.c @@ -0,0 +1,40 @@ +#include "engine/math_util.h" +#include "game/camera.h" + +void cam_rr_exit_building_side(struct Camera *c) { + set_camera_mode_8_directions(c); + s8DirModeBaseYaw = DEGREES(90); +} + +void cam_rr_exit_building_top(struct Camera *c) { + set_camera_mode_8_directions(c); + if (c->pos[1] < 6343.f) { + c->pos[1] = 7543.f; + gLakituState.goalPos[1] = c->pos[1]; + gLakituState.curPos[1] = c->pos[1]; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + } +} + +void cam_rr_enter_building_window(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + set_camera_mode_fixed(c, -2974, 478, -3975); + } +} + +void cam_rr_enter_building(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + set_camera_mode_fixed(c, -2953, 798, -3943); + } + // Prevent the camera from being above the roof + if (c->pos[1] > 6043.f) { + c->pos[1] = 6043.f; + } +} + +void cam_rr_enter_building_side(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_FIXED; + } +} diff --git a/src/camera/level_specific/sl.c b/src/camera/level_specific/sl.c new file mode 100644 index 0000000000..3f9e1c0198 --- /dev/null +++ b/src/camera/level_specific/sl.c @@ -0,0 +1,19 @@ +#include "game/camera.h" + +/** + * Sets 8 directional mode and blocks the next trigger from processing. + * Activated when Mario is walking in front of the snowman's head. + */ +void cam_sl_snowman_head_8dir(struct Camera *c) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; + transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 60); + s8DirModeBaseYaw = 0x1D27; +} + +/** + * Sets free roam mode in SL, called by a trigger that covers a large area and surrounds the 8 direction + * trigger. + */ +void cam_sl_free_roam(struct Camera *c) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 60); +} diff --git a/src/camera/level_specific/ssl.c b/src/camera/level_specific/ssl.c new file mode 100644 index 0000000000..1e61511708 --- /dev/null +++ b/src/camera/level_specific/ssl.c @@ -0,0 +1,26 @@ +#include "game/camera.h" +#include "camera/cutscene_helpers.h" + +/** + * Starts the "Enter Pyramid Top" cutscene. + */ +void cam_ssl_enter_pyramid_top(UNUSED struct Camera *c) { + start_object_cutscene_without_focus(CUTSCENE_ENTER_PYRAMID_TOP); +} + +/** + * Change to close mode in the center of the pyramid. Outside this trigger, the default mode is outwards + * radial. + */ +void cam_ssl_pyramid_center(struct Camera *c) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; + transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); +} + +/** + * Changes the mode back to outward radial in the boss room inside the pyramid. + */ +void cam_ssl_boss_room(struct Camera *c) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; + transition_to_camera_mode(c, CAMERA_MODE_OUTWARD_RADIAL, 90); +} diff --git a/src/camera/level_specific/thi.c b/src/camera/level_specific/thi.c new file mode 100644 index 0000000000..1caa98632f --- /dev/null +++ b/src/camera/level_specific/thi.c @@ -0,0 +1,21 @@ +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Moves the camera to through the tunnel by forcing sModeOffsetYaw + */ +void cam_thi_move_cam_through_tunnel(UNUSED struct Camera *c) { + if (sModeOffsetYaw < DEGREES(60)) { + sModeOffsetYaw = DEGREES(60); + } +} + +/** + * Aligns the camera to look through the tunnel + */ +void cam_thi_look_through_tunnel(UNUSED struct Camera *c) { + // ~82.5 degrees + if (sModeOffsetYaw > 0x3AAA) { + sModeOffsetYaw = 0x3AAA; + } +} diff --git a/src/camera/modes/0F_cam.c b/src/camera/modes/0F_cam.c new file mode 100644 index 0000000000..89a9e5d5fc --- /dev/null +++ b/src/camera/modes/0F_cam.c @@ -0,0 +1,13 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/game_init.h" +#include "game/camera.h" + + +static UNUSED void unused_mode_0f_camera(struct Camera *c) { + if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { + gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; + } + c->nextYaw = update_slide_camera(c); +} diff --git a/src/camera/modes/8_directions_cam.c b/src/camera/modes/8_directions_cam.c new file mode 100644 index 0000000000..a44e4d7f60 --- /dev/null +++ b/src/camera/modes/8_directions_cam.c @@ -0,0 +1,181 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/game_init.h" +#include "game/level_update.h" +#include "game/camera.h" + +/** + * Zoom the camera out of C-Up mode, avoiding moving into a wall, if possible, by searching for an open + * direction. + */ +void exit_c_up(struct Camera *c) { + struct Surface *surface; + Vec3f checkFoc; + Vec3f curPos; + // Variables for searching for an open direction + s32 searching = FALSE; + /// The current sector of the circle that we are checking + s32 sector; + f32 ceilHeight; + f32 floorHeight; + f32 curDist; + f32 d; + s16 curPitch; + s16 curYaw; + s16 checkYaw = 0; + + if ((gCameraMovementFlags & CAM_MOVE_C_UP_MODE) && !(gCameraMovementFlags & CAM_MOVE_STARTED_EXITING_C_UP)) { + vec3f_copy(checkFoc, c->focus); + checkFoc[0] = sMarioCamState->pos[0]; + checkFoc[2] = sMarioCamState->pos[2]; + vec3f_get_dist_and_angle(checkFoc, c->pos, &curDist, &curPitch, &curYaw); + vec3f_copy(curPos, c->pos); + curDist = 80.f; + + // Search for an open direction to zoom out in, if the camera is changing to close, free roam, + // or spiral-stairs mode + if (sModeInfo.lastMode == CAMERA_MODE_SPIRAL_STAIRS || sModeInfo.lastMode == CAMERA_MODE_CLOSE + || sModeInfo.lastMode == CAMERA_MODE_FREE_ROAM) { + searching = TRUE; + // Check the whole circle around Mario for an open direction to zoom out to + for (sector = 0; sector < 16 && searching == 1; sector++) { + vec3f_set_dist_and_angle(checkFoc, curPos, curDist, 0, curYaw + checkYaw); + + // If there are no walls this way, + if (f32_find_wall_collision(&curPos[0], &curPos[1], &curPos[2], 20.f, 50.f) == 0) { + + // Start close to Mario, check for walls, floors, and ceilings all the way to the + // zoomed out distance + for (d = curDist; d < gCameraZoomDist; d += 20.f) { + vec3f_set_dist_and_angle(checkFoc, curPos, d, 0, curYaw + checkYaw); + + // Check if we're zooming out into a floor or ceiling + ceilHeight = find_ceil(curPos[0], curPos[1] - 150.f, curPos[2], &surface) + -10.f; + if (surface != NULL && ceilHeight < curPos[1]) { + break; + } + floorHeight = find_floor(curPos[0], curPos[1] + 150.f, curPos[2], &surface) + 10.f; + if (surface != NULL && floorHeight > curPos[1]) { + break; + } + + // Stop checking this direction if there is a wall blocking the way + if (f32_find_wall_collision(&curPos[0], &curPos[1], &curPos[2], 20.f, 50.f) == 1) { + break; + } + } + + // If there was no collision found all the way to the max distance, it's an opening + if (d >= gCameraZoomDist) { + searching = FALSE; + } + } + + // Alternate left and right, checking each 1/16th (22.5 degrees) of the circle + if (searching == 1) { + checkYaw = -checkYaw; + if (checkYaw < 0) { + checkYaw -= 0x1000; + } else { + checkYaw += 0x1000; + } + } + } + + // Update the stored focus and pos to the direction found in the search + if (!searching) { + vec3f_set_dist_and_angle(checkFoc, sCameraStoreCUp.pos, gCameraZoomDist, 0, curYaw + checkYaw); + vec3f_copy(sCameraStoreCUp.focus, checkFoc); + vec3f_sub(sCameraStoreCUp.pos, sMarioCamState->pos); + vec3f_sub(sCameraStoreCUp.focus, sMarioCamState->pos); + } + + gCameraMovementFlags |= CAM_MOVE_STARTED_EXITING_C_UP; + transition_next_state(c, 15); + } else { + // Let the next camera mode handle it + gCameraMovementFlags &= ~(CAM_MOVE_STARTED_EXITING_C_UP | CAM_MOVE_C_UP_MODE); + vec3f_set_dist_and_angle(checkFoc, c->pos, curDist, curPitch, curYaw + checkYaw); + } + play_sound_cbutton_down(); + } +} + +/** + * The mode used when C-Up is pressed. + */ +s32 update_c_up(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { + s16 pitch = sCUpCameraPitch; + s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); + + focus_on_mario(focus, pos, 125.f, 125.f, 250.f, pitch, yaw); + return sMarioCamState->faceAngle[1]; +} + +/** + * Update the camera during 8 directional mode + */ +s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + s16 camYaw = s8DirModeBaseYaw + s8DirModeYawOffset; + s16 pitch = look_down_slopes(camYaw); + f32 posY; + f32 focusY; + f32 yOff = 125.f; + f32 baseDist = 1000.f; + + sAreaYaw = camYaw; + calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); + focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); + pan_ahead_of_player(c); +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelArea == AREA_DDD_SUB) { + camYaw = clamp_positions_and_find_yaw(pos, focus, 6839.f, 995.f, 5994.f, -3945.f); + } +#endif + return camYaw; +} + + +/** + * A mode that only has 8 camera angles, 45 degrees apart + */ +void mode_8_directions_camera(struct Camera *c) { + Vec3f pos; + s16 oldAreaYaw = sAreaYaw; + + radial_camera_input(c); + + if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { + s8DirModeYawOffset += DEGREES(45); + play_sound_cbutton_side(); + } + if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { + s8DirModeYawOffset -= DEGREES(45); + play_sound_cbutton_side(); + } +#ifdef PARALLEL_LAKITU_CAM + // extra functionality + else if (gPlayer1Controller->buttonPressed & U_JPAD) { + s8DirModeYawOffset = 0; + s8DirModeYawOffset = gMarioState->faceAngle[1] - 0x8000; + } + else if (gPlayer1Controller->buttonDown & L_JPAD) { + s8DirModeYawOffset -= DEGREES(2); + } + else if (gPlayer1Controller->buttonDown & R_JPAD) { + s8DirModeYawOffset += DEGREES(2); + } + else if (gPlayer1Controller->buttonPressed & D_JPAD) { + s8DirModeYawOffset = snap_to_45_degrees(s8DirModeYawOffset); + } +#endif + + lakitu_zoom(400.f, 0x900); + c->nextYaw = update_8_directions_camera(c, c->focus, pos); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; + sAreaYawChange = sAreaYaw - oldAreaYaw; + set_camera_height(c, pos[1]); +} diff --git a/src/camera/modes/behind_mario_cam.c b/src/camera/modes/behind_mario_cam.c new file mode 100644 index 0000000000..2d0d041251 --- /dev/null +++ b/src/camera/modes/behind_mario_cam.c @@ -0,0 +1,242 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/game_init.h" +#include "game/level_update.h" +#include "game/camera.h" + +/** + * Move `pos` between the nearest floor and ceiling + * REVIEWERS: is this a better fit for camera_math? it's only used here + */ +static void set_floor_ceiling_midpoint(Vec3f pos) { + struct Surface *surf; + + f32_find_wall_collision(&pos[0], &pos[1], &pos[2], 0.f, 100.f); + f32 floorY = find_floor(pos[0], pos[1] + 50.f, pos[2], &surf); + f32 ceilY = find_ceil(pos[0], pos[1] - 50.f, pos[2], &surf); + + if ((FLOOR_LOWER_LIMIT != floorY) && (CELL_HEIGHT_LIMIT == ceilY)) { + if (pos[1] < (floorY += 125.f)) { + pos[1] = floorY; + } + } + + if ((FLOOR_LOWER_LIMIT == floorY) && (CELL_HEIGHT_LIMIT != ceilY)) { + if (pos[1] > (ceilY -= 125.f)) { + pos[1] = ceilY; + } + } + + if ((FLOOR_LOWER_LIMIT != floorY) && (CELL_HEIGHT_LIMIT != ceilY)) { + floorY += 125.f; + ceilY -= 125.f; + + if ((pos[1] <= floorY) && (pos[1] < ceilY)) { + pos[1] = floorY; + } + if ((pos[1] > floorY) && (pos[1] >= ceilY)) { + pos[1] = ceilY; + } + if ((pos[1] <= floorY) && (pos[1] >= ceilY)) { + pos[1] = (floorY + ceilY) * 0.5f; + } + } +} + +/** + * Updates the camera in BEHIND_MARIO mode. + * + * The C-Buttons rotate the camera 90 degrees left/right and 67.5 degrees up/down. + */ +s32 update_behind_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + f32 dist; + s16 absPitch; + s16 pitch; + s16 yaw; + s16 goalPitch = -sMarioCamState->faceAngle[0]; + s16 marioYaw = sMarioCamState->faceAngle[1] + DEGREES(180); + s16 goalYawOff = 0; + s16 yawSpeed; + s16 pitchInc = 32; + f32 maxDist = 800.f; + f32 focYOff = 125.f; + + // Zoom in when Mario R_TRIG mode is active + if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { + maxDist = 350.f; + focYOff = 120.f; + } + if (!(sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))) { + pitchInc = 128; + } + + // Focus on Mario + vec3f_copy(focus, sMarioCamState->pos); + c->focus[1] += focYOff; + vec3f_get_dist_and_angle(focus, pos, &dist, &pitch, &yaw); + if (dist > maxDist) { + dist = maxDist; + } + if ((absPitch = pitch) < 0) { + absPitch = -absPitch; + } + + // Determine the yaw speed based on absPitch. A higher absPitch (further away from looking straight) + // translates to a slower speed + // Note: Pitch is always within +- 90 degrees or +-0x4000, and 0x4000 / 0x200 = 32 + yawSpeed = 32 - absPitch / 0x200; + if (yawSpeed < 1) { + yawSpeed = 1; + } + if (yawSpeed > 32) { + yawSpeed = 32; + } + + if (sCSideButtonYaw != 0) { + camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 1); + yawSpeed = 8; + } + if (sBehindMarioSoundTimer != 0) { + goalPitch = 0; + camera_approach_s16_symmetric_bool(&sBehindMarioSoundTimer, 0, 1); + pitchInc = 0x800; + } + + if (sBehindMarioSoundTimer == 28) { + if (sCSideButtonYaw < 5 || sCSideButtonYaw > 28) { + play_sound_cbutton_up(); + } + } + if (sCSideButtonYaw == 28) { + if (sBehindMarioSoundTimer < 5 || sBehindMarioSoundTimer > 28) { + play_sound_cbutton_up(); + } + } + + // C-Button input. Note: Camera rotates in the opposite direction of the button (airplane controls) + //! @bug C-Right and C-Up take precedence due to the way input is handled here + + // Rotate right + if (sCButtonsPressed & L_CBUTTONS) { + if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { + play_sound_cbutton_side(); + } + if (dist < maxDist) { + camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); + } + goalYawOff = -0x3FF8; + sCSideButtonYaw = 30; + yawSpeed = 2; + } + // Rotate left + if (sCButtonsPressed & R_CBUTTONS) { + if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { + play_sound_cbutton_side(); + } + if (dist < maxDist) { + camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); + } + goalYawOff = 0x3FF8; + sCSideButtonYaw = 30; + yawSpeed = 2; + } + // Rotate up + if (sCButtonsPressed & D_CBUTTONS) { + if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) { + play_sound_cbutton_side(); + } + if (dist < maxDist) { + camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); + } + goalPitch = -0x3000; + sBehindMarioSoundTimer = 30; + pitchInc = 0x800; + } + // Rotate down + if (sCButtonsPressed & U_CBUTTONS) { + if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) { + play_sound_cbutton_side(); + } + if (dist < maxDist) { + camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); + } + goalPitch = 0x3000; + sBehindMarioSoundTimer = 30; + pitchInc = 0x800; + } + + approach_s16_asymptotic_bool(&yaw, marioYaw + goalYawOff, yawSpeed); + camera_approach_s16_symmetric_bool(&pitch, goalPitch, pitchInc); + if (dist < 300.f) { + dist = 300.f; + } + vec3f_set_dist_and_angle(focus, pos, dist, pitch, yaw); +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelArea == AREA_WDW_MAIN) { + yaw = clamp_positions_and_find_yaw(pos, focus, 4508.f, -3739.f, 4508.f, -3739.f); + } + if (gCurrLevelArea == AREA_THI_HUGE) { + yaw = clamp_positions_and_find_yaw(pos, focus, 8192.f, -8192.f, 8192.f, -8192.f); + } + if (gCurrLevelArea == AREA_THI_TINY) { + yaw = clamp_positions_and_find_yaw(pos, focus, 2458.f, -2458.f, 2458.f, -2458.f); + } +#endif + + return yaw; +} + +/** + * "Behind Mario" mode: used when Mario is flying, on the water's surface, or shot from a cannon + */ +s32 mode_behind_mario(struct Camera *c) { + struct MarioState *marioState = &gMarioStates[0]; + struct Surface *floor; + Vec3f newPos; + f32 waterHeight; + f32 floorHeight; + f32 distCamToFocus; + s16 camPitch; + s16 camYaw; + s16 yaw; + + gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; + vec3f_copy(newPos, c->pos); + yaw = update_behind_mario_camera(c, c->focus, newPos); + c->pos[0] = newPos[0]; + c->pos[2] = newPos[2]; + + // Keep the camera above the water surface if swimming + if (c->mode == WATER_SURFACE_CAMERA_MODE) { + floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); + newPos[1] = marioState->waterLevel + 120; + if (newPos[1] < (floorHeight += 120.f)) { + newPos[1] = floorHeight; + } + } + approach_camera_height(c, newPos[1], 50.f); + waterHeight = find_water_level(c->pos[0], c->pos[2]) + 100.f; + if (c->pos[1] <= waterHeight) { + gCameraMovementFlags |= CAM_MOVE_SUBMERGED; + } else { + gCameraMovementFlags &= ~CAM_MOVE_SUBMERGED; + } + + set_floor_ceiling_midpoint(c->pos); + // Prevent camera getting too far away + vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); + if (distCamToFocus > 800.f) { + distCamToFocus = 800.f; + vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw); + } + pan_ahead_of_player(c); + + return yaw; +} + + +void mode_behind_mario_camera(struct Camera *c) { + c->nextYaw = mode_behind_mario(c); +} diff --git a/src/camera/modes/boss_fight_cam.c b/src/camera/modes/boss_fight_cam.c new file mode 100644 index 0000000000..0296ebb5d1 --- /dev/null +++ b/src/camera/modes/boss_fight_cam.c @@ -0,0 +1,139 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/level_update.h" +#include "game/camera.h" + + +/** + * Updates the camera during a boss fight + */ +s32 update_boss_fight_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + struct Object *obj; + f32 focusDistance; + // Floor normal values + f32 nx; + f32 ny; + f32 nz; + /// Floor originOffset + f32 oo; + s16 yaw; + s16 heldState; + struct Surface *floor; + Vec3f secondFocus; + Vec3f holdFocOffset = { 0.f, -150.f, -125.f }; + + handle_c_button_movement(c); + + // Start camera shakes if bowser jumps or gets thrown. + if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_JUMP) { + set_environmental_camera_shake(SHAKE_ENV_BOWSER_JUMP); + sMarioCamState->cameraEvent = CAM_EVENT_NONE; + } + if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_THROW_BOUNCE) { + set_environmental_camera_shake(SHAKE_ENV_BOWSER_THROW_BOUNCE); + sMarioCamState->cameraEvent = CAM_EVENT_NONE; + } + + yaw = sModeOffsetYaw + DEGREES(45); + // Get boss's position and whether Mario is holding it. + if ((obj = gSecondCameraFocus) != NULL) { + vec3f_copy(secondFocus, &obj->oPosVec); + heldState = obj->oHeldState; + } else { + // If no boss is there, just rotate around the area's center point. + secondFocus[0] = c->areaCenX; + secondFocus[1] = sMarioCamState->pos[1]; + secondFocus[2] = c->areaCenZ; + heldState = 0; + } + + focusDistance = calc_abs_dist(sMarioCamState->pos, secondFocus) * 1.6f; + if (focusDistance < 800.f) { + focusDistance = 800.f; + } + if (focusDistance > 5000.f) { + focusDistance = 5000.f; + } + + // If holding the boss, add a slight offset to secondFocus so that the spinning is more pronounced. + if (heldState == HELD_HELD) { + offset_rotated(secondFocus, sMarioCamState->pos, holdFocOffset, sMarioCamState->faceAngle); + } + + // Set the camera focus to the average of Mario and secondFocus + focus[0] = (sMarioCamState->pos[0] + secondFocus[0]) / 2.f; + focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 125.f; + focus[2] = (sMarioCamState->pos[2] + secondFocus[2]) / 2.f; + + // Calculate the camera's position as an offset from the focus + // When C-Down is not active, this + vec3f_set_dist_and_angle(focus, pos, focusDistance, 0x1000, yaw); + // Find the floor of the arena + pos[1] = find_floor(c->areaCenX, CELL_HEIGHT_LIMIT, c->areaCenZ, &floor); + if (floor != NULL) { + nx = floor->normal.x; + ny = floor->normal.y; + nz = floor->normal.z; + oo = floor->originOffset; + pos[1] = 300.f - (nx * pos[0] + nz * pos[2] + oo) / ny; +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + switch (gCurrLevelArea) { + case AREA_BOB: + pos[1] += 125.f; + //! fall through, makes the BoB boss fight camera move up twice as high as it should + FALL_THROUGH; + case AREA_WF: + pos[1] += 125.f; + } +#endif + } +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + // Prevent the camera from going to the ground in the outside boss fight + if (gCurrLevelNum == LEVEL_BBH) { + pos[1] = 2047.f; + } +#endif + // Rotate from C-Button input + if (sCSideButtonYaw < 0) { + sModeOffsetYaw += 0x200; + if ((sCSideButtonYaw += 0x100) > 0) { + sCSideButtonYaw = 0; + } + } + if (sCSideButtonYaw > 0) { + sModeOffsetYaw -= 0x200; + if ((sCSideButtonYaw -= 0x100) < 0) { + sCSideButtonYaw = 0; + } + } + + focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 100.f; + if (heldState == 1) { + focus[1] += 300.f * sins((gMarioStates[0].angleVel[1] > 0.f) ? gMarioStates[0].angleVel[1] + : -gMarioStates[0].angleVel[1]); + } + + //! Unnecessary conditional, focusDistance is already bounded to 800 + // if (focusDistance < 400.f) { + // focusDistance = 400.f; + // } + + // Set C-Down distance and pitch. + // C-Down will essentially double the distance from the center. + // sLakituPitch approaches 33.75 degrees. + lakitu_zoom(focusDistance, 0x1800); + + // Move the camera position back as sLakituDist and sLakituPitch increase. + // This doesn't zoom out of bounds because pos is set above each frame. + // The constant 0x1000 doubles the pitch from the center when sLakituPitch is 0 + // When Lakitu is fully zoomed out, the pitch comes to 0x3800, or 78.75 degrees, up from the focus. + vec3f_set_dist_and_angle(pos, pos, sLakituDist, sLakituPitch + 0x1000, yaw); + + return yaw; +} + +void mode_boss_fight_camera(struct Camera *c) { + c->nextYaw = update_boss_fight_camera(c, c->focus, c->pos); +} \ No newline at end of file diff --git a/src/camera/modes/c_up_cam.c b/src/camera/modes/c_up_cam.c new file mode 100644 index 0000000000..5fb2ce85d0 --- /dev/null +++ b/src/camera/modes/c_up_cam.c @@ -0,0 +1,112 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/game_init.h" +#include "game/camera.h" + +/** + * Make Mario's head move in C-Up mode. + */ +void move_mario_head_c_up(UNUSED struct Camera *c) { + sCUpCameraPitch += (s16)(gPlayer1Controller->stickY * 10.f); + sModeOffsetYaw -= (s16)(gPlayer1Controller->stickX * 10.f); + + // Bound looking up to nearly 80 degrees. + if (sCUpCameraPitch > 0x38E3) { + sCUpCameraPitch = 0x38E3; + } + // Bound looking down to -45 degrees + if (sCUpCameraPitch < -0x2000) { + sCUpCameraPitch = -0x2000; + } + + // Bound the camera yaw to +-120 degrees + if (sModeOffsetYaw > 0x5555) { + sModeOffsetYaw = 0x5555; + } + if (sModeOffsetYaw < -0x5555) { + sModeOffsetYaw = -0x5555; + } + + // Give Mario's neck natural-looking constraints + sMarioCamState->headRotation[0] = sCUpCameraPitch * 3 / 4; + sMarioCamState->headRotation[1] = sModeOffsetYaw * 3 / 4; +} + +/** + * Zooms the camera in for C-Up mode + */ +void move_into_c_up(struct Camera *c) { + struct LinearTransitionPoint *start = &sModeInfo.transitionStart; + struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; + + f32 dist = end->dist - start->dist; + s16 pitch = end->pitch - start->pitch; + s16 yaw = end->yaw - start->yaw; + + // Linearly interpolate from start to end position's polar coordinates + dist = start->dist + dist * sModeInfo.frame / sModeInfo.max; + pitch = start->pitch + pitch * sModeInfo.frame / sModeInfo.max; + yaw = start->yaw + yaw * sModeInfo.frame / sModeInfo.max; + + // Linearly interpolate the focus from start to end + c->focus[0] = start->focus[0] + (end->focus[0] - start->focus[0]) * sModeInfo.frame / sModeInfo.max; + c->focus[1] = start->focus[1] + (end->focus[1] - start->focus[1]) * sModeInfo.frame / sModeInfo.max; + c->focus[2] = start->focus[2] + (end->focus[2] - start->focus[2]) * sModeInfo.frame / sModeInfo.max; + + vec3f_add(c->focus, sMarioCamState->pos); + vec3f_set_dist_and_angle(c->focus, c->pos, dist, pitch, yaw); + + sMarioCamState->headRotation[0] = 0; + sMarioCamState->headRotation[1] = 0; + + // Finished zooming in + if (++sModeInfo.frame == sModeInfo.max) { + gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; + } +} + +/** + * The main update function for C-Up mode + */ +void mode_c_up_camera(struct Camera *c) { + // Play a sound when entering C-Up mode + if (!(sCameraSoundFlags & CAM_SOUND_C_UP_PLAYED)) { + play_sound_cbutton_up(); + sCameraSoundFlags |= CAM_SOUND_C_UP_PLAYED; + } + + // Zoom in first + if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { + gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; + move_into_c_up(c); + return; + } + + if (!(gCameraMovementFlags & CAM_MOVE_STARTED_EXITING_C_UP)) { + // Normal update + move_mario_head_c_up(c); + update_c_up(c, c->focus, c->pos); + } else { + // Exiting C-Up + if (sStatusFlags & CAM_FLAG_TRANSITION_OUT_OF_C_UP) { + // Retrieve the previous position and focus + vec3f_copy(c->pos, sCameraStoreCUp.pos); + vec3f_add(c->pos, sMarioCamState->pos); + vec3f_copy(c->focus, sCameraStoreCUp.focus); + vec3f_add(c->focus, sMarioCamState->pos); + // Make Mario look forward + camera_approach_s16_symmetric_bool(&sMarioCamState->headRotation[0], 0, 1024); + camera_approach_s16_symmetric_bool(&sMarioCamState->headRotation[1], 0, 1024); + } else { + // Finished exiting C-Up + gCameraMovementFlags &= ~(CAM_MOVE_STARTED_EXITING_C_UP | CAM_MOVE_C_UP_MODE); + } + } + sPanDistance = 0.f; + + // Exit C-Up mode + if (gPlayer1Controller->buttonPressed & (A_BUTTON | B_BUTTON | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS)) { + exit_c_up(c); + } +} diff --git a/src/camera/modes/cannon_cam.c b/src/camera/modes/cannon_cam.c new file mode 100644 index 0000000000..3556facdaf --- /dev/null +++ b/src/camera/modes/cannon_cam.c @@ -0,0 +1,33 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/game_init.h" +#include "game/camera.h" + + +/** + * Used when Mario is in a cannon. + */ +s32 update_in_cannon(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { + focus_on_mario(pos, focus, 125.f + sCannonYOffset, 125.f, 800.f, + sMarioCamState->faceAngle[0], sMarioCamState->faceAngle[1]); + return sMarioCamState->faceAngle[1]; +} + +/** + * Updates the camera when Mario is in a cannon. + * sCannonYOffset is used to make the camera rotate down when Mario has just entered the cannon + */ +void mode_cannon_camera(struct Camera *c) { + sLakituPitch = 0; + gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; + c->nextYaw = update_in_cannon(c, c->focus, c->pos); + if (gPlayer1Controller->buttonPressed & A_BUTTON) { + set_camera_mode(c, CAMERA_MODE_BEHIND_MARIO, 1); + sPanDistance = 0.f; + sCannonYOffset = 0.f; + sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + } else { + sCannonYOffset = approach_f32(sCannonYOffset, 0.f, 100.f, 100.f); + } +} diff --git a/src/camera/modes/default_cam.c b/src/camera/modes/default_cam.c new file mode 100644 index 0000000000..7882b55c0d --- /dev/null +++ b/src/camera/modes/default_cam.c @@ -0,0 +1,383 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/game_init.h" +#include "game/level_update.h" +#include "game/camera.h" + +/** + * Check `pos` for collisions within `radius`, and update `pos` + * + * @return the number of collisions found + */ +s32 collide_with_walls(Vec3f pos, f32 offsetY, f32 radius) { + struct WallCollisionData collisionData; + struct Surface *wall = NULL; + f32 normX, normY, normZ; + f32 originOffset; + f32 offset; + f32 offsetAbsolute; + Vec3f newPos[MAX_REFERENCED_WALLS]; + s32 i; + s32 numCollisions = 0; + + collisionData.x = pos[0]; + collisionData.y = pos[1]; + collisionData.z = pos[2]; + collisionData.radius = radius; + collisionData.offsetY = offsetY; + numCollisions = find_wall_collisions(&collisionData); + if (numCollisions != 0) { + for (i = 0; i < collisionData.numWalls; i++) { + wall = collisionData.walls[collisionData.numWalls - 1]; + vec3f_copy(newPos[i], pos); + normX = wall->normal.x; + normY = wall->normal.y; + normZ = wall->normal.z; + originOffset = wall->originOffset; + offset = normX * newPos[i][0] + normY * newPos[i][1] + normZ * newPos[i][2] + originOffset; + offsetAbsolute = absf(offset); + if (offsetAbsolute < radius) { + newPos[i][0] += normX * (radius - offset); + newPos[i][2] += normZ * (radius - offset); + vec3f_copy(pos, newPos[i]); + } + } + } + return numCollisions; +} + +/** + * Update the camera in default, close, and free roam mode + * + * The camera moves behind Mario, and can rotate all the way around + */ +s16 update_default_camera(struct Camera *c) { + Vec3f tempPos; + Vec3f cPos; + struct Surface *marioFloor; + struct Surface *cFloor; + struct Surface *tempFloor; + struct Surface *ceil; + f32 camFloorHeight; + f32 tempFloorHeight; + f32 marioFloorHeight; + f32 dist; + f32 zoomDist; + f32 waterHeight; + f32 gasHeight; + s16 avoidYaw; + s16 pitch; + s16 yaw; + s16 yawGoal = sMarioCamState->faceAngle[1] + DEGREES(180); + f32 posHeight; + f32 focHeight; + f32 distFromWater; + s16 tempPitch; + s16 tempYaw; + f32 xzDist; + s16 nextYawVel; + s16 yawVel = 0; + f32 scale; + s32 avoidStatus = 0; + s32 closeToMario = FALSE; + f32 ceilHeight = find_ceil(gLakituState.goalPos[0], + gLakituState.goalPos[1], + gLakituState.goalPos[2], &ceil); + s16 yawDir; + + handle_c_button_movement(c); + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + + // If C-Down is active, determine what distance the camera should be from Mario + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + //! In Mario mode, the camera is zoomed out further than in Lakitu mode (1400 vs 1200) + if (set_cam_angle(0) == CAM_ANGLE_MARIO) { + zoomDist = gCameraZoomDist + 1050; + } else { + zoomDist = gCameraZoomDist + 400; + } + } else { + zoomDist = gCameraZoomDist; + } + + if (sMarioCamState->action & ACT_FLAG_HANGING || + sMarioCamState->action == ACT_RIDING_HOOT) { + zoomDist *= 0.8f; + set_handheld_shake(HAND_CAM_SHAKE_HANG_OWL); + } + + // If not zooming out, only allow dist to decrease + if (sZoomAmount == 0.f) { + if (dist > zoomDist) { + if ((dist -= 50.f) < zoomDist) { + dist = zoomDist; + } + } + } else { + if ((sZoomAmount -= 30.f) < 0.f) { + sZoomAmount = 0.f; + } + if (dist > zoomDist) { + if ((dist -= 30.f) < zoomDist) { + dist = zoomDist; + } + } + if (dist < zoomDist) { + if ((dist += 30.f) > zoomDist) { + dist = zoomDist; + } + } + } + + // Determine how fast to rotate the camera + if (sCSideButtonYaw == 0) { + if (c->mode == CAMERA_MODE_FREE_ROAM) { + nextYawVel = 0xC0; + } else { + nextYawVel = 0x100; + } + if ((gPlayer1Controller->stickX != 0.f || gPlayer1Controller->stickY != 0.f) != 0) { + nextYawVel = 0x20; + } + } else { + if (sCSideButtonYaw < 0) { + yaw += 0x200; + } + if (sCSideButtonYaw > 0) { + yaw -= 0x200; + } + camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 0x100); + nextYawVel = 0; + } + sYawSpeed = 0x400; + xzDist = calc_hor_dist(sMarioCamState->pos, c->pos); + + if (sStatusFlags & CAM_FLAG_BEHIND_MARIO_POST_DOOR) { + if (xzDist >= 250) { + sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR; + } + if (abss((sMarioCamState->faceAngle[1] - yaw) / 2) < 0x1800) { + sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR; + yaw = sCameraYawAfterDoorCutscene + DEGREES(180); + dist = 800.f; + sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + } + } else if (xzDist < 250) { + // Turn rapidly if very close to Mario + c->pos[0] += (250 - xzDist) * sins(yaw); + c->pos[2] += (250 - xzDist) * coss(yaw); + if (sCSideButtonYaw == 0) { + nextYawVel = 0x1000; + sYawSpeed = 0; + vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); + } + closeToMario |= 1; + } + + if (-16 < gPlayer1Controller->stickY) { + c->yaw = yaw; + } + + calc_y_to_curr_floor(&posHeight, 1, 200, &focHeight, 0.9f, 200); + vec3f_copy(cPos, c->pos); + avoidStatus = rotate_camera_around_walls(c, cPos, &avoidYaw, 0x600); + // If a wall is blocking the view of Mario, then rotate in the calculated direction + if (avoidStatus == AVOID_STATUS_WALL_COVERING_MARIO) { + sAvoidYawVel = yaw; + sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL; + // Rotate to avoid the wall + approach_s16_asymptotic_bool(&yaw, avoidYaw, 10); + sAvoidYawVel = (sAvoidYawVel - yaw) / 0x100; + } else { + if (gMarioStates[0].forwardVel == 0.f) { + if (sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL) { + if ((yawGoal - yaw) / 0x100 >= 0) { + yawDir = -1; + } else { + yawDir = 1; + } + if ((sAvoidYawVel > 0 && yawDir > 0) || (sAvoidYawVel < 0 && yawDir < 0)) { + yawVel = nextYawVel; + } + } else { + yawVel = nextYawVel; + } + } else { + if (nextYawVel == 0x1000) { + yawVel = nextYawVel; + } + sStatusFlags &= ~CAM_FLAG_COLLIDED_WITH_WALL; + } + + // If a wall is near the camera, turn twice as fast + if (avoidStatus != AVOID_STATUS_NONE) { + yawVel += yawVel; + } + // ...Unless the camera already rotated from being close to Mario + if ((closeToMario & 1) && avoidStatus != AVOID_STATUS_NONE) { + yawVel = 0; + } + if (yawVel != 0 && get_dialog_id() == DIALOG_NONE) { + camera_approach_s16_symmetric_bool(&yaw, yawGoal, yawVel); + } + } + + // Only zoom out if not obstructed by walls and Lakitu hasn't collided with any + if (avoidStatus == AVOID_STATUS_NONE && !(sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL)) { + approach_f32_asymptotic_bool(&dist, zoomDist - 100.f, 0.05f); + } + vec3f_set_dist_and_angle(sMarioCamState->pos, cPos, dist, pitch, yaw); + cPos[1] += posHeight + 125.f; + + // Move the camera away from walls and set the collision flag + if (collide_with_walls(cPos, 10.f, 80.f) != 0) { + sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL; + } + + c->focus[0] = sMarioCamState->pos[0]; + c->focus[1] = sMarioCamState->pos[1] + 125.f + focHeight; + c->focus[2] = sMarioCamState->pos[2]; + + marioFloorHeight = 125.f + sMarioGeometry.currFloorHeight; + marioFloor = sMarioGeometry.currFloor; + camFloorHeight = find_floor(cPos[0], cPos[1] + 50.f, cPos[2], &cFloor) + 125.f; + for (scale = 0.1f; scale < 1.f; scale += 0.2f) { + scale_along_line(tempPos, cPos, sMarioCamState->pos, scale); + tempFloorHeight = find_floor(tempPos[0], tempPos[1], tempPos[2], &tempFloor) + 125.f; + if (tempFloor != NULL && tempFloorHeight > marioFloorHeight) { + marioFloorHeight = tempFloorHeight; + marioFloor = tempFloor; + } + } + + // Lower the camera in Mario mode + if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { + marioFloorHeight -= 35.f; + camFloorHeight -= 35.f; + c->focus[1] -= 25.f; + } + + // If there's water below the camera, decide whether to keep the camera above the water surface + waterHeight = find_water_level(cPos[0], cPos[2]); + if (waterHeight != FLOOR_LOWER_LIMIT) { + waterHeight += 125.f; + distFromWater = waterHeight - marioFloorHeight; + if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER)) { + if (distFromWater > 800.f && (sMarioCamState->action & ACT_FLAG_METAL_WATER)) { + gCameraMovementFlags |= CAM_MOVE_METAL_BELOW_WATER; + } + } else { + if (distFromWater < 400.f || !(sMarioCamState->action & ACT_FLAG_METAL_WATER)) { + gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER; + } + } + // If not wearing the metal cap, always stay above + if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER) && camFloorHeight < waterHeight) { + camFloorHeight = waterHeight; + } + } else { + gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER; + } + + cPos[1] = camFloorHeight; + vec3f_copy(tempPos, cPos); + tempPos[1] -= 125.f; + if (marioFloor != NULL && camFloorHeight <= marioFloorHeight) { + avoidStatus = is_range_behind_surface(c->focus, tempPos, marioFloor, 0, SURFACE_NULL); + if (avoidStatus != AVOID_STATUS_WALL_NEAR_CAMERA && ceilHeight > marioFloorHeight) { + camFloorHeight = marioFloorHeight; + } + } + + posHeight = 0.f; + if (c->mode == CAMERA_MODE_FREE_ROAM) { + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + posHeight = 375.f; + if (gCurrLevelArea == AREA_SSL_PYRAMID) { + posHeight /= 2; + } + } else { + posHeight = 100.f; + } + } + if ((gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) && (sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { + posHeight = 610.f; +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelArea == AREA_SSL_PYRAMID || gCurrLevelNum == LEVEL_CASTLE) { + posHeight /= 2; + } +#endif + } + + // Make Lakitu fly above the gas + gasHeight = find_poison_gas_level(cPos[0], cPos[2]); + if (gasHeight != FLOOR_LOWER_LIMIT) { + if ((gasHeight += 130.f) > c->pos[1]) { + c->pos[1] = gasHeight; + } + } + + if (sMarioCamState->action & ACT_FLAG_HANGING || sMarioCamState->action == ACT_RIDING_HOOT) { + camFloorHeight = sMarioCamState->pos[1] + 400.f; + if (c->mode == CAMERA_MODE_FREE_ROAM) { + camFloorHeight -= 100.f; + } + ceilHeight = CELL_HEIGHT_LIMIT; + vec3f_copy(c->focus, sMarioCamState->pos); + } + + if (sMarioCamState->action & ACT_FLAG_ON_POLE) { + camFloorHeight = gMarioStates[0].usedObj->oPosY + 125.f; + if (sMarioCamState->pos[1] - 100.f > camFloorHeight) { + camFloorHeight = sMarioCamState->pos[1] - 100.f; + } + ceilHeight = CELL_HEIGHT_LIMIT; + vec3f_copy(c->focus, sMarioCamState->pos); + } + if (camFloorHeight != FLOOR_LOWER_LIMIT) { + camFloorHeight += posHeight; + approach_camera_height(c, camFloorHeight, 20.f); + } + c->pos[0] = cPos[0]; + c->pos[2] = cPos[2]; + cPos[0] = gLakituState.goalPos[0]; + cPos[1] = c->pos[1]; + cPos[2] = gLakituState.goalPos[2]; + vec3f_get_dist_and_angle(cPos, c->pos, &dist, &tempPitch, &tempYaw); + // Prevent the camera from lagging behind too much + if (dist > 50.f) { + dist = 50.f; + vec3f_set_dist_and_angle(cPos, c->pos, dist, tempPitch, tempYaw); + } + if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE) { + vec3f_get_dist_and_angle(c->focus, c->pos, &dist, &tempPitch, &tempYaw); + if (dist > zoomDist) { + dist = zoomDist; + vec3f_set_dist_and_angle(c->focus, c->pos, dist, tempPitch, tempYaw); + } + } + if (ceilHeight != CELL_HEIGHT_LIMIT) { + if (c->pos[1] > (ceilHeight -= 150.f) + && (avoidStatus = is_range_behind_surface(c->pos, sMarioCamState->pos, ceil, 0, -1)) == 1) { + c->pos[1] = ceilHeight; + } + } +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelArea == AREA_WDW_TOWN) { + yaw = clamp_positions_and_find_yaw(c->pos, c->focus, 2254.f, -3789.f, 3790.f, -2253.f); + } +#endif + return yaw; +} + +/** + * The default camera mode + * Used by close and free roam modes + */ +void mode_default_camera(struct Camera *c) { + set_fov_function(CAM_FOV_DEFAULT); + c->nextYaw = update_default_camera(c); + pan_ahead_of_player(c); +} diff --git a/src/camera/modes/fixed_cam.c b/src/camera/modes/fixed_cam.c new file mode 100644 index 0000000000..c6d37ba00a --- /dev/null +++ b/src/camera/modes/fixed_cam.c @@ -0,0 +1,114 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + + +/** + * Updates the camera during fixed mode. + */ +s32 update_fixed_camera(struct Camera *c, Vec3f focus, UNUSED Vec3f pos) { + f32 focusFloorOff; + f32 goalHeight; + f32 ceilHeight; + f32 heightOffset; + f32 distCamToFocus; + f32 scaleToMario = 0.5f; + s16 pitch; + s16 yaw; + Vec3s faceAngle; + struct Surface *ceiling; + Vec3f basePos; + + play_camera_buzz_if_c_sideways(); + + // Don't move closer to Mario in these areas + switch (gCurrLevelArea) { + case AREA_RR: + scaleToMario = 0.f; + heightOffset = 0.f; + break; + + case AREA_CASTLE_LOBBY: + scaleToMario = 0.3f; + heightOffset = 0.f; + break; + + case AREA_BBH: + scaleToMario = 0.f; + heightOffset = 0.f; + break; + } + + handle_c_button_movement(c); + play_camera_buzz_if_cdown(); + + calc_y_to_curr_floor(&focusFloorOff, 1.f, 200.f, &focusFloorOff, 0.9f, 200.f); + vec3f_copy(focus, sMarioCamState->pos); + focus[1] += focusFloorOff + 125.f; + vec3f_get_dist_and_angle(focus, c->pos, &distCamToFocus, &faceAngle[0], &faceAngle[1]); + faceAngle[2] = 0; + + vec3f_copy(basePos, sFixedModeBasePosition); + vec3f_add(basePos, sCastleEntranceOffset); + + if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE + && sMarioGeometry.currFloorHeight != FLOOR_LOWER_LIMIT) { + goalHeight = sMarioGeometry.currFloorHeight + basePos[1] + heightOffset; + } else { + goalHeight = gLakituState.goalPos[1]; + } + + if (300 > distCamToFocus) { + goalHeight += 300 - distCamToFocus; + } + + ceilHeight = find_ceil(c->pos[0], goalHeight - 100.f, c->pos[2], &ceiling); + if (ceilHeight != CELL_HEIGHT_LIMIT) { + if (goalHeight > (ceilHeight -= 125.f)) { + goalHeight = ceilHeight; + } + } + + if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { + camera_approach_f32_symmetric_bool(&c->pos[1], goalHeight, 15.f); + } else { + if (goalHeight < sMarioCamState->pos[1] - 500.f) { + goalHeight = sMarioCamState->pos[1] - 500.f; + } + c->pos[1] = goalHeight; + } + + c->pos[0] = basePos[0] + (sMarioCamState->pos[0] - basePos[0]) * scaleToMario; + c->pos[2] = basePos[2] + (sMarioCamState->pos[2] - basePos[2]) * scaleToMario; + + if (scaleToMario != 0.f) { + vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &pitch, &yaw); + if (distCamToFocus > 1000.f) { + distCamToFocus = 1000.f; + vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, pitch, yaw); + } + } + + return faceAngle[1]; +} + +/** + * Fixed camera mode, the camera rotates around a point and looks and zooms toward Mario. + */ +void mode_fixed_camera(struct Camera *c) { +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelNum == LEVEL_BBH) { + set_fov_function(CAM_FOV_BBH); + } else { + set_fov_function(CAM_FOV_APP_45); + } +#else + set_fov_function(CAM_FOV_APP_45); +#endif + c->nextYaw = update_fixed_camera(c, c->focus, c->pos); + c->yaw = c->nextYaw; + pan_ahead_of_player(c); + vec3_zero(sCastleEntranceOffset); +} diff --git a/src/camera/modes/lakitu_cam.c b/src/camera/modes/lakitu_cam.c new file mode 100644 index 0000000000..343898c1f7 --- /dev/null +++ b/src/camera/modes/lakitu_cam.c @@ -0,0 +1,12 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * The mode used by close and free roam + */ +void mode_lakitu_camera(struct Camera *c) { + gCameraZoomDist = 800.f; + mode_default_camera(c); +} diff --git a/src/camera/modes/mario_cam.c b/src/camera/modes/mario_cam.c new file mode 100644 index 0000000000..2690e51e7f --- /dev/null +++ b/src/camera/modes/mario_cam.c @@ -0,0 +1,22 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Used in sModeTransitions for CLOSE and FREE_ROAM mode + */ +s32 update_mario_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { + s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); + focus_on_mario(focus, pos, 125.f, 125.f, gCameraZoomDist, 0x05B0, yaw); + + return sMarioCamState->faceAngle[1]; +} + +/** + * When no other mode is active and the current R button mode is Mario + */ +void mode_mario_camera(struct Camera *c) { + gCameraZoomDist = 350.f; + mode_default_camera(c); +} diff --git a/src/camera/modes/outward_radial_cam.c b/src/camera/modes/outward_radial_cam.c new file mode 100644 index 0000000000..ec4aa0812b --- /dev/null +++ b/src/camera/modes/outward_radial_cam.c @@ -0,0 +1,55 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/camera.h" + + +/** + * Some functions called in this mode are located in radial_cam.c + */ + +/** + * Updates the camera in outward radial mode. + * sModeOffsetYaw is calculated in radial_camera_move, which calls offset_yaw_outward_radial + */ +s32 update_outward_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + f32 xDistFocToMario = sMarioCamState->pos[0] - c->areaCenX; + f32 zDistFocToMario = sMarioCamState->pos[2] - c->areaCenZ; + s16 camYaw = atan2s(zDistFocToMario, xDistFocToMario) + sModeOffsetYaw + DEGREES(180); + s16 pitch = look_down_slopes(camYaw); + f32 baseDist = 1000.f; + // A base offset of 125.f is ~= Mario's eye height + f32 yOff = 125.f; + f32 posY; + f32 focusY; + + sAreaYaw = camYaw - sModeOffsetYaw - DEGREES(180); + calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); + focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); + + return camYaw; +} + +/** + * Input and updates for the outward radial mode. + */ +void mode_outward_radial_camera(struct Camera *c) { + Vec3f pos; + s16 oldAreaYaw = sAreaYaw; + + if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { + update_yaw_and_dist_from_c_up(c); + } + radial_camera_input_default(c); + radial_camera_move(c); + lakitu_zoom(400.f, 0x900); + c->nextYaw = update_outward_radial_camera(c, c->focus, pos); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; + sAreaYawChange = sAreaYaw - oldAreaYaw; + if (sMarioCamState->action == ACT_RIDING_HOOT) { + pos[1] += 500.f; + } + set_camera_height(c, pos[1]); + pan_ahead_of_player(c); +} diff --git a/src/camera/modes/parallel_tracking_cam.c b/src/camera/modes/parallel_tracking_cam.c new file mode 100644 index 0000000000..ed5830a8d5 --- /dev/null +++ b/src/camera/modes/parallel_tracking_cam.c @@ -0,0 +1,223 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * The index into the current parallel tracking path + */ +static u32 sParTrackIndex; + +/** + * The current list of ParallelTrackingPoints used in update_parallel_tracking_camera() + */ +static struct ParallelTrackingPoint *sParTrackPath; + +/** + * On the first frame after the camera changes to a different parallel tracking path, this stores the + * displacement between the camera's calculated new position and its previous positions + * + * This transition offset is then used to smoothly interpolate the camera's position between the two + * paths + */ +static struct CameraStoredInfo sParTrackTransOff; + +/** + * Start parallel tracking mode using the path `path` + */ +void parallel_tracking_init(struct Camera *c, struct ParallelTrackingPoint *path) { + if (c->mode != CAMERA_MODE_PARALLEL_TRACKING) { + sParTrackPath = path; + sParTrackIndex = 0; + sParTrackTransOff.pos[0] = 0.f; + sParTrackTransOff.pos[1] = 0.f; + sParTrackTransOff.pos[2] = 0.f; + // Place the camera in the middle of the path + c->pos[0] = (sParTrackPath[0].pos[0] + sParTrackPath[1].pos[0]) / 2; + c->pos[1] = (sParTrackPath[0].pos[1] + sParTrackPath[1].pos[1]) / 2; + c->pos[2] = (sParTrackPath[0].pos[2] + sParTrackPath[1].pos[2]) / 2; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_PARALLEL_TRACKING; + } +} + +/** + * Move the camera in parallel tracking mode + * + * Uses the line between the next two points in sParTrackPath + * The camera can move forward/back and side to side, but it will face perpendicular to that line + * + * Although, annoyingly, it's not truly parallel, the function returns the yaw from the camera to Mario, + * so Mario will run slightly towards the camera. + */ +s32 update_parallel_tracking_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + Vec3f path[2]; + Vec3f parMidPoint; + Vec3f marioOffset; + Vec3f camOffset; + /// Adjusts the focus to look where Mario is facing. Unused since marioOffset is copied to focus + Vec3f focOffset = { 0 }; + s16 pathPitch; + s16 pathYaw; + f32 camParDist; + f32 pathLength; + f32 parScale = 0.5f; + f32 marioFloorDist; + Vec3f marioPos; + Vec3s pathAngle; + // Variables for changing to the next/prev path in the list + Vec3f oldPos; + Vec3f prevPathPos; + Vec3f nextPathPos; + f32 distToNext, distToPrev; + s16 prevPitch, nextPitch; + s16 prevYaw, nextYaw; + + // Store camera pos, for changing between paths + vec3f_copy(oldPos, pos); + + vec3f_copy(path[0], sParTrackPath[sParTrackIndex].pos); + vec3f_copy(path[1], sParTrackPath[sParTrackIndex + 1].pos); + + f32 distThresh = sParTrackPath[sParTrackIndex].distThresh; + f32 zoom = sParTrackPath[sParTrackIndex].zoom; + calc_y_to_curr_floor(&marioFloorDist, 1.f, 200.f, &marioFloorDist, 0.9f, 200.f); + + marioPos[0] = sMarioCamState->pos[0]; + // Mario's y pos + ~Mario's height + Mario's height above the floor + marioPos[1] = sMarioCamState->pos[1] + 150.f + marioFloorDist; + marioPos[2] = sMarioCamState->pos[2]; + + // Calculate middle of the path (parScale is 0.5f) + parMidPoint[0] = path[0][0] + (path[1][0] - path[0][0]) * parScale; + parMidPoint[1] = path[0][1] + (path[1][1] - path[0][1]) * parScale; + parMidPoint[2] = path[0][2] + (path[1][2] - path[0][2]) * parScale; + + // Get direction of path + vec3f_get_dist_and_angle(path[0], path[1], &pathLength, &pathPitch, &pathYaw); + + marioOffset[0] = marioPos[0] - parMidPoint[0]; + marioOffset[1] = marioPos[1] - parMidPoint[1]; + marioOffset[2] = marioPos[2] - parMidPoint[2]; + + // Make marioOffset point from the midpoint -> the start of the path + // Rotating by -yaw then -pitch moves the hor dist from the midpoint into marioOffset's z coordinate + // marioOffset[0] = the (perpendicular) horizontal distance from the path + // marioOffset[1] = the vertical distance from the path + // marioOffset[2] = the (parallel) horizontal distance from the path's midpoint + pathYaw = -pathYaw; + rotate_in_xz(marioOffset, marioOffset, pathYaw); + pathYaw = -pathYaw; + pathPitch = -pathPitch; + rotate_in_yz(marioOffset, marioOffset, pathPitch); + pathPitch = -pathPitch; + focOffset[2] = marioOffset[2]; + + // Repeat above calcs with camOffset + camOffset[0] = pos[0] - parMidPoint[0]; + camOffset[1] = pos[1] - parMidPoint[1]; + camOffset[2] = pos[2] - parMidPoint[2]; + pathYaw = -pathYaw; + rotate_in_xz(camOffset, camOffset, pathYaw); + pathYaw = -pathYaw; + pathPitch = -pathPitch; + rotate_in_yz(camOffset, camOffset, pathPitch); + pathPitch = -pathPitch; + + // If Mario is distThresh units away from the camera along the path, move the camera + //! When distThresh != 0, it causes Mario to move slightly towards the camera when running sideways + //! Set each ParallelTrackingPoint's distThresh to 0 to make Mario truly run parallel to the path + if (marioOffset[2] > camOffset[2]) { + if (marioOffset[2] - camOffset[2] > distThresh) { + camOffset[2] = marioOffset[2] - distThresh; + } + } else { + if (marioOffset[2] - camOffset[2] < -distThresh) { + camOffset[2] = marioOffset[2] + distThresh; + } + } + + // If zoom != 0.0, the camera will move zoom% closer to Mario + marioOffset[0] = -marioOffset[0] * zoom; + marioOffset[1] = marioOffset[1] * zoom; + marioOffset[2] = camOffset[2]; + + // make marioOffset[2] == distance from the start of the path + marioOffset[2] = pathLength / 2 - marioOffset[2]; + + pathAngle[0] = pathPitch; + pathAngle[1] = pathYaw + DEGREES(180); + pathAngle[2] = 0; + + // Rotate the offset in the direction of the path again + offset_rotated(pos, path[0], marioOffset, pathAngle); + vec3f_get_dist_and_angle(path[0], c->pos, &camParDist, &pathPitch, &pathYaw); + + // Adjust the focus. Does nothing, focus is set to Mario at the end + focOffset[2] = pathLength / 2 - focOffset[2]; + offset_rotated(c->focus, path[0], focOffset, pathAngle); + + // Changing paths, update the stored position offset + if (sStatusFlags & CAM_FLAG_CHANGED_PARTRACK_INDEX) { + sStatusFlags &= ~CAM_FLAG_CHANGED_PARTRACK_INDEX; + sParTrackTransOff.pos[0] = oldPos[0] - c->pos[0]; + sParTrackTransOff.pos[1] = oldPos[1] - c->pos[1]; + sParTrackTransOff.pos[2] = oldPos[2] - c->pos[2]; + } + // Slowly transition to the next path + approach_f32_asymptotic_bool(&sParTrackTransOff.pos[0], 0.f, 0.025f); + approach_f32_asymptotic_bool(&sParTrackTransOff.pos[1], 0.f, 0.025f); + approach_f32_asymptotic_bool(&sParTrackTransOff.pos[2], 0.f, 0.025f); + vec3f_add(c->pos, sParTrackTransOff.pos); + + // Check if the camera should go to the next path + if (sParTrackPath[sParTrackIndex + 1].startOfPath != 0) { + // get Mario's distance to the next path + calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex + 2].pos, &nextPitch, &nextYaw); + vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, nextPathPos, 400.f, nextPitch, nextYaw); + distToPrev = calc_abs_dist_squared(marioPos, nextPathPos); + + // get Mario's distance to the previous path + calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex].pos, &prevPitch, &prevYaw); + vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, prevPathPos, 400.f, prevPitch, prevYaw); + distToNext = calc_abs_dist_squared(marioPos, prevPathPos); + if (distToPrev < distToNext) { + sParTrackIndex++; + sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX; + } + } + + // Check if the camera should go to the previous path + if (sParTrackIndex != 0) { + // get Mario's distance to the next path + calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex + 1)).pos, &nextPitch, &nextYaw); + vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, nextPathPos, 700.f, nextPitch, nextYaw); + distToPrev = calc_abs_dist_squared(marioPos, nextPathPos); + + // get Mario's distance to the previous path + calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex - 1)).pos, &prevPitch, &prevYaw); + vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, prevPathPos, 700.f, prevPitch, prevYaw); + distToNext = calc_abs_dist_squared(marioPos, prevPathPos); + if (distToPrev > distToNext) { + sParTrackIndex--; + sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX; + } + } + + // Update the camera focus and return the camera's yaw + vec3f_copy(focus, marioPos); + vec3f_get_dist_and_angle(focus, pos, &camParDist, &pathPitch, &pathYaw); + return pathYaw; +} + +/** + * Parallel tracking mode, the camera faces perpendicular to a line defined by sParTrackPath + * + * @see update_parallel_tracking_camera + */ +void mode_parallel_tracking_camera(struct Camera *c) { + radial_camera_input(c); + set_fov_function(CAM_FOV_DEFAULT); + c->nextYaw = update_parallel_tracking_camera(c, c->focus, c->pos); +} + diff --git a/src/camera/modes/radial_cam.c b/src/camera/modes/radial_cam.c new file mode 100644 index 0000000000..66e72df432 --- /dev/null +++ b/src/camera/modes/radial_cam.c @@ -0,0 +1,303 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/game_init.h" +#include "game/level_update.h" +#include "game/camera.h" + + +/** + * Handles input for radial, outwards radial, parallel tracking, and 8 direction mode. + */ +void radial_camera_input(struct Camera *c) { + if ((gCameraMovementFlags & CAM_MOVE_ENTERED_ROTATE_SURFACE) || !(gCameraMovementFlags & CAM_MOVE_ROTATE)) { + + // If C-L or C-R are pressed, the camera is rotating + if (gPlayer1Controller->buttonPressed & (L_CBUTTONS | R_CBUTTONS)) { + gCameraMovementFlags &= ~CAM_MOVE_ENTERED_ROTATE_SURFACE; + // @bug this does not clear the rotation flags set by the surface. It's possible to set + // both ROTATE_LEFT and ROTATE_RIGHT, locking the camera. + // Ex: If a surface set CAM_MOVE_ROTATE_RIGHT and the user presses C-R, it locks the + // camera until a different mode is activated + } + + // Rotate Right and left + if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { + if (sModeOffsetYaw > -0x800) { + // The camera is now rotating right + if (!(gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT)) { + gCameraMovementFlags |= CAM_MOVE_ROTATE_RIGHT; + } + + if (c->mode == CAMERA_MODE_RADIAL) { + // if > ~48 degrees, we're rotating for the second time. + if (sModeOffsetYaw > 0x22AA) { + s2ndRotateFlags |= CAM_MOVE_ROTATE_RIGHT; + } + + if (sModeOffsetYaw == DEGREES(105)) { + play_sound_button_change_blocked(); + } else { + play_sound_cbutton_side(); + } + } else { + if (sModeOffsetYaw == DEGREES(60)) { + play_sound_button_change_blocked(); + } else { + play_sound_cbutton_side(); + } + } + } else { + gCameraMovementFlags |= CAM_MOVE_RETURN_TO_MIDDLE; + play_sound_cbutton_up(); + } + } + if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { + if (sModeOffsetYaw < 0x800) { + if (!(gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT)) { + gCameraMovementFlags |= CAM_MOVE_ROTATE_LEFT; + } + + if (c->mode == CAMERA_MODE_RADIAL) { + // if < ~48 degrees, we're rotating for the second time. + if (sModeOffsetYaw < -0x22AA) { + s2ndRotateFlags |= CAM_MOVE_ROTATE_LEFT; + } + + if (sModeOffsetYaw == DEGREES(-105)) { + play_sound_button_change_blocked(); + } else { + play_sound_cbutton_side(); + } + } else { + if (sModeOffsetYaw == DEGREES(-60)) { + play_sound_button_change_blocked(); + } else { + play_sound_cbutton_side(); + } + } + } else { + gCameraMovementFlags |= CAM_MOVE_RETURN_TO_MIDDLE; + play_sound_cbutton_up(); + } + } + } + + // Zoom in / enter C-Up + if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; + play_sound_cbutton_up(); + } else { + set_mode_c_up(c); + } + } + + // Zoom out + if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + gCameraMovementFlags |= CAM_MOVE_ALREADY_ZOOMED_OUT; + play_camera_buzz_if_cdown(); + } else { + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; + play_sound_cbutton_down(); + } + } +} + +/** + * Rotates the camera around the area's center point. + */ +s32 update_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + f32 cenDistX = sMarioCamState->pos[0] - c->areaCenX; + f32 cenDistZ = sMarioCamState->pos[2] - c->areaCenZ; + s16 camYaw = atan2s(cenDistZ, cenDistX) + sModeOffsetYaw; + s16 pitch = look_down_slopes(camYaw); + f32 posY, focusY; + f32 yOff = 125.f; + f32 baseDist = 1000.f; + + sAreaYaw = camYaw - sModeOffsetYaw; + calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); + focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + camYaw = find_in_bounds_yaw_wdw_bob_thi(pos, focus, camYaw); +#endif + return camYaw; +} + +/** + * Moves the camera for the radial and outward radial camera modes. + * + * If sModeOffsetYaw is 0, the camera points directly at the area center point. + */ +void radial_camera_move(struct Camera *c) { + s16 maxAreaYaw = DEGREES(60); + s16 minAreaYaw = DEGREES(-60); + s16 rotateSpeed = 0x1000; + s16 avoidYaw; + f32 areaDistX = sMarioCamState->pos[0] - c->areaCenX; + f32 areaDistZ = sMarioCamState->pos[2] - c->areaCenZ; + + // How much the camera's yaw changed + s16 yawOffset = calculate_yaw(sMarioCamState->pos, c->pos) - atan2s(areaDistZ, areaDistX); + + if (yawOffset > maxAreaYaw) { + yawOffset = maxAreaYaw; + } + if (yawOffset < minAreaYaw) { + yawOffset = minAreaYaw; + } + + // Check if Mario stepped on a surface that rotates the camera. For example, when Mario enters the + // gate in BoB, the camera turns right to face up the hill path + if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) { + if (sMarioGeometry.currFloorType == SURFACE_CAMERA_MIDDLE + && sMarioGeometry.prevFloorType != SURFACE_CAMERA_MIDDLE) { + gCameraMovementFlags |= (CAM_MOVE_RETURN_TO_MIDDLE | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_RIGHT + && sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_RIGHT) { + gCameraMovementFlags |= (CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_LEFT + && sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_LEFT) { + gCameraMovementFlags |= (CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + } + + if (gCameraMovementFlags & CAM_MOVE_ENTERED_ROTATE_SURFACE) { + rotateSpeed = 0x200; + } + + if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) { + areaDistX = -areaDistX; + areaDistZ = -areaDistZ; + } + + // Avoid obstructing walls + s32 avoidStatus = rotate_camera_around_walls(c, c->pos, &avoidYaw, 0x400); + if (avoidStatus == AVOID_STATUS_WALL_COVERING_MARIO) { + if (avoidYaw - atan2s(areaDistZ, areaDistX) + DEGREES(90) < 0) { + avoidYaw += DEGREES(180); + } + + // We want to change sModeOffsetYaw so that the player is no longer obstructed by the wall. + // So, we make avoidYaw relative to the yaw around the area center + avoidYaw -= atan2s(areaDistZ, areaDistX); + + // Bound avoid yaw to radial mode constraints + if (avoidYaw > DEGREES(105)) { + avoidYaw = DEGREES(105); + } + if (avoidYaw < DEGREES(-105)) { + avoidYaw = DEGREES(-105); + } + } + + if (gCameraMovementFlags & CAM_MOVE_RETURN_TO_MIDDLE) { + if (camera_approach_s16_symmetric_bool(&sModeOffsetYaw, 0, rotateSpeed) == 0) { + gCameraMovementFlags &= ~CAM_MOVE_RETURN_TO_MIDDLE; + } + } else { + // Prevent the player from rotating into obstructing walls + if ((gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) && avoidStatus == 3 + && avoidYaw + 0x10 < sModeOffsetYaw) { + sModeOffsetYaw = avoidYaw; + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + if ((gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) && avoidStatus == 3 + && avoidYaw - 0x10 > sModeOffsetYaw) { + sModeOffsetYaw = avoidYaw; + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + + // If it's the first time rotating, just rotate to +-60 degrees + if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) + && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, maxAreaYaw, rotateSpeed) == 0) { + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) + && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, minAreaYaw, rotateSpeed) == 0) { + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + } + + // If it's the second time rotating, rotate all the way to +-105 degrees. + if ((s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) + && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(105), rotateSpeed) == 0) { + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + s2ndRotateFlags &= ~CAM_MOVE_ROTATE_RIGHT; + } + if ((s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) + && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(-105), rotateSpeed) == 0) { + gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); + s2ndRotateFlags &= ~CAM_MOVE_ROTATE_LEFT; + } + } + if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) { + // If not rotating, rotate away from walls obscuring Mario from view + if (avoidStatus == 3) { + approach_s16_asymptotic_bool(&sModeOffsetYaw, avoidYaw, 10); + } else { + if (c->mode == CAMERA_MODE_RADIAL) { + // sModeOffsetYaw only updates when Mario is moving + rotateSpeed = gMarioStates[0].forwardVel / 32.f * 128.f; + camera_approach_s16_symmetric_bool(&sModeOffsetYaw, yawOffset, rotateSpeed); + } + if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) { + sModeOffsetYaw = offset_yaw_outward_radial(c, atan2s(areaDistZ, areaDistX)); + } + } + } + + // Bound sModeOffsetYaw within (-120, 120) degrees + if (sModeOffsetYaw > 0x5554) { + sModeOffsetYaw = 0x5554; + } + if (sModeOffsetYaw < -0x5554) { + sModeOffsetYaw = -0x5554; + } +} + +void radial_camera_input_default(struct Camera *c) { + radial_camera_input(c); +} + +/** + * Makes Lakitu cam's yaw match the angle turned towards in C-Up mode, and makes Lakitu slowly fly back + * to the distance he was at before C-Up + */ +void update_yaw_and_dist_from_c_up(UNUSED struct Camera *c) { + sModeOffsetYaw = sModeInfo.transitionStart.yaw - sAreaYaw; + sLakituDist = sModeInfo.transitionStart.dist - 1000.0f; + // No longer in C-Up + gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; +} + +/** + * Handles input and updates for the radial camera mode + */ +void mode_radial_camera(struct Camera *c) { + Vec3f pos; + s16 oldAreaYaw = sAreaYaw; + + if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { + update_yaw_and_dist_from_c_up(c); + } + + radial_camera_input_default(c); + radial_camera_move(c); + + if (c->mode == CAMERA_MODE_RADIAL) { + lakitu_zoom(400.f, 0x900); + } + c->nextYaw = update_radial_camera(c, c->focus, pos); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; + sAreaYawChange = sAreaYaw - oldAreaYaw; + if (sMarioCamState->action == ACT_RIDING_HOOT) { + pos[1] += 500.f; + } + set_camera_height(c, pos[1]); + pan_ahead_of_player(c); +} diff --git a/src/camera/modes/slide_cam.c b/src/camera/modes/slide_cam.c new file mode 100644 index 0000000000..49a5abda14 --- /dev/null +++ b/src/camera/modes/slide_cam.c @@ -0,0 +1,102 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/game_init.h" +#include "game/camera.h" + +/** + * Update the camera in slide and hoot mode. + * + * In slide mode, keep the camera 800 units from Mario + */ +s16 update_slide_camera(struct Camera *c) { + struct Surface *floor; + f32 floorHeight; + Vec3f pos; + f32 distCamToFocus; + f32 maxCamDist; + f32 pitchScale; + s16 camPitch; + s16 camYaw; + s16 goalPitch = 0x1555; + s16 goalYaw = sMarioCamState->faceAngle[1] + DEGREES(180); + + // Zoom in when inside the CCM shortcut + if (sStatusFlags & CAM_FLAG_CCM_SLIDE_SHORTCUT) { + sLakituDist = approach_f32(sLakituDist, -600.f, 20.f, 20.f); + } else { + sLakituDist = approach_f32(sLakituDist, 0.f, 20.f, 20.f); + } + + // No C-Button input in this mode, notify the player with a buzzer + play_camera_buzz_if_cbutton(); + + // Focus on Mario + vec3f_copy(c->focus, sMarioCamState->pos); + c->focus[1] += 50.f; + + vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); + maxCamDist = 800.f; + + // In hoot mode, zoom further out and rotate faster + if (sMarioCamState->action == ACT_RIDING_HOOT) { + maxCamDist = 1000.f; + goalPitch = 0x2800; + camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x100); + } else { + camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x80); + } + camera_approach_s16_symmetric_bool(&camPitch, goalPitch, 0x100); + + // Hoot mode + if (sMarioCamState->action != ACT_RIDING_HOOT && sMarioGeometry.currFloorType == SURFACE_DEATH_PLANE) { + vec3f_set_dist_and_angle(c->focus, pos, maxCamDist + sLakituDist, camPitch, camYaw); + c->pos[0] = pos[0]; + c->pos[2] = pos[2]; + camera_approach_f32_symmetric_bool(&c->pos[1], c->focus[1], 30.f); + vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &camPitch, &camYaw); + pitchScale = (distCamToFocus - maxCamDist + sLakituDist) / 10000.f; + if (pitchScale > 1.f) { + pitchScale = 1.f; + } + camPitch += 0x1000 * pitchScale; + vec3f_set_dist_and_angle(c->pos, c->focus, distCamToFocus, camPitch, camYaw); + + // Slide mode + } else { + vec3f_set_dist_and_angle(c->focus, c->pos, maxCamDist + sLakituDist, camPitch, camYaw); + sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + + // Stay above the slide floor + floorHeight = find_floor(c->pos[0], c->pos[1] + 200.f, c->pos[2], &floor) + 125.f; + if (c->pos[1] < floorHeight) { + c->pos[1] = floorHeight; + } + // Stay closer than maxCamDist + vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); + if (distCamToFocus > maxCamDist + sLakituDist) { + distCamToFocus = maxCamDist + sLakituDist; + vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw); + } + } + + camYaw = calculate_yaw(c->focus, c->pos); + return camYaw; +} + +/** + * Slide/hoot mode. + * In this mode, the camera is always at the back of Mario, because Mario generally only moves forward. + */ +void mode_slide_camera(struct Camera *c) { + if (sMarioGeometry.currFloorType == SURFACE_CLOSE_CAMERA || + sMarioGeometry.currFloorType == SURFACE_NO_CAM_COL_SLIPPERY) { + mode_lakitu_camera(c); + } else { + if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { + gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; + } + c->nextYaw = update_slide_camera(c); + } +} diff --git a/src/camera/modes/spiral_stairs_cam.c b/src/camera/modes/spiral_stairs_cam.c new file mode 100644 index 0000000000..a5cb8ea7e1 --- /dev/null +++ b/src/camera/modes/spiral_stairs_cam.c @@ -0,0 +1,82 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "engine/surface_collision.h" +#include "game/camera.h" + +/** + * Stores Mario's yaw around the stairs, relative to the camera's position. + */ +s16 sSpiralStairsYawOffset; + +/** + * Rotates the camera around the spiral staircase. + */ +s32 update_spiral_stairs_camera(struct Camera *c, Vec3f focus, Vec3f pos) { + /// The returned yaw + s16 camYaw; + /// The focus (Mario)'s yaw around the stairs + s16 focYaw; + /// The camera's yaw around the stairs + s16 posYaw; + Vec3f cPos; + Vec3f checkPos; + struct Surface *floor; + f32 focusHeight; + f32 floorHeight; + f32 focY; + + handle_c_button_movement(c); + // Set base pos to the center of the staircase + vec3f_set(sFixedModeBasePosition, -1280.f, 614.f, 1740.f); + + // Focus on Mario, and move the focus up the staircase with him + calc_y_to_curr_floor(&focusHeight, 1.f, 200.f, &focusHeight, 0.9f, 200.f); + focus[0] = sMarioCamState->pos[0]; + focY = sMarioCamState->pos[1] + 125.f + focusHeight; + focus[2] = sMarioCamState->pos[2]; + + vec3f_copy(cPos, pos); + vec3f_get_yaw(sFixedModeBasePosition, focus, &focYaw); + vec3f_get_yaw(sFixedModeBasePosition, cPos, &posYaw); + + sSpiralStairsYawOffset = posYaw - focYaw; + // posYaw will change if Mario is more than 90 degrees around the stairs, relative to the camera + if (sSpiralStairsYawOffset < DEGREES(-90)) { + sSpiralStairsYawOffset = DEGREES(-90); + } + if (sSpiralStairsYawOffset > DEGREES(90)) { + sSpiralStairsYawOffset = DEGREES(90); + } + focYaw += sSpiralStairsYawOffset; + posYaw = focYaw; + + vec3f_set_dist_and_angle(sFixedModeBasePosition, cPos, 300.f, 0, posYaw); + + // Move the camera's y coord up/down the staircase + checkPos[0] = focus[0] + (cPos[0] - focus[0]) * 0.7f; + checkPos[1] = focus[1] + (cPos[1] - focus[1]) * 0.7f + 300.f; + checkPos[2] = focus[2] + (cPos[2] - focus[2]) * 0.7f; + floorHeight = find_floor(checkPos[0], checkPos[1] + 50.f, checkPos[2], &floor); + + if (floorHeight != FLOOR_LOWER_LIMIT) { + if (floorHeight < sMarioGeometry.currFloorHeight) { + floorHeight = sMarioGeometry.currFloorHeight; + } + pos[1] = approach_f32(pos[1], (floorHeight += 125.f), 30.f, 30.f); + } + + camera_approach_f32_symmetric_bool(&focus[1], focY, 30.f); + pos[0] = cPos[0]; + pos[2] = cPos[2]; + camYaw = calculate_yaw(focus, pos); + + return camYaw; +} + +/** + * The mode used in the spiral staircase in the castle + */ +void mode_spiral_stairs_camera(struct Camera *c) { + c->nextYaw = update_spiral_stairs_camera(c, c->focus, c->pos); +} diff --git a/src/camera/modes/water_surface_cam.c b/src/camera/modes/water_surface_cam.c new file mode 100644 index 0000000000..0a42ea50a5 --- /dev/null +++ b/src/camera/modes/water_surface_cam.c @@ -0,0 +1,11 @@ +#include "camera/camera_math.h" +#include "camera/camera_modes.h" +#include "engine/math_util.h" +#include "game/camera.h" + +/** + * Exactly the same as BEHIND_MARIO + */ +void mode_water_surface_camera(struct Camera *c) { + c->nextYaw = mode_behind_mario(c); +} diff --git a/src/camera/vanilla_trigger_code.h b/src/camera/vanilla_trigger_code.h new file mode 100644 index 0000000000..b9b9080816 --- /dev/null +++ b/src/camera/vanilla_trigger_code.h @@ -0,0 +1,70 @@ +#pragma once + +#include "types.h" +#include "game/camera.h" + +void cam_bbh_room_6(struct Camera *c); +void cam_bbh_fall_off_roof(struct Camera *c); +void cam_bbh_fall_into_pool(struct Camera *c); +void cam_bbh_room_1(struct Camera *c); +void cam_bbh_leave_front_door(struct Camera *c); +void cam_bbh_room_2_lower(struct Camera *c); +void cam_bbh_room_4(struct Camera *c); +void cam_bbh_room_8(struct Camera *c); +void cam_bbh_room_5_library(struct Camera *c); +void cam_bbh_room_5_library_to_hidden_transition(struct Camera *c); +void cam_bbh_room_5_hidden_to_library_transition(struct Camera *c); +void cam_bbh_room_5_hidden(struct Camera *c); +void cam_bbh_room_3(struct Camera *c); +void cam_bbh_room_7_mr_i(struct Camera *c); +void cam_bbh_room_7_mr_i_to_coffins_transition(struct Camera *c); +void cam_bbh_room_7_coffins_to_mr_i_transition(struct Camera *c); +void cam_bbh_elevator_room_lower(struct Camera *c); +void cam_bbh_room_0_back_entrance(struct Camera *c); +void cam_bbh_elevator(struct Camera *c); +void cam_bbh_room_12_upper(struct Camera *c); +void cam_bbh_enter_front_door(struct Camera *c); +void cam_bbh_room_2_library(struct Camera *c); +void cam_bbh_room_2_library_to_trapdoor_transition(struct Camera *c); +void cam_bbh_room_2_trapdoor(struct Camera *c); +void cam_bbh_room_2_trapdoor_transition(struct Camera *c); +void cam_bbh_room_9_attic(struct Camera *c); +void cam_bbh_room_9_attic_transition(struct Camera *c); +void cam_bbh_room_9_mr_i_transition(struct Camera *c); +void cam_bbh_room_13_balcony(struct Camera *c); +void cam_bbh_room_0(struct Camera *c); +void cam_bob_tower(struct Camera *c); +void cam_bob_default_free_roam(struct Camera *c); +void cam_bob_tower(struct Camera *c); +void cam_bob_default_free_roam(struct Camera *c); +void cam_castle_hmc_start_pool_cutscene(struct Camera *c); +void cam_castle_lobby_entrance(UNUSED struct Camera *c); +void cam_castle_look_upstairs(struct Camera *c); +void cam_castle_basement_look_downstairs(struct Camera *c); +void cam_castle_enter_lobby(struct Camera *c); +void cam_castle_enter_spiral_stairs(struct Camera *c); +void cam_castle_leave_spiral_stairs(struct Camera *c); +void cam_castle_close_mode(struct Camera *c); +void cam_castle_leave_lobby_sliding_door(struct Camera *c); +void cam_castle_enter_lobby_sliding_door(struct Camera *c); +void cam_ccm_enter_slide_shortcut(UNUSED struct Camera *c); +void cam_ccm_leave_slide_shortcut(UNUSED struct Camera *c); +void cam_cotmc_exit_waterfall(UNUSED struct Camera *c); +void move_camera_through_floor_while_descending(struct Camera *c, f32 height); +void cam_hmc_enter_maze(struct Camera *c); +void cam_hmc_elevator_black_hole(struct Camera *c); +void cam_hmc_elevator_maze_emergency_exit(struct Camera *c); +void cam_hmc_elevator_lake(struct Camera *c); +void cam_hmc_elevator_maze(struct Camera *c); +void cam_rr_exit_building_side(struct Camera *c); +void cam_rr_exit_building_top(struct Camera *c); +void cam_rr_enter_building_window(struct Camera *c); +void cam_rr_enter_building(struct Camera *c); +void cam_rr_enter_building_side(struct Camera *c); +void cam_sl_snowman_head_8dir(struct Camera *c); +void cam_sl_free_roam(struct Camera *c); +void cam_ssl_enter_pyramid_top(UNUSED struct Camera *c); +void cam_ssl_pyramid_center(struct Camera *c); +void cam_ssl_boss_room(struct Camera *c); +void cam_thi_move_cam_through_tunnel(UNUSED struct Camera *c); +void cam_thi_look_through_tunnel(UNUSED struct Camera *c); \ No newline at end of file diff --git a/src/game/behavior_actions.c b/src/game/behavior_actions.c index ee66b6b02a..3831a4feed 100644 --- a/src/game/behavior_actions.c +++ b/src/game/behavior_actions.c @@ -17,6 +17,7 @@ #include "behavior_actions.h" #include "behavior_data.h" #include "camera.h" +#include "camera/cutscene_helpers.h" #include "debug.h" #include "dialog_ids.h" #include "engine/behavior_script.h" diff --git a/src/game/behaviors/intro_lakitu.inc.c b/src/game/behaviors/intro_lakitu.inc.c index 3e63925227..5e34512e98 100644 --- a/src/game/behaviors/intro_lakitu.inc.c +++ b/src/game/behaviors/intro_lakitu.inc.c @@ -67,6 +67,36 @@ s32 intro_lakitu_set_pos_and_focus(struct Object *obj, struct CutsceneSplinePoin #define TIMER2 98 #endif +struct CutsceneSplinePoint gIntroLakituStartToPipeFocus[] = { + { 0, 32, { 58, -250, 346 } }, { 1, 50, { -159, -382, 224 } }, { 2, 37, { 0, -277, 237 } }, + { 3, 15, { 1, -44, 245 } }, { 4, 35, { 0, -89, 228 } }, { 5, 15, { 28, 3, 259 } }, + { 6, 25, { -38, -201, 371 } }, { 7, 20, { -642, 118, 652 } }, { 8, 25, { 103, -90, 861 } }, + { 9, 25, { 294, 145, 579 } }, { 10, 30, { 220, -42, 500 } }, { 11, 20, { 10, -134, 200 } }, + { 12, 20, { -143, -145, 351 } }, { 13, 14, { -256, -65, 528 } }, { 14, 20, { -251, -52, 459 } }, + { 15, 25, { -382, 520, 395 } }, { 16, 25, { -341, 240, 653 } }, { 17, 5, { -262, 700, 143 } }, + { 18, 15, { -760, 32, 27 } }, { 19, 20, { -756, -6, -26 } }, { 20, 20, { -613, 5, 424 } }, + { 21, 20, { -22, -100, 312 } }, { 22, 25, { 212, 80, 61 } }, { 23, 20, { 230, -28, 230 } }, + { 24, 35, { -83, -51, 303 } }, { 25, 17, { 126, 90, 640 } }, { 26, 9, { 158, 95, 763 } }, + { 27, 8, { 113, -25, 1033 } }, { 28, 20, { 57, -53, 1291 } }, { 29, 15, { 73, -34, 1350 } }, + { 30, 7, { 0, 96, 1400 } }, { 31, 8, { -59, 269, 1450 } }, { 32, 15, { 57, 1705, 1500 } }, + { 0, 15, { -227, 511, 1550 } }, { -1, 15, { -227, 511, 1600 } } +}; + +struct CutsceneSplinePoint gIntroLakituStartToPipeOffsetFromCamera[] = { + { 0, 0, { -46, 87, -15 } }, { 1, 0, { -38, 91, -11 } }, { 2, 0, { -31, 93, -13 } }, + { 3, 0, { -50, 84, -16 } }, { 4, 0, { -52, 83, -17 } }, { 5, 0, { -10, 99, 3 } }, + { 6, 0, { -54, 83, -10 } }, { 7, 0, { -31, 85, -40 } }, { 8, 0, { -34, 91, 19 } }, + { 9, 0, { -9, 95, 28 } }, { 10, 0, { 17, 72, 66 } }, { 11, 0, { 88, -7, 45 } }, + { 12, 0, { 96, -6, -26 } }, { 13, 0, { 56, -1, -82 } }, { 14, 0, { 40, 65, -63 } }, + { 15, 0, { -26, -3, -96 } }, { 16, 0, { 92, 82, 19 } }, { 17, 0, { 92, 32, 19 } }, + { 18, 0, { 92, 32, 19 } }, { 19, 0, { 92, 102, 19 } }, { 20, 0, { -69, 59, -70 } }, + { 21, 0, { -77, 109, -61 } }, { 22, 0, { -87, 59, -46 } }, { 23, 0, { -99, -3, 11 } }, + { 24, 0, { -99, -11, 5 } }, { 25, 0, { -97, -6, 19 } }, { 26, 0, { -97, 22, -7 } }, + { 27, 0, { -98, -11, -13 } }, { 28, 0, { -97, -11, 19 } }, { 29, 0, { -91, -11, 38 } }, + { 30, 0, { -76, -11, 63 } }, { 31, 0, { -13, 33, 93 } }, { 32, 0, { 51, -11, 84 } }, + { 33, 0, { 51, -11, 84 } }, { -1, 0, { 51, -11, 84 } } +}; + void bhv_intro_lakitu_loop(void) { Vec3f offset, fromPoint, toPoint; s16 yawToCam; diff --git a/src/game/camera.c b/src/game/camera.c index 8cd629d0af..f7f6c03caa 100644 --- a/src/game/camera.c +++ b/src/game/camera.c @@ -29,53 +29,13 @@ #include "puppyprint.h" #include "profiling.h" -#define CBUTTON_MASK (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS) +#include "camera/camera_math.h" +#include "camera/vanilla_trigger_code.h" +#include "camera/camera_modes.h" +#include "camera/cutscene_helpers.h" -/** - * @file camera.c - * Implements the camera system, including C-button input, camera modes, camera triggers, and cutscenes. - * - * When working with the camera, you should be familiar with sm64's coordinate system. - * Relative to the camera, the coordinate system follows the right hand rule: - * +X points right. - * +Y points up. - * +Z points out of the screen. - * - * You should also be familiar with Euler angles: 'pitch', 'yaw', and 'roll'. - * pitch: rotation about the X-axis, measured from +Y. - * Unlike yaw and roll, pitch is bounded in +-0x4000 (90 degrees). - * Pitch is 0 when the camera points parallel to the xz-plane (+Y points straight up). - * - * yaw: rotation about the Y-axis, measured from (absolute) +Z. - * Positive yaw rotates clockwise, towards +X. - * - * roll: rotation about the Z-axis, measured from the camera's right direction. - * Unfortunately, it's weird: For some reason, roll is flipped. Positive roll makes the camera - * rotate counterclockwise, which means the WORLD rotates clockwise. Luckily roll is rarely - * used. - * - * Remember the right hand rule: make a thumbs-up with your right hand, stick your thumb in the - * +direction (except for roll), and the angle follows the rotation of your curled fingers. - * - * Illustrations: - * Following the right hand rule, each hidden axis's positive direction points out of the screen. - * - * YZ-Plane (pitch) XZ-Plane (yaw) XY-Plane (roll -- Note flipped) - * +Y -Z +Y - * ^ ^ (into the ^ - * --|-- | screen) |<- - * +pitch / | \ -pitch | | \ -roll - * v | v | | | - * +Z <------O------> -Z -X <------O------> +X -X <------O------> +X - * | ^ | ^ | | - * | \ | / | / +roll - * | -yaw --|-- +yaw |<- - * v v v - * -Y +Z -Y - * - */ +#define CBUTTON_MASK (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS) -// BSS /** * Stores Lakitu's position from the last frame, used for transitioning in next_lakitu_state() */ @@ -89,76 +49,12 @@ Vec3f sOldFocus; * L is real. */ struct PlayerCameraState gPlayerCameraState[2]; -/** - * Direction controlled by player 2, moves the focus during the credits. - */ -Vec3f sPlayer2FocusOffset; -/** - * The pitch used for the credits easter egg. - */ -s16 sCreditsPlayer2Pitch; -/** - * The yaw used for the credits easter egg. - */ -s16 sCreditsPlayer2Yaw; + /** * Used to decide when to zoom out in the pause menu. */ u8 sFramesPaused; -extern struct CameraFOVStatus sFOVState; -extern struct TransitionInfo sModeTransition; -extern struct PlayerGeometry sMarioGeometry; -extern s16 sAvoidYawVel; -extern s16 sCameraYawAfterDoorCutscene; -extern struct HandheldShakePoint sHandheldShakeSpline[4]; -extern s16 sHandheldShakeMag; -extern f32 sHandheldShakeTimer; -extern f32 sHandheldShakeInc; -extern s16 sHandheldShakePitch; -extern s16 sHandheldShakeYaw; -extern s16 sHandheldShakeRoll; -extern s16 sSelectionFlags; -extern s16 s2ndRotateFlags; -extern s16 sCameraSoundFlags; -extern u16 sCButtonsPressed; -extern s16 sCutsceneDialogID; -extern struct LakituState gLakituState; -extern s16 sAreaYaw; -extern s16 sAreaYawChange; -extern s16 sLakituDist; -extern s16 sLakituPitch; -extern f32 sZoomAmount; -extern s16 sCSideButtonYaw; -extern s16 sBehindMarioSoundTimer; -extern f32 sZeroZoomDist; -extern s16 sCUpCameraPitch; -extern s16 sModeOffsetYaw; -extern s16 sSpiralStairsYawOffset; -extern s16 s8DirModeBaseYaw; -extern s16 s8DirModeYawOffset; -extern f32 sPanDistance; -extern f32 sCannonYOffset; -extern struct ModeTransitionInfo sModeInfo; -extern Vec3f sCastleEntranceOffset; -extern u32 sParTrackIndex; -extern struct ParallelTrackingPoint *sParTrackPath; -extern struct CameraStoredInfo sParTrackTransOff; -extern struct CameraStoredInfo sCameraStoreCUp; -extern struct CameraStoredInfo sCameraStoreCutscene; -extern s16 gCameraMovementFlags; -extern s16 sStatusFlags; -extern struct CutsceneSplinePoint sCurCreditsSplinePos[32]; -extern struct CutsceneSplinePoint sCurCreditsSplineFocus[32]; -extern s16 sCutsceneSplineSegment; -extern f32 sCutsceneSplineSegmentProgress; -extern s16 sCutsceneShot; -extern s16 gCutsceneTimer; -extern struct CutsceneVariable sCutsceneVars[10]; -extern s32 gObjCutsceneDone; -extern u32 gCutsceneObjSpawn; -extern struct Camera *gCamera; - /** * Lakitu's position and focus. * @see LakituState @@ -170,26 +66,6 @@ struct PlayerGeometry sMarioGeometry; struct Camera *gCamera; s16 sAvoidYawVel; s16 sCameraYawAfterDoorCutscene; -/** - * The current spline that controls the camera's position during the credits. - */ -struct CutsceneSplinePoint sCurCreditsSplinePos[32]; - -/** - * The current spline that controls the camera's focus during the credits. - */ -struct CutsceneSplinePoint sCurCreditsSplineFocus[32]; - -/** - * The progress (from 0 to 1) through the current spline segment. - * When it becomes >= 1, 1.0 is subtracted from it and sCutsceneSplineSegment is increased. - */ -f32 sCutsceneSplineSegmentProgress; - -/** - * The current segment of the CutsceneSplinePoint[] being used. - */ -s16 sCutsceneSplineSegment; // Shaky Hand-held Camera effect variables struct HandheldShakePoint sHandheldShakeSpline[4]; @@ -239,18 +115,6 @@ s16 sCameraSoundFlags; * Stores what C-Buttons are pressed this frame. */ u16 sCButtonsPressed; -/** - * A copy of gDialogID, the dialog displayed during the cutscene. - */ -s16 sCutsceneDialogID; -/** - * The currently playing shot in the cutscene. - */ -s16 sCutsceneShot; -/** - * The current frame of the cutscene shot. - */ -s16 gCutsceneTimer; /** * The angle of the direction vector from the area's center to Mario's position. @@ -298,13 +162,6 @@ s16 sCUpCameraPitch; */ s16 sModeOffsetYaw; -/** - * Stores Mario's yaw around the stairs, relative to the camera's position. - * - * Used in update_spiral_stairs_camera() - */ -s16 sSpiralStairsYawOffset; - /** * The constant offset to 8-direction mode's yaw. */ @@ -324,37 +181,13 @@ f32 sPanDistance; * This is used to make the camera start up and rotate down, like the cannon. */ f32 sCannonYOffset; -/** - * These structs are used by the cutscenes. Most of the fields are unused, and some (all?) of the used - * ones have multiple uses. - * Check the cutscene_start functions for documentation on the cvars used by a specific cutscene. - */ -struct CutsceneVariable sCutsceneVars[10]; + struct ModeTransitionInfo sModeInfo; /** * Offset added to sFixedModeBasePosition when Mario is inside, near the castle lobby entrance */ Vec3f sCastleEntranceOffset; -/** - * The index into the current parallel tracking path - */ -u32 sParTrackIndex; - -/** - * The current list of ParallelTrackingPoints used in update_parallel_tracking_camera() - */ -struct ParallelTrackingPoint *sParTrackPath; - -/** - * On the first frame after the camera changes to a different parallel tracking path, this stores the - * displacement between the camera's calculated new position and its previous positions - * - * This transition offset is then used to smoothly interpolate the camera's position between the two - * paths - */ -struct CameraStoredInfo sParTrackTransOff; - /** * The information stored when C-Up is active, used to update Lakitu's rotation when exiting C-Up */ @@ -365,9 +198,6 @@ struct CameraStoredInfo sCameraStoreCUp; */ struct CameraStoredInfo sCameraStoreCutscene; -// first iteration of data -struct Object *gCutsceneFocus = NULL; - /** * The information of a second focus camera used by some objects */ @@ -377,56 +207,20 @@ struct Object *gSecondCameraFocus = NULL; * How fast the camera's yaw should approach the next yaw. */ s16 sYawSpeed = 0x400; + s32 gCurrLevelArea = 0; u32 gPrevLevel = 0; - f32 gCameraZoomDist = 800.0f; -/** - * A cutscene that plays when the player interacts with an object - */ -u8 sObjectCutscene = CUTSCENE_NONE; - -/** - * The ID of the cutscene that ended. It's set to 0 if no cutscene ended less than 8 frames ago. - * - * It is only used to prevent the same cutscene from playing twice before 8 frames have passed. - */ -u8 gRecentCutscene = CUTSCENE_NONE; - /** * A timer that increments for 8 frames when a cutscene ends. * When it reaches 8, it sets gRecentCutscene to 0. */ u8 sFramesSinceCutsceneEnded = 0; -/** - * Mario's response to a dialog. - * 0 = No response yet - * 1 = Yes - * 2 = No - * 3 = Dialog doesn't have a response - */ -u8 sCutsceneDialogResponse = DIALOG_RESPONSE_NONE; + struct PlayerCameraState *sMarioCamState = &gPlayerCameraState[0]; -// struct PlayerCameraState *sLuigiCamState = &gPlayerCameraState[1]; Vec3f sFixedModeBasePosition = { 646.0f, 143.0f, -1513.0f }; -s32 update_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_outward_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_behind_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 unused_update_mode_5_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_c_up(struct Camera *c, Vec3f focus, Vec3f pos); -s32 nop_update_water_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_slide_or_0f_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_in_cannon(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_boss_fight_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_parallel_tracking_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_fixed_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_slide_or_0f_camera(struct Camera *c, Vec3f focus, Vec3f pos); -s32 update_spiral_stairs_camera(struct Camera *c, Vec3f focus, Vec3f pos); - typedef s32 (*CameraTransition)(struct Camera *c, Vec3f focus, Vec3f pos); CameraTransition sModeTransitions[] = { NULL, @@ -449,10 +243,6 @@ CameraTransition sModeTransitions[] = { update_spiral_stairs_camera }; -// Move these two tables to another include file? -extern u8 sDanceCutsceneIndexTable[][4]; -extern u8 sZoomOutAreaMasks[]; - /** * Starts a camera shake triggered by an interaction */ @@ -619,210 +409,6 @@ void unused_set_camera_pitch_shake_env(s16 shake) { } } -/** - * Calculates Mario's distance to the floor, or the water level if it is above the floor. Then: - * `posOff` is set to the distance multiplied by posMul and bounded to [-posBound, posBound] - * `focOff` is set to the distance multiplied by focMul and bounded to [-focBound, focBound] - * - * Notes: - * posMul is always 1.0f, focMul is always 0.9f - * both ranges are always 200.0f - * Since focMul is 0.9, `focOff` is closer to the floor than `posOff` - * posOff and focOff are sometimes the same address, which just ignores the pos calculation - */ -void calc_y_to_curr_floor(f32 *posOff, f32 posMul, f32 posBound, f32 *focOff, f32 focMul, f32 focBound) { - f32 floorHeight = sMarioGeometry.currFloorHeight; - f32 waterHeight; - - if (!(sMarioCamState->action & ACT_FLAG_METAL_WATER)) { - //! @bug this should use sMarioGeometry.waterHeight - if (floorHeight < (waterHeight = find_water_level(sMarioCamState->pos[0], sMarioCamState->pos[2]))) { - floorHeight = waterHeight; - } - } - - if (sMarioCamState->action & ACT_FLAG_ON_POLE) { - if (sMarioGeometry.currFloorHeight >= gMarioStates[0].usedObj->oPosY && sMarioCamState->pos[1] - < 0.7f * gMarioStates[0].usedObj->hitboxHeight + gMarioStates[0].usedObj->oPosY) { - posBound = 1200; - } - } - - *posOff = (floorHeight - sMarioCamState->pos[1]) * posMul; - - if (*posOff > posBound) { - *posOff = posBound; - } - - if (*posOff < -posBound) { - *posOff = -posBound; - } - - *focOff = (floorHeight - sMarioCamState->pos[1]) * focMul; - - if (*focOff > focBound) { - *focOff = focBound; - } - - if (*focOff < -focBound) { - *focOff = -focBound; - } -} - -void focus_on_mario(Vec3f focus, Vec3f pos, f32 posYOff, f32 focYOff, f32 dist, s16 pitch, s16 yaw) { - Vec3f marioPos; - - marioPos[0] = sMarioCamState->pos[0]; - marioPos[1] = sMarioCamState->pos[1] + posYOff; - marioPos[2] = sMarioCamState->pos[2]; - - vec3f_set_dist_and_angle(marioPos, pos, dist, pitch + sLakituPitch, yaw); - - focus[0] = sMarioCamState->pos[0]; - focus[1] = sMarioCamState->pos[1] + focYOff; - focus[2] = sMarioCamState->pos[2]; -} - -/** - * Set the camera's y coordinate to goalHeight, respecting floors and ceilings in the way - */ -void set_camera_height(struct Camera *c, f32 goalHeight) { - struct Surface *surface; - f32 marioFloorHeight, marioCeilHeight, camFloorHeight; - f32 baseOff = 125.f; - f32 camCeilHeight = find_ceil(c->pos[0], gLakituState.goalPos[1] - 50.f, c->pos[2], &surface); -#ifdef FAST_VERTICAL_CAMERA_MOVEMENT - f32 approachRate = 20.0f; -#endif - - if (sMarioCamState->action & ACT_FLAG_HANGING) { - marioCeilHeight = sMarioGeometry.currCeilHeight; - marioFloorHeight = sMarioGeometry.currFloorHeight; - - if (marioFloorHeight < marioCeilHeight - 400.f) { - marioFloorHeight = marioCeilHeight - 400.f; - } - - goalHeight = marioFloorHeight + (marioCeilHeight - marioFloorHeight) * 0.4f; - - if (sMarioCamState->pos[1] - 400 > goalHeight) { - goalHeight = sMarioCamState->pos[1] - 400; - } - - approach_camera_height(c, goalHeight, 5.f); - } else { - camFloorHeight = find_floor(c->pos[0], c->pos[1] + 100.f, c->pos[2], &surface) + baseOff; - marioFloorHeight = baseOff + sMarioGeometry.currFloorHeight; - - if (camFloorHeight < marioFloorHeight) { - camFloorHeight = marioFloorHeight; - } - if (goalHeight < camFloorHeight) { - goalHeight = camFloorHeight; - c->pos[1] = goalHeight; - } - // Warp camera to goalHeight if further than 1000 and Mario is stuck in the ground - if (sMarioCamState->action == ACT_BUTT_STUCK_IN_GROUND || - sMarioCamState->action == ACT_HEAD_STUCK_IN_GROUND || - sMarioCamState->action == ACT_FEET_STUCK_IN_GROUND) { - if (absf(c->pos[1] - goalHeight) > 1000.0f) { - c->pos[1] = goalHeight; - } - } - -#ifdef FAST_VERTICAL_CAMERA_MOVEMENT - approachRate += ABS(c->pos[1] - goalHeight) / 20; - approach_camera_height(c, goalHeight, approachRate); -#else - approach_camera_height(c, goalHeight, 20.f); -#endif - - if (camCeilHeight != CELL_HEIGHT_LIMIT) { - camCeilHeight -= baseOff; - if ((c->pos[1] > camCeilHeight && sMarioGeometry.currFloorHeight + baseOff < camCeilHeight) - || (sMarioGeometry.currCeilHeight != CELL_HEIGHT_LIMIT - && sMarioGeometry.currCeilHeight > camCeilHeight && c->pos[1] > camCeilHeight)) { - c->pos[1] = camCeilHeight; - } - } - } -} - -/** - * Pitch the camera down when the camera is facing down a slope - */ -s16 look_down_slopes(s16 camYaw) { - struct Surface *floor; - // Default pitch - s16 pitch = 0x05B0; - // x and z offsets towards the camera - f32 xOff = sMarioCamState->pos[0] + sins(camYaw) * 40.f; - f32 zOff = sMarioCamState->pos[2] + coss(camYaw) * 40.f; - - f32 floorDY = find_floor(xOff, sMarioCamState->pos[1], zOff, &floor) - sMarioCamState->pos[1]; - - if (floor != NULL) { - if (floor->type != SURFACE_WALL_MISC && floorDY > 0) { - if (floor->normal.z == 0.f && floorDY < 100.f) { - pitch = 0x05B0; - } else { - // Add the slope's angle of declination to the pitch - pitch += atan2s(40.f, floorDY); - } - } - } - - return pitch; -} - -/** - * Look ahead to the left or right in the direction the player is facing - * The calculation for pan[0] could be simplified to: - * yaw = -yaw; - * pan[0] = sins(sMarioCamState->faceAngle[1] + yaw) * sins(0xC00) * dist; - * Perhaps, early in development, the pan used to be calculated for both the x and z directions - * - * Since this function only affects the camera's focus, Mario's movement direction isn't affected. - */ -void pan_ahead_of_player(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - Vec3f pan = { 0, 0, 0 }; - - // Get distance and angle from camera to Mario. - vec3f_get_dist_and_angle(c->pos, sMarioCamState->pos, &dist, &pitch, &yaw); - - // The camera will pan ahead up to about 30% of the camera's distance to Mario. - pan[2] = sins(0xC00) * dist; - - rotate_in_xz(pan, pan, sMarioCamState->faceAngle[1]); - // rotate in the opposite direction - yaw = -yaw; - rotate_in_xz(pan, pan, yaw); - // Only pan left or right - pan[2] = 0.f; - - // If Mario is long jumping, or on a flag pole (but not at the top), then pan in the opposite direction - if (sMarioCamState->action == ACT_LONG_JUMP || - (sMarioCamState->action != ACT_TOP_OF_POLE && (sMarioCamState->action & ACT_FLAG_ON_POLE))) { - pan[0] = -pan[0]; - } - - // Slowly make the actual pan, sPanDistance, approach the calculated pan - // If Mario is sleeping, then don't pan - if (sStatusFlags & CAM_FLAG_SLEEPING) { - approach_f32_asymptotic_bool(&sPanDistance, 0.f, 0.025f); - } else { - approach_f32_asymptotic_bool(&sPanDistance, pan[0], 0.025f); - } - - // Now apply the pan. It's a dir vector to the left or right, rotated by the camera's yaw to Mario - pan[0] = sPanDistance; - yaw = -yaw; - rotate_in_xz(pan, pan, yaw); - vec3f_add(c->focus, pan); -} - #ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS s16 find_in_bounds_yaw_wdw_bob_thi(Vec3f pos, Vec3f origin, s16 yaw) { switch (gCurrLevelArea) { @@ -843,183 +429,6 @@ s16 find_in_bounds_yaw_wdw_bob_thi(Vec3f pos, Vec3f origin, s16 yaw) { } #endif -/** - * Rotates the camera around the area's center point. - */ -s32 update_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - f32 cenDistX = sMarioCamState->pos[0] - c->areaCenX; - f32 cenDistZ = sMarioCamState->pos[2] - c->areaCenZ; - s16 camYaw = atan2s(cenDistZ, cenDistX) + sModeOffsetYaw; - s16 pitch = look_down_slopes(camYaw); - f32 posY, focusY; - f32 yOff = 125.f; - f32 baseDist = 1000.f; - - sAreaYaw = camYaw - sModeOffsetYaw; - calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); - focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - camYaw = find_in_bounds_yaw_wdw_bob_thi(pos, focus, camYaw); -#endif - return camYaw; -} - -/** - * Update the camera during 8 directional mode - */ -s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - s16 camYaw = s8DirModeBaseYaw + s8DirModeYawOffset; - s16 pitch = look_down_slopes(camYaw); - f32 posY; - f32 focusY; - f32 yOff = 125.f; - f32 baseDist = 1000.f; - - sAreaYaw = camYaw; - calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); - focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); - pan_ahead_of_player(c); -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelArea == AREA_DDD_SUB) { - camYaw = clamp_positions_and_find_yaw(pos, focus, 6839.f, 995.f, 5994.f, -3945.f); - } -#endif - return camYaw; -} - -/** - * Moves the camera for the radial and outward radial camera modes. - * - * If sModeOffsetYaw is 0, the camera points directly at the area center point. - */ -void radial_camera_move(struct Camera *c) { - s16 maxAreaYaw = DEGREES(60); - s16 minAreaYaw = DEGREES(-60); - s16 rotateSpeed = 0x1000; - s16 avoidYaw; - f32 areaDistX = sMarioCamState->pos[0] - c->areaCenX; - f32 areaDistZ = sMarioCamState->pos[2] - c->areaCenZ; - - // How much the camera's yaw changed - s16 yawOffset = calculate_yaw(sMarioCamState->pos, c->pos) - atan2s(areaDistZ, areaDistX); - - if (yawOffset > maxAreaYaw) { - yawOffset = maxAreaYaw; - } - if (yawOffset < minAreaYaw) { - yawOffset = minAreaYaw; - } - - // Check if Mario stepped on a surface that rotates the camera. For example, when Mario enters the - // gate in BoB, the camera turns right to face up the hill path - if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) { - if (sMarioGeometry.currFloorType == SURFACE_CAMERA_MIDDLE - && sMarioGeometry.prevFloorType != SURFACE_CAMERA_MIDDLE) { - gCameraMovementFlags |= (CAM_MOVE_RETURN_TO_MIDDLE | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_RIGHT - && sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_RIGHT) { - gCameraMovementFlags |= (CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_LEFT - && sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_LEFT) { - gCameraMovementFlags |= (CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - } - - if (gCameraMovementFlags & CAM_MOVE_ENTERED_ROTATE_SURFACE) { - rotateSpeed = 0x200; - } - - if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) { - areaDistX = -areaDistX; - areaDistZ = -areaDistZ; - } - - // Avoid obstructing walls - s32 avoidStatus = rotate_camera_around_walls(c, c->pos, &avoidYaw, 0x400); - if (avoidStatus == AVOID_STATUS_WALL_COVERING_MARIO) { - if (avoidYaw - atan2s(areaDistZ, areaDistX) + DEGREES(90) < 0) { - avoidYaw += DEGREES(180); - } - - // We want to change sModeOffsetYaw so that the player is no longer obstructed by the wall. - // So, we make avoidYaw relative to the yaw around the area center - avoidYaw -= atan2s(areaDistZ, areaDistX); - - // Bound avoid yaw to radial mode constraints - if (avoidYaw > DEGREES(105)) { - avoidYaw = DEGREES(105); - } - if (avoidYaw < DEGREES(-105)) { - avoidYaw = DEGREES(-105); - } - } - - if (gCameraMovementFlags & CAM_MOVE_RETURN_TO_MIDDLE) { - if (camera_approach_s16_symmetric_bool(&sModeOffsetYaw, 0, rotateSpeed) == 0) { - gCameraMovementFlags &= ~CAM_MOVE_RETURN_TO_MIDDLE; - } - } else { - // Prevent the player from rotating into obstructing walls - if ((gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) && avoidStatus == 3 - && avoidYaw + 0x10 < sModeOffsetYaw) { - sModeOffsetYaw = avoidYaw; - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - if ((gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) && avoidStatus == 3 - && avoidYaw - 0x10 > sModeOffsetYaw) { - sModeOffsetYaw = avoidYaw; - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - - // If it's the first time rotating, just rotate to +-60 degrees - if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) - && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, maxAreaYaw, rotateSpeed) == 0) { - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) - && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, minAreaYaw, rotateSpeed) == 0) { - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - } - - // If it's the second time rotating, rotate all the way to +-105 degrees. - if ((s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) - && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(105), rotateSpeed) == 0) { - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - s2ndRotateFlags &= ~CAM_MOVE_ROTATE_RIGHT; - } - if ((s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) - && camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(-105), rotateSpeed) == 0) { - gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE); - s2ndRotateFlags &= ~CAM_MOVE_ROTATE_LEFT; - } - } - if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) { - // If not rotating, rotate away from walls obscuring Mario from view - if (avoidStatus == 3) { - approach_s16_asymptotic_bool(&sModeOffsetYaw, avoidYaw, 10); - } else { - if (c->mode == CAMERA_MODE_RADIAL) { - // sModeOffsetYaw only updates when Mario is moving - rotateSpeed = gMarioStates[0].forwardVel / 32.f * 128.f; - camera_approach_s16_symmetric_bool(&sModeOffsetYaw, yawOffset, rotateSpeed); - } - if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) { - sModeOffsetYaw = offset_yaw_outward_radial(c, atan2s(areaDistZ, areaDistX)); - } - } - } - - // Bound sModeOffsetYaw within (-120, 120) degrees - if (sModeOffsetYaw > 0x5554) { - sModeOffsetYaw = 0x5554; - } - if (sModeOffsetYaw < -0x5554) { - sModeOffsetYaw = -0x5554; - } -} - /** * Moves Lakitu from zoomed in to zoomed out and vice versa. * When C-Down mode is not active, sLakituDist and sLakituPitch decrease to 0. @@ -1058,49 +467,6 @@ void lakitu_zoom(f32 rangeDist, s16 rangePitch) { } } -void radial_camera_input_default(struct Camera *c) { - radial_camera_input(c); -} - -/** - * Makes Lakitu cam's yaw match the angle turned towards in C-Up mode, and makes Lakitu slowly fly back - * to the distance he was at before C-Up - */ -void update_yaw_and_dist_from_c_up(UNUSED struct Camera *c) { - sModeOffsetYaw = sModeInfo.transitionStart.yaw - sAreaYaw; - sLakituDist = sModeInfo.transitionStart.dist - 1000.0f; - // No longer in C-Up - gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; -} - -/** - * Handles input and updates for the radial camera mode - */ -void mode_radial_camera(struct Camera *c) { - Vec3f pos; - s16 oldAreaYaw = sAreaYaw; - - if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { - update_yaw_and_dist_from_c_up(c); - } - - radial_camera_input_default(c); - radial_camera_move(c); - - if (c->mode == CAMERA_MODE_RADIAL) { - lakitu_zoom(400.f, 0x900); - } - c->nextYaw = update_radial_camera(c, c->focus, pos); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; - sAreaYawChange = sAreaYaw - oldAreaYaw; - if (sMarioCamState->action == ACT_RIDING_HOOT) { - pos[1] += 500.f; - } - set_camera_height(c, pos[1]); - pan_ahead_of_player(c); -} - s32 snap_to_45_degrees(s16 angle) { if (angle % DEGREES(45)) { s16 d1 = ABS(angle) % DEGREES(45); @@ -1116,9277 +482,1575 @@ s32 snap_to_45_degrees(s16 angle) { return angle; } -/** - * A mode that only has 8 camera angles, 45 degrees apart - */ -void mode_8_directions_camera(struct Camera *c) { - Vec3f pos; - s16 oldAreaYaw = sAreaYaw; +s32 unused_update_mode_5_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) { + return 0; +} - radial_camera_input(c); +s32 nop_update_water_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) { + return 0; +} - if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { - s8DirModeYawOffset += DEGREES(45); - play_sound_cbutton_side(); - } - if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { - s8DirModeYawOffset -= DEGREES(45); - play_sound_cbutton_side(); - } -#ifdef PARALLEL_LAKITU_CAM - // extra functionality - else if (gPlayer1Controller->buttonPressed & U_JPAD) { - s8DirModeYawOffset = 0; - s8DirModeYawOffset = gMarioState->faceAngle[1] - 0x8000; - } - else if (gPlayer1Controller->buttonDown & L_JPAD) { - s8DirModeYawOffset -= DEGREES(2); - } - else if (gPlayer1Controller->buttonDown & R_JPAD) { - s8DirModeYawOffset += DEGREES(2); - } - else if (gPlayer1Controller->buttonPressed & D_JPAD) { - s8DirModeYawOffset = snap_to_45_degrees(s8DirModeYawOffset); - } -#endif +s32 update_slide_or_0f_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { + s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); - lakitu_zoom(400.f, 0x900); - c->nextYaw = update_8_directions_camera(c, c->focus, pos); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; - sAreaYawChange = sAreaYaw - oldAreaYaw; - set_camera_height(c, pos[1]); + focus_on_mario(focus, pos, 125.f, 125.f, 800.f, DEGREES(30), yaw); + return sMarioCamState->faceAngle[1]; } -/** - * Updates the camera in outward radial mode. - * sModeOffsetYaw is calculated in radial_camera_move, which calls offset_yaw_outward_radial - */ -s32 update_outward_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - f32 xDistFocToMario = sMarioCamState->pos[0] - c->areaCenX; - f32 zDistFocToMario = sMarioCamState->pos[2] - c->areaCenZ; - s16 camYaw = atan2s(zDistFocToMario, xDistFocToMario) + sModeOffsetYaw + DEGREES(180); - s16 pitch = look_down_slopes(camYaw); - f32 baseDist = 1000.f; - // A base offset of 125.f is ~= Mario's eye height - f32 yOff = 125.f; - f32 posY; - f32 focusY; - - sAreaYaw = camYaw - sModeOffsetYaw - DEGREES(180); - calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f); - focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw); - - return camYaw; + +void store_lakitu_cam_info_for_c_up(struct Camera *c) { + vec3f_copy(sCameraStoreCUp.pos, c->pos); + vec3f_sub(sCameraStoreCUp.pos, sMarioCamState->pos); + // Only store the y value, and as an offset from Mario, for some reason + vec3f_set(sCameraStoreCUp.focus, 0.f, c->focus[1] - sMarioCamState->pos[1], 0.f); } /** - * Input and updates for the outward radial mode. + * Start C-Up mode. The actual mode change is handled in update_mario_inputs() in mario.c + * + * @see update_mario_inputs */ -void mode_outward_radial_camera(struct Camera *c) { - Vec3f pos; - s16 oldAreaYaw = sAreaYaw; - - if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { - update_yaw_and_dist_from_c_up(c); - } - radial_camera_input_default(c); - radial_camera_move(c); - lakitu_zoom(400.f, 0x900); - c->nextYaw = update_outward_radial_camera(c, c->focus, pos); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; - sAreaYawChange = sAreaYaw - oldAreaYaw; - if (sMarioCamState->action == ACT_RIDING_HOOT) { - pos[1] += 500.f; +void set_mode_c_up(struct Camera *c) { + if (!(gCameraMovementFlags & CAM_MOVE_C_UP_MODE)) { + gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; + store_lakitu_cam_info_for_c_up(c); + sCameraSoundFlags &= ~CAM_SOUND_C_UP_PLAYED; } - set_camera_height(c, pos[1]); - pan_ahead_of_player(c); } /** - * Move the camera in parallel tracking mode + * Cause Lakitu to fly to the next Camera position and focus over a number of frames. * - * Uses the line between the next two points in sParTrackPath - * The camera can move forward/back and side to side, but it will face perpendicular to that line + * At the end of each frame, Lakitu's position and focus ("state") are stored. + * Calling this function makes next_lakitu_state() fly from the last frame's state to the + * current frame's calculated state. * - * Although, annoyingly, it's not truly parallel, the function returns the yaw from the camera to Mario, - * so Mario will run slightly towards the camera. + * @see next_lakitu_state() */ -s32 update_parallel_tracking_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - Vec3f path[2]; - Vec3f parMidPoint; - Vec3f marioOffset; - Vec3f camOffset; - /// Adjusts the focus to look where Mario is facing. Unused since marioOffset is copied to focus - Vec3f focOffset = { 0 }; - s16 pathPitch; - s16 pathYaw; - f32 camParDist; - f32 pathLength; - f32 parScale = 0.5f; - f32 marioFloorDist; - Vec3f marioPos; - Vec3s pathAngle; - // Variables for changing to the next/prev path in the list - Vec3f oldPos; - Vec3f prevPathPos; - Vec3f nextPathPos; - f32 distToNext, distToPrev; - s16 prevPitch, nextPitch; - s16 prevYaw, nextYaw; - - // Store camera pos, for changing between paths - vec3f_copy(oldPos, pos); - - vec3f_copy(path[0], sParTrackPath[sParTrackIndex].pos); - vec3f_copy(path[1], sParTrackPath[sParTrackIndex + 1].pos); - - f32 distThresh = sParTrackPath[sParTrackIndex].distThresh; - f32 zoom = sParTrackPath[sParTrackIndex].zoom; - calc_y_to_curr_floor(&marioFloorDist, 1.f, 200.f, &marioFloorDist, 0.9f, 200.f); - - marioPos[0] = sMarioCamState->pos[0]; - // Mario's y pos + ~Mario's height + Mario's height above the floor - marioPos[1] = sMarioCamState->pos[1] + 150.f + marioFloorDist; - marioPos[2] = sMarioCamState->pos[2]; - - // Calculate middle of the path (parScale is 0.5f) - parMidPoint[0] = path[0][0] + (path[1][0] - path[0][0]) * parScale; - parMidPoint[1] = path[0][1] + (path[1][1] - path[0][1]) * parScale; - parMidPoint[2] = path[0][2] + (path[1][2] - path[0][2]) * parScale; - - // Get direction of path - vec3f_get_dist_and_angle(path[0], path[1], &pathLength, &pathPitch, &pathYaw); - - marioOffset[0] = marioPos[0] - parMidPoint[0]; - marioOffset[1] = marioPos[1] - parMidPoint[1]; - marioOffset[2] = marioPos[2] - parMidPoint[2]; - - // Make marioOffset point from the midpoint -> the start of the path - // Rotating by -yaw then -pitch moves the hor dist from the midpoint into marioOffset's z coordinate - // marioOffset[0] = the (perpendicular) horizontal distance from the path - // marioOffset[1] = the vertical distance from the path - // marioOffset[2] = the (parallel) horizontal distance from the path's midpoint - pathYaw = -pathYaw; - rotate_in_xz(marioOffset, marioOffset, pathYaw); - pathYaw = -pathYaw; - pathPitch = -pathPitch; - rotate_in_yz(marioOffset, marioOffset, pathPitch); - pathPitch = -pathPitch; - focOffset[2] = marioOffset[2]; - - // Repeat above calcs with camOffset - camOffset[0] = pos[0] - parMidPoint[0]; - camOffset[1] = pos[1] - parMidPoint[1]; - camOffset[2] = pos[2] - parMidPoint[2]; - pathYaw = -pathYaw; - rotate_in_xz(camOffset, camOffset, pathYaw); - pathYaw = -pathYaw; - pathPitch = -pathPitch; - rotate_in_yz(camOffset, camOffset, pathPitch); - pathPitch = -pathPitch; - - // If Mario is distThresh units away from the camera along the path, move the camera - //! When distThresh != 0, it causes Mario to move slightly towards the camera when running sideways - //! Set each ParallelTrackingPoint's distThresh to 0 to make Mario truly run parallel to the path - if (marioOffset[2] > camOffset[2]) { - if (marioOffset[2] - camOffset[2] > distThresh) { - camOffset[2] = marioOffset[2] - distThresh; - } - } else { - if (marioOffset[2] - camOffset[2] < -distThresh) { - camOffset[2] = marioOffset[2] + distThresh; - } +void transition_next_state(UNUSED struct Camera *c, s16 frames) { + if (!(sStatusFlags & CAM_FLAG_FRAME_AFTER_CAM_INIT)) { + sStatusFlags |= (CAM_FLAG_START_TRANSITION | CAM_FLAG_TRANSITION_OUT_OF_C_UP); + sModeTransition.framesLeft = frames; } +} - // If zoom != 0.0, the camera will move zoom% closer to Mario - marioOffset[0] = -marioOffset[0] * zoom; - marioOffset[1] = marioOffset[1] * zoom; - marioOffset[2] = camOffset[2]; - - // make marioOffset[2] == distance from the start of the path - marioOffset[2] = pathLength / 2 - marioOffset[2]; - - pathAngle[0] = pathPitch; - pathAngle[1] = pathYaw + DEGREES(180); - pathAngle[2] = 0; - - // Rotate the offset in the direction of the path again - offset_rotated(pos, path[0], marioOffset, pathAngle); - vec3f_get_dist_and_angle(path[0], c->pos, &camParDist, &pathPitch, &pathYaw); - - // Adjust the focus. Does nothing, focus is set to Mario at the end - focOffset[2] = pathLength / 2 - focOffset[2]; - offset_rotated(c->focus, path[0], focOffset, pathAngle); - - // Changing paths, update the stored position offset - if (sStatusFlags & CAM_FLAG_CHANGED_PARTRACK_INDEX) { - sStatusFlags &= ~CAM_FLAG_CHANGED_PARTRACK_INDEX; - sParTrackTransOff.pos[0] = oldPos[0] - c->pos[0]; - sParTrackTransOff.pos[1] = oldPos[1] - c->pos[1]; - sParTrackTransOff.pos[2] = oldPos[2] - c->pos[2]; - } - // Slowly transition to the next path - approach_f32_asymptotic_bool(&sParTrackTransOff.pos[0], 0.f, 0.025f); - approach_f32_asymptotic_bool(&sParTrackTransOff.pos[1], 0.f, 0.025f); - approach_f32_asymptotic_bool(&sParTrackTransOff.pos[2], 0.f, 0.025f); - vec3f_add(c->pos, sParTrackTransOff.pos); - - // Check if the camera should go to the next path - if (sParTrackPath[sParTrackIndex + 1].startOfPath != 0) { - // get Mario's distance to the next path - calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex + 2].pos, &nextPitch, &nextYaw); - vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, nextPathPos, 400.f, nextPitch, nextYaw); - distToPrev = calc_abs_dist_squared(marioPos, nextPathPos); - - // get Mario's distance to the previous path - calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex].pos, &prevPitch, &prevYaw); - vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, prevPathPos, 400.f, prevPitch, prevYaw); - distToNext = calc_abs_dist_squared(marioPos, prevPathPos); - if (distToPrev < distToNext) { - sParTrackIndex++; - sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX; - } - } +/** + * Sets the camera mode to `newMode` and initializes sModeTransition with `numFrames` frames + * + * Used to change the camera mode to 'level-oriented' modes + * namely: RADIAL/OUTWARD_RADIAL, 8_DIRECTIONS, FREE_ROAM, CLOSE, SPIRAL_STAIRS, and SLIDE_HOOT + */ +void transition_to_camera_mode(struct Camera *c, s16 newMode, s16 numFrames) { + if (c->mode != newMode) { + sModeInfo.newMode = (newMode != -1) ? newMode : sModeInfo.lastMode; + sModeInfo.lastMode = c->mode; + c->mode = sModeInfo.newMode; - // Check if the camera should go to the previous path - if (sParTrackIndex != 0) { - // get Mario's distance to the next path - calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex + 1)).pos, &nextPitch, &nextYaw); - vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, nextPathPos, 700.f, nextPitch, nextYaw); - distToPrev = calc_abs_dist_squared(marioPos, nextPathPos); - - // get Mario's distance to the previous path - calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex - 1)).pos, &prevPitch, &prevYaw); - vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, prevPathPos, 700.f, prevPitch, prevYaw); - distToNext = calc_abs_dist_squared(marioPos, prevPathPos); - if (distToPrev > distToNext) { - sParTrackIndex--; - sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX; + // Clear movement flags that would affect the transition + gCameraMovementFlags &= (u16)~(CAM_MOVE_RESTRICT | CAM_MOVE_ROTATE); + if (!(sStatusFlags & CAM_FLAG_FRAME_AFTER_CAM_INIT)) { + transition_next_state(c, numFrames); + sCUpCameraPitch = 0; + sModeOffsetYaw = 0; + sLakituDist = 0; + sLakituPitch = 0; + sAreaYawChange = 0; + sPanDistance = 0.f; + sCannonYOffset = 0.f; } } - - // Update the camera focus and return the camera's yaw - vec3f_copy(focus, marioPos); - vec3f_get_dist_and_angle(focus, pos, &camParDist, &pathPitch, &pathYaw); - return pathYaw; } /** - * Updates the camera during fixed mode. + * Used to change the camera mode between its default/previous and certain Mario-oriented modes, + * namely: C_UP, WATER_SURFACE, CLOSE, and BEHIND_MARIO + * + * Stores the current pos and focus in sModeInfo->transitionStart, and + * stores the next pos and focus into sModeInfo->transitionEnd. These two fields are used in + * move_into_c_up(). + * + * @param mode the mode to change to, or -1 to switch to the previous mode + * @param frames number of frames the transition should last, only used when entering C_UP */ -s32 update_fixed_camera(struct Camera *c, Vec3f focus, UNUSED Vec3f pos) { - f32 focusFloorOff; - f32 goalHeight; - f32 ceilHeight; - f32 heightOffset; - f32 distCamToFocus; - f32 scaleToMario = 0.5f; - s16 pitch; - s16 yaw; - Vec3s faceAngle; - struct Surface *ceiling; - Vec3f basePos; - - play_camera_buzz_if_c_sideways(); - - // Don't move closer to Mario in these areas - switch (gCurrLevelArea) { - case AREA_RR: - scaleToMario = 0.f; - heightOffset = 0.f; - break; +void set_camera_mode(struct Camera *c, s16 mode, s16 frames) { + struct LinearTransitionPoint *start = &sModeInfo.transitionStart; + struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; - case AREA_CASTLE_LOBBY: - scaleToMario = 0.3f; - heightOffset = 0.f; - break; +#ifdef ENABLE_VANILLA_CAM_PROCESSING + if (mode == CAMERA_MODE_WATER_SURFACE && gCurrLevelArea == AREA_TTM_OUTSIDE) { + } else { +#endif + // Clear movement flags that would affect the transition + gCameraMovementFlags &= (u16)~(CAM_MOVE_RESTRICT | CAM_MOVE_ROTATE); + gCameraMovementFlags |= CAM_MOVING_INTO_MODE; + if (mode == CAMERA_MODE_NONE) { + mode = CAMERA_MODE_CLOSE; + } + sCUpCameraPitch = 0; + sModeOffsetYaw = 0; + sLakituDist = 0; + sLakituPitch = 0; + sAreaYawChange = 0; - case AREA_BBH: - scaleToMario = 0.f; - heightOffset = 0.f; - break; - } + sModeInfo.newMode = (mode != -1) ? mode : sModeInfo.lastMode; + sModeInfo.lastMode = c->mode; + sModeInfo.max = frames; + sModeInfo.frame = 1; - handle_c_button_movement(c); - play_camera_buzz_if_cdown(); + c->mode = sModeInfo.newMode; + gLakituState.mode = c->mode; - calc_y_to_curr_floor(&focusFloorOff, 1.f, 200.f, &focusFloorOff, 0.9f, 200.f); - vec3f_copy(focus, sMarioCamState->pos); - focus[1] += focusFloorOff + 125.f; - vec3f_get_dist_and_angle(focus, c->pos, &distCamToFocus, &faceAngle[0], &faceAngle[1]); - faceAngle[2] = 0; + vec3f_copy(end->focus, c->focus); + vec3f_sub(end->focus, sMarioCamState->pos); - vec3f_copy(basePos, sFixedModeBasePosition); - vec3f_add(basePos, sCastleEntranceOffset); + vec3f_copy(end->pos, c->pos); + vec3f_sub(end->pos, sMarioCamState->pos); - if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE - && sMarioGeometry.currFloorHeight != FLOOR_LOWER_LIMIT) { - goalHeight = sMarioGeometry.currFloorHeight + basePos[1] + heightOffset; - } else { - goalHeight = gLakituState.goalPos[1]; - } +#ifndef ENABLE_VANILLA_CAM_PROCESSING + if (mode == CAMERA_MODE_8_DIRECTIONS) { + // Helps transition from any camera mode to 8dir + s8DirModeYawOffset = snap_to_45_degrees(c->yaw); + } +#endif - if (300 > distCamToFocus) { - goalHeight += 300 - distCamToFocus; - } + sAreaYaw = sModeTransitions[sModeInfo.newMode](c, end->focus, end->pos); - ceilHeight = find_ceil(c->pos[0], goalHeight - 100.f, c->pos[2], &ceiling); - if (ceilHeight != CELL_HEIGHT_LIMIT) { - if (goalHeight > (ceilHeight -= 125.f)) { - goalHeight = ceilHeight; - } - } + // End was updated by sModeTransitions + vec3f_sub(end->focus, sMarioCamState->pos); + vec3f_sub(end->pos, sMarioCamState->pos); - if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { - camera_approach_f32_symmetric_bool(&c->pos[1], goalHeight, 15.f); - } else { - if (goalHeight < sMarioCamState->pos[1] - 500.f) { - goalHeight = sMarioCamState->pos[1] - 500.f; - } - c->pos[1] = goalHeight; - } + vec3f_copy(start->focus, gLakituState.curFocus); + vec3f_sub(start->focus, sMarioCamState->pos); - c->pos[0] = basePos[0] + (sMarioCamState->pos[0] - basePos[0]) * scaleToMario; - c->pos[2] = basePos[2] + (sMarioCamState->pos[2] - basePos[2]) * scaleToMario; + vec3f_copy(start->pos, gLakituState.curPos); + vec3f_sub(start->pos, sMarioCamState->pos); - if (scaleToMario != 0.f) { - vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &pitch, &yaw); - if (distCamToFocus > 1000.f) { - distCamToFocus = 1000.f; - vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, pitch, yaw); - } + vec3f_get_dist_and_angle(start->focus, start->pos, &start->dist, &start->pitch, &start->yaw); + vec3f_get_dist_and_angle(end->focus, end->pos, &end->dist, &end->pitch, &end->yaw); +#ifdef ENABLE_VANILLA_CAM_PROCESSING } - - return faceAngle[1]; +#endif } /** - * Updates the camera during a boss fight + * Updates Lakitu's position/focus and applies camera shakes. */ -s32 update_boss_fight_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - struct Object *obj; - f32 focusDistance; - // Floor normal values - f32 nx; - f32 ny; - f32 nz; - /// Floor originOffset - f32 oo; - s16 yaw; - s16 heldState; - struct Surface *floor; - Vec3f secondFocus; - Vec3f holdFocOffset = { 0.f, -150.f, -125.f }; - - handle_c_button_movement(c); +void update_lakitu(struct Camera *c) { + struct Surface *floor = NULL; + Vec3f newPos; + Vec3f newFoc; + f32 distToFloor; + s16 newYaw; - // Start camera shakes if bowser jumps or gets thrown. - if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_JUMP) { - set_environmental_camera_shake(SHAKE_ENV_BOWSER_JUMP); - sMarioCamState->cameraEvent = CAM_EVENT_NONE; - } - if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_THROW_BOUNCE) { - set_environmental_camera_shake(SHAKE_ENV_BOWSER_THROW_BOUNCE); - sMarioCamState->cameraEvent = CAM_EVENT_NONE; - } + if (!(gCameraMovementFlags & CAM_MOVE_PAUSE_SCREEN)) { + newYaw = next_lakitu_state(newPos, newFoc, c->pos, c->focus, sOldPosition, sOldFocus, + c->nextYaw); + set_or_approach_s16_symmetric(&c->yaw, newYaw, sYawSpeed); + sStatusFlags &= ~CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - yaw = sModeOffsetYaw + DEGREES(45); - // Get boss's position and whether Mario is holding it. - if ((obj = gSecondCameraFocus) != NULL) { - vec3f_copy(secondFocus, &obj->oPosVec); - heldState = obj->oHeldState; - } else { - // If no boss is there, just rotate around the area's center point. - secondFocus[0] = c->areaCenX; - secondFocus[1] = sMarioCamState->pos[1]; - secondFocus[2] = c->areaCenZ; - heldState = 0; - } + // Update old state + vec3f_copy(sOldPosition, newPos); + vec3f_copy(sOldFocus, newFoc); - focusDistance = calc_abs_dist(sMarioCamState->pos, secondFocus) * 1.6f; - if (focusDistance < 800.f) { - focusDistance = 800.f; - } - if (focusDistance > 5000.f) { - focusDistance = 5000.f; - } + gLakituState.yaw = c->yaw; + gLakituState.nextYaw = c->nextYaw; + vec3f_copy(gLakituState.goalPos, c->pos); + vec3f_copy(gLakituState.goalFocus, c->focus); - // If holding the boss, add a slight offset to secondFocus so that the spinning is more pronounced. - if (heldState == HELD_HELD) { - offset_rotated(secondFocus, sMarioCamState->pos, holdFocOffset, sMarioCamState->faceAngle); - } + // Simulate Lakitu flying to the new position and turning towards the new focus + set_or_approach_vec3f_asymptotic(gLakituState.curPos, newPos, + gLakituState.posHSpeed, gLakituState.posVSpeed, + gLakituState.posHSpeed); + set_or_approach_vec3f_asymptotic(gLakituState.curFocus, newFoc, + gLakituState.focHSpeed, gLakituState.focVSpeed, + gLakituState.focHSpeed); + // Adjust Lakitu's speed back to normal + set_or_approach_f32_asymptotic(&gLakituState.focHSpeed, 0.8f, 0.05f); + set_or_approach_f32_asymptotic(&gLakituState.focVSpeed, 0.3f, 0.05f); + set_or_approach_f32_asymptotic(&gLakituState.posHSpeed, 0.3f, 0.05f); + set_or_approach_f32_asymptotic(&gLakituState.posVSpeed, 0.3f, 0.05f); - // Set the camera focus to the average of Mario and secondFocus - focus[0] = (sMarioCamState->pos[0] + secondFocus[0]) / 2.f; - focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 125.f; - focus[2] = (sMarioCamState->pos[2] + secondFocus[2]) / 2.f; - - // Calculate the camera's position as an offset from the focus - // When C-Down is not active, this - vec3f_set_dist_and_angle(focus, pos, focusDistance, 0x1000, yaw); - // Find the floor of the arena - pos[1] = find_floor(c->areaCenX, CELL_HEIGHT_LIMIT, c->areaCenZ, &floor); - if (floor != NULL) { - nx = floor->normal.x; - ny = floor->normal.y; - nz = floor->normal.z; - oo = floor->originOffset; - pos[1] = 300.f - (nx * pos[0] + nz * pos[2] + oo) / ny; -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - switch (gCurrLevelArea) { - case AREA_BOB: - pos[1] += 125.f; - //! fall through, makes the BoB boss fight camera move up twice as high as it should - FALL_THROUGH; - case AREA_WF: - pos[1] += 125.f; - } -#endif - } -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - // Prevent the camera from going to the ground in the outside boss fight - if (gCurrLevelNum == LEVEL_BBH) { - pos[1] = 2047.f; - } -#endif - // Rotate from C-Button input - if (sCSideButtonYaw < 0) { - sModeOffsetYaw += 0x200; - if ((sCSideButtonYaw += 0x100) > 0) { - sCSideButtonYaw = 0; - } - } - if (sCSideButtonYaw > 0) { - sModeOffsetYaw -= 0x200; - if ((sCSideButtonYaw -= 0x100) < 0) { - sCSideButtonYaw = 0; + // Turn on smooth movement when it hasn't been blocked for 2 frames + if (sStatusFlags & CAM_FLAG_BLOCK_SMOOTH_MOVEMENT) { + sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; + } else { + sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; } - } - - focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 100.f; - if (heldState == 1) { - focus[1] += 300.f * sins((gMarioStates[0].angleVel[1] > 0.f) ? gMarioStates[0].angleVel[1] - : -gMarioStates[0].angleVel[1]); - } - - //! Unnecessary conditional, focusDistance is already bounded to 800 - // if (focusDistance < 400.f) { - // focusDistance = 400.f; - // } - // Set C-Down distance and pitch. - // C-Down will essentially double the distance from the center. - // sLakituPitch approaches 33.75 degrees. - lakitu_zoom(focusDistance, 0x1800); + vec3f_copy(gLakituState.pos, gLakituState.curPos); + vec3f_copy(gLakituState.focus, gLakituState.curFocus); - // Move the camera position back as sLakituDist and sLakituPitch increase. - // This doesn't zoom out of bounds because pos is set above each frame. - // The constant 0x1000 doubles the pitch from the center when sLakituPitch is 0 - // When Lakitu is fully zoomed out, the pitch comes to 0x3800, or 78.75 degrees, up from the focus. - vec3f_set_dist_and_angle(pos, pos, sLakituDist, sLakituPitch + 0x1000, yaw); + if (c->cutscene) { + vec3f_add(gLakituState.focus, sPlayer2FocusOffset); + vec3_zero(sPlayer2FocusOffset); + } - return yaw; -} + vec3f_get_dist_and_angle(gLakituState.pos, gLakituState.focus, &gLakituState.focusDistance, + &gLakituState.oldPitch, &gLakituState.oldYaw); -/** - * Maps cutscene to numbers in [0,4]. Used in determine_dance_cutscene() with sDanceCutsceneIndexTable. - * - * Only the first 5 entries are used. Perhaps the last 5 were bools used to indicate whether the star - * type exits the course or not. - */ -u8 sDanceCutsceneTable[] = { - CUTSCENE_DANCE_FLY_AWAY, CUTSCENE_DANCE_ROTATE, CUTSCENE_DANCE_CLOSEUP, CUTSCENE_KEY_DANCE, CUTSCENE_DANCE_DEFAULT, - CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, -}; + gLakituState.roll = 0; -/** - * Table that dictates camera movement in bookend room. - * Due to only the X being varied in the table, this only moves along the X axis linearly. - * Third entry is seemingly unused. - */ -struct ParallelTrackingPoint sBBHLibraryParTrackPath[] = { - { 1, { -929.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f }, - { 0, { -2118.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f }, - { 0, { 0.0f, 0.0f, 0.0f }, 0.0f, 0.0f }, -}; + // Apply camera shakes + shake_camera_pitch(gLakituState.pos, gLakituState.focus); + shake_camera_yaw(gLakituState.pos, gLakituState.focus); + shake_camera_roll(&gLakituState.roll); + shake_camera_handheld(gLakituState.pos, gLakituState.focus); -s32 unused_update_mode_5_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) { - return 0; -} + if (sMarioCamState->action == ACT_DIVE && gLakituState.lastFrameAction != ACT_DIVE) { + set_camera_shake_from_hit(SHAKE_HIT_FROM_BELOW); + } -void mode_boss_fight_camera(struct Camera *c) { - c->nextYaw = update_boss_fight_camera(c, c->focus, c->pos); -} + gLakituState.roll += sHandheldShakeRoll; + gLakituState.roll += gLakituState.keyDanceRoll; -/** - * Parallel tracking mode, the camera faces perpendicular to a line defined by sParTrackPath - * - * @see update_parallel_tracking_camera - */ -void mode_parallel_tracking_camera(struct Camera *c) { - radial_camera_input(c); - set_fov_function(CAM_FOV_DEFAULT); - c->nextYaw = update_parallel_tracking_camera(c, c->focus, c->pos); -} + if (c->mode != CAMERA_MODE_C_UP && c->cutscene == CUTSCENE_NONE) { + gCollisionFlags |= COLLISION_FLAG_CAMERA; + distToFloor = find_floor(gLakituState.pos[0], + gLakituState.pos[1] + 20.0f, + gLakituState.pos[2], &floor); + if (distToFloor != FLOOR_LOWER_LIMIT) { + if (gLakituState.pos[1] < (distToFloor += 100.0f)) { + gLakituState.pos[1] = distToFloor; + } else { + gCollisionFlags &= ~COLLISION_FLAG_CAMERA; + } + } + } -/** - * Fixed camera mode, the camera rotates around a point and looks and zooms toward Mario. - */ -void mode_fixed_camera(struct Camera *c) { -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelNum == LEVEL_BBH) { - set_fov_function(CAM_FOV_BBH); - } else { - set_fov_function(CAM_FOV_APP_45); + vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); } -#else - set_fov_function(CAM_FOV_APP_45); -#endif - c->nextYaw = update_fixed_camera(c, c->focus, c->pos); - c->yaw = c->nextYaw; - pan_ahead_of_player(c); - vec3_zero(sCastleEntranceOffset); + clamp_pitch(gLakituState.pos, gLakituState.focus, 0x3E00, -0x3E00); + gLakituState.mode = c->mode; + gLakituState.defMode = c->defMode; } /** - * Updates the camera in BEHIND_MARIO mode. - * - * The C-Buttons rotate the camera 90 degrees left/right and 67.5 degrees up/down. + * Reset all the camera variables to their arcane defaults */ -s32 update_behind_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - f32 dist; - s16 absPitch; - s16 pitch; - s16 yaw; - s16 goalPitch = -sMarioCamState->faceAngle[0]; - s16 marioYaw = sMarioCamState->faceAngle[1] + DEGREES(180); - s16 goalYawOff = 0; - s16 yawSpeed; - s16 pitchInc = 32; - f32 maxDist = 800.f; - f32 focYOff = 125.f; - - // Zoom in when Mario R_TRIG mode is active - if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { - maxDist = 350.f; - focYOff = 120.f; - } - if (!(sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))) { - pitchInc = 128; - } - - // Focus on Mario - vec3f_copy(focus, sMarioCamState->pos); - c->focus[1] += focYOff; - vec3f_get_dist_and_angle(focus, pos, &dist, &pitch, &yaw); - if (dist > maxDist) { - dist = maxDist; - } - if ((absPitch = pitch) < 0) { - absPitch = -absPitch; - } - - // Determine the yaw speed based on absPitch. A higher absPitch (further away from looking straight) - // translates to a slower speed - // Note: Pitch is always within +- 90 degrees or +-0x4000, and 0x4000 / 0x200 = 32 - yawSpeed = 32 - absPitch / 0x200; - if (yawSpeed < 1) { - yawSpeed = 1; - } - if (yawSpeed > 32) { - yawSpeed = 32; - } - - if (sCSideButtonYaw != 0) { - camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 1); - yawSpeed = 8; - } - if (sBehindMarioSoundTimer != 0) { - goalPitch = 0; - camera_approach_s16_symmetric_bool(&sBehindMarioSoundTimer, 0, 1); - pitchInc = 0x800; - } - - if (sBehindMarioSoundTimer == 28) { - if (sCSideButtonYaw < 5 || sCSideButtonYaw > 28) { - play_sound_cbutton_up(); - } - } - if (sCSideButtonYaw == 28) { - if (sBehindMarioSoundTimer < 5 || sBehindMarioSoundTimer > 28) { - play_sound_cbutton_up(); - } - } - - // C-Button input. Note: Camera rotates in the opposite direction of the button (airplane controls) - //! @bug C-Right and C-Up take precedence due to the way input is handled here - - // Rotate right - if (sCButtonsPressed & L_CBUTTONS) { - if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { - play_sound_cbutton_side(); - } - if (dist < maxDist) { - camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); - } - goalYawOff = -0x3FF8; - sCSideButtonYaw = 30; - yawSpeed = 2; - } - // Rotate left - if (sCButtonsPressed & R_CBUTTONS) { - if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { - play_sound_cbutton_side(); - } - if (dist < maxDist) { - camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); - } - goalYawOff = 0x3FF8; - sCSideButtonYaw = 30; - yawSpeed = 2; - } - // Rotate up - if (sCButtonsPressed & D_CBUTTONS) { - if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) { - play_sound_cbutton_side(); - } - if (dist < maxDist) { - camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); - } - goalPitch = -0x3000; - sBehindMarioSoundTimer = 30; - pitchInc = 0x800; - } - // Rotate down - if (sCButtonsPressed & U_CBUTTONS) { - if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) { - play_sound_cbutton_side(); - } - if (dist < maxDist) { - camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f); - } - goalPitch = 0x3000; - sBehindMarioSoundTimer = 30; - pitchInc = 0x800; - } - - approach_s16_asymptotic_bool(&yaw, marioYaw + goalYawOff, yawSpeed); - camera_approach_s16_symmetric_bool(&pitch, goalPitch, pitchInc); - if (dist < 300.f) { - dist = 300.f; - } - vec3f_set_dist_and_angle(focus, pos, dist, pitch, yaw); -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelArea == AREA_WDW_MAIN) { - yaw = clamp_positions_and_find_yaw(pos, focus, 4508.f, -3739.f, 4508.f, -3739.f); - } - if (gCurrLevelArea == AREA_THI_HUGE) { - yaw = clamp_positions_and_find_yaw(pos, focus, 8192.f, -8192.f, 8192.f, -8192.f); - } - if (gCurrLevelArea == AREA_THI_TINY) { - yaw = clamp_positions_and_find_yaw(pos, focus, 2458.f, -2458.f, 2458.f, -2458.f); - } -#endif - - return yaw; -} - -/** - * "Behind Mario" mode: used when Mario is flying, on the water's surface, or shot from a cannon - */ -s32 mode_behind_mario(struct Camera *c) { - struct MarioState *marioState = &gMarioStates[0]; - struct Surface *floor; - Vec3f newPos; - f32 waterHeight; - f32 floorHeight; - f32 distCamToFocus; - s16 camPitch; - s16 camYaw; - s16 yaw; - - gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; - vec3f_copy(newPos, c->pos); - yaw = update_behind_mario_camera(c, c->focus, newPos); - c->pos[0] = newPos[0]; - c->pos[2] = newPos[2]; - - // Keep the camera above the water surface if swimming - if (c->mode == WATER_SURFACE_CAMERA_MODE) { - floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); - newPos[1] = marioState->waterLevel + 120; - if (newPos[1] < (floorHeight += 120.f)) { - newPos[1] = floorHeight; - } - } - approach_camera_height(c, newPos[1], 50.f); - waterHeight = find_water_level(c->pos[0], c->pos[2]) + 100.f; - if (c->pos[1] <= waterHeight) { - gCameraMovementFlags |= CAM_MOVE_SUBMERGED; - } else { - gCameraMovementFlags &= ~CAM_MOVE_SUBMERGED; - } - - resolve_geometry_collisions(c->pos); - // Prevent camera getting too far away - vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); - if (distCamToFocus > 800.f) { - distCamToFocus = 800.f; - vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw); - } - pan_ahead_of_player(c); - - return yaw; -} - -/** - * Update the camera in slide and hoot mode. - * - * In slide mode, keep the camera 800 units from Mario - */ -s16 update_slide_camera(struct Camera *c) { - struct Surface *floor; - f32 floorHeight; - Vec3f pos; - f32 distCamToFocus; - f32 maxCamDist; - f32 pitchScale; - s16 camPitch; - s16 camYaw; - s16 goalPitch = 0x1555; - s16 goalYaw = sMarioCamState->faceAngle[1] + DEGREES(180); - - // Zoom in when inside the CCM shortcut - if (sStatusFlags & CAM_FLAG_CCM_SLIDE_SHORTCUT) { - sLakituDist = approach_f32(sLakituDist, -600.f, 20.f, 20.f); - } else { - sLakituDist = approach_f32(sLakituDist, 0.f, 20.f, 20.f); - } - - // No C-Button input in this mode, notify the player with a buzzer - play_camera_buzz_if_cbutton(); - - // Focus on Mario - vec3f_copy(c->focus, sMarioCamState->pos); - c->focus[1] += 50.f; - - vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); - maxCamDist = 800.f; - - // In hoot mode, zoom further out and rotate faster - if (sMarioCamState->action == ACT_RIDING_HOOT) { - maxCamDist = 1000.f; - goalPitch = 0x2800; - camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x100); - } else { - camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x80); - } - camera_approach_s16_symmetric_bool(&camPitch, goalPitch, 0x100); - - // Hoot mode - if (sMarioCamState->action != ACT_RIDING_HOOT && sMarioGeometry.currFloorType == SURFACE_DEATH_PLANE) { - vec3f_set_dist_and_angle(c->focus, pos, maxCamDist + sLakituDist, camPitch, camYaw); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; - camera_approach_f32_symmetric_bool(&c->pos[1], c->focus[1], 30.f); - vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &camPitch, &camYaw); - pitchScale = (distCamToFocus - maxCamDist + sLakituDist) / 10000.f; - if (pitchScale > 1.f) { - pitchScale = 1.f; - } - camPitch += 0x1000 * pitchScale; - vec3f_set_dist_and_angle(c->pos, c->focus, distCamToFocus, camPitch, camYaw); - - // Slide mode - } else { - vec3f_set_dist_and_angle(c->focus, c->pos, maxCamDist + sLakituDist, camPitch, camYaw); - sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - - // Stay above the slide floor - floorHeight = find_floor(c->pos[0], c->pos[1] + 200.f, c->pos[2], &floor) + 125.f; - if (c->pos[1] < floorHeight) { - c->pos[1] = floorHeight; - } - // Stay closer than maxCamDist - vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw); - if (distCamToFocus > maxCamDist + sLakituDist) { - distCamToFocus = maxCamDist + sLakituDist; - vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw); - } - } - - camYaw = calculate_yaw(c->focus, c->pos); - return camYaw; -} - -void mode_behind_mario_camera(struct Camera *c) { - c->nextYaw = mode_behind_mario(c); -} - -s32 nop_update_water_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) { - return 0; -} - -/** - * Exactly the same as BEHIND_MARIO - */ -void mode_water_surface_camera(struct Camera *c) { - c->nextYaw = mode_behind_mario(c); -} - -/** - * Used in sModeTransitions for CLOSE and FREE_ROAM mode - */ -s32 update_mario_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { - s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); - focus_on_mario(focus, pos, 125.f, 125.f, gCameraZoomDist, 0x05B0, yaw); - - return sMarioCamState->faceAngle[1]; -} - -/** - * Update the camera in default, close, and free roam mode - * - * The camera moves behind Mario, and can rotate all the way around - */ -s16 update_default_camera(struct Camera *c) { - Vec3f tempPos; - Vec3f cPos; - struct Surface *marioFloor; - struct Surface *cFloor; - struct Surface *tempFloor; - struct Surface *ceil; - f32 camFloorHeight; - f32 tempFloorHeight; - f32 marioFloorHeight; - f32 dist; - f32 zoomDist; - f32 waterHeight; - f32 gasHeight; - s16 avoidYaw; - s16 pitch; - s16 yaw; - s16 yawGoal = sMarioCamState->faceAngle[1] + DEGREES(180); - f32 posHeight; - f32 focHeight; - f32 distFromWater; - s16 tempPitch; - s16 tempYaw; - f32 xzDist; - s16 nextYawVel; - s16 yawVel = 0; - f32 scale; - s32 avoidStatus = 0; - s32 closeToMario = FALSE; - f32 ceilHeight = find_ceil(gLakituState.goalPos[0], - gLakituState.goalPos[1], - gLakituState.goalPos[2], &ceil); - s16 yawDir; - - handle_c_button_movement(c); - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - - // If C-Down is active, determine what distance the camera should be from Mario - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - //! In Mario mode, the camera is zoomed out further than in Lakitu mode (1400 vs 1200) - if (set_cam_angle(0) == CAM_ANGLE_MARIO) { - zoomDist = gCameraZoomDist + 1050; - } else { - zoomDist = gCameraZoomDist + 400; - } - } else { - zoomDist = gCameraZoomDist; - } - - if (sMarioCamState->action & ACT_FLAG_HANGING || - sMarioCamState->action == ACT_RIDING_HOOT) { - zoomDist *= 0.8f; - set_handheld_shake(HAND_CAM_SHAKE_HANG_OWL); - } - - // If not zooming out, only allow dist to decrease - if (sZoomAmount == 0.f) { - if (dist > zoomDist) { - if ((dist -= 50.f) < zoomDist) { - dist = zoomDist; - } - } - } else { - if ((sZoomAmount -= 30.f) < 0.f) { - sZoomAmount = 0.f; - } - if (dist > zoomDist) { - if ((dist -= 30.f) < zoomDist) { - dist = zoomDist; - } - } - if (dist < zoomDist) { - if ((dist += 30.f) > zoomDist) { - dist = zoomDist; - } - } - } - - // Determine how fast to rotate the camera - if (sCSideButtonYaw == 0) { - if (c->mode == CAMERA_MODE_FREE_ROAM) { - nextYawVel = 0xC0; - } else { - nextYawVel = 0x100; - } - if ((gPlayer1Controller->stickX != 0.f || gPlayer1Controller->stickY != 0.f) != 0) { - nextYawVel = 0x20; - } - } else { - if (sCSideButtonYaw < 0) { - yaw += 0x200; - } - if (sCSideButtonYaw > 0) { - yaw -= 0x200; - } - camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 0x100); - nextYawVel = 0; - } - sYawSpeed = 0x400; - xzDist = calc_hor_dist(sMarioCamState->pos, c->pos); - - if (sStatusFlags & CAM_FLAG_BEHIND_MARIO_POST_DOOR) { - if (xzDist >= 250) { - sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR; - } - if (abss((sMarioCamState->faceAngle[1] - yaw) / 2) < 0x1800) { - sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR; - yaw = sCameraYawAfterDoorCutscene + DEGREES(180); - dist = 800.f; - sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - } - } else if (xzDist < 250) { - // Turn rapidly if very close to Mario - c->pos[0] += (250 - xzDist) * sins(yaw); - c->pos[2] += (250 - xzDist) * coss(yaw); - if (sCSideButtonYaw == 0) { - nextYawVel = 0x1000; - sYawSpeed = 0; - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - } - closeToMario |= 1; - } - - if (-16 < gPlayer1Controller->stickY) { - c->yaw = yaw; - } - - calc_y_to_curr_floor(&posHeight, 1, 200, &focHeight, 0.9f, 200); - vec3f_copy(cPos, c->pos); - avoidStatus = rotate_camera_around_walls(c, cPos, &avoidYaw, 0x600); - // If a wall is blocking the view of Mario, then rotate in the calculated direction - if (avoidStatus == AVOID_STATUS_WALL_COVERING_MARIO) { - sAvoidYawVel = yaw; - sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL; - // Rotate to avoid the wall - approach_s16_asymptotic_bool(&yaw, avoidYaw, 10); - sAvoidYawVel = (sAvoidYawVel - yaw) / 0x100; - } else { - if (gMarioStates[0].forwardVel == 0.f) { - if (sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL) { - if ((yawGoal - yaw) / 0x100 >= 0) { - yawDir = -1; - } else { - yawDir = 1; - } - if ((sAvoidYawVel > 0 && yawDir > 0) || (sAvoidYawVel < 0 && yawDir < 0)) { - yawVel = nextYawVel; - } - } else { - yawVel = nextYawVel; - } - } else { - if (nextYawVel == 0x1000) { - yawVel = nextYawVel; - } - sStatusFlags &= ~CAM_FLAG_COLLIDED_WITH_WALL; - } - - // If a wall is near the camera, turn twice as fast - if (avoidStatus != AVOID_STATUS_NONE) { - yawVel += yawVel; - } - // ...Unless the camera already rotated from being close to Mario - if ((closeToMario & 1) && avoidStatus != AVOID_STATUS_NONE) { - yawVel = 0; - } - if (yawVel != 0 && get_dialog_id() == DIALOG_NONE) { - camera_approach_s16_symmetric_bool(&yaw, yawGoal, yawVel); - } - } - - // Only zoom out if not obstructed by walls and Lakitu hasn't collided with any - if (avoidStatus == AVOID_STATUS_NONE && !(sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL)) { - approach_f32_asymptotic_bool(&dist, zoomDist - 100.f, 0.05f); - } - vec3f_set_dist_and_angle(sMarioCamState->pos, cPos, dist, pitch, yaw); - cPos[1] += posHeight + 125.f; - - // Move the camera away from walls and set the collision flag - if (collide_with_walls(cPos, 10.f, 80.f) != 0) { - sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL; - } - - c->focus[0] = sMarioCamState->pos[0]; - c->focus[1] = sMarioCamState->pos[1] + 125.f + focHeight; - c->focus[2] = sMarioCamState->pos[2]; - - marioFloorHeight = 125.f + sMarioGeometry.currFloorHeight; - marioFloor = sMarioGeometry.currFloor; - camFloorHeight = find_floor(cPos[0], cPos[1] + 50.f, cPos[2], &cFloor) + 125.f; - for (scale = 0.1f; scale < 1.f; scale += 0.2f) { - scale_along_line(tempPos, cPos, sMarioCamState->pos, scale); - tempFloorHeight = find_floor(tempPos[0], tempPos[1], tempPos[2], &tempFloor) + 125.f; - if (tempFloor != NULL && tempFloorHeight > marioFloorHeight) { - marioFloorHeight = tempFloorHeight; - marioFloor = tempFloor; - } - } - - // Lower the camera in Mario mode - if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { - marioFloorHeight -= 35.f; - camFloorHeight -= 35.f; - c->focus[1] -= 25.f; - } - - // If there's water below the camera, decide whether to keep the camera above the water surface - waterHeight = find_water_level(cPos[0], cPos[2]); - if (waterHeight != FLOOR_LOWER_LIMIT) { - waterHeight += 125.f; - distFromWater = waterHeight - marioFloorHeight; - if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER)) { - if (distFromWater > 800.f && (sMarioCamState->action & ACT_FLAG_METAL_WATER)) { - gCameraMovementFlags |= CAM_MOVE_METAL_BELOW_WATER; - } - } else { - if (distFromWater < 400.f || !(sMarioCamState->action & ACT_FLAG_METAL_WATER)) { - gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER; - } - } - // If not wearing the metal cap, always stay above - if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER) && camFloorHeight < waterHeight) { - camFloorHeight = waterHeight; - } - } else { - gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER; - } - - cPos[1] = camFloorHeight; - vec3f_copy(tempPos, cPos); - tempPos[1] -= 125.f; - if (marioFloor != NULL && camFloorHeight <= marioFloorHeight) { - avoidStatus = is_range_behind_surface(c->focus, tempPos, marioFloor, 0, SURFACE_NULL); - if (avoidStatus != AVOID_STATUS_WALL_NEAR_CAMERA && ceilHeight > marioFloorHeight) { - camFloorHeight = marioFloorHeight; - } - } - - posHeight = 0.f; - if (c->mode == CAMERA_MODE_FREE_ROAM) { - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - posHeight = 375.f; - if (gCurrLevelArea == AREA_SSL_PYRAMID) { - posHeight /= 2; - } - } else { - posHeight = 100.f; - } - } - if ((gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) && (sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { - posHeight = 610.f; -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelArea == AREA_SSL_PYRAMID || gCurrLevelNum == LEVEL_CASTLE) { - posHeight /= 2; - } -#endif - } - - // Make Lakitu fly above the gas - gasHeight = find_poison_gas_level(cPos[0], cPos[2]); - if (gasHeight != FLOOR_LOWER_LIMIT) { - if ((gasHeight += 130.f) > c->pos[1]) { - c->pos[1] = gasHeight; - } - } - - if (sMarioCamState->action & ACT_FLAG_HANGING || sMarioCamState->action == ACT_RIDING_HOOT) { - camFloorHeight = sMarioCamState->pos[1] + 400.f; - if (c->mode == CAMERA_MODE_FREE_ROAM) { - camFloorHeight -= 100.f; - } - ceilHeight = CELL_HEIGHT_LIMIT; - vec3f_copy(c->focus, sMarioCamState->pos); - } - - if (sMarioCamState->action & ACT_FLAG_ON_POLE) { - camFloorHeight = gMarioStates[0].usedObj->oPosY + 125.f; - if (sMarioCamState->pos[1] - 100.f > camFloorHeight) { - camFloorHeight = sMarioCamState->pos[1] - 100.f; - } - ceilHeight = CELL_HEIGHT_LIMIT; - vec3f_copy(c->focus, sMarioCamState->pos); - } - if (camFloorHeight != FLOOR_LOWER_LIMIT) { - camFloorHeight += posHeight; - approach_camera_height(c, camFloorHeight, 20.f); - } - c->pos[0] = cPos[0]; - c->pos[2] = cPos[2]; - cPos[0] = gLakituState.goalPos[0]; - cPos[1] = c->pos[1]; - cPos[2] = gLakituState.goalPos[2]; - vec3f_get_dist_and_angle(cPos, c->pos, &dist, &tempPitch, &tempYaw); - // Prevent the camera from lagging behind too much - if (dist > 50.f) { - dist = 50.f; - vec3f_set_dist_and_angle(cPos, c->pos, dist, tempPitch, tempYaw); - } - if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE) { - vec3f_get_dist_and_angle(c->focus, c->pos, &dist, &tempPitch, &tempYaw); - if (dist > zoomDist) { - dist = zoomDist; - vec3f_set_dist_and_angle(c->focus, c->pos, dist, tempPitch, tempYaw); - } - } - if (ceilHeight != CELL_HEIGHT_LIMIT) { - if (c->pos[1] > (ceilHeight -= 150.f) - && (avoidStatus = is_range_behind_surface(c->pos, sMarioCamState->pos, ceil, 0, -1)) == 1) { - c->pos[1] = ceilHeight; - } - } -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelArea == AREA_WDW_TOWN) { - yaw = clamp_positions_and_find_yaw(c->pos, c->focus, 2254.f, -3789.f, 3790.f, -2253.f); - } -#endif - return yaw; -} - -/** - * The default camera mode - * Used by close and free roam modes - */ -void mode_default_camera(struct Camera *c) { - set_fov_function(CAM_FOV_DEFAULT); - c->nextYaw = update_default_camera(c); - pan_ahead_of_player(c); -} - -/** - * The mode used by close and free roam - */ -void mode_lakitu_camera(struct Camera *c) { - gCameraZoomDist = 800.f; - mode_default_camera(c); -} - -/** - * When no other mode is active and the current R button mode is Mario - */ -void mode_mario_camera(struct Camera *c) { - gCameraZoomDist = 350.f; - mode_default_camera(c); -} - -/** - * Rotates the camera around the spiral staircase. - */ -s32 update_spiral_stairs_camera(struct Camera *c, Vec3f focus, Vec3f pos) { - /// The returned yaw - s16 camYaw; - /// The focus (Mario)'s yaw around the stairs - s16 focYaw; - /// The camera's yaw around the stairs - s16 posYaw; - Vec3f cPos; - Vec3f checkPos; - struct Surface *floor; - f32 focusHeight; - f32 floorHeight; - f32 focY; - - handle_c_button_movement(c); - // Set base pos to the center of the staircase - vec3f_set(sFixedModeBasePosition, -1280.f, 614.f, 1740.f); - - // Focus on Mario, and move the focus up the staircase with him - calc_y_to_curr_floor(&focusHeight, 1.f, 200.f, &focusHeight, 0.9f, 200.f); - focus[0] = sMarioCamState->pos[0]; - focY = sMarioCamState->pos[1] + 125.f + focusHeight; - focus[2] = sMarioCamState->pos[2]; - - vec3f_copy(cPos, pos); - vec3f_get_yaw(sFixedModeBasePosition, focus, &focYaw); - vec3f_get_yaw(sFixedModeBasePosition, cPos, &posYaw); - - sSpiralStairsYawOffset = posYaw - focYaw; - // posYaw will change if Mario is more than 90 degrees around the stairs, relative to the camera - if (sSpiralStairsYawOffset < DEGREES(-90)) { - sSpiralStairsYawOffset = DEGREES(-90); - } - if (sSpiralStairsYawOffset > DEGREES(90)) { - sSpiralStairsYawOffset = DEGREES(90); - } - focYaw += sSpiralStairsYawOffset; - posYaw = focYaw; - - vec3f_set_dist_and_angle(sFixedModeBasePosition, cPos, 300.f, 0, posYaw); - - // Move the camera's y coord up/down the staircase - checkPos[0] = focus[0] + (cPos[0] - focus[0]) * 0.7f; - checkPos[1] = focus[1] + (cPos[1] - focus[1]) * 0.7f + 300.f; - checkPos[2] = focus[2] + (cPos[2] - focus[2]) * 0.7f; - floorHeight = find_floor(checkPos[0], checkPos[1] + 50.f, checkPos[2], &floor); - - if (floorHeight != FLOOR_LOWER_LIMIT) { - if (floorHeight < sMarioGeometry.currFloorHeight) { - floorHeight = sMarioGeometry.currFloorHeight; - } - pos[1] = approach_f32(pos[1], (floorHeight += 125.f), 30.f, 30.f); - } - - camera_approach_f32_symmetric_bool(&focus[1], focY, 30.f); - pos[0] = cPos[0]; - pos[2] = cPos[2]; - camYaw = calculate_yaw(focus, pos); - - return camYaw; -} - -/** - * The mode used in the spiral staircase in the castle - */ -void mode_spiral_stairs_camera(struct Camera *c) { - c->nextYaw = update_spiral_stairs_camera(c, c->focus, c->pos); -} - -s32 update_slide_or_0f_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { - s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); - - focus_on_mario(focus, pos, 125.f, 125.f, 800.f, DEGREES(30), yaw); - return sMarioCamState->faceAngle[1]; -} - -static UNUSED void unused_mode_0f_camera(struct Camera *c) { - if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { - gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; - } - c->nextYaw = update_slide_camera(c); -} - -/** - * Slide/hoot mode. - * In this mode, the camera is always at the back of Mario, because Mario generally only moves forward. - */ -void mode_slide_camera(struct Camera *c) { - if (sMarioGeometry.currFloorType == SURFACE_CLOSE_CAMERA || - sMarioGeometry.currFloorType == SURFACE_NO_CAM_COL_SLIPPERY) { - mode_lakitu_camera(c); - } else { - if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { - gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; - } - c->nextYaw = update_slide_camera(c); - } -} - -void store_lakitu_cam_info_for_c_up(struct Camera *c) { - vec3f_copy(sCameraStoreCUp.pos, c->pos); - vec3f_sub(sCameraStoreCUp.pos, sMarioCamState->pos); - // Only store the y value, and as an offset from Mario, for some reason - vec3f_set(sCameraStoreCUp.focus, 0.f, c->focus[1] - sMarioCamState->pos[1], 0.f); -} - -/** - * Start C-Up mode. The actual mode change is handled in update_mario_inputs() in mario.c - * - * @see update_mario_inputs - */ -void set_mode_c_up(struct Camera *c) { - if (!(gCameraMovementFlags & CAM_MOVE_C_UP_MODE)) { - gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; - store_lakitu_cam_info_for_c_up(c); - sCameraSoundFlags &= ~CAM_SOUND_C_UP_PLAYED; - } -} - -/** - * Zoom the camera out of C-Up mode, avoiding moving into a wall, if possible, by searching for an open - * direction. - */ -void exit_c_up(struct Camera *c) { - struct Surface *surface; - Vec3f checkFoc; - Vec3f curPos; - // Variables for searching for an open direction - s32 searching = FALSE; - /// The current sector of the circle that we are checking - s32 sector; - f32 ceilHeight; - f32 floorHeight; - f32 curDist; - f32 d; - s16 curPitch; - s16 curYaw; - s16 checkYaw = 0; - - if ((gCameraMovementFlags & CAM_MOVE_C_UP_MODE) && !(gCameraMovementFlags & CAM_MOVE_STARTED_EXITING_C_UP)) { - vec3f_copy(checkFoc, c->focus); - checkFoc[0] = sMarioCamState->pos[0]; - checkFoc[2] = sMarioCamState->pos[2]; - vec3f_get_dist_and_angle(checkFoc, c->pos, &curDist, &curPitch, &curYaw); - vec3f_copy(curPos, c->pos); - curDist = 80.f; - - // Search for an open direction to zoom out in, if the camera is changing to close, free roam, - // or spiral-stairs mode - if (sModeInfo.lastMode == CAMERA_MODE_SPIRAL_STAIRS || sModeInfo.lastMode == CAMERA_MODE_CLOSE - || sModeInfo.lastMode == CAMERA_MODE_FREE_ROAM) { - searching = TRUE; - // Check the whole circle around Mario for an open direction to zoom out to - for (sector = 0; sector < 16 && searching == 1; sector++) { - vec3f_set_dist_and_angle(checkFoc, curPos, curDist, 0, curYaw + checkYaw); - - // If there are no walls this way, - if (f32_find_wall_collision(&curPos[0], &curPos[1], &curPos[2], 20.f, 50.f) == 0) { - - // Start close to Mario, check for walls, floors, and ceilings all the way to the - // zoomed out distance - for (d = curDist; d < gCameraZoomDist; d += 20.f) { - vec3f_set_dist_and_angle(checkFoc, curPos, d, 0, curYaw + checkYaw); - - // Check if we're zooming out into a floor or ceiling - ceilHeight = find_ceil(curPos[0], curPos[1] - 150.f, curPos[2], &surface) + -10.f; - if (surface != NULL && ceilHeight < curPos[1]) { - break; - } - floorHeight = find_floor(curPos[0], curPos[1] + 150.f, curPos[2], &surface) + 10.f; - if (surface != NULL && floorHeight > curPos[1]) { - break; - } - - // Stop checking this direction if there is a wall blocking the way - if (f32_find_wall_collision(&curPos[0], &curPos[1], &curPos[2], 20.f, 50.f) == 1) { - break; - } - } - - // If there was no collision found all the way to the max distance, it's an opening - if (d >= gCameraZoomDist) { - searching = FALSE; - } - } - - // Alternate left and right, checking each 1/16th (22.5 degrees) of the circle - if (searching == 1) { - checkYaw = -checkYaw; - if (checkYaw < 0) { - checkYaw -= 0x1000; - } else { - checkYaw += 0x1000; - } - } - } - - // Update the stored focus and pos to the direction found in the search - if (!searching) { - vec3f_set_dist_and_angle(checkFoc, sCameraStoreCUp.pos, gCameraZoomDist, 0, curYaw + checkYaw); - vec3f_copy(sCameraStoreCUp.focus, checkFoc); - vec3f_sub(sCameraStoreCUp.pos, sMarioCamState->pos); - vec3f_sub(sCameraStoreCUp.focus, sMarioCamState->pos); - } - - gCameraMovementFlags |= CAM_MOVE_STARTED_EXITING_C_UP; - transition_next_state(c, 15); - } else { - // Let the next camera mode handle it - gCameraMovementFlags &= ~(CAM_MOVE_STARTED_EXITING_C_UP | CAM_MOVE_C_UP_MODE); - vec3f_set_dist_and_angle(checkFoc, c->pos, curDist, curPitch, curYaw + checkYaw); - } - play_sound_cbutton_down(); - } -} - -/** - * The mode used when C-Up is pressed. - */ -s32 update_c_up(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { - s16 pitch = sCUpCameraPitch; - s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180); - - focus_on_mario(focus, pos, 125.f, 125.f, 250.f, pitch, yaw); - return sMarioCamState->faceAngle[1]; -} - -/** - * Make Mario's head move in C-Up mode. - */ -void move_mario_head_c_up(UNUSED struct Camera *c) { - sCUpCameraPitch += (s16)(gPlayer1Controller->stickY * 10.f); - sModeOffsetYaw -= (s16)(gPlayer1Controller->stickX * 10.f); - - // Bound looking up to nearly 80 degrees. - if (sCUpCameraPitch > 0x38E3) { - sCUpCameraPitch = 0x38E3; - } - // Bound looking down to -45 degrees - if (sCUpCameraPitch < -0x2000) { - sCUpCameraPitch = -0x2000; - } - - // Bound the camera yaw to +-120 degrees - if (sModeOffsetYaw > 0x5555) { - sModeOffsetYaw = 0x5555; - } - if (sModeOffsetYaw < -0x5555) { - sModeOffsetYaw = -0x5555; - } - - // Give Mario's neck natural-looking constraints - sMarioCamState->headRotation[0] = sCUpCameraPitch * 3 / 4; - sMarioCamState->headRotation[1] = sModeOffsetYaw * 3 / 4; -} - -/** - * Zooms the camera in for C-Up mode - */ -void move_into_c_up(struct Camera *c) { - struct LinearTransitionPoint *start = &sModeInfo.transitionStart; - struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; - - f32 dist = end->dist - start->dist; - s16 pitch = end->pitch - start->pitch; - s16 yaw = end->yaw - start->yaw; - - // Linearly interpolate from start to end position's polar coordinates - dist = start->dist + dist * sModeInfo.frame / sModeInfo.max; - pitch = start->pitch + pitch * sModeInfo.frame / sModeInfo.max; - yaw = start->yaw + yaw * sModeInfo.frame / sModeInfo.max; - - // Linearly interpolate the focus from start to end - c->focus[0] = start->focus[0] + (end->focus[0] - start->focus[0]) * sModeInfo.frame / sModeInfo.max; - c->focus[1] = start->focus[1] + (end->focus[1] - start->focus[1]) * sModeInfo.frame / sModeInfo.max; - c->focus[2] = start->focus[2] + (end->focus[2] - start->focus[2]) * sModeInfo.frame / sModeInfo.max; - - vec3f_add(c->focus, sMarioCamState->pos); - vec3f_set_dist_and_angle(c->focus, c->pos, dist, pitch, yaw); - - sMarioCamState->headRotation[0] = 0; - sMarioCamState->headRotation[1] = 0; - - // Finished zooming in - if (++sModeInfo.frame == sModeInfo.max) { - gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; - } -} - -/** - * The main update function for C-Up mode - */ -void mode_c_up_camera(struct Camera *c) { - // Play a sound when entering C-Up mode - if (!(sCameraSoundFlags & CAM_SOUND_C_UP_PLAYED)) { - play_sound_cbutton_up(); - sCameraSoundFlags |= CAM_SOUND_C_UP_PLAYED; - } - - // Zoom in first - if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) { - gCameraMovementFlags |= CAM_MOVE_C_UP_MODE; - move_into_c_up(c); - return; - } - - if (!(gCameraMovementFlags & CAM_MOVE_STARTED_EXITING_C_UP)) { - // Normal update - move_mario_head_c_up(c); - update_c_up(c, c->focus, c->pos); - } else { - // Exiting C-Up - if (sStatusFlags & CAM_FLAG_TRANSITION_OUT_OF_C_UP) { - // Retrieve the previous position and focus - vec3f_copy(c->pos, sCameraStoreCUp.pos); - vec3f_add(c->pos, sMarioCamState->pos); - vec3f_copy(c->focus, sCameraStoreCUp.focus); - vec3f_add(c->focus, sMarioCamState->pos); - // Make Mario look forward - camera_approach_s16_symmetric_bool(&sMarioCamState->headRotation[0], 0, 1024); - camera_approach_s16_symmetric_bool(&sMarioCamState->headRotation[1], 0, 1024); - } else { - // Finished exiting C-Up - gCameraMovementFlags &= ~(CAM_MOVE_STARTED_EXITING_C_UP | CAM_MOVE_C_UP_MODE); - } - } - sPanDistance = 0.f; - - // Exit C-Up mode - if (gPlayer1Controller->buttonPressed & (A_BUTTON | B_BUTTON | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS)) { - exit_c_up(c); - } -} - -/** - * Used when Mario is in a cannon. - */ -s32 update_in_cannon(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) { - focus_on_mario(pos, focus, 125.f + sCannonYOffset, 125.f, 800.f, - sMarioCamState->faceAngle[0], sMarioCamState->faceAngle[1]); - return sMarioCamState->faceAngle[1]; -} - -/** - * Updates the camera when Mario is in a cannon. - * sCannonYOffset is used to make the camera rotate down when Mario has just entered the cannon - */ -void mode_cannon_camera(struct Camera *c) { - sLakituPitch = 0; - gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; - c->nextYaw = update_in_cannon(c, c->focus, c->pos); - if (gPlayer1Controller->buttonPressed & A_BUTTON) { - set_camera_mode(c, CAMERA_MODE_BEHIND_MARIO, 1); - sPanDistance = 0.f; - sCannonYOffset = 0.f; - sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - } else { - sCannonYOffset = approach_f32(sCannonYOffset, 0.f, 100.f, 100.f); - } -} - -/** - * Cause Lakitu to fly to the next Camera position and focus over a number of frames. - * - * At the end of each frame, Lakitu's position and focus ("state") are stored. - * Calling this function makes next_lakitu_state() fly from the last frame's state to the - * current frame's calculated state. - * - * @see next_lakitu_state() - */ -void transition_next_state(UNUSED struct Camera *c, s16 frames) { - if (!(sStatusFlags & CAM_FLAG_FRAME_AFTER_CAM_INIT)) { - sStatusFlags |= (CAM_FLAG_START_TRANSITION | CAM_FLAG_TRANSITION_OUT_OF_C_UP); - sModeTransition.framesLeft = frames; - } -} - -/** - * Sets the camera mode to `newMode` and initializes sModeTransition with `numFrames` frames - * - * Used to change the camera mode to 'level-oriented' modes - * namely: RADIAL/OUTWARD_RADIAL, 8_DIRECTIONS, FREE_ROAM, CLOSE, SPIRAL_STAIRS, and SLIDE_HOOT - */ -void transition_to_camera_mode(struct Camera *c, s16 newMode, s16 numFrames) { - if (c->mode != newMode) { - sModeInfo.newMode = (newMode != -1) ? newMode : sModeInfo.lastMode; - sModeInfo.lastMode = c->mode; - c->mode = sModeInfo.newMode; - - // Clear movement flags that would affect the transition - gCameraMovementFlags &= (u16)~(CAM_MOVE_RESTRICT | CAM_MOVE_ROTATE); - if (!(sStatusFlags & CAM_FLAG_FRAME_AFTER_CAM_INIT)) { - transition_next_state(c, numFrames); - sCUpCameraPitch = 0; - sModeOffsetYaw = 0; - sLakituDist = 0; - sLakituPitch = 0; - sAreaYawChange = 0; - sPanDistance = 0.f; - sCannonYOffset = 0.f; - } - } -} - -/** - * Used to change the camera mode between its default/previous and certain Mario-oriented modes, - * namely: C_UP, WATER_SURFACE, CLOSE, and BEHIND_MARIO - * - * Stores the current pos and focus in sModeInfo->transitionStart, and - * stores the next pos and focus into sModeInfo->transitionEnd. These two fields are used in - * move_into_c_up(). - * - * @param mode the mode to change to, or -1 to switch to the previous mode - * @param frames number of frames the transition should last, only used when entering C_UP - */ -void set_camera_mode(struct Camera *c, s16 mode, s16 frames) { - struct LinearTransitionPoint *start = &sModeInfo.transitionStart; - struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; - -#ifdef ENABLE_VANILLA_CAM_PROCESSING - if (mode == CAMERA_MODE_WATER_SURFACE && gCurrLevelArea == AREA_TTM_OUTSIDE) { - } else { -#endif - // Clear movement flags that would affect the transition - gCameraMovementFlags &= (u16)~(CAM_MOVE_RESTRICT | CAM_MOVE_ROTATE); - gCameraMovementFlags |= CAM_MOVING_INTO_MODE; - if (mode == CAMERA_MODE_NONE) { - mode = CAMERA_MODE_CLOSE; - } - sCUpCameraPitch = 0; - sModeOffsetYaw = 0; - sLakituDist = 0; - sLakituPitch = 0; - sAreaYawChange = 0; - - sModeInfo.newMode = (mode != -1) ? mode : sModeInfo.lastMode; - sModeInfo.lastMode = c->mode; - sModeInfo.max = frames; - sModeInfo.frame = 1; - - c->mode = sModeInfo.newMode; - gLakituState.mode = c->mode; - - vec3f_copy(end->focus, c->focus); - vec3f_sub(end->focus, sMarioCamState->pos); - - vec3f_copy(end->pos, c->pos); - vec3f_sub(end->pos, sMarioCamState->pos); - -#ifndef ENABLE_VANILLA_CAM_PROCESSING - if (mode == CAMERA_MODE_8_DIRECTIONS) { - // Helps transition from any camera mode to 8dir - s8DirModeYawOffset = snap_to_45_degrees(c->yaw); - } -#endif - - sAreaYaw = sModeTransitions[sModeInfo.newMode](c, end->focus, end->pos); - - // End was updated by sModeTransitions - vec3f_sub(end->focus, sMarioCamState->pos); - vec3f_sub(end->pos, sMarioCamState->pos); - - vec3f_copy(start->focus, gLakituState.curFocus); - vec3f_sub(start->focus, sMarioCamState->pos); - - vec3f_copy(start->pos, gLakituState.curPos); - vec3f_sub(start->pos, sMarioCamState->pos); - - vec3f_get_dist_and_angle(start->focus, start->pos, &start->dist, &start->pitch, &start->yaw); - vec3f_get_dist_and_angle(end->focus, end->pos, &end->dist, &end->pitch, &end->yaw); -#ifdef ENABLE_VANILLA_CAM_PROCESSING - } -#endif -} - -/** - * Updates Lakitu's position/focus and applies camera shakes. - */ -void update_lakitu(struct Camera *c) { - struct Surface *floor = NULL; - Vec3f newPos; - Vec3f newFoc; - f32 distToFloor; - s16 newYaw; - - if (!(gCameraMovementFlags & CAM_MOVE_PAUSE_SCREEN)) { - newYaw = next_lakitu_state(newPos, newFoc, c->pos, c->focus, sOldPosition, sOldFocus, - c->nextYaw); - set_or_approach_s16_symmetric(&c->yaw, newYaw, sYawSpeed); - sStatusFlags &= ~CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - - // Update old state - vec3f_copy(sOldPosition, newPos); - vec3f_copy(sOldFocus, newFoc); - - gLakituState.yaw = c->yaw; - gLakituState.nextYaw = c->nextYaw; - vec3f_copy(gLakituState.goalPos, c->pos); - vec3f_copy(gLakituState.goalFocus, c->focus); - - // Simulate Lakitu flying to the new position and turning towards the new focus - set_or_approach_vec3f_asymptotic(gLakituState.curPos, newPos, - gLakituState.posHSpeed, gLakituState.posVSpeed, - gLakituState.posHSpeed); - set_or_approach_vec3f_asymptotic(gLakituState.curFocus, newFoc, - gLakituState.focHSpeed, gLakituState.focVSpeed, - gLakituState.focHSpeed); - // Adjust Lakitu's speed back to normal - set_or_approach_f32_asymptotic(&gLakituState.focHSpeed, 0.8f, 0.05f); - set_or_approach_f32_asymptotic(&gLakituState.focVSpeed, 0.3f, 0.05f); - set_or_approach_f32_asymptotic(&gLakituState.posHSpeed, 0.3f, 0.05f); - set_or_approach_f32_asymptotic(&gLakituState.posVSpeed, 0.3f, 0.05f); - - // Turn on smooth movement when it hasn't been blocked for 2 frames - if (sStatusFlags & CAM_FLAG_BLOCK_SMOOTH_MOVEMENT) { - sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - } else { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - } - - vec3f_copy(gLakituState.pos, gLakituState.curPos); - vec3f_copy(gLakituState.focus, gLakituState.curFocus); - - if (c->cutscene) { - vec3f_add(gLakituState.focus, sPlayer2FocusOffset); - vec3_zero(sPlayer2FocusOffset); - } - - vec3f_get_dist_and_angle(gLakituState.pos, gLakituState.focus, &gLakituState.focusDistance, - &gLakituState.oldPitch, &gLakituState.oldYaw); - - gLakituState.roll = 0; - - // Apply camera shakes - shake_camera_pitch(gLakituState.pos, gLakituState.focus); - shake_camera_yaw(gLakituState.pos, gLakituState.focus); - shake_camera_roll(&gLakituState.roll); - shake_camera_handheld(gLakituState.pos, gLakituState.focus); - - if (sMarioCamState->action == ACT_DIVE && gLakituState.lastFrameAction != ACT_DIVE) { - set_camera_shake_from_hit(SHAKE_HIT_FROM_BELOW); - } - - gLakituState.roll += sHandheldShakeRoll; - gLakituState.roll += gLakituState.keyDanceRoll; - - if (c->mode != CAMERA_MODE_C_UP && c->cutscene == CUTSCENE_NONE) { - gCollisionFlags |= COLLISION_FLAG_CAMERA; - distToFloor = find_floor(gLakituState.pos[0], - gLakituState.pos[1] + 20.0f, - gLakituState.pos[2], &floor); - if (distToFloor != FLOOR_LOWER_LIMIT) { - if (gLakituState.pos[1] < (distToFloor += 100.0f)) { - gLakituState.pos[1] = distToFloor; - } else { - gCollisionFlags &= ~COLLISION_FLAG_CAMERA; - } - } - } - - vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); - } - clamp_pitch(gLakituState.pos, gLakituState.focus, 0x3E00, -0x3E00); - gLakituState.mode = c->mode; - gLakituState.defMode = c->defMode; -} - -/** - * The main camera update function. - * Gets controller input, checks for cutscenes, handles mode changes, and moves the camera - */ -void update_camera(struct Camera *c) { - PROFILER_GET_SNAPSHOT_TYPE(PROFILER_DELTA_COLLISION); - gCamera = c; - update_camera_hud_status(c); - if (c->cutscene == CUTSCENE_NONE -#ifdef PUPPYCAM - && !gPuppyCam.enabled -#endif - && gCurrentArea->camera->mode != CAMERA_MODE_INSIDE_CANNON) { - // Only process R_TRIG if 'fixed' is not selected in the menu - if (cam_select_alt_mode(CAM_SELECTION_NONE) == CAM_SELECTION_MARIO) { - if (gPlayer1Controller->buttonPressed & R_TRIG) { - if (set_cam_angle(0) == CAM_ANGLE_LAKITU) { - set_cam_angle(CAM_ANGLE_MARIO); - } else { - set_cam_angle(CAM_ANGLE_LAKITU); - } - } - } - play_sound_if_cam_switched_to_lakitu_or_mario(); - } - - // Initialize the camera - sStatusFlags &= ~CAM_FLAG_FRAME_AFTER_CAM_INIT; - if (gCameraMovementFlags & CAM_MOVE_INIT_CAMERA) { - init_camera(c); - gCameraMovementFlags &= ~CAM_MOVE_INIT_CAMERA; - sStatusFlags |= CAM_FLAG_FRAME_AFTER_CAM_INIT; - } - -#ifdef PUPPYCAM - if (!gPuppyCam.enabled || c->cutscene != CUTSCENE_NONE || gCurrentArea->camera->mode == CAMERA_MODE_INSIDE_CANNON) { -#endif - // Store previous geometry information - sMarioGeometry.prevFloorHeight = sMarioGeometry.currFloorHeight; - sMarioGeometry.prevCeilHeight = sMarioGeometry.currCeilHeight; - sMarioGeometry.prevFloor = sMarioGeometry.currFloor; - sMarioGeometry.prevCeil = sMarioGeometry.currCeil; - sMarioGeometry.prevFloorType = sMarioGeometry.currFloorType; - sMarioGeometry.prevCeilType = sMarioGeometry.currCeilType; - - find_mario_floor_and_ceil(&sMarioGeometry); - gCollisionFlags |= COLLISION_FLAG_CAMERA; - vec3f_copy(c->pos, gLakituState.goalPos); - vec3f_copy(c->focus, gLakituState.goalFocus); - - c->yaw = gLakituState.yaw; - c->nextYaw = gLakituState.nextYaw; - c->mode = gLakituState.mode; - c->defMode = gLakituState.defMode; -#ifdef ENABLE_VANILLA_CAM_PROCESSING - camera_course_processing(c); -#else - if (gCurrDemoInput != NULL) camera_course_processing(c); -#endif - sCButtonsPressed = find_c_buttons_pressed(sCButtonsPressed, gPlayer1Controller->buttonPressed, gPlayer1Controller->buttonDown); - - if (c->cutscene != CUTSCENE_NONE) { - sYawSpeed = 0; - play_cutscene(c); - sFramesSinceCutsceneEnded = 0; - } else { - // Clear the recent cutscene after 8 frames - if (gRecentCutscene != CUTSCENE_NONE && sFramesSinceCutsceneEnded < 8) { - sFramesSinceCutsceneEnded++; - if (sFramesSinceCutsceneEnded >= 8) { - gRecentCutscene = CUTSCENE_NONE; - sFramesSinceCutsceneEnded = 0; - } - } - } - // If not in a cutscene, do mode processing - if (c->cutscene == CUTSCENE_NONE) { - sYawSpeed = 0x400; - - if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { - switch (c->mode) { - case CAMERA_MODE_BEHIND_MARIO: - mode_behind_mario_camera(c); - break; - - case CAMERA_MODE_C_UP: - mode_c_up_camera(c); - break; - - case CAMERA_MODE_WATER_SURFACE: - mode_water_surface_camera(c); - break; - - case CAMERA_MODE_INSIDE_CANNON: - mode_cannon_camera(c); - break; - - default: - mode_mario_camera(c); - } - } else { - switch (c->mode) { - case CAMERA_MODE_BEHIND_MARIO: - mode_behind_mario_camera(c); - break; - - case CAMERA_MODE_C_UP: - mode_c_up_camera(c); - break; - - case CAMERA_MODE_WATER_SURFACE: - mode_water_surface_camera(c); - break; - - case CAMERA_MODE_INSIDE_CANNON: - mode_cannon_camera(c); - break; - - case CAMERA_MODE_8_DIRECTIONS: - mode_8_directions_camera(c); - break; - - case CAMERA_MODE_RADIAL: - mode_radial_camera(c); - break; - - case CAMERA_MODE_OUTWARD_RADIAL: - mode_outward_radial_camera(c); - break; - - case CAMERA_MODE_CLOSE: - mode_lakitu_camera(c); - break; - - case CAMERA_MODE_FREE_ROAM: - mode_lakitu_camera(c); - break; - - case CAMERA_MODE_BOSS_FIGHT: - mode_boss_fight_camera(c); - break; - - case CAMERA_MODE_PARALLEL_TRACKING: - mode_parallel_tracking_camera(c); - break; - - case CAMERA_MODE_SLIDE_HOOT: - mode_slide_camera(c); - break; - - case CAMERA_MODE_FIXED: - mode_fixed_camera(c); - break; - - case CAMERA_MODE_SPIRAL_STAIRS: - mode_spiral_stairs_camera(c); - break; - } - } - } -#ifdef PUPPYCAM - } -#endif - // Start any Mario-related cutscenes - start_cutscene(c, get_cutscene_from_mario_status(c)); - gCollisionFlags &= ~COLLISION_FLAG_CAMERA; -#ifdef PUPPYCAM - if (!gPuppyCam.enabled || c->cutscene != 0 || gCurrentArea->camera->mode == CAMERA_MODE_INSIDE_CANNON) { -#endif -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelNum != LEVEL_CASTLE) { -#endif - // If fixed camera is selected as the alternate mode, then fix the camera as long as the right - // trigger is held - if ((c->cutscene == CUTSCENE_NONE && - (gPlayer1Controller->buttonDown & R_TRIG) && cam_select_alt_mode(0) == CAM_SELECTION_FIXED) - || (gCameraMovementFlags & CAM_MOVE_FIX_IN_PLACE) - || (sMarioCamState->action) == ACT_GETTING_BLOWN) { - - // If this is the first frame that R_TRIG is held, play the "click" sound - if (c->cutscene == CUTSCENE_NONE && (gPlayer1Controller->buttonPressed & R_TRIG) - && cam_select_alt_mode(0) == CAM_SELECTION_FIXED) { - sCameraSoundFlags |= CAM_SOUND_FIXED_ACTIVE; - play_sound_rbutton_changed(); - } - - // Fixed mode only prevents Lakitu from moving. The camera pos still updates, so - // Lakitu will fly to his next position as normal whenever R_TRIG is released. - gLakituState.posHSpeed = 0.f; - gLakituState.posVSpeed = 0.f; - - vec3f_get_yaw(gLakituState.focus, gLakituState.pos, &c->nextYaw); - c->yaw = c->nextYaw; - gCameraMovementFlags &= ~CAM_MOVE_FIX_IN_PLACE; - } else { - // Play the "click" sound when fixed mode is released - if (sCameraSoundFlags & CAM_SOUND_FIXED_ACTIVE) { - play_sound_rbutton_changed(); - sCameraSoundFlags &= ~CAM_SOUND_FIXED_ACTIVE; - } - } -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - } else { - if ((gPlayer1Controller->buttonPressed & R_TRIG) && (cam_select_alt_mode(0) == CAM_SELECTION_FIXED)) { - play_sound_button_change_blocked(); - } - } -#endif - - update_lakitu(c); -#ifdef PUPPYCAM - } - // Just a cute little bit that syncs puppycamera up to vanilla when playing a vanilla cutscene :3 - if (c->cutscene != CUTSCENE_NONE) { - gPuppyCam.yawTarget = gCamera->yaw; - gPuppyCam.yaw = gCamera->yaw; - if (gMarioState->action == ACT_ENTERING_STAR_DOOR) { // god this is stupid and the fact I have to continue doing this is testament to the idiocy of the star door cutscene >:( - gPuppyCam.yawTarget = gMarioState->faceAngle[1] + 0x8000; - gPuppyCam.yaw = gMarioState->faceAngle[1] + 0x8000; - } - } - if (c->cutscene == CUTSCENE_NONE - && gPuppyCam.enabled - && gCurrentArea->camera->mode != CAMERA_MODE_INSIDE_CANNON) { - // Clear the recent cutscene after 8 frames - if (gRecentCutscene != CUTSCENE_NONE && sFramesSinceCutsceneEnded < 8) { - sFramesSinceCutsceneEnded++; - if (sFramesSinceCutsceneEnded >= 8) { - gRecentCutscene = CUTSCENE_NONE; - sFramesSinceCutsceneEnded = 0; - } - } - puppycam_loop(); - // Apply camera shakes - shake_camera_pitch(gLakituState.pos, gLakituState.focus); - shake_camera_yaw(gLakituState.pos, gLakituState.focus); - shake_camera_roll(&gLakituState.roll); - shake_camera_handheld(gLakituState.pos, gLakituState.focus); - - if ((sMarioCamState->action == ACT_DIVE) - && (gLakituState.lastFrameAction != ACT_DIVE)) { - set_camera_shake_from_hit(SHAKE_HIT_FROM_BELOW); - } - gLakituState.roll += sHandheldShakeRoll; - gLakituState.roll += gLakituState.keyDanceRoll; - } -#endif - gLakituState.lastFrameAction = sMarioCamState->action; - profiler_update(PROFILER_TIME_CAMERA, profiler_get_delta(PROFILER_DELTA_COLLISION) - first); -} - -/** - * Reset all the camera variables to their arcane defaults - */ -void reset_camera(struct Camera *c) { - gCamera = c; - s2ndRotateFlags = 0; - sStatusFlags = 0; - gCutsceneTimer = 0; - sCutsceneShot = 0; - gCutsceneObjSpawn = CUTSCENE_OBJ_NONE; - gObjCutsceneDone = FALSE; - gCutsceneFocus = NULL; - gSecondCameraFocus = NULL; - sCButtonsPressed = 0; - vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); - sModeTransition.framesLeft = 0; - gCameraMovementFlags = CAM_MOVE_INIT_CAMERA; - sStatusFlags = 0; - sCameraSoundFlags = 0; - sCUpCameraPitch = 0; - sModeOffsetYaw = 0; - sSpiralStairsYawOffset = 0; - sLakituDist = 0; - sLakituPitch = 0; - sAreaYaw = 0; - sAreaYawChange = 0.f; - sPanDistance = 0.f; - sCannonYOffset = 0.f; - sZoomAmount = 0.f; - sZeroZoomDist = 0.f; - sBehindMarioSoundTimer = 0; - sCSideButtonYaw = 0; - s8DirModeBaseYaw = 0; - s8DirModeYawOffset = 0; - c->doorStatus = DOOR_DEFAULT; - sMarioCamState->headRotation[0] = 0; - sMarioCamState->headRotation[1] = 0; - // sLuigiCamState->headRotation[0] = 0; - // sLuigiCamState->headRotation[1] = 0; - sMarioCamState->cameraEvent = CAM_EVENT_NONE; - sMarioCamState->usedObj = NULL; - gLakituState.shakeMagnitude[0] = 0; - gLakituState.shakeMagnitude[1] = 0; - gLakituState.shakeMagnitude[2] = 0; - gLakituState.unusedVec2[0] = 0; - gLakituState.unusedVec2[1] = 0; - gLakituState.unusedVec2[2] = 0; - gLakituState.unusedVec1[0] = 0.f; - gLakituState.unusedVec1[1] = 0.f; - gLakituState.unusedVec1[2] = 0.f; - gLakituState.lastFrameAction = 0; - set_fov_function(CAM_FOV_DEFAULT); - sFOVState.fov = 45.f; - sFOVState.fovOffset = 0.f; - sFOVState.unusedIsSleeping = 0; - sFOVState.shakeAmplitude = 0.f; - sFOVState.shakePhase = 0; - sObjectCutscene = CUTSCENE_NONE; - gRecentCutscene = CUTSCENE_NONE; -} - -void init_camera(struct Camera *c) { - struct Surface *floor = NULL; - Vec3f marioOffset; - s32 i; - - sCreditsPlayer2Pitch = 0; - sCreditsPlayer2Yaw = 0; - gPrevLevel = gCurrLevelArea / 16; - gCurrLevelArea = gCurrLevelNum * 16 + gCurrentArea->index; - sSelectionFlags &= CAM_MODE_MARIO_SELECTED; - sFramesPaused = 0; - gLakituState.mode = c->mode; - gLakituState.defMode = c->defMode; - gLakituState.posHSpeed = 0.3f; - gLakituState.posVSpeed = 0.3f; - gLakituState.focHSpeed = 0.8f; - gLakituState.focVSpeed = 0.3f; - gLakituState.roll = 0; - gLakituState.keyDanceRoll = 0; - gLakituState.unused = 0; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - vec3_zero(sCastleEntranceOffset); - vec3_zero(sPlayer2FocusOffset); - find_mario_floor_and_ceil(&sMarioGeometry); - sMarioGeometry.prevFloorHeight = sMarioGeometry.currFloorHeight; - sMarioGeometry.prevCeilHeight = sMarioGeometry.currCeilHeight; - sMarioGeometry.prevFloor = sMarioGeometry.currFloor; - sMarioGeometry.prevCeil = sMarioGeometry.currCeil; - sMarioGeometry.prevFloorType = sMarioGeometry.currFloorType; - sMarioGeometry.prevCeilType = sMarioGeometry.currCeilType; - for (i = 0; i < 32; i++) { - sCurCreditsSplinePos[i].index = -1; - sCurCreditsSplineFocus[i].index = -1; - } - sCutsceneSplineSegment = 0; - sCutsceneSplineSegmentProgress = 0.f; - sHandheldShakeInc = 0.f; - sHandheldShakeTimer = 0.f; - sHandheldShakeMag = 0; - for (i = 0; i < 4; i++) { - sHandheldShakeSpline[i].index = -1; - } - sHandheldShakePitch = 0; - sHandheldShakeYaw = 0; - sHandheldShakeRoll = 0; - c->cutscene = CUTSCENE_NONE; - marioOffset[0] = 0.f; - marioOffset[1] = 125.f; - marioOffset[2] = 400.f; - - // Set the camera's starting position or start a cutscene for certain levels - switch (gCurrLevelNum) { - // Calls the initial cutscene when you enter Bowser battle levels - // Note: This replaced an "old" way to call these cutscenes using - // a camEvent value: CAM_EVENT_BOWSER_INIT - case LEVEL_BOWSER_1: - // Since Bowser 1 has a demo entry, check for it - // If it is, then set CamAct to the end to directly activate Bowser - // If it isn't, then start cutscene - if (gCurrDemoInput == NULL) { - start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); - } else if (gSecondCameraFocus != NULL) { - gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_END; - } - break; - case LEVEL_BOWSER_2: - start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); - break; - case LEVEL_BOWSER_3: - start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); - break; - -#ifdef ENABLE_VANILLA_CAM_PROCESSING - //! Hardcoded position checks determine which cutscene to play when Mario enters castle grounds. - case LEVEL_CASTLE_GROUNDS: - if (is_within_100_units_of_mario(-1328.f, 260.f, 4664.f) != 1) { - marioOffset[0] = -400.f; - marioOffset[2] = -800.f; - } - if (is_within_100_units_of_mario(-6901.f, 2376.f, -6509.f) == 1) { - start_cutscene(c, CUTSCENE_EXIT_WATERFALL); - } - if (is_within_100_units_of_mario(5408.f, 4500.f, 3637.f) == 1) { - start_cutscene(c, CUTSCENE_EXIT_FALL_WMOTR); - } - gLakituState.mode = CAMERA_MODE_FREE_ROAM; - break; - case LEVEL_SA: - marioOffset[2] = 200.f; - break; - case LEVEL_CASTLE_COURTYARD: - marioOffset[2] = -300.f; - break; - case LEVEL_LLL: - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - break; - case LEVEL_CASTLE: - marioOffset[2] = 150.f; - break; - case LEVEL_RR: - vec3f_set(sFixedModeBasePosition, -2985.f, 478.f, -5568.f); - break; -#endif - } - if (c->mode == CAMERA_MODE_8_DIRECTIONS) { - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - } - switch (gCurrLevelArea) { -#ifdef ENABLE_VANILLA_CAM_PROCESSING - case AREA_SSL_EYEROK: - vec3f_set(marioOffset, 0.f, 500.f, -100.f); - break; - case AREA_CCM_SLIDE: - marioOffset[2] = -300.f; - break; - case AREA_THI_WIGGLER: - marioOffset[2] = -300.f; - break; - case AREA_SL_IGLOO: - marioOffset[2] = -300.f; - break; - case AREA_SL_OUTSIDE: - if (is_within_100_units_of_mario(257.f, 2150.f, 1399.f) == 1) { - marioOffset[2] = -300.f; - } - break; - case AREA_CCM_OUTSIDE: - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - break; - case AREA_TTM_OUTSIDE: - gLakituState.mode = CAMERA_MODE_RADIAL; - break; -#endif - } - - // Set the camera pos to marioOffset (relative to Mario), added to Mario's position - offset_rotated(c->pos, sMarioCamState->pos, marioOffset, sMarioCamState->faceAngle); - if (c->mode != CAMERA_MODE_BEHIND_MARIO) { - c->pos[1] = find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 100.f, - sMarioCamState->pos[2], &floor) + 125.f; - } - vec3f_copy(c->focus, sMarioCamState->pos); - vec3f_copy(gLakituState.curPos, c->pos); - vec3f_copy(gLakituState.curFocus, c->focus); - vec3f_copy(gLakituState.goalPos, c->pos); - vec3f_copy(gLakituState.goalFocus, c->focus); - vec3f_copy(gLakituState.pos, c->pos); - vec3f_copy(gLakituState.focus, c->focus); - if (c->mode == CAMERA_MODE_FIXED) { - set_fixed_cam_axis_sa_lobby(c->mode); - } - store_lakitu_cam_info_for_c_up(c); - gLakituState.yaw = calculate_yaw(c->focus, c->pos); - gLakituState.nextYaw = gLakituState.yaw; - c->yaw = gLakituState.yaw; - c->nextYaw = gLakituState.yaw; -#ifdef PUPPYCAM - puppycam_init(); -#endif -} - -/** - * Zooms out the camera if paused and the level is 'outside', as determined by sZoomOutAreaMasks. - * - * Because gCurrLevelArea is assigned gCurrLevelNum * 16 + gCurrentArea->index, - * dividing by 32 maps 2 levels to one index. - * - * areaBit definition: - * (gCurrLevelArea & 0x10) / 4): - * This adds 4 to the shift if the level is an odd multiple of 16 - * - * ((gCurrLevelArea & 0xF) - 1) & 3): - * This isolates the lower 16 'area' bits, subtracts 1 because areas are 1-indexed, and effectively - * modulo-4's the result, because each 8-bit mask only has 4 area bits for each level - */ -void zoom_out_if_paused_and_outside(struct GraphNodeCamera *camera) { - s16 yaw; - s32 areaMaskIndex = gCurrLevelArea / 32; - s32 areaBit = 1 << (((gCurrLevelArea & 0x10) / 4) + (((gCurrLevelArea & 0xF) - 1) & 3)); - - if (areaMaskIndex >= LEVEL_MAX / 2) { - areaMaskIndex = 0; - areaBit = 0; - } - if (gCameraMovementFlags & CAM_MOVE_PAUSE_SCREEN) { - if (sFramesPaused >= 2) { - if (sZoomOutAreaMasks[areaMaskIndex] & areaBit) { - - camera->focus[0] = gCamera->areaCenX; - camera->focus[1] = (sMarioCamState->pos[1] + gCamera->areaCenY) / 2; - camera->focus[2] = gCamera->areaCenZ; - vec3f_get_yaw(camera->focus, sMarioCamState->pos, &yaw); - vec3f_set_dist_and_angle(sMarioCamState->pos, camera->pos, 6000.f, 0x1000, yaw); -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelNum != LEVEL_THI) { - find_in_bounds_yaw_wdw_bob_thi(camera->pos, camera->focus, 0); - } -#endif - } - } else { - sFramesPaused++; - } - } else { - sFramesPaused = 0; - } -} - -void select_mario_cam_mode(void) { - sSelectionFlags = CAM_MODE_MARIO_SELECTED; -} - -/** - * Allocate the GraphNodeCamera's config.camera, and copy `c`'s focus to the Camera's area center point. - */ -void create_camera(struct GraphNodeCamera *gc, struct AllocOnlyPool *pool) { -#ifdef FORCED_CAMERA_MODE - gc->config.mode = FORCED_CAMERA_MODE; -#endif - s16 mode = gc->config.mode; - struct Camera *c = alloc_only_pool_alloc(pool, sizeof(struct Camera)); - - gc->config.camera = c; - c->mode = mode; - c->defMode = mode; - c->cutscene = CUTSCENE_NONE; - c->doorStatus = DOOR_DEFAULT; - c->areaCenX = gc->focus[0]; - c->areaCenY = gc->focus[1]; - c->areaCenZ = gc->focus[2]; - c->yaw = 0; - vec3f_copy(c->pos, gc->pos); - vec3f_copy(c->focus, gc->focus); -} - -/** - * Copy Lakitu's pos and foc into `gc` - */ -void update_graph_node_camera(struct GraphNodeCamera *gc) { - gc->rollScreen = gLakituState.roll; - vec3f_copy(gc->pos, gLakituState.pos); - vec3f_copy(gc->focus, gLakituState.focus); - zoom_out_if_paused_and_outside(gc); -} - -Gfx *geo_camera_main(s32 callContext, struct GraphNode *g, void *context) { - struct GraphNodeCamera *gc = (struct GraphNodeCamera *) g; - switch (callContext) { - case GEO_CONTEXT_CREATE: - create_camera(gc, context); - break; - case GEO_CONTEXT_RENDER: - update_graph_node_camera(gc); - break; - } - return NULL; -} - -void object_pos_to_vec3f(Vec3f dst, struct Object *obj) { - dst[0] = obj->oPosX; - dst[1] = obj->oPosY; - dst[2] = obj->oPosZ; -} - -void vec3f_to_object_pos(struct Object *obj, Vec3f src) { - obj->oPosX = src[0]; - obj->oPosY = src[1]; - obj->oPosZ = src[2]; -} - -/** - * Produces values using a cubic b-spline curve. Basically Q is the used output, - * u is a value between 0 and 1 that represents the position along the spline, - * and a0-a3 are parameters that define the spline. - * - * The spline is described at www2.cs.uregina.ca/~anima/408/Notes/Interpolation/UniformBSpline.htm - */ -void evaluate_cubic_spline(f32 u, Vec3f Q, Vec3f spline1, Vec3f spline2, Vec3f spline3, Vec3f spline4) { - f32 B[4]; - if (u > 1.0f) u = 1.0f; - - register f32 nu = 1.0f - u; - register f32 su = sqr(u); - register f32 hcu = (su * u) / 2.0f; - - B[0] = (nu * nu * nu) / 6.0f; - B[1] = hcu - su + (2.0f / 3.0f); - B[2] = -hcu + (su / 2.0f) + (u / 2.0f) + (1.0f / 6.0f); - B[3] = hcu / 3.0f; - - Q[0] = (B[0] * spline1[0]) + (B[1] * spline2[0]) + (B[2] * spline3[0]) + (B[3] * spline4[0]); - Q[1] = (B[0] * spline1[1]) + (B[1] * spline2[1]) + (B[2] * spline3[1]) + (B[3] * spline4[1]); - Q[2] = (B[0] * spline1[2]) + (B[1] * spline2[2]) + (B[2] * spline3[2]) + (B[3] * spline4[2]); -} - -/** - * Computes the point that is `progress` percent of the way through segment `splineSegment` of `spline`, - * and stores the result in `p`. `progress` and `splineSegment` are updated if `progress` becomes >= 1.0. - * - * When neither of the next two points' speeds == 0, the number of frames is between 1 and 255. Otherwise - * it's infinite. - * - * To calculate the number of frames it will take to progress through a spline segment: - * If the next two speeds are the same and nonzero, it's 1.0 / firstSpeed. - * - * s1 and s2 are short hand for first/secondSpeed. The progress at any frame n is defined by a recurrency relation: - * p(n+1) = (s2 - s1 + 1) * p(n) + s1 - * Which can be written as - * p(n) = (s2 * ((s2 - s1 + 1)^(n) - 1)) / (s2 - s1) - * - * Solving for the number of frames: - * n = log(((s2 - s1) / s1) + 1) / log(s2 - s1 + 1) - * - * @return 1 if the point has reached the end of the spline, when `progress` reaches 1.0 or greater, and - * the 4th CutsceneSplinePoint in the current segment away from spline[splineSegment] has an index of -1. - */ -s32 move_point_along_spline(Vec3f p, struct CutsceneSplinePoint spline[], s16 *splineSegment, f32 *progress) { - s32 finished = FALSE; - Vec3f controlPoints[4]; - s32 i = 0; - f32 u = *progress; - f32 progressChange; - f32 firstSpeed = 0; - f32 secondSpeed = 0; - s32 segment = *splineSegment; - - if (*splineSegment < 0) { - segment = 0; - u = 0; - } - if (spline[segment].index == -1 || spline[segment + 1].index == -1 || spline[segment + 2].index == -1) { - return 1; - } - - for (i = 0; i < 4; i++) { - controlPoints[i][0] = spline[segment + i].point[0]; - controlPoints[i][1] = spline[segment + i].point[1]; - controlPoints[i][2] = spline[segment + i].point[2]; - } - evaluate_cubic_spline(u, p, controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3]); - - if (spline[*splineSegment + 1].speed != 0) { - firstSpeed = 1.0f / spline[*splineSegment + 1].speed; - } - if (spline[*splineSegment + 2].speed != 0) { - secondSpeed = 1.0f / spline[*splineSegment + 2].speed; - } - progressChange = (secondSpeed - firstSpeed) * *progress + firstSpeed; - - if (1 <= (*progress += progressChange)) { - (*splineSegment)++; - if (spline[*splineSegment + 3].index == -1) { - *splineSegment = 0; - finished = 1; - } - (*progress)--; - } - return finished; -} - -/** - * If `selection` is 0, just get the current selection - * If `selection` is 1, select 'Mario' as the alt mode. - * If `selection` is 2, select 'fixed' as the alt mode. - * - * @return the current selection - */ -s32 cam_select_alt_mode(s32 selection) { - s32 mode = CAM_SELECTION_FIXED; - - if (selection == CAM_SELECTION_MARIO) { - if (!(sSelectionFlags & CAM_MODE_MARIO_SELECTED)) { - sSelectionFlags |= CAM_MODE_MARIO_SELECTED; - } - sCameraSoundFlags |= CAM_SOUND_UNUSED_SELECT_MARIO; - } - - // The alternate mode is up-close, but the player just selected fixed in the pause menu - if (selection == CAM_SELECTION_FIXED && (sSelectionFlags & CAM_MODE_MARIO_SELECTED)) { - // So change to normal mode in case the user paused in up-close mode - set_cam_angle(CAM_ANGLE_LAKITU); - sSelectionFlags &= ~CAM_MODE_MARIO_SELECTED; - sCameraSoundFlags |= CAM_SOUND_UNUSED_SELECT_FIXED; - } - - if (sSelectionFlags & CAM_MODE_MARIO_SELECTED) { - mode = CAM_SELECTION_MARIO; - } - return mode; -} - -/** - * Sets the camera angle to either Lakitu or Mario mode. Returns the current mode. - * - * If `mode` is 0, just returns the current mode. - * If `mode` is 1, start Mario mode - * If `mode` is 2, start Lakitu mode - */ -s32 set_cam_angle(s32 mode) { - s32 curMode = CAM_ANGLE_LAKITU; - - // Switch to Mario mode - if (mode == CAM_ANGLE_MARIO && !(sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { - sSelectionFlags |= CAM_MODE_MARIO_ACTIVE; - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - sSelectionFlags |= CAM_MODE_LAKITU_WAS_ZOOMED_OUT; - gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; - } - sCameraSoundFlags |= CAM_SOUND_MARIO_ACTIVE; - } - - // Switch back to normal mode - if (mode == CAM_ANGLE_LAKITU && (sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { - sSelectionFlags &= ~CAM_MODE_MARIO_ACTIVE; - if (sSelectionFlags & CAM_MODE_LAKITU_WAS_ZOOMED_OUT) { - sSelectionFlags &= ~CAM_MODE_LAKITU_WAS_ZOOMED_OUT; - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - } else { - gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; - } - sCameraSoundFlags |= CAM_SOUND_NORMAL_ACTIVE; - } - if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { - curMode = CAM_ANGLE_MARIO; - } - return curMode; -} - -/** - * Enables the handheld shake effect for this frame. - * - * @see shake_camera_handheld() - */ -void set_handheld_shake(u8 mode) { - switch (mode) { - // They're not in numerical order because that would be too simple... - case HAND_CAM_SHAKE_CUTSCENE: // Lowest increment - sHandheldShakeMag = 0x600; - sHandheldShakeInc = 0.04f; - break; - case HAND_CAM_SHAKE_LOW: // Lowest magnitude - sHandheldShakeMag = 0x300; - sHandheldShakeInc = 0.06f; - break; - case HAND_CAM_SHAKE_HIGH: // Highest mag and inc - sHandheldShakeMag = 0x1000; - sHandheldShakeInc = 0.1f; - break; - case HAND_CAM_SHAKE_UNUSED: // Never used - sHandheldShakeMag = 0x600; - sHandheldShakeInc = 0.07f; - break; - case HAND_CAM_SHAKE_HANG_OWL: // exactly the same as UNUSED... - sHandheldShakeMag = 0x600; - sHandheldShakeInc = 0.07f; - break; - case HAND_CAM_SHAKE_STAR_DANCE: // Slightly steadier than HANG_OWL and UNUSED - sHandheldShakeMag = 0x400; - sHandheldShakeInc = 0.07f; - break; - default: - sHandheldShakeMag = 0x0; - sHandheldShakeInc = 0.f; - } -} - -/** - * When sHandheldShakeMag is nonzero, this function adds small random offsets to `focus` every time - * sHandheldShakeTimer increases above 1.0, simulating the camera shake caused by unsteady hands. - * - * This function must be called every frame in order to actually apply the effect, since the effect's - * mag and inc are set to 0 every frame at the end of this function. - */ -void shake_camera_handheld(Vec3f pos, Vec3f focus) { - s32 i; - Vec3f shakeOffset; - Vec3f shakeSpline[4]; - f32 dist; - s16 pitch, yaw; - - if (sHandheldShakeMag == 0) { - vec3_zero(shakeOffset); - } else { - for (i = 0; i < 4; i++) { - shakeSpline[i][0] = sHandheldShakeSpline[i].point[0]; - shakeSpline[i][1] = sHandheldShakeSpline[i].point[1]; - shakeSpline[i][2] = sHandheldShakeSpline[i].point[2]; - } - evaluate_cubic_spline(sHandheldShakeTimer, shakeOffset, shakeSpline[0], - shakeSpline[1], shakeSpline[2], shakeSpline[3]); - if (1.f <= (sHandheldShakeTimer += sHandheldShakeInc)) { - // The first 3 control points are always (0,0,0), so the random spline is always just a - // straight line - for (i = 0; i < 3; i++) { - vec3s_copy(sHandheldShakeSpline[i].point, sHandheldShakeSpline[i + 1].point); - } - random_vec3s(sHandheldShakeSpline[3].point, sHandheldShakeMag, sHandheldShakeMag, sHandheldShakeMag / 2); - sHandheldShakeTimer -= 1.f; - - // Code dead, this is set to be 0 before it is used. - sHandheldShakeInc = random_float() * 0.5f; - if (sHandheldShakeInc < 0.02f) { - sHandheldShakeInc = 0.02f; - } - } - } - - approach_s16_asymptotic_bool(&sHandheldShakePitch, shakeOffset[0], 0x08); - approach_s16_asymptotic_bool(&sHandheldShakeYaw, shakeOffset[1], 0x08); - approach_s16_asymptotic_bool(&sHandheldShakeRoll, shakeOffset[2], 0x08); - - if (sHandheldShakePitch | sHandheldShakeYaw) { - vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); - pitch += sHandheldShakePitch; - yaw += sHandheldShakeYaw; - vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); - } - - // Unless called every frame, the effect will stop after the first time. - sHandheldShakeMag = 0; - sHandheldShakeInc = 0.0f; -} - -/** - * Updates C Button input state and stores it in `currentState` - */ -s32 find_c_buttons_pressed(u16 currentState, u16 buttonsPressed, u16 buttonsDown) { - buttonsPressed &= CBUTTON_MASK; - buttonsDown &= CBUTTON_MASK; - - if (buttonsPressed & L_CBUTTONS) { - currentState |= L_CBUTTONS; - currentState &= ~R_CBUTTONS; - } - if (!(buttonsDown & L_CBUTTONS)) { - currentState &= ~L_CBUTTONS; - } - - if (buttonsPressed & R_CBUTTONS) { - currentState |= R_CBUTTONS; - currentState &= ~L_CBUTTONS; - } - if (!(buttonsDown & R_CBUTTONS)) { - currentState &= ~R_CBUTTONS; - } - - if (buttonsPressed & U_CBUTTONS) { - currentState |= U_CBUTTONS; - currentState &= ~D_CBUTTONS; - } - if (!(buttonsDown & U_CBUTTONS)) { - currentState &= ~U_CBUTTONS; - } - - if (buttonsPressed & D_CBUTTONS) { - currentState |= D_CBUTTONS; - currentState &= ~U_CBUTTONS; - } - if (!(buttonsDown & D_CBUTTONS)) { - currentState &= ~D_CBUTTONS; - } - - return currentState; -} - -/** - * Determine which icon to show on the HUD - */ -s32 update_camera_hud_status(struct Camera *c) { - s16 status = CAM_STATUS_NONE; - - if (c->cutscene != CUTSCENE_NONE - || ((gPlayer1Controller->buttonDown & R_TRIG) && cam_select_alt_mode(0) == CAM_SELECTION_FIXED)) { - status |= CAM_STATUS_FIXED; - } else if (set_cam_angle(0) == CAM_ANGLE_MARIO) { - status |= CAM_STATUS_MARIO; - } else { - status |= CAM_STATUS_LAKITU; - } - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - status |= CAM_STATUS_C_DOWN; - } - if (gCameraMovementFlags & CAM_MOVE_C_UP_MODE) { - status |= CAM_STATUS_C_UP; - } - set_hud_camera_status(status); - return status; -} - -/** - * Check `pos` for collisions within `radius`, and update `pos` - * - * @return the number of collisions found - */ -s32 collide_with_walls(Vec3f pos, f32 offsetY, f32 radius) { - struct WallCollisionData collisionData; - struct Surface *wall = NULL; - f32 normX, normY, normZ; - f32 originOffset; - f32 offset; - f32 offsetAbsolute; - Vec3f newPos[MAX_REFERENCED_WALLS]; - s32 i; - s32 numCollisions = 0; - - collisionData.x = pos[0]; - collisionData.y = pos[1]; - collisionData.z = pos[2]; - collisionData.radius = radius; - collisionData.offsetY = offsetY; - numCollisions = find_wall_collisions(&collisionData); - if (numCollisions != 0) { - for (i = 0; i < collisionData.numWalls; i++) { - wall = collisionData.walls[collisionData.numWalls - 1]; - vec3f_copy(newPos[i], pos); - normX = wall->normal.x; - normY = wall->normal.y; - normZ = wall->normal.z; - originOffset = wall->originOffset; - offset = normX * newPos[i][0] + normY * newPos[i][1] + normZ * newPos[i][2] + originOffset; - offsetAbsolute = absf(offset); - if (offsetAbsolute < radius) { - newPos[i][0] += normX * (radius - offset); - newPos[i][2] += normZ * (radius - offset); - vec3f_copy(pos, newPos[i]); - } - } - } - return numCollisions; -} - -/** - * Compare a vector to a position, return TRUE if they match. - */ -s32 vec3f_compare(Vec3f pos, f32 posX, f32 posY, f32 posZ) { - return pos[0] == posX - && pos[1] == posY - && pos[2] == posZ; -} - -void clamp_pitch(Vec3f from, Vec3f to, s16 maxPitch, s16 minPitch) { - s16 pitch; - s16 yaw; - f32 dist; - - vec3f_get_dist_and_angle(from, to, &dist, &pitch, &yaw); - pitch = CLAMP(pitch, minPitch, maxPitch); - vec3f_set_dist_and_angle(from, to, dist, pitch, yaw); -} - -s32 is_within_100_units_of_mario(f32 posX, f32 posY, f32 posZ) { - Vec3f pos; - vec3f_set(pos, posX, posY, posZ); - - return calc_abs_dist_squared(sMarioCamState->pos, pos) < sqr(100.f); -} - -s32 set_or_approach_f32_asymptotic(f32 *dst, f32 goal, f32 scale) { - if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { - approach_f32_asymptotic_bool(dst, goal, scale); - } else { - *dst = goal; - } - if (*dst == goal) { - return FALSE; - } else { - return TRUE; - } -} - -/** - * Applies the approach_f32_asymptotic_bool function to each of the X, Y, & Z components of the given - * vector. - */ -void approach_vec3f_asymptotic(Vec3f current, Vec3f target, f32 xMul, f32 yMul, f32 zMul) { - approach_f32_asymptotic_bool(¤t[0], target[0], xMul); - approach_f32_asymptotic_bool(¤t[1], target[1], yMul); - approach_f32_asymptotic_bool(¤t[2], target[2], zMul); -} - -/** - * Applies the set_or_approach_f32_asymptotic_bool function to each of the X, Y, & Z components of the - * given vector. - */ -void set_or_approach_vec3f_asymptotic(Vec3f dst, Vec3f goal, f32 xMul, f32 yMul, f32 zMul) { - set_or_approach_f32_asymptotic(&dst[0], goal[0], xMul); - set_or_approach_f32_asymptotic(&dst[1], goal[1], yMul); - set_or_approach_f32_asymptotic(&dst[2], goal[2], zMul); -} - -/** - * Applies the approach_s32_asymptotic function to each of the X, Y, & Z components of the given - * vector. - */ -void approach_vec3s_asymptotic(Vec3s current, Vec3s target, s16 xMul, s16 yMul, s16 zMul) { - approach_s16_asymptotic_bool(¤t[0], target[0], xMul); - approach_s16_asymptotic_bool(¤t[1], target[1], yMul); - approach_s16_asymptotic_bool(¤t[2], target[2], zMul); -} - -s32 camera_approach_s16_symmetric_bool(s16 *current, s16 target, s16 increment) { - s16 dist = target - *current; - - if (increment < 0) { - increment = -1 * increment; - } - if (dist > 0) { - dist -= increment; - if (dist >= 0) { - *current = target - dist; - } else { - *current = target; - } - } else { - dist += increment; - if (dist <= 0) { - *current = target - dist; - } else { - *current = target; - } - } - if (*current == target) { - return FALSE; - } else { - return TRUE; - } -} - -s32 camera_approach_s16_symmetric(s16 current, s16 target, s16 increment) { - s16 dist = target - current; - - if (increment < 0) { - increment = -1 * increment; - } - if (dist > 0) { - dist -= increment; - if (dist >= 0) { - current = target - dist; - } else { - current = target; - } - } else { - dist += increment; - if (dist <= 0) { - current = target - dist; - } else { - current = target; - } - } - return current; -} - -s32 set_or_approach_s16_symmetric(s16 *current, s16 target, s16 increment) { - if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { - return camera_approach_s16_symmetric_bool(current, target, increment); - } else { - *current = target; - } - if (*current == target) { - return FALSE; - } else { - return TRUE; - } -} - -/** - * Approaches a value by a given increment, returns FALSE if the target is reached. - * Appears to be a strange way of implementing approach_f32_symmetric from object_helpers.c. - * It could possibly be an older version of the function - */ -s32 camera_approach_f32_symmetric_bool(f32 *current, f32 target, f32 increment) { - f32 dist = target - *current; - - if (increment < 0) { - increment = -1 * increment; - } - if (dist > 0) { - dist -= increment; - if (dist > 0) { - *current = target - dist; - } else { - *current = target; - } - } else { - dist += increment; - if (dist < 0) { - *current = target - dist; - } else { - *current = target; - } - } - if (*current == target) { - return FALSE; - } else { - return TRUE; - } -} - -/** - * Nearly the same as the above function, this one returns the new value in place of a bool. - */ -f32 camera_approach_f32_symmetric(f32 current, f32 target, f32 increment) { - f32 dist = target - current; - - if (increment < 0) { - increment = -1 * increment; - } - if (dist > 0) { - dist -= increment; - if (dist > 0) { - current = target - dist; - } else { - current = target; - } - } else { - dist += increment; - if (dist < 0) { - current = target - dist; - } else { - current = target; - } - } - return current; -} - -/** - * Generate a vector with all three values about zero. The - * three ranges determine how wide the range about zero. - */ -void random_vec3s(Vec3s dst, s16 xRange, s16 yRange, s16 zRange) { - f32 randomFloat; - f32 tempXRange; - f32 tempYRange; - f32 tempZRange; - - randomFloat = random_float(); - tempXRange = xRange; - dst[0] = randomFloat * tempXRange - tempXRange / 2; - - randomFloat = random_float(); - tempYRange = yRange; - dst[1] = randomFloat * tempYRange - tempYRange / 2; - - randomFloat = random_float(); - tempZRange = zRange; - dst[2] = randomFloat * tempZRange - tempZRange / 2; -} - -/** - * Decrease value by multiplying it by the distance from (`posX`, `posY`, `posZ`) to - * the camera divided by `maxDist` - * - * @return the reduced value - */ -s16 reduce_by_dist_from_camera(s16 value, f32 maxDist, f32 posX, f32 posY, f32 posZ) { - Vec3f pos; - f32 dist; - s16 pitch, yaw; - s16 goalPitch, goalYaw; - s16 result = 0; - // Direction from pos to (Lakitu's) goalPos - f32 goalDX = gLakituState.goalPos[0] - posX; - f32 goalDY = gLakituState.goalPos[1] - posY; - f32 goalDZ = gLakituState.goalPos[2] - posZ; - - dist = sqrtf(goalDX * goalDX + goalDY * goalDY + goalDZ * goalDZ); - if (maxDist > dist) { - pos[0] = posX; - pos[1] = posY; - pos[2] = posZ; - vec3f_get_dist_and_angle(gLakituState.goalPos, pos, &dist, &pitch, &yaw); - if (dist < maxDist) { - calculate_angles(gLakituState.goalPos, gLakituState.goalFocus, &goalPitch, &goalYaw); - pitch -= goalPitch; - yaw -= goalYaw; - dist -= 2000.f; - if (dist < 0.f) { - dist = 0.f; - } - maxDist -= 2000.f; - if (maxDist < 2000.f) { - maxDist = 2000.f; - } - result = value * (1.f - dist / maxDist); - if (pitch < -0x1800 || pitch > 0x400 || - yaw < -0x1800 || yaw > 0x1800) { - result /= 2; - } - } - } - return result; -} - -s32 clamp_positions_and_find_yaw(Vec3f pos, Vec3f origin, f32 xMax, f32 xMin, f32 zMax, f32 zMin) { - s16 yaw = gCamera->nextYaw; - - if (pos[0] >= xMax) { - pos[0] = xMax; - } - if (pos[0] <= xMin) { - pos[0] = xMin; - } - if (pos[2] >= zMax) { - pos[2] = zMax; - } - if (pos[2] <= zMin) { - pos[2] = zMin; - } - yaw = calculate_yaw(origin, pos); - return yaw; -} - -/** - * The yaw passed here is the yaw of the direction FROM Mario TO Lakitu. - * - * wallYaw always has 90 degrees added to it before this is called -- it's parallel to the wall. - * - * @return the new yaw from Mario to rotate towards. - * - * @warning this is jank. It actually returns the yaw that will rotate further INTO the wall. So, the - * developers just add 180 degrees to the result. - */ -s32 calc_avoid_yaw(s16 yawFromMario, s16 wallYaw) { - s16 yawDiff; - yawDiff = wallYaw - yawFromMario + DEGREES(90); - - if (yawDiff < 0) { - // Deflect to the right - yawFromMario = wallYaw; - } else { - // Note: this favors the left side if the wall is exactly perpendicular to the camera. - // Deflect to the left - yawFromMario = wallYaw + DEGREES(180); - } - return yawFromMario; -} - -/** - * Checks if `surf` is within the rect prism defined by xMax, yMax, and zMax - * - * @param surf surface to check - * @param xMax absolute-value max size in x, set to -1 to ignore - * @param yMax absolute-value max size in y, set to -1 to ignore - * @param zMax absolute-value max size in z, set to -1 to ignore - */ -s32 is_surf_within_bounding_box(struct Surface *surf, f32 xMax, f32 yMax, f32 zMax) { - // Surface vertex coordinates - Vec3s sx, sy, sz; - // Max delta between x, y, and z - s16 dxMax = 0; - s16 dyMax = 0; - s16 dzMax = 0; - // Current deltas between x, y, and z - f32 dx, dy, dz; - s32 i, j; - // result - s32 smaller = FALSE; - - sx[0] = surf->vertex1[0]; - sx[1] = surf->vertex2[0]; - sx[2] = surf->vertex3[0]; - sy[0] = surf->vertex1[1]; - sy[1] = surf->vertex2[1]; - sy[2] = surf->vertex3[1]; - sz[0] = surf->vertex1[2]; - sz[1] = surf->vertex2[2]; - sz[2] = surf->vertex3[2]; - - for (i = 0; i < 3; i++) { - j = i + 1; - if (j >= 3) { - j = 0; - } - dx = abss(sx[i] - sx[j]); - if (dx > dxMax) { - dxMax = dx; - } - dy = abss(sy[i] - sy[j]); - if (dy > dyMax) { - dyMax = dy; - } - dz = abss(sz[i] - sz[j]); - if (dz > dzMax) { - dzMax = dz; - } - } - if (yMax != -1.f) { - if (dyMax < yMax) { - smaller = TRUE; - } - } - if (xMax != -1.f && zMax != -1.f) { - if (dxMax < xMax && dzMax < zMax) { - smaller = TRUE; - } - } - return smaller; -} - -/** - * Checks if `pos` is behind the surface, using the dot product. - * - * Because the function only uses `surf`s first vertex, some surfaces can shadow others. - */ -s32 is_behind_surface(Vec3f pos, struct Surface *surf) { - s32 behindSurface = 0; - // Surface normal - f32 normX = (surf->vertex2[1] - surf->vertex1[1]) * (surf->vertex3[2] - surf->vertex2[2]) - - (surf->vertex3[1] - surf->vertex2[1]) * (surf->vertex2[2] - surf->vertex1[2]); - f32 normY = (surf->vertex2[2] - surf->vertex1[2]) * (surf->vertex3[0] - surf->vertex2[0]) - - (surf->vertex3[2] - surf->vertex2[2]) * (surf->vertex2[0] - surf->vertex1[0]); - f32 normZ = (surf->vertex2[0] - surf->vertex1[0]) * (surf->vertex3[1] - surf->vertex2[1]) - - (surf->vertex3[0] - surf->vertex2[0]) * (surf->vertex2[1] - surf->vertex1[1]); - f32 dirX = surf->vertex1[0] - pos[0]; - f32 dirY = surf->vertex1[1] - pos[1]; - f32 dirZ = surf->vertex1[2] - pos[2]; - - if (dirX * normX + dirY * normY + dirZ * normZ < 0) { - behindSurface = 1; - } - return behindSurface; -} - -/** - * Checks if the whole circular sector is behind the surface. - */ -s32 is_range_behind_surface(Vec3f from, Vec3f to, struct Surface *surf, s16 range, s16 surfType) { - s32 behindSurface = TRUE; - s32 leftBehind = 0; - s32 rightBehind = 0; - f32 checkDist; - s16 checkPitch; - s16 checkYaw; - Vec3f checkPos; - - if (surf != NULL) { - if (surfType == SURFACE_NULL || surf->type != surfType) { - if (range == 0) { - behindSurface = is_behind_surface(to, surf); - } else { - vec3f_get_dist_and_angle(from, to, &checkDist, &checkPitch, &checkYaw); - vec3f_set_dist_and_angle(from, checkPos, checkDist, checkPitch, checkYaw + range); - leftBehind = is_behind_surface(checkPos, surf); - vec3f_set_dist_and_angle(from, checkPos, checkDist, checkPitch, checkYaw - range); - rightBehind = is_behind_surface(checkPos, surf); - behindSurface = leftBehind * rightBehind; - } - } - } - return behindSurface; -} - -s32 is_mario_behind_surface(UNUSED struct Camera *c, struct Surface *surf) { - s32 behindSurface = is_behind_surface(sMarioCamState->pos, surf); - - return behindSurface; -} - -/** - * Calculates the distance between two points and sets a vector to a point - * scaled along a line between them. Typically, somewhere in the middle. - */ -void scale_along_line(Vec3f dst, Vec3f from, Vec3f to, f32 scale) { - dst[0] = (to[0] - from[0]) * scale + from[0]; - dst[1] = (to[1] - from[1]) * scale + from[1]; - dst[2] = (to[2] - from[2]) * scale + from[2]; -} -/** - * Effectively created a rectangular prism defined by a vector starting at the center - * and extending to the corners. If the position is in this box, the function returns true. - */ -s32 is_pos_in_bounds(Vec3f pos, Vec3f center, Vec3f bounds, s16 boundsYaw) { - Vec3f rel; - vec3_diff(rel, center, pos); - - rotate_in_xz(rel, rel, boundsYaw); - - return (-bounds[0] < rel[0] && rel[0] < bounds[0] && - -bounds[1] < rel[1] && rel[1] < bounds[1] && - -bounds[2] < rel[2] && rel[2] < bounds[2]); -} - -s16 calculate_pitch(Vec3f from, Vec3f to) { - f32 dx = to[0] - from[0]; - f32 dy = to[1] - from[1]; - f32 dz = to[2] - from[2]; - s16 pitch = atan2s(sqrtf(dx * dx + dz * dz), dy); - - return pitch; -} - -s16 calculate_yaw(Vec3f from, Vec3f to) { - f32 dx = to[0] - from[0]; - // UNUSED f32 dy = to[1] - from[1]; - f32 dz = to[2] - from[2]; - - return atan2s(dz, dx); -} - -/** - * Calculates the pitch and yaw between two vectors. - */ -void calculate_angles(Vec3f from, Vec3f to, s16 *pitch, s16 *yaw) { - f32 dx = to[0] - from[0]; - f32 dy = to[1] - from[1]; - f32 dz = to[2] - from[2]; - - *pitch = atan2s(sqrtf(sqr(dx) + sqr(dz)), dy); - *yaw = atan2s(dz, dx); -} - -/** - * Finds the distance between two vectors. - */ -f32 calc_abs_dist(Vec3f a, Vec3f b) { - register f32 distX = b[0] - a[0]; - register f32 distY = b[1] - a[1]; - register f32 distZ = b[2] - a[2]; - - return sqrtf(sqr(distX) + sqr(distY) + sqr(distZ)); -} - -f32 calc_abs_dist_squared(Vec3f a, Vec3f b) { - register f32 distX = b[0] - a[0]; - register f32 distY = b[1] - a[1]; - register f32 distZ = b[2] - a[2]; - - return (sqr(distX) + sqr(distY) + sqr(distZ)); -} - -/** - * Finds the horizontal distance between two vectors. - */ -f32 calc_hor_dist(Vec3f a, Vec3f b) { - register f32 distX = b[0] - a[0]; - register f32 distZ = b[2] - a[2]; - - return sqrtf(sqr(distX) + sqr(distZ)); -} - -/** - * Rotates a vector in the horizontal plane and copies it to a new vector. - */ -void rotate_in_xz(Vec3f dst, Vec3f src, s16 yaw) { - register f32 x = src[0]; - register f32 z = src[2]; - register f32 sy = sins(yaw); - register f32 cy = coss(yaw); - - dst[0] = z * sy + x * cy; - dst[1] = src[1]; - dst[2] = z * cy - x * sy; -} - -/** - * Rotates a vector in the YZ plane and copies it to a new vector. - * - * Note: This function also flips the Z axis, so +Z moves forward, not backward like it would in world - * space. If possible, use vec3f_set_dist_and_angle() - */ -void rotate_in_yz(Vec3f dst, Vec3f src, s16 pitch) { - dst[2] = -(src[2] * coss(pitch) - src[1] * sins(pitch)); - dst[1] = src[2] * sins(pitch) + src[1] * coss(pitch); - dst[0] = src[0]; -} - -/** - * Start shaking the camera's pitch (up and down) - */ -void set_camera_pitch_shake(s16 mag, s16 decay, s16 inc) { - if (gLakituState.shakeMagnitude[0] < mag) { - gLakituState.shakeMagnitude[0] = mag; - gLakituState.shakePitchDecay = decay; - gLakituState.shakePitchVel = inc; - } -} - -/** - * Start shaking the camera's yaw (side to side) - */ -void set_camera_yaw_shake(s16 mag, s16 decay, s16 inc) { - if (abss(mag) > abss(gLakituState.shakeMagnitude[1])) { - gLakituState.shakeMagnitude[1] = mag; - gLakituState.shakeYawDecay = decay; - gLakituState.shakeYawVel = inc; - } -} - -/** - * Start shaking the camera's roll (rotate screen clockwise and counterclockwise) - */ -void set_camera_roll_shake(s16 mag, s16 decay, s16 inc) { - if (gLakituState.shakeMagnitude[2] < mag) { - gLakituState.shakeMagnitude[2] = mag; - gLakituState.shakeRollDecay = decay; - gLakituState.shakeRollVel = inc; - } -} - -/** - * Start shaking the camera's pitch, but reduce `mag` by it's distance from the camera - */ -void set_pitch_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ) { - mag = reduce_by_dist_from_camera(mag, maxDist, posX, posY, posZ); - if (mag != 0) { - set_camera_pitch_shake(mag, decay, inc); - } -} - -/** - * Start shaking the camera's yaw, but reduce `mag` by it's distance from the camera - */ -void set_yaw_shake_from_point(s16 mag, s16 decay, s16 inc, f32 maxDist, f32 posX, f32 posY, f32 posZ) { - mag = reduce_by_dist_from_camera(mag, maxDist, posX, posY, posZ); - if (mag != 0) { - set_camera_yaw_shake(mag, decay, inc); - } -} - -/** - * Update the shake offset by `increment` - */ -void increment_shake_offset(s16 *offset, s16 increment) { - if (increment == -0x8000) { - *offset = (*offset & 0x8000) + 0xC000; - } else { - *offset += increment; - } -} - -/** - * Apply a vertical shake to the camera by adjusting its pitch - */ -void shake_camera_pitch(Vec3f pos, Vec3f focus) { - f32 dist; - s16 pitch, yaw; - - if (gLakituState.shakeMagnitude[0] | gLakituState.shakeMagnitude[1]) { - vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); - pitch += gLakituState.shakeMagnitude[0] * sins(gLakituState.shakePitchPhase); - vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); - increment_shake_offset(&gLakituState.shakePitchPhase, gLakituState.shakePitchVel); - if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[0], 0, - gLakituState.shakePitchDecay) == 0) { - gLakituState.shakePitchPhase = 0; - } - } -} - -/** - * Apply a horizontal shake to the camera by adjusting its yaw - */ -void shake_camera_yaw(Vec3f pos, Vec3f focus) { - f32 dist; - s16 pitch, yaw; - - if (gLakituState.shakeMagnitude[1] != 0) { - vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); - yaw += gLakituState.shakeMagnitude[1] * sins(gLakituState.shakeYawPhase); - vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); - increment_shake_offset(&gLakituState.shakeYawPhase, gLakituState.shakeYawVel); - if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[1], 0, - gLakituState.shakeYawDecay) == 0) { - gLakituState.shakeYawPhase = 0; - } - } -} - -/** - * Apply a rotational shake to the camera by adjusting its roll - */ -void shake_camera_roll(s16 *roll) { - if (gLakituState.shakeMagnitude[2] != 0) { - increment_shake_offset(&gLakituState.shakeRollPhase, gLakituState.shakeRollVel); - *roll += gLakituState.shakeMagnitude[2] * sins(gLakituState.shakeRollPhase); - if (camera_approach_s16_symmetric_bool(&gLakituState.shakeMagnitude[2], 0, - gLakituState.shakeRollDecay) == 0) { - gLakituState.shakeRollPhase = 0; - } - } -} - -/** - * Add an offset to the camera's yaw, used in levels that are inside a rectangular building, like the - * pyramid or TTC. - */ -s32 offset_yaw_outward_radial(struct Camera *c, s16 areaYaw) { - s16 yawGoal = DEGREES(60); - s16 yaw = sModeOffsetYaw; - Vec3f areaCenter; - s16 dYaw; - switch (gCurrLevelArea) { - case AREA_TTC: - areaCenter[0] = c->areaCenX; - areaCenter[1] = sMarioCamState->pos[1]; - areaCenter[2] = c->areaCenZ; - if (sqr(800.f) > calc_abs_dist_squared(areaCenter, sMarioCamState->pos)) { - yawGoal = 0x3800; - } - break; - case AREA_SSL_PYRAMID: - // This mask splits the 360 degrees of yaw into 4 corners. It adds 45 degrees so that the yaw - // offset at the corner will be 0, but the yaw offset near the center will face more towards - // the direction Mario is running in. - yawGoal = (areaYaw & 0xC000) - areaYaw + DEGREES(45); - if (yawGoal < 0) { - yawGoal = -yawGoal; - } - yawGoal = yawGoal / 32 * 48; - break; - case AREA_LLL_OUTSIDE: - yawGoal = 0; - break; - } - dYaw = gMarioStates[0].forwardVel / 32.f * 128.f; - - if (sAreaYawChange < 0) { - camera_approach_s16_symmetric_bool(&yaw, -yawGoal, dYaw); - } - if (sAreaYawChange > 0) { - camera_approach_s16_symmetric_bool(&yaw, yawGoal, dYaw); - } - // When the final yaw is out of [-60,60] degrees, approach yawGoal faster than dYaw will ever be, - // making the camera lock in one direction until yawGoal drops below 60 (or Mario presses a C button) - if (yaw < -DEGREES(60)) { - //! Maybe they meant to reverse yawGoal's sign? - camera_approach_s16_symmetric_bool(&yaw, -yawGoal, 0x200); - } - if (yaw > DEGREES(60)) { - //! Maybe they meant to reverse yawGoal's sign? - camera_approach_s16_symmetric_bool(&yaw, yawGoal, 0x200); - } - return yaw; -} - -/** - * Plays the background music that starts while peach reads the intro message. - */ -void cutscene_intro_peach_play_message_music(void) { - play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, SEQ_EVENT_PEACH_MESSAGE), 0); -} - -/** - * Plays the music that starts after peach fades and Lakitu appears. - */ -void cutscene_intro_peach_play_lakitu_flying_music(void) { - play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(15, SEQ_EVENT_CUTSCENE_INTRO), 0); -} - -void play_camera_buzz_if_cdown(void) { - if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { - play_sound_button_change_blocked(); - } -} - -void play_camera_buzz_if_cbutton(void) { - if (gPlayer1Controller->buttonPressed & CBUTTON_MASK) { - play_sound_button_change_blocked(); - } -} - -void play_camera_buzz_if_c_sideways(void) { - if (gPlayer1Controller->buttonPressed & (L_CBUTTONS | R_CBUTTONS)) { - play_sound_button_change_blocked(); - } -} - -void play_sound_cbutton_up(void) { - play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gGlobalSoundSource); -} - -void play_sound_cbutton_down(void) { - play_sound(SOUND_MENU_CAMERA_ZOOM_OUT, gGlobalSoundSource); -} - -void play_sound_cbutton_side(void) { - play_sound(SOUND_MENU_CAMERA_TURN, gGlobalSoundSource); -} - -void play_sound_button_change_blocked(void) { - play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource); -} - -void play_sound_rbutton_changed(void) { - play_sound(SOUND_MENU_CLICK_CHANGE_VIEW, gGlobalSoundSource); -} - -void play_sound_if_cam_switched_to_lakitu_or_mario(void) { - if (sCameraSoundFlags & CAM_SOUND_MARIO_ACTIVE) { - play_sound_rbutton_changed(); - } - if (sCameraSoundFlags & CAM_SOUND_NORMAL_ACTIVE) { - play_sound_rbutton_changed(); - } - sCameraSoundFlags &= ~(CAM_SOUND_MARIO_ACTIVE | CAM_SOUND_NORMAL_ACTIVE); -} - -/** - * Handles input for radial, outwards radial, parallel tracking, and 8 direction mode. - */ -void radial_camera_input(struct Camera *c) { - if ((gCameraMovementFlags & CAM_MOVE_ENTERED_ROTATE_SURFACE) || !(gCameraMovementFlags & CAM_MOVE_ROTATE)) { - - // If C-L or C-R are pressed, the camera is rotating - if (gPlayer1Controller->buttonPressed & (L_CBUTTONS | R_CBUTTONS)) { - gCameraMovementFlags &= ~CAM_MOVE_ENTERED_ROTATE_SURFACE; - // @bug this does not clear the rotation flags set by the surface. It's possible to set - // both ROTATE_LEFT and ROTATE_RIGHT, locking the camera. - // Ex: If a surface set CAM_MOVE_ROTATE_RIGHT and the user presses C-R, it locks the - // camera until a different mode is activated - } - - // Rotate Right and left - if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { - if (sModeOffsetYaw > -0x800) { - // The camera is now rotating right - if (!(gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT)) { - gCameraMovementFlags |= CAM_MOVE_ROTATE_RIGHT; - } - - if (c->mode == CAMERA_MODE_RADIAL) { - // if > ~48 degrees, we're rotating for the second time. - if (sModeOffsetYaw > 0x22AA) { - s2ndRotateFlags |= CAM_MOVE_ROTATE_RIGHT; - } - - if (sModeOffsetYaw == DEGREES(105)) { - play_sound_button_change_blocked(); - } else { - play_sound_cbutton_side(); - } - } else { - if (sModeOffsetYaw == DEGREES(60)) { - play_sound_button_change_blocked(); - } else { - play_sound_cbutton_side(); - } - } - } else { - gCameraMovementFlags |= CAM_MOVE_RETURN_TO_MIDDLE; - play_sound_cbutton_up(); - } - } - if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { - if (sModeOffsetYaw < 0x800) { - if (!(gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT)) { - gCameraMovementFlags |= CAM_MOVE_ROTATE_LEFT; - } - - if (c->mode == CAMERA_MODE_RADIAL) { - // if < ~48 degrees, we're rotating for the second time. - if (sModeOffsetYaw < -0x22AA) { - s2ndRotateFlags |= CAM_MOVE_ROTATE_LEFT; - } - - if (sModeOffsetYaw == DEGREES(-105)) { - play_sound_button_change_blocked(); - } else { - play_sound_cbutton_side(); - } - } else { - if (sModeOffsetYaw == DEGREES(-60)) { - play_sound_button_change_blocked(); - } else { - play_sound_cbutton_side(); - } - } - } else { - gCameraMovementFlags |= CAM_MOVE_RETURN_TO_MIDDLE; - play_sound_cbutton_up(); - } - } - } - - // Zoom in / enter C-Up - if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; - play_sound_cbutton_up(); - } else { - set_mode_c_up(c); - } - } - - // Zoom out - if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - gCameraMovementFlags |= CAM_MOVE_ALREADY_ZOOMED_OUT; - play_camera_buzz_if_cdown(); - } else { - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - play_sound_cbutton_down(); - } - } -} - -/** - * Starts a cutscene dialog. Only has an effect when `trigger` is 1 - */ -void trigger_cutscene_dialog(s32 trigger) { - if (trigger == 1) start_object_cutscene_without_focus(CUTSCENE_READ_MESSAGE); -} - -/** - * Updates the camera based on which C buttons are pressed this frame - */ -void handle_c_button_movement(struct Camera *c) { - s16 cSideYaw; - - // Zoom in - if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { - if (c->mode != CAMERA_MODE_FIXED && (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT)) { - gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; - play_sound_cbutton_up(); - } else { - set_mode_c_up(c); - if (sZeroZoomDist > gCameraZoomDist) { - sZoomAmount = -gCameraZoomDist; - } else { - sZoomAmount = gCameraZoomDist; - } - } - } - if (c->mode != CAMERA_MODE_FIXED) { - // Zoom out - if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - gCameraMovementFlags |= CAM_MOVE_ALREADY_ZOOMED_OUT; - sZoomAmount = gCameraZoomDist + 400.f; - play_camera_buzz_if_cdown(); - } else { - gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; - sZoomAmount = gCameraZoomDist + 400.f; - play_sound_cbutton_down(); - } - } - - // Rotate left or right - cSideYaw = 0x1000; - if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { - if (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) { - gCameraMovementFlags &= ~CAM_MOVE_ROTATE_LEFT; - } else { - gCameraMovementFlags |= CAM_MOVE_ROTATE_RIGHT; - if (sCSideButtonYaw == 0) { - play_sound_cbutton_side(); - } - sCSideButtonYaw = -cSideYaw; - } - } - if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { - if (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) { - gCameraMovementFlags &= ~CAM_MOVE_ROTATE_RIGHT; - } else { - gCameraMovementFlags |= CAM_MOVE_ROTATE_LEFT; - if (sCSideButtonYaw == 0) { - play_sound_cbutton_side(); - } - sCSideButtonYaw = cSideYaw; - } - } - } -} - -/** - * Zero the 10 cvars. - */ -void clear_cutscene_vars(UNUSED struct Camera *c) { - s32 i; - - for (i = 0; i < 10; i++) { - sCutsceneVars[i].unused1 = 0; - vec3_zero(sCutsceneVars[i].point); - vec3_zero(sCutsceneVars[i].unusedPoint); - vec3_zero(sCutsceneVars[i].angle); - sCutsceneVars[i].unused2 = 0; - } -} - -/** - * Start the cutscene, `cutscene`, if it is not already playing. - */ -void start_cutscene(struct Camera *c, u8 cutscene) { - if (c->cutscene != cutscene) { - c->cutscene = cutscene; - clear_cutscene_vars(c); - } -} - -/** - * Look up the victory dance cutscene in sDanceCutsceneTable - * - * First the index entry is determined based on the course and the star that was just picked up - * Like the entries in sZoomOutAreaMasks, each entry represents two stars - * The current courses's 4 bits of the index entry are used as the actual index into sDanceCutsceneTable - * - * @return the victory cutscene to use - */ -s32 determine_dance_cutscene(UNUSED struct Camera *c) { -#ifdef NON_STOP_STARS - return CUTSCENE_DANCE_DEFAULT; -#else - u8 cutscene = CUTSCENE_NONE; - u8 cutsceneIndex = 0; - u8 starIndex = (gLastCompletedStarNum - 1) / 2; - u8 courseNum = gCurrCourseNum; - - if (starIndex > 3) { - starIndex = 0; - } - if (courseNum > COURSE_MAX) { - courseNum = COURSE_NONE; - } - cutsceneIndex = sDanceCutsceneIndexTable[courseNum][starIndex]; - - if (gLastCompletedStarNum & 1) { - // Odd stars take the lower four bytes - cutsceneIndex &= 0xF; - } else { - // Even stars use the upper four bytes - cutsceneIndex = cutsceneIndex >> 4; - } - cutscene = sDanceCutsceneTable[cutsceneIndex]; - return cutscene; -#endif -} - -/** - * @return `pullResult` or `pushResult` depending on Mario's door action - */ -u8 open_door_cutscene(u8 pullResult, u8 pushResult) { - if (sMarioCamState->action == ACT_PULLING_DOOR) { - return pullResult; - } - if (sMarioCamState->action == ACT_PUSHING_DOOR) { - return pushResult; - } - return CUTSCENE_NONE; -} - -/** - * If no cutscenes are playing, determines if a cutscene should play based on Mario's action and - * cameraEvent - * - * @return the cutscene that should start, 0 if none - */ -u8 get_cutscene_from_mario_status(struct Camera *c) { - u8 cutscene = c->cutscene; - - if (cutscene == CUTSCENE_NONE) { - // A cutscene started by an object, if any, will start if nothing else happened - cutscene = sObjectCutscene; - sObjectCutscene = CUTSCENE_NONE; - if (sMarioCamState->cameraEvent == CAM_EVENT_DOOR) { - switch (gCurrLevelArea) { - case AREA_CASTLE_LOBBY: - //! doorStatus is never DOOR_ENTER_LOBBY when cameraEvent == 6, because - //! doorStatus is only used for the star door in the lobby, which uses - //! ACT_ENTERING_STAR_DOOR - if (c->mode == CAMERA_MODE_SPIRAL_STAIRS || c->mode == CAMERA_MODE_CLOSE || c->doorStatus == DOOR_ENTER_LOBBY) { - cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL_MODE, CUTSCENE_DOOR_PUSH_MODE); - } else { - cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); - } - break; - case AREA_BBH: - //! Castle Lobby uses 0 to mean 'no special modes', but BBH uses 1... - if (c->doorStatus == DOOR_LEAVING_SPECIAL) { - cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); - } else { - cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL_MODE, CUTSCENE_DOOR_PUSH_MODE); - } - break; - default: - cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); - break; - } - } - if (sMarioCamState->cameraEvent == CAM_EVENT_DOOR_WARP) { - cutscene = CUTSCENE_DOOR_WARP; - } - if (sMarioCamState->cameraEvent == CAM_EVENT_CANNON) { - cutscene = CUTSCENE_ENTER_CANNON; - } - if (SURFACE_IS_PAINTING_WARP(sMarioGeometry.currFloorType)) { - cutscene = CUTSCENE_ENTER_PAINTING; - } - switch (sMarioCamState->action) { - case ACT_DEATH_EXIT: - cutscene = CUTSCENE_DEATH_EXIT; - break; - case ACT_EXIT_AIRBORNE: - cutscene = CUTSCENE_EXIT_PAINTING_SUCC; - break; - case ACT_SPECIAL_EXIT_AIRBORNE: - if (gPrevLevel == LEVEL_BOWSER_1 || gPrevLevel == LEVEL_BOWSER_2 - || gPrevLevel == LEVEL_BOWSER_3) { - cutscene = CUTSCENE_EXIT_BOWSER_SUCC; - } else { - cutscene = CUTSCENE_EXIT_SPECIAL_SUCC; - } - break; - case ACT_SPECIAL_DEATH_EXIT: - if (gPrevLevel == LEVEL_BOWSER_1 || gPrevLevel == LEVEL_BOWSER_2 - || gPrevLevel == LEVEL_BOWSER_3) { - cutscene = CUTSCENE_EXIT_BOWSER_DEATH; - } else { - cutscene = CUTSCENE_NONPAINTING_DEATH; - } - break; - case ACT_ENTERING_STAR_DOOR: - if (c->doorStatus == DOOR_DEFAULT) { - cutscene = CUTSCENE_SLIDING_DOORS_OPEN; - } else { - cutscene = CUTSCENE_DOOR_PULL_MODE; - } - break; - case ACT_UNLOCKING_KEY_DOOR: - cutscene = CUTSCENE_UNLOCK_KEY_DOOR; - break; - case ACT_WATER_DEATH: - cutscene = CUTSCENE_WATER_DEATH; - break; - case ACT_DEATH_ON_BACK: - cutscene = CUTSCENE_DEATH_ON_BACK; - break; - case ACT_DEATH_ON_STOMACH: - cutscene = CUTSCENE_DEATH_ON_STOMACH; - break; - case ACT_STANDING_DEATH: - cutscene = CUTSCENE_STANDING_DEATH; - break; - case ACT_SUFFOCATION: - cutscene = CUTSCENE_SUFFOCATION_DEATH; - break; - case ACT_QUICKSAND_DEATH: - cutscene = CUTSCENE_QUICKSAND_DEATH; - break; - case ACT_ELECTROCUTION: - cutscene = CUTSCENE_STANDING_DEATH; - break; - case ACT_STAR_DANCE_EXIT: - cutscene = determine_dance_cutscene(c); - break; - case ACT_STAR_DANCE_WATER: - cutscene = determine_dance_cutscene(c); - break; - case ACT_STAR_DANCE_NO_EXIT: - cutscene = CUTSCENE_DANCE_DEFAULT; - break; - } - switch (sMarioCamState->cameraEvent) { - case CAM_EVENT_START_INTRO: - cutscene = CUTSCENE_INTRO_PEACH; - break; - case CAM_EVENT_START_GRAND_STAR: - cutscene = CUTSCENE_GRAND_STAR; - break; - case CAM_EVENT_START_ENDING: - cutscene = CUTSCENE_ENDING; - break; - case CAM_EVENT_START_END_WAVING: - cutscene = CUTSCENE_END_WAVING; - break; - case CAM_EVENT_START_CREDITS: - cutscene = CUTSCENE_CREDITS; - break; - } - } - //! doorStatus is reset every frame. CameraTriggers need to constantly set doorStatus - c->doorStatus = DOOR_DEFAULT; - - return cutscene; -} - -/** - * Moves the camera when Mario has triggered a warp - */ -void warp_camera(f32 displacementX, f32 displacementY, f32 displacementZ) { - Vec3f displacement; - struct MarioState *marioStates = &gMarioStates[0]; - struct LinearTransitionPoint *start = &sModeInfo.transitionStart; - struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; - - gCurrLevelArea = gCurrLevelNum * 16 + gCurrentArea->index; - displacement[0] = displacementX; - displacement[1] = displacementY; - displacement[2] = displacementZ; - vec3f_add(gLakituState.curPos, displacement); - vec3f_add(gLakituState.curFocus, displacement); - vec3f_add(gLakituState.goalPos, displacement); - vec3f_add(gLakituState.goalFocus, displacement); - marioStates->waterLevel += displacementY; - - vec3f_add(start->focus, displacement); - vec3f_add(start->pos, displacement); - vec3f_add(end->focus, displacement); - vec3f_add(end->pos, displacement); -} - -/** - * Make the camera's y coordinate approach `goal`, - * unless smooth movement is off, in which case the y coordinate is simply set to `goal` - */ -void approach_camera_height(struct Camera *c, f32 goal, f32 inc) { - if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { - if (c->pos[1] < goal) { - if ((c->pos[1] += inc) > goal) { - c->pos[1] = goal; - } - } else { - if ((c->pos[1] -= inc) < goal) { - c->pos[1] = goal; - } - } - } else { - c->pos[1] = goal; - } -} - -/** - * Set the camera's focus to Mario's position, and add several relative offsets. - * - * @param leftRight offset to Mario's left/right, relative to his faceAngle - * @param yOff y offset - * @param forwBack offset to Mario's front/back, relative to his faceAngle - * @param yawOff offset to Mario's faceAngle, changes the direction of `leftRight` and `forwBack` - */ -void set_focus_rel_mario(struct Camera *c, f32 leftRight, f32 yOff, f32 forwBack, s16 yawOff) { - s16 yaw; - f32 focFloorYOff; - - calc_y_to_curr_floor(&focFloorYOff, 1.f, 200.f, &focFloorYOff, 0.9f, 200.f); - yaw = sMarioCamState->faceAngle[1] + yawOff; - c->focus[2] = sMarioCamState->pos[2] + forwBack * coss(yaw) - leftRight * sins(yaw); - c->focus[0] = sMarioCamState->pos[0] + forwBack * sins(yaw) + leftRight * coss(yaw); - c->focus[1] = sMarioCamState->pos[1] + yOff + focFloorYOff; -} - -/** - * Set the camera's position to Mario's position, and add several relative offsets. Unused. - * - * @param leftRight offset to Mario's left/right, relative to his faceAngle - * @param yOff y offset - * @param forwBack offset to Mario's front/back, relative to his faceAngle - * @param yawOff offset to Mario's faceAngle, changes the direction of `leftRight` and `forwBack` - */ -UNUSED static void unused_set_pos_rel_mario(struct Camera *c, f32 leftRight, f32 yOff, f32 forwBack, s16 yawOff) { - u16 yaw = sMarioCamState->faceAngle[1] + yawOff; - - c->pos[0] = sMarioCamState->pos[0] + forwBack * sins(yaw) + leftRight * coss(yaw); - c->pos[1] = sMarioCamState->pos[1] + yOff; - c->pos[2] = sMarioCamState->pos[2] + forwBack * coss(yaw) - leftRight * sins(yaw); -} - -/** - * Rotates the offset `to` according to the pitch and yaw values in `rotation`. - * Adds `from` to the rotated offset, and stores the result in `dst`. - * - * @warning Flips the Z axis, so that relative to `rotation`, -Z moves forwards and +Z moves backwards. - */ -void offset_rotated(Vec3f dst, Vec3f from, Vec3f to, Vec3s rotation) { - Vec3f pitchRotated; - - // First rotate the direction by rotation's pitch - //! The Z axis is flipped here. - pitchRotated[2] = -(to[2] * coss(rotation[0]) - to[1] * sins(rotation[0])); - pitchRotated[1] = to[2] * sins(rotation[0]) + to[1] * coss(rotation[0]); - pitchRotated[0] = to[0]; - - // Rotate again by rotation's yaw - dst[0] = from[0] + pitchRotated[2] * sins(rotation[1]) + pitchRotated[0] * coss(rotation[1]); - dst[1] = from[1] + pitchRotated[1]; - dst[2] = from[2] + pitchRotated[2] * coss(rotation[1]) - pitchRotated[0] * sins(rotation[1]); -} - -/** - * Rotates the offset defined by (`xTo`, `yTo`, `zTo`) according to the pitch and yaw values in `rotation`. - * Adds `from` to the rotated offset, and stores the result in `dst`. - * - * @warning Flips the Z axis, so that relative to `rotation`, -Z moves forwards and +Z moves backwards. - */ -void offset_rotated_coords(Vec3f dst, Vec3f from, Vec3s rotation, f32 xTo, f32 yTo, f32 zTo) { - Vec3f to; - - vec3f_set(to, xTo, yTo, zTo); - offset_rotated(dst, from, to, rotation); -} - -void determine_pushing_or_pulling_door(s16 *rotation) { - if (sMarioCamState->action == ACT_PULLING_DOOR) { - *rotation = 0; - } else { - *rotation = DEGREES(-180); - } -} - -/** - * Calculate Lakitu's next position and focus, according to gCamera's state, - * and store them in `newPos` and `newFoc`. - * - * @param newPos where Lakitu should fly towards this frame - * @param newFoc where Lakitu should look towards this frame - * - * @param curPos gCamera's pos this frame - * @param curFoc gCamera's foc this frame - * - * @param oldPos gCamera's pos last frame - * @param oldFoc gCamera's foc last frame - * - * @return Lakitu's next yaw, which is the same as the yaw passed in if no transition happened - */ -s16 next_lakitu_state(Vec3f newPos, Vec3f newFoc, Vec3f curPos, Vec3f curFoc, - Vec3f oldPos, Vec3f oldFoc, s16 yaw) { - s16 yawVelocity; - s16 pitchVelocity; - f32 distVelocity; - f32 goalDist; - s16 goalPitch; - s16 goalYaw; - f32 distTimer = sModeTransition.framesLeft; - s16 angleTimer = sModeTransition.framesLeft; - Vec3f nextPos; - Vec3f nextFoc; - Vec3f startPos; - Vec3f startFoc; - s32 i; - f32 floorHeight; - struct Surface *floor; - - // If not transitioning, just use gCamera's current pos and foc - vec3f_copy(newPos, curPos); - vec3f_copy(newFoc, curFoc); - - if (sStatusFlags & CAM_FLAG_START_TRANSITION) { - for (i = 0; i < 3; i++) { - // Add Mario's displacement from this frame to the last frame's pos and focus - // Makes the transition start from where the camera would have moved - startPos[i] = oldPos[i] + sMarioCamState->pos[i] - sModeTransition.marioPos[i]; - startFoc[i] = oldFoc[i] + sMarioCamState->pos[i] - sModeTransition.marioPos[i]; - } - - - vec3f_get_dist_and_angle(curFoc, startFoc, &sModeTransition.focDist, &sModeTransition.focPitch, - &sModeTransition.focYaw); - vec3f_get_dist_and_angle(curFoc, startPos, &sModeTransition.posDist, &sModeTransition.posPitch, - &sModeTransition.posYaw); - sStatusFlags &= ~CAM_FLAG_START_TRANSITION; - } - - // Transition from the last mode to the current one - if (sModeTransition.framesLeft > 0) { - vec3f_get_dist_and_angle(curFoc, curPos, &goalDist, &goalPitch, &goalYaw); - distVelocity = abss(goalDist - sModeTransition.posDist) / distTimer; - pitchVelocity = abss(goalPitch - sModeTransition.posPitch) / angleTimer; - yawVelocity = abss(goalYaw - sModeTransition.posYaw) / angleTimer; - - camera_approach_f32_symmetric_bool(&sModeTransition.posDist, goalDist, distVelocity); - camera_approach_s16_symmetric_bool(&sModeTransition.posYaw, goalYaw, yawVelocity); - camera_approach_s16_symmetric_bool(&sModeTransition.posPitch, goalPitch, pitchVelocity); - vec3f_set_dist_and_angle(curFoc, nextPos, sModeTransition.posDist, sModeTransition.posPitch, - sModeTransition.posYaw); - - vec3f_get_dist_and_angle(curPos, curFoc, &goalDist, &goalPitch, &goalYaw); - pitchVelocity = sModeTransition.focPitch / (s16) sModeTransition.framesLeft; - yawVelocity = sModeTransition.focYaw / (s16) sModeTransition.framesLeft; - distVelocity = sModeTransition.focDist / sModeTransition.framesLeft; - - camera_approach_s16_symmetric_bool(&sModeTransition.focPitch, goalPitch, pitchVelocity); - camera_approach_s16_symmetric_bool(&sModeTransition.focYaw, goalYaw, yawVelocity); - camera_approach_f32_symmetric_bool(&sModeTransition.focDist, 0, distVelocity); - vec3f_set_dist_and_angle(curFoc, nextFoc, sModeTransition.focDist, sModeTransition.focPitch, - sModeTransition.focYaw); - - vec3f_copy(newFoc, nextFoc); - vec3f_copy(newPos, nextPos); - - if (gCamera->cutscene != 0 || !(gCameraMovementFlags & CAM_MOVE_C_UP_MODE)) { - floorHeight = find_floor(newPos[0], newPos[1], newPos[2], &floor); - if (floorHeight != FLOOR_LOWER_LIMIT) { - if ((floorHeight += 125.f) > newPos[1]) { - newPos[1] = floorHeight; - } - } - f32_find_wall_collision(&newPos[0], &newPos[1], &newPos[2], 0.f, 100.f); - } - sModeTransition.framesLeft--; - yaw = calculate_yaw(newFoc, newPos); - } else { - sModeTransition.posDist = 0.f; - sModeTransition.posPitch = 0; - sModeTransition.posYaw = 0; - sStatusFlags &= ~CAM_FLAG_TRANSITION_OUT_OF_C_UP; - } - vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); - return yaw; -} - -static UNUSED void stop_transitional_movement(void) { - sStatusFlags &= ~(CAM_FLAG_START_TRANSITION | CAM_FLAG_TRANSITION_OUT_OF_C_UP); - sModeTransition.framesLeft = 0; -} - -/** - * Start fixed camera mode, setting the base position to (`x`, `y`, `z`) - * - * @return TRUE if the base pos was updated - */ -s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { - s32 basePosSet = FALSE; - f32 posX = x; - f32 posY = y; - f32 posZ = z; - - if (sFixedModeBasePosition[0] != posX || sFixedModeBasePosition[1] != posY - || sFixedModeBasePosition[2] != posZ) { - basePosSet = TRUE; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - } - vec3f_set(sFixedModeBasePosition, posX, posY, posZ); - if (c->mode != CAMERA_MODE_FIXED) { - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - c->mode = CAMERA_MODE_FIXED; - vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], - sFixedModeBasePosition[2]); - } - return basePosSet; -} - -void set_camera_mode_8_directions(struct Camera *c) { - if (c->mode != CAMERA_MODE_8_DIRECTIONS) { - c->mode = CAMERA_MODE_8_DIRECTIONS; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - s8DirModeBaseYaw = 0; - s8DirModeYawOffset = 0; - } -} - -/** - * If the camera mode is not already the boss fight camera (camera with two foci) - * set it to be so. - */ -void set_camera_mode_boss_fight(struct Camera *c) { - if (c->mode != CAMERA_MODE_BOSS_FIGHT) { - transition_to_camera_mode(c, CAMERA_MODE_BOSS_FIGHT, 15); - sModeOffsetYaw = c->nextYaw - DEGREES(45); - } -} - -void set_camera_mode_close_cam(u8 *mode) { - if (*mode != CAMERA_MODE_CLOSE) { - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - *mode = CAMERA_MODE_CLOSE; - } -} - -/** - * Change to radial mode. - * If the difference in yaw between pos -> Mario and pos > focus is < 90 degrees, transition. - * Otherwise jump to radial mode. - */ -void set_camera_mode_radial(struct Camera *c, s16 transitionTime) { - Vec3f focus; - s16 yaw; - - focus[0] = c->areaCenX; - focus[1] = sMarioCamState->pos[1]; - focus[2] = c->areaCenZ; - if (c->mode != CAMERA_MODE_RADIAL) { - yaw = calculate_yaw(focus, sMarioCamState->pos) - calculate_yaw(c->focus, c->pos) + DEGREES(90); - if (yaw > 0) { - transition_to_camera_mode(c, CAMERA_MODE_RADIAL, transitionTime); - } else { - c->mode = CAMERA_MODE_RADIAL; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - } - sModeOffsetYaw = 0; - } -} - -/** - * Start parallel tracking mode using the path `path` - */ -void parallel_tracking_init(struct Camera *c, struct ParallelTrackingPoint *path) { - if (c->mode != CAMERA_MODE_PARALLEL_TRACKING) { - sParTrackPath = path; - sParTrackIndex = 0; - sParTrackTransOff.pos[0] = 0.f; - sParTrackTransOff.pos[1] = 0.f; - sParTrackTransOff.pos[2] = 0.f; - // Place the camera in the middle of the path - c->pos[0] = (sParTrackPath[0].pos[0] + sParTrackPath[1].pos[0]) / 2; - c->pos[1] = (sParTrackPath[0].pos[1] + sParTrackPath[1].pos[1]) / 2; - c->pos[2] = (sParTrackPath[0].pos[2] + sParTrackPath[1].pos[2]) / 2; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - c->mode = CAMERA_MODE_PARALLEL_TRACKING; - } -} - -/** - * Set the fixed camera base pos depending on the current level area - */ -void set_fixed_cam_axis_sa_lobby(UNUSED s16 preset) { - switch (gCurrLevelArea) { - case AREA_SA: - vec3f_set(sFixedModeBasePosition, 646.f, 143.f, -1513.f); - break; - - case AREA_CASTLE_LOBBY: - vec3f_set(sFixedModeBasePosition, -577.f, 143.f, 1443.f); - break; - } -} - -/** - * Block area-specific CameraTrigger and special surface modes. - * Generally, block area mode changes if: - * Mario is wearing the metal cap, or at the water's surface, or the camera is in Mario mode - * - * However, if the level is WDW, DDD, or COTMC (levels that have metal cap and water): - * Only block area mode changes if Mario is in a cannon, - * or if the camera is in Mario mode and Mario is not swimming or in water with the metal cap - */ -#ifdef ENABLE_VANILLA_CAM_PROCESSING -void check_blocking_area_processing(const u8 *mode) { - if (sMarioCamState->action & ACT_FLAG_METAL_WATER || - *mode == DEEP_WATER_CAMERA_MODE || *mode == WATER_SURFACE_CAMERA_MODE) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - } - - if (gCurrLevelNum == LEVEL_DDD || gCurrLevelNum == LEVEL_WDW || gCurrLevelNum == LEVEL_COTMC) { - sStatusFlags &= ~CAM_FLAG_BLOCK_AREA_PROCESSING; - } - - if ((*mode == DEEP_WATER_CAMERA_MODE && - !(sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))) || - *mode == CAMERA_MODE_INSIDE_CANNON) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - } -#else -void check_blocking_area_processing(UNUSED const u8 *mode) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; -#endif -} - -void cam_rr_exit_building_side(struct Camera *c) { - set_camera_mode_8_directions(c); - s8DirModeBaseYaw = DEGREES(90); -} - -void cam_rr_exit_building_top(struct Camera *c) { - set_camera_mode_8_directions(c); - if (c->pos[1] < 6343.f) { - c->pos[1] = 7543.f; - gLakituState.goalPos[1] = c->pos[1]; - gLakituState.curPos[1] = c->pos[1]; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - } -} - -void cam_rr_enter_building_window(struct Camera *c) { - if (c->mode != CAMERA_MODE_FIXED) { - set_camera_mode_fixed(c, -2974, 478, -3975); - } -} - -void cam_rr_enter_building(struct Camera *c) { - if (c->mode != CAMERA_MODE_FIXED) { - set_camera_mode_fixed(c, -2953, 798, -3943); - } - // Prevent the camera from being above the roof - if (c->pos[1] > 6043.f) { - c->pos[1] = 6043.f; - } -} - -void cam_rr_enter_building_side(struct Camera *c) { - if (c->mode != CAMERA_MODE_FIXED) { - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - c->mode = CAMERA_MODE_FIXED; - } -} - -/** - * Fix the camera in place as Mario gets exits out the MC cave into the waterfall. - */ -void cam_cotmc_exit_waterfall(UNUSED struct Camera *c) { - gCameraMovementFlags |= CAM_MOVE_FIX_IN_PLACE; -} - -/** - * Sets 8 directional mode and blocks the next trigger from processing. - * Activated when Mario is walking in front of the snowman's head. - */ -void cam_sl_snowman_head_8dir(struct Camera *c) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 60); - s8DirModeBaseYaw = 0x1D27; -} - -/** - * Sets free roam mode in SL, called by a trigger that covers a large area and surrounds the 8 direction - * trigger. - */ -void cam_sl_free_roam(struct Camera *c) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 60); -} - -/** - * Warps the camera underneath the floor, used in HMC to move under the elevator platforms - */ -void move_camera_through_floor_while_descending(struct Camera *c, f32 height) { - if ((sMarioGeometry.currFloorHeight < height - 100.f) - && (sMarioGeometry.prevFloorHeight > sMarioGeometry.currFloorHeight)) { - c->pos[1] = height - 400.f; - gLakituState.curPos[1] = height - 400.f; - gLakituState.goalPos[1] = height - 400.f; - } -} - -void cam_hmc_enter_maze(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - if (c->pos[1] > -102.f) { - vec3f_get_dist_and_angle(c->focus, gLakituState.goalPos, &dist, &pitch, &yaw); - vec3f_set_dist_and_angle(c->focus, gLakituState.goalPos, 300.f, pitch, yaw); - gLakituState.goalPos[1] = -800.f; - c->pos[1] = gLakituState.goalPos[1]; - gLakituState.curPos[1] = gLakituState.goalPos[1]; - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - } -} - -void cam_hmc_elevator_black_hole(struct Camera *c) { - move_camera_through_floor_while_descending(c, 1536.f); -} - -void cam_hmc_elevator_maze_emergency_exit(struct Camera *c) { - move_camera_through_floor_while_descending(c, 2355.f); -} - -void cam_hmc_elevator_lake(struct Camera *c) { - move_camera_through_floor_while_descending(c, 1843.f); -} - -void cam_hmc_elevator_maze(struct Camera *c) { - move_camera_through_floor_while_descending(c, 1843.f); -} - -/** - * Starts the "Enter Pyramid Top" cutscene. - */ -void cam_ssl_enter_pyramid_top(UNUSED struct Camera *c) { - start_object_cutscene_without_focus(CUTSCENE_ENTER_PYRAMID_TOP); -} - -/** - * Change to close mode in the center of the pyramid. Outside this trigger, the default mode is outwards - * radial. - */ -void cam_ssl_pyramid_center(struct Camera *c) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); -} - -/** - * Changes the mode back to outward radial in the boss room inside the pyramid. - */ -void cam_ssl_boss_room(struct Camera *c) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - transition_to_camera_mode(c, CAMERA_MODE_OUTWARD_RADIAL, 90); -} - -/** - * Moves the camera to through the tunnel by forcing sModeOffsetYaw - */ -void cam_thi_move_cam_through_tunnel(UNUSED struct Camera *c) { - if (sModeOffsetYaw < DEGREES(60)) { - sModeOffsetYaw = DEGREES(60); - } -} - -/** - * Aligns the camera to look through the tunnel - */ -void cam_thi_look_through_tunnel(UNUSED struct Camera *c) { - // ~82.5 degrees - if (sModeOffsetYaw > 0x3AAA) { - sModeOffsetYaw = 0x3AAA; - } -} - -/** - * Unused. Changes the camera to radial mode when Mario is on the tower. - * - * @see sCamBOB for bounds. - */ -void cam_bob_tower(struct Camera *c) { - sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; - transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 90); -} - -/** - * Unused. Changes the camera to free roam mode when Mario is not climbing the tower. - * - * This is the only CameraTrigger event that uses the area == -1 feature: - * If this was used, it would be called by default in BoB. - * - * @see sCamBOB - */ -void cam_bob_default_free_roam(struct Camera *c) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); -} - -/** - * Starts the pool entrance cutscene if Mario is not exiting the pool. - * Used in both the castle and HMC. - */ -void cam_castle_hmc_start_pool_cutscene(struct Camera *c) { - if ((sMarioCamState->action != ACT_SPECIAL_DEATH_EXIT) - && (sMarioCamState->action != ACT_SPECIAL_EXIT_AIRBORNE)) { - start_cutscene(c, CUTSCENE_ENTER_POOL); - } -} - -/** - * Sets the fixed mode pos offset so that the camera faces the doorway when Mario is near the entrance - * to the castle lobby - */ -void cam_castle_lobby_entrance(UNUSED struct Camera *c) { - vec3f_set(sCastleEntranceOffset, -813.f - sFixedModeBasePosition[0], - 378.f - sFixedModeBasePosition[1], 1103.f - sFixedModeBasePosition[2]); -} - -/** - * Make the camera look up the stairs from the 2nd to 3rd floor of the castle - */ -void cam_castle_look_upstairs(struct Camera *c) { - struct Surface *floor; - f32 floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); - - // If Mario is on the first few steps, fix the camera pos, making it look up - if ((sMarioGeometry.currFloorHeight > 1229.f) && (floorHeight < 1229.f) - && (sCSideButtonYaw == 0)) { - vec3f_set(c->pos, -227.f, 1425.f, 1533.f); - } -} - -/** - * Make the camera look down the stairs towards the basement star door - */ -void cam_castle_basement_look_downstairs(struct Camera *c) { - struct Surface *floor; - f32 floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor); - - // Fix the camera pos, making it look downwards. Only active on the top few steps - if ((floorHeight > -110.f) && (sCSideButtonYaw == 0)) { - vec3f_set(c->pos, -980.f, 249.f, -1398.f); - } -} - -/** - * Enter the fixed-mode castle lobby. A trigger for this is placed in every entrance so that the camera - * changes to fixed mode. - */ -void cam_castle_enter_lobby(struct Camera *c) { - if (c->mode != CAMERA_MODE_FIXED) { - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - set_fixed_cam_axis_sa_lobby(c->mode); - c->mode = CAMERA_MODE_FIXED; - } -} - -/** - * Starts spiral stairs mode. - */ -void cam_castle_enter_spiral_stairs(struct Camera *c) { - transition_to_camera_mode(c, CAMERA_MODE_SPIRAL_STAIRS, 20); -} - -/** - * unused, starts close mode if the camera is in spiral stairs mode. - * This was replaced with cam_castle_close_mode - */ -static UNUSED void cam_castle_leave_spiral_stairs(struct Camera *c) { - if (c->mode == CAMERA_MODE_SPIRAL_STAIRS) { - transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 30); - } else { - set_camera_mode_close_cam(&c->mode); - } -} - -/** - * The default mode when outside of the lobby and spiral staircase. A trigger for this is placed at - * every door leaving the lobby and spiral staircase. - */ -void cam_castle_close_mode(struct Camera *c) { - set_camera_mode_close_cam(&c->mode); -} - -/** - * Functions the same as cam_castle_close_mode, but sets doorStatus so that the camera will enter - * fixed-mode when Mario leaves the room. - */ -void cam_castle_leave_lobby_sliding_door(struct Camera *c) { - cam_castle_close_mode(c); - c->doorStatus = DOOR_ENTER_LOBBY; -} - -/** - * Just calls cam_castle_enter_lobby - */ -void cam_castle_enter_lobby_sliding_door(struct Camera *c) { - cam_castle_enter_lobby(c); -} - -void cam_bbh_room_6(struct Camera *c) { - parallel_tracking_init(c, sBBHLibraryParTrackPath); -} - -void cam_bbh_fall_off_roof(struct Camera *c) { - set_camera_mode_close_cam(&c->mode); -} - -void cam_bbh_fall_into_pool(struct Camera *c) { - Vec3f dir; - set_camera_mode_close_cam(&c->mode); - vec3f_set(dir, 0.f, 0.f, 300.f); - offset_rotated(gLakituState.goalPos, sMarioCamState->pos, dir, sMarioCamState->faceAngle); - gLakituState.goalPos[1] = -2300.f; - vec3f_copy(c->pos, gLakituState.goalPos); - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; -} - -void cam_bbh_room_1(struct Camera *c) { - set_camera_mode_fixed(c, 956, 440, 1994); -} - -void cam_bbh_leave_front_door(struct Camera *c) { - c->doorStatus = DOOR_LEAVING_SPECIAL; - cam_bbh_room_1(c); -} - -void cam_bbh_room_2_lower(struct Camera *c) { - set_camera_mode_fixed(c, 2591, 400, 1284); -} - -void cam_bbh_room_4(struct Camera *c) { - set_camera_mode_fixed(c, 3529, 340, -1384); -} - -void cam_bbh_room_8(struct Camera *c) { - set_camera_mode_fixed(c, -500, 740, -1306); -} - -/** - * In BBH's room 5's library (the first floor room with the vanish cap/boo painting) - * set the camera mode to fixed and position to (-2172, 200, 675) - */ -void cam_bbh_room_5_library(struct Camera *c) { - set_camera_mode_fixed(c, -2172, 200, 675); -} - -/** - * In BBH's room 5 (the first floor room with the vanish cap/boo painting) - * set the camera mode to to the hidden room's position - * if coming from the library. - */ -void cam_bbh_room_5_library_to_hidden_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, -2172, 200, 675) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_5_hidden_to_library_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, -1542, 320, -307) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_5_hidden(struct Camera *c) { - c->doorStatus = DOOR_LEAVING_SPECIAL; - set_camera_mode_fixed(c, -1542, 320, -307); -} - -void cam_bbh_room_3(struct Camera *c) { - set_camera_mode_fixed(c, -1893, 320, 2327); -} - -void cam_bbh_room_7_mr_i(struct Camera *c) { - set_camera_mode_fixed(c, 1371, 360, -1302); -} - -void cam_bbh_room_7_mr_i_to_coffins_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, 1371, 360, -1302) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_7_coffins_to_mr_i_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, 2115, 260, -772) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_elevator_room_lower(struct Camera *c) { - c->doorStatus = DOOR_LEAVING_SPECIAL; - set_camera_mode_close_cam(&c->mode); -} - -void cam_bbh_room_0_back_entrance(struct Camera *c) { - set_camera_mode_close_cam(&c->mode); -} - -void cam_bbh_elevator(struct Camera *c) { - if (c->mode == CAMERA_MODE_FIXED) { - set_camera_mode_close_cam(&c->mode); - c->pos[1] = -405.f; - gLakituState.goalPos[1] = -405.f; - } -} - -void cam_bbh_room_12_upper(struct Camera *c) { - c->doorStatus = DOOR_LEAVING_SPECIAL; - set_camera_mode_fixed(c, -2932, 296, 4429); -} - -void cam_bbh_enter_front_door(struct Camera *c) { - set_camera_mode_close_cam(&c->mode); -} - -void cam_bbh_room_2_library(struct Camera *c) { - set_camera_mode_fixed(c, 3493, 440, 617); -} - -void cam_bbh_room_2_library_to_trapdoor_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, 3493, 440, 617) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_2_trapdoor(struct Camera *c) { - set_camera_mode_fixed(c, 3502, 440, 1217); -} - -void cam_bbh_room_2_trapdoor_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, 3502, 440, 1217) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_9_attic(struct Camera *c) { - set_camera_mode_fixed(c, -670, 460, 372); -} - -void cam_bbh_room_9_attic_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, -670, 460, 372) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_9_mr_i_transition(struct Camera *c) { - if (set_camera_mode_fixed(c, 131, 380, -263) == 1) { - transition_next_state(c, 20); - } -} - -void cam_bbh_room_13_balcony(struct Camera *c) { - set_camera_mode_fixed(c, 210, 420, 3109); -} - -void cam_bbh_room_0(struct Camera *c) { - c->doorStatus = DOOR_LEAVING_SPECIAL; - set_camera_mode_fixed(c, -204, 807, 204); -} - -void cam_ccm_enter_slide_shortcut(UNUSED struct Camera *c) { - sStatusFlags |= CAM_FLAG_CCM_SLIDE_SHORTCUT; -} - -void cam_ccm_leave_slide_shortcut(UNUSED struct Camera *c) { - sStatusFlags &= ~CAM_FLAG_CCM_SLIDE_SHORTCUT; -} - -/** - * Apply any modes that are triggered by special floor surface types - */ -u32 surface_type_modes(struct Camera *c) { - u32 modeChanged = 0; - - switch (sMarioGeometry.currFloorType) { - case SURFACE_CLOSE_CAMERA: - transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); - modeChanged++; - break; - - case SURFACE_CAMERA_FREE_ROAM: - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); - modeChanged++; - break; - - case SURFACE_NO_CAM_COL_SLIPPERY: - transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); - modeChanged++; - break; - } - return modeChanged; -} - -/** - * Set the camera mode to `mode` if Mario is not standing on a special surface - */ -u32 set_mode_if_not_set_by_surface(struct Camera *c, u8 mode) { - u32 modeChanged = surface_type_modes(c); - - if ((modeChanged == 0) && (mode != 0)) { - transition_to_camera_mode(c, mode, 90); - } - - return modeChanged; -} - -/** - * Used in THI, check if Mario is standing on any of the special surfaces in that area - */ -void surface_type_modes_thi(struct Camera *c) { - switch (sMarioGeometry.currFloorType) { - case SURFACE_CLOSE_CAMERA: - if (c->mode != CAMERA_MODE_CLOSE) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); - } - break; - - case SURFACE_CAMERA_FREE_ROAM: - if (c->mode != CAMERA_MODE_CLOSE) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); - } - break; - - case SURFACE_NO_CAM_COL_SLIPPERY: - if (c->mode != CAMERA_MODE_CLOSE) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); - } - break; - - case SURFACE_CAMERA_8_DIR: - transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 90); - break; - - default: - transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 90); - } -} - -/** - * Terminates a list of CameraTriggers. - */ -#define NULL_TRIGGER \ - { 0, NULL, 0, 0, 0, 0, 0, 0, 0 } - -/** - * The SL triggers operate camera behavior in front of the snowman who blows air. - * The first sets a 8 direction mode, while the latter (which encompasses the former) - * sets free roam mode. - * - * This behavior is exploitable, since the ranges assume that Mario must pass through the latter on - * exit. Using hyperspeed, the earlier area can be directly exited from, keeping the changes it applies. - */ -struct CameraTrigger sCamSL[] = { - { 1, cam_sl_snowman_head_8dir, 1119, 3584, 1125, 1177, 358, 358, -0x1D27 }, - // This trigger surrounds the previous one - { 1, cam_sl_free_roam, 1119, 3584, 1125, 4096, 4096, 4096, -0x1D27 }, - NULL_TRIGGER -}; - -/** - * The THI triggers are specifically for the tunnel near the start of the Huge Island. - * The first helps the camera from getting stuck on the starting side, the latter aligns with the - * tunnel. Both sides achieve their effect by editing the camera yaw. - */ -struct CameraTrigger sCamTHI[] = { - { 1, cam_thi_move_cam_through_tunnel, -4609, -2969, 6448, 100, 300, 300, 0 }, - { 1, cam_thi_look_through_tunnel, -4809, -2969, 6448, 100, 300, 300, 0 }, - NULL_TRIGGER -}; - -/** - * The HMC triggers are mostly for warping the camera below platforms, but the second trigger is used to - * start the cutscene for entering the CotMC pool. - */ -struct CameraTrigger sCamHMC[] = { - { 1, cam_hmc_enter_maze, 1996, 102, 0, 205, 100, 205, 0 }, - { 1, cam_castle_hmc_start_pool_cutscene, 3350, -4689, 4800, 600, 50, 600, 0 }, - { 1, cam_hmc_elevator_black_hole, -3278, 1236, 1379, 358, 200, 358, 0 }, - { 1, cam_hmc_elevator_maze_emergency_exit, -2816, 2055, -2560, 358, 200, 358, 0 }, - { 1, cam_hmc_elevator_lake, -3532, 1543, -7040, 358, 200, 358, 0 }, - { 1, cam_hmc_elevator_maze, -972, 1543, -7347, 358, 200, 358, 0 }, - NULL_TRIGGER -}; - -/** - * The SSL triggers are for starting the enter pyramid top cutscene, - * setting close mode in the middle of the pyramid, and setting the boss fight camera mode to outward - * radial. - */ -struct CameraTrigger sCamSSL[] = { - { 1, cam_ssl_enter_pyramid_top, -2048, 1080, -1024, 150, 150, 150, 0 }, - { 2, cam_ssl_pyramid_center, 0, -104, -104, 1248, 1536, 2950, 0 }, - { 2, cam_ssl_pyramid_center, 0, 2500, 256, 515, 5000, 515, 0 }, - { 3, cam_ssl_boss_room, 0, -1534, -2040, 1000, 800, 1000, 0 }, - NULL_TRIGGER -}; - -/** - * The RR triggers are for changing between fixed and 8 direction mode when entering / leaving the building at - * the end of the ride. - */ -struct CameraTrigger sCamRR[] = { - { 1, cam_rr_exit_building_side, -4197, 3819, -3087, 1769, 1490, 342, 0 }, - { 1, cam_rr_enter_building_side, -4197, 3819, -3771, 769, 490, 342, 0 }, - { 1, cam_rr_enter_building_window, -5603, 4834, -5209, 300, 600, 591, 0 }, - { 1, cam_rr_enter_building, -2609, 3730, -5463, 300, 650, 577, 0 }, - { 1, cam_rr_exit_building_top, -4196, 7343, -5155, 4500, 1000, 4500, 0 }, - { 1, cam_rr_enter_building, -4196, 6043, -5155, 500, 300, 500, 0 }, - NULL_TRIGGER, -}; - -/** - * These triggers are unused, but because the first trigger surrounds the BoB tower and activates radial - * mode (which is called "tower mode" in the patent), it's speculated they belonged to BoB. - * - * This table contains the only instance of a CameraTrigger with an area set to -1, and it sets the mode - * to free_roam when Mario is not walking up the tower. - */ -struct CameraTrigger sCamBOB[] = { - { 1, cam_bob_tower, 2468, 2720, -4608, 3263, 1696, 3072, 0 }, - { -1, cam_bob_default_free_roam, 0, 0, 0, 0, 0, 0, 0 }, - NULL_TRIGGER -}; - -/** - * The CotMC trigger is only used to prevent fix Lakitu in place when Mario exits through the waterfall. - */ -struct CameraTrigger sCamCotMC[] = { - { 1, cam_cotmc_exit_waterfall, 0, 1500, 3500, 550, 10000, 1500, 0 }, - NULL_TRIGGER -}; - -/** - * The CCM triggers are used to set the flag that says when Mario is in the slide shortcut. - */ -struct CameraTrigger sCamCCM[] = { - { 2, cam_ccm_enter_slide_shortcut, -4846, 2061, 27, 1229, 1342, 396, 0 }, - { 2, cam_ccm_leave_slide_shortcut, -6412, -3917, -6246, 307, 185, 132, 0 }, - NULL_TRIGGER -}; - -/** - * The Castle triggers are used to set the camera to fixed mode when entering the lobby, and to set it - * to close mode when leaving it. They also set the mode to spiral staircase. - * - * There are two triggers for looking up and down straight staircases when Mario is at the start, - * and one trigger that starts the enter pool cutscene when Mario enters HMC. - */ -struct CameraTrigger sCamCastle[] = { - { 1, cam_castle_close_mode, -1100, 657, -1346, 300, 150, 300, 0 }, - { 1, cam_castle_enter_lobby, -1099, 657, -803, 300, 150, 300, 0 }, - { 1, cam_castle_close_mode, -2304, -264, -4072, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, -2304, 145, -1344, 140, 150, 140, 0 }, - { 1, cam_castle_enter_lobby, -2304, 145, -802, 140, 150, 140, 0 }, - //! Sets the camera mode when leaving secret aquarium - { 1, cam_castle_close_mode, 2816, 1200, -256, 100, 100, 100, 0 }, - { 1, cam_castle_close_mode, 256, -161, -4226, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, 256, 145, -1344, 140, 150, 140, 0 }, - { 1, cam_castle_enter_lobby, 256, 145, -802, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, -1023, 44, -4870, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, -459, 145, -1020, 140, 150, 140, 0x6000 }, - { 1, cam_castle_enter_lobby, -85, 145, -627, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, -1589, 145, -1020, 140, 150, 140, -0x6000 }, - { 1, cam_castle_enter_lobby, -1963, 145, -627, 140, 150, 140, 0 }, - { 1, cam_castle_leave_lobby_sliding_door, -2838, 657, -1659, 200, 150, 150, 0x2000 }, - { 1, cam_castle_enter_lobby_sliding_door, -2319, 512, -1266, 300, 150, 300, 0x2000 }, - { 1, cam_castle_close_mode, 844, 759, -1657, 40, 150, 40, -0x2000 }, - { 1, cam_castle_enter_lobby, 442, 759, -1292, 140, 150, 140, -0x2000 }, - { 2, cam_castle_enter_spiral_stairs, -1000, 657, 1740, 200, 300, 200, 0 }, - { 2, cam_castle_enter_spiral_stairs, -996, 1348, 1814, 200, 300, 200, 0 }, - { 2, cam_castle_close_mode, -946, 657, 2721, 50, 150, 50, 0 }, - { 2, cam_castle_close_mode, -996, 1348, 907, 50, 150, 50, 0 }, - { 2, cam_castle_close_mode, -997, 1348, 1450, 140, 150, 140, 0 }, - { 1, cam_castle_close_mode, -4942, 452, -461, 140, 150, 140, 0x4000 }, - { 1, cam_castle_close_mode, -3393, 350, -793, 140, 150, 140, 0x4000 }, - { 1, cam_castle_enter_lobby, -2851, 350, -792, 140, 150, 140, 0x4000 }, - { 1, cam_castle_enter_lobby, 803, 350, -228, 140, 150, 140, -0x4000 }, - //! Duplicate camera trigger outside JRB door - { 1, cam_castle_enter_lobby, 803, 350, -228, 140, 150, 140, -0x4000 }, - { 1, cam_castle_close_mode, 1345, 350, -229, 140, 150, 140, 0x4000 }, - { 1, cam_castle_close_mode, -946, -929, 622, 300, 150, 300, 0 }, - { 2, cam_castle_look_upstairs, -205, 1456, 2508, 210, 928, 718, 0 }, - { 1, cam_castle_basement_look_downstairs, -1027, -587, -718, 318, 486, 577, 0 }, - { 1, cam_castle_lobby_entrance, -1023, 376, 1830, 300, 400, 300, 0 }, - { 3, cam_castle_hmc_start_pool_cutscene, 2485, -1689, -2659, 600, 50, 600, 0 }, - NULL_TRIGGER -}; - -/** - * The BBH triggers are the most complex, they cause the camera to enter fixed mode for each room, - * transition between rooms, and enter free roam when outside. - * - * The triggers are also responsible for warping the camera below platforms. - */ -struct CameraTrigger sCamBBH[] = { - { 1, cam_bbh_enter_front_door, 742, 0, 2369, 200, 200, 200, 0 }, - { 1, cam_bbh_leave_front_door, 741, 0, 1827, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 222, 0, 1458, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 222, 0, 639, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 435, 0, 222, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1613, 0, 222, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1827, 0, 1459, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, -495, 819, 1407, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, -495, 819, 640, 250, 200, 200, 0 }, - { 1, cam_bbh_room_1, 179, 819, 222, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1613, 819, 222, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1827, 819, 486, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1827, 819, 1818, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_lower, 2369, 0, 1459, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_lower, 3354, 0, 1347, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_lower, 2867, 514, 1843, 512, 102, 409, 0 }, - { 1, cam_bbh_room_4, 3354, 0, 804, 200, 200, 200, 0 }, - { 1, cam_bbh_room_4, 1613, 0, -320, 200, 200, 200, 0 }, - { 1, cam_bbh_room_8, 435, 0, -320, 200, 200, 200, 0 }, - { 1, cam_bbh_room_5_library, -2021, 0, 803, 200, 200, 200, 0 }, - { 1, cam_bbh_room_5_library, -320, 0, 640, 200, 200, 200, 0 }, - { 1, cam_bbh_room_5_library_to_hidden_transition, -1536, 358, -254, 716, 363, 102, 0 }, - { 1, cam_bbh_room_5_hidden_to_library_transition, -1536, 358, -459, 716, 363, 102, 0 }, - { 1, cam_bbh_room_5_hidden, -1560, 0, -1314, 200, 200, 200, 0 }, - { 1, cam_bbh_room_3, -320, 0, 1459, 200, 200, 200, 0 }, - { 1, cam_bbh_room_3, -2021, 0, 1345, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_library, 2369, 819, 486, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_library, 2369, 1741, 486, 200, 200, 200, 0 }, - { 1, cam_bbh_room_2_library_to_trapdoor_transition, 2867, 1228, 1174, 716, 414, 102, 0 }, - { 1, cam_bbh_room_2_trapdoor_transition, 2867, 1228, 1378, 716, 414, 102, 0 }, - { 1, cam_bbh_room_2_trapdoor, 2369, 819, 1818, 200, 200, 200, 0 }, - { 1, cam_bbh_room_9_attic, 1829, 1741, 486, 200, 200, 200, 0 }, - { 1, cam_bbh_room_9_attic, 741, 1741, 1587, 200, 200, 200, 0 }, - { 1, cam_bbh_room_9_attic_transition, 102, 2048, -191, 100, 310, 307, 0 }, - { 1, cam_bbh_room_9_mr_i_transition, 409, 2048, -191, 100, 310, 307, 0 }, - { 1, cam_bbh_room_13_balcony, 742, 1922, 2164, 200, 200, 200, 0 }, - { 1, cam_bbh_fall_off_roof, 587, 1322, 2677, 1000, 400, 600, 0 }, - { 1, cam_bbh_room_3, -1037, 819, 1408, 200, 200, 200, 0 }, - { 1, cam_bbh_room_3, -1970, 1024, 1345, 200, 200, 200, 0 }, - { 1, cam_bbh_room_8, 179, 819, -320, 200, 200, 200, 0 }, - { 1, cam_bbh_room_7_mr_i, 1613, 819, -320, 200, 200, 200, 0 }, - { 1, cam_bbh_room_7_mr_i_to_coffins_transition, 2099, 1228, -819, 102, 414, 716, 0 }, - { 1, cam_bbh_room_7_coffins_to_mr_i_transition, 2304, 1228, -819, 102, 414, 716, 0 }, - { 1, cam_bbh_room_6, -1037, 819, 640, 200, 200, 200, 0 }, - { 1, cam_bbh_room_6, -1970, 1024, 803, 200, 200, 200, 0 }, - { 1, cam_bbh_room_1, 1827, 819, 1818, 200, 200, 200, 0 }, - { 1, cam_bbh_fall_into_pool, 2355, -1112, -193, 1228, 500, 1343, 0 }, - { 1, cam_bbh_fall_into_pool, 2355, -1727, 1410, 1228, 500, 705, 0 }, - { 1, cam_bbh_elevator_room_lower, 0, -2457, 1827, 250, 200, 250, 0 }, - { 1, cam_bbh_elevator_room_lower, 0, -2457, 2369, 250, 200, 250, 0 }, - { 1, cam_bbh_elevator_room_lower, 0, -2457, 4929, 250, 200, 250, 0 }, - { 1, cam_bbh_elevator_room_lower, 0, -2457, 4387, 250, 200, 250, 0 }, - { 1, cam_bbh_room_0_back_entrance, 1887, -2457, 204, 250, 200, 250, 0 }, - { 1, cam_bbh_room_0, 1272, -2457, 204, 250, 200, 250, 0 }, - { 1, cam_bbh_room_0, -1681, -2457, 204, 250, 200, 250, 0 }, - { 1, cam_bbh_room_0_back_entrance, -2296, -2457, 204, 250, 200, 250, 0 }, - { 1, cam_bbh_elevator, -2939, -605, 5367, 800, 100, 800, 0 }, - { 1, cam_bbh_room_12_upper, -2939, -205, 5367, 300, 100, 300, 0 }, - { 1, cam_bbh_room_12_upper, -2332, -204, 4714, 250, 200, 250, 0x6000 }, - { 1, cam_bbh_room_0_back_entrance, -1939, -204, 4340, 250, 200, 250, 0x6000 }, - NULL_TRIGGER -}; - -#define _ NULL -#define STUB_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, cameratable) cameratable, -#define DEFINE_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, cameratable) cameratable, - -/* - * This table has an extra 2 levels after the last unknown_38 stub level. What I think - * the programmer was thinking was that the table is null terminated and so used the - * level count as a correspondence to the ID of the final level, but the enum represents - * an ID *after* the last stub level, not before or during it. - * - * Each table is terminated with NULL_TRIGGER - */ -struct CameraTrigger *sCameraTriggers[LEVEL_COUNT + 1] = { - NULL, - #include "levels/level_defines.h" -}; -#undef _ -#undef STUB_LEVEL -#undef DEFINE_LEVEL - -struct CutsceneSplinePoint sIntroStartToPipePosition[] = { - { 0, 0, { 2122, 8762, 9114 } }, { 0, 0, { 2122, 8762, 9114 } }, { 1, 0, { 2122, 7916, 9114 } }, - { 1, 0, { 2122, 7916, 9114 } }, { 2, 0, { 957, 5166, 8613 } }, { 3, 0, { 589, 4338, 7727 } }, - { 4, 0, { 690, 3366, 6267 } }, { 5, 0, { -1600, 2151, 4955 } }, { 6, 0, { -1557, 232, 1283 } }, - { 7, 0, { -6962, -295, 2729 } }, { 8, 0, { -6979, 131, 3246 } }, { 9, 0, { -6360, -283, 4044 } }, - { 0, 0, { -5695, -334, 5264 } }, { 1, 0, { -5568, -319, 7933 } }, { 2, 0, { -3848, -200, 6278 } }, - { 3, 0, { -965, -263, 6092 } }, { 4, 0, { 1607, 2465, 6329 } }, { 5, 0, { 2824, 180, 3548 } }, - { 6, 0, { 1236, 136, 945 } }, { 0, 0, { 448, 136, 564 } }, { 0, 0, { 448, 136, 564 } }, - { 0, 0, { 448, 136, 564 } }, { -1, 0, { 448, 136, 564 } } -}; - -struct CutsceneSplinePoint sIntroStartToPipeFocus[] = { - { 0, 50, { 1753, 29800, 8999 } }, { 0, 50, { 1753, 29800, 8999 } }, - { 1, 50, { 1753, 8580, 8999 } }, { 1, 100, { 1753, 8580, 8999 } }, - { 2, 50, { 520, 5400, 8674 } }, { 3, 50, { 122, 4437, 7875 } }, - { 4, 50, { 316, 3333, 6538 } }, { 5, 36, { -1526, 2189, 5448 } }, - { 6, 50, { -1517, 452, 1731 } }, { 7, 50, { -6659, -181, 3109 } }, - { 8, 17, { -6649, 183, 3618 } }, { 9, 20, { -6009, -214, 4395 } }, - { 0, 50, { -5258, -175, 5449 } }, { 1, 36, { -5158, -266, 7651 } }, - { 2, 26, { -3351, -192, 6222 } }, { 3, 25, { -483, -137, 6060 } }, - { 4, 100, { 1833, 2211, 5962 } }, { 5, 26, { 3022, 207, 3090 } }, - { 6, 20, { 1250, 197, 449 } }, { 7, 50, { 248, 191, 227 } }, - { 7, 0, { 48, 191, 227 } }, { 7, 0, { 48, 191, 227 } }, - { -1, 0, { 48, 191, 227 } } -}; - -/** - * Describes the spline the camera follows, starting when the camera jumps to Lakitu and ending after - * Mario jumps out of the pipe when the first dialog opens. This table specifically updates the - * camera's position. - */ -struct CutsceneSplinePoint sIntroPipeToDialogPosition[] = { - { 0, 0, { -785, 625, 4527 } }, { 1, 0, { -785, 625, 4527 } }, { 2, 0, { -1286, 644, 4376 } }, - { 3, 0, { -1286, 623, 4387 } }, { 4, 0, { -1286, 388, 3963 } }, { 5, 0, { -1286, 358, 4093 } }, - { 6, 0, { -1386, 354, 4159 } }, { 7, 0, { -1477, 306, 4223 } }, { 8, 0, { -1540, 299, 4378 } }, - { 9, 0, { -1473, 316, 4574 } }, { 0, 0, { -1328, 485, 5017 } }, { 0, 0, { -1328, 485, 5017 } }, - { 0, 0, { -1328, 485, 5017 } }, { -1, 0, { -1328, 485, 5017 } } -}; - -/** - * Describes the spline that the camera's focus follows, during the same part of the intro as the above. - */ -#ifdef VERSION_EU -struct CutsceneSplinePoint sIntroPipeToDialogFocus[] = { - { 0, 25, { -1248, 450, 4596 } }, { 1, 71, { -1258, 485, 4606 } }, { 2, 71, { -1379, 344, 4769 } }, - { 3, 22, { -1335, 366, 4815 } }, { 4, 23, { -1315, 370, 4450 } }, { 5, 40, { -1322, 333, 4591 } }, - { 6, 25, { -1185, 329, 4616 } }, { 7, 21, { -1059, 380, 4487 } }, { 8, 14, { -1086, 421, 4206 } }, - { 9, 21, { -1321, 346, 4098 } }, { 0, 0, { -1328, 385, 4354 } }, { 0, 0, { -1328, 385, 4354 } }, - { 0, 0, { -1328, 385, 4354 } }, { -1, 0, { -1328, 385, 4354 } } -}; -#else -struct CutsceneSplinePoint sIntroPipeToDialogFocus[] = { - { 0, 20, { -1248, 450, 4596 } }, { 1, 59, { -1258, 485, 4606 } }, { 2, 59, { -1379, 344, 4769 } }, - { 3, 20, { -1335, 366, 4815 } }, { 4, 23, { -1315, 370, 4450 } }, { 5, 40, { -1322, 333, 4591 } }, - { 6, 25, { -1185, 329, 4616 } }, { 7, 21, { -1059, 380, 4487 } }, { 8, 14, { -1086, 421, 4206 } }, - { 9, 21, { -1321, 346, 4098 } }, { 0, 0, { -1328, 385, 4354 } }, { 0, 0, { -1328, 385, 4354 } }, - { 0, 0, { -1328, 385, 4354 } }, { -1, 0, { -1328, 385, 4354 } } -}; -#endif - -struct CutsceneSplinePoint sEndingFlyToWindowPos[] = { - { 0, 0, { -86, 876, 640 } }, { 1, 0, { -86, 876, 610 } }, { 2, 0, { -66, 945, 393 } }, - { 3, 0, { -80, 976, 272 } }, { 4, 0, { -66, 1306, -36 } }, { 5, 0, { -70, 1869, -149 } }, - { 6, 0, { -10, 2093, -146 } }, { 7, 0, { -10, 2530, -248 } }, { 8, 0, { -10, 2530, -263 } }, - { 9, 0, { -10, 2530, -273 } } -}; - -struct CutsceneSplinePoint sEndingFlyToWindowFocus[] = { - { 0, 50, { -33, 889, -7 } }, { 1, 35, { -33, 889, -7 } }, { 2, 31, { -17, 1070, -193 } }, - { 3, 25, { -65, 1182, -272 } }, { 4, 20, { -64, 1559, -542 } }, { 5, 25, { -68, 2029, -677 } }, - { 6, 25, { -9, 2204, -673 } }, { 7, 25, { -8, 2529, -772 } }, { 8, 0, { -8, 2529, -772 } }, - { 9, 0, { -8, 2529, -772 } }, { -1, 0, { -8, 2529, -772 } } -}; - -struct CutsceneSplinePoint sEndingPeachDescentCamPos[] = { - { 0, 50, { 1, 120, -1150 } }, { 1, 50, { 1, 120, -1150 } }, { 2, 40, { 118, 121, -1199 } }, - { 3, 40, { 147, 74, -1306 } }, { 4, 40, { 162, 95, -1416 } }, { 5, 40, { 25, 111, -1555 } }, - { 6, 40, { -188, 154, -1439 } }, { 7, 40, { -203, 181, -1242 } }, { 8, 40, { 7, 191, -1057 } }, - { 9, 40, { 262, 273, -1326 } }, { 0, 40, { -4, 272, -1627 } }, { 1, 35, { -331, 206, -1287 } }, - { 2, 30, { -65, 219, -877 } }, { 3, 25, { 6, 216, -569 } }, { 4, 25, { -8, 157, 40 } }, - { 5, 25, { -4, 106, 200 } }, { 6, 25, { -6, 72, 574 } }, { 7, 0, { -6, 72, 574 } }, - { 8, 0, { -6, 72, 574 } }, { -1, 0, { -6, 72, 574 } } -}; - -struct CutsceneSplinePoint sEndingMarioToPeachPos[] = { - { 0, 0, { -130, 1111, -1815 } }, { 1, 0, { -131, 1052, -1820 } }, { 2, 0, { -271, 1008, -1651 } }, - { 3, 0, { -439, 1043, -1398 } }, { 4, 0, { -433, 1040, -1120 } }, { 5, 0, { -417, 1040, -1076 } }, - { 6, 0, { -417, 1040, -1076 } }, { 7, 0, { -417, 1040, -1076 } }, { -1, 0, { -417, 1040, -1076 } } -}; - -struct CutsceneSplinePoint sEndingMarioToPeachFocus[] = { - { 0, 50, { -37, 1020, -1332 } }, { 1, 20, { -36, 1012, -1330 } }, { 2, 20, { -24, 1006, -1215 } }, - { 3, 20, { 28, 1002, -1224 } }, { 4, 24, { 45, 1013, -1262 } }, { 5, 35, { 34, 1000, -1287 } }, - { 6, 0, { 34, 1000, -1287 } }, { 7, 0, { 34, 1000, -1287 } }, { -1, 0, { 34, 1000, -1287 } } -}; - -struct CutsceneSplinePoint sEndingLookUpAtCastle[] = { - { 0, 50, { 200, 1066, -1414 } }, { 0, 50, { 200, 1066, -1414 } }, { 0, 30, { 198, 1078, -1412 } }, - { 0, 33, { 15, 1231, -1474 } }, { 0, 39, { -94, 1381, -1368 } }, { 0, 0, { -92, 1374, -1379 } }, - { 0, 0, { -92, 1374, -1379 } }, { -1, 0, { -92, 1374, -1379 } } -}; - -struct CutsceneSplinePoint sEndingLookAtSkyFocus[] = { -#ifdef VERSION_EU - { 0, 50, { 484, 1368, -868 } }, { 0, 72, { 479, 1372, -872 } }, { 0, 50, { 351, 1817, -918 } }, -#else - { 0, 50, { 484, 1368, -888 } }, { 0, 72, { 479, 1372, -892 } }, { 0, 50, { 351, 1817, -918 } }, -#endif - { 0, 50, { 351, 1922, -598 } }, { 0, 0, { 636, 2027, -415 } }, { 0, 0, { 636, 2027, -415 } }, - { -1, 0, { 636, 2027, -415 } } -}; - -/** - * Activates any CameraTriggers that Mario is inside. - * Then, applies area-specific processing to the camera, such as setting the default mode, or changing - * the mode based on the terrain type Mario is standing on. - * - * @return the camera's mode after processing, although this is unused in the code - */ -s16 camera_course_processing(struct Camera *c) { - s16 level = gCurrLevelNum; - s8 area = gCurrentArea->index; - // Bounds iterator - u32 b; - // Camera trigger's bounding box - Vec3f center, bounds; - u32 insideBounds = FALSE; - u8 oldMode = c->mode; - - if (c->mode == CAMERA_MODE_C_UP) { - c->mode = sModeInfo.lastMode; - } - check_blocking_area_processing(&c->mode); - if (level > LEVEL_COUNT + 1) { - level = LEVEL_COUNT + 1; - } - - if (sCameraTriggers[level] != NULL) { - b = 0; - - // Process positional triggers. - // All triggered events are called, not just the first one. - while (sCameraTriggers[level][b].event != NULL) { - - // Check only the current area's triggers - if (sCameraTriggers[level][b].area == area) { - // Copy the bounding box into center and bounds - vec3f_set(center, sCameraTriggers[level][b].centerX, - sCameraTriggers[level][b].centerY, - sCameraTriggers[level][b].centerZ); - vec3f_set(bounds, sCameraTriggers[level][b].boundsX, - sCameraTriggers[level][b].boundsY, - sCameraTriggers[level][b].boundsZ); - - // Check if Mario is inside the bounds - if (is_pos_in_bounds(sMarioCamState->pos, center, bounds, - sCameraTriggers[level][b].boundsYaw) == TRUE) { - //! This should be checked before calling is_pos_in_bounds. (It doesn't belong - //! outside the while loop because some events disable area processing) - if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { - sCameraTriggers[level][b].event(c); - insideBounds = TRUE; - } - } - } - - if ((sCameraTriggers[level])[b].area == -1) { - // Default triggers are only active if Mario is not already inside another trigger - if (!insideBounds) { - if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { - sCameraTriggers[level][b].event(c); - } - } - } - - b++; - } - } -#if defined(ENABLE_VANILLA_CAM_PROCESSING) && !defined(FORCED_CAMERA_MODE) && !defined(USE_COURSE_DEFAULT_MODE) - // Area-specific camera processing - if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { - switch (gCurrLevelArea) { - case AREA_WF: - if (sMarioCamState->action == ACT_RIDING_HOOT) { - transition_to_camera_mode(c, CAMERA_MODE_SLIDE_HOOT, 60); - } else { - switch (sMarioGeometry.currFloorType) { - case SURFACE_CAMERA_8_DIR: - transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 90); - s8DirModeBaseYaw = DEGREES(90); - break; - - case SURFACE_BOSS_FIGHT_CAMERA: - if (gCurrActNum == 1) { - set_camera_mode_boss_fight(c); - } else { - set_camera_mode_radial(c, 60); - } - break; - default: - set_camera_mode_radial(c, 60); - } - } - break; - - case AREA_BBH: - // if camera is fixed at bbh_room_13_balcony_camera (but as floats) - if (vec3f_compare(sFixedModeBasePosition, 210.f, 420.f, 3109.f) == TRUE) { - if (sMarioCamState->pos[1] < 1800.f) { - transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 30); - } - } - break; - - case AREA_SSL_PYRAMID: - set_mode_if_not_set_by_surface(c, CAMERA_MODE_OUTWARD_RADIAL); - break; - - case AREA_SSL_OUTSIDE: - set_mode_if_not_set_by_surface(c, CAMERA_MODE_RADIAL); - break; - - case AREA_THI_HUGE: - break; - - case AREA_THI_TINY: - surface_type_modes_thi(c); - break; - - case AREA_TTC: - set_mode_if_not_set_by_surface(c, CAMERA_MODE_OUTWARD_RADIAL); - break; - - case AREA_BOB: - if (set_mode_if_not_set_by_surface(c, CAMERA_MODE_NONE) == 0) { - if (sMarioGeometry.currFloorType == SURFACE_BOSS_FIGHT_CAMERA) { - set_camera_mode_boss_fight(c); - } else { - if (c->mode == CAMERA_MODE_CLOSE) { - transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 60); - } else { - set_camera_mode_radial(c, 60); - } - } - } - break; - - case AREA_WDW_MAIN: - switch (sMarioGeometry.currFloorType) { - case SURFACE_INSTANT_WARP_1B: - c->defMode = CAMERA_MODE_RADIAL; - break; - } - break; - - case AREA_WDW_TOWN: - switch (sMarioGeometry.currFloorType) { - case SURFACE_INSTANT_WARP_1C: - c->defMode = CAMERA_MODE_CLOSE; - break; - } - break; - - case AREA_DDD_WHIRLPOOL: - //! @bug this does nothing - gLakituState.defMode = CAMERA_MODE_OUTWARD_RADIAL; - break; - - case AREA_DDD_SUB: - if ((c->mode != CAMERA_MODE_BEHIND_MARIO) - && (c->mode != CAMERA_MODE_WATER_SURFACE)) { - if (((sMarioCamState->action & ACT_FLAG_ON_POLE) != 0) - || (sMarioGeometry.currFloorHeight > 800.f)) { - transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 60); - - } else { - if (sMarioCamState->pos[1] < 800.f) { - transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 60); - } - } - } - //! @bug this does nothing - gLakituState.defMode = CAMERA_MODE_FREE_ROAM; - break; - } - } -#endif - - sStatusFlags &= ~CAM_FLAG_BLOCK_AREA_PROCESSING; - if (oldMode == CAMERA_MODE_C_UP) { - sModeInfo.lastMode = c->mode; - c->mode = oldMode; - } - return c->mode; -} - -/** - * Move `pos` between the nearest floor and ceiling - */ -void resolve_geometry_collisions(Vec3f pos) { - struct Surface *surf; - - f32_find_wall_collision(&pos[0], &pos[1], &pos[2], 0.f, 100.f); - f32 floorY = find_floor(pos[0], pos[1] + 50.f, pos[2], &surf); - f32 ceilY = find_ceil(pos[0], pos[1] - 50.f, pos[2], &surf); - - if ((FLOOR_LOWER_LIMIT != floorY) && (CELL_HEIGHT_LIMIT == ceilY)) { - if (pos[1] < (floorY += 125.f)) { - pos[1] = floorY; - } - } - - if ((FLOOR_LOWER_LIMIT == floorY) && (CELL_HEIGHT_LIMIT != ceilY)) { - if (pos[1] > (ceilY -= 125.f)) { - pos[1] = ceilY; - } - } - - if ((FLOOR_LOWER_LIMIT != floorY) && (CELL_HEIGHT_LIMIT != ceilY)) { - floorY += 125.f; - ceilY -= 125.f; - - if ((pos[1] <= floorY) && (pos[1] < ceilY)) { - pos[1] = floorY; - } - if ((pos[1] > floorY) && (pos[1] >= ceilY)) { - pos[1] = ceilY; - } - if ((pos[1] <= floorY) && (pos[1] >= ceilY)) { - pos[1] = (floorY + ceilY) * 0.5f; - } - } -} - -/** - * Checks for any walls obstructing Mario from view, and calculates a new yaw that the camera should - * rotate towards. - * - * @param[out] avoidYaw the angle (from Mario) that the camera should rotate towards to avoid the wall. - * The camera then approaches avoidYaw until Mario is no longer obstructed. - * avoidYaw is always parallel to the wall. - * @param yawRange how wide of an arc to check for walls obscuring Mario. - * - * @return 3 if a wall is covering Mario, 1 if a wall is only near the camera. - */ -s32 rotate_camera_around_walls(UNUSED struct Camera *c, Vec3f cPos, s16 *avoidYaw, s16 yawRange) { - struct WallCollisionData colData; - struct Surface *wall; - f32 dummyDist, checkDist; - f32 coarseRadius; - f32 fineRadius; - s16 wallYaw, horWallNorm; - s16 dummyPitch; - // The yaw of the vector from Mario to the camera. - s16 yawFromMario; - s32 status = 0; - /// The current iteration. The algorithm takes 8 equal steps from Mario back to the camera. - s32 step = 0; - - vec3f_get_dist_and_angle(sMarioCamState->pos, cPos, &dummyDist, &dummyPitch, &yawFromMario); - sStatusFlags &= ~CAM_FLAG_CAM_NEAR_WALL; - colData.offsetY = 100.0f; - // The distance from Mario to Lakitu - checkDist = 0.0f; - /// The radius used to find potential walls to avoid. - /// @bug Increases to 250.f, but the max collision radius is 200.f - coarseRadius = 150.0f; - /// This only increases when there is a wall collision found in the coarse pass - fineRadius = 100.0f; - - for (step = 0; step < 8; step++) { - // Start at Mario, move backwards to Lakitu's position - colData.x = sMarioCamState->pos[0] + ((cPos[0] - sMarioCamState->pos[0]) * checkDist); - colData.y = sMarioCamState->pos[1] + ((cPos[1] - sMarioCamState->pos[1]) * checkDist); - colData.z = sMarioCamState->pos[2] + ((cPos[2] - sMarioCamState->pos[2]) * checkDist); - colData.radius = coarseRadius; - // Increase the coarse check radius - camera_approach_f32_symmetric_bool(&coarseRadius, 250.f, 30.f); - - if (find_wall_collisions(&colData) != 0) { - wall = colData.walls[colData.numWalls - 1]; - - // If we're over halfway from Mario to Lakitu, then there's a wall near the camera, but - // not necessarily obstructing Mario - if (step >= 5) { - sStatusFlags |= CAM_FLAG_CAM_NEAR_WALL; - if (status <= 0) { - status = 1; - wall = colData.walls[colData.numWalls - 1]; - // wallYaw is parallel to the wall, not perpendicular - wallYaw = atan2s(wall->normal.z, wall->normal.x) + DEGREES(90); - // Calculate the avoid direction. The function returns the opposite direction so add 180 - // degrees. - *avoidYaw = calc_avoid_yaw(yawFromMario, wallYaw) + DEGREES(180); - } - } - - colData.x = sMarioCamState->pos[0] + ((cPos[0] - sMarioCamState->pos[0]) * checkDist); - colData.y = sMarioCamState->pos[1] + ((cPos[1] - sMarioCamState->pos[1]) * checkDist); - colData.z = sMarioCamState->pos[2] + ((cPos[2] - sMarioCamState->pos[2]) * checkDist); - colData.radius = fineRadius; - // Increase the fine check radius - camera_approach_f32_symmetric_bool(&fineRadius, 200.f, 20.f); - - if (find_wall_collisions(&colData) != 0) { - wall = colData.walls[colData.numWalls - 1]; - horWallNorm = atan2s(wall->normal.z, wall->normal.x); - wallYaw = horWallNorm + DEGREES(90); - // If Mario would be blocked by the surface, then avoid it - if ((is_range_behind_surface(sMarioCamState->pos, cPos, wall, yawRange, SURFACE_WALL_MISC) == 0) - && (is_mario_behind_surface(c, wall) == TRUE) - // Also check if the wall is tall enough to cover Mario - && (is_surf_within_bounding_box(wall, -1.f, 150.f, -1.f) == FALSE)) { - // Calculate the avoid direction. The function returns the opposite direction so add 180 - // degrees. - *avoidYaw = calc_avoid_yaw(yawFromMario, wallYaw) + DEGREES(180); - camera_approach_s16_symmetric_bool(avoidYaw, horWallNorm, yawRange); - status = 3; - step = 8; - } - } - } - checkDist += 0.125f; - } - - return status; -} - -/** - * Stores type and height of the nearest floor and ceiling to Mario in `pg` - * - * Note: Also finds the water level, but waterHeight is unused - */ -void find_mario_floor_and_ceil(struct PlayerGeometry *pg) { - struct Surface *surf; - s32 tempCollisionFlags = gCollisionFlags; - gCollisionFlags |= COLLISION_FLAG_CAMERA; - - if (find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 10.f, - sMarioCamState->pos[2], &surf) != FLOOR_LOWER_LIMIT) { - pg->currFloorType = surf->type; - } else { - pg->currFloorType = 0; - } - - if (find_ceil(sMarioCamState->pos[0], sMarioCamState->pos[1] - 10.f, - sMarioCamState->pos[2], &surf) != CELL_HEIGHT_LIMIT) { - pg->currCeilType = surf->type; - } else { - pg->currCeilType = 0; - } - - gCollisionFlags &= ~COLLISION_FLAG_CAMERA; - pg->currFloorHeight = find_floor(sMarioCamState->pos[0], - sMarioCamState->pos[1] + 10.f, - sMarioCamState->pos[2], &pg->currFloor); - pg->currCeilHeight = find_ceil(sMarioCamState->pos[0], - sMarioCamState->pos[1] - 10.f, - sMarioCamState->pos[2], &pg->currCeil); - pg->waterHeight = find_water_level(sMarioCamState->pos[0], sMarioCamState->pos[2]); - gCollisionFlags = tempCollisionFlags; -} - -/** - * Start a cutscene focusing on an object - * This will play if nothing else happened in the same frame, like exiting or warping. - */ -void start_object_cutscene(u8 cutscene, struct Object *obj) { - sObjectCutscene = cutscene; - gRecentCutscene = CUTSCENE_NONE; - gCutsceneFocus = obj; - gObjCutsceneDone = FALSE; -} - -/** - * Start a low-priority cutscene without focusing on an object - * This will play if nothing else happened in the same frame, like exiting or warping. - */ -void start_object_cutscene_without_focus(u8 cutscene) { - sObjectCutscene = cutscene; - sCutsceneDialogResponse = DIALOG_RESPONSE_NONE; -} - -UNUSED s32 unused_dialog_cutscene_response(u8 cutscene) { - // if not in a cutscene, start this one - if ((gCamera->cutscene == 0) && (sObjectCutscene == 0)) { - sObjectCutscene = cutscene; - } - - // if playing this cutscene and Mario responded, return the response - if ((gCamera->cutscene == cutscene) && (sCutsceneDialogResponse)) { - return sCutsceneDialogResponse; - } else { - return 0; - } -} - -s16 cutscene_object_with_dialog(u8 cutscene, struct Object *obj, s16 dialogID) { - s16 response = DIALOG_RESPONSE_NONE; - - if ((gCamera->cutscene == CUTSCENE_NONE) && (sObjectCutscene == CUTSCENE_NONE)) { - if (gRecentCutscene != cutscene) { - start_object_cutscene(cutscene, obj); - if (dialogID != DIALOG_NONE) { - sCutsceneDialogID = dialogID; - } else { - sCutsceneDialogID = DIALOG_001; - } - } else { - response = sCutsceneDialogResponse; - } - - gRecentCutscene = CUTSCENE_NONE; - } - return response; -} - -s16 cutscene_object_without_dialog(u8 cutscene, struct Object *obj) { - return cutscene_object_with_dialog(cutscene, obj, DIALOG_NONE); -} - -/** - * @return 0 if not started, 1 if started, and -1 if finished - */ -s16 cutscene_object(u8 cutscene, struct Object *obj) { - s16 status = 0; - - if ((gCamera->cutscene == 0) && (sObjectCutscene == 0)) { - if (gRecentCutscene != cutscene) { - start_object_cutscene(cutscene, obj); - status = 1; - } else { - status = -1; - } - } - return status; -} - -/** - * Update the camera's yaw and nextYaw. This is called from cutscenes to ignore the camera mode's yaw. - */ -void update_camera_yaw(struct Camera *c) { - c->nextYaw = calculate_yaw(c->focus, c->pos); - c->yaw = c->nextYaw; -} - -void cutscene_reset_spline(void) { - sCutsceneSplineSegment = 0; - sCutsceneSplineSegmentProgress = 0; -} - -void stop_cutscene_and_retrieve_stored_info(struct Camera *c) { - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; - vec3f_copy(c->focus, sCameraStoreCutscene.focus); - vec3f_copy(c->pos, sCameraStoreCutscene.pos); -} - -void cap_switch_save(UNUSED s16 param) { - save_file_do_save(gCurrSaveFileNum - 1); -} - -void init_spline_point(struct CutsceneSplinePoint *splinePoint, s8 index, u8 speed, Vec3s point) { - splinePoint->index = index; - splinePoint->speed = speed; - vec3s_copy(splinePoint->point, point); -} - -// TODO: (Scrub C) -void copy_spline_segment(struct CutsceneSplinePoint dst[], struct CutsceneSplinePoint src[]) { - s32 j = 0; - s32 i = 0; - - init_spline_point(&dst[i], src[j].index, src[j].speed, src[j].point); - i++; - do { - do { - init_spline_point(&dst[i], src[j].index, src[j].speed, src[j].point); - i++; - j++; - } while (src[j].index != -1); - } while (j > 16); - - // Create the end of the spline by duplicating the last point - init_spline_point(&dst[i + 0], 0, src[j].speed, src[j].point); - init_spline_point(&dst[i + 1], 0, 0, src[j].point); - init_spline_point(&dst[i + 2], 0, 0, src[j].point); - init_spline_point(&dst[i + 3], -1, 0, src[j].point); -} - -/** - * Triggers Mario to enter a dialog state. This is used to make Mario look at the focus of a cutscene, - * for example, bowser. - * @param state 0 = stop, 1 = start, 2 = start and look up, and 3 = start and look down - * - * @return if Mario left the dialog state, return CUTSCENE_LOOP, else return gCutsceneTimer - */ -s16 cutscene_common_set_dialog_state(s32 state) { - s16 timer = gCutsceneTimer; - // If the dialog ended, return CUTSCENE_LOOP, which would end the cutscene shot - if (set_mario_npc_dialog(state) == MARIO_DIALOG_STATUS_SPEAK) { - timer = CUTSCENE_LOOP; - } - return timer; -} - -/// Unused SSL cutscene? -static UNUSED void unused_cutscene_mario_dialog_looking_down(UNUSED struct Camera *c) { - gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_DOWN); -} - -/** - * Cause Mario to enter the normal dialog state. - */ -static void cutscene_mario_dialog(UNUSED struct Camera *c) { - gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_FRONT); -} - -/// Unused SSL cutscene? -static UNUSED void unused_cutscene_mario_dialog_looking_up(UNUSED struct Camera *c) { - gCutsceneTimer = cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_UP); -} - -/** - * Lower the volume (US only) and start the peach letter background music - */ -void cutscene_intro_peach_start_letter_music(UNUSED struct Camera *c) { -#if defined(VERSION_US) || defined(VERSION_SH) - seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); -#endif - cutscene_intro_peach_play_message_music(); -} - -/** - * Raise the volume (not in JP) and start the flying music. - */ -void cutscene_intro_peach_start_flying_music(UNUSED struct Camera *c) { - seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60); - cutscene_intro_peach_play_lakitu_flying_music(); -} - -#ifdef VERSION_EU -/** - * Lower the volume for the letter background music. In US, this happens on the same frame as the music - * starts. - */ -void cutscene_intro_peach_eu_lower_volume(UNUSED struct Camera *c) { - seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); -} -#endif - -void reset_pan_distance(UNUSED struct Camera *c) { - sPanDistance = 0; -} - -/** - * Easter egg: the player 2 controller can move the camera's focus in the ending and credits. - */ -void player2_rotate_cam(struct Camera *c, s16 minPitch, s16 maxPitch, s16 minYaw, s16 maxYaw) { - f32 distCamToFocus; - s16 pitch, yaw, pitchCap; - - // Change the camera rotation to match the 2nd player's stick - approach_s16_asymptotic_bool(&sCreditsPlayer2Yaw, -(s16)(gPlayer2Controller->stickX * 250.f), 4); - approach_s16_asymptotic_bool(&sCreditsPlayer2Pitch, -(s16)(gPlayer2Controller->stickY * 265.f), 4); - vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &pitch, &yaw); - - pitchCap = 0x3800 - pitch; - if (pitchCap < 0) { - pitchCap = 0; - } - if (maxPitch > pitchCap) { - maxPitch = pitchCap; - } - - pitchCap = -0x3800 - pitch; - if (pitchCap > 0) { - pitchCap = 0; - } - if (minPitch < pitchCap) { - minPitch = pitchCap; - } - - if (sCreditsPlayer2Pitch > maxPitch) { - sCreditsPlayer2Pitch = maxPitch; - } - if (sCreditsPlayer2Pitch < minPitch) { - sCreditsPlayer2Pitch = minPitch; - } - - if (sCreditsPlayer2Yaw > maxYaw) { - sCreditsPlayer2Yaw = maxYaw; - } - if (sCreditsPlayer2Yaw < minYaw) { - sCreditsPlayer2Yaw = minYaw; - } - - pitch += sCreditsPlayer2Pitch; - yaw += sCreditsPlayer2Yaw; - vec3f_set_dist_and_angle(c->pos, sPlayer2FocusOffset, distCamToFocus, pitch, yaw); - vec3f_sub(sPlayer2FocusOffset, c->focus); -} - -/** - * Store camera info for the cannon opening cutscene - */ -void store_info_cannon(struct Camera *c) { - vec3f_copy(sCameraStoreCutscene.pos, c->pos); - vec3f_copy(sCameraStoreCutscene.focus, c->focus); - sCameraStoreCutscene.panDist = sPanDistance; - sCameraStoreCutscene.cannonYOffset = sCannonYOffset; -} - -/** - * Retrieve camera info for the cannon opening cutscene - */ -void retrieve_info_cannon(struct Camera *c) { - vec3f_copy(c->pos, sCameraStoreCutscene.pos); - vec3f_copy(c->focus, sCameraStoreCutscene.focus); - sPanDistance = sCameraStoreCutscene.panDist; - sCannonYOffset = sCameraStoreCutscene.cannonYOffset; -} - -/** - * Store camera info for the star spawn cutscene - */ -void store_info_star(struct Camera *c) { - reset_pan_distance(c); - vec3f_copy(sCameraStoreCutscene.pos, c->pos); - sCameraStoreCutscene.focus[0] = sMarioCamState->pos[0]; - sCameraStoreCutscene.focus[1] = c->focus[1]; - sCameraStoreCutscene.focus[2] = sMarioCamState->pos[2]; -} - -/** - * Retrieve camera info for the star spawn cutscene - */ -void retrieve_info_star(struct Camera *c) { - vec3f_copy(c->pos, sCameraStoreCutscene.pos); - vec3f_copy(c->focus, sCameraStoreCutscene.focus); -} - -/** - * Rotate the camera's focus around the camera's position by incYaw and incPitch - */ -void pan_camera(struct Camera *c, s16 incPitch, s16 incYaw) { - f32 distCamToFocus; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &pitch, &yaw); - pitch += incPitch; - yaw += incYaw; - vec3f_set_dist_and_angle(c->pos, c->focus, distCamToFocus, pitch, yaw); -} - -void cutscene_shake_explosion(UNUSED struct Camera *c) { - set_environmental_camera_shake(SHAKE_ENV_EXPLOSION); - cutscene_set_fov_shake_preset(1); -} - -static UNUSED void unused_start_bowser_bounce_shake(UNUSED struct Camera *c) { - set_environmental_camera_shake(SHAKE_ENV_BOWSER_THROW_BOUNCE); -} - -/** - * Change the spherical coordinates of `to` relative to `from` by `incDist`, `incPitch`, and `incYaw` - * - * @param from the base position - * @param[out] to the destination position - */ -void rotate_and_move_vec3f(Vec3f to, Vec3f from, f32 incDist, s16 incPitch, s16 incYaw) { - f32 dist; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(from, to, &dist, &pitch, &yaw); - pitch += incPitch; - yaw += incYaw; - dist += incDist; - vec3f_set_dist_and_angle(from, to, dist, pitch, yaw); -} - -void set_flag_post_door(struct Camera *c) { - sStatusFlags |= CAM_FLAG_BEHIND_MARIO_POST_DOOR; - sCameraYawAfterDoorCutscene = calculate_yaw(c->focus, c->pos); -} - -void cutscene_soften_music(UNUSED struct Camera *c) { - seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); -} - -void cutscene_unsoften_music(UNUSED struct Camera *c) { - seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60); -} - -void cutscene_unused_start(UNUSED struct Camera *c) { -} - -void cutscene_unused_loop(UNUSED struct Camera *c) { -} - -/** - * Set the camera position and focus for when Mario falls from the sky. - */ -void cutscene_ending_mario_fall_start(struct Camera *c) { - vec3f_set(c->focus, -26.f, 0.f, -137.f); - vec3f_set(c->pos, 165.f, 4725.f, 324.f); -} - -/** - * Focus on Mario when he's falling from the sky. - */ -void cutscene_ending_mario_fall_focus_mario(struct Camera *c) { - Vec3f offset; - vec3f_set(offset, 0.f, 80.f, 0.f); - - offset[2] = ABS(sMarioCamState->pos[1] - c->pos[1]) * -0.1f; - if (offset[2] > -100.f) { - offset[2] = -100.f; - } - - offset_rotated(c->focus, sMarioCamState->pos, offset, sMarioCamState->faceAngle); -} - -/** - * Mario falls from the sky after the grand star cutscene. - */ -void cutscene_ending_mario_fall(struct Camera *c) { - cutscene_event(cutscene_ending_mario_fall_start, c, 0, 0); - cutscene_event(cutscene_ending_mario_fall_focus_mario, c, 0, -1); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Closeup of Mario as the wing cap fades and Mario looks up. - */ -void cutscene_ending_mario_land_closeup(struct Camera *c) { - vec3f_set(c->focus, 85.f, 826.f, 250.f); - vec3f_set(c->pos, -51.f, 988.f, -202.f); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Reset the spline progress and cvar9. - */ -void cutscene_ending_reset_spline(UNUSED struct Camera *c) { - sCutsceneVars[9].point[0] = 0.f; - cutscene_reset_spline(); -} - -/** - * Follow sEndingFlyToWindowPos/Focus up to the window. - */ -void cutscene_ending_fly_up_to_window(struct Camera *c) { - move_point_along_spline(c->pos, sEndingFlyToWindowPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - move_point_along_spline(c->focus, sEndingFlyToWindowFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); -} - -/** - * Move the camera up to the window as the star power frees peach. - */ -void cutscene_ending_stars_free_peach(struct Camera *c) { - cutscene_event(cutscene_ending_reset_spline, c, 0, 0); - cutscene_event(cutscene_ending_fly_up_to_window, c, 0, -1); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Move the camera to the ground as Mario lands. - */ -void cutscene_ending_mario_land(struct Camera *c) { - vec3f_set(c->focus, sEndingFlyToWindowFocus[0].point[0], sEndingFlyToWindowFocus[0].point[1] + 80.f, sEndingFlyToWindowFocus[0].point[2]); - vec3f_set(c->pos, sEndingFlyToWindowPos[0].point[0], sEndingFlyToWindowPos[0].point[1], sEndingFlyToWindowPos[0].point[2] + 150.f); - player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); -} - -/** - * Move the camera closer to peach appearing. - */ -void cutscene_ending_peach_appear_closeup(struct Camera *c) { - vec3f_set(c->pos, 179.f, 2463.f, -1216.f); - c->pos[1] = gCutsceneFocus->oPosY + 35.f; - vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); -} - -/** - * Peach fades in, the camera focuses on her. - */ -void cutscene_ending_peach_appears(struct Camera *c) { - cutscene_event(cutscene_ending_peach_appear_closeup, c, 0, 0); - approach_f32_asymptotic_bool(&c->pos[1], gCutsceneFocus->oPosY + 35.f, 0.02f); - approach_f32_asymptotic_bool(&c->focus[1], gCutsceneFocus->oPosY + 125.f, 0.15f); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Reset spline progress, set cvar2 y offset. - */ -void cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { - cutscene_reset_spline(); - sCutsceneVars[2].point[1] = 150.f; -} - -/** - * Follow the sEndingPeachDescentCamPos spline, which rotates around peach. - */ -void cutscene_ending_follow_peach_descent(struct Camera *c) { - move_point_along_spline(c->pos, sEndingPeachDescentCamPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - c->pos[1] += gCutsceneFocus->oPosY + sCutsceneVars[3].point[1]; -} - -/** - * Decrease cvar2's y offset while the camera flies backwards to Mario. - */ -void cutscene_ending_peach_descent_lower_focus(UNUSED struct Camera *c) { - camera_approach_f32_symmetric_bool(&(sCutsceneVars[2].point[1]), 90.f, 0.5f); -} - -/** - * Keep following the sEndingPeachDescentCamPos spline, which leads back to Mario. - */ -void cutscene_ending_peach_descent_back_to_mario(struct Camera *c) { - Vec3f pos; - - move_point_along_spline(pos, sEndingPeachDescentCamPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; - approach_f32_asymptotic_bool(&c->pos[1], (pos[1] += gCutsceneFocus->oPosY), 0.07f); -} - -/** - * Peach starts floating to the ground. Rotate the camera around her, then fly backwards to Mario when - * she lands. - */ -void cutscene_ending_peach_descends(struct Camera *c) { - cutscene_event(cutscene_ending_peach_descends_start, c, 0, 0); - cutscene_event(cutscene_ending_follow_peach_descent, c, 0, 299); - cutscene_event(cutscene_ending_peach_descent_back_to_mario, c, 300, -1); - cutscene_event(cutscene_ending_peach_descent_lower_focus, c, 300, -1); - vec3f_set(c->focus, gCutsceneFocus->oPosX, sCutsceneVars[2].point[1] + gCutsceneFocus->oPosY, - gCutsceneFocus->oPosZ); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Mario runs across the bridge to peach, and takes off his cap. - * Follow the sEndingMarioToPeach* splines while Mario runs across. - */ -void cutscene_ending_mario_to_peach(struct Camera *c) { - cutscene_event(cutscene_ending_reset_spline, c, 0, 0); - move_point_along_spline(c->pos, sEndingMarioToPeachPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - move_point_along_spline(c->focus, sEndingMarioToPeachFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -} - -/** - * Make the focus follow the sEndingLookUpAtCastle spline. - */ -void cutscene_ending_look_up_at_castle(UNUSED struct Camera *c) { - move_point_along_spline(c->focus, sEndingLookUpAtCastle, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); -} - -/** - * Peach opens her eyes and the camera looks at the castle window again. - */ -void cutscene_ending_peach_wakeup(struct Camera *c) { - cutscene_event(cutscene_ending_reset_spline, c, 0, 0); - cutscene_event(cutscene_ending_look_up_at_castle, c, 0, 0); -#ifdef VERSION_EU - cutscene_event(cutscene_ending_look_up_at_castle, c, 265, -1); - cutscene_spawn_obj(7, 315); - cutscene_spawn_obj(9, 355); -#else - cutscene_event(cutscene_ending_look_up_at_castle, c, 250, -1); - cutscene_spawn_obj(7, 300); - cutscene_spawn_obj(9, 340); -#endif - vec3f_set(c->pos, -163.f, 978.f, -1082.f); - player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); -} - -/** - * Side view of peach and Mario. Peach thanks Mario for saving her. - */ -void cutscene_ending_dialog(struct Camera *c) { - vec3f_set(c->focus, 11.f, 983.f, -1273.f); - vec3f_set(c->pos, -473.f, 970.f, -1152.f); - player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); -} - -/** - * Zoom in and move the camera close to Mario and peach. - */ -void cutscene_ending_kiss_closeup(struct Camera *c) { - set_fov_function(CAM_FOV_SET_29); - vec3f_set(c->focus, 350.f, 1034.f, -1216.f); - vec3f_set(c->pos, -149.f, 1021.f, -1216.f); -} - -/** - * Fly back and zoom out for Mario's spin after the kiss. - */ -void cutscene_ending_kiss_here_we_go(struct Camera *c) { - Vec3f pos, foc; - - set_fov_function(CAM_FOV_DEFAULT); - vec3f_set(foc, 233.f, 1068.f, -1298.f); - vec3f_set(pos, -250.f, 966.f, -1111.f); - approach_vec3f_asymptotic(c->pos, pos, 0.2f, 0.1f, 0.2f); - approach_vec3f_asymptotic(c->focus, foc, 0.2f, 0.1f, 0.2f); -} - -/** - * Peach kisses Mario on the nose. - */ -void cutscene_ending_kiss(struct Camera *c) { - cutscene_event(cutscene_ending_kiss_closeup, c, 0, 0); - cutscene_event(cutscene_ending_kiss_here_we_go, c, 155, -1); - player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); -} - -/** - * Make the focus follow sEndingLookAtSkyFocus. - */ -void cutscene_ending_look_at_sky(struct Camera *c) { - move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - vec3f_set(c->pos, 699.f, 1680.f, -703.f); -} - -/** - * Zoom in the fov. The fovFunc was just set to default, so it wants to approach 45. But while this is - * called, it will stay at about 37.26f - */ -void cutscene_ending_zoom_fov(UNUSED struct Camera *c) { - sFOVState.fov = 37.f; -} - -/** - * Peach suggests baking a cake for Mario. Mario looks back at the camera before going inside the castle. - */ -void cutscene_ending_cake_for_mario(struct Camera *c) { - cutscene_event(cutscene_ending_reset_spline, c, 0, 0); - cutscene_event(cutscene_ending_look_at_sky, c, 0, 0); - cutscene_event(cutscene_ending_zoom_fov, c, 0, 499); - cutscene_event(cutscene_ending_look_at_sky, c, 500, -1); - cutscene_spawn_obj(8, 600); - cutscene_spawn_obj(8, 608); - cutscene_spawn_obj(8, 624); - cutscene_spawn_obj(8, 710); -} - -/** - * Stop the ending cutscene, reset the fov. - */ -void cutscene_ending_stop(struct Camera *c) { - set_fov_function(CAM_FOV_SET_45); - c->cutscene = 0; - gCutsceneTimer = CUTSCENE_STOP; -} - -/** - * Start the grand star cutscene. - * cvar0 is a relative offset from Mario. - * cvar1 is the is the camera's goal position. - */ -void cutscene_grand_star_start(UNUSED struct Camera *c) { - vec3f_set(sCutsceneVars[0].point, 0.f, 150.f, -600.f); - offset_rotated(sCutsceneVars[1].point, sMarioCamState->pos, sCutsceneVars[0].point, sMarioCamState->faceAngle); - sCutsceneVars[1].point[1] = 457.f; -} - -/** - * Make the camera fly to the front of Mario. - */ -void cutscene_grand_star_front_of_mario(struct Camera *c) { - f32 goalDist; - s16 goalPitch, goalYaw; - f32 dist; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(sMarioCamState->pos, sCutsceneVars[1].point, &goalDist, &goalPitch, &goalYaw); - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, goalDist, 0.1f); - approach_s16_asymptotic_bool(&pitch, goalPitch, 32); - approach_s16_asymptotic_bool(&yaw, goalYaw + 0x1200, 20); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Started shortly after Mario starts the triple jump. Stores Mario's face angle and zeros cvar2. - */ -void cutscene_grand_star_mario_jump(UNUSED struct Camera *c) { - vec3s_set(sCutsceneVars[0].angle, 0, sMarioCamState->faceAngle[1], 0); - vec3_zero(sCutsceneVars[2].point); -} - -/** - * Accelerate cvar2 to point back and to the left (relative to the camera). - */ -void cutscene_grand_star_accel_cvar2(UNUSED struct Camera *c) { - camera_approach_f32_symmetric_bool(&sCutsceneVars[2].point[2], -40.f, 2.0f); - sCutsceneVars[2].point[0] = 5.0f; -} - -/** - * Decrease cvar2 offset, follow Mario by directly updating the camera's pos. - */ -void cutscene_grand_star_approach_mario(struct Camera *c) { - camera_approach_f32_symmetric_bool(&sCutsceneVars[2].point[2], 0.f, 2.f); - sCutsceneVars[2].point[0] = 0.f; - approach_f32_asymptotic_bool(&c->pos[0], sMarioCamState->pos[0], 0.01f); - approach_f32_asymptotic_bool(&c->pos[2], sMarioCamState->pos[2], 0.01f); -} - -/** - * Offset the camera's position by cvar2. Before Mario triple jumps, this moves back and to the left. - * After the triple jump, cvar2 decelerates to 0. - */ -void cutscene_grand_star_move_cvar2(struct Camera *c) { - offset_rotated(c->pos, c->pos, sCutsceneVars[2].point, sCutsceneVars[0].angle); -} - -void cutscene_grand_star_focus_mario(struct Camera *c) { - Vec3f foc; - - vec3f_set(foc, sMarioCamState->pos[0], (sMarioCamState->pos[1] - 307.f) * 0.5f + 407.f, sMarioCamState->pos[2]); - approach_vec3f_asymptotic(c->focus, foc, 0.5f, 0.8f, 0.5f); -} - -/** - * The first part of the grand star cutscene, after Mario has collected the grand star. - */ -void cutscene_grand_star(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - cutscene_event(cutscene_grand_star_start, c, 0, 0); - cutscene_event(cutscene_grand_star_front_of_mario, c, 0, 109); - cutscene_event(cutscene_grand_star_focus_mario, c, 0, -1); - cutscene_event(cutscene_grand_star_mario_jump, c, 110, 110); - cutscene_event(cutscene_grand_star_accel_cvar2, c, 110, 159); - cutscene_event(cutscene_grand_star_approach_mario, c, 160, -1); - cutscene_event(cutscene_grand_star_move_cvar2, c, 110, -1); -} - -/** - * Zero the cvars that are used when Mario is flying. - */ -void cutscene_grand_star_fly_start(struct Camera *c) { - //! cvar7 is unused in grand star - vec3f_set(sCutsceneVars[7].point, 0.5f, 0.5f, 0.5f); - //! cvar6 is unused in grand star - vec3f_set(sCutsceneVars[6].point, 0.01f, 0.01f, 0.01f); - vec3f_set(sCutsceneVars[4].point, 0.f, 0.f, 0.f); - vec3f_set(sCutsceneVars[5].point, 0.f, c->focus[1] - sMarioCamState->pos[1], 0.f); - sCutsceneVars[8].point[2] = 0.f; - sCutsceneVars[8].point[0] = 0.f; -} - -/** - * Decrease the cvar offsets so that Lakitu flies closer to Mario. - */ -void cutscene_grand_star_fly_move_to_mario(UNUSED struct Camera *c) { - Vec3f posOff; - - vec3f_set(posOff, -600.f, 0.f, -400.f); - approach_vec3f_asymptotic(sCutsceneVars[4].point, posOff, 0.05f, 0.05f, 0.05f); - camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[1], 0.f, 2.f); - camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[2], -200.f, 6.f); -} - -/** - * Gradually increase the cvar offsets so Lakitu flies away. Mario flies offscreen to the right. - * - * cvar4 is the position offset from Mario. - * cvar5 is the focus offset from Mario. - * cvar8.point[0] is the approach velocity. - */ -void cutscene_grand_star_fly_mario_offscreen(UNUSED struct Camera *c) { - camera_approach_f32_symmetric_bool(&sCutsceneVars[8].point[0], 15.f, 0.1f); - - camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[0], -2000.f, sCutsceneVars[8].point[0]); - camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[1], 1200.f, sCutsceneVars[8].point[0] / 10.f); - camera_approach_f32_symmetric_bool(&sCutsceneVars[4].point[2], 1000.f, sCutsceneVars[8].point[0] / 10.f); - - camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[0], 0.f, sCutsceneVars[8].point[0]); - camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[1], 1200.f, sCutsceneVars[8].point[0] / 2); - camera_approach_f32_symmetric_bool(&sCutsceneVars[5].point[2], 1000.f, sCutsceneVars[8].point[0] / 1.5f); -} - -/** - * Make Lakitu approach the cvars. - * cvar4 is the position offset. - * cvar5 is the focus offset. - */ -void cutscene_grand_star_fly_app_cvars(struct Camera *c) { - Vec3f goalPos, goalFoc; - f32 dist; - s16 pitch, yaw; - - camera_approach_f32_symmetric_bool(&sCutsceneVars[8].point[2], 90.f, 2.5f); - offset_rotated(goalPos, sMarioCamState->pos, sCutsceneVars[4].point, sMarioCamState->faceAngle); - offset_rotated(goalFoc, sMarioCamState->pos, sCutsceneVars[5].point, sMarioCamState->faceAngle); - - // Move towards goalPos by cvar8's Z speed - vec3f_get_dist_and_angle(goalPos, c->pos, &dist, &pitch, &yaw); - camera_approach_f32_symmetric_bool(&dist, 0, sCutsceneVars[8].point[2]); - vec3f_set_dist_and_angle(goalPos, c->pos, dist, pitch, yaw); - - approach_vec3f_asymptotic(c->pos, goalPos, 0.01f, 0.01f, 0.01f); - approach_vec3f_asymptotic(c->focus, goalFoc, 0.5f, 0.8f, 0.5f); -} - -/** - * Part of the grand star cutscene, starts after Mario is flying. - * - * cvar4 and cvar5 are directions, relative to Mario: - * cvar4 is used as the camera position's offset from Mario. - * cvar5 is used as the camera focus's offset from Mario. - * - * cvar8.point[2] is Lakitu's speed. - */ -void cutscene_grand_star_fly(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - cutscene_event(cutscene_grand_star_fly_start, c, 0, 0); - cutscene_event(cutscene_grand_star_fly_move_to_mario, c, 0, 140); - cutscene_event(cutscene_grand_star_fly_mario_offscreen, c, 141, -1); - cutscene_event(cutscene_grand_star_fly_app_cvars, c, 0, -1); -} - -/** - * Adjust the camera focus towards a point `dist` units in front of Mario. - * @param dist distance in Mario's forward direction. Note that this is relative to Mario, so a negative - * distance will focus in front of Mario, and a positive distance will focus behind him. - */ -void focus_in_front_of_mario(struct Camera *c, f32 dist, f32 speed) { - Vec3f goalFocus, offset; - - offset[0] = 0.f; - offset[2] = dist; - offset[1] = 100.f; - - offset_rotated(goalFocus, sMarioCamState->pos, offset, sMarioCamState->faceAngle); - approach_vec3f_asymptotic(c->focus, goalFocus, speed, speed, speed); -} - -/** - * Approach Mario and look up. Since Mario faces the camera when he collects the star, there's no need - * to worry about the camera's yaw. - */ -void cutscene_dance_move_to_mario(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 600.f, 0.3f); - approach_s16_asymptotic_bool(&pitch, 0x1000, 0x10); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -void cutscene_dance_rotate(struct Camera *c) { - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, 0, 0x200); -} - -void cutscene_dance_rotate_move_back(struct Camera *c) { - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, -15.f, 0, 0); -} - -void cutscene_dance_rotate_move_towards_mario(struct Camera *c) { - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 20.f, 0, 0); -} - -/** - * Speculated to be dance-related due to its proximity to the other dance functions - */ -UNUSED static void cutscene_dance_unused(UNUSED struct Camera *c) { -} - -/** - * Slowly turn to the point 100 units in front of Mario - */ -void cutscene_dance_default_focus_mario(struct Camera *c) { - focus_in_front_of_mario(c, -100.f, 0.2f); -} - -/** - * Focus twice as far away as default dance, and move faster. - */ -void cutscene_dance_rotate_focus_mario(struct Camera *c) { - focus_in_front_of_mario(c, -200.f, 0.03f); -} - -void cutscene_dance_shake_fov(UNUSED struct Camera *c) { - set_fov_shake(0x200, 0x28, 0x8000); -} - -/** - * Handles both the default and rotate dance cutscenes. - * In the default dance: the camera moves closer to Mario, then stays in place. - * In the rotate dance: the camera moves closer and rotates clockwise around Mario. - */ -void cutscene_dance_default_rotate(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sYawSpeed = 0; - set_fov_function(CAM_FOV_DEFAULT); - cutscene_event(cutscene_dance_default_focus_mario, c, 0, 20); - cutscene_event(cutscene_dance_move_to_mario, c, 0, 39); - // Shake the camera on the 4th beat of the music, when Mario gives the peace sign. - cutscene_event(cutscene_dance_shake_fov, c, 40, 40); - - if (c->cutscene != CUTSCENE_DANCE_DEFAULT) { // CUTSCENE_DANCE_ROTATE - cutscene_event(cutscene_dance_rotate_focus_mario, c, 75, 102); - cutscene_event(cutscene_dance_rotate, c, 50, -1); - // These two functions move the camera away and then towards Mario. - cutscene_event(cutscene_dance_rotate_move_back, c, 50, 80); - cutscene_event(cutscene_dance_rotate_move_towards_mario, c, 70, 90); - } else { - // secret star, 100 coin star, or bowser red coin star. - if ((sMarioCamState->action != ACT_STAR_DANCE_NO_EXIT) - && (sMarioCamState->action != ACT_STAR_DANCE_WATER) - && (sMarioCamState->action != ACT_STAR_DANCE_EXIT)) { - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; - transition_next_state(c, 20); - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - } - } -} - -/** - * If the camera's yaw is out of the range of `absYaw` +- `yawMax`, then set the yaw to `absYaw` - */ -void star_dance_bound_yaw(struct Camera *c, s16 absYaw, s16 yawMax) { - s16 yaw; - - vec3f_get_yaw(sMarioCamState->pos, c->pos, &yaw); - s16 yawFromAbs = yaw - absYaw; - - // Because angles are s16, this checks if yaw is negative - if ((yawFromAbs & 0x8000) != 0) { - yawFromAbs = -yawFromAbs; - } - if (yawFromAbs > yawMax) { - yaw = absYaw; - c->nextYaw = yaw; - c->yaw = yaw; - } -} - -/** - * Start the closeup dance cutscene by restricting the camera's yaw in certain areas. - * Store the camera's focus in cvar9. - */ -void cutscene_dance_closeup_start(struct Camera *c) { -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if ((gLastCompletedStarNum == 4) && (gCurrCourseNum == COURSE_JRB)) { - star_dance_bound_yaw(c, 0x0, 0x4000); - } - if ((gLastCompletedStarNum == 1) && (gCurrCourseNum == COURSE_DDD)) { - star_dance_bound_yaw(c, 0x8000, 0x5000); - } - if ((gLastCompletedStarNum == 5) && (gCurrCourseNum == COURSE_WDW)) { - star_dance_bound_yaw(c, 0x8000, 0x800); - } -#endif - - vec3f_copy(sCutsceneVars[9].point, c->focus); - //! cvar8 is unused in the closeup cutscene - sCutsceneVars[8].angle[0] = 0x2000; -} - -/** - * Focus the camera on Mario eye height. - */ -void cutscene_dance_closeup_focus_mario(struct Camera *c) { - Vec3f marioPos; - - vec3f_set(marioPos, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); - approach_vec3f_asymptotic(sCutsceneVars[9].point, marioPos, 0.2f, 0.2f, 0.2f); - vec3f_copy(c->focus, sCutsceneVars[9].point); -} - -/** - * Fly above Mario, looking down. - */ -void cutscene_dance_closeup_fly_above(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - s16 goalPitch = 0x1800; -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if ((gLastCompletedStarNum == 6 && gCurrCourseNum == COURSE_SL) || - (gLastCompletedStarNum == 4 && gCurrCourseNum == COURSE_TTC)) { - goalPitch = 0x800; - } -#endif - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 800.f, 0.05f); - approach_s16_asymptotic_bool(&pitch, goalPitch, 16); - approach_s16_asymptotic_bool(&yaw, c->yaw, 8); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Fly closer right when Mario gives the peace sign. - */ -void cutscene_dance_closeup_fly_closer(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 240.f, 0.4f); - approach_s16_asymptotic_bool(&yaw, c->yaw, 8); - approach_s16_asymptotic_bool(&pitch, 0x1000, 5); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Zoom in by increasing fov to 80 degrees. Most dramatic zoom in the game. - */ -void cutscene_dance_closeup_zoom(UNUSED struct Camera *c) { - set_fov_function(CAM_FOV_APP_80); -} - -/** - * Shake fov, starts on the first frame Mario has the peace sign up. - */ -void cutscene_dance_closeup_shake_fov(UNUSED struct Camera *c) { - set_fov_shake(0x300, 0x30, 0x8000); -} - -/** - * The camera moves in for a closeup on Mario. Used for stars that are underwater or in tight places. - */ -void cutscene_dance_closeup(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - - if (sMarioCamState->action == ACT_STAR_DANCE_WATER) { - cutscene_event(cutscene_dance_closeup_start, c, 0, 0); - cutscene_event(cutscene_dance_closeup_focus_mario, c, 0, -1); - cutscene_event(cutscene_dance_closeup_fly_above, c, 0, 62); - cutscene_event(cutscene_dance_closeup_fly_closer, c, 63, -1); - cutscene_event(cutscene_dance_closeup_zoom, c, 63, 63); - cutscene_event(cutscene_dance_closeup_shake_fov, c, 70, 70); - } else { - cutscene_event(cutscene_dance_closeup_start, c, 0, 0); - cutscene_event(cutscene_dance_closeup_focus_mario, c, 0, -1); - // Almost twice as fast as under water - cutscene_event(cutscene_dance_closeup_fly_above, c, 0, 32); - cutscene_event(cutscene_dance_closeup_fly_closer, c, 33, -1); - cutscene_event(cutscene_dance_closeup_zoom, c, 33, 33); - cutscene_event(cutscene_dance_closeup_shake_fov, c, 40, 40); - } - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -/** - * cvar8.point[2] is the amount to increase distance from Mario - */ -void cutscene_dance_fly_away_start(struct Camera *c) { - Vec3f areaCenter; - - vec3f_copy(sCutsceneVars[9].point, c->focus); - sCutsceneVars[8].point[2] = 65.f; - - if (c->mode == CAMERA_MODE_RADIAL) { - vec3f_set(areaCenter, c->areaCenX, c->areaCenY, c->areaCenZ); - c->yaw = calculate_yaw(areaCenter, c->pos); - c->nextYaw = c->yaw; - } - -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - // Restrict the camera yaw in tight spaces - if ((gLastCompletedStarNum == 6) && (gCurrCourseNum == COURSE_CCM)) { - star_dance_bound_yaw(c, 0x5600, 0x800); - } - if ((gLastCompletedStarNum == 2) && (gCurrCourseNum == COURSE_TTM)) { - star_dance_bound_yaw(c, 0x0, 0x800); - } - if ((gLastCompletedStarNum == 1) && (gCurrCourseNum == COURSE_SL)) { - star_dance_bound_yaw(c, 0x2000, 0x800); - } - if ((gLastCompletedStarNum == 3) && (gCurrCourseNum == COURSE_RR)) { - star_dance_bound_yaw(c, 0x0, 0x800); - } -#endif -} - -void cutscene_dance_fly_away_approach_mario(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 600.f, 0.3f); - approach_s16_asymptotic_bool(&pitch, 0x1000, 16); - approach_s16_asymptotic_bool(&yaw, c->yaw, 8); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -void cutscene_dance_fly_away_focus_mario(struct Camera *c) { - Vec3f marioPos; - - vec3f_set(marioPos, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); - approach_vec3f_asymptotic(sCutsceneVars[9].point, marioPos, 0.2f, 0.2f, 0.2f); - vec3f_copy(c->focus, sCutsceneVars[9].point); -} - -/** - * Slowly pan the camera downwards and to the camera's right, using cvar9's angle. - */ -void cutscene_pan_cvar9(struct Camera *c) { - vec3f_copy(c->focus, sCutsceneVars[9].point); - sCutsceneVars[9].angle[0] -= 29; - sCutsceneVars[9].angle[1] += 29; - pan_camera(c, sCutsceneVars[9].angle[0], sCutsceneVars[9].angle[1]); -} - -/** - * Move backwards and rotate slowly around Mario. - */ -void cutscene_dance_fly_rotate_around_mario(struct Camera *c) { - cutscene_pan_cvar9(c); - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, sCutsceneVars[8].point[2], 0, 0); -} - -/** - * Rotate quickly while Lakitu flies up. - */ -void cutscene_dance_fly_away_rotate_while_flying(struct Camera *c) { - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, 0, 0x80); -} - -void cutscene_dance_fly_away_shake_fov(UNUSED struct Camera *c) { - set_fov_shake(0x400, 0x30, 0x8000); -} - -/** - * After collecting the star, Lakitu flies upwards out of the course. - */ -void cutscene_dance_fly_away(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - cutscene_event(cutscene_dance_fly_away_start, c, 0, 0); - cutscene_event(cutscene_dance_fly_away_focus_mario, c, 0, 30); - cutscene_event(cutscene_dance_fly_away_approach_mario, c, 0, 30); - cutscene_event(cutscene_dance_fly_rotate_around_mario, c, 55, 124); - cutscene_event(cutscene_dance_fly_away_rotate_while_flying, c, 55, 124); - cutscene_event(cutscene_dance_fly_away_shake_fov, c, 40, 40); - set_fov_function(CAM_FOV_DEFAULT); - set_handheld_shake(HAND_CAM_SHAKE_STAR_DANCE); -} - -/** - * Jump the camera pos and focus to cvar 8 and 7. - * Called every frame, starting after 10, so when these cvars are updated, the camera will jump. - */ -void cutscene_key_dance_jump_cvar(struct Camera *c) { - offset_rotated(c->pos, sMarioCamState->pos, sCutsceneVars[8].point, sMarioCamState->faceAngle); - offset_rotated(c->focus, sMarioCamState->pos, sCutsceneVars[7].point, sMarioCamState->faceAngle); -} - -/** - * Jump to a closeup view of Mario and the key. - */ -void cutscene_key_dance_jump_closeup(UNUSED struct Camera *c) { - vec3f_set(sCutsceneVars[8].point, 38.f, 171.f, -248.f); - vec3f_set(sCutsceneVars[7].point, -57.f, 51.f, 187.f); -} - -/** - * Jump to a view from the lower left (Mario's right). - */ -void cutscene_key_dance_jump_lower_left(UNUSED struct Camera *c) { - vec3f_set(sCutsceneVars[8].point, -178.f, 62.f, -132.f); - vec3f_set(sCutsceneVars[7].point, 299.f, 91.f, 58.f); -} - -/** - * Jump to a rotated view from above. - */ -void cutscene_key_dance_jump_above(UNUSED struct Camera *c) { - gLakituState.keyDanceRoll = 0x2800; - vec3f_set(sCutsceneVars[8].point, 89.f, 373.f, -304.f); - vec3f_set(sCutsceneVars[7].point, 0.f, 127.f, 0.f); -} - -/** - * Finally, jump to a further view, slightly to Mario's left. - */ -void cutscene_key_dance_jump_last(UNUSED struct Camera *c) { - gLakituState.keyDanceRoll = 0; - vec3f_set(sCutsceneVars[8].point, 135.f, 158.f, -673.f); - vec3f_set(sCutsceneVars[7].point, -20.f, 135.f, -198.f); -} - -void cutscene_key_dance_shake_fov(UNUSED struct Camera *c) { - set_fov_shake(0x180, 0x30, 0x8000); -} - -void cutscene_key_dance_handheld_shake(UNUSED struct Camera *c) { - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -void cutscene_key_dance_focus_mario(struct Camera *c) { - focus_in_front_of_mario(c, 0, 0.2f); -} - -/** - * Cutscene that plays when Mario collects a key from bowser. It's basically a sequence of four jump - * cuts. - */ -void cutscene_key_dance(struct Camera *c) { - cutscene_event(cutscene_dance_move_to_mario, c, 0, 10); - cutscene_event(cutscene_key_dance_focus_mario, c, 0, 10); - cutscene_event(cutscene_key_dance_jump_closeup, c, 0, 0); - cutscene_event(cutscene_key_dance_jump_lower_left, c, 20, 20); - cutscene_event(cutscene_key_dance_jump_above, c, 35, 35); - cutscene_event(cutscene_key_dance_jump_last, c, 52, 52); - cutscene_event(cutscene_key_dance_jump_cvar, c, 11, -1); - cutscene_event(cutscene_key_dance_shake_fov, c, 54, 54); - cutscene_event(cutscene_key_dance_handheld_shake, c, 52, -1); -} - -void cutscene_bowser_area_shake_fov(UNUSED struct Camera *c) { - cutscene_set_fov_shake_preset(2); -} - -/** - * Set oBowserCamAct to 1, which causes bowser to start walking. - */ -void cutscene_bowser_area_start_bowser_walking(UNUSED struct Camera *c) { - gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_WALK; -} - -/** - * Offset the camera from bowser using cvar2 and cvar3 - * @bug cvar2.point is (0,0,0) on the first frame, but because of the warp transition, this behavior - * isn't seen. After the first frame, cvar2.point is bowser's position. - */ -void cutscene_bowser_arena_set_pos(struct Camera *c) { - vec3f_set_dist_and_angle(sCutsceneVars[2].point, c->pos, sCutsceneVars[3].point[2], - sCutsceneVars[3].angle[0], sCutsceneVars[3].angle[1]); - vec3f_set(sCutsceneVars[2].point, gSecondCameraFocus->oPosX, gSecondCameraFocus->oPosY, - gSecondCameraFocus->oPosZ); -} - -/** - * Apply a sine wave to the focus's y coordinate. - * The y offset starts at 120, then decreases to 0 before reaching ~240 on the last frame. - */ -void cutscene_bowser_arena_focus_sine(UNUSED struct Camera *c) { - // cvar4 was zeroed when the cutscene started. - f32 yOff = sins(sCutsceneVars[4].angle[1]) * 120.0f + 120.0f; - sCutsceneVars[4].angle[1] -= 0x200; - approach_f32_asymptotic_bool(&sCutsceneVars[0].point[1], yOff, 0.5f); -} - -/** - * Set the camera focus according to cvar0 and cvar2. - */ -void cutscene_bowser_arena_set_focus(struct Camera *c) { - offset_rotated(c->focus, sCutsceneVars[2].point, sCutsceneVars[0].point, sCutsceneVars[2].angle); -} - -/** - * Adjust the cvar offsets, making the camera look up, move slightly further back, and focus a little - * further in front of bowser. - */ -void cutscene_bowser_arena_adjust_offsets(UNUSED struct Camera *c) { - approach_s16_asymptotic_bool(&sCutsceneVars[3].angle[0], 0x6C8, 30); - approach_f32_asymptotic_bool(&sCutsceneVars[0].point[2], -200.f, 0.02f); - approach_f32_asymptotic_bool(&sCutsceneVars[3].point[2], 550.f, 0.02f); -} - -/** - * Decrease cvar0's z offset, making the camera focus pan left towards bowser. - */ -void cutscene_bowser_arena_pan_left(UNUSED struct Camera *c) { - approach_f32_asymptotic_bool(&sCutsceneVars[0].point[2], 0.f, 0.05f); -} - -/** - * Duplicate of cutscene_mario_dialog(). - */ -void cutscene_bowser_arena_mario_dialog(UNUSED struct Camera *c) { - cutscene_common_set_dialog_state(MARIO_DIALOG_LOOK_FRONT); -} - -void cutscene_stop_dialog(UNUSED struct Camera *c) { - cutscene_common_set_dialog_state(MARIO_DIALOG_STOP); -} - -/** - * Active for the first 5 frames of the cutscene. - * cvar3 is the camera's polar offset from bowser - * cvar2.angle is bowser's move angle - * - * cvar0 is the focus offset from bowser - */ -void cutscene_bowser_arena_start(struct Camera *c) { - sCutsceneVars[3].point[2] = 430.f; - sCutsceneVars[3].angle[1] = gSecondCameraFocus->oMoveAngleYaw - DEGREES(45); - sCutsceneVars[3].angle[0] = 0xD90; - - //! Tricky math: Bowser starts at (0, 307, -1000), with a moveAngle of (0,0,0). A sane person would - //! expect this offset to move the focus to (0, 427, -1800). - //! BUT because offset_rotated() flips the Z direction (to match sm64's coordinate system), this - //! offset actually moves the focus to (0, 427, -200) - vec3f_set(sCutsceneVars[0].point, 0.f, 120.f, -800.f); - vec3s_set(sCutsceneVars[2].angle, gSecondCameraFocus->oMoveAnglePitch, - gSecondCameraFocus->oMoveAngleYaw, gSecondCameraFocus->oMoveAngleRoll); - - // Set the camera's position and focus. - cutscene_bowser_arena_set_pos(c); - cutscene_bowser_arena_set_focus(c); -} - -/** - * Create the dialog box depending on which bowser fight Mario is in. - */ -void bowser_fight_intro_dialog(UNUSED struct Camera *c) { - s16 dialog; - - switch (gCurrLevelNum) { - case LEVEL_BOWSER_1: - dialog = DIALOG_067; - break; - case LEVEL_BOWSER_2: - dialog = DIALOG_092; - break; - default: // LEVEL_BOWSER_3 - dialog = DIALOG_093; - } - - create_dialog_box(dialog); -} - -/** - * Create the dialog box and wait until it's gone. - */ -void cutscene_bowser_arena_dialog(struct Camera *c) { - cutscene_event(bowser_fight_intro_dialog, c, 0, 0); - - if (get_dialog_id() == DIALOG_NONE) { - gCutsceneTimer = CUTSCENE_LOOP; - } -} - -/** - * End the bowser arena cutscene. - */ -void cutscene_bowser_arena_end(struct Camera *c) { - cutscene_stop_dialog(c); - c->cutscene = 0; - transition_next_state(c, 20); - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - sModeOffsetYaw = sMarioCamState->faceAngle[1] + DEGREES(90); - gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_END; -} - -/** - * Cutscene that plays when Mario enters a bowser fight. - */ -void cutscene_bowser_arena(struct Camera *c) { - //! This does nothing, but may have been used in development - cutscene_spawn_obj(CUTSCENE_OBJ_UNUSED_2, 0); - - if (gSecondCameraFocus != NULL) { - cutscene_event(cutscene_bowser_arena_mario_dialog, c, 0, -1); - cutscene_event(cutscene_bowser_arena_start, c, 0, 5); - cutscene_event(cutscene_bowser_area_start_bowser_walking, c, 40, 40); - cutscene_event(cutscene_bowser_area_shake_fov, c, 145, 145); - cutscene_event(cutscene_bowser_arena_set_pos, c, 40, -1); - cutscene_event(cutscene_bowser_arena_pan_left, c, 40, 99); - cutscene_event(cutscene_bowser_arena_adjust_offsets, c, 100, -1); - cutscene_event(cutscene_bowser_arena_focus_sine, c, 40, 140); - cutscene_event(cutscene_bowser_arena_set_focus, c, 40, -1); - cutscene_event(cutscene_shake_explosion, c, 60, 60); - cutscene_event(cutscene_shake_explosion, c, 82, 82); - cutscene_event(cutscene_shake_explosion, c, 109, 109); - cutscene_event(cutscene_shake_explosion, c, 127, 127); - } -} - -void cutscene_star_spawn_store_info(struct Camera *c) { - store_info_star(c); -} - -/** - * Focus on the top of the star. - */ -void cutscene_star_spawn_focus_star(struct Camera *c) { - Vec3f starPos; - - if (gCutsceneFocus != NULL) { - object_pos_to_vec3f(starPos, gCutsceneFocus); - starPos[1] += gCutsceneFocus->hitboxHeight; - approach_vec3f_asymptotic(c->focus, starPos, 0.1f, 0.1f, 0.1f); - } -} - -/** - * Use boss fight mode's update function to move the focus back. - */ -void cutscene_star_spawn_update_boss_fight(struct Camera *c) { - Vec3f pos, focus; - - update_boss_fight_camera(c, focus, pos); - approach_vec3f_asymptotic(c->focus, focus, 0.2f, 0.2f, 0.2f); - approach_vec3f_asymptotic(c->pos, pos, 0.2f, 0.2f, 0.2f); -} - -/** - * Fly back to the camera's previous pos and focus. - */ -void cutscene_star_spawn_fly_back(struct Camera *c) { - retrieve_info_star(c); - transition_next_state(c, 15); -} - -/** - * Plays when a star spawns (ie from a box). - */ -void cutscene_star_spawn(struct Camera *c) { - cutscene_event(cutscene_star_spawn_store_info, c, 0, 0); - cutscene_event(cutscene_star_spawn_focus_star, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - - if (gObjCutsceneDone) { - // Set the timer to CUTSCENE_LOOP, which start the next shot. - gCutsceneTimer = CUTSCENE_LOOP; - } -} - -/** - * Move the camera back to Mario. - */ -void cutscene_star_spawn_back(struct Camera *c) { - if ((c->mode == CAMERA_MODE_BOSS_FIGHT) && (set_cam_angle(0) == CAM_ANGLE_LAKITU)) { - cutscene_event(cutscene_star_spawn_update_boss_fight, c, 0, -1); - } else { - cutscene_event(cutscene_star_spawn_fly_back, c, 0, 0); - } - - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; -} - -void cutscene_star_spawn_end(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; -} - -void cutscene_exit_waterfall_warp(struct Camera *c) { - //! hardcoded position - vec3f_set(c->pos, -3899.f, 39.f, -5671.f); -} - -/** - * Look at Mario, used by cutscenes that play when Mario exits a course to castle grounds. - */ -void cutscene_exit_to_castle_grounds_focus_mario(struct Camera *c) { - vec3f_copy(c->focus, sMarioCamState->pos); - c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] + 125.f - c->pos[1]) * 0.5f; - approach_vec3f_asymptotic(c->focus, sMarioCamState->pos, 0.05f, 0.4f, 0.05f); -} - -/** - * Cutscene that plays when Mario leaves CotMC through the waterfall. - */ -void cutscene_exit_waterfall(struct Camera *c) { - cutscene_event(cutscene_exit_waterfall_warp, c, 0, 0); - cutscene_event(cutscene_exit_to_castle_grounds_focus_mario, c, 0, -1); - update_camera_yaw(c); -} - -/** - * End the cutscene, used by cutscenes that play when Mario exits a course to castle grounds. - */ -void cutscene_exit_to_castle_grounds_end(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; - update_camera_yaw(c); -} - -void cutscene_exit_fall_to_castle_grounds_warp(struct Camera *c) { - //! hardcoded position - vec3f_set(c->pos, 5830.f, 32.f, 3985.f); -} - -/** - * Cutscene that plays when Mario falls from WMOTR. - */ -void cutscene_exit_fall_to_castle_grounds(struct Camera *c) { - cutscene_event(cutscene_exit_fall_to_castle_grounds_warp, c, 0, 0); - cutscene_event(cutscene_exit_to_castle_grounds_focus_mario, c, 0, -1); - update_camera_yaw(c); -} - -/** - * Start the red coin star spawning cutscene. - */ -void cutscene_red_coin_star_start(struct Camera *c) { - object_pos_to_vec3f(sCutsceneVars[1].point, gCutsceneFocus); - store_info_star(c); - // Store the default fov for after the cutscene - sCutsceneVars[2].point[2] = sFOVState.fov; -} - -/** - * Look towards the star's x and z position - */ -void cutscene_red_coin_star_focus_xz(struct Camera *c) { - approach_f32_asymptotic_bool(&c->focus[0], gCutsceneFocus->oPosX, 0.15f); - approach_f32_asymptotic_bool(&c->focus[2], gCutsceneFocus->oPosZ, 0.15f); -} - -/** - * Look towards the star's y position. Only active before the camera warp. - */ -void cutscene_red_coin_star_focus_y(struct Camera *c) { - approach_f32_asymptotic_bool(&c->focus[1], gCutsceneFocus->oPosY, 0.1f); -} - -/** - * Look 80% up towards the star. Only active after the camera warp. - */ -void cutscene_red_coin_star_look_up_at_star(struct Camera *c) { - c->focus[1] = sCutsceneVars[1].point[1] + (gCutsceneFocus->oPosY - sCutsceneVars[1].point[1]) * 0.8f; -} - -/** - * Warp the camera near the star's spawn point - */ -void cutscene_red_coin_star_warp(struct Camera *c) { - f32 dist; - s16 pitch, yaw, posYaw; - struct Object *obj = gCutsceneFocus; - - vec3f_set(sCutsceneVars[1].point, obj->oHomeX, obj->oHomeY, obj->oHomeZ); - vec3f_get_dist_and_angle(sCutsceneVars[1].point, c->pos, &dist, &pitch, &yaw); - posYaw = calculate_yaw(sCutsceneVars[1].point, c->pos); - yaw = calculate_yaw(sCutsceneVars[1].point, sMarioCamState->pos); - - if (ABS(yaw - posYaw + DEGREES(90)) < ABS(yaw - posYaw - DEGREES(90))) { - yaw += DEGREES(90); - } else { - yaw -= DEGREES(90); - } - - vec3f_set_dist_and_angle(sCutsceneVars[1].point, c->pos, 400.f, 0x1000, yaw); - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; -} - -/** - * Zoom out while looking at the star. - */ -void cutscene_red_coin_star_set_fov(UNUSED struct Camera *c) { - sFOVState.fov = 60.f; -} - -void cutscene_red_coin_star(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - cutscene_event(cutscene_red_coin_star_start, c, 0, 0); - cutscene_event(cutscene_red_coin_star_warp, c, 30, 30); - cutscene_event(cutscene_red_coin_star_focus_xz, c, 0, -1); - cutscene_event(cutscene_red_coin_star_focus_y, c, 0, 29); - cutscene_event(cutscene_red_coin_star_look_up_at_star, c, 30, -1); - cutscene_event(cutscene_red_coin_star_set_fov, c, 30, -1); - - if (gObjCutsceneDone) { - // Set the timer to CUTSCENE_LOOP, which start the next shot. - gCutsceneTimer = CUTSCENE_LOOP; - } -} - -/** - * End the red coin star spawning cutscene - */ -void cutscene_red_coin_star_end(struct Camera *c) { - retrieve_info_star(c); - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; - // Restore the default fov - sFOVState.fov = sCutsceneVars[2].point[2]; -} - -/** - * Moves the camera towards the cutscene's focus, stored in sCutsceneVars[3].point - * - * sCutsceneVars[3].point is used as the target point - * sCutsceneVars[0].point is used as the current camera focus during the transition - * - * @param rotPitch constant pitch offset to add to the camera's focus - * @param rotYaw constant yaw offset to add to the camera's focus - */ -void cutscene_goto_cvar_pos(struct Camera *c, f32 goalDist, s16 goalPitch, s16 rotPitch, s16 rotYaw) { - f32 nextDist; - s16 nextPitch, nextYaw; - // The next 2 polar coord points are only used in CUTSCENE_PREPARE_CANNON - f32 cannonDist; - s16 cannonPitch, cannonYaw; - f32 curDist; - s16 curPitch, curYaw; - - vec3f_get_dist_and_angle(sCutsceneVars[3].point, c->pos, &nextDist, &nextPitch, &nextYaw); - - // If over 8000 units away from the cannon, just teleport there - if ((nextDist > 8000.f) && (c->cutscene == CUTSCENE_PREPARE_CANNON)) { - nextDist = goalDist * 4.f; - nextPitch = goalPitch; - vec3f_copy(sCutsceneVars[0].point, sCutsceneVars[3].point); - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelNum == LEVEL_TTM) { - nextYaw = atan2s(sCutsceneVars[3].point[2] - c->areaCenZ, - sCutsceneVars[3].point[0] - c->areaCenX); - } -#endif - } else { - if (c->cutscene == CUTSCENE_PREPARE_CANNON) { - vec3f_get_dist_and_angle(c->pos, sCutsceneVars[0].point, &curDist, &curPitch, &curYaw); - vec3f_get_dist_and_angle(c->pos, sCutsceneVars[3].point, &cannonDist, &cannonPitch, &cannonYaw); - approach_f32_asymptotic_bool(&curDist, cannonDist, 0.1f); - approach_s16_asymptotic_bool(&curPitch, cannonPitch, 15); - approach_s16_asymptotic_bool(&curYaw, cannonYaw, 15); - // Move the current focus, sCutsceneVars[0].point, in the direction towards the cannon - vec3f_set_dist_and_angle(c->pos, sCutsceneVars[0].point, curDist, curPitch, curYaw); - } else { - approach_vec3f_asymptotic(sCutsceneVars[0].point, sCutsceneVars[3].point, 0.1f, 0.1f, 0.1f); - } - } - - approach_f32_asymptotic_bool(&nextDist, goalDist, 0.05f); - approach_s16_asymptotic_bool(&nextPitch, goalPitch, 0x20); - - vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, nextDist, nextPitch, nextYaw); - vec3f_copy(c->focus, sCutsceneVars[0].point); - - // Apply the constant rotation given - pan_camera(c, rotPitch, rotYaw); - vec3f_get_dist_and_angle(c->pos, c->focus, &nextDist, &nextPitch, &nextYaw); - - if (nextPitch < -0x3000) { - nextPitch = -0x3000; - } - if (nextPitch > 0x3000) { - nextPitch = 0x3000; - } - - vec3f_set_dist_and_angle(c->pos, c->focus, nextDist, nextPitch, nextYaw); -} - -/** - * Store the camera's pos and focus, and copy the cannon's position to cvars. - */ -void cutscene_prepare_cannon_start(struct Camera *c) { - store_info_cannon(c); - vec3f_copy(sCutsceneVars[0].point, c->focus); - sCutsceneVars[2].point[0] = 30.f; - // Store the cannon door's position in sCutsceneVars[3]'s point - object_pos_to_vec3f(sCutsceneVars[3].point, gCutsceneFocus); - vec3s_set(sCutsceneVars[5].angle, 0, 0, 0); -} - -/** - * Fly towards the cannon door. - */ -void cutscene_prepare_cannon_fly_to_cannon(struct Camera *c) { - cutscene_goto_cvar_pos(c, 300.f, 0x2000, 0, sCutsceneVars[5].angle[1]); - camera_approach_s16_symmetric_bool(&sCutsceneVars[5].angle[1], 0x400, 17); - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -/** - * Used in the cannon opening cutscene to fly back to the camera's last position and focus - */ -void cannon_approach_prev(f32 *value, f32 target) { - f32 inc = absf(target - *value) / sCutsceneVars[2].point[0]; - camera_approach_f32_symmetric_bool(value, target, inc); -} - -/** - * Fly or warp back to the previous pos and focus, stored in sCameraStoreCutscene. - */ -void cutscene_prepare_cannon_fly_back(struct Camera *c) { - f32 distToPrevPos = calc_abs_dist_squared(c->pos, sCameraStoreCutscene.pos); - - if (distToPrevPos < sqr(8000.f)) { - cannon_approach_prev(&c->pos[0], sCameraStoreCutscene.pos[0]); - cannon_approach_prev(&c->pos[1], sCameraStoreCutscene.pos[1]); - cannon_approach_prev(&c->pos[2], sCameraStoreCutscene.pos[2]); - cannon_approach_prev(&c->focus[0], sCameraStoreCutscene.focus[0]); - cannon_approach_prev(&c->focus[1], sCameraStoreCutscene.focus[1]); - cannon_approach_prev(&c->focus[2], sCameraStoreCutscene.focus[2]); - } else { - // If too far away, just warp back - vec3f_copy(c->focus, sCameraStoreCutscene.focus); - vec3f_copy(c->pos, sCameraStoreCutscene.pos); - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - } - if (sCutsceneVars[2].point[0] > 1.f) { - sCutsceneVars[2].point[0] -= 1.f; - } -} - -/** - * Cutscene that plays when the cannon is opened. - */ -void cutscene_prepare_cannon(struct Camera *c) { - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - cutscene_event(cutscene_prepare_cannon_start, c, 0, 0); - cutscene_event(cutscene_prepare_cannon_fly_to_cannon, c, 0, 140); - cutscene_event(cutscene_prepare_cannon_fly_back, c, 141, -1); -} - -/** - * Stop the cannon opening cutscene. - */ -void cutscene_prepare_cannon_end(struct Camera *c) { - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; - retrieve_info_cannon(c); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; -} - -/** - * Moves the camera to Mario's side when Mario starts ACT_WATER_DEATH - * Note that ACT_WATER_DEATH only starts when Mario gets hit by an enemy under water. It does not start - * when Mario drowns. - */ -void water_death_move_to_mario_side(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_s16_asymptotic_bool(&yaw, (sMarioCamState->faceAngle[1] - 0x3000), 8); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Unnecessary, only used in cutscene_death_standing_goto_mario() - */ -void death_goto_mario(struct Camera *c) { - cutscene_goto_cvar_pos(c, 400.f, 0x1000, 0x300, 0); -} - -void cutscene_death_standing_start(struct Camera *c) { - vec3f_copy(sCutsceneVars[0].point, c->focus); - vec3f_copy(sCutsceneVars[3].point, sMarioCamState->pos); - sCutsceneVars[3].point[1] += 70.f; -} - -/** - * Fly to Mario and turn on handheld shake. - */ -void cutscene_death_standing_goto_mario(struct Camera *c) { - death_goto_mario(c); - set_handheld_shake(HAND_CAM_SHAKE_HIGH); -} - -/** - * Cutscene that plays when Mario dies while standing. - */ -void cutscene_death_standing(struct Camera *c) { - cutscene_event(cutscene_death_standing_start, c, 0, 0); - cutscene_event(cutscene_death_standing_goto_mario, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; -} - -void cutscene_death_stomach_start(struct Camera *c) { - Vec3f offset = { 0, 40.f, -60.f }; - - offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, offset, sMarioCamState->faceAngle); - vec3f_copy(sCutsceneVars[0].point, c->focus); -} - -void cutscene_death_stomach_goto_mario(struct Camera *c) { - cutscene_goto_cvar_pos(c, 400.f, 0x1800, 0, -0x400); -} - -/** - * Cutscene that plays when Mario dies on his stomach. - */ -void cutscene_death_stomach(struct Camera *c) { - cutscene_event(cutscene_death_stomach_start, c, 0, 0); - cutscene_event(cutscene_death_stomach_goto_mario, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -void cutscene_bbh_death_start(struct Camera *c) { - Vec3f dir = { 0, 40.f, 60.f }; - - offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, dir, sMarioCamState->faceAngle); - vec3f_copy(sCutsceneVars[0].point, c->focus); -} - -void cutscene_bbh_death_goto_mario(struct Camera *c) { - cutscene_goto_cvar_pos(c, 400.f, 0x1800, 0, 0x400); -} - -/** - * Cutscene that plays when Mario dies in BBH. - */ -void cutscene_bbh_death(struct Camera *c) { - cutscene_event(cutscene_bbh_death_start, c, 0, 0); - cutscene_event(cutscene_bbh_death_goto_mario, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -/** - * Copy the camera's focus to cvar0 - */ -void cutscene_quicksand_death_start(struct Camera *c) { - vec3f_copy(sCutsceneVars[0].point, c->focus); -} - -/** - * Fly closer to Mario. In WATER_DEATH, move to Mario's side. - */ -void cutscene_quicksand_death_goto_mario(struct Camera *c) { - cutscene_goto_cvar_pos(c, 400.f, 0x2800, 0x200, 0); - - if (c->cutscene == CUTSCENE_WATER_DEATH) { - water_death_move_to_mario_side(c); - } -} - -/** - * Cutscene that plays when Mario dies in quicksand. - */ -void cutscene_quicksand_death(struct Camera *c) { - sCutsceneVars[3].point[0] = sMarioCamState->pos[0]; - sCutsceneVars[3].point[1] = sMarioCamState->pos[1] + 20.f; - sCutsceneVars[3].point[2] = sMarioCamState->pos[2]; - - cutscene_event(cutscene_quicksand_death_start, c, 0, 0); - cutscene_event(cutscene_quicksand_death_goto_mario, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - set_handheld_shake(HAND_CAM_SHAKE_HIGH); -} - -/** - * Fly away from Mario near the end of the cutscene. - */ -void cutscene_suffocation_fly_away(UNUSED struct Camera *c) { - Vec3f target; - Vec3f offset = { 0, 20.f, 120.f }; - - offset_rotated(target, sMarioCamState->pos, offset, sMarioCamState->faceAngle); - approach_vec3f_asymptotic(sCutsceneVars[3].point, target, 0.1f, 0.1f, 0.1f); -} - -/** - * Keep Lakitu above the gas level. - */ -void cutscene_suffocation_stay_above_gas(struct Camera *c) { - cutscene_goto_cvar_pos(c, 400.f, 0x2800, 0x200, 0); - f32 gasLevel = find_poison_gas_level(sMarioCamState->pos[0], sMarioCamState->pos[2]); - - if (gasLevel != FLOOR_LOWER_LIMIT) { - if ((gasLevel += 130.f) > c->pos[1]) { - c->pos[1] = gasLevel; - } - } -} - -/** - * Quickly rotate around Mario. - */ -void cutscene_suffocation_rotate(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - yaw += 0x100; - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Cutscene that plays when Mario dies from suffocation (ie due to HMC gas). - */ -void cutscene_suffocation(struct Camera *c) { - cutscene_event(cutscene_death_stomach_start, c, 0, 0); - cutscene_event(cutscene_suffocation_rotate, c, 0, -1); - cutscene_event(cutscene_suffocation_stay_above_gas, c, 0, -1); - cutscene_event(cutscene_suffocation_fly_away, c, 50, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - set_handheld_shake(HAND_CAM_SHAKE_HIGH); -} - -void cutscene_enter_pool_start(struct Camera *c) { - vec3f_copy(sCutsceneVars[3].point, sMarioCamState->pos); - -#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS - if (gCurrLevelNum == LEVEL_CASTLE) { // entering HMC - vec3f_set(sCutsceneVars[3].point, 2485.f, -1589.f, -2659.f); - } - if (gCurrLevelNum == LEVEL_HMC) { // entering CotMC - vec3f_set(sCutsceneVars[3].point, 3350.f, -4589.f, 4800.f); - } -#endif - - vec3f_copy(sCutsceneVars[0].point, c->focus); -} - -void cutscene_enter_pool_loop(struct Camera *c) { - cutscene_goto_cvar_pos(c, 1200.f, 0x2000, 0x200, 0); -} - -void cutscene_enter_pool(struct Camera *c) { - cutscene_event(cutscene_enter_pool_start, c, 0, 0); - cutscene_event(cutscene_enter_pool_loop, c, 0, -1); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; -} - -/** - * Store the camera focus in cvar1. - * Store the area's center position (which happens to be the pyramid, in SSL) in cvar3. - */ -void cutscene_pyramid_top_explode_start(struct Camera *c) { - reset_pan_distance(c); - store_info_cannon(c); - - vec3f_copy(sCutsceneVars[1].point, c->focus); - vec3f_set(sCutsceneVars[3].point, c->areaCenX, 1280.f, c->areaCenZ); -} - -/** - * Zoom in on the pyramid. - */ -void cutscene_pyramid_top_explode_zoom_in(UNUSED struct Camera *c) { - set_fov_function(CAM_FOV_APP_30); -} - -/** - * Look at the pyramid top. - */ -void cutscene_pyramid_top_explode_focus(struct Camera *c) { - approach_vec3f_asymptotic(c->focus, sCutsceneVars[3].point, 0.02f, 0.02f, 0.02f); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; -} - -/** - * Store the old pos and focus, then warp to the pyramid top. - */ -void cutscene_pyramid_top_explode_warp(struct Camera *c) { - s16 pitch, yaw; - f32 dist; +void reset_camera(struct Camera *c) { + gCamera = c; + s2ndRotateFlags = 0; + sStatusFlags = 0; + reset_cutscene_vars(); + gSecondCameraFocus = NULL; + sCButtonsPressed = 0; + vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); + sModeTransition.framesLeft = 0; + gCameraMovementFlags = CAM_MOVE_INIT_CAMERA; + sStatusFlags = 0; + sCameraSoundFlags = 0; + sCUpCameraPitch = 0; + sModeOffsetYaw = 0; + sSpiralStairsYawOffset = 0; + sLakituDist = 0; + sLakituPitch = 0; + sAreaYaw = 0; + sAreaYawChange = 0.f; + sPanDistance = 0.f; + sCannonYOffset = 0.f; + sZoomAmount = 0.f; + sZeroZoomDist = 0.f; + sBehindMarioSoundTimer = 0; + sCSideButtonYaw = 0; + s8DirModeBaseYaw = 0; + s8DirModeYawOffset = 0; + c->doorStatus = DOOR_DEFAULT; + sMarioCamState->headRotation[0] = 0; + sMarioCamState->headRotation[1] = 0; + sMarioCamState->cameraEvent = CAM_EVENT_NONE; + sMarioCamState->usedObj = NULL; + gLakituState.shakeMagnitude[0] = 0; + gLakituState.shakeMagnitude[1] = 0; + gLakituState.shakeMagnitude[2] = 0; + gLakituState.lastFrameAction = 0; set_fov_function(CAM_FOV_DEFAULT); sFOVState.fov = 45.f; - - vec3f_copy(sCutsceneVars[4].point, c->pos); - vec3f_copy(sCutsceneVars[5].point, c->focus); - vec3f_copy(c->focus, sCutsceneVars[3].point); - - vec3f_get_dist_and_angle(sCutsceneVars[3].point, sMarioCamState[0].pos, &dist, &pitch, &yaw); - vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, 2000.f, 0, yaw); - c->pos[1] += 500.f; -} - -/** - * Close up view of the spinning pyramid top as it rises. - */ -void cutscene_pyramid_top_explode_closeup(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - vec3f_get_dist_and_angle(sCutsceneVars[3].point, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 2000.f, 0.1f); - vec3f_set_dist_and_angle(sCutsceneVars[3].point, c->pos, dist, pitch, yaw); - - c->focus[1] += 4.f; - c->pos[1] -= 5.f; - sFOVState.fov = 45.f; - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); -} - -/** - * Shake the camera during the closeup. - */ -void cutscene_pyramid_top_explode_cam_shake(UNUSED struct Camera *c) { - set_environmental_camera_shake(SHAKE_ENV_PYRAMID_EXPLODE); -} - -/** - * Warp back to the old position, and start a heavy camera shake. - */ -void cutscene_pyramid_top_explode_warp_back(struct Camera *c) { - vec3f_copy(c->pos, sCutsceneVars[4].point); - vec3f_copy(c->focus, sCutsceneVars[5].point); - set_environmental_camera_shake(SHAKE_ENV_BOWSER_JUMP); -} - -/** - * An unused cutscene for when the pyramid explodes. - */ -void cutscene_pyramid_top_explode(struct Camera *c) { - cutscene_event(cutscene_pyramid_top_explode_start, c, 0, 0); - cutscene_event(cutscene_pyramid_top_explode_focus, c, 0, 30); - cutscene_event(cutscene_pyramid_top_explode_warp, c, 31, 31); - cutscene_event(cutscene_pyramid_top_explode_closeup, c, 31, 139); - cutscene_event(cutscene_pyramid_top_explode_zoom_in, c, 23, 23); - cutscene_event(cutscene_pyramid_top_explode_warp_back, c, 140, 140); - cutscene_event(cutscene_pyramid_top_explode_cam_shake, c, 31, 139); -} - -/** - * End the pyramid top explosion cutscene. - */ -void cutscene_pyramid_top_explode_end(struct Camera *c) { - cutscene_stop_dialog(c); - stop_cutscene_and_retrieve_stored_info(c); - // Move the camera back to Mario - transition_next_state(c, 30); -} - -/** - * Store the camera focus in cvar0, and store the top of the pyramid in cvar3. - */ -void cutscene_enter_pyramid_top_start(struct Camera *c) { - vec3f_copy(sCutsceneVars[0].point, c->focus); - vec3f_set(sCutsceneVars[3].point, c->areaCenX, 1280.f, c->areaCenZ); -} - -/** - * Cutscene that plays when Mario enters the top of the pyramid. - */ -void cutscene_enter_pyramid_top(struct Camera *c) { - cutscene_event(cutscene_enter_pyramid_top_start, c, 0, 0); - // Move to cvar3 - cutscene_goto_cvar_pos(c, 200.f, 0x3000, 0, 0); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - set_handheld_shake(HAND_CAM_SHAKE_CUTSCENE); - - if (sMarioCamState->pos[1] > 1250.f) { - // End the cutscene early if Mario ledge-grabbed. - // This only works because of the janky way that ledge-grabbing is implemented. - cutscene_exit_to_castle_grounds_end(c); - } -} - -/** - * cvar8 is Mario's position and faceAngle - * - * cvar9.point is gCutsceneFocus's position - * cvar9.angle[1] is the yaw between Mario and the gCutsceneFocus - */ -void cutscene_dialog_start(struct Camera *c) { - s16 yaw; - - cutscene_soften_music(c); - set_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); - - if (c->mode == CAMERA_MODE_BOSS_FIGHT) { - vec3f_copy(sCameraStoreCutscene.focus, c->focus); - vec3f_copy(sCameraStoreCutscene.pos, c->pos); - } else { - store_info_star(c); - } - - // Store Mario's position and faceAngle - sCutsceneVars[8].angle[0] = 0; - vec3f_copy(sCutsceneVars[8].point, sMarioCamState->pos); - sCutsceneVars[8].point[1] += 125.f; - - // Store gCutsceneFocus's position and yaw - object_pos_to_vec3f(sCutsceneVars[9].point, gCutsceneFocus); - sCutsceneVars[9].point[1] += gCutsceneFocus->hitboxHeight + 200.f; - sCutsceneVars[9].angle[1] = calculate_yaw(sCutsceneVars[8].point, sCutsceneVars[9].point); - - yaw = calculate_yaw(sMarioCamState->pos, gLakituState.curPos); - if ((yaw - sCutsceneVars[9].angle[1]) & 0x8000) { - sCutsceneVars[9].angle[1] -= 0x6000; - } else { - sCutsceneVars[9].angle[1] += 0x6000; - } -} - -/** - * Move closer to Mario and the object, adjusting to their difference in height. - * The camera's generally ends up looking over Mario's shoulder. - */ -void cutscene_dialog_move_mario_shoulder(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - Vec3f focus, pos; - - scale_along_line(focus, sCutsceneVars[9].point, sMarioCamState->pos, 0.7f); - vec3f_get_dist_and_angle(c->pos, focus, &dist, &pitch, &yaw); - pitch = calculate_pitch(c->pos, sCutsceneVars[9].point); - vec3f_set_dist_and_angle(c->pos, pos, dist, pitch, yaw); - - focus[1] = focus[1] + (sCutsceneVars[9].point[1] - focus[1]) * 0.1f; - approach_vec3f_asymptotic(c->focus, focus, 0.2f, 0.2f, 0.2f); - - vec3f_copy(pos, c->pos); - - // Set y pos to cvar8's y (top of focus object) - pos[1] = sCutsceneVars[8].point[1]; - vec3f_get_dist_and_angle(sCutsceneVars[8].point, pos, &dist, &pitch, &yaw); - approach_s16_asymptotic_bool(&yaw, sCutsceneVars[9].angle[1], 0x10); - approach_f32_asymptotic_bool(&dist, 180.f, 0.05f); - vec3f_set_dist_and_angle(sCutsceneVars[8].point, pos, dist, pitch, yaw); - - // Move up if Mario is below the focus object, down is Mario is above - pos[1] = sCutsceneVars[8].point[1] - + sins(calculate_pitch(sCutsceneVars[9].point, sCutsceneVars[8].point)) * 100.f; - - approach_f32_asymptotic_bool(&c->pos[1], pos[1], 0.05f); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; -} - -/** - * Create the dialog with sCutsceneDialogID - */ -void cutscene_dialog_create_dialog_box(struct Camera *c) { - if (c->cutscene == CUTSCENE_RACE_DIALOG) { - create_dialog_box_with_response(sCutsceneDialogID); - } else { - create_dialog_box(sCutsceneDialogID); - } - - //! Unused. This may have been used before sCutsceneDialogResponse was implemented. - sCutsceneVars[8].angle[0] = DIALOG_RESPONSE_NOT_DEFINED; + sFOVState.fovOffset = 0.f; + sFOVState.unusedIsSleeping = 0; + sFOVState.shakeAmplitude = 0.f; + sFOVState.shakePhase = 0; } -/** - * Cutscene that plays when Mario talks to an object. - */ -void cutscene_dialog(struct Camera *c) { - cutscene_event(cutscene_dialog_start, c, 0, 0); - cutscene_event(cutscene_dialog_move_mario_shoulder, c, 0, -1); - cutscene_event(cutscene_dialog_create_dialog_box, c, 10, 10); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - - if (gDialogResponse != DIALOG_RESPONSE_NONE) { - sCutsceneDialogResponse = gDialogResponse; - } - - if ((get_dialog_id() == DIALOG_NONE) && (sCutsceneVars[8].angle[0] != 0)) { - if (c->cutscene != CUTSCENE_RACE_DIALOG) { - sCutsceneDialogResponse = DIALOG_RESPONSE_NOT_DEFINED; - } +void init_camera(struct Camera *c) { + struct Surface *floor = NULL; + Vec3f marioOffset; + s32 i; - gCutsceneTimer = CUTSCENE_LOOP; - retrieve_info_star(c); - transition_next_state(c, 15); - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - cutscene_unsoften_music(c); + init_cutscene_vars(); + gPrevLevel = gCurrLevelArea / 16; + gCurrLevelArea = gCurrLevelNum * 16 + gCurrentArea->index; + sSelectionFlags &= CAM_MODE_MARIO_SELECTED; + sFramesPaused = 0; + gLakituState.mode = c->mode; + gLakituState.defMode = c->defMode; + gLakituState.posHSpeed = 0.3f; + gLakituState.posVSpeed = 0.3f; + gLakituState.focHSpeed = 0.8f; + gLakituState.focVSpeed = 0.3f; + gLakituState.roll = 0; + gLakituState.keyDanceRoll = 0; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + vec3_zero(sCastleEntranceOffset); + find_mario_floor_and_ceil(&sMarioGeometry); + sMarioGeometry.prevFloorHeight = sMarioGeometry.currFloorHeight; + sMarioGeometry.prevCeilHeight = sMarioGeometry.currCeilHeight; + sMarioGeometry.prevFloor = sMarioGeometry.currFloor; + sMarioGeometry.prevCeil = sMarioGeometry.currCeil; + sMarioGeometry.prevFloorType = sMarioGeometry.currFloorType; + sMarioGeometry.prevCeilType = sMarioGeometry.currCeilType; + init_current_credits_spline(); + cutscene_reset_spline(); + sHandheldShakeInc = 0.f; + sHandheldShakeTimer = 0.f; + sHandheldShakeMag = 0; + for (i = 0; i < 4; i++) { + sHandheldShakeSpline[i].index = -1; } -} - -/** - * Sets the CAM_FLAG_UNUSED_CUTSCENE_ACTIVE flag, which does nothing. - */ -void cutscene_dialog_set_flag(UNUSED struct Camera *c) { - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; -} - -/** - * Ends the dialog cutscene. - */ -void cutscene_dialog_end(struct Camera *c) { - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - c->cutscene = 0; - clear_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); -} - -/** - * Soften the music, clear cvar0 - * - * In this cutscene, cvar0.angle[0] is used as a state variable. - */ -void cutscene_read_message_start(struct Camera *c) { - cutscene_soften_music(c); - transition_next_state(c, 30); - reset_pan_distance(c); - store_info_star(c); - - sCutsceneVars[1].angle[0] = sCUpCameraPitch; - sCutsceneVars[1].angle[1] = sModeOffsetYaw; - sCUpCameraPitch = -0x830; - sModeOffsetYaw = 0; - sCutsceneVars[0].angle[0] = 0; -} - -UNUSED static void unused_cam_to_mario(struct Camera *c) { - Vec3s dir; - - vec3s_set(dir, 0, sMarioCamState->faceAngle[1], 0); - offset_rotated_coords(c->pos, sMarioCamState->pos, dir, 0, 100.f, 190.f); - offset_rotated_coords(c->focus, sMarioCamState->pos, dir, 0, 70.f, -20.f); -} + sHandheldShakePitch = 0; + sHandheldShakeYaw = 0; + sHandheldShakeRoll = 0; + c->cutscene = CUTSCENE_NONE; + marioOffset[0] = 0.f; + marioOffset[1] = 125.f; + marioOffset[2] = 400.f; -/** - * Cutscene that plays when Mario is reading a message (a sign or message on the wall) - */ -void cutscene_read_message(struct Camera *c) { - cutscene_event(cutscene_read_message_start, c, 0, 0); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - - switch (sCutsceneVars[0].angle[0]) { - // Do nothing until message is gone. - case 0: - if (get_dialog_id() != DIALOG_NONE) { - sCutsceneVars[0].angle[0]++; - set_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); + // Set the camera's starting position or start a cutscene for certain levels + switch (gCurrLevelNum) { + // Calls the initial cutscene when you enter Bowser battle levels + // Note: This replaced an "old" way to call these cutscenes using + // a camEvent value: CAM_EVENT_BOWSER_INIT + case LEVEL_BOWSER_1: + // Since Bowser 1 has a demo entry, check for it + // If it is, then set CamAct to the end to directly activate Bowser + // If it isn't, then start cutscene + if (gCurrDemoInput == NULL) { + start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); + } else if (gSecondCameraFocus != NULL) { + gSecondCameraFocus->oBowserCamAct = BOWSER_CAM_ACT_END; } break; - // Leave the dialog. - case 1: - move_mario_head_c_up(c); - update_c_up(c, c->focus, c->pos); - - // This could cause softlocks. If a message starts one frame after another one closes, the - // cutscene will never end. - if (get_dialog_id() == DIALOG_NONE) { - gCutsceneTimer = CUTSCENE_LOOP; - retrieve_info_star(c); - transition_next_state(c, 15); - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - clear_time_stop_flags(TIME_STOP_ENABLED | TIME_STOP_DIALOG); - // Retrieve previous state - sCUpCameraPitch = sCutsceneVars[1].angle[0]; - sModeOffsetYaw = sCutsceneVars[1].angle[1]; - cutscene_unsoften_music(c); - } - } - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; -} - -/** - * Set CAM_FLAG_UNUSED_CUTSCENE_ACTIVE, which does nothing. - */ -void cutscene_read_message_set_flag(UNUSED struct Camera *c) { - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; -} - -/** - * End the message cutscene. - */ -void cutscene_read_message_end(struct Camera *c) { - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - c->cutscene = 0; -} - -/** - * Set cvars: - * cvar7 is Mario's pos and angle - * cvar6 is the focus offset - * cvar5 is the position offset - */ -void cutscene_exit_succ_start(UNUSED struct Camera *c) { - vec3f_copy(sCutsceneVars[7].point, sMarioCamState->pos); - vec3s_copy(sCutsceneVars[7].angle, sMarioCamState->faceAngle); - vec3f_set(sCutsceneVars[6].point, 6.f, 363.f, 543.f); - vec3f_set(sCutsceneVars[5].point, 137.f, 226.f, 995.f); -} - -/** - * Set the camera pos depending on which level Mario exited. - */ -void cutscene_non_painting_set_cam_pos(struct Camera *c) { - struct Surface *floor; + case LEVEL_BOWSER_2: + start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); + break; + case LEVEL_BOWSER_3: + start_cutscene(c, CUTSCENE_ENTER_BOWSER_ARENA); + break; - switch (gPrevLevel) { - case LEVEL_HMC: - vec3f_set(c->pos, 3465.f, -1008.f, -2961.f); +#ifdef ENABLE_VANILLA_CAM_PROCESSING + //! Hardcoded position checks determine which cutscene to play when Mario enters castle grounds. + case LEVEL_CASTLE_GROUNDS: + if (is_within_100_units_of_mario(-1328.f, 260.f, 4664.f) != 1) { + marioOffset[0] = -400.f; + marioOffset[2] = -800.f; + } + if (is_within_100_units_of_mario(-6901.f, 2376.f, -6509.f) == 1) { + start_cutscene(c, CUTSCENE_EXIT_WATERFALL); + } + if (is_within_100_units_of_mario(5408.f, 4500.f, 3637.f) == 1) { + start_cutscene(c, CUTSCENE_EXIT_FALL_WMOTR); + } + gLakituState.mode = CAMERA_MODE_FREE_ROAM; break; - - case LEVEL_COTMC: - vec3f_set(c->pos, 3465.f, -1008.f, -2961.f); + case LEVEL_SA: + marioOffset[2] = 200.f; break; - - case LEVEL_RR: - vec3f_set(c->pos, -3741.f, 3151.f, 6065.f); + case LEVEL_CASTLE_COURTYARD: + marioOffset[2] = -300.f; break; - - case LEVEL_WMOTR: - vec3f_set(c->pos, 1972.f, 3230.f, 5891.f); + case LEVEL_LLL: + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; break; - - default: - offset_rotated(c->pos, sCutsceneVars[7].point, sCutsceneVars[5].point, sCutsceneVars[7].angle); - c->pos[1] = find_floor(c->pos[0], c->pos[1] + 1000.f, c->pos[2], &floor) + 125.f; + case LEVEL_CASTLE: + marioOffset[2] = 150.f; + break; + case LEVEL_RR: + vec3f_set(sFixedModeBasePosition, -2985.f, 478.f, -5568.f); break; +#endif } -} - -/** - * Update the camera focus depending on which level Mario exited. - */ -void cutscene_non_painting_set_cam_focus(struct Camera *c) { - offset_rotated(c->focus, sCutsceneVars[7].point, sCutsceneVars[6].point, sCutsceneVars[7].angle); - - if ((gPrevLevel == LEVEL_COTMC) || (gPrevLevel == LEVEL_HMC) || (gPrevLevel == LEVEL_RR) - || (gPrevLevel == LEVEL_WMOTR)) { - c->focus[0] = c->pos[0] + (sMarioCamState->pos[0] - c->pos[0]) * 0.7f; - c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] - c->pos[1]) * 0.4f; - c->focus[2] = c->pos[2] + (sMarioCamState->pos[2] - c->pos[2]) * 0.7f; - } else { - c->focus[1] = c->pos[1] + (sMarioCamState->pos[1] - c->pos[1]) * 0.2f; + if (c->mode == CAMERA_MODE_8_DIRECTIONS) { + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; } -} - -/** - * Focus slightly left of Mario. Perhaps to keep the bowser painting in view? - */ -void cutscene_exit_bowser_succ_focus_left(UNUSED struct Camera *c) { - approach_f32_asymptotic_bool(&sCutsceneVars[6].point[0], -24.f, 0.05f); -} - -/** - * Instead of focusing on the key, just start a pitch shake. Clever! - * The shake lasts 32 frames. - */ -void cutscene_exit_bowser_key_toss_shake(struct Camera *c) { - //! Unnecessary check. - if (c->cutscene == CUTSCENE_EXIT_BOWSER_SUCC) { - set_camera_pitch_shake(0x800, 0x40, 0x800); + switch (gCurrLevelArea) { +#ifdef ENABLE_VANILLA_CAM_PROCESSING + case AREA_SSL_EYEROK: + vec3f_set(marioOffset, 0.f, 500.f, -100.f); + break; + case AREA_CCM_SLIDE: + marioOffset[2] = -300.f; + break; + case AREA_THI_WIGGLER: + marioOffset[2] = -300.f; + break; + case AREA_SL_IGLOO: + marioOffset[2] = -300.f; + break; + case AREA_SL_OUTSIDE: + if (is_within_100_units_of_mario(257.f, 2150.f, 1399.f) == 1) { + marioOffset[2] = -300.f; + } + break; + case AREA_CCM_OUTSIDE: + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; + break; + case AREA_TTM_OUTSIDE: + gLakituState.mode = CAMERA_MODE_RADIAL; + break; +#endif } -} - -/** - * Start a camera shake when Mario lands on the ground. - */ -void cutscene_exit_succ_shake_landing(UNUSED struct Camera *c) { - set_environmental_camera_shake(SHAKE_ENV_EXPLOSION); -} - -/** - * Cutscene that plays when Mario beats bowser and exits the level. - */ -void cutscene_exit_bowser_succ(struct Camera *c) { - cutscene_event(cutscene_exit_succ_start, c, 0, 0); - cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); - cutscene_event(cutscene_exit_bowser_succ_focus_left, c, 18, -1); - cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); - cutscene_event(cutscene_exit_bowser_key_toss_shake, c, 125, 125); - cutscene_event(cutscene_exit_succ_shake_landing, c, 41, 41); -} - -/** - * End a non-painting exit cutscene. Used by BBH and bowser courses. - */ -void cutscene_non_painting_end(struct Camera *c) { - c->cutscene = 0; -#ifdef USE_COURSE_DEFAULT_MODE - c->mode = c->defMode; -#else - if (c->defMode == CAMERA_MODE_CLOSE) { - c->mode = CAMERA_MODE_CLOSE; - } else { - c->mode = CAMERA_MODE_FREE_ROAM; + // Set the camera pos to marioOffset (relative to Mario), added to Mario's position + offset_rotated(c->pos, sMarioCamState->pos, marioOffset, sMarioCamState->faceAngle); + if (c->mode != CAMERA_MODE_BEHIND_MARIO) { + c->pos[1] = find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 100.f, + sMarioCamState->pos[2], &floor) + 125.f; + } + vec3f_copy(c->focus, sMarioCamState->pos); + vec3f_copy(gLakituState.curPos, c->pos); + vec3f_copy(gLakituState.curFocus, c->focus); + vec3f_copy(gLakituState.goalPos, c->pos); + vec3f_copy(gLakituState.goalFocus, c->focus); + vec3f_copy(gLakituState.pos, c->pos); + vec3f_copy(gLakituState.focus, c->focus); + if (c->mode == CAMERA_MODE_FIXED) { + set_fixed_cam_axis_sa_lobby(c->mode); } + store_lakitu_cam_info_for_c_up(c); + gLakituState.yaw = calculate_yaw(c->focus, c->pos); + gLakituState.nextYaw = gLakituState.yaw; + c->yaw = gLakituState.yaw; + c->nextYaw = gLakituState.yaw; +#ifdef PUPPYCAM + puppycam_init(); #endif - - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - transition_next_state(c, 60); - update_camera_yaw(c); -} - -/** - * Override the position offset. - */ -void cutscene_exit_non_painting_succ_override_cvar(UNUSED struct Camera *c) { - vec3f_set(sCutsceneVars[5].point, 137.f, 246.f, 1115.f); -} - -/** - * Cutscene that plays when Mario collects a star and leaves a non-painting course, like HMC or BBH. - */ -void cutscene_exit_non_painting_succ(struct Camera *c) { - cutscene_event(cutscene_exit_succ_start, c, 0, 0); - cutscene_event(cutscene_exit_non_painting_succ_override_cvar, c, 0, 0); - cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); - cutscene_event(cutscene_exit_bowser_succ_focus_left, c, 18, -1); - cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); - cutscene_event(cutscene_exit_succ_shake_landing, c, 41, 41); - update_camera_yaw(c); } -/** - * Set cvar7 to Mario's pos and faceAngle - * Set cvar6 to the focus offset from Mario. - * set cvar5 to the pos offset from Mario. (This is always overwritten) - */ -void cutscene_non_painting_death_start(UNUSED struct Camera *c) { - vec3f_copy(sCutsceneVars[7].point, sMarioCamState->pos); - vec3s_copy(sCutsceneVars[7].angle, sMarioCamState->faceAngle); - vec3f_set(sCutsceneVars[6].point, -42.f, 350.f, 727.f); - // This is always overwritten, except in the unused cutscene_exit_bowser_death() - vec3f_set(sCutsceneVars[5].point, 107.f, 226.f, 1187.f); +void select_mario_cam_mode(void) { + sSelectionFlags = CAM_MODE_MARIO_SELECTED; } - /** - * This cutscene is the same as non_painting_death, but the camera is closer to Mario and lower. - * Because it it doesn't call cutscene_non_painting_death_override_offset, the value from - * cutscene_non_painting_death_start is used. + * If `selection` is 0, just get the current selection + * If `selection` is 1, select 'Mario' as the alt mode. + * If `selection` is 2, select 'fixed' as the alt mode. * - * This cutscene is unused, dying in bowser's arena spawns Mario near the warp pipe, not back in the - * hub. + * @return the current selection */ -void cutscene_exit_bowser_death(struct Camera *c) { - cutscene_event(cutscene_non_painting_death_start, c, 0, 0); - cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); - cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); -} +s32 cam_select_alt_mode(s32 selection) { + s32 mode = CAM_SELECTION_FIXED; -/** - * Set the offset from Mario depending on the course Mario exited. - * This overrides cutscene_non_painting_death_start() - */ -void cutscene_non_painting_death_override_offset(UNUSED struct Camera *c) { - switch (gPrevLevel) { - case LEVEL_HMC: - vec3f_set(sCutsceneVars[5].point, 187.f, 369.f, -197.f); - break; - case LEVEL_COTMC: - vec3f_set(sCutsceneVars[5].point, 187.f, 369.f, -197.f); - break; - default: - vec3f_set(sCutsceneVars[5].point, 107.f, 246.f, 1307.f); - break; + if (selection == CAM_SELECTION_MARIO) { + if (!(sSelectionFlags & CAM_MODE_MARIO_SELECTED)) { + sSelectionFlags |= CAM_MODE_MARIO_SELECTED; + } + sCameraSoundFlags |= CAM_SOUND_UNUSED_SELECT_MARIO; } -} - -/** - * Cutscene played when Mario dies in a non-painting course, like HMC or BBH. - */ -void cutscene_non_painting_death(struct Camera *c) { - cutscene_event(cutscene_non_painting_death_start, c, 0, 0); - cutscene_event(cutscene_non_painting_death_override_offset, c, 0, 0); - cutscene_event(cutscene_non_painting_set_cam_pos, c, 0, -1); - cutscene_event(cutscene_non_painting_set_cam_focus, c, 0, -1); - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; -} - -/** - * Set cvars: - * cvar3 is an offset applied to the camera's rotation around Mario. It starts at 0x1200 - * cvar 1 is more complicated: - * First the yaw from Mario to the camera is calculated. cvar1 is the high byte of the difference - * between that yaw and Mario's faceAngle plus 0x1200. The reason for taking the high byte is - * because cvar1 rotates until is reaches 0, so it's important that it's a multiple of 0x100. - */ -void cutscene_cap_switch_press_start(struct Camera *c) { - store_info_star(c); - s16 yaw = calculate_yaw(sMarioCamState->pos, c->pos); - sCutsceneVars[3].angle[1] = 0x1200; - // Basically the amount of rotation to get from behind Mario to in front of Mario - sCutsceneVars[1].angle[1] = (yaw - (sMarioCamState->faceAngle[1] + sCutsceneVars[3].angle[1])) & 0xFF00; -} - -/** - * Rotate around Mario. As each cvar stops updating, the rotation slows until the camera ends up in - * front of Mario. - */ -void cutscene_cap_switch_press_rotate_around_mario(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - - // cvar3 wraps around until it reaches 0x1000 - if (sCutsceneVars[3].angle[1] != 0x1000) { - sCutsceneVars[3].angle[1] += 0x100; + // The alternate mode is up-close, but the player just selected fixed in the pause menu + if (selection == CAM_SELECTION_FIXED && (sSelectionFlags & CAM_MODE_MARIO_SELECTED)) { + // So change to normal mode in case the user paused in up-close mode + set_cam_angle(CAM_ANGLE_LAKITU); + sSelectionFlags &= ~CAM_MODE_MARIO_SELECTED; + sCameraSoundFlags |= CAM_SOUND_UNUSED_SELECT_FIXED; } - // cvar1 wraps until 0 - if (sCutsceneVars[1].angle[1] != 0) { - sCutsceneVars[1].angle[1] += 0x100; + if (sSelectionFlags & CAM_MODE_MARIO_SELECTED) { + mode = CAM_SELECTION_MARIO; } - - yaw = sMarioCamState->faceAngle[1] + sCutsceneVars[3].angle[1] + sCutsceneVars[1].angle[1]; - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * Move the camera slightly downwards. - */ -void cutscene_cap_switch_press_lower_cam(struct Camera *c) { - rotate_and_move_vec3f(c->pos, sMarioCamState->pos, 0, -0x20, 0); -} - -/** - * Move the camera closer to Mario. - */ -void cutscene_cap_switch_press_approach_mario(struct Camera *c) { - s16 pitch, yaw; - f32 dist; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - approach_f32_asymptotic_bool(&dist, 195.f, 0.2f); - approach_s16_asymptotic_bool(&pitch, 0, 0x10); - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); - - approach_f32_asymptotic_bool(&c->focus[0], sMarioCamState->pos[0], 0.1f); - approach_f32_asymptotic_bool(&c->focus[1], sMarioCamState->pos[1] + 110.f, 0.1f); - approach_f32_asymptotic_bool(&c->focus[2], sMarioCamState->pos[2], 0.1f); -} - -/** - * Pan the camera left so that Mario is on the right side of the screen when the camera stops spinning. - */ -void cutscene_cap_switch_press_pan_left(struct Camera *c) { - vec3f_copy(c->focus, sMarioCamState->pos); - c->focus[1] += 110.f; - camera_approach_s16_symmetric_bool(&sCutsceneVars[0].angle[1], 0x800, 0x20); - pan_camera(c, sCutsceneVars[0].angle[0], sCutsceneVars[0].angle[1]); -} - -/** - * Create a dialog box with the cap switch's text. - */ -void cutscene_cap_switch_press_create_dialog(UNUSED struct Camera *c) { - create_dialog_box_with_response(gCutsceneFocus->oBehParams2ndByte + DIALOG_010); -} - -static UNUSED void unused_cap_switch_retrieve_info(struct Camera *c) { - retrieve_info_star(c); - transition_next_state(c, 30); + return mode; } /** - * Cutscene that plays when Mario presses a cap switch. + * Sets the camera angle to either Lakitu or Mario mode. Returns the current mode. + * + * If `mode` is 0, just returns the current mode. + * If `mode` is 1, start Mario mode + * If `mode` is 2, start Lakitu mode */ -void cutscene_cap_switch_press(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; - - cutscene_event(cutscene_cap_switch_press_start, c, 0, 0); - cutscene_event(cutscene_cap_switch_press_approach_mario, c, 0, 30); - cutscene_event(cutscene_cap_switch_press_pan_left, c, 0, -1); - cutscene_event(cutscene_cap_switch_press_rotate_around_mario, c, 30, -1); - cutscene_event(cutscene_cap_switch_press_lower_cam, c, 10, 70); - cutscene_event(cutscene_cap_switch_press_create_dialog, c, 10, 10); - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); +s32 set_cam_angle(s32 mode) { + s32 curMode = CAM_ANGLE_LAKITU; - if (gDialogResponse != DIALOG_RESPONSE_NONE) { - sCutsceneVars[4].angle[0] = gDialogResponse; + // Switch to Mario mode + if (mode == CAM_ANGLE_MARIO && !(sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { + sSelectionFlags |= CAM_MODE_MARIO_ACTIVE; + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + sSelectionFlags |= CAM_MODE_LAKITU_WAS_ZOOMED_OUT; + gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; + } + sCameraSoundFlags |= CAM_SOUND_MARIO_ACTIVE; } - if ((get_dialog_id() == DIALOG_NONE) && (sCutsceneVars[4].angle[0] != 0)) { - sCutsceneDialogResponse = sCutsceneVars[4].angle[0]; - if (sCutsceneVars[4].angle[0] == 1) { - cap_switch_save(gCutsceneFocus->oBehParams2ndByte); + // Switch back to normal mode + if (mode == CAM_ANGLE_LAKITU && (sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) { + sSelectionFlags &= ~CAM_MODE_MARIO_ACTIVE; + if (sSelectionFlags & CAM_MODE_LAKITU_WAS_ZOOMED_OUT) { + sSelectionFlags &= ~CAM_MODE_LAKITU_WAS_ZOOMED_OUT; + gCameraMovementFlags |= CAM_MOVE_ZOOMED_OUT; + } else { + gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; } - stop_cutscene_and_retrieve_stored_info(c); - transition_next_state(c, 30); + sCameraSoundFlags |= CAM_SOUND_NORMAL_ACTIVE; } + if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { + curMode = CAM_ANGLE_MARIO; + } + return curMode; } /** - * Sets cvars: - * cvar0 is the camera's position - * cvar1 is the camera's focus - * cvar2 is the goal position - * cvar3 is the goal focus - */ -void cutscene_unlock_key_door_start(struct Camera *c) { - Vec3f posOff, focusOff; - - vec3f_copy(sCutsceneVars[0].point, c->pos); - vec3f_copy(sCutsceneVars[1].point, c->focus); - vec3f_set(posOff, -206.f, 108.f, 234.f); - vec3f_set(focusOff, 48.f, 104.f, -193.f); - offset_rotated(sCutsceneVars[2].point, sMarioCamState->pos, posOff, sMarioCamState->faceAngle); - offset_rotated(sCutsceneVars[3].point, sMarioCamState->pos, focusOff, sMarioCamState->faceAngle); -} - -/** - * Move the camera to the cvars position and focus, closer to Mario. - * Gives a better view of the key. - */ -void cutscene_unlock_key_door_approach_mario(struct Camera *c) { - approach_vec3f_asymptotic(c->pos, sCutsceneVars[2].point, 0.1f, 0.1f, 0.1f); - approach_vec3f_asymptotic(c->focus, sCutsceneVars[3].point, 0.1f, 0.1f, 0.1f); -} - -/** - * Move the camera focus up a bit, focusing on the key in the lock. - */ -void cutscene_unlock_key_door_focus_lock(UNUSED struct Camera *c) { - approach_f32_asymptotic_bool(&sCutsceneVars[3].point[1], sMarioCamState->pos[1] + 140.f, 0.07f); -} - -void cutscene_unlock_key_door_stub(UNUSED struct Camera *c) { -} - -/** - * Move back to the previous pos and focus, stored in cvar0 and cvar1. - */ -void cutscene_unlock_key_door_fly_back(struct Camera *c) { - approach_vec3f_asymptotic(c->pos, sCutsceneVars[0].point, 0.1f, 0.1f, 0.1f); - approach_vec3f_asymptotic(c->focus, sCutsceneVars[1].point, 0.1f, 0.1f, 0.1f); -} - -/** - * Shake the camera's fov when the key is put in the lock. + * Enables the handheld shake effect for this frame. + * + * @see shake_camera_handheld() */ -void cutscene_unlock_key_door_fov_shake(UNUSED struct Camera *c) { - cutscene_set_fov_shake_preset(1); +void set_handheld_shake(u8 mode) { + switch (mode) { + // They're not in numerical order because that would be too simple... + case HAND_CAM_SHAKE_CUTSCENE: // Lowest increment + sHandheldShakeMag = 0x600; + sHandheldShakeInc = 0.04f; + break; + case HAND_CAM_SHAKE_LOW: // Lowest magnitude + sHandheldShakeMag = 0x300; + sHandheldShakeInc = 0.06f; + break; + case HAND_CAM_SHAKE_HIGH: // Highest mag and inc + sHandheldShakeMag = 0x1000; + sHandheldShakeInc = 0.1f; + break; + case HAND_CAM_SHAKE_UNUSED: // Never used + sHandheldShakeMag = 0x600; + sHandheldShakeInc = 0.07f; + break; + case HAND_CAM_SHAKE_HANG_OWL: // exactly the same as UNUSED... + sHandheldShakeMag = 0x600; + sHandheldShakeInc = 0.07f; + break; + case HAND_CAM_SHAKE_STAR_DANCE: // Slightly steadier than HANG_OWL and UNUSED + sHandheldShakeMag = 0x400; + sHandheldShakeInc = 0.07f; + break; + default: + sHandheldShakeMag = 0x0; + sHandheldShakeInc = 0.f; + } } /** - * Cutscene that plays when Mario unlocks a key door. + * When sHandheldShakeMag is nonzero, this function adds small random offsets to `focus` every time + * sHandheldShakeTimer increases above 1.0, simulating the camera shake caused by unsteady hands. + * + * This function must be called every frame in order to actually apply the effect, since the effect's + * mag and inc are set to 0 every frame at the end of this function. */ -void cutscene_unlock_key_door(UNUSED struct Camera *c) { - cutscene_event(cutscene_unlock_key_door_start, c, 0, 0); - cutscene_event(cutscene_unlock_key_door_approach_mario, c, 0, 123); - cutscene_event(cutscene_unlock_key_door_fly_back, c, 124, -1); - cutscene_event(cutscene_unlock_key_door_fov_shake, c, 79, 79); - cutscene_event(cutscene_unlock_key_door_focus_lock, c, 70, 110); - cutscene_event(cutscene_unlock_key_door_stub, c, 112, 112); -} +void shake_camera_handheld(Vec3f pos, Vec3f focus) { + s32 i; + Vec3f shakeOffset; + Vec3f shakeSpline[4]; + f32 dist; + s16 pitch, yaw; -/** - * Move the camera along `positionSpline` and point its focus at the corresponding point along - * `focusSpline`. sCutsceneSplineSegmentProgress is updated after pos and focus are calculated. - */ -s32 intro_peach_move_camera_start_to_pipe(struct Camera *c, struct CutsceneSplinePoint positionSpline[], - struct CutsceneSplinePoint focusSpline[]) { - Vec3f offset; - s32 posReturn = 0; - s32 focusReturn = 0; - - /** - * The position spline's speed parameters are all 0, so sCutsceneSplineSegmentProgress doesn't get - * updated. Otherwise position would move two frames ahead, and c->focus would always be one frame - * further along the spline than c->pos. - */ - posReturn = move_point_along_spline(c->pos, positionSpline, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - focusReturn = move_point_along_spline(c->focus, focusSpline, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - - // The two splines used by this function are reflected in the horizontal plane for some reason, - // so they are rotated every frame. Why do this, Nintendo? - rotate_in_xz(c->focus, c->focus, DEGREES(-180)); - rotate_in_xz(c->pos, c->pos, DEGREES(-180)); - - vec3f_set(offset, -1328.f, 260.f, 4664.f); - vec3f_add(c->focus, offset); - vec3f_add(c->pos, offset); - - posReturn += focusReturn; // Unused - return focusReturn; -} + if (sHandheldShakeMag == 0) { + vec3_zero(shakeOffset); + } else { + for (i = 0; i < 4; i++) { + shakeSpline[i][0] = sHandheldShakeSpline[i].point[0]; + shakeSpline[i][1] = sHandheldShakeSpline[i].point[1]; + shakeSpline[i][2] = sHandheldShakeSpline[i].point[2]; + } + evaluate_cubic_spline(sHandheldShakeTimer, shakeOffset, shakeSpline[0], + shakeSpline[1], shakeSpline[2], shakeSpline[3]); + if (1.f <= (sHandheldShakeTimer += sHandheldShakeInc)) { + // The first 3 control points are always (0,0,0), so the random spline is always just a + // straight line + for (i = 0; i < 3; i++) { + vec3s_copy(sHandheldShakeSpline[i].point, sHandheldShakeSpline[i + 1].point); + } + random_vec3s(sHandheldShakeSpline[3].point, sHandheldShakeMag, sHandheldShakeMag, sHandheldShakeMag / 2); + sHandheldShakeTimer -= 1.f; -/** - * Create a dialog box with the letter text - */ -void peach_letter_text(UNUSED struct Camera *c) { - create_dialog_box(DIALOG_020); -} + // Code dead, this is set to be 0 before it is used. + sHandheldShakeInc = random_float() * 0.5f; + if (sHandheldShakeInc < 0.02f) { + sHandheldShakeInc = 0.02f; + } + } + } -#ifndef VERSION_JP -void play_sound_peach_reading_letter(UNUSED struct Camera *c) { - play_sound(SOUND_PEACH_DEAR_MARIO, gGlobalSoundSource); -} -#endif + approach_s16_asymptotic_bool(&sHandheldShakePitch, shakeOffset[0], 0x08); + approach_s16_asymptotic_bool(&sHandheldShakeYaw, shakeOffset[1], 0x08); + approach_s16_asymptotic_bool(&sHandheldShakeRoll, shakeOffset[2], 0x08); -/** - * Move the camera from peach reading the letter all the way to Mario's warp pipe. Follow the - * sIntroStartToPipe splines. - */ -void cutscene_intro_peach_start_to_pipe_spline(struct Camera *c) { - if (intro_peach_move_camera_start_to_pipe(c, sIntroStartToPipePosition, sIntroStartToPipeFocus) != 0) { - gCameraMovementFlags &= ~CAM_MOVE_C_UP_MODE; - gCutsceneTimer = CUTSCENE_LOOP; + if (sHandheldShakePitch | sHandheldShakeYaw) { + vec3f_get_dist_and_angle(pos, focus, &dist, &pitch, &yaw); + pitch += sHandheldShakePitch; + yaw += sHandheldShakeYaw; + vec3f_set_dist_and_angle(pos, focus, dist, pitch, yaw); } + + // Unless called every frame, the effect will stop after the first time. + sHandheldShakeMag = 0; + sHandheldShakeInc = 0.0f; } /** - * Loop the cutscene until Mario exits the dialog. + * Updates C Button input state and stores it in `currentState` */ -void cutscene_intro_peach_dialog(struct Camera *c) { - if (get_dialog_id() == DIALOG_NONE) { - vec3f_copy(gLakituState.goalPos, c->pos); - vec3f_copy(gLakituState.goalFocus, c->focus); - sStatusFlags |= (CAM_FLAG_SMOOTH_MOVEMENT | CAM_FLAG_UNUSED_CUTSCENE_ACTIVE); - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; +s32 find_c_buttons_pressed(u16 currentState, u16 buttonsPressed, u16 buttonsDown) { + buttonsPressed &= CBUTTON_MASK; + buttonsDown &= CBUTTON_MASK; + + if (buttonsPressed & L_CBUTTONS) { + currentState |= L_CBUTTONS; + currentState &= ~R_CBUTTONS; + } + if (!(buttonsDown & L_CBUTTONS)) { + currentState &= ~L_CBUTTONS; } -} -void cutscene_intro_peach_follow_pipe_spline(struct Camera *c) { - move_point_along_spline(c->pos, sIntroPipeToDialogPosition, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - move_point_along_spline(c->focus, sIntroPipeToDialogFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); -} + if (buttonsPressed & R_CBUTTONS) { + currentState |= R_CBUTTONS; + currentState &= ~L_CBUTTONS; + } + if (!(buttonsDown & R_CBUTTONS)) { + currentState &= ~R_CBUTTONS; + } -void cutscene_intro_peach_clear_cutscene_status(UNUSED struct Camera *c) { - sMarioCamState->cameraEvent = CAM_EVENT_NONE; -} + if (buttonsPressed & U_CBUTTONS) { + currentState |= U_CBUTTONS; + currentState &= ~D_CBUTTONS; + } + if (!(buttonsDown & U_CBUTTONS)) { + currentState &= ~U_CBUTTONS; + } -/** - * Set fov to 8 degrees, then zoom out to 30. - */ -void cutscene_intro_peach_zoom_fov(UNUSED struct Camera *c) { - sFOVState.fov = 8.f; - set_fov_function(CAM_FOV_ZOOM_30); -} + if (buttonsPressed & D_CBUTTONS) { + currentState |= D_CBUTTONS; + currentState &= ~U_CBUTTONS; + } + if (!(buttonsDown & D_CBUTTONS)) { + currentState &= ~D_CBUTTONS; + } -/** - * Reset the spline progress, turn on handheld shake. - */ -void cutscene_intro_peach_reset_spline(UNUSED struct Camera *c) { - sCutsceneSplineSegment = 0; - sCutsceneSplineSegmentProgress = 0.1f; - //! @bug since this event is only called for one frame, this handheld shake is turned off on the - //! next frame. - set_handheld_shake(HAND_CAM_SHAKE_HIGH); + return currentState; } /** - * Turn off handheld shake. This was likely written before handheld shake was changed to turn off every - * frame, as it's the only instance of HAND_CAM_SHAKE_OFF. + * Determine which icon to show on the HUD */ -void cutscene_intro_peach_handheld_shake_off(UNUSED struct Camera *c) { - set_handheld_shake(HAND_CAM_SHAKE_OFF); -} +s32 update_camera_hud_status(struct Camera *c) { + s16 status = CAM_STATUS_NONE; -void intro_pipe_exit_text(UNUSED struct Camera *c) { - create_dialog_box(DIALOG_033); + if (c->cutscene != CUTSCENE_NONE + || ((gPlayer1Controller->buttonDown & R_TRIG) && cam_select_alt_mode(0) == CAM_SELECTION_FIXED)) { + status |= CAM_STATUS_FIXED; + } else if (set_cam_angle(0) == CAM_ANGLE_MARIO) { + status |= CAM_STATUS_MARIO; + } else { + status |= CAM_STATUS_LAKITU; + } + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + status |= CAM_STATUS_C_DOWN; + } + if (gCameraMovementFlags & CAM_MOVE_C_UP_MODE) { + status |= CAM_STATUS_C_UP; + } + set_hud_camera_status(status); + return status; } -void play_sound_intro_turn_on_hud(UNUSED struct Camera *c) { - play_sound_rbutton_changed(); +void play_camera_buzz_if_cdown(void) { + if (gPlayer1Controller->buttonPressed & D_CBUTTONS) { + play_sound_button_change_blocked(); + } } -/** - * Fly to the pipe. Near the end, the camera jumps to Lakitu's position and the hud turns on. - */ -void cutscene_intro_peach_fly_to_pipe(struct Camera *c) { - cutscene_event(play_sound_intro_turn_on_hud, c, 818, 818); - cutscene_spawn_obj(6, 1); - cutscene_event(cutscene_intro_peach_start_flying_music, c, 0, 0); - cutscene_event(cutscene_intro_peach_start_to_pipe_spline, c, 0, -1); - cutscene_event(cutscene_intro_peach_clear_cutscene_status, c, 717, 717); - clamp_pitch(c->pos, c->focus, 0x3B00, -0x3B00); - sCutsceneVars[1].point[1] = 400.f; +void play_camera_buzz_if_cbutton(void) { + if (gPlayer1Controller->buttonPressed & CBUTTON_MASK) { + play_sound_button_change_blocked(); + } } -/** - * Lakitu flies around the warp pipe, then Mario jumps out. - */ -void cutscene_intro_peach_mario_appears(struct Camera *c) { - sMarioCamState->cameraEvent = 0; - cutscene_event(cutscene_intro_peach_reset_spline, c, 0, 0); - cutscene_event(cutscene_intro_peach_follow_pipe_spline, c, 0, -1); - cutscene_event(cutscene_intro_peach_handheld_shake_off, c, 70, 70); - cutscene_event(intro_pipe_exit_text, c, 250, 250); - - approach_f32_asymptotic_bool(&sCutsceneVars[1].point[1], 80.f + sMarioGeometry.currFloorHeight + - (sMarioCamState->pos[1] - sMarioGeometry.currFloorHeight) * 1.1f, 0.4f); - - // Make the camera look up as Mario jumps out of the pipe - if (c->focus[1] < sCutsceneVars[1].point[1]) { - c->focus[1] = sCutsceneVars[1].point[1]; +void play_camera_buzz_if_c_sideways(void) { + if (gPlayer1Controller->buttonPressed & (L_CBUTTONS | R_CBUTTONS)) { + play_sound_button_change_blocked(); } - - sStatusFlags |= CAM_FLAG_UNUSED_CUTSCENE_ACTIVE; } -/** - * Reset the fov. This gives the effect of peach zooming out as she fades. - */ -void cutscene_intro_peach_reset_fov(UNUSED struct Camera *c) { - set_fov_function(CAM_FOV_DEFAULT); +void play_sound_cbutton_up(void) { + play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gGlobalSoundSource); } -/** - * Peach reads the letter to Mario. - */ -void cutscene_intro_peach_letter(struct Camera *c) { - cutscene_spawn_obj(CUTSCENE_OBJ_BEGINNING_PEACH, 0); - cutscene_event(cutscene_intro_peach_zoom_fov, c, 0, 0); - cutscene_event(cutscene_intro_peach_start_letter_music, c, 65, 65); - cutscene_event(cutscene_intro_peach_start_to_pipe_spline, c, 0, 0); - cutscene_event(peach_letter_text, c, 65, 65); -#ifndef VERSION_JP - cutscene_event(play_sound_peach_reading_letter, c, 83, 83); -#endif - - if ((gCutsceneTimer > 120) && (get_dialog_id() == DIALOG_NONE)) { - // Start the next scene - gCutsceneTimer = CUTSCENE_LOOP; - } - - clamp_pitch(c->pos, c->focus, 0x3B00, -0x3B00); +void play_sound_cbutton_down(void) { + play_sound(SOUND_MENU_CAMERA_ZOOM_OUT, gGlobalSoundSource); } -/** - * Reset the spline progress. - */ -void cutscene_end_waving_start(UNUSED struct Camera *c) { - cutscene_reset_spline(); +void play_sound_cbutton_side(void) { + play_sound(SOUND_MENU_CAMERA_TURN, gGlobalSoundSource); } -// 3rd part of data -struct CutsceneSplinePoint gIntroLakituStartToPipeFocus[] = { - { 0, 32, { 58, -250, 346 } }, { 1, 50, { -159, -382, 224 } }, { 2, 37, { 0, -277, 237 } }, - { 3, 15, { 1, -44, 245 } }, { 4, 35, { 0, -89, 228 } }, { 5, 15, { 28, 3, 259 } }, - { 6, 25, { -38, -201, 371 } }, { 7, 20, { -642, 118, 652 } }, { 8, 25, { 103, -90, 861 } }, - { 9, 25, { 294, 145, 579 } }, { 10, 30, { 220, -42, 500 } }, { 11, 20, { 10, -134, 200 } }, - { 12, 20, { -143, -145, 351 } }, { 13, 14, { -256, -65, 528 } }, { 14, 20, { -251, -52, 459 } }, - { 15, 25, { -382, 520, 395 } }, { 16, 25, { -341, 240, 653 } }, { 17, 5, { -262, 700, 143 } }, - { 18, 15, { -760, 32, 27 } }, { 19, 20, { -756, -6, -26 } }, { 20, 20, { -613, 5, 424 } }, - { 21, 20, { -22, -100, 312 } }, { 22, 25, { 212, 80, 61 } }, { 23, 20, { 230, -28, 230 } }, - { 24, 35, { -83, -51, 303 } }, { 25, 17, { 126, 90, 640 } }, { 26, 9, { 158, 95, 763 } }, - { 27, 8, { 113, -25, 1033 } }, { 28, 20, { 57, -53, 1291 } }, { 29, 15, { 73, -34, 1350 } }, - { 30, 7, { 0, 96, 1400 } }, { 31, 8, { -59, 269, 1450 } }, { 32, 15, { 57, 1705, 1500 } }, - { 0, 15, { -227, 511, 1550 } }, { -1, 15, { -227, 511, 1600 } } -}; - -struct CutsceneSplinePoint gIntroLakituStartToPipeOffsetFromCamera[] = { - { 0, 0, { -46, 87, -15 } }, { 1, 0, { -38, 91, -11 } }, { 2, 0, { -31, 93, -13 } }, - { 3, 0, { -50, 84, -16 } }, { 4, 0, { -52, 83, -17 } }, { 5, 0, { -10, 99, 3 } }, - { 6, 0, { -54, 83, -10 } }, { 7, 0, { -31, 85, -40 } }, { 8, 0, { -34, 91, 19 } }, - { 9, 0, { -9, 95, 28 } }, { 10, 0, { 17, 72, 66 } }, { 11, 0, { 88, -7, 45 } }, - { 12, 0, { 96, -6, -26 } }, { 13, 0, { 56, -1, -82 } }, { 14, 0, { 40, 65, -63 } }, - { 15, 0, { -26, -3, -96 } }, { 16, 0, { 92, 82, 19 } }, { 17, 0, { 92, 32, 19 } }, - { 18, 0, { 92, 32, 19 } }, { 19, 0, { 92, 102, 19 } }, { 20, 0, { -69, 59, -70 } }, - { 21, 0, { -77, 109, -61 } }, { 22, 0, { -87, 59, -46 } }, { 23, 0, { -99, -3, 11 } }, - { 24, 0, { -99, -11, 5 } }, { 25, 0, { -97, -6, 19 } }, { 26, 0, { -97, 22, -7 } }, - { 27, 0, { -98, -11, -13 } }, { 28, 0, { -97, -11, 19 } }, { 29, 0, { -91, -11, 38 } }, - { 30, 0, { -76, -11, 63 } }, { 31, 0, { -13, 33, 93 } }, { 32, 0, { 51, -11, 84 } }, - { 33, 0, { 51, -11, 84 } }, { -1, 0, { 51, -11, 84 } } -}; - -struct CutsceneSplinePoint gEndWavingPos[] = { - { 0, 0, { -5, 975, -917 } }, { 0, 0, { -5, 975, -917 } }, { 0, 0, { -5, 975, -917 } }, - { 0, 0, { -76, 1067, 742 } }, { 0, 0, { -105, 1576, 3240 } }, { 0, 0, { -177, 1709, 5586 } }, - { 0, 0, { -177, 1709, 5586 } }, { 0, 0, { -177, 1709, 5586 } }, { 0, 0, { -177, 1709, 5586 } } -}; - -struct CutsceneSplinePoint gEndWavingFocus[] = { - { 0, 50, { 18, 1013, -1415 } }, { 0, 100, { 17, 1037, -1412 } }, { 0, 100, { 16, 1061, -1408 } }, - { 0, 100, { -54, 1053, 243 } }, { 0, 100, { -84, 1575, 2740 } }, { 0, 50, { -156, 1718, 5086 } }, - { 0, 0, { -156, 1718, 5086 } }, { 0, 0, { -156, 1718, 5086 } }, { 0, 0, { -156, 1718, 5086 } } -}; - -void cutscene_end_waving(struct Camera *c) { - cutscene_event(cutscene_end_waving_start, c, 0, 0); - move_point_along_spline(c->pos, gEndWavingPos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - move_point_along_spline(c->focus, gEndWavingFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - cutscene_spawn_obj(CUTSCENE_OBJ_BEGINNING_LAKITU, 120); +void play_sound_button_change_blocked(void) { + play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource); } -/** - * Called on the first frame of the credits. Resets the spline progress. - */ -void cutscene_credits_reset_spline(UNUSED struct Camera *c) { - cutscene_reset_spline(); +void play_sound_rbutton_changed(void) { + play_sound(SOUND_MENU_CLICK_CHANGE_VIEW, gGlobalSoundSource); } -extern struct CutsceneSplinePoint sBobCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sBobCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sWfCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sWfCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sJrbCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sJrbCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sCcmSlideCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sCcmSlideCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sBbhCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sBbhCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sHmcCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sHmcCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sThiWigglerCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sThiWigglerCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sVolcanoCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sVolcanoCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sSslCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sSslCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sDddCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sDddCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sSlCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sSlCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sWdwCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sWdwCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sTtmCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sTtmCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sThiHugeCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sThiHugeCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sTtcCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sTtcCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sRrCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sRrCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sSaCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sSaCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sCotmcCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sCotmcCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sDddSubCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sDddSubCreditsSplineFocus[]; -extern struct CutsceneSplinePoint sCcmOutsideCreditsSplinePositions[]; -extern struct CutsceneSplinePoint sCcmOutsideCreditsSplineFocus[]; - -/** - * Follow splines through the courses of the game. - */ -void cutscene_credits(struct Camera *c) { - struct CutsceneSplinePoint *focus, *pos; - - cutscene_event(cutscene_credits_reset_spline, c, 0, 0); - - switch (gCurrLevelArea) { - case AREA_BOB: - pos = sBobCreditsSplinePositions; - focus = sBobCreditsSplineFocus; - break; - case AREA_WF: - pos = sWfCreditsSplinePositions; - focus = sWfCreditsSplineFocus; - break; - case AREA_JRB_MAIN: - pos = sJrbCreditsSplinePositions; - focus = sJrbCreditsSplineFocus; - break; - case AREA_CCM_SLIDE: - pos = sCcmSlideCreditsSplinePositions; - focus = sCcmSlideCreditsSplineFocus; - break; - case AREA_BBH: - pos = sBbhCreditsSplinePositions; - focus = sBbhCreditsSplineFocus; - break; - case AREA_HMC: - pos = sHmcCreditsSplinePositions; - focus = sHmcCreditsSplineFocus; - break; - case AREA_THI_WIGGLER: - pos = sThiWigglerCreditsSplinePositions; - focus = sThiWigglerCreditsSplineFocus; - break; - case AREA_LLL_VOLCANO: - pos = sVolcanoCreditsSplinePositions; - focus = sVolcanoCreditsSplineFocus; - break; - case AREA_SSL_OUTSIDE: - pos = sSslCreditsSplinePositions; - focus = sSslCreditsSplineFocus; - break; - case AREA_DDD_WHIRLPOOL: - pos = sDddCreditsSplinePositions; - focus = sDddCreditsSplineFocus; - break; - case AREA_SL_OUTSIDE: - pos = sSlCreditsSplinePositions; - focus = sSlCreditsSplineFocus; - break; - case AREA_WDW_MAIN: - pos = sWdwCreditsSplinePositions; - focus = sWdwCreditsSplineFocus; - break; - case AREA_TTM_OUTSIDE: - pos = sTtmCreditsSplinePositions; - focus = sTtmCreditsSplineFocus; - break; - case AREA_THI_HUGE: - pos = sThiHugeCreditsSplinePositions; - focus = sThiHugeCreditsSplineFocus; - break; - case AREA_TTC: - pos = sTtcCreditsSplinePositions; - focus = sTtcCreditsSplineFocus; - break; - case AREA_RR: - pos = sRrCreditsSplinePositions; - focus = sRrCreditsSplineFocus; - break; - case AREA_SA: - pos = sSaCreditsSplinePositions; - focus = sSaCreditsSplineFocus; - break; - case AREA_COTMC: - pos = sCotmcCreditsSplinePositions; - focus = sCotmcCreditsSplineFocus; - break; - case AREA_DDD_SUB: - pos = sDddSubCreditsSplinePositions; - focus = sDddSubCreditsSplineFocus; - break; - case AREA_CCM_OUTSIDE: - //! Checks if the "Snowman's Lost His Head" star was collected. The credits likely would - //! have avoided the snowman if the player didn't collect that star, but in the end the - //! developers decided against it. - if (save_file_get_star_flags(gCurrSaveFileNum - 1, COURSE_NUM_TO_INDEX(gCurrCourseNum)) & (1 << 4)) { - pos = sCcmOutsideCreditsSplinePositions; - focus = sCcmOutsideCreditsSplineFocus; - } else { - pos = sCcmOutsideCreditsSplinePositions; - focus = sCcmOutsideCreditsSplineFocus; - } - break; - default: - pos = sCcmOutsideCreditsSplinePositions; - focus = sCcmOutsideCreditsSplineFocus; +void play_sound_if_cam_switched_to_lakitu_or_mario(void) { + if (sCameraSoundFlags & CAM_SOUND_MARIO_ACTIVE) { + play_sound_rbutton_changed(); } - - copy_spline_segment(sCurCreditsSplinePos, pos); - copy_spline_segment(sCurCreditsSplineFocus, focus); - move_point_along_spline(c->pos, sCurCreditsSplinePos, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - move_point_along_spline(c->focus, sCurCreditsSplineFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); - player2_rotate_cam(c, -0x2000, 0x2000, -0x4000, 0x4000); -} - -/** - * Set the camera pos relative to Mario. - */ -void cutscene_sliding_doors_open_start(struct Camera *c) { - f32 dist; - s16 pitch, yaw; - - vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw); - - // If the camera is too close, warp it backwards set it to a better angle. - if (dist < 500.f) { - dist = 500.f; - yaw = sMarioCamState->faceAngle[1] + 0x8800; - pitch = 0x800; + if (sCameraSoundFlags & CAM_SOUND_NORMAL_ACTIVE) { + play_sound_rbutton_changed(); } - - vec3f_set_dist_and_angle(sMarioCamState->pos, c->pos, dist, pitch, yaw); -} - -/** - * cvar1: Mario's position - * cvar0.angle: Mario's angle - * cvar0.point: offset from Mario - */ -void cutscene_sliding_doors_open_set_cvars(UNUSED struct Camera *c) { - vec3f_copy(sCutsceneVars[1].point, sMarioCamState->pos); - vec3s_copy(sCutsceneVars[0].angle, sMarioCamState->faceAngle); - vec3f_set(sCutsceneVars[0].point, 80.f, 325.f, 200.f); + sCameraSoundFlags &= ~(CAM_SOUND_MARIO_ACTIVE | CAM_SOUND_NORMAL_ACTIVE); } /** - * Decrease the cvar0 y offset to 75, which would simulate Lakitu flying under the doorway. - * However, the initial y offset is too high for Lakitu to reach 75 in time. + * Moves the camera when Mario has triggered a warp */ -void cutscene_sliding_doors_go_under_doorway(UNUSED struct Camera *c) { - camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[1], 75.f, 10.f); -} +void warp_camera(f32 displacementX, f32 displacementY, f32 displacementZ) { + Vec3f displacement; + struct MarioState *marioStates = &gMarioStates[0]; + struct LinearTransitionPoint *start = &sModeInfo.transitionStart; + struct LinearTransitionPoint *end = &sModeInfo.transitionEnd; -/** - * Approach a y offset of 125 again. - */ -void cutscene_sliding_doors_fly_back_up(UNUSED struct Camera *c) { - camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[1], 125.f, 10.f); -} + gCurrLevelArea = gCurrLevelNum * 16 + gCurrentArea->index; + displacement[0] = displacementX; + displacement[1] = displacementY; + displacement[2] = displacementZ; + vec3f_add(gLakituState.curPos, displacement); + vec3f_add(gLakituState.curFocus, displacement); + vec3f_add(gLakituState.goalPos, displacement); + vec3f_add(gLakituState.goalFocus, displacement); + marioStates->waterLevel += displacementY; -/** - * Follow Mario through the door, by approaching cvar1.point. - */ -void cutscene_sliding_doors_follow_mario(struct Camera *c) { - Vec3f pos; - vec3f_copy(pos, c->pos); - - // Update cvar1 with Mario's position (the y value doesn't change) - sCutsceneVars[1].point[0] = sMarioCamState->pos[0]; - sCutsceneVars[1].point[2] = sMarioCamState->pos[2]; - - // Decrease cvar0's offsets, moving the camera behind Mario at his eye height. - approach_f32_asymptotic_bool(&sCutsceneVars[0].point[0], 0, 0.1f); - camera_approach_f32_symmetric_bool(&sCutsceneVars[0].point[2], 125.f, 50.f); - // Update cvar0's angle - approach_vec3s_asymptotic(sCutsceneVars[0].angle, sMarioCamState->faceAngle, 16, 16, 16); - - // Apply the offset to the camera's position - offset_rotated(pos, sCutsceneVars[1].point, sCutsceneVars[0].point, sCutsceneVars[0].angle); - approach_vec3f_asymptotic(c->pos, pos, 0.15f, 0.05f, 0.15f); - - // Focus on Mario's eye height - set_focus_rel_mario(c, 0, 125.f, 0, 0); + vec3f_add(start->focus, displacement); + vec3f_add(start->pos, displacement); + vec3f_add(end->focus, displacement); + vec3f_add(end->pos, displacement); } /** - * Plays when Mario opens the sliding doors. - * Note: the star door unlocking event is not a cutscene, it's handled by Mario separately. + * Make the camera's y coordinate approach `goal`, + * unless smooth movement is off, in which case the y coordinate is simply set to `goal` */ -void cutscene_sliding_doors_open(struct Camera *c) { - reset_pan_distance(c); - cutscene_event(cutscene_sliding_doors_open_start, c, 0, 8); - cutscene_event(cutscene_sliding_doors_open_set_cvars, c, 8, 8); - cutscene_event(cutscene_sliding_doors_go_under_doorway, c, 8, 28); - cutscene_event(cutscene_sliding_doors_fly_back_up, c, 29, -1); - cutscene_event(cutscene_sliding_doors_follow_mario, c, 8, -1); +void approach_camera_height(struct Camera *c, f32 goal, f32 inc) { + if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) { + if (c->pos[1] < goal) { + if ((c->pos[1] += inc) > goal) { + c->pos[1] = goal; + } + } else { + if ((c->pos[1] -= inc) < goal) { + c->pos[1] = goal; + } + } + } else { + c->pos[1] = goal; + } } /** - * Ends the double door cutscene. + * Set the camera's position to Mario's position, and add several relative offsets. Unused. + * + * @param leftRight offset to Mario's left/right, relative to his faceAngle + * @param yOff y offset + * @param forwBack offset to Mario's front/back, relative to his faceAngle + * @param yawOff offset to Mario's faceAngle, changes the direction of `leftRight` and `forwBack` */ -void cutscene_double_doors_end(struct Camera *c) { - set_flag_post_door(c); - c->cutscene = 0; - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; -} +UNUSED static void unused_set_pos_rel_mario(struct Camera *c, f32 leftRight, f32 yOff, f32 forwBack, s16 yawOff) { + u16 yaw = sMarioCamState->faceAngle[1] + yawOff; -void cutscene_enter_painting_stub(UNUSED struct Camera *c) { + c->pos[0] = sMarioCamState->pos[0] + forwBack * sins(yaw) + leftRight * coss(yaw); + c->pos[1] = sMarioCamState->pos[1] + yOff; + c->pos[2] = sMarioCamState->pos[2] + forwBack * coss(yaw) - leftRight * sins(yaw); } /** - * Plays when Mario enters a painting. The camera flies up to the painting's center, then it slowly - * zooms in until the star select screen appears. + * Calculate Lakitu's next position and focus, according to gCamera's state, + * and store them in `newPos` and `newFoc`. + * + * @param newPos where Lakitu should fly towards this frame + * @param newFoc where Lakitu should look towards this frame + * + * @param curPos gCamera's pos this frame + * @param curFoc gCamera's foc this frame + * + * @param oldPos gCamera's pos last frame + * @param oldFoc gCamera's foc last frame + * + * @return Lakitu's next yaw, which is the same as the yaw passed in if no transition happened */ -void cutscene_enter_painting(struct Camera *c) { - struct Surface *floor, *highFloor; - Vec3f paintingPos, focus, focusOffset; - Vec3s paintingAngle; +s16 next_lakitu_state(Vec3f newPos, Vec3f newFoc, Vec3f curPos, Vec3f curFoc, + Vec3f oldPos, Vec3f oldFoc, s16 yaw) { + s16 yawVelocity; + s16 pitchVelocity; + f32 distVelocity; + f32 goalDist; + s16 goalPitch; + s16 goalYaw; + f32 distTimer = sModeTransition.framesLeft; + s16 angleTimer = sModeTransition.framesLeft; + Vec3f nextPos; + Vec3f nextFoc; + Vec3f startPos; + Vec3f startFoc; + s32 i; f32 floorHeight; + struct Surface *floor; + + // If not transitioning, just use gCamera's current pos and foc + vec3f_copy(newPos, curPos); + vec3f_copy(newFoc, curFoc); - cutscene_event(cutscene_enter_painting_stub, c, 0, 0); - // Zoom in - set_fov_function(CAM_FOV_APP_20); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + if (sStatusFlags & CAM_FLAG_START_TRANSITION) { + for (i = 0; i < 3; i++) { + // Add Mario's displacement from this frame to the last frame's pos and focus + // Makes the transition start from where the camera would have moved + startPos[i] = oldPos[i] + sMarioCamState->pos[i] - sModeTransition.marioPos[i]; + startFoc[i] = oldFoc[i] + sMarioCamState->pos[i] - sModeTransition.marioPos[i]; + } - if (gRipplingPainting != NULL) { - paintingAngle[0] = 0; - paintingAngle[1] = (s32)((gRipplingPainting->yaw / 360.f) * 65536.f); // convert degrees to IAU - paintingAngle[2] = 0; - focusOffset[0] = gRipplingPainting->size / 2; - focusOffset[1] = focusOffset[0]; - focusOffset[2] = 0; + vec3f_get_dist_and_angle(curFoc, startFoc, &sModeTransition.focDist, &sModeTransition.focPitch, + &sModeTransition.focYaw); + vec3f_get_dist_and_angle(curFoc, startPos, &sModeTransition.posDist, &sModeTransition.posPitch, + &sModeTransition.posYaw); + sStatusFlags &= ~CAM_FLAG_START_TRANSITION; + } - paintingPos[0] = gRipplingPainting->posX; - paintingPos[1] = gRipplingPainting->posY; - paintingPos[2] = gRipplingPainting->posZ; + // Transition from the last mode to the current one + if (sModeTransition.framesLeft > 0) { + vec3f_get_dist_and_angle(curFoc, curPos, &goalDist, &goalPitch, &goalYaw); + distVelocity = abss(goalDist - sModeTransition.posDist) / distTimer; + pitchVelocity = abss(goalPitch - sModeTransition.posPitch) / angleTimer; + yawVelocity = abss(goalYaw - sModeTransition.posYaw) / angleTimer; - offset_rotated(focus, paintingPos, focusOffset, paintingAngle); - approach_vec3f_asymptotic(c->focus, focus, 0.1f, 0.1f, 0.1f); - focusOffset[2] = -(((gRipplingPainting->size * 1000.f) / 2) / 307.f); - offset_rotated(focus, paintingPos, focusOffset, paintingAngle); - floorHeight = find_floor(focus[0], focus[1] + 500.f, focus[2], &highFloor) + 125.f; + camera_approach_f32_symmetric_bool(&sModeTransition.posDist, goalDist, distVelocity); + camera_approach_s16_symmetric_bool(&sModeTransition.posYaw, goalYaw, yawVelocity); + camera_approach_s16_symmetric_bool(&sModeTransition.posPitch, goalPitch, pitchVelocity); + vec3f_set_dist_and_angle(curFoc, nextPos, sModeTransition.posDist, sModeTransition.posPitch, + sModeTransition.posYaw); - if (focus[1] < floorHeight) { - focus[1] = floorHeight; - } + vec3f_get_dist_and_angle(curPos, curFoc, &goalDist, &goalPitch, &goalYaw); + pitchVelocity = sModeTransition.focPitch / (s16) sModeTransition.framesLeft; + yawVelocity = sModeTransition.focYaw / (s16) sModeTransition.framesLeft; + distVelocity = sModeTransition.focDist / sModeTransition.framesLeft; - if (c->cutscene == CUTSCENE_ENTER_PAINTING) { - approach_vec3f_asymptotic(c->pos, focus, 0.2f, 0.1f, 0.2f); - } else { - approach_vec3f_asymptotic(c->pos, focus, 0.9f, 0.9f, 0.9f); - } + camera_approach_s16_symmetric_bool(&sModeTransition.focPitch, goalPitch, pitchVelocity); + camera_approach_s16_symmetric_bool(&sModeTransition.focYaw, goalYaw, yawVelocity); + camera_approach_f32_symmetric_bool(&sModeTransition.focDist, 0, distVelocity); + vec3f_set_dist_and_angle(curFoc, nextFoc, sModeTransition.focDist, sModeTransition.focPitch, + sModeTransition.focYaw); - find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 50.f, sMarioCamState->pos[2], &floor); + vec3f_copy(newFoc, nextFoc); + vec3f_copy(newPos, nextPos); - if ((floor->type < SURFACE_PAINTING_WOBBLE_A6) || (floor->type > SURFACE_PAINTING_WARP_F9)) { - c->cutscene = 0; - gCutsceneTimer = CUTSCENE_STOP; - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; + if (gCamera->cutscene != 0 || !(gCameraMovementFlags & CAM_MOVE_C_UP_MODE)) { + floorHeight = find_floor(newPos[0], newPos[1], newPos[2], &floor); + if (floorHeight != FLOOR_LOWER_LIMIT) { + if ((floorHeight += 125.f) > newPos[1]) { + newPos[1] = floorHeight; + } + } + f32_find_wall_collision(&newPos[0], &newPos[1], &newPos[2], 0.f, 100.f); } + sModeTransition.framesLeft--; + yaw = calculate_yaw(newFoc, newPos); + } else { + sModeTransition.posDist = 0.f; + sModeTransition.posPitch = 0; + sModeTransition.posYaw = 0; + sStatusFlags &= ~CAM_FLAG_TRANSITION_OUT_OF_C_UP; } - c->mode = CAMERA_MODE_CLOSE; + vec3f_copy(sModeTransition.marioPos, sMarioCamState->pos); + return yaw; +} + +static UNUSED void stop_transitional_movement(void) { + sStatusFlags &= ~(CAM_FLAG_START_TRANSITION | CAM_FLAG_TRANSITION_OUT_OF_C_UP); + sModeTransition.framesLeft = 0; } /** - * Warp the camera to Mario, then use his faceAngle to calculate the right relative position. - * - * cvar0.point is Mario's position - * cvar0.angle is Mario's faceAngle + * Start fixed camera mode, setting the base position to (`x`, `y`, `z`) * - * cvar1 is the camera's position relative to Mario - * cvar2 is the camera's focus relative to Mario + * @return TRUE if the base pos was updated */ -void cutscene_exit_painting_start(struct Camera *c) { - struct Surface *floor; - f32 floorHeight; - - vec3f_set(sCutsceneVars[2].point, 258.f, -352.f, 1189.f); - vec3f_set(sCutsceneVars[1].point, 65.f, -155.f, 444.f); +s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { + s32 basePosSet = FALSE; + f32 posX = x; + f32 posY = y; + f32 posZ = z; - if (gPrevLevel == LEVEL_TTM) { - sCutsceneVars[1].point[1] = 0.f; - sCutsceneVars[1].point[2] = 0.f; + if (sFixedModeBasePosition[0] != posX || sFixedModeBasePosition[1] != posY + || sFixedModeBasePosition[2] != posZ) { + basePosSet = TRUE; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; } - vec3f_copy(sCutsceneVars[0].point, sMarioCamState->pos); - sCutsceneVars[0].angle[0] = 0; - sCutsceneVars[0].angle[1] = sMarioCamState->faceAngle[1]; - sCutsceneVars[0].angle[2] = 0; - offset_rotated(c->focus, sCutsceneVars[0].point, sCutsceneVars[1].point, sCutsceneVars[0].angle); - offset_rotated(c->pos, sCutsceneVars[0].point, sCutsceneVars[2].point, sCutsceneVars[0].angle); - floorHeight = find_floor(c->pos[0], c->pos[1] + 10.f, c->pos[2], &floor); - - if (floorHeight != FLOOR_LOWER_LIMIT) { - if (c->pos[1] < (floorHeight += 60.f)) { - c->pos[1] = floorHeight; - } + vec3f_set(sFixedModeBasePosition, posX, posY, posZ); + if (c->mode != CAMERA_MODE_FIXED) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_FIXED; + vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], + sFixedModeBasePosition[2]); } + return basePosSet; } -/** - * Decrease cvar2's x and z offset, moving closer to Mario. - */ -void cutscene_exit_painting_move_to_mario(struct Camera *c) { - Vec3f pos; - - //! Tricky math: Since offset_rotated() flips Z offsets, you'd expect a positive Z offset to move - //! the camera into the wall. However, Mario's faceAngle always points into the painting, so a - //! positive Z offset moves the camera "behind" Mario, away from the painting. - //! - //! In the success cutscene, when Mario jumps out face-first, only his gfx angle is updated. His - //! actual face angle isn't updated until after the cutscene. - approach_f32_asymptotic_bool(&sCutsceneVars[2].point[0], 178.f, 0.05f); - approach_f32_asymptotic_bool(&sCutsceneVars[2].point[2], 889.f, 0.05f); - offset_rotated(pos, sCutsceneVars[0].point, sCutsceneVars[2].point, sCutsceneVars[0].angle); - c->pos[0] = pos[0]; - c->pos[2] = pos[2]; -} - -/** - * Move the camera down to the floor Mario lands on. - */ -void cutscene_exit_painting_move_to_floor(struct Camera *c) { - struct Surface *floor; - Vec3f floorHeight; - - vec3f_copy(floorHeight, sMarioCamState->pos); - floorHeight[1] = find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 10.f, sMarioCamState->pos[2], &floor); - - if (floor != NULL) { - floorHeight[1] = floorHeight[1] + (sMarioCamState->pos[1] - floorHeight[1]) * 0.7f + 125.f; - approach_vec3f_asymptotic(c->focus, floorHeight, 0.2f, 0.2f, 0.2f); - - if (floorHeight[1] < c->pos[1]) { - approach_f32_asymptotic_bool(&c->pos[1], floorHeight[1], 0.05f); - } +void set_camera_mode_8_directions(struct Camera *c) { + if (c->mode != CAMERA_MODE_8_DIRECTIONS) { + c->mode = CAMERA_MODE_8_DIRECTIONS; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + s8DirModeBaseYaw = 0; + s8DirModeYawOffset = 0; } } /** - * Cutscene played when Mario leaves a painting, either due to death or collecting a star. + * If the camera mode is not already the boss fight camera (camera with two foci) + * set it to be so. */ -void cutscene_exit_painting(struct Camera *c) { - cutscene_event(cutscene_exit_painting_start, c, 0, 0); - cutscene_event(cutscene_exit_painting_move_to_mario, c, 5, -1); - cutscene_event(cutscene_exit_painting_move_to_floor, c, 5, -1); - - //! Hardcoded position. TTM's painting is close to an opposite wall, so just fix the pos. - if (gPrevLevel == LEVEL_TTM) { - vec3f_set(c->pos, -296.f, 1261.f, 3521.f); +void set_camera_mode_boss_fight(struct Camera *c) { + if (c->mode != CAMERA_MODE_BOSS_FIGHT) { + transition_to_camera_mode(c, CAMERA_MODE_BOSS_FIGHT, 15); + sModeOffsetYaw = c->nextYaw - DEGREES(45); } - - update_camera_yaw(c); } -/** - * Unused. Warp the camera to Mario. - */ -void cutscene_unused_exit_start(struct Camera *c) { - Vec3f offset; - Vec3s marioAngle; - - vec3f_set(offset, 200.f, 300.f, 200.f); - vec3s_set(marioAngle, 0, sMarioCamState->faceAngle[1], 0); - offset_rotated(c->pos, sMarioCamState->pos, offset, marioAngle); - set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); +void set_camera_mode_close_cam(u8 *mode) { + if (*mode != CAMERA_MODE_CLOSE) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + *mode = CAMERA_MODE_CLOSE; + } } /** - * Unused. Focus on Mario as he exits. + * Change to radial mode. + * If the difference in yaw between pos -> Mario and pos > focus is < 90 degrees, transition. + * Otherwise jump to radial mode. */ -void cutscene_unused_exit_focus_mario(struct Camera *c) { +void set_camera_mode_radial(struct Camera *c, s16 transitionTime) { Vec3f focus; + s16 yaw; - vec3f_set(focus, sMarioCamState->pos[0], sMarioCamState->pos[1] + 125.f, sMarioCamState->pos[2]); - set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); - approach_vec3f_asymptotic(c->focus, focus, 0.02f, 0.001f, 0.02f); - update_camera_yaw(c); -} - -/** - * Give control back to the player. - */ -void cutscene_exit_painting_end(struct Camera *c) { -#ifdef USE_COURSE_DEFAULT_MODE - c->mode = c->defMode; -#else - c->mode = CAMERA_MODE_CLOSE; -#endif - c->cutscene = CUTSCENE_NONE; - gCutsceneTimer = CUTSCENE_STOP; - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - update_camera_yaw(c); -} - -/** - * End the cutscene, starting cannon mode. - */ -void cutscene_enter_cannon_end(struct Camera *c) { - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - c->mode = CAMERA_MODE_INSIDE_CANNON; - c->cutscene = CUTSCENE_NONE; - sCannonYOffset = 800.f; + focus[0] = c->areaCenX; + focus[1] = sMarioCamState->pos[1]; + focus[2] = c->areaCenZ; + if (c->mode != CAMERA_MODE_RADIAL) { + yaw = calculate_yaw(focus, sMarioCamState->pos) - calculate_yaw(c->focus, c->pos) + DEGREES(90); + if (yaw > 0) { + transition_to_camera_mode(c, CAMERA_MODE_RADIAL, transitionTime); + } else { + c->mode = CAMERA_MODE_RADIAL; + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + } + sModeOffsetYaw = 0; + } } /** - * Rotate around the cannon as it rises out of the hole. + * Set the fixed camera base pos depending on the current level area */ -void cutscene_enter_cannon_raise(struct Camera *c) { - struct Object *obj; - f32 floorHeight; - struct Surface *floor; - Vec3f cannonFocus; - Vec3s cannonAngle; - - // Shake the camera when the cannon is fully raised - cutscene_event(cutscene_shake_explosion, c, 70, 70); - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - camera_approach_s16_symmetric_bool(&sCutsceneVars[1].angle[0], 0, 0x80); - camera_approach_s16_symmetric_bool(&sCutsceneVars[2].angle[0], 0, 0x80); - // Move the camera around the cannon, gradually rotating and moving closer - vec3f_set_dist_and_angle(sCutsceneVars[0].point, c->pos, sCutsceneVars[1].point[2], sCutsceneVars[1].angle[0], - sCutsceneVars[1].angle[1]); - sCutsceneVars[1].point[2] = approach_f32(sCutsceneVars[1].point[2], 400.f, 5.f, 5.f); - sCutsceneVars[1].angle[1] += 0x40; - sCutsceneVars[3].point[1] += 2.f; - c->pos[1] += sCutsceneVars[3].point[1]; - - if ((obj = sMarioCamState->usedObj) != NULL) { - sCutsceneVars[0].point[1] = obj->oPosY; - cannonAngle[0] = obj->oMoveAnglePitch; - cannonAngle[1] = obj->oMoveAngleYaw; - cannonAngle[2] = obj->oMoveAngleRoll; - c->focus[0] = obj->oPosX; - c->focus[1] = obj->oPosY; - c->focus[2] = obj->oPosZ; - cannonFocus[0] = 0.f; - cannonFocus[1] = 100.f; - cannonFocus[2] = 0.f; - offset_rotated(c->focus, c->focus, cannonFocus, cannonAngle); - } - - floorHeight = find_floor(c->pos[0], c->pos[1] + 500.f, c->pos[2], &floor) + 100.f; +void set_fixed_cam_axis_sa_lobby(UNUSED s16 preset) { + switch (gCurrLevelArea) { + case AREA_SA: + vec3f_set(sFixedModeBasePosition, 646.f, 143.f, -1513.f); + break; - if (c->pos[1] < floorHeight) { - c->pos[1] = floorHeight; + case AREA_CASTLE_LOBBY: + vec3f_set(sFixedModeBasePosition, -577.f, 143.f, 1443.f); + break; } } /** - * Start the cannon entering cutscene + * Block area-specific CameraTrigger and special surface modes. + * Generally, block area mode changes if: + * Mario is wearing the metal cap, or at the water's surface, or the camera is in Mario mode + * + * However, if the level is WDW, DDD, or COTMC (levels that have metal cap and water): + * Only block area mode changes if Mario is in a cannon, + * or if the camera is in Mario mode and Mario is not swimming or in water with the metal cap */ -void cutscene_enter_cannon_start(struct Camera *c) { - struct Object *obj; - - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sMarioCamState->cameraEvent = 0; - - // Store the cannon's position and angle in cvar0 - if ((obj = sMarioCamState->usedObj) != NULL) { - sCutsceneVars[0].point[0] = obj->oPosX; - sCutsceneVars[0].point[1] = obj->oPosY; - sCutsceneVars[0].point[2] = obj->oPosZ; - sCutsceneVars[0].angle[0] = obj->oMoveAnglePitch; - sCutsceneVars[0].angle[1] = obj->oMoveAngleYaw; - sCutsceneVars[0].angle[2] = obj->oMoveAngleRoll; +#ifdef ENABLE_VANILLA_CAM_PROCESSING +void check_blocking_area_processing(const u8 *mode) { + if (sMarioCamState->action & ACT_FLAG_METAL_WATER || + *mode == DEEP_WATER_CAMERA_MODE || *mode == WATER_SURFACE_CAMERA_MODE) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; } - // Store the camera's polar offset from the cannon in cvar1 - vec3f_get_dist_and_angle(sCutsceneVars[0].point, c->pos, &sCutsceneVars[1].point[2], - &sCutsceneVars[1].angle[0], &sCutsceneVars[1].angle[1]); - sCutsceneVars[3].point[1] = 0.f; - //! cvar4 is unused in this cutscene - sCutsceneVars[4].point[1] = 0.f; -} - -/** - * Store the camera's pos and focus for the door cutscene - */ -void cutscene_door_start(struct Camera *c) { - vec3f_copy(sCutsceneVars[0].point, c->pos); - vec3f_copy(sCutsceneVars[1].point, c->focus); -} - -/** - * Fix the camera in place while the door opens. - */ -void cutscene_door_fix_cam(struct Camera *c) { - vec3f_copy(c->pos, sCutsceneVars[0].point); - vec3f_copy(c->focus, sCutsceneVars[1].point); -} + if (gCurrLevelNum == LEVEL_DDD || gCurrLevelNum == LEVEL_WDW || gCurrLevelNum == LEVEL_COTMC) { + sStatusFlags &= ~CAM_FLAG_BLOCK_AREA_PROCESSING; + } -/** - * Loop until Mario is no longer using the door. - */ -void cutscene_door_loop(struct Camera *c) { - //! bitwise AND instead of boolean - if ((sMarioCamState->action != ACT_PULLING_DOOR) & (sMarioCamState->action != ACT_PUSHING_DOOR)) { - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; + if ((*mode == DEEP_WATER_CAMERA_MODE && + !(sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))) || + *mode == CAMERA_MODE_INSIDE_CANNON) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; } +#else +void check_blocking_area_processing(UNUSED const u8 *mode) { + sStatusFlags |= CAM_FLAG_BLOCK_AREA_PROCESSING; +#endif } /** - * Warp the camera behind Mario. + * Apply any modes that are triggered by special floor surface types */ -void cutscene_door_move_behind_mario(struct Camera *c) { - Vec3f camOffset; - s16 doorRotation; - - reset_pan_distance(c); - determine_pushing_or_pulling_door(&doorRotation); - set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); - vec3s_set(sCutsceneVars[0].angle, 0, sMarioCamState->faceAngle[1] + doorRotation, 0); - vec3f_set(camOffset, 0.f, 125.f, 250.f); +u32 surface_type_modes(struct Camera *c) { + u32 modeChanged = 0; - offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); -} + switch (sMarioGeometry.currFloorType) { + case SURFACE_CLOSE_CAMERA: + transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); + modeChanged++; + break; -/** - * Follow Mario through the door. - */ -void cutscene_door_follow_mario(struct Camera *c) { - s16 pitch, yaw; - f32 dist; + case SURFACE_CAMERA_FREE_ROAM: + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); + modeChanged++; + break; - set_focus_rel_mario(c, 0.f, 125.f, 0.f, 0); - vec3f_get_dist_and_angle(c->focus, c->pos, &dist, &pitch, &yaw); - camera_approach_f32_symmetric_bool(&dist, 150.f, 7.f); - vec3f_set_dist_and_angle(c->focus, c->pos, dist, pitch, yaw); - update_camera_yaw(c); + case SURFACE_NO_CAM_COL_SLIPPERY: + transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 90); + modeChanged++; + break; + } + return modeChanged; } /** - * Ends the door cutscene. Sets the camera mode to close mode unless the default is free roam. + * Set the camera mode to `mode` if Mario is not standing on a special surface */ -void cutscene_door_end(struct Camera *c) { -#ifdef USE_COURSE_DEFAULT_MODE - c->mode = c->defMode; -#else - c->mode = c->defMode == CAMERA_MODE_FREE_ROAM ? CAMERA_MODE_FREE_ROAM : CAMERA_MODE_CLOSE; -#endif +u32 set_mode_if_not_set_by_surface(struct Camera *c, u8 mode) { + u32 modeChanged = surface_type_modes(c); - c->cutscene = CUTSCENE_NONE; - gCutsceneTimer = CUTSCENE_STOP; - sStatusFlags |= CAM_FLAG_SMOOTH_MOVEMENT; - sStatusFlags &= ~CAM_FLAG_BLOCK_SMOOTH_MOVEMENT; - set_flag_post_door(c); - update_camera_yaw(c); + if ((modeChanged == 0) && (mode != 0)) { + transition_to_camera_mode(c, mode, 90); + } + + return modeChanged; } /** - * Used for entering a room that uses a specific camera mode, like the castle lobby or BBH + * Used in THI, check if Mario is standing on any of the special surfaces in that area */ -void cutscene_door_mode(struct Camera *c) { - reset_pan_distance(c); -#ifdef USE_COURSE_DEFAULT_MODE - c->mode = c->defMode; -#else - camera_course_processing(c); +void surface_type_modes_thi(struct Camera *c) { + switch (sMarioGeometry.currFloorType) { + case SURFACE_CLOSE_CAMERA: + if (c->mode != CAMERA_MODE_CLOSE) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); + } + break; - if (c->mode == CAMERA_MODE_FIXED) { - c->nextYaw = update_fixed_camera(c, c->focus, c->pos); - } - if (c->mode == CAMERA_MODE_PARALLEL_TRACKING) { - c->nextYaw = update_parallel_tracking_camera(c, c->focus, c->pos); - } -#endif + case SURFACE_CAMERA_FREE_ROAM: + if (c->mode != CAMERA_MODE_CLOSE) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); + } + break; + + case SURFACE_NO_CAM_COL_SLIPPERY: + if (c->mode != CAMERA_MODE_CLOSE) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 90); + } + break; - c->yaw = c->nextYaw; + case SURFACE_CAMERA_8_DIR: + transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 90); + break; - // Loop until Mario is no longer using the door - if (sMarioCamState->action != ACT_ENTERING_STAR_DOOR && - sMarioCamState->action != ACT_PULLING_DOOR && - sMarioCamState->action != ACT_PUSHING_DOOR) { - gCutsceneTimer = CUTSCENE_STOP; - c->cutscene = 0; + default: + transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 90); } } -/****************************************************************************************************** - * Cutscenes - ******************************************************************************************************/ - -/** - * Cutscene that plays when Mario beats the game. - */ -struct Cutscene sCutsceneEnding[] = { - { cutscene_ending_mario_fall, 170 }, - { cutscene_ending_mario_land, 70 }, - { cutscene_ending_mario_land_closeup, 75 }, -#ifdef VERSION_SH - { cutscene_ending_stars_free_peach, 431 }, -#else - { cutscene_ending_stars_free_peach, 386 }, -#endif - { cutscene_ending_peach_appears, 139 }, - { cutscene_ending_peach_descends, 590 }, - { cutscene_ending_mario_to_peach, 95 }, -#ifdef VERSION_SH - { cutscene_ending_peach_wakeup, 455 }, - { cutscene_ending_dialog, 286 }, -#else - { cutscene_ending_peach_wakeup, 425 }, - { cutscene_ending_dialog, 236 }, -#endif - { cutscene_ending_kiss, 245 }, - { cutscene_ending_cake_for_mario, CUTSCENE_LOOP }, - { cutscene_ending_stop, 0 } -}; - -/** - * Cutscene that plays when Mario collects the grand star from bowser. - */ -struct Cutscene sCutsceneGrandStar[] = { - { cutscene_grand_star, 360 }, - { cutscene_grand_star_fly, CUTSCENE_LOOP } -}; - -struct Cutscene sCutsceneUnused[] = { - { cutscene_unused_start, 1 }, - { cutscene_unused_loop, CUTSCENE_LOOP } -}; - /** - * Cutscene that plays when Mario enters a door that warps to another area. + * Terminates a list of CameraTriggers. */ -struct Cutscene sCutsceneDoorWarp[] = { - { cutscene_door_start, 1 }, - { cutscene_door_loop, CUTSCENE_LOOP } -}; +#define NULL_TRIGGER \ + { 0, NULL, 0, 0, 0, 0, 0, 0, 0 } /** - * Cutscene that plays after the credits, when Lakitu is flying away from the castle. + * The SL triggers operate camera behavior in front of the snowman who blows air. + * The first sets a 8 direction mode, while the latter (which encompasses the former) + * sets free roam mode. + * + * This behavior is exploitable, since the ranges assume that Mario must pass through the latter on + * exit. Using hyperspeed, the earlier area can be directly exited from, keeping the changes it applies. */ -struct Cutscene sCutsceneEndWaving[] = { - { cutscene_end_waving, CUTSCENE_LOOP } +struct CameraTrigger sCamSL[] = { + { 1, cam_sl_snowman_head_8dir, 1119, 3584, 1125, 1177, 358, 358, -0x1D27 }, + // This trigger surrounds the previous one + { 1, cam_sl_free_roam, 1119, 3584, 1125, 4096, 4096, 4096, -0x1D27 }, + NULL_TRIGGER }; /** - * The game's credits. + * The THI triggers are specifically for the tunnel near the start of the Huge Island. + * The first helps the camera from getting stuck on the starting side, the latter aligns with the + * tunnel. Both sides achieve their effect by editing the camera yaw. */ -struct Cutscene sCutsceneCredits[] = { - { cutscene_credits, CUTSCENE_LOOP } +struct CameraTrigger sCamTHI[] = { + { 1, cam_thi_move_cam_through_tunnel, -4609, -2969, 6448, 100, 300, 300, 0 }, + { 1, cam_thi_look_through_tunnel, -4809, -2969, 6448, 100, 300, 300, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when Mario pulls open a door. + * The HMC triggers are mostly for warping the camera below platforms, but the second trigger is used to + * start the cutscene for entering the CotMC pool. */ -struct Cutscene sCutsceneDoorPull[] = { -// HackerSM64 TODO: Properly transition when moving through doors -#ifndef FORCED_CAMERA_MODE - { cutscene_door_start, 1 }, - { cutscene_door_fix_cam, 30 }, - { cutscene_door_move_behind_mario, 1 }, - { cutscene_door_follow_mario, 50 }, -#endif - { cutscene_door_end, 0 } +struct CameraTrigger sCamHMC[] = { + { 1, cam_hmc_enter_maze, 1996, 102, 0, 205, 100, 205, 0 }, + { 1, cam_castle_hmc_start_pool_cutscene, 3350, -4689, 4800, 600, 50, 600, 0 }, + { 1, cam_hmc_elevator_black_hole, -3278, 1236, 1379, 358, 200, 358, 0 }, + { 1, cam_hmc_elevator_maze_emergency_exit, -2816, 2055, -2560, 358, 200, 358, 0 }, + { 1, cam_hmc_elevator_lake, -3532, 1543, -7040, 358, 200, 358, 0 }, + { 1, cam_hmc_elevator_maze, -972, 1543, -7347, 358, 200, 358, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when Mario pushes open a door. + * The SSL triggers are for starting the enter pyramid top cutscene, + * setting close mode in the middle of the pyramid, and setting the boss fight camera mode to outward + * radial. */ -struct Cutscene sCutsceneDoorPush[] = { -// HackerSM64 TODO: Properly transition when moving through doors -#ifndef FORCED_CAMERA_MODE - { cutscene_door_start, 1 }, - { cutscene_door_fix_cam, 20 }, - { cutscene_door_move_behind_mario, 1 }, - { cutscene_door_follow_mario, 50 }, -#endif - { cutscene_door_end, 0 } +struct CameraTrigger sCamSSL[] = { + { 1, cam_ssl_enter_pyramid_top, -2048, 1080, -1024, 150, 150, 150, 0 }, + { 2, cam_ssl_pyramid_center, 0, -104, -104, 1248, 1536, 2950, 0 }, + { 2, cam_ssl_pyramid_center, 0, 2500, 256, 515, 5000, 515, 0 }, + { 3, cam_ssl_boss_room, 0, -1534, -2040, 1000, 800, 1000, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when Mario pulls open a door that has some special mode requirement on the other - * side. + * The RR triggers are for changing between fixed and 8 direction mode when entering / leaving the building at + * the end of the ride. */ -struct Cutscene sCutsceneDoorPullMode[] = { -// HackerSM64 TODO: Properly transition when moving through doors -#ifndef FORCED_CAMERA_MODE - { cutscene_door_start, 1 }, - { cutscene_door_fix_cam, 30 }, -#endif - { cutscene_door_mode, CUTSCENE_LOOP } +struct CameraTrigger sCamRR[] = { + { 1, cam_rr_exit_building_side, -4197, 3819, -3087, 1769, 1490, 342, 0 }, + { 1, cam_rr_enter_building_side, -4197, 3819, -3771, 769, 490, 342, 0 }, + { 1, cam_rr_enter_building_window, -5603, 4834, -5209, 300, 600, 591, 0 }, + { 1, cam_rr_enter_building, -2609, 3730, -5463, 300, 650, 577, 0 }, + { 1, cam_rr_exit_building_top, -4196, 7343, -5155, 4500, 1000, 4500, 0 }, + { 1, cam_rr_enter_building, -4196, 6043, -5155, 500, 300, 500, 0 }, + NULL_TRIGGER, }; /** - * Cutscene that plays when Mario pushes open a door that has some special mode requirement on the other - * side. + * These triggers are unused, but because the first trigger surrounds the BoB tower and activates radial + * mode (which is called "tower mode" in the patent), it's speculated they belonged to BoB. + * + * This table contains the only instance of a CameraTrigger with an area set to -1, and it sets the mode + * to free_roam when Mario is not walking up the tower. */ -struct Cutscene sCutsceneDoorPushMode[] = { -// HackerSM64 TODO: Properly transition when moving through doors -#ifndef FORCED_CAMERA_MODE - { cutscene_door_start, 1 }, - { cutscene_door_fix_cam, 20 }, -#endif - { cutscene_door_mode, CUTSCENE_LOOP } +struct CameraTrigger sCamBOB[] = { + { 1, cam_bob_tower, 2468, 2720, -4608, 3263, 1696, 3072, 0 }, + { -1, cam_bob_default_free_roam, 0, 0, 0, 0, 0, 0, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when Mario enters the cannon and it rises out of the hole. + * The CotMC trigger is only used to prevent fix Lakitu in place when Mario exits through the waterfall. */ -struct Cutscene sCutsceneEnterCannon[] = { - { cutscene_enter_cannon_start, 1 }, - { cutscene_enter_cannon_raise, 121 }, - { cutscene_enter_cannon_end, 0 } +struct CameraTrigger sCamCotMC[] = { + { 1, cam_cotmc_exit_waterfall, 0, 1500, 3500, 550, 10000, 1500, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when a star spawns from ie a box or after a boss fight. + * The CCM triggers are used to set the flag that says when Mario is in the slide shortcut. */ -struct Cutscene sCutsceneStarSpawn[] = { - { cutscene_star_spawn, CUTSCENE_LOOP }, - { cutscene_star_spawn_back, 15 }, - { cutscene_star_spawn_end, 0 } +struct CameraTrigger sCamCCM[] = { + { 2, cam_ccm_enter_slide_shortcut, -4846, 2061, 27, 1229, 1342, 396, 0 }, + { 2, cam_ccm_leave_slide_shortcut, -6412, -3917, -6246, 307, 185, 132, 0 }, + NULL_TRIGGER }; /** - * Cutscene for the red coin star spawning. Compared to a regular star, this cutscene can warp long - * distances. + * The Castle triggers are used to set the camera to fixed mode when entering the lobby, and to set it + * to close mode when leaving it. They also set the mode to spiral staircase. + * + * There are two triggers for looking up and down straight staircases when Mario is at the start, + * and one trigger that starts the enter pool cutscene when Mario enters HMC. */ -struct Cutscene sCutsceneRedCoinStarSpawn[] = { - { cutscene_red_coin_star, CUTSCENE_LOOP }, - { cutscene_red_coin_star_end, 0 } +struct CameraTrigger sCamCastle[] = { + { 1, cam_castle_close_mode, -1100, 657, -1346, 300, 150, 300, 0 }, + { 1, cam_castle_enter_lobby, -1099, 657, -803, 300, 150, 300, 0 }, + { 1, cam_castle_close_mode, -2304, -264, -4072, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, -2304, 145, -1344, 140, 150, 140, 0 }, + { 1, cam_castle_enter_lobby, -2304, 145, -802, 140, 150, 140, 0 }, + //! Sets the camera mode when leaving secret aquarium + { 1, cam_castle_close_mode, 2816, 1200, -256, 100, 100, 100, 0 }, + { 1, cam_castle_close_mode, 256, -161, -4226, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, 256, 145, -1344, 140, 150, 140, 0 }, + { 1, cam_castle_enter_lobby, 256, 145, -802, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, -1023, 44, -4870, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, -459, 145, -1020, 140, 150, 140, 0x6000 }, + { 1, cam_castle_enter_lobby, -85, 145, -627, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, -1589, 145, -1020, 140, 150, 140, -0x6000 }, + { 1, cam_castle_enter_lobby, -1963, 145, -627, 140, 150, 140, 0 }, + { 1, cam_castle_leave_lobby_sliding_door, -2838, 657, -1659, 200, 150, 150, 0x2000 }, + { 1, cam_castle_enter_lobby_sliding_door, -2319, 512, -1266, 300, 150, 300, 0x2000 }, + { 1, cam_castle_close_mode, 844, 759, -1657, 40, 150, 40, -0x2000 }, + { 1, cam_castle_enter_lobby, 442, 759, -1292, 140, 150, 140, -0x2000 }, + { 2, cam_castle_enter_spiral_stairs, -1000, 657, 1740, 200, 300, 200, 0 }, + { 2, cam_castle_enter_spiral_stairs, -996, 1348, 1814, 200, 300, 200, 0 }, + { 2, cam_castle_close_mode, -946, 657, 2721, 50, 150, 50, 0 }, + { 2, cam_castle_close_mode, -996, 1348, 907, 50, 150, 50, 0 }, + { 2, cam_castle_close_mode, -997, 1348, 1450, 140, 150, 140, 0 }, + { 1, cam_castle_close_mode, -4942, 452, -461, 140, 150, 140, 0x4000 }, + { 1, cam_castle_close_mode, -3393, 350, -793, 140, 150, 140, 0x4000 }, + { 1, cam_castle_enter_lobby, -2851, 350, -792, 140, 150, 140, 0x4000 }, + { 1, cam_castle_enter_lobby, 803, 350, -228, 140, 150, 140, -0x4000 }, + //! Duplicate camera trigger outside JRB door + { 1, cam_castle_enter_lobby, 803, 350, -228, 140, 150, 140, -0x4000 }, + { 1, cam_castle_close_mode, 1345, 350, -229, 140, 150, 140, 0x4000 }, + { 1, cam_castle_close_mode, -946, -929, 622, 300, 150, 300, 0 }, + { 2, cam_castle_look_upstairs, -205, 1456, 2508, 210, 928, 718, 0 }, + { 1, cam_castle_basement_look_downstairs, -1027, -587, -718, 318, 486, 577, 0 }, + { 1, cam_castle_lobby_entrance, -1023, 376, 1830, 300, 400, 300, 0 }, + { 3, cam_castle_hmc_start_pool_cutscene, 2485, -1689, -2659, 600, 50, 600, 0 }, + NULL_TRIGGER }; /** - * Cutscene that plays when Mario enters a course painting. + * The BBH triggers are the most complex, they cause the camera to enter fixed mode for each room, + * transition between rooms, and enter free roam when outside. + * + * The triggers are also responsible for warping the camera below platforms. */ -struct Cutscene sCutsceneEnterPainting[] = { - { cutscene_enter_painting, CUTSCENE_LOOP } +struct CameraTrigger sCamBBH[] = { + { 1, cam_bbh_enter_front_door, 742, 0, 2369, 200, 200, 200, 0 }, + { 1, cam_bbh_leave_front_door, 741, 0, 1827, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 222, 0, 1458, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 222, 0, 639, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 435, 0, 222, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1613, 0, 222, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1827, 0, 1459, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, -495, 819, 1407, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, -495, 819, 640, 250, 200, 200, 0 }, + { 1, cam_bbh_room_1, 179, 819, 222, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1613, 819, 222, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1827, 819, 486, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1827, 819, 1818, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_lower, 2369, 0, 1459, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_lower, 3354, 0, 1347, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_lower, 2867, 514, 1843, 512, 102, 409, 0 }, + { 1, cam_bbh_room_4, 3354, 0, 804, 200, 200, 200, 0 }, + { 1, cam_bbh_room_4, 1613, 0, -320, 200, 200, 200, 0 }, + { 1, cam_bbh_room_8, 435, 0, -320, 200, 200, 200, 0 }, + { 1, cam_bbh_room_5_library, -2021, 0, 803, 200, 200, 200, 0 }, + { 1, cam_bbh_room_5_library, -320, 0, 640, 200, 200, 200, 0 }, + { 1, cam_bbh_room_5_library_to_hidden_transition, -1536, 358, -254, 716, 363, 102, 0 }, + { 1, cam_bbh_room_5_hidden_to_library_transition, -1536, 358, -459, 716, 363, 102, 0 }, + { 1, cam_bbh_room_5_hidden, -1560, 0, -1314, 200, 200, 200, 0 }, + { 1, cam_bbh_room_3, -320, 0, 1459, 200, 200, 200, 0 }, + { 1, cam_bbh_room_3, -2021, 0, 1345, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_library, 2369, 819, 486, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_library, 2369, 1741, 486, 200, 200, 200, 0 }, + { 1, cam_bbh_room_2_library_to_trapdoor_transition, 2867, 1228, 1174, 716, 414, 102, 0 }, + { 1, cam_bbh_room_2_trapdoor_transition, 2867, 1228, 1378, 716, 414, 102, 0 }, + { 1, cam_bbh_room_2_trapdoor, 2369, 819, 1818, 200, 200, 200, 0 }, + { 1, cam_bbh_room_9_attic, 1829, 1741, 486, 200, 200, 200, 0 }, + { 1, cam_bbh_room_9_attic, 741, 1741, 1587, 200, 200, 200, 0 }, + { 1, cam_bbh_room_9_attic_transition, 102, 2048, -191, 100, 310, 307, 0 }, + { 1, cam_bbh_room_9_mr_i_transition, 409, 2048, -191, 100, 310, 307, 0 }, + { 1, cam_bbh_room_13_balcony, 742, 1922, 2164, 200, 200, 200, 0 }, + { 1, cam_bbh_fall_off_roof, 587, 1322, 2677, 1000, 400, 600, 0 }, + { 1, cam_bbh_room_3, -1037, 819, 1408, 200, 200, 200, 0 }, + { 1, cam_bbh_room_3, -1970, 1024, 1345, 200, 200, 200, 0 }, + { 1, cam_bbh_room_8, 179, 819, -320, 200, 200, 200, 0 }, + { 1, cam_bbh_room_7_mr_i, 1613, 819, -320, 200, 200, 200, 0 }, + { 1, cam_bbh_room_7_mr_i_to_coffins_transition, 2099, 1228, -819, 102, 414, 716, 0 }, + { 1, cam_bbh_room_7_coffins_to_mr_i_transition, 2304, 1228, -819, 102, 414, 716, 0 }, + { 1, cam_bbh_room_6, -1037, 819, 640, 200, 200, 200, 0 }, + { 1, cam_bbh_room_6, -1970, 1024, 803, 200, 200, 200, 0 }, + { 1, cam_bbh_room_1, 1827, 819, 1818, 200, 200, 200, 0 }, + { 1, cam_bbh_fall_into_pool, 2355, -1112, -193, 1228, 500, 1343, 0 }, + { 1, cam_bbh_fall_into_pool, 2355, -1727, 1410, 1228, 500, 705, 0 }, + { 1, cam_bbh_elevator_room_lower, 0, -2457, 1827, 250, 200, 250, 0 }, + { 1, cam_bbh_elevator_room_lower, 0, -2457, 2369, 250, 200, 250, 0 }, + { 1, cam_bbh_elevator_room_lower, 0, -2457, 4929, 250, 200, 250, 0 }, + { 1, cam_bbh_elevator_room_lower, 0, -2457, 4387, 250, 200, 250, 0 }, + { 1, cam_bbh_room_0_back_entrance, 1887, -2457, 204, 250, 200, 250, 0 }, + { 1, cam_bbh_room_0, 1272, -2457, 204, 250, 200, 250, 0 }, + { 1, cam_bbh_room_0, -1681, -2457, 204, 250, 200, 250, 0 }, + { 1, cam_bbh_room_0_back_entrance, -2296, -2457, 204, 250, 200, 250, 0 }, + { 1, cam_bbh_elevator, -2939, -605, 5367, 800, 100, 800, 0 }, + { 1, cam_bbh_room_12_upper, -2939, -205, 5367, 300, 100, 300, 0 }, + { 1, cam_bbh_room_12_upper, -2332, -204, 4714, 250, 200, 250, 0x6000 }, + { 1, cam_bbh_room_0_back_entrance, -1939, -204, 4340, 250, 200, 250, 0x6000 }, + NULL_TRIGGER }; -/** - * Cutscene that plays when Mario dies and warps back to the castle. - */ -struct Cutscene sCutsceneDeathExit[] = { - { cutscene_exit_painting, 118 }, - { cutscene_exit_painting_end, 0 } -}; +#define _ NULL +#define STUB_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, cameratable) cameratable, +#define DEFINE_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, cameratable) cameratable, -/** - * Cutscene that plays when Mario warps to the castle after collecting a star. +/* + * This table has an extra 2 levels after the last unknown_38 stub level. What I think + * the programmer was thinking was that the table is null terminated and so used the + * level count as a correspondence to the ID of the final level, but the enum represents + * an ID *after* the last stub level, not before or during it. + * + * Each table is terminated with NULL_TRIGGER */ -struct Cutscene sCutsceneExitPaintingSuccess[] = { - { cutscene_exit_painting, 180 }, - { cutscene_exit_painting_end, 0 } +struct CameraTrigger *sCameraTriggers[LEVEL_COUNT + 1] = { + NULL, + #include "levels/level_defines.h" }; +#undef _ +#undef STUB_LEVEL +#undef DEFINE_LEVEL -struct Cutscene sCutsceneUnusedExit[] = { - { cutscene_unused_exit_start, 1 }, - { cutscene_unused_exit_focus_mario, 60 }, - { cutscene_exit_painting_end, 0 } -}; +void area_specific_camera_processing(struct Camera *c) { + // Area-specific camera processing + if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { + switch (gCurrLevelArea) { + case AREA_WF: + if (sMarioCamState->action == ACT_RIDING_HOOT) { + transition_to_camera_mode(c, CAMERA_MODE_SLIDE_HOOT, 60); + } else { + switch (sMarioGeometry.currFloorType) { + case SURFACE_CAMERA_8_DIR: + transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 90); + s8DirModeBaseYaw = DEGREES(90); + break; -/** - * The intro of the game. Peach reads her letter and Lakitu flies down to Mario's warp pipe. - */ -struct Cutscene sCutsceneIntroPeach[] = { - { cutscene_intro_peach_letter, CUTSCENE_LOOP }, - { cutscene_intro_peach_reset_fov, 35 }, -#ifdef VERSION_EU - { cutscene_intro_peach_fly_to_pipe, 675 }, -#else - { cutscene_intro_peach_fly_to_pipe, 820 }, -#endif - { cutscene_intro_peach_mario_appears, 270 }, - { cutscene_intro_peach_dialog, CUTSCENE_LOOP } -}; + case SURFACE_BOSS_FIGHT_CAMERA: + if (gCurrActNum == 1) { + set_camera_mode_boss_fight(c); + } else { + set_camera_mode_radial(c, 60); + } + break; + default: + set_camera_mode_radial(c, 60); + } + } + break; -/** - * Cutscene that plays when a cannon door is opened. - */ -struct Cutscene sCutscenePrepareCannon[] = { - { cutscene_prepare_cannon, 170 }, - { cutscene_prepare_cannon_end, 0 } -}; + case AREA_BBH: + // if camera is fixed at bbh_room_13_balcony_camera (but as floats) + if (vec3f_compare(sFixedModeBasePosition, 210.f, 420.f, 3109.f) == TRUE) { + if (sMarioCamState->pos[1] < 1800.f) { + transition_to_camera_mode(c, CAMERA_MODE_CLOSE, 30); + } + } + break; -/** - * Cutscene that plays when Mario enters the castle grounds after leaving CotMC through the waterfall. - */ -struct Cutscene sCutsceneExitWaterfall[] = { - { cutscene_exit_waterfall, 52 }, - { cutscene_exit_to_castle_grounds_end, 0 } -}; + case AREA_SSL_PYRAMID: + set_mode_if_not_set_by_surface(c, CAMERA_MODE_OUTWARD_RADIAL); + break; -/** - * Cutscene that plays when Mario falls from WMOTR. - */ -struct Cutscene sCutsceneFallToCastleGrounds[] = { - { cutscene_exit_fall_to_castle_grounds, 73 }, - { cutscene_exit_to_castle_grounds_end, 0 } -}; + case AREA_SSL_OUTSIDE: + set_mode_if_not_set_by_surface(c, CAMERA_MODE_RADIAL); + break; -/** - * Cutscene that plays when Mario enters the pyramid through the hole at the top. - */ -struct Cutscene sCutsceneEnterPyramidTop[] = { - { cutscene_enter_pyramid_top, 90 }, - { cutscene_exit_to_castle_grounds_end, 0 } -}; + case AREA_THI_HUGE: + break; -/** - * Unused cutscene for when the pyramid explodes. - */ -struct Cutscene sCutscenePyramidTopExplode[] = { - { cutscene_mario_dialog, CUTSCENE_LOOP }, - { cutscene_pyramid_top_explode, 150 }, - { cutscene_pyramid_top_explode_end, 0 } -}; + case AREA_THI_TINY: + surface_type_modes_thi(c); + break; -/** - * Cutscene that plays when Mario dies while standing, or from electrocution. - */ -struct Cutscene sCutsceneStandingDeath[] = { - { cutscene_death_standing, CUTSCENE_LOOP } -}; + case AREA_TTC: + set_mode_if_not_set_by_surface(c, CAMERA_MODE_OUTWARD_RADIAL); + break; -/** - * Cutscene that plays when Mario enters HMC or CotMC. - */ -struct Cutscene sCutsceneEnterPool[] = { - { cutscene_enter_pool, 100 }, - { cutscene_exit_to_castle_grounds_end, 0 } -}; + case AREA_BOB: + if (set_mode_if_not_set_by_surface(c, CAMERA_MODE_NONE) == 0) { + if (sMarioGeometry.currFloorType == SURFACE_BOSS_FIGHT_CAMERA) { + set_camera_mode_boss_fight(c); + } else { + if (c->mode == CAMERA_MODE_CLOSE) { + transition_to_camera_mode(c, CAMERA_MODE_RADIAL, 60); + } else { + set_camera_mode_radial(c, 60); + } + } + } + break; -/** - * Cutscene that plays when Mario dies on his stomach. - */ -struct Cutscene sCutsceneDeathStomach[] = { - { cutscene_death_stomach, CUTSCENE_LOOP } -}; + case AREA_WDW_MAIN: + switch (sMarioGeometry.currFloorType) { + case SURFACE_INSTANT_WARP_1B: + c->defMode = CAMERA_MODE_RADIAL; + break; + } + break; -/** - * Cutscene that plays when Mario dies on his back. - */ -struct Cutscene sCutsceneDeathOnBack[] = { - { cutscene_bbh_death, CUTSCENE_LOOP } -}; + case AREA_WDW_TOWN: + switch (sMarioGeometry.currFloorType) { + case SURFACE_INSTANT_WARP_1C: + c->defMode = CAMERA_MODE_CLOSE; + break; + } + break; -/** - * Cutscene that plays when Mario dies in quicksand. - */ -struct Cutscene sCutsceneQuicksandDeath[] = { - { cutscene_quicksand_death, CUTSCENE_LOOP }, -}; + case AREA_DDD_WHIRLPOOL: + //! @bug this does nothing + gLakituState.defMode = CAMERA_MODE_OUTWARD_RADIAL; + break; -/** - * Unused cutscene for ACT_WATER_DEATH, which happens when Mario gets hit by an enemy under water. - */ -struct Cutscene sCutsceneWaterDeath[] = { - { cutscene_quicksand_death, CUTSCENE_LOOP } -}; + case AREA_DDD_SUB: + if ((c->mode != CAMERA_MODE_BEHIND_MARIO) + && (c->mode != CAMERA_MODE_WATER_SURFACE)) { + if (((sMarioCamState->action & ACT_FLAG_ON_POLE) != 0) + || (sMarioGeometry.currFloorHeight > 800.f)) { + transition_to_camera_mode(c, CAMERA_MODE_8_DIRECTIONS, 60); -/** - * Cutscene that plays when Mario suffocates. - */ -struct Cutscene sCutsceneSuffocation[] = { - { cutscene_suffocation, CUTSCENE_LOOP } -}; + } else { + if (sMarioCamState->pos[1] < 800.f) { + transition_to_camera_mode(c, CAMERA_MODE_FREE_ROAM, 60); + } + } + } + //! @bug this does nothing + gLakituState.defMode = CAMERA_MODE_FREE_ROAM; + break; + } + } +} /** - * Cutscene that plays when entering bowser's arenas. + * Activates any CameraTriggers that Mario is inside. + * Then, applies area-specific processing to the camera, such as setting the default mode, or changing + * the mode based on the terrain type Mario is standing on. + * + * @return the camera's mode after processing, although this is unused in the code */ -struct Cutscene sCutsceneEnterBowserArena[] = { - { cutscene_bowser_arena, 180 }, - { cutscene_bowser_arena_dialog, CUTSCENE_LOOP }, - { cutscene_bowser_arena_end, 0 } -}; - -// The dance cutscenes are automatically stopped since reset_camera() is called after Mario warps. +s16 camera_course_processing(struct Camera *c) { + s16 level = gCurrLevelNum; + s8 area = gCurrentArea->index; + // Bounds iterator + u32 b; + // Camera trigger's bounding box + Vec3f center, bounds; + u32 insideBounds = FALSE; + u8 oldMode = c->mode; -/** - * Star dance cutscene. - * For the default dance, the camera moves closer to Mario, then stays in place. - * For the rotate dance, the camera moves closer and rotates clockwise around Mario. - */ -struct Cutscene sCutsceneDanceDefaultRotate[] = { - { cutscene_dance_default_rotate, CUTSCENE_LOOP } -}; + if (c->mode == CAMERA_MODE_C_UP) { + c->mode = sModeInfo.lastMode; + } + check_blocking_area_processing(&c->mode); + if (level > LEVEL_COUNT + 1) { + level = LEVEL_COUNT + 1; + } -/** - * Star dance cutscene. - * The camera moves closer and rotates clockwise around Mario. - */ -struct Cutscene sCutsceneDanceFlyAway[] = { - { cutscene_dance_fly_away, CUTSCENE_LOOP } -}; + if (sCameraTriggers[level] != NULL) { + b = 0; -/** - * Star dance cutscene. - * The camera moves in for a closeup on Mario. Used in tight spaces and underwater. - */ -struct Cutscene sCutsceneDanceCloseup[] = { - { cutscene_dance_closeup, CUTSCENE_LOOP } -}; + // Process positional triggers. + // All triggered events are called, not just the first one. + while (sCameraTriggers[level][b].event != NULL) { -/** - * Star dance cutscene. - * The camera moves closer and rotates clockwise around Mario. - */ -struct Cutscene sCutsceneKeyDance[] = { - { cutscene_key_dance, CUTSCENE_LOOP } -}; + // Check only the current area's triggers + if (sCameraTriggers[level][b].area == area) { + // Copy the bounding box into center and bounds + vec3f_set(center, sCameraTriggers[level][b].centerX, + sCameraTriggers[level][b].centerY, + sCameraTriggers[level][b].centerZ); + vec3f_set(bounds, sCameraTriggers[level][b].boundsX, + sCameraTriggers[level][b].boundsY, + sCameraTriggers[level][b].boundsZ); -/** - * Cutscene that plays when Mario presses a cap switch. - */ -struct Cutscene sCutsceneCapSwitchPress[] = { - { cutscene_cap_switch_press, CUTSCENE_LOOP } -}; + // Check if Mario is inside the bounds + if (is_pos_in_bounds(sMarioCamState->pos, center, bounds, + sCameraTriggers[level][b].boundsYaw) == TRUE) { + //! This should be checked before calling is_pos_in_bounds. (It doesn't belong + //! outside the while loop because some events disable area processing) + if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { + sCameraTriggers[level][b].event(c); + insideBounds = TRUE; + } + } + } -/** - * Cutscene that plays when Mario opens a sliding star door. - */ -struct Cutscene sCutsceneSlidingDoorsOpen[] = { - { cutscene_sliding_doors_open, 50 }, - { cutscene_double_doors_end, 0 } -}; + if ((sCameraTriggers[level])[b].area == -1) { + // Default triggers are only active if Mario is not already inside another trigger + if (!insideBounds) { + if (!(sStatusFlags & CAM_FLAG_BLOCK_AREA_PROCESSING)) { + sCameraTriggers[level][b].event(c); + } + } + } -/** - * Cutscene that plays when Mario unlocks the basement or upstairs key door. - */ -struct Cutscene sCutsceneUnlockKeyDoor[] = { - { cutscene_unlock_key_door, 200 }, - { cutscene_double_doors_end, 0 } -}; + b++; + } + } +#if defined(ENABLE_VANILLA_CAM_PROCESSING) && !defined(FORCED_CAMERA_MODE) && !defined(USE_COURSE_DEFAULT_MODE) + area_specific_camera_processing(c); +#endif -/** - * Cutscene that plays when Mario exits bowser's arena after getting the key. - */ -struct Cutscene sCutsceneExitBowserSuccess[] = { - { cutscene_exit_bowser_succ, 190 }, - { cutscene_non_painting_end, 0 } -}; + sStatusFlags &= ~CAM_FLAG_BLOCK_AREA_PROCESSING; + if (oldMode == CAMERA_MODE_C_UP) { + sModeInfo.lastMode = c->mode; + c->mode = oldMode; + } + return c->mode; +} /** - * Unused cutscene for when Mario dies in bowser's arena. Instead, Mario just respawns at the warp pipe. + * Stores type and height of the nearest floor and ceiling to Mario in `pg` + * + * Note: Also finds the water level, but waterHeight is unused */ -struct Cutscene sCutsceneExitBowserDeath[] = { - { cutscene_exit_bowser_death, 120 }, - { cutscene_non_painting_end, 0 } -}; +void find_mario_floor_and_ceil(struct PlayerGeometry *pg) { + struct Surface *surf; + s32 tempCollisionFlags = gCollisionFlags; + gCollisionFlags |= COLLISION_FLAG_CAMERA; -/** - * Cutscene that plays when Mario exits a non-painting course, like HMC. - */ -struct Cutscene sCutsceneExitSpecialSuccess[] = { - { cutscene_exit_non_painting_succ, 163 }, - { cutscene_non_painting_end, 0 } -}; + if (find_floor(sMarioCamState->pos[0], sMarioCamState->pos[1] + 10.f, + sMarioCamState->pos[2], &surf) != FLOOR_LOWER_LIMIT) { + pg->currFloorType = surf->type; + } else { + pg->currFloorType = 0; + } -/** - * Cutscene that plays when Mario exits from dying in a non-painting course, like HMC. - */ -struct Cutscene sCutsceneNonPaintingDeath[] = { - { cutscene_non_painting_death, 120 }, - { cutscene_non_painting_end, 0 } -}; + if (find_ceil(sMarioCamState->pos[0], sMarioCamState->pos[1] - 10.f, + sMarioCamState->pos[2], &surf) != CELL_HEIGHT_LIMIT) { + pg->currCeilType = surf->type; + } else { + pg->currCeilType = 0; + } -/** - * Cutscene that plays when Mario talks to a creature. - */ -struct Cutscene sCutsceneDialog[] = { - { cutscene_dialog, CUTSCENE_LOOP }, - { cutscene_dialog_set_flag, 12 }, - { cutscene_dialog_end, 0 } -}; + gCollisionFlags &= ~COLLISION_FLAG_CAMERA; + pg->currFloorHeight = find_floor(sMarioCamState->pos[0], + sMarioCamState->pos[1] + 10.f, + sMarioCamState->pos[2], &pg->currFloor); + pg->currCeilHeight = find_ceil(sMarioCamState->pos[0], + sMarioCamState->pos[1] - 10.f, + sMarioCamState->pos[2], &pg->currCeil); + pg->waterHeight = find_water_level(sMarioCamState->pos[0], sMarioCamState->pos[2]); + gCollisionFlags = tempCollisionFlags; +} /** - * Cutscene that plays when Mario reads a sign or message. - */ -struct Cutscene sCutsceneReadMessage[] = { - { cutscene_read_message, CUTSCENE_LOOP }, - { cutscene_read_message_set_flag, 15 }, - { cutscene_read_message_end, 0 } -}; - -/* TODO: - * The next two arrays are both related to levels, and they look generated. - * These should be split into their own file. + * Rotate the camera's focus around the camera's position by incYaw and incPitch */ +void pan_camera(struct Camera *c, s16 incPitch, s16 incYaw) { + f32 distCamToFocus; + s16 pitch, yaw; -/** - * Converts the u32 given in DEFINE_COURSE to a u8 with the odd and even digits rotated into the right - * order for sDanceCutsceneIndexTable - */ -#define DROT(value, index) ((value >> (32 - (index + 1) * 8)) & 0xF0) >> 4 | \ - ((value >> (32 - (index + 1) * 8)) & 0x0F) << 4 + vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &pitch, &yaw); + pitch += incPitch; + yaw += incYaw; + vec3f_set_dist_and_angle(c->pos, c->focus, distCamToFocus, pitch, yaw); +} -#define DANCE_ENTRY(c) { DROT(c, 0), DROT(c, 1), DROT(c, 2), DROT(c, 3) }, -#define DEFINE_COURSE(_0, cutscenes) DANCE_ENTRY(cutscenes) -#define DEFINE_COURSES_END() -#define DEFINE_BONUS_COURSE(_0, cutscenes) DANCE_ENTRY(cutscenes) +static UNUSED void unused_start_bowser_bounce_shake(UNUSED struct Camera *c) { + set_environmental_camera_shake(SHAKE_ENV_BOWSER_THROW_BOUNCE); +} -/** - * Each hex digit is an index into sDanceCutsceneTable. - * - * 0: Lakitu flies away after the dance - * 1: Only rotates the camera, doesn't zoom out - * 2: The camera goes to a close up of Mario - * 3: Bowser keys and the grand star - * 4: Default, used for 100 coin stars, 8 red coin stars in bowser levels, and secret stars +/* TODO: + * The next two arrays are both related to levels, and they look generated. + * These should be split into their own file. */ -u8 sDanceCutsceneIndexTable[][4] = { - #include "levels/course_defines.h" - { 0x44, 0x44, 0x44, 0x04 }, // (26) Why go to all this trouble to save bytes and do this?! -}; -#undef DEFINE_COURSE -#undef DEFINE_COURSES_END -#undef DEFINE_BONUS_COURSE - -#undef DANCE_ENTRY -#undef DROT /** * These masks set whether or not the camera zooms out when game is paused. @@ -10423,445 +2087,6 @@ u8 sZoomOutAreaMasks[] = { STATIC_ASSERT(ARRAY_COUNT(sZoomOutAreaMasks) - 1 == LEVEL_MAX / 2, "Make sure you edit sZoomOutAreaMasks when adding / removing courses."); -/* - * credits spline paths. - * TODO: Separate these into their own file(s) - */ - -struct CutsceneSplinePoint sBobCreditsSplinePositions[] = { - { 1, 0, { 5984, 3255, 4975 } }, - { 2, 0, { 4423, 3315, 1888 } }, - { 3, 0, { 776, 2740, -1825 } }, - { 4, 0, { -146, 3894, -3167 } }, - { -1, 0, { 741, 4387, -5474 } } -}; - -struct CutsceneSplinePoint sBobCreditsSplineFocus[] = { - { 0, 30, { 5817, 3306, 4507 } }, - { 0, 40, { 4025, 3378, 1593 } }, - { 0, 50, { 1088, 2652, -2205 } }, - { 0, 60, { 205, 3959, -3517 } }, - { -1, 60, { 1231, 4400, -5649 } } -}; - -struct CutsceneSplinePoint sWfCreditsSplinePositions[] = { - { 0, 0, { -301, 1399, 2643 } }, - { 0, 0, { -182, 2374, 4572 } }, - { 0, 0, { 4696, 3864, 413 } }, - { 0, 0, { 1738, 4891, -1516 } }, - { -1, 0, { 1783, 4891, -1516 } } -}; - -struct CutsceneSplinePoint sWfCreditsSplineFocus[] = { - { 1, 30, { -249, 1484, 2153 } }, - { 2, 40, { -200, 2470, 4082 } }, - { 3, 40, { 4200, 3916, 370 } }, - { 4, 40, { 1523, 4976, -1072 } }, - { -1, 40, { 1523, 4976, -1072 } } -}; - -struct CutsceneSplinePoint sJrbCreditsSplinePositions[] = { - { 0, 0, { 5538, -4272, 2376 } }, - { 0, 0, { 5997, -3303, 2261 } }, - { 0, 0, { 6345, -3255, 2179 } }, - { 0, 0, { 6345, -3255, 2179 } }, - { -1, 0, { 6694, -3203, 2116 } } -}; - -struct CutsceneSplinePoint sJrbCreditsSplineFocus[] = { - { 0, 50, { 5261, -4683, 2443 } }, - { 0, 50, { 5726, -3675, 2456 } }, - { 0, 50, { 6268, -2817, 2409 } }, - { 0, 50, { 6596, -2866, 2369 } }, - { -1, 50, { 7186, -3153, 2041 } } -}; - -struct CutsceneSplinePoint sCcmSlideCreditsSplinePositions[] = { - { 0, 0, { -6324, 6745, -5626 } }, - { 1, 0, { -6324, 6745, -5626 } }, - { 2, 0, { -6108, 6762, -5770 } }, - { 3, 0, { -5771, 6787, -5962 } }, - { -1, 0, { -5672, 6790, -5979 } } -}; - -struct CutsceneSplinePoint sCcmSlideCreditsSplineFocus[] = { - { 0, 50, { -5911, 6758, -5908 } }, - { 1, 50, { -5911, 6758, -5908 } }, - { 2, 50, { -5652, 6814, -5968 } }, - { 3, 50, { -5277, 6801, -6043 } }, - { -1, 50, { -5179, 6804, -6060 } } -}; - -struct CutsceneSplinePoint sBbhCreditsSplinePositions[] = { - { 1, 0, { 1088, 341, 2447 } }, - { 2, 0, { 1338, 610, 2808 } }, - { 3, 0, { 2267, 1612, 2966 } }, - { -1, 0, { 2296, 1913, 2990 } } -}; - -struct CutsceneSplinePoint sBbhCreditsSplineFocus[] = { - { 1, 50, { 1160, 263, 1958 } }, - { 2, 50, { 1034, 472, 2436 } }, - { 3, 50, { 1915, 1833, 2688 } }, - { -1, 50, { 2134, 2316, 2742 } } -}; - -struct CutsceneSplinePoint sHmcCreditsSplinePositions[] = { - { 1, 0, { -5952, 1807, -5882 } }, - { 2, 0, { -5623, 1749, -4863 } }, - { 3, 0, { -5472, 1955, -2520 } }, - { 4, 0, { -5544, 1187, -1085 } }, - { -1, 0, { -5547, 391, -721 } } -}; - -struct CutsceneSplinePoint sHmcCreditsSplineFocus[] = { - { 1, 210, { -5952, 1884, -6376 } }, - { 2, 58, { -5891, 1711, -5283 } }, - { 3, 30, { -5595, 1699, -2108 } }, - { 4, 31, { -5546, 794, -777 } }, - { -1, 31, { -5548, -85, -572 } } -}; - -struct CutsceneSplinePoint sThiWigglerCreditsSplinePositions[] = { - { 1, 0, { -1411, 2474, -1276 } }, - { 2, 0, { -1606, 2479, -434 } }, - { -1, 0, { -1170, 2122, 1337 } } -}; - -struct CutsceneSplinePoint sThiWigglerCreditsSplineFocus[] = { - { 1, 50, { -1053, 2512, -928 } }, - { 2, 50, { -1234, 2377, -114 } }, - { -1, 50, { -758, 2147, 1054 } } -}; - -struct CutsceneSplinePoint sVolcanoCreditsSplinePositions[] = { - { 0, 0, { -1445, 1094, 1617 } }, - { 0, 0, { -1509, 649, 871 } }, - { 0, 0, { -1133, 420, -248 } }, - { 0, 0, { -778, 359, -1052 } }, - { 0, 0, { -565, 260, -1730 } }, - { -1, 0, { 1274, 473, -275 } } -}; - -struct CutsceneSplinePoint sVolcanoCreditsSplineFocus[] = { - { 0, 50, { -1500, 757, 1251 } }, - { 0, 50, { -1401, 439, 431 } }, - { 0, 50, { -749, 270, -532 } }, - { 0, 50, { -396, 270, -1363 } }, - { 0, 50, { -321, 143, -2151 } }, - { -1, 50, { 1002, 460, -694 } } -}; - -struct CutsceneSplinePoint sSslCreditsSplinePositions[] = { - { 0, 0, { -4262, 4658, -5015 } }, - { 0, 0, { -3274, 2963, -4661 } }, - { 0, 0, { -2568, 812, -6528 } }, - { 0, 0, { -414, 660, -7232 } }, - { 0, 0, { 1466, 660, -6898 } }, - { -1, 0, { 2724, 660, -6298 } } -}; - -struct CutsceneSplinePoint sSslCreditsSplineFocus[] = { - { 0, 50, { -4083, 4277, -4745 } }, - { 0, 50, { -2975, 2574, -4759 } }, - { 0, 50, { -2343, 736, -6088 } }, - { 0, 50, { -535, 572, -6755 } }, - { 0, 50, { 1311, 597, -6427 } }, - { -1, 50, { 2448, 612, -5884 } } -}; - -struct CutsceneSplinePoint sDddCreditsSplinePositions[] = { - { 0, 0, { -874, -4933, 366 } }, - { 0, 0, { -1463, -4782, 963 } }, - { 0, 0, { -1893, -4684, 1303 } }, - { 0, 0, { -2818, -4503, 1583 } }, - { 0, 0, { -4095, -2924, 730 } }, - { 0, 0, { -4737, -1594, -63 } }, - { -1, 0, { -4681, -1084, -623 } } -}; - -struct CutsceneSplinePoint sDddCreditsSplineFocus[] = { - { 0, 50, { -1276, -4683, 622 } }, - { 0, 50, { -1858, -4407, 1097 } }, - { 0, 50, { -2324, -4332, 1318 } }, - { 0, 50, { -3138, -4048, 1434 } }, - { 0, 50, { -4353, -2444, 533 } }, - { 0, 50, { -4807, -1169, -436 } }, - { -1, 50, { -4665, -664, -1007 } } -}; - -struct CutsceneSplinePoint sSlCreditsSplinePositions[] = { - { 0, 0, { 939, 6654, 6196 } }, - { 0, 0, { 1873, 5160, 3714 } }, - { 0, 0, { 3120, 3564, 1314 } }, - { -1, 0, { 2881, 4231, 573 } } -}; - -struct CutsceneSplinePoint sSlCreditsSplineFocus[] = { - { 0, 50, { 875, 6411, 5763 } }, - { 0, 50, { 1659, 4951, 3313 } }, - { 0, 50, { 2630, 3565, 1215 } }, - { -1, 50, { 2417, 4056, 639 } } -}; - -struct CutsceneSplinePoint sWdwCreditsSplinePositions[] = { - { 0, 0, { 3927, 2573, 3685 } }, - { 0, 0, { 2389, 2054, 1210 } }, - { 0, 0, { 2309, 2069, 22 } }, - { -1, 0, { 2122, 2271, -979 } } -}; - -struct CutsceneSplinePoint sWdwCreditsSplineFocus[] = { - { 0, 50, { 3637, 2460, 3294 } }, - { 0, 50, { 1984, 2067, 918 } }, - { 0, 50, { 1941, 2255, -261 } }, - { -1, 50, { 1779, 2587, -1158 } } -}; - -struct CutsceneSplinePoint sTtmCreditsSplinePositions[] = { - { 0, 0, { 386, 2535, 644 } }, - { 0, 0, { 1105, 2576, 918 } }, - { 0, 0, { 3565, 2261, 2098 } }, - { 0, 0, { 6715, -2791, 4554 } }, - { 0, 0, { 3917, -3130, 3656 } }, - { -1, 0, { 3917, -3130, 3656 } } -}; - -struct CutsceneSplinePoint sTtmCreditsSplineFocus[] = { - { 1, 50, { 751, 2434, 318 } }, - { 2, 50, { 768, 2382, 603 } }, - { 3, 60, { 3115, 2086, 1969 } }, - { 4, 30, { 6370, -3108, 4727 } }, - { 5, 50, { 4172, -3385, 4001 } }, - { -1, 50, { 4172, -3385, 4001 } } -}; - -struct CutsceneSplinePoint sThiHugeCreditsSplinePositions[] = { - { 0, 0, { 6990, -1000, -4858 } }, - { 0, 0, { 7886, -1055, 2878 } }, - { 0, 0, { 1952, -1481, 10920 } }, - { 0, 0, { -1684, -219, 2819 } }, - { 0, 0, { -2427, -131, 2755 } }, - { 0, 0, { -3246, 416, 3286 } }, - { -1, 0, { -3246, 416, 3286 } } -}; - -struct CutsceneSplinePoint sThiHugeCreditsSplineFocus[] = { - { 1, 70, { 7022, -965, -5356 } }, - { 2, 40, { 7799, -915, 2405 } }, - { 3, 60, { 1878, -1137, 10568 } }, - { 4, 50, { -1931, -308, 2394 } }, - { 5, 50, { -2066, -386, 2521 } }, - { 6, 50, { -2875, 182, 3045 } }, - { -1, 50, { -2875, 182, 3045 } } -}; - -struct CutsceneSplinePoint sTtcCreditsSplinePositions[] = { - { 1, 0, { -1724, 277, -994 } }, - { 2, 0, { -1720, 456, -995 } }, - { 3, 0, { -1655, 810, -1014 } }, - { -1, 0, { -1753, 883, -1009 } } -}; - -struct CutsceneSplinePoint sTtcCreditsSplineFocus[] = { - { 1, 50, { -1554, 742, -1063 } }, - { 2, 50, { -1245, 571, -1102 } }, - { 3, 50, { -1220, 603, -1151 } }, - { -1, 50, { -1412, 520, -1053 } } -}; - -struct CutsceneSplinePoint sRrCreditsSplinePositions[] = { - { 0, 0, { -1818, 4036, 97 } }, - { 0, 0, { -575, 3460, -505 } }, - { 0, 0, { 1191, 3611, -1134 } }, - { -1, 0, { 2701, 3777, -3686 } } -}; - -struct CutsceneSplinePoint sRrCreditsSplineFocus[] = { - { 0, 50, { -1376, 3885, -81 } }, - { 0, 50, { -146, 3343, -734 } }, - { 0, 50, { 1570, 3446, -1415 } }, - { -1, 50, { 2794, 3627, -3218 } } -}; - -struct CutsceneSplinePoint sSaCreditsSplinePositions[] = { - { 0, 0, { -295, -396, -585 } }, - { 1, 0, { -295, -396, -585 } }, - { 2, 0, { -292, -856, -573 } }, - { 3, 0, { -312, -856, -541 } }, - { -1, 0, { 175, -856, -654 } } -}; - -struct CutsceneSplinePoint sSaCreditsSplineFocus[] = { - { 0, 50, { -175, -594, -142 } }, - { 1, 50, { -175, -594, -142 } }, - { 2, 50, { -195, -956, -92 } }, - { 3, 50, { -572, -956, -150 } }, - { -1, 50, { -307, -956, -537 } } -}; - -struct CutsceneSplinePoint sCotmcCreditsSplinePositions[] = { - { 0, 0, { -296, 495, 1607 } }, - { 0, 0, { -430, 541, 654 } }, - { 0, 0, { -466, 601, -359 } }, - { 0, 0, { -217, 433, -1549 } }, - { -1, 0, { -95, 366, -2922 } } -}; - -struct CutsceneSplinePoint sCotmcCreditsSplineFocus[] = { - { 0, 50, { -176, 483, 2092 } }, - { 0, 50, { -122, 392, 1019 } }, - { 0, 50, { -268, 450, -792 } }, - { 0, 50, { -172, 399, -2046 } }, - { -1, 50, { -51, 355, -3420 } } -}; - -struct CutsceneSplinePoint sDddSubCreditsSplinePositions[] = { - { 0, 0, { 4656, 2171, 5028 } }, - { 0, 0, { 4548, 1182, 4596 } }, - { 0, 0, { 5007, 813, 3257 } }, - { 0, 0, { 5681, 648, 1060 } }, - { -1, 0, { 4644, 774, 113 } } -}; - -struct CutsceneSplinePoint sDddSubCreditsSplineFocus[] = { - { 0, 50, { 4512, 2183, 4549 } }, - { 0, 50, { 4327, 838, 4308 } }, - { 0, 50, { 4774, 749, 2819 } }, - { 0, 50, { 5279, 660, 763 } }, - { -1, 50, { 4194, 885, -75 } } -}; - -struct CutsceneSplinePoint sCcmOutsideCreditsSplinePositions[] = { - { 1, 0, { 1427, -1387, 5409 } }, - { 2, 0, { -1646, -1536, 4526 } }, - { 3, 0, { -3852, -1448, 3913 } }, - { -1, 0, { -5199, -1366, 1886 } } -}; - -struct CutsceneSplinePoint sCcmOutsideCreditsSplineFocus[] = { - { 1, 50, { 958, -1481, 5262 } }, - { 2, 50, { -2123, -1600, 4391 } }, - { 3, 50, { -3957, -1401, 3426 } }, - { -1, 50, { -4730, -1215, 1795 } } -}; - -/** - * Play the current cutscene until either gCutsceneTimer reaches the max time, or c->cutscene is set to 0 - * - * Note that CAM_FLAG_SMOOTH_MOVEMENT is cleared while a cutscene is playing, so cutscenes set it for - * the duration they want the flag to be active. - */ -void play_cutscene(struct Camera *c) { - s16 cutsceneDuration; - u8 oldCutscene = c->cutscene; - - sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; - gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE; - -#define CUTSCENE(id, cutscene) \ - case id: \ - cutsceneDuration = cutscene[sCutsceneShot].duration; \ - cutscene[sCutsceneShot].shot(c); \ - break; - - switch (c->cutscene) { - CUTSCENE(CUTSCENE_STAR_SPAWN, sCutsceneStarSpawn) - CUTSCENE(CUTSCENE_RED_COIN_STAR_SPAWN, sCutsceneRedCoinStarSpawn) - CUTSCENE(CUTSCENE_ENDING, sCutsceneEnding) - CUTSCENE(CUTSCENE_GRAND_STAR, sCutsceneGrandStar) - CUTSCENE(CUTSCENE_DOOR_WARP, sCutsceneDoorWarp) - CUTSCENE(CUTSCENE_DOOR_PULL, sCutsceneDoorPull) - CUTSCENE(CUTSCENE_DOOR_PUSH, sCutsceneDoorPush) - CUTSCENE(CUTSCENE_DOOR_PULL_MODE, sCutsceneDoorPullMode) - CUTSCENE(CUTSCENE_DOOR_PUSH_MODE, sCutsceneDoorPushMode) - CUTSCENE(CUTSCENE_ENTER_CANNON, sCutsceneEnterCannon) - CUTSCENE(CUTSCENE_ENTER_PAINTING, sCutsceneEnterPainting) - CUTSCENE(CUTSCENE_DEATH_EXIT, sCutsceneDeathExit) - CUTSCENE(CUTSCENE_EXIT_PAINTING_SUCC, sCutsceneExitPaintingSuccess) - CUTSCENE(CUTSCENE_UNUSED_EXIT, sCutsceneUnusedExit) - CUTSCENE(CUTSCENE_INTRO_PEACH, sCutsceneIntroPeach) - CUTSCENE(CUTSCENE_ENTER_BOWSER_ARENA, sCutsceneEnterBowserArena) - CUTSCENE(CUTSCENE_DANCE_ROTATE, sCutsceneDanceDefaultRotate) - CUTSCENE(CUTSCENE_DANCE_DEFAULT, sCutsceneDanceDefaultRotate) - CUTSCENE(CUTSCENE_DANCE_FLY_AWAY, sCutsceneDanceFlyAway) - CUTSCENE(CUTSCENE_DANCE_CLOSEUP, sCutsceneDanceCloseup) - CUTSCENE(CUTSCENE_KEY_DANCE, sCutsceneKeyDance) - CUTSCENE(CUTSCENE_0F_UNUSED, sCutsceneUnused) - CUTSCENE(CUTSCENE_END_WAVING, sCutsceneEndWaving) - CUTSCENE(CUTSCENE_CREDITS, sCutsceneCredits) - CUTSCENE(CUTSCENE_CAP_SWITCH_PRESS, sCutsceneCapSwitchPress) - CUTSCENE(CUTSCENE_SLIDING_DOORS_OPEN, sCutsceneSlidingDoorsOpen) - CUTSCENE(CUTSCENE_PREPARE_CANNON, sCutscenePrepareCannon) - CUTSCENE(CUTSCENE_UNLOCK_KEY_DOOR, sCutsceneUnlockKeyDoor) - CUTSCENE(CUTSCENE_STANDING_DEATH, sCutsceneStandingDeath) - CUTSCENE(CUTSCENE_ENTER_POOL, sCutsceneEnterPool) - CUTSCENE(CUTSCENE_DEATH_ON_STOMACH, sCutsceneDeathStomach) - CUTSCENE(CUTSCENE_DEATH_ON_BACK, sCutsceneDeathOnBack) - CUTSCENE(CUTSCENE_QUICKSAND_DEATH, sCutsceneQuicksandDeath) - CUTSCENE(CUTSCENE_SUFFOCATION_DEATH, sCutsceneSuffocation) - CUTSCENE(CUTSCENE_EXIT_BOWSER_SUCC, sCutsceneExitBowserSuccess) - CUTSCENE(CUTSCENE_EXIT_BOWSER_DEATH, sCutsceneExitBowserDeath) - CUTSCENE(CUTSCENE_EXIT_SPECIAL_SUCC, sCutsceneExitSpecialSuccess) - CUTSCENE(CUTSCENE_EXIT_WATERFALL, sCutsceneExitWaterfall) - CUTSCENE(CUTSCENE_EXIT_FALL_WMOTR, sCutsceneFallToCastleGrounds) - CUTSCENE(CUTSCENE_NONPAINTING_DEATH, sCutsceneNonPaintingDeath) - CUTSCENE(CUTSCENE_DIALOG, sCutsceneDialog) - CUTSCENE(CUTSCENE_READ_MESSAGE, sCutsceneReadMessage) - CUTSCENE(CUTSCENE_RACE_DIALOG, sCutsceneDialog) - CUTSCENE(CUTSCENE_ENTER_PYRAMID_TOP, sCutsceneEnterPyramidTop) - CUTSCENE(CUTSCENE_SSL_PYRAMID_EXPLODE, sCutscenePyramidTopExplode) - } - -#undef CUTSCENE - - if ((cutsceneDuration != 0) && !(gCutsceneTimer & CUTSCENE_STOP)) { - if (gCutsceneTimer < CUTSCENE_LOOP) { - gCutsceneTimer++; - } - //! Because gCutsceneTimer is often set to 0x7FFF (CUTSCENE_LOOP), this conditional can only - //! check for == due to overflow - if (gCutsceneTimer == cutsceneDuration) { - sCutsceneShot++; - gCutsceneTimer = 0; - } - } else { - sMarioCamState->cameraEvent = CAM_EVENT_NONE; - sCutsceneShot = 0; - gCutsceneTimer = 0; - } - - sAreaYawChange = 0; - - // The cutscene just ended - if ((c->cutscene == CUTSCENE_NONE) && (oldCutscene != 0)) { - gRecentCutscene = oldCutscene; - } -} - -/** - * Call the event while `start` <= gCutsceneTimer <= `end` - * If `end` is -1, call for the rest of the shot. - */ -void cutscene_event(CameraEvent event, struct Camera *c, s16 start, s16 end) { - if (start <= gCutsceneTimer) { - if (end == -1 || end >= gCutsceneTimer) { - event(c); - } - } -} - -/** - * Set gCutsceneObjSpawn when gCutsceneTimer == `frame`. - * - * @see intro_scene.inc.c for details on which objects are spawned. - */ -void cutscene_spawn_obj(u32 obj, s16 frame) { - if (frame == gCutsceneTimer) { - gCutsceneObjSpawn = obj; - } -} - /** * Start shaking the camera's field of view. * @@ -10887,180 +2112,17 @@ void set_fov_shake_from_point(s16 amplitude, s16 decay, s16 shakeSpeed, f32 maxD } } -/** - * Add a cyclic offset to the camera's field of view based on a cosine wave - */ -void shake_camera_fov(struct GraphNodePerspective *perspective) { - if (sFOVState.shakeAmplitude != 0.f) { - sFOVState.fovOffset = coss(sFOVState.shakePhase) * sFOVState.shakeAmplitude / 0x100; - sFOVState.shakePhase += sFOVState.shakeSpeed; - camera_approach_f32_symmetric_bool(&sFOVState.shakeAmplitude, 0.f, sFOVState.decay); - perspective->fov += sFOVState.fovOffset; - } else { - sFOVState.shakePhase = 0; - } -} - static UNUSED void unused_deactivate_sleeping_camera(UNUSED struct MarioState *m) { sStatusFlags &= ~CAM_FLAG_SLEEPING; } -void set_fov_30(UNUSED struct MarioState *m) { - sFOVState.fov = 30.f; -} - -void approach_fov_20(UNUSED struct MarioState *m) { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 20.f, 0.3f); -} - -void set_fov_45(UNUSED struct MarioState *m) { - sFOVState.fov = 45.f; -} - -void set_fov_29(UNUSED struct MarioState *m) { - sFOVState.fov = 29.f; -} - -void zoom_fov_30(UNUSED struct MarioState *m) { - // Pretty sure approach_f32_asymptotic_bool would do a much nicer job here, but you do you, - // Nintendo. - camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, (30.f - sFOVState.fov) / 60.f); -} - -/** - * This is the default fov function. It makes fov approach 45 degrees, and it handles zooming in when - * Mario falls a sleep. - */ -void fov_default(struct MarioState *m) { - sStatusFlags &= ~CAM_FLAG_SLEEPING; - - if ((m->action == ACT_SLEEPING) || (m->action == ACT_START_SLEEPING)) { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, (30.f - sFOVState.fov) / 30.f); - sStatusFlags |= CAM_FLAG_SLEEPING; - } else { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 45.f, (45.f - sFOVState.fov) / 30.f); - sFOVState.unusedIsSleeping = 0; - } - if (m->area->camera->cutscene == CUTSCENE_0F_UNUSED) { - sFOVState.fov = 45.f; - } -} - -void approach_fov_30(UNUSED struct MarioState *m) { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 30.f, 1.f); -} - -void approach_fov_60(UNUSED struct MarioState *m) { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 60.f, 1.f); -} - -void approach_fov_45(struct MarioState *m) { - f32 targetFoV = sFOVState.fov; - - if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) { - targetFoV = 45.f; - } else { - targetFoV = 45.f; - } - - sFOVState.fov = approach_f32(sFOVState.fov, targetFoV, 2.f, 2.f); -} - -void approach_fov_80(UNUSED struct MarioState *m) { - camera_approach_f32_symmetric_bool(&sFOVState.fov, 80.f, 3.5f); -} - -/** - * Sets the fov in BBH. - * If there's a cutscene, sets fov to 45. Otherwise sets fov to 60. - */ -void set_fov_bbh(struct MarioState *m) { - f32 targetFoV = sFOVState.fov; - - if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) { - targetFoV = 60.f; - } else { - targetFoV = 45.f; - } - - sFOVState.fov = approach_f32(sFOVState.fov, targetFoV, 2.f, 2.f); -} - -/** - * Sets the field of view for the GraphNodeCamera - */ -Gfx *geo_camera_fov(s32 callContext, struct GraphNode *g, UNUSED void *context) { - struct GraphNodePerspective *perspective = (struct GraphNodePerspective *) g; - struct MarioState *marioState = &gMarioStates[0]; - u8 fovFunc = sFOVState.fovFunc; - - if (callContext == GEO_CONTEXT_RENDER) { - switch (fovFunc) { - case CAM_FOV_SET_45: - set_fov_45(marioState); - break; - case CAM_FOV_SET_29: - set_fov_29(marioState); - break; - case CAM_FOV_ZOOM_30: - zoom_fov_30(marioState); - break; - case CAM_FOV_DEFAULT: - fov_default(marioState); - break; - case CAM_FOV_BBH: - set_fov_bbh(marioState); - break; - case CAM_FOV_APP_45: - approach_fov_45(marioState); - break; - case CAM_FOV_SET_30: - set_fov_30(marioState); - break; - case CAM_FOV_APP_20: - approach_fov_20(marioState); - break; - case CAM_FOV_APP_80: - approach_fov_80(marioState); - break; - case CAM_FOV_APP_30: - approach_fov_30(marioState); - break; - case CAM_FOV_APP_60: - approach_fov_60(marioState); - break; - default: - set_fov_45(marioState); - break; - } - } - - perspective->fov = sFOVState.fov; - shake_camera_fov(perspective); - return NULL; -} - /** * Change the camera's FOV mode. - * - * @see geo_camera_fov - */ -void set_fov_function(u8 func) { - sFOVState.fovFunc = func; -} - -/** - * Start a preset fov shake. Used in cutscenes - */ -void cutscene_set_fov_shake_preset(u8 preset) { - switch (preset) { - case 1: - set_fov_shake(0x100, 0x30, 0x8000); - break; - case 2: - set_fov_shake(0x400, 0x20, 0x4000); - break; - } + * + * @see geo_camera_fov + */ +void set_fov_function(u8 func) { + sFOVState.fovFunc = func; } /** @@ -11121,6 +2183,257 @@ void obj_rotate_towards_point(struct Object *obj, Vec3f point, s16 pitchOff, s16 obj->oMoveAngleYaw = approach_s16_asymptotic(obj->oMoveAngleYaw, yaw + yawOff, yawDiv); } +/** + * The main camera update function. + * Gets controller input, checks for cutscenes, handles mode changes, and moves the camera + */ +void update_camera(struct Camera *c) { + PROFILER_GET_SNAPSHOT_TYPE(PROFILER_DELTA_COLLISION); + gCamera = c; + update_camera_hud_status(c); + if (c->cutscene == CUTSCENE_NONE +#ifdef PUPPYCAM + && !gPuppyCam.enabled +#endif + && gCurrentArea->camera->mode != CAMERA_MODE_INSIDE_CANNON) { + // Only process R_TRIG if 'fixed' is not selected in the menu + if (cam_select_alt_mode(CAM_SELECTION_NONE) == CAM_SELECTION_MARIO) { + if (gPlayer1Controller->buttonPressed & R_TRIG) { + if (set_cam_angle(0) == CAM_ANGLE_LAKITU) { + set_cam_angle(CAM_ANGLE_MARIO); + } else { + set_cam_angle(CAM_ANGLE_LAKITU); + } + } + } + play_sound_if_cam_switched_to_lakitu_or_mario(); + } + + // Initialize the camera + sStatusFlags &= ~CAM_FLAG_FRAME_AFTER_CAM_INIT; + if (gCameraMovementFlags & CAM_MOVE_INIT_CAMERA) { + init_camera(c); + gCameraMovementFlags &= ~CAM_MOVE_INIT_CAMERA; + sStatusFlags |= CAM_FLAG_FRAME_AFTER_CAM_INIT; + } + +#ifdef PUPPYCAM + if (!gPuppyCam.enabled || c->cutscene != CUTSCENE_NONE || gCurrentArea->camera->mode == CAMERA_MODE_INSIDE_CANNON) { +#endif + // Store previous geometry information + sMarioGeometry.prevFloorHeight = sMarioGeometry.currFloorHeight; + sMarioGeometry.prevCeilHeight = sMarioGeometry.currCeilHeight; + sMarioGeometry.prevFloor = sMarioGeometry.currFloor; + sMarioGeometry.prevCeil = sMarioGeometry.currCeil; + sMarioGeometry.prevFloorType = sMarioGeometry.currFloorType; + sMarioGeometry.prevCeilType = sMarioGeometry.currCeilType; + + find_mario_floor_and_ceil(&sMarioGeometry); + gCollisionFlags |= COLLISION_FLAG_CAMERA; + vec3f_copy(c->pos, gLakituState.goalPos); + vec3f_copy(c->focus, gLakituState.goalFocus); + + c->yaw = gLakituState.yaw; + c->nextYaw = gLakituState.nextYaw; + c->mode = gLakituState.mode; + c->defMode = gLakituState.defMode; +#ifdef ENABLE_VANILLA_CAM_PROCESSING + camera_course_processing(c); +#else + if (gCurrDemoInput != NULL) camera_course_processing(c); +#endif + sCButtonsPressed = find_c_buttons_pressed(sCButtonsPressed, gPlayer1Controller->buttonPressed, gPlayer1Controller->buttonDown); + + if (c->cutscene != CUTSCENE_NONE) { + sYawSpeed = 0; + play_cutscene(c); + sFramesSinceCutsceneEnded = 0; + } else { + // Clear the recent cutscene after 8 frames + if (gRecentCutscene != CUTSCENE_NONE && sFramesSinceCutsceneEnded < 8) { + sFramesSinceCutsceneEnded++; + if (sFramesSinceCutsceneEnded >= 8) { + gRecentCutscene = CUTSCENE_NONE; + sFramesSinceCutsceneEnded = 0; + } + } + } + // If not in a cutscene, do mode processing + if (c->cutscene == CUTSCENE_NONE) { + sYawSpeed = 0x400; + + if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { + switch (c->mode) { + case CAMERA_MODE_BEHIND_MARIO: + mode_behind_mario_camera(c); + break; + + case CAMERA_MODE_C_UP: + mode_c_up_camera(c); + break; + + case CAMERA_MODE_WATER_SURFACE: + mode_water_surface_camera(c); + break; + + case CAMERA_MODE_INSIDE_CANNON: + mode_cannon_camera(c); + break; + + default: + mode_mario_camera(c); + } + } else { + switch (c->mode) { + case CAMERA_MODE_BEHIND_MARIO: + mode_behind_mario_camera(c); + break; + + case CAMERA_MODE_C_UP: + mode_c_up_camera(c); + break; + + case CAMERA_MODE_WATER_SURFACE: + mode_water_surface_camera(c); + break; + + case CAMERA_MODE_INSIDE_CANNON: + mode_cannon_camera(c); + break; + + case CAMERA_MODE_8_DIRECTIONS: + mode_8_directions_camera(c); + break; + + case CAMERA_MODE_RADIAL: + mode_radial_camera(c); + break; + + case CAMERA_MODE_OUTWARD_RADIAL: + mode_outward_radial_camera(c); + break; + + case CAMERA_MODE_CLOSE: + mode_lakitu_camera(c); + break; + + case CAMERA_MODE_FREE_ROAM: + mode_lakitu_camera(c); + break; + + case CAMERA_MODE_BOSS_FIGHT: + mode_boss_fight_camera(c); + break; + + case CAMERA_MODE_PARALLEL_TRACKING: + mode_parallel_tracking_camera(c); + break; + + case CAMERA_MODE_SLIDE_HOOT: + mode_slide_camera(c); + break; + + case CAMERA_MODE_FIXED: + mode_fixed_camera(c); + break; + + case CAMERA_MODE_SPIRAL_STAIRS: + mode_spiral_stairs_camera(c); + break; + } + } + } +#ifdef PUPPYCAM + } +#endif + // Start any Mario-related cutscenes + start_cutscene(c, get_cutscene_from_mario_status(c)); + gCollisionFlags &= ~COLLISION_FLAG_CAMERA; +#ifdef PUPPYCAM + if (!gPuppyCam.enabled || c->cutscene != 0 || gCurrentArea->camera->mode == CAMERA_MODE_INSIDE_CANNON) { +#endif +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (gCurrLevelNum != LEVEL_CASTLE) { +#endif + // If fixed camera is selected as the alternate mode, then fix the camera as long as the right + // trigger is held + if ((c->cutscene == CUTSCENE_NONE && + (gPlayer1Controller->buttonDown & R_TRIG) && cam_select_alt_mode(0) == CAM_SELECTION_FIXED) + || (gCameraMovementFlags & CAM_MOVE_FIX_IN_PLACE) + || (sMarioCamState->action) == ACT_GETTING_BLOWN) { + + // If this is the first frame that R_TRIG is held, play the "click" sound + if (c->cutscene == CUTSCENE_NONE && (gPlayer1Controller->buttonPressed & R_TRIG) + && cam_select_alt_mode(0) == CAM_SELECTION_FIXED) { + sCameraSoundFlags |= CAM_SOUND_FIXED_ACTIVE; + play_sound_rbutton_changed(); + } + + // Fixed mode only prevents Lakitu from moving. The camera pos still updates, so + // Lakitu will fly to his next position as normal whenever R_TRIG is released. + gLakituState.posHSpeed = 0.f; + gLakituState.posVSpeed = 0.f; + + vec3f_get_yaw(gLakituState.focus, gLakituState.pos, &c->nextYaw); + c->yaw = c->nextYaw; + gCameraMovementFlags &= ~CAM_MOVE_FIX_IN_PLACE; + } else { + // Play the "click" sound when fixed mode is released + if (sCameraSoundFlags & CAM_SOUND_FIXED_ACTIVE) { + play_sound_rbutton_changed(); + sCameraSoundFlags &= ~CAM_SOUND_FIXED_ACTIVE; + } + } +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + } else { + if ((gPlayer1Controller->buttonPressed & R_TRIG) && (cam_select_alt_mode(0) == CAM_SELECTION_FIXED)) { + play_sound_button_change_blocked(); + } + } +#endif + + update_lakitu(c); +#ifdef PUPPYCAM + } + // Just a cute little bit that syncs puppycamera up to vanilla when playing a vanilla cutscene :3 + if (c->cutscene != CUTSCENE_NONE) { + gPuppyCam.yawTarget = gCamera->yaw; + gPuppyCam.yaw = gCamera->yaw; + if (gMarioState->action == ACT_ENTERING_STAR_DOOR) { // god this is stupid and the fact I have to continue doing this is testament to the idiocy of the star door cutscene >:( + gPuppyCam.yawTarget = gMarioState->faceAngle[1] + 0x8000; + gPuppyCam.yaw = gMarioState->faceAngle[1] + 0x8000; + } + } + if (c->cutscene == CUTSCENE_NONE + && gPuppyCam.enabled + && gCurrentArea->camera->mode != CAMERA_MODE_INSIDE_CANNON) { + // Clear the recent cutscene after 8 frames + if (gRecentCutscene != CUTSCENE_NONE && sFramesSinceCutsceneEnded < 8) { + sFramesSinceCutsceneEnded++; + if (sFramesSinceCutsceneEnded >= 8) { + gRecentCutscene = CUTSCENE_NONE; + sFramesSinceCutsceneEnded = 0; + } + } + puppycam_loop(); + // Apply camera shakes + shake_camera_pitch(gLakituState.pos, gLakituState.focus); + shake_camera_yaw(gLakituState.pos, gLakituState.focus); + shake_camera_roll(&gLakituState.roll); + shake_camera_handheld(gLakituState.pos, gLakituState.focus); + + if ((sMarioCamState->action == ACT_DIVE) + && (gLakituState.lastFrameAction != ACT_DIVE)) { + set_camera_shake_from_hit(SHAKE_HIT_FROM_BELOW); + } + gLakituState.roll += sHandheldShakeRoll; + gLakituState.roll += gLakituState.keyDanceRoll; + } +#endif + gLakituState.lastFrameAction = sMarioCamState->action; + profiler_update(PROFILER_TIME_CAMERA, profiler_get_delta(PROFILER_DELTA_COLLISION) - first); +} + #include "behaviors/intro_peach.inc.c" #include "behaviors/intro_lakitu.inc.c" #include "behaviors/end_birds_1.inc.c" diff --git a/src/game/camera.h b/src/game/camera.h index fcd1f59f97..b9231b21ee 100644 --- a/src/game/camera.h +++ b/src/game/camera.h @@ -8,18 +8,58 @@ #include "engine/geo_layout.h" #include "engine/graph_node.h" #include "puppycam2.h" +#include "camera/camera_geo.h" #include "level_table.h" -// X position of the mirror -#define CASTLE_MIRROR_X 4331.53f - /** * @file camera.h * Constants, defines, and structs used by the camera system. - * @see camera.c + * + * When working with the camera, you should be familiar with sm64's coordinate system. + * Relative to the camera, the coordinate system follows the right hand rule: + * +X points right. + * +Y points up. + * +Z points out of the screen. + * + * You should also be familiar with Euler angles: 'pitch', 'yaw', and 'roll'. + * pitch: rotation about the X-axis, measured from +Y. + * Unlike yaw and roll, pitch is bounded in +-0x4000 (90 degrees). + * Pitch is 0 when the camera points parallel to the xz-plane (+Y points straight up). + * + * yaw: rotation about the Y-axis, measured from (absolute) +Z. + * Positive yaw rotates clockwise, towards +X. + * + * roll: rotation about the Z-axis, measured from the camera's right direction. + * Unfortunately, it's weird: For some reason, roll is flipped. Positive roll makes the camera + * rotate counterclockwise, which means the WORLD rotates clockwise. Luckily roll is rarely + * used. + * + * Remember the right hand rule: make a thumbs-up with your right hand, stick your thumb in the + * +direction (except for roll), and the angle follows the rotation of your curled fingers. + * + * Illustrations: + * Following the right hand rule, each hidden axis's positive direction points out of the screen. + * + * YZ-Plane (pitch) XZ-Plane (yaw) XY-Plane (roll -- Note flipped) + * +Y -Z +Y + * ^ ^ (into the ^ + * --|-- | screen) |<- + * +pitch / | \ -pitch | | \ -roll + * v | v | | | + * +Z <------O------> -Z -X <------O------> +X -X <------O------> +X + * | ^ | ^ | | + * | \ | / | / +roll + * | -yaw --|-- +yaw |<- + * v v v + * -Y +Z -Y + * */ + +// X position of the mirror +#define CASTLE_MIRROR_X 4331.53f + #ifndef ABS2 #define ABS2(x) ((x) >= 0.f ? (x) : -(x)) #endif @@ -312,6 +352,7 @@ enum CameraDoor { }; // Might rename these to reflect what they are used for instead "SET_45" etc. +// TODO: to camera_geo enum CameraFov { CAM_FOV_NONE, CAM_FOV_SET_45, @@ -405,14 +446,6 @@ struct HandheldShakePoint { * These are concurrent: multiple CameraEvents can occur on the same frame. */ typedef void (*CameraEvent)(struct Camera *c); -/** - * The same type as a CameraEvent, but because these are generally longer, and happen in sequential - * order, they're are called "shots," a term taken from cinematography. - * - * To further tell the difference: CutsceneShots usually call multiple CameraEvents at once, but only - * one CutsceneShot is ever called on a given frame. - */ -typedef CameraEvent CutsceneShot; /** * Defines a bounding box which activates an event while Mario is inside @@ -441,19 +474,9 @@ struct CameraTrigger { s16 boundsYaw; }; -/** - * A camera shot that is active for a number of frames. - * Together, a sequence of shots makes up a cutscene. - */ -struct Cutscene { - /// The function that gets called. - CutsceneShot shot; - /// How long the shot lasts. - s16 duration; -}; - /** * Info for the camera's field of view and the FOV shake effect. + * TODO: to camera_geo */ struct CameraFOVStatus { /// The current function being used to set the camera's field of view (before any fov shake is applied). @@ -533,20 +556,6 @@ struct ModeTransitionInfo { struct LinearTransitionPoint transitionEnd; }; -/** - * A point in a path used by update_parallel_tracking_camera - */ -struct ParallelTrackingPoint { - /// Whether this point is the start of a path - s16 startOfPath; - /// Point used to define a line segment to follow - Vec3f pos; - /// The distance Mario can move along the line before the camera should move - f32 distThresh; - /// The percentage that the camera should move from the line to Mario - f32 zoom; -}; - /** * Stores the camera's info */ @@ -636,15 +645,11 @@ struct LakituState { */ /*0x24*/ Vec3f goalPos; - /*0x30*/ u8 filler1[12]; // extra unused Vec3f? - /// Copy of the active camera mode /*0x3C*/ u8 mode; /// Copy of the default camera mode /*0x3D*/ u8 defMode; - /*0x3E*/ u8 filler2[10]; - /*0x48*/ f32 focusDistance; // unused /*0x4C*/ s16 oldPitch; // unused /*0x4E*/ s16 oldYaw; // unused @@ -660,10 +665,6 @@ struct LakituState { /*0x5A*/ s16 shakePitchVel; /*0x5C*/ s16 shakePitchDecay; - /*0x60*/ Vec3f unusedVec1; - /*0x6C*/ Vec3s unusedVec2; - /*0x72*/ u8 filler3[8]; - /// Used to rotate the screen when rendering. /*0x7A*/ s16 roll; /// Copy of the camera's yaw. @@ -698,10 +699,8 @@ struct LakituState { /*0xB4*/ s16 keyDanceRoll; /// Mario's action from the previous frame. Only used to determine if Mario just finished a dive. /*0xB8*/ u32 lastFrameAction; - /*0xBC*/ s16 unused; }; -// BSS extern s16 sSelectionFlags; extern s16 sCameraSoundFlags; extern u16 sCButtonsPressed; @@ -710,54 +709,29 @@ extern struct LakituState gLakituState; extern s16 gCameraMovementFlags; extern s32 gObjCutsceneDone; extern struct Camera *gCamera; -extern struct Object *gCutsceneFocus; extern struct Object *gSecondCameraFocus; -extern u8 gRecentCutscene; // TODO: sort all of this extremely messy shit out after the split -void set_camera_shake_from_hit(s16 shake); -void set_environmental_camera_shake(s16 shake); -void set_camera_shake_from_point(s16 shake, f32 posX, f32 posY, f32 posZ); -void move_mario_head_c_up(UNUSED struct Camera *c); void transition_next_state(UNUSED struct Camera *c, s16 frames); -void set_camera_mode(struct Camera *c, s16 mode, s16 frames); void update_camera(struct Camera *c); void reset_camera(struct Camera *c); void init_camera(struct Camera *c); void select_mario_cam_mode(void); -Gfx *geo_camera_main(s32 callContext, struct GraphNode *g, void *context); void object_pos_to_vec3f(Vec3f dst, struct Object *obj); void vec3f_to_object_pos(struct Object *obj, Vec3f src); -s32 move_point_along_spline(Vec3f p, struct CutsceneSplinePoint spline[], s16 *splineSegment, f32 *progress); s32 cam_select_alt_mode(s32 angle); s32 set_cam_angle(s32 mode); -void set_handheld_shake(u8 mode); -void shake_camera_handheld(Vec3f pos, Vec3f focus); s32 find_c_buttons_pressed(u16 currentState, u16 buttonsPressed, u16 buttonsDown); s32 update_camera_hud_status(struct Camera *c); s32 collide_with_walls(Vec3f pos, f32 offsetY, f32 radius); void clamp_pitch(Vec3f from, Vec3f to, s16 maxPitch, s16 minPitch); s32 is_within_100_units_of_mario(f32 posX, f32 posY, f32 posZ); -s32 set_or_approach_f32_asymptotic(f32 *dst, f32 goal, f32 scale); -void approach_vec3f_asymptotic(Vec3f current, Vec3f target, f32 xMul, f32 yMul, f32 zMul); -void set_or_approach_vec3f_asymptotic(Vec3f dst, Vec3f goal, f32 xMul, f32 yMul, f32 zMul); -s32 camera_approach_s16_symmetric_bool(s16 *current, s16 target, s16 increment); -s32 set_or_approach_s16_symmetric(s16 *current, s16 target, s16 increment); -s32 camera_approach_f32_symmetric_bool(f32 *current, f32 target, f32 increment); -f32 camera_approach_f32_symmetric(f32 value, f32 target, f32 increment); -void random_vec3s(Vec3s dst, s16 xRange, s16 yRange, s16 zRange); -s32 clamp_positions_and_find_yaw(Vec3f pos, Vec3f origin, f32 xMax, f32 xMin, f32 zMax, f32 zMin); -s32 is_range_behind_surface(Vec3f from, Vec3f to, struct Surface *surf, s16 range, s16 surfType); -void scale_along_line(Vec3f dest, Vec3f from, Vec3f to, f32 scale); -s16 calculate_pitch(Vec3f from, Vec3f to); -s16 calculate_yaw(Vec3f from, Vec3f to); -void calculate_angles(Vec3f from, Vec3f to, s16 *pitch, s16 *yaw); -f32 calc_abs_dist(Vec3f a, Vec3f b); -f32 calc_abs_dist_squared(Vec3f a, Vec3f b); -f32 calc_hor_dist(Vec3f a, Vec3f b); -void rotate_in_xz(Vec3f dst, Vec3f src, s16 yaw); -void rotate_in_yz(Vec3f dst, Vec3f src, s16 pitch); +void set_camera_shake_from_hit(s16 shake); +void set_environmental_camera_shake(s16 shake); +void set_camera_shake_from_point(s16 shake, f32 posX, f32 posY, f32 posZ); +void set_handheld_shake(u8 mode); +void shake_camera_handheld(Vec3f pos, Vec3f focus); void set_camera_pitch_shake(s16 mag, s16 decay, s16 inc); void set_camera_yaw_shake(s16 mag, s16 decay, s16 inc); void set_camera_roll_shake(s16 mag, s16 decay, s16 inc); @@ -775,34 +749,95 @@ void play_sound_cbutton_side(void); void play_sound_button_change_blocked(void); void play_sound_rbutton_changed(void); void play_sound_if_cam_switched_to_lakitu_or_mario(void); -void radial_camera_input(struct Camera *c); -void trigger_cutscene_dialog(s32 trigger); -void handle_c_button_movement(struct Camera *c); -void start_cutscene(struct Camera *c, u8 cutscene); -u8 get_cutscene_from_mario_status(struct Camera *c); +void pan_camera(struct Camera *c, s16 incPitch, s16 incYaw); void warp_camera(f32 displacementX, f32 displacementY, f32 displacementZ); void approach_camera_height(struct Camera *c, f32 goal, f32 inc); void offset_rotated(Vec3f dst, Vec3f from, Vec3f to, Vec3s rotation); s16 next_lakitu_state(Vec3f newPos, Vec3f newFoc, Vec3f curPos, Vec3f curFoc, Vec3f oldPos, Vec3f oldFoc, s16 yaw); void set_fixed_cam_axis_sa_lobby(UNUSED s16 preset); s16 camera_course_processing(struct Camera *c); -void resolve_geometry_collisions(Vec3f pos); -s32 rotate_camera_around_walls(struct Camera *c, Vec3f cPos, s16 *avoidYaw, s16 yawRange); void find_mario_floor_and_ceil(struct PlayerGeometry *pg); -void start_object_cutscene_without_focus(u8 cutscene); -s16 cutscene_object_with_dialog(u8 cutscene, struct Object *obj, s16 dialogID); -s16 cutscene_object_without_dialog(u8 cutscene, struct Object *obj); -s16 cutscene_object(u8 cutscene, struct Object *obj); -void play_cutscene(struct Camera *c); -void cutscene_event(CameraEvent event, struct Camera * c, s16 start, s16 end); -void cutscene_spawn_obj(u32 obj, s16 frame); void set_fov_shake(s16 amplitude, s16 decay, s16 shakeSpeed); - +s32 snap_to_45_degrees(s16 angle); +void set_camera_mode(struct Camera *c, s16 mode, s16 frames); +s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z); +void set_camera_mode_8_directions(struct Camera *c); +void set_camera_mode_boss_fight(struct Camera *c); +void set_camera_mode_close_cam(u8 *mode); +void set_camera_mode_radial(struct Camera *c, s16 transitionTime); +void transition_to_camera_mode(struct Camera *c, s16 newMode, s16 numFrames); void set_fov_function(u8 func); -void cutscene_set_fov_shake_preset(u8 preset); void set_fov_shake_from_point_preset(u8 preset, f32 posX, f32 posY, f32 posZ); void obj_rotate_towards_point(struct Object *obj, Vec3f point, s16 pitchOff, s16 yawOff, s16 pitchDiv, s16 yawDiv); +void set_mode_c_up(struct Camera *c); + +s16 update_slide_camera(struct Camera *c); +s32 update_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_outward_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_behind_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 unused_update_mode_5_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_c_up(struct Camera *c, Vec3f focus, Vec3f pos); +s32 nop_update_water_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_slide_or_0f_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_in_cannon(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_boss_fight_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_parallel_tracking_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_fixed_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_slide_or_0f_camera(struct Camera *c, Vec3f focus, Vec3f pos); +s32 update_spiral_stairs_camera(struct Camera *c, Vec3f focus, Vec3f pos); + +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS +s16 find_in_bounds_yaw_wdw_bob_thi(Vec3f pos, Vec3f origin, s16 yaw); +#endif + +extern s16 sYawSpeed; +extern struct PlayerCameraState *sMarioCamState; +extern struct CameraFOVStatus sFOVState; +extern struct TransitionInfo sModeTransition; +extern struct PlayerGeometry sMarioGeometry; +extern s16 sAvoidYawVel; +extern struct HandheldShakePoint sHandheldShakeSpline[4]; +extern s16 sHandheldShakeMag; +extern f32 sHandheldShakeTimer; +extern f32 sHandheldShakeInc; +extern s16 sHandheldShakePitch; +extern s16 sHandheldShakeYaw; +extern s16 sHandheldShakeRoll; +extern s16 sSelectionFlags; +extern s16 s2ndRotateFlags; +extern s16 sCameraSoundFlags; +extern u16 sCButtonsPressed; +extern struct LakituState gLakituState; +extern s16 sAreaYaw; +extern s16 sAreaYawChange; +extern s16 sLakituDist; +extern s16 sLakituPitch; +extern f32 sZoomAmount; +extern s16 sCSideButtonYaw; +extern s16 sBehindMarioSoundTimer; +extern f32 sZeroZoomDist; +extern s16 sCUpCameraPitch; +extern s16 sModeOffsetYaw; +extern s16 s8DirModeBaseYaw; +extern s16 s8DirModeYawOffset; +extern f32 sPanDistance; +extern f32 sCannonYOffset; +extern struct ModeTransitionInfo sModeInfo; +extern Vec3f sCastleEntranceOffset; +extern Vec3f sFixedModeBasePosition; +extern s16 gCameraMovementFlags; +extern s16 sStatusFlags; +extern struct Camera *gCamera; +extern u8 sFramesPaused; +extern u8 sZoomOutAreaMasks[]; +extern s32 gCurrLevelArea; +extern u32 gPrevLevel; +extern f32 gCameraZoomDist; +extern struct CameraStoredInfo sCameraStoreCUp; +extern struct CameraStoredInfo sCameraStoreCutscene; +extern s16 sCameraYawAfterDoorCutscene; -Gfx *geo_camera_fov(s32 callContext, struct GraphNode *g, UNUSED void *context); #endif // CAMERA_H diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index e09a64b4ac..f4f1612e18 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -8,6 +8,8 @@ #include "behavior_data.h" #include "camera.h" #include "dialog_ids.h" +#include "camera/camera_math.h" +#include "camera/cutscene_helpers.h" #include "engine/behavior_script.h" #include "engine/graph_node.h" #include "engine/math_util.h" @@ -78,6 +80,51 @@ static Vec4s sJumboStarKeyframes[27] = { #define CREDIT_TEXT_X_LEFT GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(CREDIT_TEXT_MARGIN_X) #define CREDIT_TEXT_X_RIGHT GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(CREDIT_TEXT_MARGIN_X) + +/** + * Converts the u32 given in DEFINE_COURSE to a u8 with the odd and even digits rotated into the right + * order for sDanceCutsceneIndexTable + */ +#define DROT(value, index) ((value >> (32 - (index + 1) * 8)) & 0xF0) >> 4 | \ + ((value >> (32 - (index + 1) * 8)) & 0x0F) << 4 + +#define DANCE_ENTRY(c) { DROT(c, 0), DROT(c, 1), DROT(c, 2), DROT(c, 3) }, + +#define DEFINE_COURSE(_0, cutscenes) DANCE_ENTRY(cutscenes) +#define DEFINE_COURSES_END() +#define DEFINE_BONUS_COURSE(_0, cutscenes) DANCE_ENTRY(cutscenes) + +/** + * Each hex digit is an index into sDanceCutsceneTable. + * + * 0: Lakitu flies away after the dance + * 1: Only rotates the camera, doesn't zoom out + * 2: The camera goes to a close up of Mario + * 3: Bowser keys and the grand star + * 4: Default, used for 100 coin stars, 8 red coin stars in bowser levels, and secret stars + */ +u8 sDanceCutsceneIndexTable[][4] = { + #include "levels/course_defines.h" + { 0x44, 0x44, 0x44, 0x04 }, // (26) Why go to all this trouble to save bytes and do this?! +}; +#undef DEFINE_COURSE +#undef DEFINE_COURSES_END +#undef DEFINE_BONUS_COURSE + +#undef DANCE_ENTRY +#undef DROT + +/** + * Maps cutscene to numbers in [0,4]. Used in determine_dance_cutscene() with sDanceCutsceneIndexTable. + * + * Only the first 5 entries are used. Perhaps the last 5 were bools used to indicate whether the star + * type exits the course or not. + */ +u8 sDanceCutsceneTable[] = { + CUTSCENE_DANCE_FLY_AWAY, CUTSCENE_DANCE_ROTATE, CUTSCENE_DANCE_CLOSEUP, CUTSCENE_KEY_DANCE, CUTSCENE_DANCE_DEFAULT, + CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, CUTSCENE_NONE, +}; + /** * print_displaying_credits_entry: Print the current displaying Credits Entry * Called in render_game. This function checks if sDispCreditsEntry points to a @@ -183,6 +230,192 @@ Gfx *geo_switch_peach_eyes(s32 callContext, struct GraphNode *node, UNUSED s32 c return NULL; } +/** + * @return `pullResult` or `pushResult` depending on Mario's door action + */ +u8 open_door_cutscene(u8 pullResult, u8 pushResult) { + if (sMarioCamState->action == ACT_PULLING_DOOR) { + return pullResult; + } + if (sMarioCamState->action == ACT_PUSHING_DOOR) { + return pushResult; + } + return CUTSCENE_NONE; +} + +/** + * Look up the victory dance cutscene in sDanceCutsceneTable + * + * First the index entry is determined based on the course and the star that was just picked up + * Like the entries in sZoomOutAreaMasks, each entry represents two stars + * The current courses's 4 bits of the index entry are used as the actual index into sDanceCutsceneTable + * + * @return the victory cutscene to use + */ +s32 determine_dance_cutscene(UNUSED struct Camera *c) { +#ifdef NON_STOP_STARS + return CUTSCENE_DANCE_DEFAULT; +#else + u8 cutscene = CUTSCENE_NONE; + u8 cutsceneIndex = 0; + u8 starIndex = (gLastCompletedStarNum - 1) / 2; + u8 courseNum = gCurrCourseNum; + + if (starIndex > 3) { + starIndex = 0; + } + if (courseNum > COURSE_MAX) { + courseNum = COURSE_NONE; + } + cutsceneIndex = sDanceCutsceneIndexTable[courseNum][starIndex]; + + if (gLastCompletedStarNum & 1) { + // Odd stars take the lower four bytes + cutsceneIndex &= 0xF; + } else { + // Even stars use the upper four bytes + cutsceneIndex = cutsceneIndex >> 4; + } + cutscene = sDanceCutsceneTable[cutsceneIndex]; + return cutscene; +#endif +} + +/** + * If no cutscenes are playing, determines if a cutscene should play based on Mario's action and + * cameraEvent + * + * @return the cutscene that should start, 0 if none + */ +u8 get_cutscene_from_mario_status(struct Camera *c) { + u8 cutscene = c->cutscene; + + if (cutscene == CUTSCENE_NONE) { + // A cutscene started by an object, if any, will start if nothing else happened + cutscene = sObjectCutscene; + sObjectCutscene = CUTSCENE_NONE; + if (sMarioCamState->cameraEvent == CAM_EVENT_DOOR) { + switch (gCurrLevelArea) { + case AREA_CASTLE_LOBBY: + //! doorStatus is never DOOR_ENTER_LOBBY when cameraEvent == 6, because + //! doorStatus is only used for the star door in the lobby, which uses + //! ACT_ENTERING_STAR_DOOR + if (c->mode == CAMERA_MODE_SPIRAL_STAIRS || c->mode == CAMERA_MODE_CLOSE || c->doorStatus == DOOR_ENTER_LOBBY) { + cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL_MODE, CUTSCENE_DOOR_PUSH_MODE); + } else { + cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); + } + break; + case AREA_BBH: + //! Castle Lobby uses 0 to mean 'no special modes', but BBH uses 1... + if (c->doorStatus == DOOR_LEAVING_SPECIAL) { + cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); + } else { + cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL_MODE, CUTSCENE_DOOR_PUSH_MODE); + } + break; + default: + cutscene = open_door_cutscene(CUTSCENE_DOOR_PULL, CUTSCENE_DOOR_PUSH); + break; + } + } + if (sMarioCamState->cameraEvent == CAM_EVENT_DOOR_WARP) { + cutscene = CUTSCENE_DOOR_WARP; + } + if (sMarioCamState->cameraEvent == CAM_EVENT_CANNON) { + cutscene = CUTSCENE_ENTER_CANNON; + } + if (SURFACE_IS_PAINTING_WARP(sMarioGeometry.currFloorType)) { + cutscene = CUTSCENE_ENTER_PAINTING; + } + switch (sMarioCamState->action) { + case ACT_DEATH_EXIT: + cutscene = CUTSCENE_DEATH_EXIT; + break; + case ACT_EXIT_AIRBORNE: + cutscene = CUTSCENE_EXIT_PAINTING_SUCC; + break; + case ACT_SPECIAL_EXIT_AIRBORNE: + if (gPrevLevel == LEVEL_BOWSER_1 || gPrevLevel == LEVEL_BOWSER_2 + || gPrevLevel == LEVEL_BOWSER_3) { + cutscene = CUTSCENE_EXIT_BOWSER_SUCC; + } else { + cutscene = CUTSCENE_EXIT_SPECIAL_SUCC; + } + break; + case ACT_SPECIAL_DEATH_EXIT: + if (gPrevLevel == LEVEL_BOWSER_1 || gPrevLevel == LEVEL_BOWSER_2 + || gPrevLevel == LEVEL_BOWSER_3) { + cutscene = CUTSCENE_EXIT_BOWSER_DEATH; + } else { + cutscene = CUTSCENE_NONPAINTING_DEATH; + } + break; + case ACT_ENTERING_STAR_DOOR: + if (c->doorStatus == DOOR_DEFAULT) { + cutscene = CUTSCENE_SLIDING_DOORS_OPEN; + } else { + cutscene = CUTSCENE_DOOR_PULL_MODE; + } + break; + case ACT_UNLOCKING_KEY_DOOR: + cutscene = CUTSCENE_UNLOCK_KEY_DOOR; + break; + case ACT_WATER_DEATH: + cutscene = CUTSCENE_WATER_DEATH; + break; + case ACT_DEATH_ON_BACK: + cutscene = CUTSCENE_DEATH_ON_BACK; + break; + case ACT_DEATH_ON_STOMACH: + cutscene = CUTSCENE_DEATH_ON_STOMACH; + break; + case ACT_STANDING_DEATH: + cutscene = CUTSCENE_STANDING_DEATH; + break; + case ACT_SUFFOCATION: + cutscene = CUTSCENE_SUFFOCATION_DEATH; + break; + case ACT_QUICKSAND_DEATH: + cutscene = CUTSCENE_QUICKSAND_DEATH; + break; + case ACT_ELECTROCUTION: + cutscene = CUTSCENE_STANDING_DEATH; + break; + case ACT_STAR_DANCE_EXIT: + cutscene = determine_dance_cutscene(c); + break; + case ACT_STAR_DANCE_WATER: + cutscene = determine_dance_cutscene(c); + break; + case ACT_STAR_DANCE_NO_EXIT: + cutscene = CUTSCENE_DANCE_DEFAULT; + break; + } + switch (sMarioCamState->cameraEvent) { + case CAM_EVENT_START_INTRO: + cutscene = CUTSCENE_INTRO_PEACH; + break; + case CAM_EVENT_START_GRAND_STAR: + cutscene = CUTSCENE_GRAND_STAR; + break; + case CAM_EVENT_START_ENDING: + cutscene = CUTSCENE_ENDING; + break; + case CAM_EVENT_START_END_WAVING: + cutscene = CUTSCENE_END_WAVING; + break; + case CAM_EVENT_START_CREDITS: + cutscene = CUTSCENE_CREDITS; + break; + } + } + //! doorStatus is reset every frame. CameraTriggers need to constantly set doorStatus + c->doorStatus = DOOR_DEFAULT; + + return cutscene; +} + /** * get_star_collection_dialog: Determine what dialog should show when Mario * collects a star. diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c index 14f0b885aa..e78e505116 100644 --- a/src/game/obj_behaviors.c +++ b/src/game/obj_behaviors.c @@ -6,6 +6,7 @@ #include "behavior_actions.h" #include "behavior_data.h" #include "camera.h" +#include "camera/cutscene_helpers.h" #include "course_table.h" #include "dialog_ids.h" #include "engine/behavior_script.h" diff --git a/src/game/obj_behaviors_2.c b/src/game/obj_behaviors_2.c index 8b396b898d..392c779a62 100644 --- a/src/game/obj_behaviors_2.c +++ b/src/game/obj_behaviors_2.c @@ -17,6 +17,7 @@ #include "behavior_actions.h" #include "behavior_data.h" #include "camera.h" +#include "camera/cutscene_helpers.h" #include "dialog_ids.h" #include "engine/behavior_script.h" #include "engine/math_util.h" diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 82435a4f63..3b8fc42c0d 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -5,6 +5,7 @@ #include "behavior_actions.h" #include "behavior_data.h" #include "camera.h" +#include "camera/cutscene_helpers.h" #include "debug.h" #include "dialog_ids.h" #include "engine/behavior_script.h"