diff --git a/assets/config_menu/general.rml b/assets/config_menu/general.rml index 291bf8c..3fdf8ca 100644 --- a/assets/config_menu/general.rml +++ b/assets/config_menu/general.rml @@ -92,7 +92,7 @@ /> - +
@@ -171,7 +171,7 @@
- +
@@ -225,7 +225,7 @@
- +
@@ -255,7 +255,7 @@
- +
@@ -268,7 +268,7 @@ data-checked="analog_camera_invert_mode" value="InvertNone" id="analog_camera_inversion_none" - style="nav-up: #analog_cam_enabled;" + style="nav-up: #analog_cam_enabled; nav-down: #dsot_enabled" /> @@ -280,7 +280,7 @@ data-checked="analog_camera_invert_mode" value="InvertX" id="analog_camera_inversion_x" - style="nav-up: #analog_cam_disabled;" + style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled" /> @@ -292,7 +292,7 @@ data-checked="analog_camera_invert_mode" value="InvertY" id="analog_camera_inversion_y" - style="nav-up: #analog_cam_disabled;" + style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled" /> @@ -304,16 +304,45 @@ data-checked="analog_camera_invert_mode" value="InvertBoth" id="analog_camera_inversion_both" - style="nav-up: #analog_cam_disabled;" + style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled" />
+ +
+ +
+ + + + + +
+

- Controls how targeting enemies and objects works. Switch will start or stop targeting each time the target button is pressed. Hold will start when the target button is pressed and stop when the button is released. + Controls how targeting enemies and objects works. Switch will start or stop targeting each time the target button is pressed. Hold will start when the target button is pressed and stop when the button is released.

Controls the strength of rumble when using a controller that supports it. Setting this to zero will disable rumble. @@ -362,6 +391,12 @@

Inverts the camera controls for the analog camera if it's enabled. None is the default.

+

+ Enables hour selection for Double Song of Time. +
+
+ To select an hour move the left analog stick in horizontal axis. +

