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 new file mode 100644 index 0000000..b00a1da --- /dev/null +++ b/patches/better_double_sot.c @@ -0,0 +1,403 @@ +#include "patches.h" + +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) { + choiceHour = (u16)(TIME_TO_HOURS_F(CURRENT_TIME - 1)) + 1; + if (choiceHour >= 24) { + choiceHour = 0; + } + if (choiceHour == 6) { + if (gSaveContext.save.day == 3) { + choiceHour = 5; + } else { + dsot_load_day_number_texture(play, gSaveContext.save.day + 1); + } + } +} + +#define TIMER_INIT 10 +#define TIMER_STEP_RESET 0 + +void dsot_handle_hour_selection(PlayState* play) { + static s8 sAnalogStickTimer = TIMER_INIT; + static s8 sPrevStickX = 0; + static bool sAnalogStickHeld = false; + Input* input = CONTROLLER1(&play->state); + + if ((input->rel.stick_x >= 30) && !sAnalogStickHeld) { + u8 prevHour = choiceHour; + sAnalogStickHeld = true; + + choiceHour++; + if (choiceHour >= 24) { + choiceHour = 0; + } + + if ((CURRENT_DAY == 3) && (prevHour <= 5) && (choiceHour > 5)) { + choiceHour = 5; + } else if ((prevHour <= 6) && (choiceHour > 6)) { + choiceHour = 6; + } else { + Audio_PlaySfx(NA_SE_SY_CURSOR); + } + + if ((choiceHour == 6) && (choiceHour > prevHour)) { + dsot_load_day_number_texture(play, gSaveContext.save.day + 1); + } + } else if ((input->rel.stick_x <= -30) && !sAnalogStickHeld) { + u8 prevHour = choiceHour; + sAnalogStickHeld = true; + + choiceHour--; + if (choiceHour > 24) { + choiceHour = 23; + } + + if (((CLOCK_TIME(choiceHour, 0) <= CURRENT_TIME) && (CLOCK_TIME(prevHour, 0) > CURRENT_TIME) + || ((choiceHour > prevHour) && (CURRENT_TIME >= CLOCK_TIME(23, 0))) + || ((choiceHour > prevHour) && (CURRENT_TIME < CLOCK_TIME(1, 0))))) { + choiceHour = prevHour; + } + else { + Audio_PlaySfx(NA_SE_SY_CURSOR); + } + + if ((prevHour == 6) && (choiceHour < prevHour)) { + dsot_load_day_number_texture(play, gSaveContext.save.day); + } + } else if (ABS_ALT(input->rel.stick_x) < 30) { + sAnalogStickHeld = false; + sAnalogStickTimer = TIMER_INIT; + } + + if (ABS_ALT(input->rel.stick_x) >= 30) { + if (!DECR(sAnalogStickTimer)) { + sAnalogStickHeld = false; + sAnalogStickTimer = TIMER_STEP_RESET; + } + } + + if (sPrevStickX * input->rel.stick_x < 0) { + sAnalogStickHeld = false; + sAnalogStickTimer = TIMER_INIT; + } + + sPrevStickX = input->rel.stick_x; +} + +void dsot_cancel_hour_selection(PlayState* play) { + if (choiceHour == 6) { + dsot_load_day_number_texture(play, gSaveContext.save.day); + } +} + +void dsot_advance_hour(PlayState* play) { + gSaveContext.save.time = CLOCK_TIME(choiceHour, 0); + gSaveContext.skyboxTime = CURRENT_TIME; + + // Instantly enable/disable rain on day 2. + if ((CURRENT_DAY == 2) && (Environment_GetStormState(play) != STORM_STATE_OFF)) { + if ((CURRENT_TIME >= CLOCK_TIME(8, 0)) && (CURRENT_TIME < CLOCK_TIME(18, 00))) { + gWeatherMode = WEATHER_MODE_RAIN; + play->envCtx.lightningState = LIGHTNING_ON; + play->envCtx.precipitation[PRECIP_RAIN_MAX] = 60; + play->envCtx.precipitation[PRECIP_RAIN_CUR] = 60; + Environment_PlayStormNatureAmbience(play); + } else { + gWeatherMode = WEATHER_MODE_CLEAR; + play->envCtx.lightningState = LIGHTNING_OFF; + play->envCtx.precipitation[PRECIP_RAIN_MAX] = 0; + play->envCtx.precipitation[PRECIP_RAIN_CUR] = 0; + Environment_StopStormNatureAmbience(play); + } + } + + // Play music/ambience. + play->envCtx.timeSeqState = TIMESEQ_FADE_DAY_BGM; + + if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6,0))) { + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0); + play->envCtx.timeSeqState = TIMESEQ_NIGHT_BEGIN_SFX; + } else if (CURRENT_TIME > CLOCK_TIME(17, 10)) { + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 240); + play->envCtx.timeSeqState = TIMESEQ_NIGHT_BEGIN_SFX; + } + + if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) { + Audio_PlayAmbience(play->sequenceCtx.ambienceId); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 1); + play->envCtx.timeSeqState = TIMESEQ_NIGHT_DELAY; + } + + if ((CURRENT_TIME >= CLOCK_TIME(19, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) { + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 0); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 1); + play->envCtx.timeSeqState = TIMESEQ_DAY_BEGIN_SFX; + } + + if ((CURRENT_TIME >= CLOCK_TIME(5, 0)) && (CURRENT_TIME <= CLOCK_TIME(6, 0))) { + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 0); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_4 << 4 | AMBIENCE_CHANNEL_CRITTER_5, 1, 1); + play->envCtx.timeSeqState = TIMESEQ_DAY_DELAY; + } + + dsot_actor_fixes(play); +} + +// Loads day number texture from week_static for the three-day clock. +void dsot_load_day_number_texture(PlayState* play, s32 day) { + s16 i = day - 1; + + if ((i < 0) || (i >= 3)) { + i = 0; + } + + DmaMgr_SendRequest0((void*)(play->interfaceCtx.doActionSegment + 0x780), + SEGMENT_ROM_START_OFFSET(week_static, i * 0x510), 0x510); +} + +#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" + +void Interface_DrawClock(PlayState* play); + +// Wrapper for Interface_DrawClock to display selected hour. +void dsot_draw_clock(PlayState* play) { + MessageContext *msgCtx = &play->msgCtx; + + u8 prev_msgMode = msgCtx->msgMode; + msgCtx->msgMode = MSGMODE_NONE; + + PlayState* state = (PlayState*)&play->state; + s32 prev_frameAdvanceCtx = state->frameAdvCtx.enabled; + state->frameAdvCtx.enabled = 0; + + u16 prev_pauseCtx_state = play->pauseCtx.state; + play->pauseCtx.state = PAUSE_STATE_OFF; + + u16 prev_pauseCtx_debugEditor = play->pauseCtx.state; + play->pauseCtx.debugEditor = DEBUG_EDITOR_NONE; + + bool flag_modified = false; + if (!(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) { + play->actorCtx.flags ^= ACTORCTX_FLAG_TELESCOPE_ON; + flag_modified = true; + } + + u16 prev_time = gSaveContext.save.time; + gSaveContext.save.time = CLOCK_TIME(choiceHour, 0); + + Interface_DrawClock(play); + + gSaveContext.save.time = prev_time; + + if (flag_modified) { + play->actorCtx.flags ^= ACTORCTX_FLAG_TELESCOPE_ON; + } + + play->pauseCtx.debugEditor == prev_pauseCtx_debugEditor; + play->pauseCtx.state = prev_pauseCtx_state; + state->frameAdvCtx.enabled = prev_frameAdvanceCtx; + msgCtx->msgMode = prev_msgMode; +} + + +#include "overlays/actors/ovl_En_Test4/z_en_test4.h" +#include "overlays/actors/ovl_Obj_Tokei_Step/z_obj_tokei_step.h" +#include "overlays/actors/ovl_Obj_Tokeidai/z_obj_tokeidai.h" + +void dsot_ObjEnTest4_fix(EnTest4* this, PlayState* play); +void dsot_ObjTokeiStep_fix(ObjTokeiStep* this, PlayState* play); +void dsot_ObjTokeidai_fix(ObjTokeidai* this, PlayState* play); + +// Calls actor specific fixes. +void dsot_actor_fixes(PlayState* play) { + s32 category; + Actor* actor; + Player* player = GET_PLAYER(play); + u32* categoryFreezeMaskP; + ActorListEntry* entry; + + ActorContext* actorCtx = &play->actorCtx; + + for (category = 0, entry = actorCtx->actorLists; category < ACTORCAT_MAX; + entry++, categoryFreezeMaskP++, category++) { + actor = entry->first; + + for (actor = entry->first; actor != NULL; actor = actor->next) { + switch(actor->id) { + case ACTOR_EN_TEST4: + dsot_ObjEnTest4_fix(actor, play); + break; + case ACTOR_OBJ_TOKEI_STEP: + dsot_ObjTokeiStep_fix(actor, play); + break; + case ACTOR_OBJ_TOKEIDAI: + dsot_ObjTokeidai_fix(actor, play); + break; + } + } + } +} + +#define PAST_MIDNIGHT ((CURRENT_DAY == 3) && (CURRENT_TIME < CLOCK_TIME(6, 0)) && (CURRENT_TIME > CLOCK_TIME(0, 0))) + +// z_obj_en_test_4 +void func_80A42198(EnTest4* this); // EnTest4_GetBellTimeOnDay3 + +void dsot_ObjEnTest4_fix(EnTest4* this, PlayState* play) { + this->prevTime = CURRENT_TIME; + this->prevBellTime = CURRENT_TIME; + + // Change daytime to night manually if necessary. + if ((this->daytimeIndex = THREEDAY_DAYTIME_DAY) && (CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) { + this->daytimeIndex = THREEDAY_DAYTIME_NIGHT; + // Re-spawn the setup actors. + play->numSetupActors = -play->numSetupActors; + } + + // Setup next bell time. + if (CURRENT_DAY == 3) { + gSaveContext.save.time--; + func_80A42198(this); + gSaveContext.save.time++; + } +} + +// z_obj_tokei_step + +void ObjTokeiStep_InitStepsOpen(ObjTokeiStep* this); +void ObjTokeiStep_SetupDoNothingOpen(ObjTokeiStep* this); +void ObjTokeiStep_DrawOpen(Actor* thisx, PlayState* play); + +void dsot_ObjTokeiStep_fix(ObjTokeiStep* this, PlayState* play) { + if (PAST_MIDNIGHT) { + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + this->dyna.actor.draw = actor_relocate(this, ObjTokeiStep_DrawOpen); + ObjTokeiStep_InitStepsOpen(this); + ObjTokeiStep_SetupDoNothingOpen(this); + DynaPoly_DisableCollision(play, &play->colCtx.dyna, this->dyna.bgId); + } +} + +// z_obj_tokeidai + +#define GET_CLOCK_FACE_ROTATION(currentClockHour) (TRUNCF_BINANG(currentClockHour * (0x10000 / 24.0f))) +#define GET_MINUTE_RING_OR_EXTERIOR_GEAR_ROTATION(currentClockMinute) \ + (TRUNCF_BINANG(currentClockMinute * (0x10000 * 12.0f / 360))) + +s32 ObjTokeidai_GetTargetSunMoonPanelRotation(void); +void ObjTokeidai_Init(Actor* thisx, PlayState* play); +void ObjTokeidai_Draw(Actor* thisx, PlayState* play); +void ObjTokeidai_ExteriorGear_Draw(Actor* thisx, PlayState* play); +void ObjTokeidai_Clock_Draw(Actor* thisx, PlayState* play); + +void ObjTokeidai_Counterweight_Idle(ObjTokeidai* this, PlayState* play); +void ObjTokeidai_ExteriorGear_Idle(ObjTokeidai* this, PlayState* play); +void ObjTokeidai_TowerClock_Idle(ObjTokeidai* this, PlayState* play); + +s32 dsot_ObjTokeidai_get_target_sun_moon_panel_rotation(void) { + if (CURRENT_TIME >= CLOCK_TIME(18, 0) || CURRENT_TIME < CLOCK_TIME(6, 0)) { + return 0x8000; + } + return 0; +} + +void dsot_ObjTokeidai_update_clock(ObjTokeidai* this, u16 currentHour, u16 currentMinute) { + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + this->clockTime = CURRENT_TIME; + + // Instantly switch to desired hour. + u16 clockHour = currentHour; + if (currentMinute == 0) { + clockHour--; + if (clockHour > 23) { + clockHour = 23; + } + } + + this->clockFaceRotation = GET_CLOCK_FACE_ROTATION(clockHour); + this->clockHour = clockHour; + this->clockFaceAngularVelocity = 0; + this->clockFaceRotationTimer = 0; + + // Instantly switch to desired minute. + u16 clockMinute = currentMinute - 1; + if (clockMinute > 59) { + clockMinute = 59; + } + + this->minuteRingOrExteriorGearRotation = GET_MINUTE_RING_OR_EXTERIOR_GEAR_ROTATION(clockMinute); + this->clockMinute = clockMinute; + this->minuteRingOrExteriorGearAngularVelocity = 0x5A; + this->minuteRingOrExteriorGearRotationTimer = 0; + + // Switch the medalion rotation immediately. + if (((currentHour != 6) && (currentHour != 18)) || (currentMinute != 0)) { + this->sunMoonPanelRotation = dsot_ObjTokeidai_get_target_sun_moon_panel_rotation(); + this->sunMoonPanelAngularVelocity = 0; + } +} + +void dsot_ObjTokeidai_fix(ObjTokeidai* this, PlayState* play) { + s32 type = OBJ_TOKEIDAI_TYPE(&this->actor); + u16 currentHour = TIME_TO_HOURS_F(CURRENT_TIME); + u16 currentMinute = TIME_TO_MINUTES_F(CURRENT_TIME); + + switch(type) { + case OBJ_TOKEIDAI_TYPE_COUNTERWEIGHT_TERMINA_FIELD: + case OBJ_TOKEIDAI_TYPE_COUNTERWEIGHT_CLOCK_TOWN: + if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_Counterweight_Idle))) { + this->actor.shape.rot.y = 0; + ObjTokeidai_Init(this, play); + } + break; + + case OBJ_TOKEIDAI_TYPE_EXTERIOR_GEAR_TERMINA_FIELD: + case OBJ_TOKEIDAI_TYPE_EXTERIOR_GEAR_CLOCK_TOWN: + if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_ExteriorGear_Idle))) { + dsot_ObjTokeidai_update_clock(this, 0, 0); + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + this->actor.draw = actor_relocate(this, ObjTokeidai_ExteriorGear_Draw); + ObjTokeidai_Init(this, play); + } else { + dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute); + } + break; + + case OBJ_TOKEIDAI_TYPE_TOWER_CLOCK_TERMINA_FIELD: + case OBJ_TOKEIDAI_TYPE_TOWER_CLOCK_CLOCK_TOWN: + if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_TowerClock_Idle))) { + dsot_ObjTokeidai_update_clock(this, 0, 0); + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + this->actor.draw = actor_relocate(this, ObjTokeidai_Clock_Draw); + ObjTokeidai_Init(this, play); + } else { + dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute); + } + break; + case OBJ_TOKEIDAI_TYPE_STAIRCASE_TO_ROOFTOP: + if (PAST_MIDNIGHT) { + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + this->actor.draw = actor_relocate(this, ObjTokeidai_Draw); + } + break; + case OBJ_TOKEIDAI_TYPE_WALL_CLOCK: + case OBJ_TOKEIDAI_TYPE_SMALL_WALL_CLOCK: + dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute); + break; + } +} \ No newline at end of file diff --git a/patches/event_patches.c b/patches/event_patches.c new file mode 100644 index 0000000..484ec95 --- /dev/null +++ b/patches/event_patches.c @@ -0,0 +1,626 @@ +#include "patches.h" + +u8 func_800FE5D0(struct PlayState* play); + +void Environment_UpdateTimeBasedSequence(PlayState* play) { + s32 pad; + + //! FAKE: + if (gSaveContext.sceneLayer) {} + + if ((play->csCtx.state == CS_STATE_IDLE) && !(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) { + switch (play->envCtx.timeSeqState) { + case TIMESEQ_DAY_BGM: + break; + + case TIMESEQ_FADE_DAY_BGM: + if (CURRENT_TIME > CLOCK_TIME(17, 10)) { + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 240); + play->envCtx.timeSeqState++; + } + break; + + case TIMESEQ_NIGHT_BEGIN_SFX: + if (CURRENT_TIME >= CLOCK_TIME(18, 0)) { + play->envCtx.timeSeqState++; + } + break; + + case TIMESEQ_EARLY_NIGHT_CRITTERS: + if (play->envCtx.precipitation[PRECIP_RAIN_CUR] < 9) { + Audio_PlayAmbience(play->sequenceCtx.ambienceId); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 1); + } + play->envCtx.timeSeqState++; + break; + + case TIMESEQ_NIGHT_DELAY: + if (CURRENT_TIME >= CLOCK_TIME(19, 0)) { + play->envCtx.timeSeqState++; + } + break; + + case TIMESEQ_NIGHT_CRITTERS: + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 0); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 1); + play->envCtx.timeSeqState++; + break; + + case TIMESEQ_DAY_BEGIN_SFX: + if ((CURRENT_TIME < CLOCK_TIME(19, 0)) && (CURRENT_TIME >= CLOCK_TIME(5, 0))) { + play->envCtx.timeSeqState++; + } + break; + + case TIMESEQ_MORNING_CRITTERS: + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 0); + Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_4 << 4 | AMBIENCE_CHANNEL_CRITTER_5, 1, 1); + play->envCtx.timeSeqState++; + break; + + case TIMESEQ_DAY_DELAY: + break; + + default: + break; + } + } + + // @recomp Don't play final hours until it's actually past midnight + if ((play->envCtx.timeSeqState != TIMESEQ_REQUEST) && (((void)0, gSaveContext.save.day) == 3) && + (CURRENT_TIME < CLOCK_TIME(6, 0)) && !func_800FE5D0(play) && (play->transitionTrigger == TRANS_TRIGGER_OFF) && + (play->transitionMode == TRANS_MODE_OFF) && (play->csCtx.state == CS_STATE_IDLE) && + ((play->sceneId != SCENE_00KEIKOKU) || (((void)0, gSaveContext.sceneLayer) != 1)) && + (CutsceneManager_GetCurrentCsId() == CS_ID_NONE) && + (AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN) != NA_BGM_FINAL_HOURS) && + (AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN) != NA_BGM_SONG_OF_SOARING) && + (CURRENT_TIME > CLOCK_TIME(0, 0))) { + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_AMBIENCE, 0); + Audio_PlaySceneSequence(NA_BGM_FINAL_HOURS, 3 - 1); + } +} + +#include "overlays/actors/ovl_En_Test6/z_en_test6.h" +#include "z64quake.h" + +void func_800B7298(PlayState* play, Actor* csActor, u8 csAction); // Player_SetCsActionWithHaltedActors + +void EnTest6_SharedSoTCutscene(EnTest6* this, PlayState* play); + +extern CutsceneData sDoubleSoTCsCamData[]; +static Vec3f sSubCamUp; + +struct SoTCsAmmoDrops; + +typedef void (*SoTCsAmmoDropDrawFunc)(EnTest6*, PlayState*, struct SoTCsAmmoDrops*); + +typedef enum SoTCsAmmoDropType { + /* 0 */ SOTCS_AMMO_DROP_NONE, + /* 1 */ SOTCS_AMMO_DROP_ARROWS, + /* 2 */ SOTCS_AMMO_DROP_BOMB, + /* 3 */ SOTCS_AMMO_DROP_DEKU_NUT, + /* 4 */ SOTCS_AMMO_DROP_DEKU_STICK, + /* 5 */ SOTCS_AMMO_DROP_RUPEE_GREEN, + /* 6 */ SOTCS_AMMO_DROP_RUPEE_BLUE +} SoTCsAmmoDropType; + +typedef struct SoTCsAmmoDrops { + /* 0x00 */ SoTCsAmmoDropType type; + /* 0x04 */ f32 scale; + /* 0x08 */ Vec3f pos; + /* 0x14 */ SoTCsAmmoDropDrawFunc draw; +} SoTCsAmmoDrops; // size = 0x18 + +extern SoTCsAmmoDrops sSoTCsAmmoDrops[12]; + +typedef enum SoTCsDrawType { + /* 0 */ SOTCS_DRAW_DOUBLE_SOT, + /* 1 */ SOTCS_DRAW_RESET_CYCLE_SOT, + /* 2 */ SOTCS_DRAW_INVERTED_SOT, + /* 99 */ SOTCS_DRAW_TYPE_NONE = 99 +} SoTCsDrawType; + +void EnTest6_EnableMotionBlur(s16 alpha); +void EnTest6_DisableMotionBlur(void); + +void EnTest6_EnableWhiteFillScreen(PlayState* play, f32 alphaRatio); +void EnTest6_DisableWhiteFillScreen(PlayState* play); +void EnTest6_StopDoubleSoTCutscene(EnTest6* this, PlayState* play); + +extern Color_RGB8 sDoubleSoTCsFogColor; +extern Color_RGB8 sDoubleSoTCsAmbientColor; +extern Color_RGB8 sDoubleSoTCsDiffuseColor; +extern s16 sDoubleSoTCsFogNear; +extern s16 sDoubleSoTCsZFar; + +void EnTest6_DoubleSoTCutscene(EnTest6* this, PlayState* play) { + Input* input = CONTROLLER1(&play->state); + Player* player = GET_PLAYER(play); + Camera* subCam; + s32 pad; + s16 subCamId; + s16 pad2; + + if (this->timer > 115) { + this->doubleSoTEnvLerp += 0.2f; + EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp); + } else if (this->timer > 90) { + this->doubleSoTEnvLerp -= 0.05f; + EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp); + } else if (this->timer == 90) { + this->doubleSoTEnvLerp = 0.0f; + EnTest6_DisableWhiteFillScreen(play); + } + + if (this->timer == 1) { + this->doubleSoTEnvLerp = 0.0f; + EnTest6_DisableWhiteFillScreen(play); + } else if (this->timer < 17) { + this->doubleSoTEnvLerp -= 0.06666666f; + EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp); + } else if (this->timer < 22) { + this->doubleSoTEnvLerp += 0.2f; + EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp); + } + + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + s16* sDoubleSoTCsFogNear_ptr = actor_relocate(&this->actor, &sDoubleSoTCsFogNear); + s16* sDoubleSoTCsZFar_ptr = actor_relocate(&this->actor, &sDoubleSoTCsZFar); + + Color_RGB8* sDoubleSoTCsFogColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsFogColor); + Color_RGB8* sDoubleSoTCsAmbientColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsAmbientColor); + Color_RGB8* sDoubleSoTCsDiffuseColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsDiffuseColor); + + if (this->timer == 115) { + Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, 1.0f); + Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, 1.0f); + Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, 1.0f); + Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, 1.0f); + play->unk_18844 = true; + } + + if (this->timer == 15) { + Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, 0.0f); + Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, 0.0f); + Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, 0.0f); + Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, 0.0f); + play->unk_18844 = false; + } + + if (this->screenFillAlpha >= 20) { + Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, this->doubleSoTEnvLerp); + Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, this->doubleSoTEnvLerp); + Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, this->doubleSoTEnvLerp); + Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, this->doubleSoTEnvLerp); + play->unk_18844 = false; + } + + Actor_PlaySfx_FlaggedCentered1(&player->actor, NA_SE_PL_FLYING_AIR - SFX_FLAG); + + switch (this->timer) { + case 119: + EnTest6_EnableMotionBlur(50); + break; + + case 115: + EnTest6_EnableMotionBlur(20); + Distortion_Request(DISTORTION_TYPE_SONG_OF_TIME); + Distortion_SetDuration(90); + this->cueId = SOTCS_CUEID_DOUBLE_INIT; + break; + + case 110: + Audio_PlayFanfare(NA_BGM_SONG_OF_DOUBLE_TIME); + break; + + case 38: + case 114: + this->cueId = SOTCS_CUEID_DOUBLE_WAIT; + break; + + case 76: + this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_INWARD; + break; + + case 61: + EnTest6_EnableMotionBlur(150); + this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_SPIN; + break; + + case 51: + EnTest6_EnableMotionBlur(180); + this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD; + break; + + case 14: + case 15: + EnTest6_EnableMotionBlur(50); + Distortion_RemoveRequest(DISTORTION_TYPE_SONG_OF_TIME); + this->cueId = SOTCS_CUEID_NONE; + break; + + case 1: + EnTest6_DisableMotionBlur(); + if (CHECK_EVENTINF(EVENTINF_HAS_DAYTIME_TRANSITION_CS)) { + this->cueId = SOTCS_CUEID_DOUBLE_END; + } + break; + + default: + break; + } + + EnTest6_SharedSoTCutscene(this, play); + + if (this->timer == 115) { + subCamId = CutsceneManager_GetCurrentSubCamId(play->playerCsIds[PLAYER_CS_ID_SONG_WARP]); + subCam = Play_GetCamera(play, subCamId); + + this->subCamAt = subCam->at; + this->subCamEye = subCam->eye; + this->subCamFov = subCam->fov; + CutsceneCamera_Init(subCam, &this->csCamInfo); + } + + if ((this->timer <= 115) && (this->timer >= 16)) { + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + CutsceneData* sDoubleSoTCsCamData_reloc = actor_relocate(&this->actor, sDoubleSoTCsCamData); + CutsceneCamera_UpdateSplines((u8*)sDoubleSoTCsCamData_reloc, &this->csCamInfo); + } else if (this->timer < 16) { + subCamId = CutsceneManager_GetCurrentSubCamId(play->playerCsIds[PLAYER_CS_ID_SONG_WARP]); + + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + Vec3f* sSubCamUp_ptr = KaleidoManager_GetRamAddr(&sSubCamUp); + Play_SetCameraAtEyeUp(play, subCamId, &this->subCamAt, &this->subCamEye, sSubCamUp_ptr); + Play_SetCameraFov(play, subCamId, this->subCamFov); + Play_SetCameraRoll(play, subCamId, 0); + } + + switch (this->timer) { + case 116: + player->actor.freezeTimer = 2; + player->actor.shape.rot.x = 0; + player->actor.shape.rot.y = 0; + player->actor.world.pos.x = 0.0f; + player->actor.world.pos.y = 0.0f; + player->actor.world.pos.z = 0.0f; + player->actor.home.pos.x = 0.0f; + player->actor.home.pos.y = 0.0f; + player->actor.home.pos.z = 0.0f; + break; + + case 98: + func_800B7298(play, NULL, PLAYER_CSACTION_64); + break; + + case 68: + func_800B7298(play, NULL, PLAYER_CSACTION_65); + break; + + case 52: + func_800B7298(play, NULL, PLAYER_CSACTION_88); + break; + + case 43: + func_800B7298(play, NULL, PLAYER_CSACTION_114); + break; + + case 38: + func_800B7298(play, NULL, PLAYER_CSACTION_WAIT); + break; + + case 14: + player->actor.freezeTimer = 5; + player->actor.world.pos = player->actor.home.pos = this->actor.home.pos; + player->actor.shape.rot = this->actor.home.rot; + player->actor.focus.rot.y = player->actor.shape.rot.y; + player->currentYaw = player->actor.shape.rot.y; + player->unk_ABC = 0.0f; + player->unk_AC0 = 0.0f; + player->actor.shape.yOffset = 0.0f; + break; + + default: + break; + } + + if ((this->screenFillAlpha > 0) && (this->screenFillAlpha < 20)) { + EnTest6_EnableWhiteFillScreen(play, this->screenFillAlpha * 0.05f); + this->screenFillAlpha++; + if (this->screenFillAlpha >= 20) { + this->timer = 15; + this->doubleSoTEnvLerp = 0.9333333f; + } + } else if ((this->timer < 96) && (this->timer > 50) && + (CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_B))) { + this->screenFillAlpha = 1; + this->timer = 39; + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_FANFARE, 20); + } + + // @recomp Replace DSoT functionality if the option for it is enabled. + if (dsot_enabled() && this->timer == 15) { + dsot_advance_hour(play); + } + + if (DECR(this->timer) == 0) { + EnTest6_StopDoubleSoTCutscene(this, play); + } +} + +void Play_InitScene(PlayState* this, s32 spawn); + +/** + * Processes two different cutscenes: + * return to "Dawn of the First Day" Cs, and Song of Double Time Cs + */ +void EnTest6_SharedSoTCutscene(EnTest6* this, PlayState* play) { + s32 pad[2]; + Player* player = GET_PLAYER(play); + f32 yDiff; + s32 i; + s32 cueChannel; + + if (Cutscene_IsCueInChannel(play, CS_CMD_ACTOR_CUE_SOTCS)) { + cueChannel = Cutscene_GetCueChannel(play, CS_CMD_ACTOR_CUE_SOTCS); + this->cueId = play->csCtx.actorCues[cueChannel]->id; + + switch (this->cueId) { + case SOTCS_CUEID_DOUBLE_WAIT: + break; + + case SOTCS_CUEID_DOUBLE_INIT: + this->drawType = SOTCS_DRAW_DOUBLE_SOT; + this->counter = 0; + this->clockAngle = 0; + player->actor.shape.shadowDraw = NULL; + + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } else { + this->clockSpeed = 150.0f; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) { + this->clockColorGray = play->csCtx.actorCues[cueChannel]->startPos.y; + } else { + this->clockColorGray = 38; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) { + this->clockDist = (u32)play->csCtx.actorCues[cueChannel]->startPos.z; + } else { + this->clockDist = 480.0f; + } + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_INWARD: + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) { + this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y; + + } else { + this->clockColorGray += 6; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) { + this->clockRadialSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.z; + } else { + this->clockRadialSpeed = -32.0f; + } + this->clockDist += this->clockRadialSpeed; + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_SPIN: + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) { + this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y; + } else { + this->clockColorGray -= 4; + } + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD: + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) { + this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y; + } else { + this->clockColorGray -= 8; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) { + this->clockRadialSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.z; + } else { + this->clockRadialSpeed += 20.0f; + } + + this->clockDist += this->clockRadialSpeed; + if (this->clockDist > 3500.0f) { + this->clockDist = 3500.0f; + this->cueId = SOTCS_CUEID_NONE; + } + break; + + case SOTCS_CUEID_RESET_INIT: + this->drawType = SOTCS_DRAW_RESET_CYCLE_SOT; + this->counter = 0; + this->clockAngle = 0; + player->actor.shape.shadowDraw = NULL; + + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } else { + this->clockSpeed = 100.0f; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) { + this->speed = (u32)play->csCtx.actorCues[cueChannel]->startPos.y; + } else { + this->speed = 20.0f; + } + + if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) { + this->clockDist = (u32)play->csCtx.actorCues[cueChannel]->startPos.z; + } else { + this->clockDist = 300.0f; + } + this->clockAccel = 0.0f; + break; + + case SOTCS_CUEID_RESET_CLOCKS_SLOW_DOWN: + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockAccel = (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } else { + this->clockAccel = -5.0f; + } + this->clockSpeed += this->clockAccel; + break; + + case SOTCS_CUEID_RESET_CLOCKS_SPEED_UP: + if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) { + this->clockAccel += (u32)play->csCtx.actorCues[cueChannel]->startPos.x; + } else { + this->clockAccel += 2.0f; + } + + this->clockSpeed += this->clockAccel; + if (this->clockSpeed > 10000.0f) { + this->clockSpeed = 10000.0f; + this->cueId = SOTCS_CUEID_NONE; + } + break; + + case SOTCS_CUEID_NONE: + default: + this->drawType = SOTCS_DRAW_TYPE_NONE; + return; + + case SOTCS_CUEID_DOUBLE_END: + 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; + if ((CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME < CLOCK_TIME(6, 0))) { + gSaveContext.respawnFlag = -0x63; + SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); + } else { + gSaveContext.respawnFlag = 2; + } + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + return; + } + } else { + switch (this->cueId) { + case SOTCS_CUEID_DOUBLE_INIT: + this->drawType = SOTCS_DRAW_DOUBLE_SOT; + this->counter = 0; + this->clockAngle = 0; + player->actor.shape.shadowDraw = NULL; + this->clockColorGray = 38; + this->clockSpeed = 150.0f; + this->clockDist = 480.0f; + + case SOTCS_CUEID_DOUBLE_WAIT: + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_INWARD: + this->clockRadialSpeed = -32.0f; + this->clockColorGray += 6; + this->clockDist += -32.0f; + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_SPIN: + this->clockColorGray -= 4; + break; + + case SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD: + this->clockColorGray -= 8; + this->clockRadialSpeed += 20.0f; + this->clockDist += this->clockRadialSpeed; + if (this->clockDist > 3500.0f) { + this->clockDist = 3500.0f; + this->cueId = SOTCS_CUEID_NONE; + } + break; + + case SOTCS_CUEID_RESET_INIT: + this->drawType = SOTCS_DRAW_RESET_CYCLE_SOT; + this->counter = 0; + this->clockAngle = 0; + player->actor.shape.shadowDraw = NULL; + this->clockSpeed = 100.0f; + this->speed = 20.0f; + this->clockDist = 300.0f; + this->clockAccel = 0.0f; + break; + + case SOTCS_CUEID_RESET_CLOCKS_SLOW_DOWN: + this->clockAccel = -5.0f; + this->clockSpeed += -5.0f; + break; + + case SOTCS_CUEID_RESET_CLOCKS_SPEED_UP: + this->clockAccel += 2.0f; + this->clockSpeed += this->clockAccel; + if (this->clockSpeed > 10000.0f) { + this->clockSpeed = 10000.0f; + this->cueId = SOTCS_CUEID_NONE; + } + break; + + case SOTCS_CUEID_NONE: + default: + this->drawType = SOTCS_DRAW_TYPE_NONE; + return; + + case SOTCS_CUEID_DOUBLE_END: + // @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; + } + } + + SoTCsAmmoDrops* sSoTCsAmmoDrops_reloc = KaleidoManager_GetRamAddr(sSoTCsAmmoDrops); + if (this->drawType == SOTCS_DRAW_RESET_CYCLE_SOT) { + for (i = 0; i < ARRAY_COUNT(sSoTCsAmmoDrops_reloc); i++) { + sSoTCsAmmoDrops_reloc[i].pos.x += 2.0f * ((2.0f * Rand_ZeroOne()) - 1.0f); + sSoTCsAmmoDrops_reloc[i].pos.z += 2.0f * ((2.0f * Rand_ZeroOne()) - 1.0f); + sSoTCsAmmoDrops_reloc[i].pos.y += 3.0f; + + if (player->actor.world.pos.y < sSoTCsAmmoDrops_reloc[i].pos.y) { + yDiff = sSoTCsAmmoDrops_reloc[i].pos.y - player->actor.world.pos.y; + if (yDiff > 550.0f) { + yDiff = 1.0f; + } else { + yDiff /= 550.0f; + } + sSoTCsAmmoDrops_reloc[i].scale = SQ(yDiff); + } else { + sSoTCsAmmoDrops_reloc[i].scale = -10.0f; + } + } + } + this->counter++; +} \ No newline at end of file diff --git a/patches/message_patches.c b/patches/message_patches.c new file mode 100644 index 0000000..58af342 --- /dev/null +++ b/patches/message_patches.c @@ -0,0 +1,925 @@ +#include "patches.h" + +extern u8 D_801C6A70; + +extern s32 sCharTexSize; +extern s32 sCharTexScale; +extern s32 D_801F6B08; + +typedef enum { + /* 0 */ PAUSE_ITEM, + /* 1 */ PAUSE_MAP, + /* 2 */ PAUSE_QUEST, + /* 3 */ PAUSE_MASK, + /* 4 */ PAUSE_WORLD_MAP +} PauseMenuPage; + +typedef enum { + /* 0x00 */ PAUSE_STATE_OFF, + /* 0x01 */ PAUSE_STATE_OPENING_0, + /* 0x02 */ PAUSE_STATE_OPENING_1, + /* 0x03 */ PAUSE_STATE_OPENING_2, + /* 0x04 */ PAUSE_STATE_OPENING_3, + /* 0x05 */ PAUSE_STATE_OPENING_4, + /* 0x06 */ PAUSE_STATE_MAIN, // Pause menu ready for player inputs. + /* 0x07 */ PAUSE_STATE_SAVEPROMPT, + /* 0x08 */ PAUSE_STATE_GAMEOVER_0, + /* 0x09 */ PAUSE_STATE_GAMEOVER_1, + /* 0x0A */ PAUSE_STATE_GAMEOVER_2, + /* 0x0B */ PAUSE_STATE_GAMEOVER_3, + /* 0x0C */ PAUSE_STATE_GAMEOVER_4, + /* 0x0D */ PAUSE_STATE_GAMEOVER_5, + /* 0x0E */ PAUSE_STATE_GAMEOVER_SAVE_PROMPT, + /* 0x0F */ PAUSE_STATE_GAMEOVER_7, + /* 0x10 */ PAUSE_STATE_GAMEOVER_8, + /* 0x11 */ PAUSE_STATE_GAMEOVER_CONTINUE_PROMPT, + /* 0x12 */ PAUSE_STATE_GAMEOVER_10, + /* 0x13 */ PAUSE_STATE_OWL_WARP_0, + /* 0x14 */ PAUSE_STATE_OWL_WARP_1, + /* 0x15 */ PAUSE_STATE_OWL_WARP_2, + /* 0x16 */ PAUSE_STATE_OWL_WARP_3, + /* 0x17 */ PAUSE_STATE_OWL_WARP_SELECT, // Selecting the destination + /* 0x18 */ PAUSE_STATE_OWL_WARP_CONFIRM, // Confirming the choice given + /* 0x19 */ PAUSE_STATE_OWL_WARP_6, + /* 0x1A */ PAUSE_STATE_UNPAUSE_SETUP, // Unpause + /* 0x1B */ PAUSE_STATE_UNPAUSE_CLOSE +} PauseState; + +extern s16 sLastPlayedSong; + +extern s16 sTextboxUpperYPositions[]; +extern s16 sTextboxLowerYPositions[]; +extern s16 sTextboxMidYPositions[]; +extern s16 sTextboxXPositions[TEXTBOX_TYPE_MAX]; + +extern s16 D_801D0464[]; +extern s16 D_801D045C[]; + +void func_80150A84(PlayState* play); +void Message_GrowTextbox(PlayState* play); +void Message_Decode(PlayState* play); +s32 Message_BombersNotebookProcessEventQueue(PlayState* play); +void Message_HandleChoiceSelection(PlayState* play, u8 numChoices); +bool Message_ShouldAdvanceSilent(PlayState* play); + +void ShrinkWindow_Letterbox_SetSizeTarget(s32 target); +s32 ShrinkWindow_Letterbox_GetSizeTarget(void); + +// @recomp Patched to change functionality of Double SoT. +void Message_Update(PlayState* play) { + static u8 D_801D0468 = 0; + MessageContext* msgCtx = &play->msgCtx; + SramContext* sramCtx = &play->sramCtx; // Optional + PauseContext* pauseCtx = &play->pauseCtx; + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Input* input = CONTROLLER1(&play->state); + s16 avgScreenPosY; + s16 screenPosX; + u16 temp_v1_2; + s16 playerScreenPosY; + s16 actorScreenPosY; + s16 sp48; + s32 sp44; + s32 sp40; + u16 sp3E; + s16 var_v1; + u16 temp; + + msgCtx->stickAdjX = input->rel.stick_x; + msgCtx->stickAdjY = input->rel.stick_y; + + avgScreenPosY = 0; + + // If stickAdj is held, set a delay to allow the cursor to read the next input. + // The first delay is given a longer time than all subsequent delays. + if (msgCtx->stickAdjX < -30) { + if (msgCtx->stickXRepeatState == -1) { + msgCtx->stickXRepeatTimer--; + if (msgCtx->stickXRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + msgCtx->stickXRepeatTimer = 2; + } else { + // Cancel the current input + msgCtx->stickAdjX = 0; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + msgCtx->stickXRepeatTimer = 10; + msgCtx->stickXRepeatState = -1; + } + } else if (msgCtx->stickAdjX > 30) { + if (msgCtx->stickXRepeatState == 1) { + msgCtx->stickXRepeatTimer--; + if (msgCtx->stickXRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + msgCtx->stickXRepeatTimer = 2; + } else { + // Cancel the current input + msgCtx->stickAdjX = 0; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + msgCtx->stickXRepeatTimer = 10; + msgCtx->stickXRepeatState = 1; + } + } else { + msgCtx->stickXRepeatState = 0; + } + + if (msgCtx->stickAdjY < -30) { + if (msgCtx->stickYRepeatState == -1) { + msgCtx->stickYRepeatTimer--; + if (msgCtx->stickYRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + msgCtx->stickYRepeatTimer = 2; + } else { + // Cancel the current input + msgCtx->stickAdjY = 0; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + msgCtx->stickYRepeatTimer = 10; + msgCtx->stickYRepeatState = -1; + } + } else if (msgCtx->stickAdjY > 30) { + if (msgCtx->stickYRepeatState == 1) { + msgCtx->stickYRepeatTimer--; + if (msgCtx->stickYRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + msgCtx->stickYRepeatTimer = 2; + } else { + // Cancel the current input + msgCtx->stickAdjY = 0; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + msgCtx->stickYRepeatTimer = 10; + msgCtx->stickYRepeatState = 1; + } + } else { + msgCtx->stickYRepeatState = 0; + } + + if (msgCtx->msgLength == 0) { + return; + } + + switch (msgCtx->msgMode) { + case MSGMODE_TEXT_START: + D_801C6A70++; + + temp = false; + if ((D_801C6A70 >= 4) || ((msgCtx->talkActor == NULL) && (D_801C6A70 >= 2))) { + temp = true; + } + if (temp) { + if (msgCtx->talkActor != NULL) { + Actor_GetScreenPos(play, &GET_PLAYER(play)->actor, &screenPosX, &playerScreenPosY); + Actor_GetScreenPos(play, msgCtx->talkActor, &screenPosX, &actorScreenPosY); + if (playerScreenPosY >= actorScreenPosY) { + avgScreenPosY = ((playerScreenPosY - actorScreenPosY) / 2) + actorScreenPosY; + } else { + avgScreenPosY = ((actorScreenPosY - playerScreenPosY) / 2) + playerScreenPosY; + } + } else { + msgCtx->textboxX = msgCtx->textboxXTarget; + msgCtx->textboxY = msgCtx->textboxYTarget; + } + + var_v1 = msgCtx->textBoxType; + + if ((u32)msgCtx->textBoxPos == 0) { + if ((play->sceneId == SCENE_UNSET_04) || (play->sceneId == SCENE_UNSET_05) || + (play->sceneId == SCENE_UNSET_06)) { + if (avgScreenPosY < 100) { + msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1]; + } else { + msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1]; + } + } else { + if (avgScreenPosY < 160) { + msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1]; + } else { + msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1]; + } + } + } else if (msgCtx->textBoxPos == 1) { + msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1]; + } else if (msgCtx->textBoxPos == 2) { + msgCtx->textboxYTarget = sTextboxMidYPositions[var_v1]; + } else if (msgCtx->textBoxPos == 7) { + msgCtx->textboxYTarget = 0x9E; + } else { + msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1]; + } + + msgCtx->textboxXTarget = sTextboxXPositions[var_v1]; + + if ((gSaveContext.options.language == LANGUAGE_JPN) && !msgCtx->textIsCredits) { + msgCtx->unk11FFE[0] = (s16)(msgCtx->textboxYTarget + 7); + msgCtx->unk11FFE[1] = (s16)(msgCtx->textboxYTarget + 0x19); + msgCtx->unk11FFE[2] = (s16)(msgCtx->textboxYTarget + 0x2B); + } else { + msgCtx->unk11FFE[0] = (s16)(msgCtx->textboxYTarget + 0x14); + msgCtx->unk11FFE[1] = (s16)(msgCtx->textboxYTarget + 0x20); + msgCtx->unk11FFE[2] = (s16)(msgCtx->textboxYTarget + 0x2C); + } + + if ((msgCtx->textBoxType == TEXTBOX_TYPE_4) || (msgCtx->textBoxType == TEXTBOX_TYPE_5)) { + msgCtx->msgMode = MSGMODE_TEXT_STARTING; + msgCtx->textboxX = msgCtx->textboxXTarget; + msgCtx->textboxY = msgCtx->textboxYTarget; + msgCtx->unk12008 = 0x100; + msgCtx->unk1200A = 0x40; + msgCtx->unk1200C = 0x200; + msgCtx->unk1200E = 0x200; + break; + } + + Message_GrowTextbox(play); + Audio_PlaySfx_IfNotInCutscene(NA_SE_NONE); + msgCtx->stateTimer = 0; + msgCtx->msgMode = MSGMODE_TEXT_BOX_GROWING; + + if (!pauseCtx->itemDescriptionOn) { + func_80150A84(play); + } + } + break; + + case MSGMODE_TEXT_BOX_GROWING: + Message_GrowTextbox(play); + break; + + case MSGMODE_TEXT_STARTING: + msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG; + if (!pauseCtx->itemDescriptionOn) { + if (msgCtx->currentTextId == 0xFF) { + func_8011552C(play, DO_ACTION_STOP); + } else if (msgCtx->currentTextId != 0xF8) { + func_8011552C(play, DO_ACTION_NEXT); + } + } + break; + + case MSGMODE_TEXT_NEXT_MSG: + Message_Decode(play); + if (msgCtx->textFade) { + Interface_SetHudVisibility(HUD_VISIBILITY_NONE); + } + if (D_801D0468 != 0) { + msgCtx->textDrawPos = msgCtx->decodedTextLen; + D_801D0468 = 0; + } + break; + + case MSGMODE_TEXT_CONTINUING: + msgCtx->stateTimer--; + if (msgCtx->stateTimer == 0) { + Message_Decode(play); + } + break; + + case MSGMODE_TEXT_DISPLAYING: + if (msgCtx->textBoxType != TEXTBOX_TYPE_4) { + if (CHECK_BTN_ALL(input->press.button, BTN_B) && !msgCtx->textUnskippable) { + msgCtx->textboxSkipped = true; + msgCtx->textDrawPos = msgCtx->decodedTextLen; + } else if (CHECK_BTN_ALL(input->press.button, BTN_A) && !msgCtx->textUnskippable) { + + while (true) { + temp_v1_2 = msgCtx->decodedBuffer.wchar[msgCtx->textDrawPos]; + if ((temp_v1_2 == 0x10) || (temp_v1_2 == 0x12) || (temp_v1_2 == 0x1B) || (temp_v1_2 == 0x1C) || + (temp_v1_2 == 0x1D) || (temp_v1_2 == 0x19) || (temp_v1_2 == 0xE0) || (temp_v1_2 == 0xBF) || + (temp_v1_2 == 0x15) || (temp_v1_2 == 0x1A)) { + break; + } + msgCtx->textDrawPos++; + } + } + } else if (CHECK_BTN_ALL(input->press.button, BTN_A) && (msgCtx->textUnskippable == 0)) { + while (true) { + temp_v1_2 = msgCtx->decodedBuffer.wchar[msgCtx->textDrawPos]; + if ((temp_v1_2 == 0x10) || (temp_v1_2 == 0x12) || (temp_v1_2 == 0x1B) || (temp_v1_2 == 0x1C) || + (temp_v1_2 == 0x1D) || (temp_v1_2 == 0x19) || (temp_v1_2 == 0xE0) || (temp_v1_2 == 0xBF) || + (temp_v1_2 == 0x15) || (temp_v1_2 == 0x1A)) { + break; + } + msgCtx->textDrawPos++; + } + } + break; + + case MSGMODE_TEXT_AWAIT_INPUT: + if (Message_ShouldAdvance(play)) { + msgCtx->msgMode = MSGMODE_TEXT_DISPLAYING; + msgCtx->textDrawPos++; + } + break; + + case MSGMODE_TEXT_DELAYED_BREAK: + msgCtx->stateTimer--; + if (msgCtx->stateTimer == 0) { + msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG; + } + break; + + case MSGMODE_TEXT_AWAIT_NEXT: + if (Message_ShouldAdvance(play)) { + msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG; + msgCtx->msgBufPos++; + } + break; + + case MSGMODE_TEXT_DONE: + if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52)) { + msgCtx->stateTimer--; + if ((msgCtx->stateTimer == 0) || + ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52) && Message_ShouldAdvance(play))) { + if (msgCtx->nextTextId != 0xFFFF) { + Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS); + Message_ContinueTextbox(play, msgCtx->nextTextId); + } else if (msgCtx->bombersNotebookEventQueueCount != 0) { + if (Message_BombersNotebookProcessEventQueue(play) == 0) { + Message_CloseTextbox(play); + } + } else { + Message_CloseTextbox(play); + } + } + } else { + if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_30) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_40) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_42) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_41)) { + return; + } + + switch (msgCtx->textboxEndType) { + case TEXTBOX_ENDTYPE_55: + msgCtx->textColorAlpha += 20; + if (msgCtx->textColorAlpha >= 255) { + msgCtx->textColorAlpha = 255; + msgCtx->textboxEndType = TEXTBOX_ENDTYPE_56; + } + break; + + case TEXTBOX_ENDTYPE_56: + msgCtx->stateTimer--; + if (msgCtx->stateTimer == 0) { + msgCtx->textboxEndType = TEXTBOX_ENDTYPE_57; + } + break; + + case TEXTBOX_ENDTYPE_57: + msgCtx->textColorAlpha -= 20; + if (msgCtx->textColorAlpha <= 0) { + msgCtx->textColorAlpha = 0; + if (msgCtx->nextTextId != 0xFFFF) { + Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS); + Message_ContinueTextbox(play, msgCtx->nextTextId); + return; + } + if (msgCtx->bombersNotebookEventQueueCount != 0) { + if (Message_BombersNotebookProcessEventQueue(play) == 0) { + Message_CloseTextbox(play); + return; + } + } else { + Message_CloseTextbox(play); + return; + } + } + break; + + case TEXTBOX_ENDTYPE_10: + Message_HandleChoiceSelection(play, 1); + break; + + case TEXTBOX_ENDTYPE_11: + Message_HandleChoiceSelection(play, 2); + break; + + case TEXTBOX_ENDTYPE_12: + Message_HandleChoiceSelection(play, 1); + break; + + default: + break; + } + + if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_ACTIVE)) { + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + play->msgCtx.ocarinaMode = OCARINA_MODE_WARP; + } else { + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } + Message_CloseTextbox(play); + } + } else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_SOT)) { + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + Audio_PlaySfx_MessageDecide(); + msgCtx->msgMode = MSGMODE_NEW_CYCLE_0; + msgCtx->decodedTextLen -= 3; + msgCtx->unk120D6 = 0; + msgCtx->unk120D4 = 0; + } else { + Audio_PlaySfx_MessageCancel(); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + Message_CloseTextbox(play); + } + } + } else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_INVERTED_TIME)) { + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + Audio_PlaySfx_MessageDecide(); + if (gSaveContext.save.timeSpeedOffset == 0) { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_INV_SOT_SLOW; + gSaveContext.save.timeSpeedOffset = -2; + } else { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_INV_SOT_FAST; + gSaveContext.save.timeSpeedOffset = 0; + } + Message_CloseTextbox(play); + } else { + Audio_PlaySfx_MessageCancel(); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + Message_CloseTextbox(play); + } + } + } else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME)) { + // @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(); + + 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); + } + 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); + } + } + } else if ((msgCtx->textboxEndType != TEXTBOX_ENDTYPE_10) || + (pauseCtx->state != PAUSE_STATE_OWL_WARP_CONFIRM)) { + if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_1B)) { + if (Message_ShouldAdvance(play)) { + if (msgCtx->choiceIndex == 0) { + Audio_PlaySfx_MessageDecide(); + play->msgCtx.ocarinaMode = OCARINA_MODE_WARP_TO_ENTRANCE; + } else { + Audio_PlaySfx_MessageCancel(); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } + Message_CloseTextbox(play); + } + } else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_60) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_61) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_11) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_55) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_56) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_57) || + (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_62)) { + //! FAKE: debug? + if (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) {} + } else if (pauseCtx->itemDescriptionOn) { + if ((input->rel.stick_x != 0) || (input->rel.stick_y != 0) || + CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_B) || + CHECK_BTN_ALL(input->press.button, BTN_START)) { + Audio_PlaySfx(NA_SE_SY_DECIDE); + Message_CloseTextbox(play); + } + } else if (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_RESTRICTED_SONG) { + if (Message_ShouldAdvanceSilent(play)) { + Audio_PlaySfx(NA_SE_SY_DECIDE); + Message_CloseTextbox(play); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } + } else if ((msgCtx->currentTextId != 0x2790) && Message_ShouldAdvanceSilent(play)) { + if (msgCtx->nextTextId != 0xFFFF) { + Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS); + Message_ContinueTextbox(play, msgCtx->nextTextId); + } else if ((msgCtx->bombersNotebookEventQueueCount == 0) || + (Message_BombersNotebookProcessEventQueue(play) != 1)) { + if (msgCtx->currentTextId == 0x579) { + gSaveContext.hudVisibility = HUD_VISIBILITY_IDLE; + } + Audio_PlaySfx(NA_SE_SY_DECIDE); + Message_CloseTextbox(play); + } + } + } + } + break; + + case MSGMODE_TEXT_CLOSING: + msgCtx->stateTimer--; + if (msgCtx->stateTimer != 0) { + break; + } + + if (sLastPlayedSong == OCARINA_SONG_SOARING) { + if (interfaceCtx->restrictions.songOfSoaring == 0) { + if (func_8010A0A4(play) || (play->sceneId == SCENE_SECOM)) { + Message_StartTextbox(play, 0x1B93, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_1B; + sLastPlayedSong = 0xFF; + } else if (!msgCtx->ocarinaSongEffectActive) { + if (gSaveContext.save.saveInfo.playerData.owlActivationFlags != 0) { + pauseCtx->unk_2C8 = pauseCtx->pageIndex; + pauseCtx->unk_2CA = pauseCtx->cursorPoint[4]; + pauseCtx->pageIndex = PAUSE_ITEM; + pauseCtx->state = PAUSE_STATE_OWL_WARP_0; + func_800F4A10(play); + pauseCtx->pageIndex = PAUSE_MAP; + sLastPlayedSong = 0xFF; + Message_CloseTextbox(play); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + gSaveContext.prevHudVisibility = HUD_VISIBILITY_A_B; + func_80115844(play, DO_ACTION_STOP); + GameState_SetFramerateDivisor(&play->state, 2); + if (ShrinkWindow_Letterbox_GetSizeTarget() != 0) { + ShrinkWindow_Letterbox_SetSizeTarget(0); + } + Audio_PlaySfx_PauseMenuOpenOrClose(1); + break; + } + sLastPlayedSong = 0xFF; + Message_StartTextbox(play, 0xFB, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + } else { + msgCtx->stateTimer = 1; + } + } else { + sLastPlayedSong = 0xFF; + Message_StartTextbox(play, 0x1B95, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + } + break; + } + + if ((msgCtx->currentTextId == 0xC) || (msgCtx->currentTextId == 0xD) || (msgCtx->currentTextId == 0xC5) || + (msgCtx->currentTextId == 0xC6) || (msgCtx->currentTextId == 0xC7) || + (msgCtx->currentTextId == 0x2165) || (msgCtx->currentTextId == 0x2166) || + (msgCtx->currentTextId == 0x2167) || (msgCtx->currentTextId == 0x2168)) { + gSaveContext.healthAccumulator = 20 * 0x10; // Refill 20 hearts + } + + if ((play->csCtx.state == CS_STATE_IDLE) && (gSaveContext.save.cutsceneIndex < 0xFFF0) && + ((play->activeCamId == CAM_ID_MAIN) || + ((play->transitionTrigger == TRANS_TRIGGER_OFF) && (play->transitionMode == TRANS_MODE_OFF))) && + (play->msgCtx.ocarinaMode == OCARINA_MODE_END)) { + if (((u32)gSaveContext.prevHudVisibility == HUD_VISIBILITY_IDLE) || + (gSaveContext.prevHudVisibility == HUD_VISIBILITY_NONE) || + (gSaveContext.prevHudVisibility == HUD_VISIBILITY_NONE_ALT)) { + gSaveContext.prevHudVisibility = HUD_VISIBILITY_ALL; + } + gSaveContext.hudVisibility = HUD_VISIBILITY_IDLE; + } + + if ((msgCtx->currentTextId >= 0x1BB2) && (msgCtx->currentTextId <= 0x1BB6) && + (play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) { + Message_StartTextbox(play, 0x5E6, NULL); + break; + } + + if (msgCtx->bombersNotebookEventQueueCount != 0) { + if (Message_BombersNotebookProcessEventQueue(play) == 0) { + msgCtx->stateTimer = 1; + } + break; + } + + msgCtx->msgLength = 0; + msgCtx->msgMode = MSGMODE_NONE; + msgCtx->currentTextId = 0; + msgCtx->stateTimer = 0; + XREG(31) = 0; + + if (pauseCtx->itemDescriptionOn) { + func_8011552C(play, DO_ACTION_INFO); + pauseCtx->itemDescriptionOn = false; + } + + if (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_30) { + msgCtx->textboxEndType = TEXTBOX_ENDTYPE_00; + play->msgCtx.ocarinaMode = OCARINA_MODE_WARP; + } else { + msgCtx->textboxEndType = TEXTBOX_ENDTYPE_00; + } + + if (EQ_MAX_QUEST_HEART_PIECE_COUNT) { + RESET_HEART_PIECE_COUNT; + gSaveContext.save.saveInfo.playerData.healthCapacity += 0x10; + gSaveContext.save.saveInfo.playerData.health += 0x10; + } + + if (msgCtx->ocarinaAction != OCARINA_ACTION_CHECK_NOTIME_DONE) { + s16 pad; + + if (sLastPlayedSong == OCARINA_SONG_TIME) { + if (interfaceCtx->restrictions.songOfTime == 0) { + Message_StartTextbox(play, 0x1B8A, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_SOT; + } else { + sLastPlayedSong = 0xFF; + Message_StartTextbox(play, 0x1B95, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + } + } else if (sLastPlayedSong == OCARINA_SONG_INVERTED_TIME) { + if (interfaceCtx->restrictions.invSongOfTime == 0) { + if (R_TIME_SPEED != 0) { + if (gSaveContext.save.timeSpeedOffset == 0) { + Message_StartTextbox(play, 0x1B8C, NULL); + } else { + Message_StartTextbox(play, 0x1B8D, NULL); + } + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_INVERTED_TIME; + } else { + Message_StartTextbox(play, 0x1B8B, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + } + } else { + sLastPlayedSong = 0xFF; + Message_StartTextbox(play, 0x1B95, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + } + } else if (sLastPlayedSong == OCARINA_SONG_DOUBLE_TIME) { + if (interfaceCtx->restrictions.songOfDoubleTime == 0) { + // @recomp + dsot_determine_enabled(); + + // @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); + + // @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; + + 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 { + 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; + Message_StartTextbox(play, 0x1B95, NULL); + play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + } + } else if ((msgCtx->ocarinaAction == OCARINA_ACTION_FREE_PLAY_DONE) && + ((play->msgCtx.ocarinaMode == OCARINA_MODE_ACTIVE) || + (play->msgCtx.ocarinaMode == OCARINA_MODE_EVENT) || + (play->msgCtx.ocarinaMode == OCARINA_MODE_PLAYED_SCARECROW_SPAWN) || + (play->msgCtx.ocarinaMode == OCARINA_MODE_PLAYED_FULL_EVAN_SONG))) { + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + if (msgCtx->lastPlayedSong == OCARINA_SONG_SOARING) { + play->msgCtx.ocarinaMode = OCARINA_MODE_ACTIVE; + } + } + sLastPlayedSong = 0xFF; + } + break; + + case MSGMODE_OCARINA_PLAYING: + if (CHECK_BTN_ALL(input->press.button, BTN_B)) { + AudioOcarina_SetInstrument(OCARINA_INSTRUMENT_OFF); + play->msgCtx.ocarinaMode = OCARINA_MODE_END; + Message_CloseTextbox(play); + } else { + msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID; + } + break; + + case MSGMODE_OCARINA_AWAIT_INPUT: + if ((msgCtx->ocarinaAction != OCARINA_ACTION_PROMPT_EVAN_PART1_SECOND_HALF) && + (msgCtx->ocarinaAction != OCARINA_ACTION_PROMPT_EVAN_PART2_SECOND_HALF)) { + if (Message_ShouldAdvance(play)) { + Message_DisplayOcarinaStaff(play, msgCtx->ocarinaAction); + } + } + break; + + case MSGMODE_SCARECROW_SPAWN_RECORDING_ONGOING: + if (CHECK_BTN_ALL(input->press.button, BTN_B)) { + AudioOcarina_SetRecordingState(OCARINA_RECORD_OFF); + Audio_PlaySfx(NA_SE_SY_OCARINA_ERROR); + Message_CloseTextbox(play); + msgCtx->msgMode = MSGMODE_SCARECROW_SPAWN_RECORDING_FAILED; + } else { + msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID; + } + break; + + case MSGMODE_SCENE_TITLE_CARD_FADE_IN_BACKGROUND: + msgCtx->textboxColorAlphaCurrent += XREG(73); + if (msgCtx->textboxColorAlphaCurrent >= 255) { + msgCtx->textboxColorAlphaCurrent = 255; + msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_IN_TEXT; + } + break; + + case MSGMODE_SCENE_TITLE_CARD_FADE_IN_TEXT: + msgCtx->textColorAlpha += XREG(73); + if (msgCtx->textColorAlpha >= 255) { + msgCtx->textColorAlpha = 255; + msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_DISPLAYING; + } + break; + + case MSGMODE_SCENE_TITLE_CARD_DISPLAYING: + msgCtx->stateTimer--; + if (msgCtx->stateTimer == 0) { + msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_OUT_TEXT; + } + break; + + case MSGMODE_SCENE_TITLE_CARD_FADE_OUT_TEXT: + msgCtx->textColorAlpha -= XREG(70); + if (msgCtx->textColorAlpha <= 0) { + msgCtx->textColorAlpha = 0; + msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_OUT_BACKGROUND; + } + break; + + case MSGMODE_SCENE_TITLE_CARD_FADE_OUT_BACKGROUND: + msgCtx->textboxColorAlphaCurrent -= XREG(70); + if (msgCtx->textboxColorAlphaCurrent <= 0) { + if ((msgCtx->currentTextId >= 0x1BB2) && (msgCtx->currentTextId <= 0x1BB6) && + (play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) { + Message_StartTextbox(play, 0x5E6, NULL); + Interface_SetHudVisibility(HUD_VISIBILITY_NONE_ALT); + } else { + //! FAKE: debug? + if (msgCtx->currentTextId >= 0x100) { + if (msgCtx && msgCtx && msgCtx) {} + } + msgCtx->textboxColorAlphaCurrent = 0; + msgCtx->msgLength = 0; + msgCtx->msgMode = MSGMODE_NONE; + msgCtx->currentTextId = 0; + msgCtx->stateTimer = 0; + } + } + break; + + case MSGMODE_NEW_CYCLE_0: + play->state.unk_A3 = 1; + sp44 = gSaveContext.save.cutsceneIndex; + sp3E = CURRENT_TIME; + sp40 = gSaveContext.save.day; + + Sram_SaveEndOfCycle(play); + gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF; + func_8014546C(&play->sramCtx); + + gSaveContext.save.day = sp40; + gSaveContext.save.time = sp3E; + gSaveContext.save.cutsceneIndex = sp44; + + if (gSaveContext.fileNum != 0xFF) { + Sram_SetFlashPagesDefault(&play->sramCtx, gFlashSaveStartPages[gSaveContext.fileNum * 2], + gFlashSpecialSaveNumPages[gSaveContext.fileNum * 2]); + Sram_StartWriteToFlashDefault(&play->sramCtx); + } + msgCtx->msgMode = MSGMODE_NEW_CYCLE_1; + break; + + case MSGMODE_NEW_CYCLE_1: + if (gSaveContext.fileNum != 0xFF) { + play->state.unk_A3 = 1; + if (play->sramCtx.status == 0) { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT; + msgCtx->msgMode = MSGMODE_NEW_CYCLE_2; + } + } else { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT; + msgCtx->msgMode = MSGMODE_NEW_CYCLE_2; + } + break; + + case MSGMODE_OWL_SAVE_0: + play->state.unk_A3 = 1; + gSaveContext.save.isOwlSave = true; + Play_SaveCycleSceneFlags(&play->state); + func_8014546C(&play->sramCtx); + + if (gSaveContext.fileNum != 0xFF) { + Sram_SetFlashPagesOwlSave(&play->sramCtx, gFlashOwlSaveStartPages[gSaveContext.fileNum * 2], + gFlashOwlSaveNumPages[gSaveContext.fileNum * 2]); + Sram_StartWriteToFlashOwlSave(&play->sramCtx); + } + msgCtx->msgMode = MSGMODE_OWL_SAVE_1; + break; + + case MSGMODE_OWL_SAVE_1: + if (gSaveContext.fileNum != 0xFF) { + play->state.unk_A3 = 1; + if (play->sramCtx.status == 0) { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT; + msgCtx->msgMode = MSGMODE_OWL_SAVE_2; + } + } else { + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT; + msgCtx->msgMode = MSGMODE_OWL_SAVE_2; + } + + if (msgCtx->msgMode == MSGMODE_OWL_SAVE_2) { + gSaveContext.gameMode = GAMEMODE_OWL_SAVE; + play->transitionTrigger = TRANS_TRIGGER_START; + play->transitionType = TRANS_TYPE_FADE_BLACK; + play->nextEntrance = ENTRANCE(CUTSCENE, 0); + gSaveContext.save.cutsceneIndex = 0; + gSaveContext.sceneLayer = 0; + } + break; + + case MSGMODE_9: + case MSGMODE_PAUSED: + case MSGMODE_NEW_CYCLE_2: + case MSGMODE_OWL_SAVE_2: + break; + + default: + msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID; + break; + } +} \ No newline at end of file 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 9ad713b..54fbfd3 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -108,4 +108,12 @@ 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); +void dsot_advance_hour(PlayState* play); +void dsot_draw_clock(PlayState* play); + #endif 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 bd299c7..455bc01 100644 --- a/patches/ui_patches.c +++ b/patches/ui_patches.c @@ -94,8 +94,8 @@ void Graph_ExecuteAndDraw(GraphicsContext* gfxCtx, GameState* gameState) { GameState_Update(gameState); OPEN_DISPS(gfxCtx); - - // @recomp Send the current framerate to RT64, including any extra VI interrupt periods. + + // @recomp Send the current framerate to RT64, including any extra VI interrupt periods. gEXSetRefreshRate(POLY_OPA_DISP++, 60 / (gameState->framerateDivisor + extra_vis)); // @recomp Edit billboard groups to skip interpolation if the camera also skipped. @@ -485,7 +485,7 @@ void Interface_Draw(PlayState* play) { gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, -margin_reduction * 4, -margin_reduction * 4, -margin_reduction * 4, -margin_reduction * 4); Magic_DrawMeter(play); - + // @recomp Draw the D-Pad and its item icons as well as the autosave icon if the game is unpaused. if (pauseCtx->state != PAUSE_STATE_MAIN) { draw_dpad(play); @@ -617,7 +617,7 @@ void Interface_Draw(PlayState* play) { Interface_DrawClock(play); } } - + // @recomp Restore normal alignment and reset shift for minigame "Perfect" text gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, G_EX_ORIGIN_NONE, 0, 0, 0, 0); gEXSetViewportAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, 0, 0); @@ -635,7 +635,7 @@ void Interface_Draw(PlayState* play) { Interface_DrawMinigameIcons(play); Interface_DrawTimers(play); - + // @recomp Restore normal alignment and shift down for minigame countdown or clock gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, G_EX_ORIGIN_NONE, 0, 0, 0, 0); gEXSetViewportAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, 0, 0); @@ -936,7 +936,7 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { textbox_viewport->vp.vscale[1] = (gCfbHeight / 2) << 2; textbox_viewport->vp.vscale[2] = G_MAXZ; textbox_viewport->vp.vscale[3] = 0; - + textbox_viewport->vp.vtrans[0] = (gCfbWidth / 2) << 2; textbox_viewport->vp.vtrans[1] = (gCfbHeight / 2) << 2; textbox_viewport->vp.vtrans[2] = 0; @@ -990,6 +990,11 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { gSPPopMatrix(gfx++, G_MTX_MODELVIEW); } + // @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); + } + // Draw treble clef if (msgCtx->textBoxType == TEXTBOX_TYPE_3) { gDPPipeSync(gfx++); @@ -1137,7 +1142,7 @@ void ShrinkWindow_Draw(GraphicsContext* gfxCtx) { gSPMatrix(gfx++, letterbox_matrix_top, G_MTX_MODELVIEW | G_MTX_PUSH | G_MTX_LOAD); gSPVertex(gfx++, letterbox_verts, 4, 0); gSP2Triangles(gfx++, 0, 1, 3, 0x0, 0, 3, 2, 0x0); - + // @recomp Draw the bottom letterbox element. gSPMatrix(gfx++, letterbox_matrix_bottom, G_MTX_MODELVIEW | G_MTX_NOPUSH | G_MTX_LOAD); gSPVertex(gfx++, letterbox_verts, 4, 0); @@ -1187,7 +1192,7 @@ void ShrinkWindow_Draw(GraphicsContext* gfxCtx) { extern u64 gSceneTitleCardGradientTex[]; -// @recomp Patch the scene title card (the one with purple background when entering a new scene) +// @recomp Patch the scene title card (the one with purple background when entering a new scene) // to not glitch out on the right edge, which is hidden by overscan on N64. void Message_DrawSceneTitleCard(PlayState* play, Gfx** gfxP) { MessageContext* msgCtx = &play->msgCtx; @@ -1217,4 +1222,4 @@ void Message_DrawSceneTitleCard(PlayState* play, Gfx** gfxP) { msgCtx->unk11FFA = XREG(74); Message_DrawTextNES(play, &gfx, 0); *gfxP = gfx++; -} +} \ No newline at end of file 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();