diff --git a/CMakeLists.txt b/CMakeLists.txt index d2f8f1d6b..1fa0a5dc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ file(GLOB_RECURSE ALL_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/enhancements/*.c" "src/enhancements/*.h" "src/enhancements/freecam/*.c" + "src/enhancements/freecam/*.cpp" "src/enhancements/freecam/*.h" "src/engine/*.cpp" "src/engine/*.h" diff --git a/libultraship b/libultraship index 80c2c5e9c..3e46fe77a 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 80c2c5e9c6a5681428f759100c2428c5e41f333c +Subproject commit 3e46fe77a4581a84f9c0edfab9c3a69a5320fe01 diff --git a/src/camera.c b/src/camera.c index 8992371d6..ef298f55b 100644 --- a/src/camera.c +++ b/src/camera.c @@ -17,7 +17,7 @@ #include "main.h" #include "spawn_players.h" #include "enhancements/freecam/freecam_engine.h" -#include "freecam.h" +#include "enhancements/freecam/freecam.h" #include "engine/GameAPI.h" #include "port/Game.h" diff --git a/src/enhancements/freecam/freecam.c b/src/enhancements/freecam/freecam.c deleted file mode 100644 index ef9df134c..000000000 --- a/src/enhancements/freecam/freecam.c +++ /dev/null @@ -1,374 +0,0 @@ -#include -#include -#include -#include -#include "main.h" -#include -#include -#include "racing/collision.h" -#include "player_controller.h" -#include "code_80057C60.h" -#include "code_800029B0.h" -#include "code_80005FD0.h" -#include -#include "freecam_engine.h" -#include "n64_freecam.h" -#include "math_util.h" -#include "skybox_and_splitscreen.h" -#include "freecam.h" - -#include "engine/Engine.h" -#include "engine/courses/Course.h" - -typedef struct { - Vec3f pos; - Vec3f lookAt; - Vec3s rot; -} freecamSaveState; - -freecamSaveState fState; - -u32 fRankIndex = -1; -u32 fTargetPlayer = false; -u32 fMode; // freecam mode should probably be an enum -u32 fModeInit = false; - -int rightMouseButtonDown = 0; // Track if right mouse button is held down - -u32 gFreecamControllerType = 0; - -// void freecam_n64_calculate_forward_vector(Camera* camera, Vec3f forwardVector); -// void freecam_n64_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance); -// void freecam_calculate_forward_vector(Camera* camera, Vec3f forwardVector); -// void freecam_move_camera_forward(Camera* camera, f32 distance); - -// void freecam_update(Camera* camera, struct Controller *controller); -// void freecam_n64_controller_manager(Camera *camera, struct Controller *controller, Player *player); -// void freecam_target_player(Camera *camera, u32 playerIndex); -// void freecam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance); - -/** - * 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 freecam at the player's position - * Camera mode 2: Enter freecam at previous freecam spot - * - */ - -void freecam(Camera* camera, Player* player, s8 index) { - struct Controller* controller = &gControllers[0]; - f32 dirX; - f32 dirY; - f32 dirZ; - f32 length; - - if (controller->buttonPressed & L_TRIG) { - // Toggle freecam - CVarSetInteger("gFreecam", !CVarGetInteger("gFreecam", 0)); - - // 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 (CVarGetInteger("gFreecam", 0) == 1) { - player->type |= PLAYER_KART_AI; - } else { - player->type &= PLAYER_KART_AI; - } - - gIsHUDVisible = !CVarGetInteger("gFreecam", 0); - - if (CVarGetInteger("gFreecam", 0) == 1) { - - if (fMode && fModeInit) { - freecam_load_state(camera); - } else { - // !fMode or fMode not initialized - freecam_target_player(camera, get_player_index_for_player(player)); - } - - return; - } else { - if (fMode) { - freecam_save_state(camera); - } - } - } - - // Driving mode - if (CVarGetInteger("gFreecam", 0) == 0) { - // 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; } - - // Mouse/Keyboard - if (gFreecamControllerType == 0) { - freecam_calculate_forward_vector_allow_rotation(camera, freeCam.forwardVector); - freecam_mouse_manager(camera, freeCam.forwardVector); - freecam_keyboard_manager(camera, freeCam.forwardVector); - } else { // Stock N64 controller - freecam_n64_controller_manager(camera, controller, player); - } - if (!fTargetPlayer) { - freecam_update(camera, freeCam.forwardVector); - } -} - -void freecam_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 freecam_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]; -} - -f32 gFreecamRotateSmoothingFactor = 0.85f; - -void freecam_mouse_manager(Camera* camera, Vec3f forwardVector) { - int mouseX, mouseY; - static int prevMouseX = 0, prevMouseY = 0; - Uint32 mouseState = SDL_GetRelativeMouseState(&mouseX, &mouseY); - - mouseX = (mouseX + prevMouseX) / 2; - mouseY = (mouseY + prevMouseY) / 2; - prevMouseX = mouseX; - prevMouseY = mouseY; - - if (mouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) { - // Calculate yaw (left/right) and pitch (up/down) changes - f32 yawChange = mouseX * MOUSE_SENSITIVITY_X; - f32 pitchChange = mouseY * MOUSE_SENSITIVITY_Y; - - // Smoothly update yaw and pitch - camera->rot[1] += (short) (yawChange * 65535.0f / (2 * M_PI)); // Yaw (left/right) - camera->rot[2] += (short) (pitchChange * 65535.0f / (2 * M_PI)); // Pitch (up/down) - - // Clamp pitch to avoid extreme values - if (camera->rot[2] > 15999) { - camera->rot[2] = 15999; - } - if (camera->rot[2] < -15999) { - camera->rot[2] = -15999; - } - - // Calculate the forward vector based on the new yaw and pitch - // freecam_calculate_forward_vector_allow_rotation(camera, forwardVector); - - // Smoothly interpolate the lookAt position - Vec3f targetLookAt = { camera->pos[0] + forwardVector[0], camera->pos[1] + forwardVector[1], - camera->pos[2] + forwardVector[2] }; - - // Smoothing - camera->lookAt[0] += (targetLookAt[0] - camera->lookAt[0]) * gFreecamRotateSmoothingFactor; - camera->lookAt[1] += (targetLookAt[1] - camera->lookAt[1]) * gFreecamRotateSmoothingFactor; - camera->lookAt[2] += (targetLookAt[2] - camera->lookAt[2]) * gFreecamRotateSmoothingFactor; - } -} - -f32 gFreecamSpeed = 3.0f; -f32 gFreecamSpeedMultiplier = 2.0f; - -void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { - const uint8_t* keystate = SDL_GetKeyboardState(NULL); - float moveSpeed = gFreecamSpeed; - - // Fast movement with Ctrl - if (keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL]) { - moveSpeed *= gFreecamSpeedMultiplier; - } - - // Determine movement direction based on keys pressed - Vec3f totalMove = { 0.0f, 0.0f, 0.0f }; - - // if (keystate[SDL_SCANCODE_F]) { - // fMode = true; - // } - - // // Target a player - // if (keystate[SDL_SCANCODE_G]) { - // fTargetPlayer = false; - // } - - // Target next player - if (keystate[SDL_SCANCODE_N]) { - if (fRankIndex > 0) { - fRankIndex--; - camera->playerId = fRankIndex; - D_800DC5EC->player = &gPlayers[fRankIndex]; - } - } - - // Target previous player - if (keystate[SDL_SCANCODE_M]) { - if (fRankIndex < 7) { - fRankIndex++; - camera->playerId = fRankIndex; - D_800DC5EC->player = &gPlayers[fRankIndex]; - } - } - - // Target camera at chosen player - if (fRankIndex != -1) { - freecam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]); - // Don't run the other camera code. - return; - } - - if (keystate[SDL_SCANCODE_W]) { - totalMove[0] += forwardVector[0] * moveSpeed; - totalMove[2] += forwardVector[2] * moveSpeed; - } - if (keystate[SDL_SCANCODE_S]) { - totalMove[0] -= forwardVector[0] * moveSpeed; - totalMove[2] -= forwardVector[2] * moveSpeed; - } - if (keystate[SDL_SCANCODE_D]) { - totalMove[0] -= forwardVector[2] * moveSpeed; // Pan right - totalMove[2] += forwardVector[0] * moveSpeed; - } - if (keystate[SDL_SCANCODE_A]) { - totalMove[0] += forwardVector[2] * moveSpeed; // Pan left - totalMove[2] -= forwardVector[0] * moveSpeed; - } - if (keystate[SDL_SCANCODE_SPACE]) { - totalMove[1] += moveSpeed; // Move up - } - if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) { - totalMove[1] -= moveSpeed; // Move down - } - freeCam.velocity[0] += totalMove[0]; - freeCam.velocity[1] += totalMove[1]; - freeCam.velocity[2] += totalMove[2]; - - // Update camera's lookAt position - camera->lookAt[0] = camera->pos[0] + forwardVector[0]; - camera->lookAt[2] = camera->pos[2] + forwardVector[2]; -} - -// Control the camera using the n64 controller -void freecam_n64_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) { - freecam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]); - // Don't run the other camera code. - return; - } - - // Rotation - if (!fTargetPlayer) { - if (controller->stickDirection != 0) { - freecam_n64_update(camera, controller); - } - } - - // Forward - if (controller->button & A_BUTTON) { - freecam_n64_move_camera_forward(camera, controller, 3.0f); - } - - // Backward B button but not A button. - if (controller->button & B_BUTTON && !(controller->button & A_BUTTON)) { - freecam_n64_move_camera_forward(camera, controller, -3.0f); - } - - // Up - if (controller->button & U_CBUTTONS) { - freecam_n64_move_camera_up(camera, controller, 2.0f); - } - // Up - if (controller->button & D_CBUTTONS) { - freecam_n64_move_camera_up(camera, controller, -2.0f); - } -} - -void freecam_render_setup(void) { - u16 perspNorm; - Mat4 matrix; - init_rdp(); - func_802A53A4(); - init_rdp(); - func_80057FC4(0); - gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH); - gSPClearGeometryMode(gDisplayListHead++, G_CULL_BACK | G_CULL_BOTH | G_CULL_FRONT); - guPerspective(&gGfxPool->mtxPersp[0], &perspNorm, gCameraZoom[0], gScreenAspect, CourseManager_GetProps()->NearPersp, CourseManager_GetProps()->FarPersp, 1.0f); - gSPPerspNormalize(gDisplayListHead++, perspNorm); - gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxPersp[0]), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); - guLookAt(&gGfxPool->mtxLookAt[0], camera1->pos[0], camera1->pos[1], camera1->pos[2], camera1->lookAt[0], - camera1->lookAt[1], camera1->lookAt[2], camera1->up[0], camera1->up[1], camera1->up[2]); - gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxLookAt[0]), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); - mtxf_identity(matrix); - gSPSetGeometryMode(gDisplayListHead++, G_CULL_BACK); - render_set_position(matrix, 0); - init_rdp(); -} diff --git a/src/enhancements/freecam/freecam.cpp b/src/enhancements/freecam/freecam.cpp new file mode 100644 index 000000000..0602f9d27 --- /dev/null +++ b/src/enhancements/freecam/freecam.cpp @@ -0,0 +1,381 @@ +#include +#include +#include "port/Engine.h" +#include "port/Game.h" +#include +#include + +extern "C" { +#include +#include +#include "main.h" +#include +#include +#include "racing/collision.h" +#include "player_controller.h" +#include "code_80057C60.h" +#include "code_800029B0.h" +#include "code_80005FD0.h" +#include +#include "freecam_engine.h" +#include "math_util.h" +#include "skybox_and_splitscreen.h" +#include "freecam.h" +} + +#include "engine/Engine.h" +#include "engine/courses/Course.h" + +typedef struct { + Vec3f pos; + Vec3f lookAt; + Vec3s rot; +} freecamSaveState; + +freecamSaveState fState; + +u32 fRankIndex = 0; +u32 fTargetPlayer = true; +u32 fMode; // freecam mode should probably be an enum +u32 fModeInit = false; + +int rightMouseButtonDown = 0; // Track if right mouse button is held down + +u32 gFreecamControllerType = 0; + +/** + * 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 freecam at the player's position + * Camera mode 2: Enter freecam at previous freecam spot + * + */ +void freecam(Camera* camera, Player* player, s8 index) { + struct Controller* controller = &gControllers[0]; + f32 dirX; + f32 dirY; + f32 dirZ; + f32 length; + + if (controller->buttonPressed & L_TRIG) { + // Toggle freecam + CVarSetInteger("gFreecam", !CVarGetInteger("gFreecam", 0)); + + // 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 (CVarGetInteger("gFreecam", 0) == 1) { + player->type |= PLAYER_KART_AI; + } else { + player->type &= PLAYER_KART_AI; + } + + gIsHUDVisible = !CVarGetInteger("gFreecam", 0); + } + + // Calculate forward direction + freecam_calculate_forward_vector_allow_rotation(camera, freeCam.forwardVector); + + // Adjust camera rotation + if (fTargetPlayer) { + freecam_target_player(camera, freeCam.forwardVector); + } else { + freecam_mouse_manager(camera, freeCam.forwardVector); + } + + // Adjust camera position + freecam_keyboard_manager(camera, freeCam.forwardVector); + + // Apply final position, velocity, and lookAt + freecam_tick(camera, freeCam.forwardVector); + +} + +f32 gFreecamRotateSmoothingFactor = 0.85f; + +void freecam_mouse_manager(Camera* camera, Vec3f forwardVector) { + static int prevMouseX = 0, prevMouseY = 0; + auto wnd = GameEngine::Instance->context->GetWindow(); + Ship::Coords mouse = wnd->GetMouseDelta(); + + //Uint32 mouseState = SDL_GetRelativeMouseState(&mouse.x, &mouse.y); + + //printf("MOUSE %d %d\n", mouse.x, mouse.y); + + mouse.x = (mouse.x + prevMouseX) / 2; + mouse.y = (mouse.y + prevMouseY) / 2; + prevMouseX = mouse.x; + prevMouseY = mouse.y; + + if (wnd->GetMouseState(Ship::LUS_MOUSE_BTN_RIGHT)) { + // Calculate yaw (left/right) and pitch (up/down) changes + f32 yawChange = mouse.x * MOUSE_SENSITIVITY_X; + f32 pitchChange = mouse.y * MOUSE_SENSITIVITY_Y; + + // Smoothly update yaw and pitch + camera->rot[1] += (short) (yawChange * 65535.0f / (2 * M_PI)); // Yaw (left/right) + camera->rot[2] += (short) (pitchChange * 65535.0f / (2 * M_PI)); // Pitch (up/down) + + // Clamp pitch to avoid extreme values + if (camera->rot[2] > 15999) { + camera->rot[2] = 15999; + } + if (camera->rot[2] < -15999) { + camera->rot[2] = -15999; + } + + // Smoothly interpolate the lookAt position + Vec3f targetLookAt = { camera->pos[0] + forwardVector[0], camera->pos[1] + forwardVector[1], + camera->pos[2] + forwardVector[2] }; + + // Smoothing + camera->lookAt[0] += (targetLookAt[0] - camera->lookAt[0]) * gFreecamRotateSmoothingFactor; + camera->lookAt[1] += (targetLookAt[1] - camera->lookAt[1]) * gFreecamRotateSmoothingFactor; + camera->lookAt[2] += (targetLookAt[2] - camera->lookAt[2]) * gFreecamRotateSmoothingFactor; + } +} + +f32 gFreecamSpeed = 3.0f; +f32 gFreecamSpeedMultiplier = 2.0f; +bool prevPrev = false; + +#define MAX_KEYS 256 +static bool prevKeyState[MAX_KEYS] = { false }; + +// KeyDown function +bool FreecamKeyDown(int virtualKey) { + auto wnd = GameEngine::Instance->context->GetWindow(); + static bool prevKeyState[256] = { false }; // Store previous key states + bool isDownNow = false; + + if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_SDL_OPENGL) { + // Use SDL to check key states + const uint8_t* keystate = SDL_GetKeyboardState(NULL); + isDownNow = keystate[virtualKey] != 0; + } else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { + // Use Windows GetKeyState for DirectX + SHORT keyState = GetKeyState(virtualKey); + isDownNow = (keyState & 0x8000) != 0; + } + + // Determine if this is a new key press + bool isKeyDownEvent = isDownNow && !prevKeyState[virtualKey]; + + // Update the previous state for this key + prevKeyState[virtualKey] = isDownNow; + + return isKeyDownEvent; +} + + +void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { + auto wnd = GameEngine::Instance->context->GetWindow(); + float moveSpeed = gFreecamSpeed; + Controller* controller = &gControllers[0]; + + // Determine movement direction based on keys pressed + Vec3f totalMove = { 0.0f, 0.0f, 0.0f }; + + // if (keystate[SDL_SCANCODE_F]) { + // fMode = true; + // } + + // // Target a player + // if (keystate[SDL_SCANCODE_G]) { + // fTargetPlayer = false; + // } + bool TargetNextPlayer = false, TargetPreviousPlayer = false; bool prevNext; + bool Forward = false, PanLeft = false, Backward = false, PanRight = false; + bool Up = false, Down = false; + bool FastMove = false; + + // Use n64 controls for use with a controller + //! @todo configure this properly + if (gFreecamControllerType == 1) { + // Targeting /fMode is broken + // if (controller->buttonPressed & U_JPAD) { + // fMode = !fMode; + // } + // Target a player + if (controller->buttonPressed & R_TRIG) { + fTargetPlayer = !fTargetPlayer; + } + if (controller->buttonPressed & L_CBUTTONS) { + TargetPreviousPlayer = true; + } + if (controller->buttonPressed & R_CBUTTONS) { + TargetNextPlayer = true; + } + if (controller->button & A_BUTTON) { + Forward = true; + } + if (controller->button & B_BUTTON) { + Backward = true; + } + if (controller->button & L_JPAD) { + PanLeft = true; + } + if (controller->button & R_JPAD) { + PanLeft = true; + } + if (controller->button & U_CBUTTONS) { + Down = true; + } + if (controller->button & U_CBUTTONS) { + Up = true; + } + // if (controller->button ??) { + // FastMove = true; + // } + // Keyboard and mouse DX + } else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { + if (FreecamKeyDown('F')) { + fTargetPlayer = !fTargetPlayer; + } + if (FreecamKeyDown('N')) { + TargetPreviousPlayer = true; + } + if (FreecamKeyDown('M')) { + TargetNextPlayer = true; + } + if (GetKeyState('W') & 0x8000) { + Forward = true; + } + if (GetKeyState('S') & 0x8000) { + Backward = true; + } + if (GetKeyState('D') & 0x8000) { + PanRight = true; + } + if (GetKeyState('A') & 0x8000) { + PanLeft = true; + } + if (GetKeyState(VK_SPACE) & 0x8000) { + Up = true; + } + if (GetKeyState(VK_LSHIFT) & 0x8000 || GetKeyState(VK_RSHIFT) & 0x8000) { + Down = true; + } + // Fast movement with Ctrl + if (GetKeyState(VK_LCONTROL) || GetKeyState(VK_RCONTROL)) { + FastMove = true; + } + // Keyboard/mouse OpenGL/SDL + } else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_SDL_OPENGL) { + const uint8_t* keystate = SDL_GetKeyboardState(NULL); + if (FreecamKeyDown(SDL_SCANCODE_F)) { + fTargetPlayer = !fTargetPlayer; + } + if (FreecamKeyDown(SDL_SCANCODE_N)) { + TargetPreviousPlayer = true; + } + if (FreecamKeyDown(SDL_SCANCODE_M)) { + TargetNextPlayer = true; + } + if (keystate[SDL_SCANCODE_W]) { + Forward = true; + } + if (keystate[SDL_SCANCODE_S]) { + Backward = true; + } + if (keystate[SDL_SCANCODE_D]) { + PanRight = true; + } + if (keystate[SDL_SCANCODE_A]) { + PanLeft = true; + } + if (keystate[SDL_SCANCODE_SPACE]) { + Up = true; + } + if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) { + Down = true; + } + if (keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL]) { + FastMove = true; + } + } + + // Target previous player + if (TargetPreviousPlayer) { + if (fRankIndex > 0) { + fRankIndex--; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + // Target next player + if (TargetNextPlayer) { + if (fRankIndex < 7) { + fRankIndex++; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + if (FastMove) { + moveSpeed *= gFreecamSpeedMultiplier; + } + + if (Forward) { + totalMove[0] += forwardVector[0] * moveSpeed; + totalMove[2] += forwardVector[2] * moveSpeed; + } + if (Backward) { + totalMove[0] -= forwardVector[0] * moveSpeed; + totalMove[2] -= forwardVector[2] * moveSpeed; + } + if (PanRight) { + totalMove[0] -= forwardVector[2] * moveSpeed; // Pan right + totalMove[2] += forwardVector[0] * moveSpeed; + } + if (PanLeft) { + totalMove[0] += forwardVector[2] * moveSpeed; // Pan left + totalMove[2] -= forwardVector[0] * moveSpeed; + } + if (Up) { + totalMove[1] += moveSpeed; // Move up + } + if (Down) { + totalMove[1] -= moveSpeed; // Move down + } + freeCam.velocity[0] += totalMove[0]; + freeCam.velocity[1] += totalMove[1]; + freeCam.velocity[2] += totalMove[2]; +} + +void freecam_render_setup(void) { + u16 perspNorm; + Mat4 matrix; + init_rdp(); + func_802A53A4(); + init_rdp(); + func_80057FC4(0); + gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH); + gSPClearGeometryMode(gDisplayListHead++, G_CULL_BACK | G_CULL_BOTH | G_CULL_FRONT); + guPerspective(&gGfxPool->mtxPersp[0], &perspNorm, gCameraZoom[0], gScreenAspect, CourseManager_GetProps()->NearPersp, CourseManager_GetProps()->FarPersp, 1.0f); + gSPPerspNormalize(gDisplayListHead++, perspNorm); + gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxPersp[0]), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); + guLookAt(&gGfxPool->mtxLookAt[0], camera1->pos[0], camera1->pos[1], camera1->pos[2], camera1->lookAt[0], + camera1->lookAt[1], camera1->lookAt[2], camera1->up[0], camera1->up[1], camera1->up[2]); + gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxLookAt[0]), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); + mtxf_identity(matrix); + gSPSetGeometryMode(gDisplayListHead++, G_CULL_BACK); + render_set_position(matrix, 0); + init_rdp(); +} diff --git a/src/enhancements/freecam/freecam.h b/src/enhancements/freecam/freecam.h index b07edaa06..4eaa45a89 100644 --- a/src/enhancements/freecam/freecam.h +++ b/src/enhancements/freecam/freecam.h @@ -10,8 +10,6 @@ extern "C" { void freecam(Camera*, Player*, s8); void freecam_render_setup(void); -void freecam_save_state(Camera*); -void freecam_load_state(Camera*); void freecam_mouse_manager(Camera*, Vec3f); void freecam_keyboard_manager(Camera*, Vec3f); diff --git a/src/enhancements/freecam/freecam_engine.c b/src/enhancements/freecam/freecam_engine.c index bcef5380b..1116de60f 100644 --- a/src/enhancements/freecam/freecam_engine.c +++ b/src/enhancements/freecam/freecam_engine.c @@ -17,7 +17,7 @@ FreeCam freeCam; f32 gDampValue = 0.99f; -void freecam_update(Camera* camera, Vec3f forwardVector) { +void freecam_tick(Camera* camera, Vec3f forwardVector) { // Update FreeCam state camera->pos[0] += freeCam.velocity[0] * gDeltaTime; camera->pos[1] += freeCam.velocity[1] * gDeltaTime; @@ -33,49 +33,6 @@ void freecam_update(Camera* camera, Vec3f forwardVector) { camera->lookAt[2] = camera->pos[2] + forwardVector[2]; } -// Function to move the camera forward or backward without changing its height -void freecam_move_camera_forward(Camera* camera, f32 moveSpeed, Vec3f forwardVector) { - // Calculate the forward vector - forwardVector[0] = camera->lookAt[0] - camera->pos[0]; - // forwardVector[1] = 0; // Don't change the height - forwardVector[2] = camera->lookAt[2] - camera->pos[2]; - - // Normalize the forward vector - f32 length = sqrtf(forwardVector[0] * forwardVector[0] + forwardVector[2] * forwardVector[2]); - if (length != 0) { - forwardVector[0] /= length; - forwardVector[2] /= length; - } - - // Update FreeCam velocity - freeCam.velocity[0] = forwardVector[0] * moveSpeed; - freeCam.velocity[2] = forwardVector[2] * moveSpeed; - - camera->lookAt[0] = camera->pos[0] + forwardVector[0]; - camera->lookAt[2] = camera->pos[0] + forwardVector[2]; -} - -void freecam_pan_camera(Camera* camera, f32 moveSpeed) { - Vec3f rightVector; - f32 length; - - // Calculate the right vector for pan movement - rightVector[0] = camera->lookAt[2] - camera->pos[2]; - // rightVector[1] = 0; // Maintain the same height - rightVector[2] = -(camera->lookAt[0] - camera->pos[0]); - - // Normalize the right vector - length = sqrtf(rightVector[0] * rightVector[0] + rightVector[2] * rightVector[2]); - if (length != 0) { - rightVector[0] /= length; - rightVector[2] /= length; - } - - // Update camera's position - freeCam.velocity[0] = rightVector[0] * moveSpeed; - freeCam.velocity[2] = rightVector[2] * moveSpeed; -} - void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwardVector) { // Convert yaw from 0-65535 to degrees f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; @@ -91,42 +48,16 @@ void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwa forwardVector[2] = cosf(yaw); } -// Calculates the forward direction vector based on camera orientation -void freecam_calculate_forward_vector(Camera* camera, Vec3f forwardVector) { - // Convert yaw from 0-65535 to degrees - f32 yaw = (camera->rot[0] / 65535.0f) * 360.0f; - f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; - - // Convert degrees to radians - yaw = yaw * M_PI / 180.0f; - pitch = pitch * M_PI / 180.0f; - - // Calculate the forward vector based on yaw and pitch - forwardVector[0] = -sinf(yaw) * cosf(pitch); // x-component - forwardVector[1] = sinf(pitch); // y-component (vertical) - forwardVector[2] = cosf(yaw) * cosf(pitch); // z-component -} - -// Function to move the camera forward -void freecam_move_camera_up(Camera* camera, f32 distance) { - // Update the velocity of the camera in the Y direction - freeCam.velocity[1] += distance; - camera->lookAt[1] += distance; -} -void freecam_move_camera_down(Camera* camera, f32 distance) { - // Update the velocity of the camera in the Y direction - freeCam.velocity[1] += distance; - camera->lookAt[1] += distance; -} - -void freecam_target_player(Camera* camera, u32 playerIndex) { - Vec3f forwardVector; // = 2.0f; - Player* player = &gPlayers[playerIndex]; +void freecam_target_player(Camera* camera, Vec3f forwardVector) { + // Apply damping to velocity + freeCam.velocity[0] *= gDampValue; + freeCam.velocity[1] *= gDampValue; + freeCam.velocity[2] *= gDampValue; // 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]; + f32 dirX = gPlayers[fRankIndex].pos[0] - camera->pos[0]; + f32 dirY = gPlayers[fRankIndex].pos[1] - camera->pos[1]; + f32 dirZ = gPlayers[fRankIndex].pos[2] - camera->pos[2]; // Normalize the direction vector (if needed) f32 length = sqrtf(dirX * dirX + dirY * dirY + dirZ * dirZ); @@ -140,4 +71,24 @@ void freecam_target_player(Camera* camera, u32 playerIndex) { camera->lookAt[0] = camera->pos[0] + dirX; camera->lookAt[1] = camera->pos[1] + dirY; camera->lookAt[2] = camera->pos[2] + dirZ; + + // Calculate the forward vector based on the updated look-at direction + forwardVector[0] = camera->lookAt[0] - camera->pos[0]; + forwardVector[1] = camera->lookAt[1] - camera->pos[1]; + forwardVector[2] = camera->lookAt[2] - camera->pos[2]; + + // Normalize the forward vector + f32 forwardLength = sqrtf(forwardVector[0] * forwardVector[0] + + forwardVector[1] * forwardVector[1] + + forwardVector[2] * forwardVector[2]); + if (forwardLength > 0.0f) { + forwardVector[0] /= forwardLength; + forwardVector[1] /= forwardLength; + forwardVector[2] /= forwardLength; + } + + // Store or return the forward vector as needed for subsequent movement calculations + freeCam.forwardVector[0] = forwardVector[0]; + freeCam.forwardVector[1] = forwardVector[1]; + freeCam.forwardVector[2] = forwardVector[2]; } diff --git a/src/enhancements/freecam/freecam_engine.h b/src/enhancements/freecam/freecam_engine.h index 18e6d258c..7c7ecc754 100644 --- a/src/enhancements/freecam/freecam_engine.h +++ b/src/enhancements/freecam/freecam_engine.h @@ -19,16 +19,12 @@ typedef struct { extern FreeCam freeCam; extern f32 gDampValue; +extern u32 fTargetPlayer; +extern u32 fRankIndex; -// void update_camera(Camera *camera, f32 deltaTime); -void freecam_pan_camera(Camera* camera, f32 moveSpeed); void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwardVector); -void freecam_calculate_forward_vector(Camera* camera, Vec3f forwardVector); -void freecam_move_camera_forward(Camera* camera, f32 distance, Vec3f forwardVector); -void freecam_target_player(Camera* camera, u32 playerIndex); -void freecam_update(Camera* camera, Vec3f forwardVector); -void freecam_move_camera_up(Camera* camera, f32); -void freecam_move_camera_down(Camera* camera, f32); +void freecam_target_player(Camera* camera, Vec3f forwardVector); +void freecam_tick(Camera* camera, Vec3f forwardVector); extern f32 gDampValue; diff --git a/src/enhancements/freecam/n64_freecam.c b/src/enhancements/freecam/n64_freecam.c deleted file mode 100644 index 48628507a..000000000 --- a/src/enhancements/freecam/n64_freecam.c +++ /dev/null @@ -1,102 +0,0 @@ -#include -#include -#include -#include "code_80057C60.h" -#include "code_800029B0.h" -#include "code_80005FD0.h" -#include "camera.h" -#include -#include "player_controller.h" -#include "collision.h" -#include "freecam_engine.h" - -#define N64_SENSITIVITY_X 0.0003f -#define N64_SENSITIVITY_Y 0.0003f - -// Calculates the forward direction vector based on camera orientation -void freecam_n64_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 freecam_n64_move_camera_forward(Camera* camera, struct Controller* controller, f32 distance) { - Vec3f forwardVector; - Vec3f rightVector; - f32 length; - freecam_n64_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 freecam_n64_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 freecam_n64_update(Camera* camera, struct Controller* controller) { - // Calculate yaw (horizontal movement) - f32 yawChange = controller->rawStickX * N64_SENSITIVITY_X; - f32 pitchChange = controller->rawStickY * N64_SENSITIVITY_Y; - Vec3f forwardVector; - - check_bounding_collision(&camera->collision, 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 - freecam_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]; -} diff --git a/src/enhancements/freecam/n64_freecam.h b/src/enhancements/freecam/n64_freecam.h deleted file mode 100644 index 17eba53f4..000000000 --- a/src/enhancements/freecam/n64_freecam.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef N64_FREECAM_H -#define N64_FREECAM_H - -#include - -void freecam_n64_move_camera_up(Camera* camera, struct Controller* controller, f32 distance); -void freecam_n64_controller_manager(Camera* camera, struct Controller* controller, Player* player); -void freecam_n64_calculate_forward_vector(Camera* camera, Vec3f forwardVector); -void freecam_n64_move_camera_forward(Camera* camera, struct Controller* controller, f32 distance); -void freecam_n64_update(Camera* camera, struct Controller* controller); - -#endif // N64_FREECAM_H diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 6c43b48bb..262cca633 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -62,10 +62,29 @@ GameEngine::GameEngine() { } } - this->context = Ship::Context::CreateInstance("Spaghettify", "skart64", "spaghettify.cfg.json", OTRFiles, {}, 3, - { 26800, 512, 1100 }); + this->context = + Ship::Context::CreateUninitializedInstance("Spaghetti Kart", "spaghettify", "spaghettify.cfg.json"); + + this->context->InitConfiguration(); // without this line InitConsoleVariables fails at Config::Reload() + this->context->InitConsoleVariables(); // without this line the controldeck constructor failes in ShipDeviceIndexMappingManager::UpdateControllerNamesFromConfig() + + auto controlDeck = std::make_shared(); + + this->context->InitResourceManager(OTRFiles, {}, 3); // without this line InitWindow fails in Gui::Init() + this->context->InitConsole(); // without this line the GuiWindow constructor fails in ConsoleWindow::InitElement() + + auto wnd = std::make_shared(std::vector>({})); + //auto wnd = std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetWindow()); + + // Turn off texture filtering because menus are borked with it on + CVarRegisterInteger("gTextureFilter", 2); + + this->context->Init(OTRFiles, {}, 3, { 26800, 512, 1100 }, wnd, controlDeck); + + + // this->context = Ship::Context::CreateInstance("Spaghettify", "skart64", "spaghettify.cfg.json", OTRFiles, {}, 3, + // { 26800, 512, 1100 }); - auto wnd = std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetWindow()); wnd->SetRendererUCode(ucode_f3dex); this->context->InitGfxDebugger(); @@ -84,27 +103,27 @@ GameEngine::GameEngine() { loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "GenericArray", static_cast(SF64::ResourceType::GenericArray), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Texture", static_cast(LUS::ResourceType::Texture), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Texture", static_cast(LUS::ResourceType::Texture), 1); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Texture", static_cast(Fast::ResourceType::Texture), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Texture", static_cast(Fast::ResourceType::Texture), 1); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Vertex", static_cast(LUS::ResourceType::Vertex), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Vertex", - static_cast(LUS::ResourceType::Vertex), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Vertex", static_cast(Fast::ResourceType::Vertex), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Vertex", + static_cast(Fast::ResourceType::Vertex), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "DisplayList", static_cast(LUS::ResourceType::DisplayList), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, - "DisplayList", static_cast(LUS::ResourceType::DisplayList), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "DisplayList", static_cast(Fast::ResourceType::DisplayList), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "DisplayList", static_cast(Fast::ResourceType::DisplayList), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Matrix", static_cast(LUS::ResourceType::Matrix), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Blob", static_cast(LUS::ResourceType::Blob), 0); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, - "Lights1", static_cast(LUS::ResourceType::Light), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Matrix", static_cast(Fast::ResourceType::Matrix), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Blob", static_cast(Ship::ResourceType::Blob), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, + "Lights1", static_cast(Fast::ResourceType::Light), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Array", static_cast(MK64::ResourceType::MK_Array), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, @@ -233,7 +252,7 @@ void GameEngine::EndAudioFrame() { void GameEngine::AudioInit() { const auto resourceMgr = Ship::Context::GetInstance()->GetResourceManager(); - resourceMgr->LoadDirectory("sound"); + resourceMgr->LoadResources("sound"); const auto banksFiles = resourceMgr->GetArchiveManager()->ListFiles("sound/banks/*"); const auto sequences_files = resourceMgr->GetArchiveManager()->ListFiles("sound/sequences/*"); diff --git a/src/port/Engine.h b/src/port/Engine.h index f1166d89e..aab8cb8d0 100644 --- a/src/port/Engine.h +++ b/src/port/Engine.h @@ -84,3 +84,4 @@ uint32_t OTRCalculateCenterOfAreaFromLeftEdge(int32_t center); #ifdef __cplusplus } #endif + diff --git a/src/port/GBIMiddleware.cpp b/src/port/GBIMiddleware.cpp index 9b3f1e71d..3881453d1 100644 --- a/src/port/GBIMiddleware.cpp +++ b/src/port/GBIMiddleware.cpp @@ -13,7 +13,7 @@ extern "C" void gSPDisplayList(Gfx* pkt, Gfx* dl) { if (GameEngine_OTRSigCheck(imgData)) { auto resource = Ship::Context::GetInstance()->GetResourceManager()->LoadResource(imgData); - auto res = std::static_pointer_cast(resource); + auto res = std::static_pointer_cast(resource); dl = &res->Instructions[0]; } @@ -34,10 +34,10 @@ extern "C" void gSPInvalidateTexCache(Gfx* pkt, uintptr_t texAddr) { if (texAddr != 0 && GameEngine_OTRSigCheck(data)) { const auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResource(data); - const auto type = static_cast(res->GetInitData()->Type); + const auto type = static_cast(res->GetInitData()->Type); - if (res->GetInitData()->Type == static_cast(LUS::ResourceType::DisplayList)) { - texAddr = reinterpret_cast(&std::static_pointer_cast(res)->Instructions[0]); + if (res->GetInitData()->Type == static_cast(Fast::ResourceType::DisplayList)) { + texAddr = reinterpret_cast(&std::static_pointer_cast(res)->Instructions[0]); } else if (res->GetInitData()->Type == static_cast(MK64::ResourceType::MK_Array)) { texAddr = reinterpret_cast(std::static_pointer_cast(res)->Vertices.data()); } else { diff --git a/src/port/ui/FreecamWindow.cpp b/src/port/ui/FreecamWindow.cpp index 4bbfc9055..fcf532747 100644 --- a/src/port/ui/FreecamWindow.cpp +++ b/src/port/ui/FreecamWindow.cpp @@ -53,16 +53,15 @@ void FreecamWindow::DrawElement() { const float deltatime = ImGui::GetIO().DeltaTime; ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); - ImGui::Text("Freecam: Mouse/Keyboard Requires OpenGL. DX is not supported"); + ImGui::Text("Controller mode is not configured yet."); - const char* items[] = { "Mouse/Keyboard", "N64 Controls" }; + const char* items[] = { "Mouse/Keyboard", "Controller" }; static int current_item = 0; if (ImGui::Combo("Dropdown", ¤t_item, items, IM_ARRAYSIZE(items))) { gFreecamControllerType = current_item; } - ImGui::Text("Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget previous " - "player: N, Target next player: M"); + ImGui::Text("Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget Player Mode: F, Next: M, Previous: N"); ImGui::Spacing(); UIWidgets::CVarCheckbox("Enable Flycam", "gFreecam", { .tooltip = "Allows you to fly around the course" });