diff --git a/include/zelda_config.h b/include/zelda_config.h index f5b206a..aefe0c3 100644 --- a/include/zelda_config.h +++ b/include/zelda_config.h @@ -12,16 +12,16 @@ namespace zelda64 { // TODO: Move loading configs to the runtime once we have a way to allow per-project customization. void load_config(); void save_config(); - + void reset_input_bindings(); void reset_cont_input_bindings(); void reset_kb_input_bindings(); std::filesystem::path get_app_folder_path(); - + bool get_debug_mode_enabled(); void set_debug_mode_enabled(bool enabled); - + enum class AutosaveMode { On, Off, @@ -79,6 +79,17 @@ namespace zelda64 { {zelda64::AnalogCamMode::Off, "Off"} }); + enum class DsotMode { + On, + Off, + OptionCount + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::DsotMode, { + {zelda64::DsotMode::On, "On"}, + {zelda64::DsotMode::Off, "Off"} + }); + AutosaveMode get_autosave_mode(); void set_autosave_mode(AutosaveMode mode); @@ -86,6 +97,9 @@ namespace zelda64 { void set_analog_cam_mode(AnalogCamMode mode); void open_quit_game_prompt(); + + DsotMode get_dsot_mode(); + void set_dsot_mode(DsotMode mode); }; #endif diff --git a/patches/better_double_sot.c b/patches/better_double_sot.c index 0abaf04..b00a1da 100644 --- a/patches/better_double_sot.c +++ b/patches/better_double_sot.c @@ -3,6 +3,16 @@ void dsot_load_day_number_texture(PlayState* play, s32 day); void dsot_actor_fixes(PlayState* play); +bool dsot_on; + +void dsot_determine_enabled(void) { + dsot_on = recomp_dsot_enabled(); +} + +bool dsot_enabled(void) { + return dsot_on; +} + u8 choiceHour; void dsot_init_hour_selection(PlayState* play) { diff --git a/patches/event_patches.c b/patches/event_patches.c index e6526e2..484ec95 100644 --- a/patches/event_patches.c +++ b/patches/event_patches.c @@ -338,8 +338,8 @@ void EnTest6_DoubleSoTCutscene(EnTest6* this, PlayState* play) { SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_FANFARE, 20); } - // @recomp Advance hour as Double SoT ends. - if (this->timer == 15) { + // @recomp Replace DSoT functionality if the option for it is enabled. + if (dsot_enabled() && this->timer == 15) { dsot_advance_hour(play); } @@ -586,7 +586,18 @@ void EnTest6_SharedSoTCutscene(EnTest6* this, PlayState* play) { return; case SOTCS_CUEID_DOUBLE_END: - // @recomp Disable scene reset after Double SoT ends. + // @recomp Replace DSoT functionality if the option for it is enabled. + if (!dsot_enabled() && (CURRENT_TIME > CLOCK_TIME(12, 0))) { + Play_SetRespawnData(&play->state, RESPAWN_MODE_RETURN, ((void)0, gSaveContext.save.entrance), + player->unk_3CE, PLAYER_PARAMS(0xFF, PLAYER_INITMODE_B), &player->unk_3C0, + player->unk_3CC); + this->drawType = SOTCS_DRAW_TYPE_NONE; + play->transitionTrigger = TRANS_TRIGGER_START; + play->nextEntrance = gSaveContext.respawn[RESPAWN_MODE_RETURN].entrance; + play->transitionType = TRANS_TYPE_FADE_BLACK; + gSaveContext.respawnFlag = 2; + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } return; } } diff --git a/patches/message_patches.c b/patches/message_patches.c index 4baf842..58af342 100644 --- a/patches/message_patches.c +++ b/patches/message_patches.c @@ -452,23 +452,43 @@ void Message_Update(PlayState* play) { } } else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME)) { - // @recomp - dsot_handle_hour_selection(play); + // @recomp Replace DSoT functionality if the option for it is enabled. + if (dsot_enabled()) { + // @recomp + dsot_handle_hour_selection(play); - if (Message_ShouldAdvance(play)) { - if (msgCtx->choiceIndex == 0) { - Audio_PlaySfx_MessageDecide(); + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + Audio_PlaySfx_MessageDecide(); - play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT; - gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF; - } else { - Audio_PlaySfx_MessageCancel(); - play->msgCtx.ocarinaMode = OCARINA_MODE_END; + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT; + gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF; + } else { + Audio_PlaySfx_MessageCancel(); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; - // @recomp - dsot_cancel_hour_selection(play); + // @recomp + dsot_cancel_hour_selection(play); + } + Message_CloseTextbox(play); + } + } else { + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + Audio_PlaySfx_MessageDecide(); + if (gSaveContext.save.isNight != 0) { + gSaveContext.save.time = CLOCK_TIME(6, 0); + } else { + gSaveContext.save.time = CLOCK_TIME(18, 0); + } + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT; + gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF; + } else { + Audio_PlaySfx_MessageCancel(); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } + Message_CloseTextbox(play); } - Message_CloseTextbox(play); } } else if ((msgCtx->textboxEndType != TEXTBOX_ENDTYPE_10) || (pauseCtx->state != PAUSE_STATE_OWL_WARP_CONFIRM)) { @@ -659,49 +679,66 @@ void Message_Update(PlayState* play) { play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; } } else if (sLastPlayedSong == OCARINA_SONG_DOUBLE_TIME) { - // @recomp Patch Song of Double Time. if (interfaceCtx->restrictions.songOfDoubleTime == 0) { - if ((CURRENT_DAY != 3) || (CURRENT_TIME < CLOCK_TIME(5, 0)) || (CURRENT_TIME >= CLOCK_TIME(6, 0))) { - Message_StartTextbox(play, D_801D0464[0], NULL); + // @recomp + dsot_determine_enabled(); - // @recomp Replace message text. - char *buf = play->msgCtx.font.msgBuf.schar; - buf[25] = ' '; - buf[26] = 1; - buf[27] = 'S'; - buf[28] = 'e'; - buf[29] = 'l'; - buf[30] = 'e'; - buf[31] = 'c'; - buf[32] = 't'; - buf[33] = 'e'; - buf[34] = 'd'; - buf[35] = ' '; - buf[36] = 'H'; - buf[37] = 'o'; - buf[38] = 'u'; - buf[39] = 'r'; - buf[40] = 0; - buf[41] = '?'; - buf[42] = 17; - buf[43] = 17; - buf[44] = 2; - buf[45] = -62; - buf[46] = 'Y'; - buf[47] = 'e'; - buf[48] = 's'; - buf[49] = 17; - buf[50] = 'N'; - buf[51] = 'o'; - buf[52] = -65; + // @recomp Replace DSoT functionality if the option for it is enabled. + if (dsot_enabled()) { + if ((CURRENT_DAY != 3) || (CURRENT_TIME < CLOCK_TIME(5, 0)) || (CURRENT_TIME >= CLOCK_TIME(6, 0))) { + Message_StartTextbox(play, D_801D0464[0], NULL); - play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; + // @recomp Replace message text. + char *buf = play->msgCtx.font.msgBuf.schar; + buf[25] = ' '; + buf[26] = 1; + buf[27] = 'S'; + buf[28] = 'e'; + buf[29] = 'l'; + buf[30] = 'e'; + buf[31] = 'c'; + buf[32] = 't'; + buf[33] = 'e'; + buf[34] = 'd'; + buf[35] = ' '; + buf[36] = 'H'; + buf[37] = 'o'; + buf[38] = 'u'; + buf[39] = 'r'; + buf[40] = 0; + buf[41] = '?'; + buf[42] = 17; + buf[43] = 17; + buf[44] = 2; + buf[45] = -62; + buf[46] = 'Y'; + buf[47] = 'e'; + buf[48] = 's'; + buf[49] = 17; + buf[50] = 'N'; + buf[51] = 'o'; + buf[52] = -65; - // @recomp - dsot_init_hour_selection(play); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; + + // @recomp + dsot_init_hour_selection(play); + } else { + Message_StartTextbox(play, 0x1B94, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } } else { - Message_StartTextbox(play, 0x1B94, NULL); - play->msgCtx.ocarinaMode = OCARINA_MODE_END; + if ((CURRENT_DAY != 3) || (gSaveContext.save.isNight == 0)) { + if (gSaveContext.save.isNight) { + Message_StartTextbox(play, D_801D0464[CURRENT_DAY - 1], NULL); + } else { + Message_StartTextbox(play, D_801D045C[CURRENT_DAY - 1], NULL); + } + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; + } else { + Message_StartTextbox(play, 0x1B94, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } } } else { sLastPlayedSong = 0xFF; diff --git a/patches/misc_funcs.h b/patches/misc_funcs.h index 853877b..a83a9ad 100644 --- a/patches/misc_funcs.h +++ b/patches/misc_funcs.h @@ -11,5 +11,6 @@ DECLARE_FUNC(void, recomp_handle_quicksave_actions_main, OSMesgQueue* enter_mq, DECLARE_FUNC(u16, recomp_get_pending_warp); DECLARE_FUNC(u32, recomp_get_pending_set_time); DECLARE_FUNC(s32, recomp_autosave_enabled); +DECLARE_FUNC(s32, recomp_dsot_enabled); #endif diff --git a/patches/patches.h b/patches/patches.h index b77db60..54fbfd3 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -108,6 +108,8 @@ void draw_autosave_icon(PlayState* play); void recomp_crash(const char* err); +void dsot_determine_enabled(void); +bool dsot_enabled(void); void dsot_init_hour_selection(PlayState* play); void dsot_handle_hour_selection(PlayState* play); void dsot_cancel_hour_selection(PlayState* play); diff --git a/patches/syms.ld b/patches/syms.ld index 3381c18..c535ca1 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -47,7 +47,7 @@ osContStartReadData_recomp = 0x8F000070; osContGetReadData_recomp = 0x8F000074; osContStartQuery_recomp = 0x8F000078; osContGetQuery_recomp = 0x8F00007C; -recomp_get_mouse_deltas = 0x8F000080; +recomp_get_mouse_deltas = 0x8F000080; bcmp_recomp = 0x8F000084; osGetTime_recomp = 0x8F000088; recomp_autosave_enabled = 0x8F00008C; @@ -60,3 +60,4 @@ recomp_get_inverted_axes = 0x8F0000A4; recomp_high_precision_fb_enabled = 0x8F0000A8; recomp_get_resolution_scale = 0x8F0000AC; recomp_get_analog_inverted_axes = 0x8F0000B0; +recomp_dsot_enabled = 0x8F0000B4; diff --git a/patches/ui_patches.c b/patches/ui_patches.c index 5f07315..455bc01 100644 --- a/patches/ui_patches.c +++ b/patches/ui_patches.c @@ -990,8 +990,8 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { gSPPopMatrix(gfx++, G_MTX_MODELVIEW); } - // @recomp Draw clock for Double SoT. - if (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME) { + // @recomp Replace DSoT functionality if the option for it is enabled. + if (dsot_enabled() && (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME)) { dsot_draw_clock(play); } diff --git a/src/game/config.cpp b/src/game/config.cpp index ece24b6..0672019 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -72,7 +72,7 @@ T from_or_default(const json& j, const std::string& key, T default_value) { else { ret = default_value; } - + return ret; } @@ -130,7 +130,7 @@ namespace recomp { } std::filesystem::path zelda64::get_app_folder_path() { - // directly check for portable.txt (windows and native linux binary) + // directly check for portable.txt (windows and native linux binary) if (std::filesystem::exists("portable.txt")) { return std::filesystem::current_path(); } @@ -207,7 +207,7 @@ bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::j return recomp::finalize_output_file_with_backup(path); } -bool save_general_config(const std::filesystem::path& path) { +bool save_general_config(const std::filesystem::path& path) { nlohmann::json config_json{}; zelda64::to_json(config_json["targeting_mode"], zelda64::get_targeting_mode()); @@ -221,7 +221,8 @@ bool save_general_config(const std::filesystem::path& path) { config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode(); config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode(); config_json["debug_mode"] = zelda64::get_debug_mode_enabled(); - + config_json["dsot_mode"] = zelda64::get_dsot_mode(); + return save_json_with_backups(path, config_json); } @@ -237,6 +238,7 @@ void set_general_settings_from_json(const nlohmann::json& config_json) { zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off)); zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone)); zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false)); + zelda64::set_dsot_mode(from_or_default(config_json, "dsot_mode", zelda64::DsotMode::On)); } bool load_general_config(const std::filesystem::path& path) { @@ -423,7 +425,7 @@ bool save_sound_config(const std::filesystem::path& path) { config_json["main_volume"] = zelda64::get_main_volume(); config_json["bgm_volume"] = zelda64::get_bgm_volume(); config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled(); - + return save_json_with_backups(path, config_json); } @@ -485,7 +487,7 @@ void zelda64::save_config() { } std::filesystem::create_directories(recomp_dir); - + // TODO error handling for failing to save config files. save_general_config(recomp_dir / general_filename); diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 28b4779..b90a8b6 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -95,6 +95,10 @@ extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) { _return(ctx, static_cast(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On)); } +extern "C" void recomp_dsot_enabled(uint8_t* rdram, recomp_context* ctx) { + _return(ctx, static_cast(zelda64::get_dsot_mode() == zelda64::DsotMode::On)); +} + extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) { u32 rom = _arg<0, u32>(rdram, ctx); PTR(void) ram = _arg<1, PTR(void)>(rdram, ctx); diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp index 6869731..8e59422 100644 --- a/src/ui/ui_config.cpp +++ b/src/ui/ui_config.cpp @@ -281,6 +281,7 @@ struct ControlOptionsContext { zelda64::CameraInvertMode camera_invert_mode; zelda64::AnalogCamMode analog_cam_mode; zelda64::CameraInvertMode analog_camera_invert_mode; + zelda64::DsotMode dsot_mode; }; ControlOptionsContext control_options_context; @@ -401,6 +402,17 @@ void zelda64::set_analog_camera_invert_mode(zelda64::CameraInvertMode mode) { } } +zelda64::DsotMode zelda64::get_dsot_mode() { + return control_options_context.dsot_mode; +} + +void zelda64::set_dsot_mode(zelda64::DsotMode mode) { + control_options_context.dsot_mode = mode; + if (general_model_handle) { + general_model_handle.DirtyVariable("dsot_mode"); + } +} + struct SoundOptionsContext { std::atomic main_volume; // Option to control the volume of all sound std::atomic bgm_volume; @@ -461,7 +473,7 @@ struct DebugContext { Rml::DataModelHandle model_handle; std::vector area_names; std::vector scene_names; - std::vector entrance_names; + std::vector entrance_names; int area_index = 0; int scene_index = 0; int entrance_index = 0; @@ -482,7 +494,7 @@ struct DebugContext { for (const auto& scene : zelda64::game_warps[area_index].scenes) { scene_names.emplace_back(scene.name); } - + entrance_names = zelda64::game_warps[area_index].scenes[scene_index].entrances; } }; @@ -557,7 +569,7 @@ public: controls_model_handle.DirtyVariable("input_device_is_keyboard"); controls_model_handle.DirtyVariable("inputs"); }); - + recompui::register_event(listener, "area_index_changed", [](const std::string& param, Rml::Event& event) { debug_context.area_index = event.GetParameter("value", 0); @@ -569,7 +581,7 @@ public: debug_context.model_handle.DirtyVariable("scene_names"); debug_context.model_handle.DirtyVariable("entrance_names"); }); - + recompui::register_event(listener, "scene_index_changed", [](const std::string& param, Rml::Event& event) { debug_context.scene_index = event.GetParameter("value", 0); @@ -684,7 +696,7 @@ public: } out = ""; }); - + constructor.BindFunc("gfx_help__apply", [](Rml::Variant& out) { if (cont_active) { out = PF_GAMEPAD_X " " PF_GAMEPAD_START; @@ -892,7 +904,7 @@ public: } bind_config_list_events(constructor); - + constructor.Bind("rumble_strength", &control_options_context.rumble_strength); constructor.Bind("gyro_sensitivity", &control_options_context.gyro_sensitivity); constructor.Bind("mouse_sensitivity", &control_options_context.mouse_sensitivity); @@ -903,10 +915,11 @@ public: bind_option(constructor, "camera_invert_mode", &control_options_context.camera_invert_mode); bind_option(constructor, "analog_cam_mode", &control_options_context.analog_cam_mode); bind_option(constructor, "analog_camera_invert_mode", &control_options_context.analog_camera_invert_mode); + bind_option(constructor, "dsot_mode", &control_options_context.dsot_mode); general_model_handle = constructor.GetModelHandle(); } - + void make_sound_options_bindings(Rml::Context* context) { Rml::DataModelConstructor constructor = context->CreateDataModel("sound_options_model"); if (!constructor) { @@ -914,7 +927,7 @@ public: } bind_config_list_events(constructor); - + sound_options_model_handle = constructor.GetModelHandle(); bind_atomic(constructor, sound_options_model_handle, "main_volume", &sound_options_context.main_volume); @@ -932,10 +945,10 @@ public: // Bind the debug mode enabled flag. constructor.Bind("debug_enabled", &debug_context.debug_enabled); - + // Register the array type for string vectors. constructor.RegisterArray>(); - + // Bind the warp parameter indices constructor.Bind("area_index", &debug_context.area_index); constructor.Bind("scene_index", &debug_context.scene_index); @@ -1004,7 +1017,7 @@ void recompui::update_supported_options() { msaa4x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X; msaa8x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X; sample_positions_supported = zelda64::renderer::RT64SamplePositionsSupported(); - + new_options = ultramodern::renderer::get_graphics_config(); graphics_model_handle.DirtyAllVariables();