diff --git a/enhancements/README.md b/enhancements/README.md new file mode 100644 index 000000000..b86d3c8b6 --- /dev/null +++ b/enhancements/README.md @@ -0,0 +1,20 @@ +# Mario Kart 64 Enhancements + +This directory contains unofficial patches to the source code that provide various features +and enhancements. + +To apply a patch, run `tools/apply_patch.sh [patch]` where `[patch]` is the name of the +.patch file you wish to apply. This will perform all of the patch's changes +to the source code. + +Likewise, to undo the changes from a patch you applied, run +`tools/revert_patch.sh` with the name of the .patch file you wish to undo. + +To create your own enhancement patch, switch to the `master` Git +branch, make your changes to the code (but do not commit), then run `tools/create_patch.sh`. Your changes will be stored in the .patch file you specify. + +The following enhancements are included in this directory: + +## Flycam - `flycam.patch` + +This enhancement provides a flycam mode. diff --git a/enhancements/flycam.patch b/enhancements/flycam.patch new file mode 100644 index 000000000..024efbd6e --- /dev/null +++ b/enhancements/flycam.patch @@ -0,0 +1,445 @@ +diff --git a/mk64.ld b/mk64.ld +index 70f4820..f36f11c 100644 +--- a/mk64.ld ++++ b/mk64.ld +@@ -75,6 +75,7 @@ SECTIONS + BUILD_DIR/src/audio/seqplayer.o(.text); + BUILD_DIR/src/audio/external.o(.text); + BUILD_DIR/src/audio/port_eu.o(.text); ++ BUILD_DIR/src/flycam.o(.text); + #if DEBUG + BUILD_DIR/src/os/osCartRomInit.o(.text); + BUILD_DIR/src/debug/crash_screen_enhancement.o(.text); +@@ -229,6 +230,7 @@ SECTIONS + BUILD_DIR/src/audio/load.o(.data); + BUILD_DIR/src/audio/playback.o(.data); + BUILD_DIR/src/audio/effects.o(.data); ++ BUILD_DIR/src/flycam.o(.data); + #if DEBUG + BUILD_DIR/src/debug/crash_screen_enhancement.o(.data); + BUILD_DIR/src/debug/debug.o(.data); +@@ -277,6 +279,7 @@ SECTIONS + BUILD_DIR/src/audio/seqplayer.o(.rodata); + BUILD_DIR/src/audio/external.o(.rodata); + BUILD_DIR/src/audio/port_eu.o(.rodata); ++ BUILD_DIR/src/flycam.o(.rodata); + #if DEBUG + BUILD_DIR/src/debug/crash_screen_enhancement.o(.rodata); + BUILD_DIR/src/debug/debug.o(.rodata); +@@ -311,6 +314,7 @@ SECTIONS + BUILD_DIR/src/code_80091750.jp.o(.bss); + BUILD_DIR/src/code_800AF9B0.o(.bss); + BUILD_DIR/src/menus.o(.bss); ++ BUILD_DIR/src/flycam.o(.bss); + #if DEBUG + BUILD_DIR/src/os/osCartRomInit.o(.bss); + BUILD_DIR/src/debug/crash_screen_enhancement.o(.bss); +diff --git a/src/camera.c b/src/camera.c +index 02275b1..406739d 100644 +--- a/src/camera.c ++++ b/src/camera.c +@@ -969,7 +969,9 @@ void func_8001EE98(Player *player, Camera *camera, s8 index) { + func_8001E8E8(camera, player, index); + break; + } +- func_8001E45C(camera, player, index); ++ ++ flycam(camera, player, index); ++ + break; + case 8: + func_8001E0C4(camera, player, index); +diff --git a/src/code_8001F980.c b/src/code_8001F980.c +index 6b14e27..d10796b 100644 +--- a/src/code_8001F980.c ++++ b/src/code_8001F980.c +@@ -65,6 +65,8 @@ void func_8001F9E4(Player *player, Camera *camera, s8 arg2) { + } + } + ++extern u32 isFlycam; ++ + u16 check_player_camera_collision(Player *player, Camera *camera, f32 arg2, f32 arg3) { + UNUSED f32 pad[6]; + f32 sp64; +@@ -79,6 +81,10 @@ u16 check_player_camera_collision(Player *player, Camera *camera, f32 arg2, f32 + s16 var_v0; + u16 ret; + ++ if (isFlycam) { ++ return 1; ++ } ++ + ret = 0; + switch (gActiveScreenMode) { /* irregular */ + case SCREEN_MODE_1P: +diff --git a/src/flycam.c b/src/flycam.c +new file mode 100644 +index 0000000..5569838 +--- /dev/null ++++ b/src/flycam.c +@@ -0,0 +1,328 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "main.h" ++#include ++#include ++#include "racing/collision.h" ++#include ++#include "player_controller.h" ++#include "code_80057C60.h" ++ ++// Yaw/pitch rotation sensitivity ++#define SENSITIVITY_X 0.0003f ++#define SENSITIVITY_Y 0.0003f ++ ++u32 isFlycam = FALSE; ++u32 fRankIndex = 0; ++u32 fTargetPlayer = FALSE; ++u32 fMode; // flycam mode should probably be an enum ++u32 fModeInit = FALSE; ++ ++typedef struct { ++ Vec3f pos; ++ Vec3f lookAt; ++ Vec3s rot; ++} FlycamSaveState; ++ ++FlycamSaveState fState; ++ ++void flycam_calculate_forward_vector(Camera* camera, Vec3f forwardVector); ++void flycam_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance); ++void flycam_update(Camera* camera, struct Controller *controller); ++void flycam_controller_manager(Camera *camera, struct Controller *controller, Player *player); ++void flycam_target_player(Camera *camera, u32 playerIndex); ++void flycam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance); ++void flycam_save_state(Camera *camera); ++void flycam_load_state(Camera *camera); ++ ++ ++/** ++ * Controls ++ * ++ * Forward: A ++ * Backward: B ++ * ++ * Go faster: Z ++ * ++ * Up: C-up ++ * Down: C-down ++ * ++ * Targets players based on rank position ++ * ++ * Target player: R-trig ++ * Target next player: C-right ++ * Target previous player: C-left ++ * ++ * Switch camera modes: D-pad left ++ * ++ * Camera mode 1: Enter flycam at the player's position ++ * Camera mode 2: Enter flycam at previous flycam spot ++ * ++*/ ++ ++void flycam(Camera *camera, Player *player, s8 index) { ++ struct Controller *controller = &gControllers[0]; ++ Vec3f forwardVector; ++ f32 dirX; ++ f32 dirY; ++ f32 dirZ; ++ f32 length; ++ ++ if (controller->buttonPressed & L_TRIG) { ++ isFlycam = !isFlycam; ++ ++ // Don't use `bool = !bool` here as the game code can swap these on you. ++ // Which will confuse the code. This forces it to always be correct ++ if (isFlycam) { ++ player->type |= PLAYER_CPU; ++ } else { ++ player->type &= PLAYER_CPU; ++ } ++ ++ gIsHUDVisible = !isFlycam; ++ ++ if (isFlycam) { ++ ++ if (fMode && fModeInit) { ++ flycam_load_state(camera); ++ } else { ++ // !fMode or fMode not initialized ++ flycam_target_player(camera, get_player_index_for_player(player)); ++ } ++ ++ return; ++ } else { ++ if(fMode) { ++ flycam_save_state(camera); ++ } ++ } ++ } ++ ++ // Driving mode ++ if (!isFlycam) { ++ // Use normal camera code ++ func_8001E45C(camera, &gPlayers[fRankIndex], index); ++ return; ++ } ++ ++ ++ //if (player == gPlayerOne) { return; } ++ ++ //player->type &= ~PLAYER_HUMAN; ++ //player->type |= PLAYER_HUMAN; ++ ++ if ((player->type & PLAYER_START_SEQUENCE)) { return; } ++ ++ ++ flycam_controller_manager(camera, controller, player); ++ ++ ++ ++ ++} ++ ++void flycam_save_state(Camera *camera) { ++ fState.pos[0] = camera->pos[0]; ++ fState.pos[1] = camera->pos[1]; ++ fState.pos[2] = camera->pos[2]; ++ ++ fState.lookAt[0] = camera->lookAt[0]; ++ fState.lookAt[1] = camera->lookAt[1]; ++ fState.lookAt[2] = camera->lookAt[2]; ++ ++ fState.rot[0] = camera->rot[0]; ++ fState.rot[1] = camera->rot[1]; ++ fState.rot[2] = camera->rot[2]; ++ fModeInit = TRUE; ++} ++ ++void flycam_load_state(Camera *camera) { ++ camera->pos[0] = fState.pos[0]; ++ camera->pos[1] = fState.pos[1]; ++ camera->pos[2] = fState.pos[2]; ++ ++ camera->lookAt[0] = fState.lookAt[0]; ++ camera->lookAt[1] = fState.lookAt[1]; ++ camera->lookAt[2] = fState.lookAt[2]; ++ ++ camera->rot[0] = fState.rot[0]; ++ camera->rot[1] = fState.rot[1]; ++ camera->rot[2] = fState.rot[2]; ++} ++ ++void flycam_controller_manager(Camera *camera, struct Controller *controller, Player *player) { ++ ++ if (controller->buttonPressed & U_JPAD) { ++ fMode = !fMode; ++ } ++ ++ // Target a player ++ if (controller->buttonPressed & R_TRIG) { ++ fTargetPlayer = !fTargetPlayer; ++ } ++ ++ // Target next player ++ if (controller->buttonPressed & L_CBUTTONS) { ++ if (fRankIndex > 0) { ++ fRankIndex--; ++ camera->playerId = fRankIndex; ++ D_800DC5EC->player = &gPlayers[fRankIndex]; ++ } ++ } ++ ++ // Target previous player ++ if (controller->buttonPressed & R_CBUTTONS) { ++ if (fRankIndex < 7) { ++ fRankIndex++; ++ camera->playerId = fRankIndex; ++ D_800DC5EC->player = &gPlayers[fRankIndex]; ++ } ++ } ++ ++ // Target camera at chosen player ++ if (fTargetPlayer) { ++ flycam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]); ++ // Don't run the other camera code. ++ return; ++ } ++ ++ // Rotation ++ if (!fTargetPlayer) { ++ if (controller->stickDirection != 0) { ++ flycam_update(camera, controller); ++ } ++ } ++ ++ // Forward ++ if (controller->button & A_BUTTON) { ++ flycam_move_camera_forward(camera, controller, 3.0f); ++ } ++ ++ // Backward B button but not A button. ++ if (controller->button & B_BUTTON && !(controller->button & A_BUTTON)) { ++ flycam_move_camera_forward(camera, controller, -3.0f); ++ } ++ ++ // Up ++ if (controller->button & U_CBUTTONS) { ++ flycam_move_camera_up(camera, controller, 2.0f); ++ } ++ // Up ++ if (controller->button & D_CBUTTONS) { ++ flycam_move_camera_up(camera, controller, -2.0f); ++ } ++} ++ ++// Calculates the forward direction vector based on camera orientation ++void flycam_calculate_forward_vector(Camera* camera, Vec3f forwardVector) { ++ f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; // Convert pitch from 0-65535 to degrees ++ f32 yaw = (camera->rot[1] / 65535.0f) * 360.0f; // Convert yaw from 0-65535 to degrees ++ ++ // Convert degrees to radians ++ pitch = pitch * M_PI / 180.0f; ++ yaw = yaw * M_PI / 180.0f; ++ ++ forwardVector[0] = -sinf(yaw) * cosf(pitch); ++ forwardVector[1] = -sinf(pitch); ++ forwardVector[2] = cosf(yaw) * cosf(pitch); ++} ++ ++// Function to move the camera forward ++void flycam_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance) { ++ Vec3f forwardVector; ++ Vec3f rightVector; ++ f32 length; ++ flycam_calculate_forward_vector(camera, forwardVector); ++ ++ if (controller->button & Z_TRIG) { ++ distance *= 3; ++ } ++ ++ // Normalize the forward vector ++ length = sqrtf(forwardVector[0] * forwardVector[0] + forwardVector[1] * forwardVector[1] + forwardVector[2] * forwardVector[2]); ++ forwardVector[0] /= length; ++ forwardVector[1] /= length; ++ forwardVector[2] /= length; ++ ++ // Calculate the right vector by taking the cross product of forward and up ++ rightVector[0] = forwardVector[1] * camera->up[2] - forwardVector[2] * camera->up[1]; ++ rightVector[1] = forwardVector[2] * camera->up[0] - forwardVector[0] * camera->up[2]; ++ rightVector[2] = forwardVector[0] * camera->up[1] - forwardVector[1] * camera->up[0]; ++ ++ // Move the camera's position along the forward vector while considering its up vector ++ camera->pos[0] += forwardVector[0] * distance; ++ camera->pos[1] += forwardVector[1] * distance; ++ camera->pos[2] += forwardVector[2] * distance; ++ ++ // Optionally, you can also adjust the lookAt point to maintain the same relative position ++ camera->lookAt[0] += forwardVector[0] * distance; ++ camera->lookAt[1] += forwardVector[1] * distance; ++ camera->lookAt[2] += forwardVector[2] * distance; ++} ++ ++// Function to move the camera forward ++void flycam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance) { ++ // Check if the Z button is pressed (for faster movement) ++ if (controller->button & Z_TRIG) { ++ distance *= 3; ++ } ++ ++ // Move the camera's position along its up vector (Y-axis) ++ camera->pos[1] += distance; ++ ++ // Optionally, adjust the lookAt point to maintain the same relative position ++ camera->lookAt[1] += distance; ++} ++ ++// Update camera rotation and lookAt point based on input ++void flycam_update(Camera* camera, struct Controller *controller) { ++ // Calculate yaw (horizontal movement) ++ f32 yawChange = controller->rawStickX * SENSITIVITY_X; ++ f32 pitchChange = controller->rawStickY * SENSITIVITY_Y; ++ Vec3f forwardVector; ++ ++ func_802ADDC8(&camera->unk_54, 50, camera->pos[0], camera->pos[1], camera->pos[2]); ++ ++ camera->rot[1] += (short)(yawChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range ++ ++ camera->rot[2] += (short)(-pitchChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range ++ ++ if (camera->rot[2] > 15999) { ++ camera->rot[2] = 15999; ++ } else if (camera->rot[2] < -15999) { ++ camera->rot[2] = -15999; ++ } ++ ++ // Update the lookAt point based on the new orientation ++ flycam_calculate_forward_vector(camera, forwardVector); ++ camera->lookAt[0] = camera->pos[0] + forwardVector[0]; ++ camera->lookAt[1] = camera->pos[1] + forwardVector[1]; ++ camera->lookAt[2] = camera->pos[2] + forwardVector[2]; ++} ++ ++void flycam_target_player(Camera *camera, u32 playerIndex) { ++ Vec3f forwardVector;// = 2.0f; ++ Player *player = &gPlayers[playerIndex]; ++ ++ // Calculate the direction from the player to the camera ++ f32 dirX = player->pos[0] - camera->pos[0]; ++ f32 dirY = player->pos[1] - camera->pos[1]; ++ f32 dirZ = player->pos[2] - camera->pos[2]; ++ ++ // Normalize the direction vector (if needed) ++ f32 length = sqrtf(dirX * dirX + dirY * dirY + dirZ * dirZ); ++ if (length > 0) { ++ dirX /= length; ++ dirY /= length; ++ dirZ /= length; ++ } ++ ++ // Update the camera's look-at direction ++ camera->lookAt[0] = camera->pos[0] + dirX; ++ camera->lookAt[1] = camera->pos[1] + dirY; ++ camera->lookAt[2] = camera->pos[2] + dirZ; ++} +diff --git a/src/main.c b/src/main.c +index 7c90951..135e98c 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -578,6 +578,7 @@ void race_logic_loop(void) { + gTickSpeed = 2; + staff_ghosts_loop(); + if (gIsGamePaused == 0) { ++ func_8001EE98(gPlayerOneCopy, camera1, 0); + for (i = 0; i < gTickSpeed; i++) { + if (D_8015011E) { + gCourseTimer += 0.01666666; // 1 / 60 +@@ -585,7 +586,6 @@ void race_logic_loop(void) { + func_802909F0(); + evaluate_player_collision(); + func_800382DC(); +- func_8001EE98(gPlayerOneCopy, camera1, 0); + func_80028F70(); + func_8028F474(); + func_80059AC8(); +diff --git a/src/racing/render_courses.c b/src/racing/render_courses.c +index c2a84aa..338e2bd 100644 +--- a/src/racing/render_courses.c ++++ b/src/racing/render_courses.c +@@ -180,6 +180,11 @@ void load_surface_map(uintptr_t addr, struct UnkStruct_800DC5EC *arg1) { + arg1->pathCounter = temp_v1; + temp_v1 = ((temp_v1 - 1) * 4) + var_a3; + ++ if (isFlycam) { ++ func_8029569C(); ++ return; ++ } ++ + gSPDisplayList(gDisplayListHead++, gfx[temp_v1]); + } + diff --git a/tools/apply_patch.sh b/tools/apply_patch.sh new file mode 100755 index 000000000..1ab8ed239 --- /dev/null +++ b/tools/apply_patch.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# apply_patch.sh - Applies an enhancement patch +# + +if [ "$#" -ne 1 ] +then + echo "Usage: $0 patch_file" + echo ' Applies a patch file to the current directory' + exit 1 +fi + +read -p "Do you wish to apply the patch '$1'? [Y/N] " response +case "$response" in + Y|y) + patch -p1 < "$1" + ;; + N|n) + echo 'Quit' + exit 1 + ;; + *) + echo "Invalid response '$response'." + exit 1 + ;; +esac + diff --git a/tools/create_patch.sh b/tools/create_patch.sh new file mode 100755 index 000000000..689f11ddd --- /dev/null +++ b/tools/create_patch.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# create_patch.sh - Creates an enhancement patch based on the current Git changes +# + +if [ "$#" -ne 1 ] +then + echo "Usage: $0 patch_file" + echo ' Creates a patch file based on the changes made to the current directory' + exit 1 +fi + +# Make sure this is a git repository +if [ ! -d .git ] +then + echo 'Error: The current directory is not a Git repository.' + exit 1 +fi + +# 'git diff' is stupid and doesn't show new untracked files, so we must add them first. +git add . +# Generate the patch. +git diff -p --staged > "$1" +# Undo the 'git add'. +git reset diff --git a/tools/revert_patch.sh b/tools/revert_patch.sh new file mode 100755 index 000000000..af19abda7 --- /dev/null +++ b/tools/revert_patch.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# revert_patch.sh - Reverts an enhancement patch +# + +if [ "$#" -ne 1 ] +then + echo "Usage: $0 patch_file" + echo ' Reverts the changes made by an enhancement patch' + exit 1 +fi + +read -p "Do you wish to revert the patch '$1'? [Y/N] " response +case "$response" in + Y|y) + patch -p1 -R < "$1" + ;; + N|n) + echo 'Quit' + exit 1 + ;; + *) + echo "Invalid response '$response'." + exit 1 + ;; +esac + +