diff --git a/README.md b/README.md index cda57beb68..e422ac4b67 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,7 @@ Thanks to Frame#5375 and AloXado320 for also helping with silhouette stuff - Removed course-specific camera processing * - Ability to set Mario's movement speed when hanging from a ceiling * - Tighter hanging controls (Mario will face the direction of the analog stick directly while hanging from a ceiling) * -- reonucam3: custom camera by Reonu. This is included as a .patch file in the enhancements folder, you need to apply it if you want this camera. - This video shows a rundown of the features: https://youtu.be/TQNkznX9Z3k +- Reonucam: Custom camera by Reonu. Now included as a define in config_camera.h. - Ability to disable Mario getting suck in snow or sand **Hacker QOL:** diff --git a/include/config/config_camera.h b/include/config/config_camera.h index 3fea62ba16..6e60cfdf27 100644 --- a/include/config/config_camera.h +++ b/include/config/config_camera.h @@ -40,21 +40,25 @@ #define FAST_VERTICAL_CAMERA_MOVEMENT /** - * Enables "parallel lakitu camera" or "aglab cam" which lets you move the camera smoothly with the D-pad. + * Enables camera collision for 8 direction camera and, by extension, to Parallel Lakitu cam or Reonucam if enabled. + * If you enable it, please consider using surface types with the SURFACE_FLAG_NO_CAM_COLLISION flag for small obstacles, + * such as fences, pillars, signs, etc, in order to make your game more enjoyable and not let the camera get in the way of gameplay. */ -#define PARALLEL_LAKITU_CAM +#define EIGHT_DIR_CAMERA_COLLISION /** - * Enables Puppy Camera 2, a rewritten camera that can be freely configured and modified. + * Enables "parallel lakitu camera" or "aglab cam" which lets you move the camera smoothly with the D-pad. Will be disabled if Reonucam is enabled. */ -// #define PUPPYCAM +#define PARALLEL_LAKITU_CAM + +// Enables Reonucam, a custom camera that aims to be a more feature-rich "aglabcam" that doesn't use a single button more than the vanilla camera. +// An explanation the features can be seen here: https://www.youtube.com/watch?v=TQNkznX9Z3k (please note that the analog feature shown at the end is no longer present) +//#define REONUCAM /** - * Note: Reonucam is available, but because we had no time to test it properly, it's included as a patch rather than being in the code by default. - * Run this command to apply the patch if you want to use it: - * tools/apply_patch.sh enhancements/reonucam.patch - * Consider it a beta, but it should work fine. Please report any bugs with it. Applying the patch will simply add a define here, so you can still turn it off even after patching. + * Enables Puppy Camera 2, a rewritten camera that can be freely configured and modified. */ +//#define PUPPYCAM /**********************************/ /***** Vanilla config options *****/ diff --git a/include/config/config_safeguards.h b/include/config/config_safeguards.h index 8462ec10c2..a8ad664e81 100644 --- a/include/config/config_safeguards.h +++ b/include/config/config_safeguards.h @@ -139,6 +139,27 @@ #define FLYING_CAMERA_MODE CAMERA_MODE_BEHIND_MARIO #endif // !FLYING_CAMERA_MODE +// Reonucam overrides +#ifdef REONUCAM + // Use course default mode + #ifndef USE_COURSE_DEFAULT_MODE + #define USE_COURSE_DEFAULT_MODE + #endif + + // Force camera mode to 8 Dir + #ifdef FORCED_CAMERA_MODE + #undef FORCED_CAMERA_MODE + #endif + #define FORCED_CAMERA_MODE CAMERA_MODE_8_DIRECTIONS + + // Disable vanilla cam processing + #undef ENABLE_VANILLA_CAM_PROCESSING + + // Disable aglab cam + #undef PARALLEL_LAKITU_CAM +#endif + + /***************** * config_game.h diff --git a/src/game/camera.c b/src/game/camera.c index 8cd629d0af..2fd5640c75 100644 --- a/src/game/camera.c +++ b/src/game/camera.c @@ -75,6 +75,10 @@ * */ +#ifdef REONUCAM +struct ReonucamState gReonucamState = { 2, FALSE, FALSE, FALSE, 0, 0, }; +#endif + // BSS /** * Stores Lakitu's position from the last frame, used for transitioning in next_lakitu_state() @@ -875,6 +879,25 @@ s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) { f32 yOff = 125.f; f32 baseDist = 1000.f; +#ifdef REONUCAM + if (gMarioState->action & ACT_FLAG_SWIMMING) { + yOff = -125.f; + } else if (gMarioState->action & ACT_FLAG_SWIMMING_OR_FLYING) { + yOff = 325.f; + } else { + yOff = 125.f; + } + + if ((gPlayer1Controller->buttonDown & R_TRIG) && (gPlayer1Controller->buttonDown & U_CBUTTONS)) { + gReonucamState.keepCliffCam = 1; + pitch = DEGREES(60); + } else if (((gPlayer1Controller->buttonDown & U_CBUTTONS) || (gPlayer1Controller->buttonDown & R_TRIG)) && gReonucamState.keepCliffCam) { + pitch = DEGREES(60); + } else { + gReonucamState.keepCliffCam = 0; + } +#endif + 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); @@ -1116,13 +1139,117 @@ s32 snap_to_45_degrees(s16 angle) { return angle; } +#ifdef EIGHT_DIR_CAMERA_COLLISION + +#define MIN_CAMERA_DISTANCE 300.0f // Minimum distance between Mario and the camera. +#define VERTICAL_RAY_OFFSET 300.0f // The ray is cast from 300 units above Mario in order to prevent small obstacles from constantly snapping the camera + +void eight_dir_collision_handler(struct Camera *c) { + struct Surface *surf; + + Vec3f camdir; + Vec3f origin; + Vec3f thick; + Vec3f hitpos; + + vec3f_copy(origin,gMarioState->pos); + + origin[1] += VERTICAL_RAY_OFFSET; + camdir[0] = c->pos[0] - origin[0]; + camdir[1] = c->pos[1] - origin[1]; + camdir[2] = c->pos[2] - origin[2]; + + find_surface_on_ray(origin, camdir, &surf, hitpos, (RAYCAST_FIND_FLOOR | RAYCAST_FIND_WALL | RAYCAST_FIND_CEIL)); + + if (surf) { + f32 distFromSurf = 100.0f; + f32 dist; + f32 yDist = 0; + Vec3f camToMario; + vec3f_diff(camToMario, gMarioState->pos, hitpos); + s16 yaw = atan2s(camToMario[2], camToMario[0]); + vec3f_get_lateral_dist(hitpos,gMarioState->pos, &dist); + if (dist < MIN_CAMERA_DISTANCE) { + distFromSurf += (dist - MIN_CAMERA_DISTANCE); // If Mario runs right up to the screen, the camera pull back slightly... + yDist = MIN_CAMERA_DISTANCE - CLAMP(dist, 0, MIN_CAMERA_DISTANCE); // ...and also up slightly. + } + thick[0] = sins(yaw) * distFromSurf; + thick[1] = yDist; + thick[2] = coss(yaw) * distFromSurf; + vec3f_add(hitpos,thick); + vec3f_copy(c->pos,hitpos); + } + + c->yaw = atan2s(c->pos[2] - gMarioState->pos[2], c->pos[0] - gMarioState->pos[0]); + +} +#endif + +#ifdef REONUCAM +f32 cameraSpeeds[] = {0.5f, 1.f, 1.5f, 2.f, 3.5f}; // The camera speed settings, from slowest to fastest. +#define R_DOUBLE_TAP_WINDOW 5 // How many frames the player has to double tap R in order to ender Mario cam mode. + +void reonucam_handler(void) { + // Get the camera speed based on the user's setting + f32 cameraSpeed = cameraSpeeds[gReonucamState.speed]; + + //45º rotations + if ((gPlayer1Controller->buttonPressed & L_CBUTTONS) && !(gPlayer1Controller->buttonDown & R_TRIG)) { + s8DirModeBaseYaw = snap_to_45_degrees(s8DirModeBaseYaw - DEGREES(45)); + } else if ((gPlayer1Controller->buttonPressed & R_CBUTTONS) && !(gPlayer1Controller->buttonDown & R_TRIG)) { + s8DirModeBaseYaw = snap_to_45_degrees(s8DirModeBaseYaw + DEGREES(45)); + } + + //Smooth rotation + if (gPlayer1Controller->buttonDown & R_TRIG) { + if (gPlayer1Controller->buttonDown & L_CBUTTONS) { + s8DirModeBaseYaw -= DEGREES(cameraSpeed); + } else if (gPlayer1Controller->buttonDown & R_CBUTTONS) { + s8DirModeBaseYaw += DEGREES(cameraSpeed); + } + if (gReonucamState.rButtonCounter++ > 100) { // This increses whenever R is held. + gReonucamState.rButtonCounter = 100; + } + } else { + if (gReonucamState.rButtonCounter > 0 && gReonucamState.rButtonCounter <= R_DOUBLE_TAP_WINDOW && !((gPlayer1Controller->buttonDown & L_CBUTTONS) || (gPlayer1Controller->buttonDown & R_CBUTTONS) || (gMarioState->action & ACT_FLAG_SWIMMING_OR_FLYING))) { + // This centers the camera behind mario. It triggers when you let go of R in less than 5 frames. + s8DirModeYawOffset = 0; + s8DirModeBaseYaw = gMarioState->faceAngle[1]-0x8000; + gMarioState->area->camera->yaw = s8DirModeBaseYaw; + play_sound_rbutton_changed(); + } + gReonucamState.rButtonCounter = 0; + } + + if (gPlayer1Controller->buttonPressed & R_TRIG) { + if (gReonucamState.rButtonCounter2 <= R_DOUBLE_TAP_WINDOW) { + set_cam_angle(CAM_ANGLE_MARIO); // Enter mario cam if R is pressed 2 times in less than 5 frames + gReonucamState.rButtonCounter2 = R_DOUBLE_TAP_WINDOW + 1; + } else { + gReonucamState.rButtonCounter2 = 0; + } + } else { + if (gReonucamState.rButtonCounter2++ > 100) { + gReonucamState.rButtonCounter2 = 100; + } + } + + print_text_fmt_int(20, 40, "R %d", gReonucamState.rButtonCounter); + print_text_fmt_int(20, 20, "R %d", gReonucamState.rButtonCounter2); + +} +#endif + /** * A mode that only has 8 camera angles, 45 degrees apart */ void mode_8_directions_camera(struct Camera *c) { Vec3f pos; s16 oldAreaYaw = sAreaYaw; - +#ifdef REONUCAM + reonucam_handler(); + radial_camera_input(c); +#else radial_camera_input(c); if (gPlayer1Controller->buttonPressed & R_CBUTTONS) { @@ -1149,13 +1276,21 @@ void mode_8_directions_camera(struct Camera *c) { s8DirModeYawOffset = snap_to_45_degrees(s8DirModeYawOffset); } #endif - +#endif lakitu_zoom(400.f, 0x900); c->nextYaw = update_8_directions_camera(c, c->focus, pos); +#ifdef REONUCAM + set_camera_height(c, pos[1]); +#endif c->pos[0] = pos[0]; c->pos[2] = pos[2]; sAreaYawChange = sAreaYaw - oldAreaYaw; +#ifdef EIGHT_DIR_CAMERA_COLLISION + eight_dir_collision_handler(c); +#endif +#ifndef REONUCAM set_camera_height(c, pos[1]); +#endif } /** @@ -2751,6 +2886,9 @@ void set_camera_mode(struct Camera *c, s16 mode, s16 frames) { #ifndef ENABLE_VANILLA_CAM_PROCESSING if (mode == CAMERA_MODE_8_DIRECTIONS) { // Helps transition from any camera mode to 8dir +#ifdef REONUCAM + s8DirModeBaseYaw = 0; +#endif s8DirModeYawOffset = snap_to_45_degrees(c->yaw); } #endif @@ -2882,14 +3020,23 @@ void update_camera(struct Camera *c) { // 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) { +#ifdef REONUCAM + if (set_cam_angle(0) == CAM_ANGLE_MARIO) { + s8DirModeBaseYaw = snap_to_45_degrees(s8DirModeBaseYaw); + set_cam_angle(CAM_ANGLE_LAKITU); + } +#else if (set_cam_angle(0) == CAM_ANGLE_LAKITU) { set_cam_angle(CAM_ANGLE_MARIO); } else { set_cam_angle(CAM_ANGLE_LAKITU); } +#endif } } +#ifndef REONUCAM play_sound_if_cam_switched_to_lakitu_or_mario(); +#endif } // Initialize the camera @@ -4545,15 +4692,21 @@ void play_camera_buzz_if_c_sideways(void) { } void play_sound_cbutton_up(void) { +#ifndef REONUCAM play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gGlobalSoundSource); +#endif } void play_sound_cbutton_down(void) { +#ifndef REONUCAM play_sound(SOUND_MENU_CAMERA_ZOOM_OUT, gGlobalSoundSource); +#endif } void play_sound_cbutton_side(void) { +#ifndef REONUCAM play_sound(SOUND_MENU_CAMERA_TURN, gGlobalSoundSource); +#endif } void play_sound_button_change_blocked(void) { @@ -4652,7 +4805,11 @@ void radial_camera_input(struct Camera *c) { } // Zoom in / enter C-Up +#ifdef REONUCAM + if ((gPlayer1Controller->buttonPressed & U_CBUTTONS) && !(gPlayer1Controller->buttonDown & R_TRIG)) { +#else if (gPlayer1Controller->buttonPressed & U_CBUTTONS) { +#endif if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) { gCameraMovementFlags &= ~CAM_MOVE_ZOOMED_OUT; play_sound_cbutton_up(); @@ -4741,6 +4898,7 @@ void handle_c_button_movement(struct Camera *c) { } } + /** * Zero the 10 cvars. */ @@ -5210,7 +5368,11 @@ 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; +#ifdef REONUCAM + s8DirModeBaseYaw = snap_to_45_degrees(s8DirModeBaseYaw); +#else s8DirModeBaseYaw = 0; +#endif s8DirModeYawOffset = 0; } } diff --git a/src/game/camera.h b/src/game/camera.h index fcd1f59f97..1a7e3f4d95 100644 --- a/src/game/camera.h +++ b/src/game/camera.h @@ -90,6 +90,18 @@ #define CAM_MODE_LAKITU_WAS_ZOOMED_OUT 0x02 #define CAM_MODE_MARIO_SELECTED 0x04 +#ifdef REONUCAM +struct ReonucamState { + s8 speed; + u8 waterCamOverride; + u8 flyingCamOverride; + u8 keepCliffCam; + u16 rButtonCounter; + u16 rButtonCounter2; +}; +extern struct ReonucamState gReonucamState; +#endif + enum CameraSelection { CAM_SELECTION_NONE, CAM_SELECTION_MARIO, diff --git a/src/game/game_init.c b/src/game/game_init.c index bec0cfd3ec..47307f68d8 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -31,6 +31,7 @@ #include "vc_ultra.h" #include "profiling.h" #include "emutest.h" +#include "camera.h" // Emulators that the Instant Input patch should not be applied to #define INSTANT_INPUT_BLACKLIST (EMU_CONSOLE | EMU_WIIVC | EMU_ARES | EMU_SIMPLE64 | EMU_CEN64) @@ -785,6 +786,9 @@ void thread5_game_loop(UNUSED void *arg) { play_music(SEQ_PLAYER_SFX, SEQUENCE_ARGS(0, SEQ_SOUND_PLAYER), 0); set_sound_mode(save_file_get_sound_mode()); +#ifdef REONUCAM + gReonucamState.speed = save_file_get_camera_speed(); +#endif #ifdef WIDE gConfig.widescreen = save_file_get_widescreen_mode(); #endif diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index 6713bae8a8..7c479bd7e7 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -1523,6 +1523,45 @@ void reset_red_coins_collected(void) { gRedCoinsCollected = 0; } +#ifdef REONUCAM + +LangArray textReonucam1 = DEFINE_LANGUAGE_ARRAY( + "CAMERA SPEED: %d", + "VITESSE CAMÉRA: %d", + "KAMERA GESCHWINDIGKEIT: %d", + "カメラそくど: %d", + "VELOCIDAD DE CÁMARA: %d"); + +LangArray textReonucam2 = DEFINE_LANGUAGE_ARRAY( + "DPAD TO CHANGE", + "CHANGER AVEC DPAD", + "DPAD ZU ÄNDERN", + "DPADでへんこう", + "DPAD PARA CAMBIAR"); + +void render_reonucam_speed_setting(void) { + char buf[32]; + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); + sprintf(buf, LANG_ARRAY(textReonucam1), gReonucamState.speed +1 ); + + print_generic_string_aligned(310, 24, buf, TEXT_ALIGN_RIGHT); + print_generic_string_aligned(310, 8, LANG_ARRAY(textReonucam2), TEXT_ALIGN_RIGHT); + + + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); + + if (gPlayer1Controller->buttonPressed & R_JPAD) { + gReonucamState.speed++; + } else if (gPlayer1Controller->buttonPressed & L_JPAD) { + gReonucamState.speed--; + } + gReonucamState.speed = (gReonucamState.speed + 5) % 5; + save_file_set_camera_speed(gReonucamState.speed); +} +#endif + + void change_dialog_camera_angle(void) { if (cam_select_alt_mode(0) == CAM_SELECTION_MARIO) { gDialogCameraAngleIndex = CAM_SELECTION_MARIO; @@ -2050,6 +2089,9 @@ s32 render_pause_courses_and_castle(void) { } #if defined(WIDE) && !defined(PUPPYCAM) render_widescreen_setting(); +#endif +#ifdef REONUCAM + render_reonucam_speed_setting(); #endif gDialogTextAlpha += 25; if (gDialogTextAlpha > 250) { diff --git a/src/game/mario.c b/src/game/mario.c index af26781131..9554ab56e8 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -1381,8 +1381,25 @@ void update_mario_inputs(struct MarioState *m) { void set_submerged_cam_preset_and_spawn_bubbles(struct MarioState *m) { f32 heightBelowWater; s16 camPreset; +#ifdef REONUCAM + // skip if not submerged + if ((m->action & ACT_GROUP_MASK) != ACT_GROUP_SUBMERGED) + return; + + // R Trigger toggles camera mode override + if ((gPlayer1Controller->buttonPressed & R_TRIG) && (m->action & ACT_FLAG_SWIMMING)) { + gReonucamState.waterCamOverride ^= 1; + } + // If override, set mode to 8 dir. Otherwise, use normal water processing + if (gReonucamState.waterCamOverride) { + if (m->area->camera->mode != CAMERA_MODE_8_DIRECTIONS) { + set_camera_mode(m->area->camera, CAMERA_MODE_8_DIRECTIONS, 1); + } + } else { +#else if ((m->action & ACT_GROUP_MASK) == ACT_GROUP_SUBMERGED) { +#endif heightBelowWater = (f32)(m->waterLevel - 80) - m->pos[1]; camPreset = m->area->camera->mode; diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index 26051395f2..133724e14c 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -1678,6 +1678,12 @@ s32 act_shot_from_cannon(struct MarioState *m) { s32 act_flying(struct MarioState *m) { s16 startPitch = m->faceAngle[0]; +#ifdef REONUCAM + if (gPlayer1Controller->buttonPressed & R_TRIG) { + gReonucamState.flyingCamOverride ^= 1; + } +#endif + if (m->input & INPUT_Z_PRESSED) { if (m->area->camera->mode == FLYING_CAMERA_MODE) { set_camera_mode(m->area->camera, m->area->camera->defMode, 1); @@ -1692,9 +1698,17 @@ s32 act_flying(struct MarioState *m) { return set_mario_action(m, ACT_FREEFALL, 0); } +#ifdef REONUCAM + if (!gReonucamState.flyingCamOverride && m->area->camera->mode != FLYING_CAMERA_MODE) { + set_camera_mode(m->area->camera, FLYING_CAMERA_MODE, 1); + } else if (gReonucamState.flyingCamOverride && m->area->camera->mode != CAMERA_MODE_8_DIRECTIONS) { + set_camera_mode(m->area->camera, CAMERA_MODE_8_DIRECTIONS, 1); + } +#else if (m->area->camera->mode != FLYING_CAMERA_MODE) { set_camera_mode(m->area->camera, FLYING_CAMERA_MODE, 1); } +#endif if (m->actionState == ACT_STATE_FLYING_SPIN) { if (m->actionArg == ACT_ARG_FLYING_FROM_CANNON) { diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index 1a4fb83914..a1aba97861 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -502,7 +502,10 @@ s32 act_debug_free_move(struct MarioState *m) { f32 speed = (gPlayer1Controller->buttonDown & B_BUTTON) ? 4.0f : 1.0f; if (gPlayer1Controller->buttonDown & Z_TRIG) speed = 0.01f; - if (m->area->camera->mode != CAMERA_MODE_8_DIRECTIONS) set_camera_mode(m->area->camera, CAMERA_MODE_8_DIRECTIONS, 1); + if (m->area->camera->mode != CAMERA_MODE_8_DIRECTIONS) { + set_camera_mode(m->area->camera, CAMERA_MODE_8_DIRECTIONS, 1); + } + set_mario_animation(m, MARIO_ANIM_A_POSE); vec3f_copy(pos, m->pos); diff --git a/src/game/save_file.c b/src/game/save_file.c index 5682478633..2e222ca254 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -236,6 +236,10 @@ static void wipe_main_menu_data(void) { gSaveBuffer.menuData.coinScoreAges[1] = 0x2AAAAAAA; gSaveBuffer.menuData.coinScoreAges[2] = 0x15555555; +#ifdef REONUCAM + gSaveBuffer.menuData.cameraSpeedSetting = 2; // Set default Reonucam speed to medium on first boot +#endif + gMainMenuDataModified = TRUE; save_main_menu_data(); } @@ -730,6 +734,19 @@ u32 save_file_get_sound_mode(void) { return gSaveBuffer.menuData.soundMode; } +#ifdef REONUCAM +void save_file_set_camera_speed(u8 speed) { + gSaveBuffer.menuData.cameraSpeedSetting = speed; + gMainMenuDataModified = TRUE; + save_main_menu_data(); +} + +u8 save_file_get_camera_speed(void) { + return gSaveBuffer.menuData.cameraSpeedSetting; +} + +#endif + void save_file_move_cap_to_default_location(void) { if (save_file_get_flags() & SAVE_FLAG_CAP_ON_GROUND) { switch (gSaveBuffer.files[gCurrSaveFileNum - 1][0].capLevel) { diff --git a/src/game/save_file.h b/src/game/save_file.h index 41a2c69457..f27135dedb 100644 --- a/src/game/save_file.h +++ b/src/game/save_file.h @@ -65,7 +65,9 @@ struct MainMenuSaveData { #ifdef MULTILANG u8 language: 3; #endif - +#ifdef REONUCAM + u8 cameraSpeedSetting: 3; +#endif #ifdef PUPPYCAM u8 firstBoot; struct gPuppyOptions saveOptions; @@ -187,6 +189,10 @@ u32 save_file_get_sound_mode(void); u32 save_file_get_widescreen_mode(void); void save_file_set_widescreen_mode(u8 mode); #endif +#ifdef REONUCAM +u8 save_file_get_camera_speed(void); +void save_file_set_camera_speed(u8 speed); +#endif void save_file_move_cap_to_default_location(void); void disable_warp_checkpoint(void);