diff --git a/include/types.h b/include/types.h index 520ab3365..538247079 100644 --- a/include/types.h +++ b/include/types.h @@ -28,6 +28,11 @@ struct Controller { #if ENABLE_RUMBLE /*0x1C*/ s32 port; #endif + /*0x20*/ s16 rawStick2X; + /*0x22*/ s16 rawStick2Y; + /*0x24*/ float stick2X; // [-64, 64] positive is right + /*0x28*/ float stick2Y; // [-64, 64] positive is up + /*0x2C*/ float stick2Mag; // distance from center [0, 64] }; typedef f32 Vec2f[2]; diff --git a/src/game/camera.c b/src/game/camera.c index fb9dced10..3c3ecd3e5 100644 --- a/src/game/camera.c +++ b/src/game/camera.c @@ -839,7 +839,7 @@ s16 look_down_slopes(s16 camYaw) { * * Since this function only affects the camera's focus, Mario's movement direction isn't affected. */ -void pan_ahead_of_player(struct Camera *c) { +void pan_ahead_of_player(struct Camera *c, u32 panLess) { f32 dist; s16 pitch; s16 yaw; @@ -849,7 +849,7 @@ void pan_ahead_of_player(struct Camera *c) { 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; + pan[2] = sins(panLess ? 0x600 : 0xC00) * dist; rotate_in_xz(pan, pan, sMarioCamState->faceAngle[1]); // rotate in the opposite direction @@ -937,7 +937,7 @@ s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) { 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); + pan_ahead_of_player(c, FALSE); if (gCurrLevelArea == AREA_DDD_SUB) { camYaw = clamp_positions_and_find_yaw(pos, focus, 6839.f, 995.f, 5994.f, -3945.f); } @@ -945,6 +945,55 @@ s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) { return camYaw; } +/** + * Update the camera position/focus for manual (custom) camera mode. + * Computes distance based on zoom state and pitch angle, then focuses on Mario. + * Steps the camera closer to Mario if walls obstruct the view. + */ +s32 update_custom_camera(struct Camera *c, Vec3f focus, Vec3f pos, f32 yOff, f32 additionalDistance, s8 dynamic) { + s16 camYaw = sModeOffsetYaw; + s16 pitch = 0.0f; + f32 posY; + f32 focusY; + struct WallCollisionData colData; + s32 i; + + f32 dist = (sLakituDist + (((gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) ? CAMERA_DISTANCE_ZOOMED_OUT : CAMERA_DISTANCE) + ADDITIONAL_CAMERA_DISTANCE) * 10.0f + additionalDistance) + * (((MIN(sLakituPitch, VERTICAL_MAX_PITCH) + VERTICAL_MIN) / (VERTICAL_MIN + VERTICAL_MAX_PITCH)) * 1.25f + 0.5f); + + 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, dist, pitch, camYaw); + + // Walk from camera toward Mario checking for walls. If a wall is found, + // pull the camera closer to avoid clipping through geometry. + for (i = 0; i < 5; i++) { + f32 checkDist = dist * (1.0f - (f32)i / 5.0f); + colData.x = sMarioCamState->pos[0] + (pos[0] - sMarioCamState->pos[0]) * (checkDist / dist); + colData.y = sMarioCamState->pos[1] + (pos[1] - sMarioCamState->pos[1]) * (checkDist / dist); + colData.z = sMarioCamState->pos[2] + (pos[2] - sMarioCamState->pos[2]) * (checkDist / dist); + colData.radius = 150.0f; + colData.offsetY = 100.0f; + + if (find_wall_collisions(&colData) != 0) { + // Found a wall — pull camera to just before the collision point + f32 pullDist = checkDist * 0.85f; + if (pullDist < dist) { + dist = pullDist; + focus_on_mario(focus, pos, posY + yOff, focusY + yOff, dist, pitch, camYaw); + } + break; + } + } + + if (dynamic) { + pan_ahead_of_player(c, TRUE); + } + + return camYaw; +} + /** * Moves the camera for the radial and outward radial camera modes. * @@ -1105,17 +1154,22 @@ void lakitu_zoom(f32 rangeDist, s16 rangePitch) { } } - if (gCurrLevelArea == AREA_SSL_PYRAMID && gCamera->mode == CAMERA_MODE_OUTWARD_RADIAL) { - rangePitch /= 2; - } - - if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { - if ((sLakituPitch += rangePitch / 13) > rangePitch) { - sLakituPitch = rangePitch; - } + // When rangePitch is 0, use right analog stick for free vertical control + if (rangePitch == 0) { + sLakituPitch = MIN(MAX(sLakituPitch + ANALOG_AMOUNT_VERTICAL * (gPlayer1Controller->stick2Y / 48.0f) * CAMERA_SPEED, -VERTICAL_MIN), VERTICAL_MAX); } else { - if ((sLakituPitch -= rangePitch / 13) < 0) { - sLakituPitch = 0; + if (gCurrLevelArea == AREA_SSL_PYRAMID && gCamera->mode == CAMERA_MODE_OUTWARD_RADIAL) { + rangePitch /= 2; + } + + if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { + if ((sLakituPitch += rangePitch / 13) > rangePitch) { + sLakituPitch = rangePitch; + } + } else { + if ((sLakituPitch -= rangePitch / 13) < 0) { + sLakituPitch = 0; + } } } } @@ -1164,7 +1218,7 @@ void mode_radial_camera(struct Camera *c) { pos[1] += 500.f; } set_camera_height(c, pos[1]); - pan_ahead_of_player(c); + pan_ahead_of_player(c, FALSE); } /** @@ -1194,6 +1248,96 @@ void mode_8_directions_camera(struct Camera *c) { set_camera_height(c, pos[1]); } +/** + * Manual/free camera mode input processing. + * Handles C-button rotation, right analog stick, L-trigger centering, + * auto-rotation, vertical control, and wall avoidance. + */ +void mode_custom_camera(struct Camera *c, f32 yOff, f32 additionalDistance, s8 limitedAngle, s8 autoRotate, s8 dynamic) { + Vec3f pos; + s16 oldAreaYaw = sAreaYaw; + s16 avoidYaw; + + // Right stick is often mapped to C-buttons at the controller config level. + // To avoid double-processing, only handle C-button L/R rotation when the + // right stick is idle (allows keyboard C-buttons to still work). + s8 rightStickActive = (gPlayer1Controller->stick2X != 0 || gPlayer1Controller->stick2Y != 0); + + // Right analog stick horizontal rotation (primary input) + if (gPlayer1Controller->stick2X != 0 && gCurrDemoInput == NULL) { + sModeOffsetYaw -= ANALOG_AMOUNT * (gPlayer1Controller->stick2X / 32.0f) * CAMERA_SPEED; + } + + // C-button L/R rotation (fallback for keyboard users, skipped when right stick is active) + if (!rightStickActive) { + if (IMPROVED_C_BUTTON_CAMERA) { + if (MANUAL_CAMERA_SOUNDS && (gPlayer1Controller->buttonPressed & (L_CBUTTONS | R_CBUTTONS))) { + play_sound_cbutton_side(); + } + if (gPlayer1Controller->buttonDown & L_CBUTTONS) { + sModeOffsetYaw -= ANALOG_AMOUNT * CAMERA_SPEED * 2.0f; + } + if (gPlayer1Controller->buttonDown & R_CBUTTONS) { + sModeOffsetYaw += ANALOG_AMOUNT * CAMERA_SPEED * 2.0f; + } + } else { + if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { + sModeOffsetYaw += DEGREES(22.5); + if (MANUAL_CAMERA_SOUNDS) play_sound_cbutton_side(); + } + if (gPlayer1Controller->buttonPressed & L_CBUTTONS) { + sModeOffsetYaw -= DEGREES(22.5); + if (MANUAL_CAMERA_SOUNDS) play_sound_cbutton_side(); + } + } + } + + // L-trigger centers camera behind Mario + if (CENTER_CAMERA_BUTTON) { + if (gPlayer1Controller->buttonPressed & L_TRIG) { + sModeOffsetYaw = sMarioCamState->faceAngle[1] + DEGREES(180); + if (MANUAL_CAMERA_SOUNDS) play_sound_rbutton_changed(); + } + if (gPlayer1Controller->buttonDown & L_TRIG) { + camera_approach_s16_symmetric_bool(&sModeOffsetYaw, sMarioCamState->faceAngle[1] + DEGREES(180), LROTATE_SPEED); + } + } + + // Auto-rotate camera to follow Mario's movement direction + if (autoRotate) { + camera_approach_s16_symmetric_bool(&sModeOffsetYaw, sMarioCamState->faceAngle[1] + DEGREES(180), + ABS(gMarioState->forwardVel + * ((gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) ? 8.0f : 12.0f) + * (1.0f - MIN(MAX(sqrt((sLakituPitch + VERTICAL_MIN) / (VERTICAL_MAX + VERTICAL_MIN)), 0.0f), 0.75f)) + * (MIN(MAX(sqr(abs_angle_diff(sModeOffsetYaw, sMarioCamState->faceAngle[1] + DEGREES(180)) / 180), 0.0f), 1.0f)) + )); + } + + lakitu_zoom(0, VERTICAL_ANALOG_CAMERA ? 0 : 0x900); + + if (limitedAngle) { + sLakituPitch = MIN(MAX(sLakituPitch, -VERTICAL_MIN), VERTICAL_MAX_LIMITED); + } + + c->nextYaw = update_custom_camera(c, c->focus, pos, yOff, additionalDistance, dynamic); + c->pos[0] = pos[0]; + c->pos[1] = pos[1]; + c->pos[2] = pos[2]; + sAreaYawChange = sAreaYaw - oldAreaYaw; + set_camera_height(c, pos[1]); + + if (dynamic) { + s32 wallStatus = rotate_camera_around_walls(c, c->pos, &avoidYaw, 0x600); + if (wallStatus == 3) { + // Wall is directly blocking Mario — rotate aggressively + approach_s16_asymptotic_bool(&sModeOffsetYaw, avoidYaw, 10); + } else if (wallStatus > 0) { + // Wall is nearby — rotate gently + camera_approach_s16_symmetric_bool(&sModeOffsetYaw, avoidYaw, 0x600); + } + } +} + /** * Updates the camera in outward radial mode. * sModeOffsetYaw is calculated in radial_camera_move, which calls offset_yaw_outward_radial @@ -1237,7 +1381,7 @@ void mode_outward_radial_camera(struct Camera *c) { pos[1] += 500.f; } set_camera_height(c, pos[1]); - pan_ahead_of_player(c); + pan_ahead_of_player(c, FALSE); } /** @@ -1738,7 +1882,7 @@ void mode_fixed_camera(struct Camera *c) { } c->nextYaw = update_fixed_camera(c, c->focus, c->pos); c->yaw = c->nextYaw; - pan_ahead_of_player(c); + pan_ahead_of_player(c, FALSE); vec3f_set(sCastleEntranceOffset, 0.f, 0.f, 0.f); } @@ -1936,7 +2080,7 @@ s32 mode_behind_mario(struct Camera *c) { distCamToFocus = 800.f; vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw); } - pan_ahead_of_player(c); + pan_ahead_of_player(c, FALSE); return yaw; } @@ -2382,7 +2526,7 @@ s16 update_default_camera(struct Camera *c) { void mode_default_camera(struct Camera *c) { set_fov_function(CAM_FOV_DEFAULT); c->nextYaw = update_default_camera(c); - pan_ahead_of_player(c); + pan_ahead_of_player(c, FALSE); } /** @@ -3001,6 +3145,42 @@ void update_lakitu(struct Camera *c) { } +/** + * Dispatcher for manual camera mode. + * Routes to mode_custom_camera with per-context parameters. + * Clamps pitch in enclosed/indoor areas to prevent wall clipping. + */ +void manual_cam_modes(struct Camera *c) { + switch (c->mode) { + case CAMERA_MODE_8_DIRECTIONS: + mode_custom_camera(c, 150.0f, 256.0f, TRUE, FALSE, TRUE); + break; + case CAMERA_MODE_BEHIND_MARIO: + mode_custom_camera(c, -75.0f, 0.0f, FALSE, TRUE, TRUE); + break; + case CAMERA_MODE_C_UP: + mode_c_up_camera(c); + break; + case CAMERA_MODE_WATER_SURFACE: + mode_custom_camera(c, 75.0f, 0.0f, FALSE, TRUE, TRUE); + break; + case CAMERA_MODE_INSIDE_CANNON: + mode_cannon_camera(c); + break; + // Indoor/enclosed area modes — limit pitch to prevent excessive zoom + case CAMERA_MODE_CLOSE: + case CAMERA_MODE_FIXED: + case CAMERA_MODE_SPIRAL_STAIRS: + case CAMERA_MODE_PARALLEL_TRACKING: + sLakituPitch = MIN(MAX(sLakituPitch, -VERTICAL_MIN), VERTICAL_MAX_LIMITED); + mode_custom_camera(c, 150.0f, 0.0f, TRUE, TRUE, TRUE); + break; + default: + mode_custom_camera(c, 150.0f, 0.0f, FALSE, TRUE, TRUE); + break; + } +} + /** * The main camera update function. * Gets controller input, checks for cutscenes, handles mode changes, and moves the camera @@ -3085,81 +3265,99 @@ void update_camera(struct Camera *c) { sYawSpeed = 0x400; if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) { - switch (c->mode) { - case CAMERA_MODE_BEHIND_MARIO: - mode_behind_mario_camera(c); + // Alternate camera mode (R-trigger toggled) + switch (ALTERNATE_CAMERA_MODE) { + case 2: + manual_cam_modes(c); break; + case 1: + default: + 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_C_UP: + mode_c_up_camera(c); + break; - case CAMERA_MODE_WATER_SURFACE: - mode_water_surface_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_INSIDE_CANNON: + mode_cannon_camera(c); + break; - default: - mode_mario_camera(c); + default: + mode_mario_camera(c); + } + break; } } else { - switch (c->mode) { - case CAMERA_MODE_BEHIND_MARIO: - mode_behind_mario_camera(c); + // Default camera mode + switch (DEFAULT_CAMERA_MODE) { + case 2: + manual_cam_modes(c); break; + case 1: + default: + 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_C_UP: + mode_c_up_camera(c); + break; - case CAMERA_MODE_WATER_SURFACE: - mode_water_surface_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_INSIDE_CANNON: + mode_cannon_camera(c); + break; - case CAMERA_MODE_8_DIRECTIONS: - mode_8_directions_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_RADIAL: + mode_radial_camera(c); + break; - case CAMERA_MODE_OUTWARD_RADIAL: - mode_outward_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_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_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_PARALLEL_TRACKING: + mode_parallel_tracking_camera(c); + break; - case CAMERA_MODE_SLIDE_HOOT: - mode_slide_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_FIXED: + mode_fixed_camera(c); + break; - case CAMERA_MODE_SPIRAL_STAIRS: - mode_spiral_stairs_camera(c); + case CAMERA_MODE_SPIRAL_STAIRS: + mode_spiral_stairs_camera(c); + break; + } break; } } diff --git a/src/game/camera.h b/src/game/camera.h index e6815f270..4d16db8db 100644 --- a/src/game/camera.h +++ b/src/game/camera.h @@ -9,6 +9,7 @@ #include "engine/graph_node.h" #include "level_table.h" +#include "camera_settings.h" /** * @file camera.h diff --git a/src/game/camera_settings.h b/src/game/camera_settings.h new file mode 100644 index 000000000..03b578962 --- /dev/null +++ b/src/game/camera_settings.h @@ -0,0 +1,27 @@ +#ifndef CAMERA_SETTINGS_H +#define CAMERA_SETTINGS_H + +#define DEFAULT_CAMERA_MODE CVarGetInteger("gEnhancements.Camera.DefaultMode", 0) +#define ALTERNATE_CAMERA_MODE CVarGetInteger("gEnhancements.Camera.AlternateMode", 0) +#define HORIZONTAL_ANALOG_CAMERA CVarGetInteger("gEnhancements.Camera.HorizontalAnalog", 0) +#define VERTICAL_ANALOG_CAMERA CVarGetInteger("gEnhancements.Camera.VerticalAnalog", 0) +#define IMPROVED_C_BUTTON_CAMERA CVarGetInteger("gEnhancements.Camera.ImprovedCButtonCamera", 0) +#define CENTER_CAMERA_BUTTON CVarGetInteger("gEnhancements.Camera.CenterCameraButton", 0) +#define INVERTED_HORIZONTAL_CAMERA CVarGetInteger("gEnhancements.Camera.InvertedHorizontalCamera", 0) +#define INVERTED_VERTICAL_CAMERA CVarGetInteger("gEnhancements.Camera.InvertedVerticalCamera", 0) +#define CAMERA_SPEED CVarGetFloat("gEnhancements.Camera.CameraSpeed", 32.0) +#define CAMERA_DISTANCE CVarGetFloat("gEnhancements.Camera.CameraDistance", 100) +#define CAMERA_DISTANCE_ZOOMED_OUT CVarGetFloat("gEnhancements.Camera.CameraDistanceZoomedOut", 150) +#define ADDITIONAL_CAMERA_DISTANCE CVarGetFloat("gEnhancements.Camera.AdditionalCameraDistance", 0) +#define MANUAL_CAMERA_SOUNDS CVarGetInteger("gEnhancements.Camera.ManualCameraSounds", 1) + +#define ANALOG_AMOUNT (12 * (1 - INVERTED_HORIZONTAL_CAMERA * 2)) +#define ANALOG_AMOUNT_VERTICAL (12 * (1 - INVERTED_VERTICAL_CAMERA * 2)) +#define LROTATE_SPEED 0x400 + +#define VERTICAL_MIN 6144.0f +#define VERTICAL_MAX 12288.0f +#define VERTICAL_MAX_LIMITED 8192.0f +#define VERTICAL_MAX_PITCH 9216.0f + +#endif // CAMERA_SETTINGS_H diff --git a/src/game/game_init.c b/src/game/game_init.c index 0a2f40b91..4f3a56eed 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -438,8 +438,10 @@ void adjust_analog_stick(struct Controller *controller) { // Reset the controller's x and y floats. controller->stickX = 0; controller->stickY = 0; + controller->stick2X = 0; + controller->stick2Y = 0; - // Modulate the rawStickX and rawStickY to be the new f32 values by adding/subtracting 6. + // Modulate the rawStickX, rawStickY, rawStick2X and rawStick2Y to be the new f32 values by adding/subtracting 6. if (controller->rawStickX <= -8) { controller->stickX = controller->rawStickX + 6; } @@ -456,10 +458,29 @@ void adjust_analog_stick(struct Controller *controller) { controller->stickY = controller->rawStickY - 6; } + if (controller->rawStick2X <= -8) { + controller->stick2X = controller->rawStick2X + 6; + } + + if (controller->rawStick2X >= 8) { + controller->stick2X = controller->rawStick2X - 6; + } + + if (controller->rawStick2Y <= -8) { + controller->stick2Y = controller->rawStick2Y + 6; + } + + if (controller->rawStick2Y >= 8) { + controller->stick2Y = controller->rawStick2Y - 6; + } + // Calculate f32 magnitude from the center by vector length. controller->stickMag = sqrtf(controller->stickX * controller->stickX + controller->stickY * controller->stickY); + controller->stick2Mag = + sqrtf(controller->stick2X * controller->stick2X + controller->stick2Y * controller->stick2Y); + // Magnitude cannot exceed 64.0f: if it does, modify the values // appropriately to flatten the values down to the allowed maximum value. if (controller->stickMag > 64) { @@ -467,6 +488,12 @@ void adjust_analog_stick(struct Controller *controller) { controller->stickY *= 64 / controller->stickMag; controller->stickMag = 64; } + + if (controller->stick2Mag > 64) { + controller->stick2X *= 64 / controller->stick2Mag; + controller->stick2Y *= 64 / controller->stick2Mag; + controller->stick2Mag = 64; + } } /** @@ -551,6 +578,8 @@ void read_controller_inputs(void) { if (controller->controllerData != NULL) { controller->rawStickX = controller->controllerData->stick_x; controller->rawStickY = controller->controllerData->stick_y; + controller->rawStick2X = controller->controllerData->right_stick_x; + controller->rawStick2Y = controller->controllerData->right_stick_y; controller->buttonPressed = controller->controllerData->button & (controller->controllerData->button ^ controller->buttonDown); // 0.5x A presses are a good meme @@ -562,11 +591,16 @@ void read_controller_inputs(void) { } else { // otherwise, if the controllerData is NULL, 0 out all of the inputs. controller->rawStickX = 0; controller->rawStickY = 0; + controller->rawStick2X = 0; + controller->rawStick2Y = 0; controller->buttonPressed = 0; controller->buttonDown = 0; controller->stickX = 0; controller->stickY = 0; controller->stickMag = 0; + controller->stick2X = 0; + controller->stick2Y = 0; + controller->stick2Mag = 0; } } @@ -578,6 +612,11 @@ void read_controller_inputs(void) { gPlayer3Controller->stickX = gPlayer1Controller->stickX; gPlayer3Controller->stickY = gPlayer1Controller->stickY; gPlayer3Controller->stickMag = gPlayer1Controller->stickMag; + gPlayer3Controller->rawStick2X = gPlayer1Controller->rawStick2X; + gPlayer3Controller->rawStick2Y = gPlayer1Controller->rawStick2Y; + gPlayer3Controller->stick2X = gPlayer1Controller->stick2X; + gPlayer3Controller->stick2Y = gPlayer1Controller->stick2Y; + gPlayer3Controller->stick2Mag = gPlayer1Controller->stick2Mag; gPlayer3Controller->buttonPressed = gPlayer1Controller->buttonPressed; gPlayer3Controller->buttonDown = gPlayer1Controller->buttonDown; } @@ -732,4 +771,4 @@ void thread5_iteration(void){ #if ENABLE_RUMBLE create_thread_6(); #endif -} \ No newline at end of file +} diff --git a/src/game/mario.c b/src/game/mario.c index e13e0b77a..521772d64 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -789,9 +789,12 @@ static u32 set_mario_action_airborne(struct MarioState *m, u32 action, u32 actio m->forwardVel *= 0.8f; break; - case ACT_FLYING_TRIPLE_JUMP: - set_mario_y_vel_based_on_fspeed(m, 82.0f, 0.0f); + case ACT_FLYING_TRIPLE_JUMP: { + f32 launchVelocity = 82.0f; + CALL_EVENT(FlyingTripleJumpLaunch, m, &launchVelocity); + set_mario_y_vel_based_on_fspeed(m, launchVelocity, 0.0f); break; + } case ACT_WATER_JUMP: case ACT_HOLD_WATER_JUMP: diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index 1815424ac..e5ce0556b 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -14,6 +14,7 @@ #include "save_file.h" #include "rumble_init.h" #include "seq_ids.h" +#include "port/events/list/PlayerEvent.h" void play_flip_sounds(struct MarioState *m, s16 frame1, s16 frame2, s16 frame3) { s32 animFrame = m->marioObj->header.gfx.animInfo.animFrame; @@ -1709,7 +1710,9 @@ s32 act_flying(struct MarioState *m) { return set_mario_action(m, ACT_GROUND_POUND, 1); } - if (!(m->flags & MARIO_WING_CAP)) { + bool canFly = (m->flags & MARIO_WING_CAP) != 0; + CALL_EVENT(FlyingActionUpdate, m, &canFly); + if (!canFly) { if (m->area->camera->mode == CAMERA_MODE_BEHIND_MARIO) { set_camera_mode(m->area->camera, m->area->camera->defMode, 1); } diff --git a/src/game/mario_actions_moving.c b/src/game/mario_actions_moving.c index 884a23ede..413201e6c 100644 --- a/src/game/mario_actions_moving.c +++ b/src/game/mario_actions_moving.c @@ -13,6 +13,7 @@ #include "behavior_data.h" #include "rumble_init.h" #include "seq_ids.h" +#include "port/events/list/PlayerEvent.h" struct LandingAction { s16 numFrames; @@ -146,7 +147,10 @@ void slide_bonk(struct MarioState *m, u32 fastAction, u32 slowAction) { } s32 set_triple_jump_action(struct MarioState *m, UNUSED u32 action, UNUSED u32 actionArg) { - if (m->flags & MARIO_WING_CAP) { + bool useFlyingVariant = (m->flags & MARIO_WING_CAP) != 0; + CALL_EVENT(SetTripleJumpAction, m, &useFlyingVariant); + + if (useFlyingVariant) { return set_mario_action(m, ACT_FLYING_TRIPLE_JUMP, 0); } else if (m->forwardVel > 20.0f) { return set_mario_action(m, ACT_TRIPLE_JUMP, 0); diff --git a/src/port/events/list/PlayerEvent.h b/src/port/events/list/PlayerEvent.h index 75db37e2e..a6fe7a26c 100644 --- a/src/port/events/list/PlayerEvent.h +++ b/src/port/events/list/PlayerEvent.h @@ -51,6 +51,21 @@ DEFINE_EVENT(PlayerSetAction, u32 arg; ); +DEFINE_EVENT(SetTripleJumpAction, + struct MarioState* m; + bool* useFlyingVariant; +); + +DEFINE_EVENT(FlyingActionUpdate, + struct MarioState* m; + bool* canFly; +); + +DEFINE_EVENT(FlyingTripleJumpLaunch, + struct MarioState* m; + f32* launchVelocity; +); + DEFINE_EVENT(ItemCollected, int16_t type; struct MarioState* marioState; diff --git a/src/port/mods/PortEnhancements.cpp b/src/port/mods/PortEnhancements.cpp index f3b022e60..ba790ee13 100644 --- a/src/port/mods/PortEnhancements.cpp +++ b/src/port/mods/PortEnhancements.cpp @@ -84,6 +84,28 @@ void PortEnhancements_Init() { } }); + REGISTER_LISTENER(SetTripleJumpAction, EVENT_PRIORITY_NORMAL, [](IEvent* event) { + if (CVarGetInteger("gCheats.AlwaysFlyTripleJump", 0) == 0) { + return; + } + SetTripleJumpAction* ev = (SetTripleJumpAction*)event; + *ev->useFlyingVariant = true; + }); + REGISTER_LISTENER(FlyingActionUpdate, EVENT_PRIORITY_NORMAL, [](IEvent* event) { + if (CVarGetInteger("gCheats.AlwaysFlyTripleJump", 0) == 0) { + return; + } + FlyingActionUpdate* ev = (FlyingActionUpdate*)event; + *ev->canFly = true; + }); + REGISTER_LISTENER(FlyingTripleJumpLaunch, EVENT_PRIORITY_NORMAL, [](IEvent* event) { + if (CVarGetInteger("gCheats.FlyingTripleJumpHighLaunch", 0) == 0) { + return; + } + FlyingTripleJumpLaunch* ev = (FlyingTripleJumpLaunch*)event; + *ev->launchVelocity = 246.0f; + }); + auto OnDistanceFunc = [](IEvent* event) { if (CVarGetInteger("gEnhancements.DisableDrawDistance", 0) == 0) { return; @@ -127,6 +149,9 @@ void PortEnhancements_Register() { REGISTER_EVENT(PlayerDeath); REGISTER_EVENT(PlayerSetAction); REGISTER_EVENT(PlayerExecuteAction); + REGISTER_EVENT(SetTripleJumpAction); + REGISTER_EVENT(FlyingActionUpdate); + REGISTER_EVENT(FlyingTripleJumpLaunch); REGISTER_EVENT(PlayerCheckCommonAirborneCancels); REGISTER_EVENT(PlayerLanded); REGISTER_EVENT(PlayerHit); diff --git a/src/port/ui/GhostshipMenuEnhancements.cpp b/src/port/ui/GhostshipMenuEnhancements.cpp index e6b1ff7f2..b2a555875 100644 --- a/src/port/ui/GhostshipMenuEnhancements.cpp +++ b/src/port/ui/GhostshipMenuEnhancements.cpp @@ -20,6 +20,12 @@ static std::unordered_map hudAspects = { { 6, "Ultrawide (21:9)" }, }; +static std::unordered_map cameraModes = { + { 0, "Lakitu" }, + { 1, "Mario" }, + { 2, "Manual" }, +}; + using namespace UIWidgets; void GhostshipMenu::AddMenuEnhancements() { @@ -124,6 +130,66 @@ void GhostshipMenu::AddMenuEnhancements() { AddSidebarEntry("Enhancements", path.sidebarName, 1); path.column = SECTION_COLUMN_1; + AddWidget(path, "Camera", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Default Camera Mode", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("Camera.DefaultMode")) + .RaceDisable(false) + .Options(ComboboxOptions().Tooltip("Primary camera mode.").ComboMap(cameraModes).DefaultIndex(0)); + AddWidget(path, "Alternate Camera Mode", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("Camera.AlternateMode")) + .RaceDisable(false) + .Options( + ComboboxOptions().Tooltip("Alternate camera (R-trigger toggle)").ComboMap(cameraModes).DefaultIndex(0)); + AddWidget(path, "Horizontal Analog Camera", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.HorizontalAnalog")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Enable horizontal analog camera")); + AddWidget(path, "Vertical analog camera", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.VerticalAnalog")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Enable vertical analog camera")); + AddWidget(path, "Improved C-button Camera", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.ImprovedCButtonCamera")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Enables smooth C-button rotation (hold = continuous)")); + AddWidget(path, "Center camera button", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.CenterCameraButton")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("L-trigger centers camera behind Mario")); + AddWidget(path, "Inverted horizontal camera", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.InvertedHorizontalCamera")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Invert horizontal camera")); + AddWidget(path, "Inverted vertical camera", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.InvertedVerticalCamera")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Invert vertical camera")); + AddWidget(path, "Camera Speed", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("Camera.CameraSpeed")) + .RaceDisable(false) + .Options(FloatSliderOptions().Min(1.0).Max(100.0).DefaultValue(32.0).ShowButtons(true).Tooltip( + "Analog camera sensitivity multiplier")); + AddWidget(path, "Camera distance", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("Camera.CameraDistance")) + .RaceDisable(false) + .Options(FloatSliderOptions().Min(1.0).Max(1000.0).DefaultValue(100.0).ShowButtons(true).Tooltip( + "Normal zoom distance")); + AddWidget(path, "Camera distance zoomed out", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("Camera.CameraDistanceZoomedOut")) + .RaceDisable(false) + .Options(FloatSliderOptions().Min(1.0).Max(1000.0).DefaultValue(150.0).ShowButtons(true).Tooltip( + "Zoomed-out distance")); + AddWidget(path, "Additional camera distance", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("Camera.AdditionalCameraDistance")) + .RaceDisable(false) + .Options(FloatSliderOptions().Min(1.0).Max(1000.0).DefaultValue(0.0).ShowButtons(true).Tooltip( + "Extra uniform distance offset")); + AddWidget(path, "Manual camera sounds", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Camera.ManualCameraSounds")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Play sounds when using manual camera controls (C-buttons, L-trigger)")); + + AddWidget(path, "Miscellaneous", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Disable LoD", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DisableLOD")) .RaceDisable(false) @@ -177,6 +243,14 @@ void GhostshipMenu::AddMenuEnhancements() { .CVar(CVAR_CHEAT("PlayInDemo")) .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Allows normal gameplay when a demo is playing.")); + AddWidget(path, "Always Fly on Triple Jump", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("AlwaysFlyTripleJump")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Triple jumping always starts flying, as if you always have the Wing Cap.")); + AddWidget(path, "Triple Jump High Launch", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FlyingTripleJumpHighLaunch")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip("Flying triple jump launches 3x higher than normal.")); } } // namespace GhostshipGui