From e0e19d9bf6efe8c32937de395cf3df9d55c72797 Mon Sep 17 00:00:00 2001 From: Archez Date: Tue, 21 Jan 2025 19:24:06 -0500 Subject: [PATCH 1/6] Add ability to override current hud editor element mode --- mm/2s2h/BenGui/HudEditor.cpp | 25 ++++++++++++++++++------- mm/2s2h/BenGui/HudEditor.h | 2 ++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/mm/2s2h/BenGui/HudEditor.cpp b/mm/2s2h/BenGui/HudEditor.cpp index ad17c82595..604951f633 100644 --- a/mm/2s2h/BenGui/HudEditor.cpp +++ b/mm/2s2h/BenGui/HudEditor.cpp @@ -6,6 +6,7 @@ extern "C" int16_t OTRGetRectDimensionFromLeftEdge(float v); extern "C" int16_t OTRGetRectDimensionFromRightEdge(float v); HudEditorElementID hudEditorActiveElement = HUD_EDITOR_ELEMENT_NONE; +HudEditorElementMode hudEditorOverrideNextElemMode = HUD_EDITOR_ELEMENT_MODE_NONE; HudEditorElement hudEditorElements[HUD_EDITOR_ELEMENT_MAX] = { HUD_EDITOR_ELEMENT(HUD_EDITOR_ELEMENT_B, "B Button", "B", 167, 17, 100, 255, 120, 255), @@ -30,10 +31,18 @@ HudEditorElement hudEditorElements[HUD_EDITOR_ELEMENT_MAX] = { HUD_EDITOR_ELEMENT(HUD_EDITOR_ELEMENT_SKULLTULA_COUNTER, "Skulltulas", "Skulltulas", 26, 190, 255, 255, 255, 255), }; +// Allows specifying an override mode to the next active element. +// Must be called again with HUD_EDITOR_ELEMENT_MODE_NONE when done overriding. +extern "C" void HudEditor_OverrideNextElementMode(HudEditorElementMode mode) { + hudEditorOverrideNextElemMode = mode; +} + extern "C" bool HudEditor_ShouldOverrideDraw() { return hudEditorActiveElement != HUD_EDITOR_ELEMENT_NONE && - CVarGetInteger(hudEditorElements[hudEditorActiveElement].modeCvar, HUD_EDITOR_ELEMENT_MODE_VANILLA) != - HUD_EDITOR_ELEMENT_MODE_VANILLA; + (hudEditorOverrideNextElemMode != HUD_EDITOR_ELEMENT_MODE_NONE + ? hudEditorOverrideNextElemMode + : CVarGetInteger(hudEditorElements[hudEditorActiveElement].modeCvar, + HUD_EDITOR_ELEMENT_MODE_VANILLA)) != HUD_EDITOR_ELEMENT_MODE_VANILLA; } extern "C" void HudEditor_SetActiveElement(HudEditorElementID id) { @@ -41,14 +50,16 @@ extern "C" void HudEditor_SetActiveElement(HudEditorElementID id) { } extern "C" bool HudEditor_IsActiveElementHidden() { - return hudEditorActiveElement != HUD_EDITOR_ELEMENT_NONE - ? CVarGetInteger(hudEditorElements[hudEditorActiveElement].modeCvar, HUD_EDITOR_ELEMENT_MODE_VANILLA) == - HUD_EDITOR_ELEMENT_MODE_HIDDEN - : false; + return hudEditorActiveElement != HUD_EDITOR_ELEMENT_NONE && + (hudEditorOverrideNextElemMode != HUD_EDITOR_ELEMENT_MODE_NONE + ? hudEditorOverrideNextElemMode + : CVarGetInteger(hudEditorElements[hudEditorActiveElement].modeCvar, + HUD_EDITOR_ELEMENT_MODE_VANILLA)) == HUD_EDITOR_ELEMENT_MODE_HIDDEN; } extern "C" f32 HudEditor_GetActiveElementScale() { - return hudEditorActiveElement != HUD_EDITOR_ELEMENT_NONE + return (hudEditorActiveElement != HUD_EDITOR_ELEMENT_NONE && + hudEditorOverrideNextElemMode == HUD_EDITOR_ELEMENT_MODE_NONE) ? CVarGetFloat(hudEditorElements[hudEditorActiveElement].scaleCvar, 1.0f) : 1.0f; } diff --git a/mm/2s2h/BenGui/HudEditor.h b/mm/2s2h/BenGui/HudEditor.h index 386ba61950..ebe980efb5 100644 --- a/mm/2s2h/BenGui/HudEditor.h +++ b/mm/2s2h/BenGui/HudEditor.h @@ -46,6 +46,7 @@ typedef enum { } HudEditorElementID; typedef enum { + HUD_EDITOR_ELEMENT_MODE_NONE = -1, HUD_EDITOR_ELEMENT_MODE_VANILLA, HUD_EDITOR_ELEMENT_MODE_HIDDEN, HUD_EDITOR_ELEMENT_MODE_MOVABLE_43, @@ -53,6 +54,7 @@ typedef enum { HUD_EDITOR_ELEMENT_MODE_MOVABLE_RIGHT, } HudEditorElementMode; +void HudEditor_OverrideNextElementMode(HudEditorElementMode mode); void HudEditor_SetActiveElement(HudEditorElementID id); bool HudEditor_ShouldOverrideDraw(); bool HudEditor_IsActiveElementHidden(); From fac93547c9a522fcfa8213ec4824f44a991eed68 Mon Sep 17 00:00:00 2001 From: Archez Date: Tue, 21 Jan 2025 19:25:19 -0500 Subject: [PATCH 2/6] Add gfx setup to 3ds clock to ensure it always renders properly --- mm/2s2h/Enhancements/Graphics/3DSClock.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mm/2s2h/Enhancements/Graphics/3DSClock.cpp b/mm/2s2h/Enhancements/Graphics/3DSClock.cpp index 0e06d30785..6fcbe647be 100644 --- a/mm/2s2h/Enhancements/Graphics/3DSClock.cpp +++ b/mm/2s2h/Enhancements/Graphics/3DSClock.cpp @@ -73,10 +73,16 @@ void Draw3DSClock() { sThreeDayClockAlpha = gPlayState->interfaceCtx.bAlpha; - OPEN_DISPS(gPlayState->state.gfxCtx); s16 posX = 160; s16 posY = 210; + OPEN_DISPS(gPlayState->state.gfxCtx); + + Gfx_SetupDL42_Overlay(gPlayState->state.gfxCtx); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + // Draw background of 3DS clock // TODO: Replace this with matrix/vertex handling to avoid gaps when scaled OVERLAY_DISP = Gfx_DrawTexRectIA8_DropShadow( From f45375a32f006dd9eefb8cb7fd862ebf5e25d3a7 Mon Sep 17 00:00:00 2001 From: Archez Date: Tue, 21 Jan 2025 19:25:43 -0500 Subject: [PATCH 3/6] Add check for hud override on text-based clock --- mm/2s2h/Enhancements/Graphics/TextBasedClock.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/2s2h/Enhancements/Graphics/TextBasedClock.cpp b/mm/2s2h/Enhancements/Graphics/TextBasedClock.cpp index fba535c019..42cb255677 100644 --- a/mm/2s2h/Enhancements/Graphics/TextBasedClock.cpp +++ b/mm/2s2h/Enhancements/Graphics/TextBasedClock.cpp @@ -55,7 +55,9 @@ void DrawTextBasedClock() { s16 posX = 13 * 8; s16 posY = 26 * 8; - HudEditor_ModifyRectPosValues(&posX, &posY); + if (HudEditor_ShouldOverrideDraw()) { + HudEditor_ModifyRectPosValues(&posX, &posY); + } // scale back down and clamp to avoid negative values posX = std::max(posX / 8, 0); From 7861ce0a50200a5312a065df421f9c32d06dbabe Mon Sep 17 00:00:00 2001 From: Archez Date: Tue, 21 Jan 2025 19:37:57 -0500 Subject: [PATCH 4/6] Add hooks for before/after drawing clock --- mm/2s2h/GameInteractor/GameInteractor.cpp | 8 ++++++++ mm/2s2h/GameInteractor/GameInteractor.h | 4 ++++ mm/src/code/z_parameter.c | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/mm/2s2h/GameInteractor/GameInteractor.cpp b/mm/2s2h/GameInteractor/GameInteractor.cpp index f9b66a3b80..71d0002988 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/GameInteractor/GameInteractor.cpp @@ -58,6 +58,14 @@ void GameInteractor_ExecuteBeforeMoonCrashSaveReset() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteAfterInterfaceClockDraw() { + GameInteractor::Instance->ExecuteHooks(); +} + +void GameInteractor_ExecuteBeforeInterfaceClockDraw() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnSceneInit(s16 sceneId, s8 spawnNum) { SPDLOG_DEBUG("OnSceneInit: sceneId: {}, spawnNum: {}", sceneId, spawnNum); GameInteractor::Instance->ExecuteHooks(sceneId, spawnNum); diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index 4dcc07bb89..34fe8738df 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.h +++ b/mm/2s2h/GameInteractor/GameInteractor.h @@ -318,6 +318,8 @@ class GameInteractor { DEFINE_HOOK(BeforeEndOfCycleSave, ()); DEFINE_HOOK(AfterEndOfCycleSave, ()); DEFINE_HOOK(BeforeMoonCrashSaveReset, ()); + DEFINE_HOOK(AfterInterfaceClockDraw, ()); + DEFINE_HOOK(BeforeInterfaceClockDraw, ()); DEFINE_HOOK(OnSceneInit, (s8 sceneId, s8 spawnNum)); DEFINE_HOOK(OnRoomInit, (s8 sceneId, s8 roomNum)); @@ -369,6 +371,8 @@ void GameInteractor_ExecuteOnSaveInit(s16 fileNum); void GameInteractor_ExecuteBeforeEndOfCycleSave(); void GameInteractor_ExecuteAfterEndOfCycleSave(); void GameInteractor_ExecuteBeforeMoonCrashSaveReset(); +void GameInteractor_ExecuteAfterInterfaceClockDraw(); +void GameInteractor_ExecuteBeforeInterfaceClockDraw(); void GameInteractor_ExecuteOnSceneInit(s16 sceneId, s8 spawnNum); void GameInteractor_ExecuteOnRoomInit(s16 sceneId, s8 roomNum); diff --git a/mm/src/code/z_parameter.c b/mm/src/code/z_parameter.c index 8160e12734..7ad3815cb6 100644 --- a/mm/src/code/z_parameter.c +++ b/mm/src/code/z_parameter.c @@ -6160,7 +6160,10 @@ void Interface_DrawClock(PlayState* play) { s16 finalHoursClockSlots[8]; s16 index; + GameInteractor_ExecuteBeforeInterfaceClockDraw(); + if (GameInteractor_Should(VB_PREVENT_CLOCK_DISPLAY, false)) { + GameInteractor_ExecuteAfterInterfaceClockDraw(); return; } @@ -6794,6 +6797,8 @@ void Interface_DrawClock(PlayState* play) { } CLOSE_DISPS(play->state.gfxCtx); + + GameInteractor_ExecuteAfterInterfaceClockDraw(); } void Interface_SetPerfectLetters(PlayState* play, s16 perfectLettersType) { From 441b9bac71ac4500c4f445c333b61adb12e5aba4 Mon Sep 17 00:00:00 2001 From: Archez Date: Tue, 21 Jan 2025 19:38:19 -0500 Subject: [PATCH 5/6] Improve better song of double time experience --- mm/2s2h/BenGui/BenMenu.cpp | 3 +- .../Songs/BetterSongOfDoubleTime.cpp | 359 ++++++++++++++---- 2 files changed, 277 insertions(+), 85 deletions(-) diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index 47538c38cc..4bf7de5ed7 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -986,7 +986,8 @@ void BenMenu::AddEnhancements() { .CVar("gEnhancements.Songs.BetterSongOfDoubleTime") .Options(CheckboxOptions().Tooltip( "When playing the Song of Double Time, you can now choose the exact time you want to go " - "to, similar to the 3DS version.")); + "to, similar to the 3DS version.\n\n" + "Holding Z allows decreasing the time adjustment factor, while holding R will increase the factor")); AddWidget(path, "Enable Sun's Song", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Songs.EnableSunsSong") .Options(CheckboxOptions().Tooltip( diff --git a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp index 50b915d360..33784bfb5b 100644 --- a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp +++ b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp @@ -1,24 +1,39 @@ #include -#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/BenGui/HudEditor.h" #include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" +#include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" extern "C" { #include "variables.h" #include "functions.h" #include "assets/interface/week_static/week_static.h" -#include "objects/gameplay_keep/gameplay_keep.h" - -Gfx* Gfx_DrawTexRect4b(Gfx* gfx, TexturePtr texture, s32 fmt, s16 textureWidth, s16 textureHeight, s16 rectLeft, - s16 rectTop, s16 rectWidth, s16 rectHeight, s32 cms, s32 masks, s32 rects, u16 dsdx, u16 dtdy); +#include "assets/archives/icon_item_static/icon_item_static_yar.h" +#include "assets/objects/gameplay_keep/gameplay_keep.h" +#include "assets/interface/message_static/message_static.h" +#include "assets/interface/nes_font_static/nes_font_static.h" } #define CVAR_NAME "gEnhancements.Songs.BetterSongOfDoubleTime" #define CVAR CVarGetInteger(CVAR_NAME, 0) -static bool activelyChangingTime = false; -static u16 originalTime = CLOCK_TIME(0, 0); -static s32 originalDay = 0; +// Normalize's time so that 6am is considered 0, and anything before rolls over u16 +#define CLOCK_TIME_NORMALIZED(time) ((u16)(time - CLOCK_TIME(6, 0))) +#define CLOCK_TIME_MINUTE_F CLOCK_TIME_F(0, 1) + +#define COL_CHAN_MIX(c1, c2, m) (c1 - (s32)(c2 * m)) & 0xFF + +typedef enum AdjustDirection { + ADJUST_DIRECTION_NONE, + ADJUST_DIRECTION_REVERSE, + ADJUST_DIRECTION_FORWARD, +} AdjustDirection; + +static bool sActivelyChangingTime = false; +static u16 sOriginalTime = CLOCK_TIME(0, 0); +static s32 sOriginalDay = 0; +static u16 sSelectedTime = CLOCK_TIME(0, 0); +static s32 sSelectedDay = 0; extern void UpdateGameTime(u16 gameTime); @@ -29,14 +44,62 @@ static const char* sDoWeekTableCopy[] = { }; static HOOK_ID onPlayerUpdateHookId = 0; +static HOOK_ID onEnTest6KillHookId = 0; +static HOOK_ID onPlayDestroyHookId = 0; + +Color_RGBA8 sArrowAnimColor = {}; +static f32 sArrowAnimTween = 0.0f; +static f32 sStickAnimTween = 0.0f; +static s16 sArrowAnimState = 0; +static s16 sStickAnimState = 0; + +// Tracks animation state for the arrows and control sticks +void UpdateStickDirectionPromptAnim() { + f32 arrowAnimTween = sArrowAnimTween; + f32 stickAnimTween = sStickAnimTween; + + if (sArrowAnimState == 0) { + arrowAnimTween += 0.05f; + if (arrowAnimTween > 1.0f) { + arrowAnimTween = 1.0f; + sArrowAnimState = 1; + } + } else { + arrowAnimTween -= 0.05f; + if (arrowAnimTween < 0.0f) { + arrowAnimTween = 0.0f; + sArrowAnimState = 0; + } + } + sArrowAnimTween = arrowAnimTween; + + if (sStickAnimState == 0) { + stickAnimTween += 0.1f; + if (stickAnimTween > 1.0f) { + stickAnimTween = 1.0f; + sStickAnimState = 1; + } + } else { + stickAnimTween = 0.0f; + sStickAnimState = 0; + } + sStickAnimTween = stickAnimTween; + + sArrowAnimColor.r = COL_CHAN_MIX(255, 155.0f, arrowAnimTween); + sArrowAnimColor.g = COL_CHAN_MIX(255, 155.0f, arrowAnimTween); + sArrowAnimColor.b = COL_CHAN_MIX(0, -100.0f, arrowAnimTween); + sArrowAnimColor.a = COL_CHAN_MIX(200, 50.0f, arrowAnimTween); +} void OnPlayerUpdate(Actor* actor) { - if (!activelyChangingTime) { + if (!sActivelyChangingTime) { GameInteractor::Instance->UnregisterGameHookForID(onPlayerUpdateHookId); onPlayerUpdateHookId = 0; return; } + UpdateStickDirectionPromptAnim(); + gPlayState->interfaceCtx.bAlpha = 255; Input* input = &gPlayState->state.input[0]; @@ -45,122 +108,250 @@ void OnPlayerUpdate(Actor* actor) { if (CHECK_BTN_ALL(input->press.button, BTN_B)) { Audio_PlaySfx_MessageCancel(); gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_END; - activelyChangingTime = false; - - gSaveContext.save.day = originalDay; - UpdateGameTime(originalTime); - Interface_NewDay(gPlayState, CURRENT_DAY); - // This may have happened in the UpdateGameTime function, if there was a day/night difference, but - // we need to ensure it happens regardless because the day may have changed even if the time is the same - gPlayState->numSetupActors = ABS(gPlayState->numSetupActors); - // Load environment values for new day - func_800FEAF4(&gPlayState->envCtx); + sActivelyChangingTime = false; } // Pressing A should confirm the song if (CHECK_BTN_ALL(input->press.button, BTN_A)) { Audio_PlaySfx_MessageDecide(); - gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_END; - activelyChangingTime = false; + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT; + sActivelyChangingTime = false; + + // Use a hook for when the song of double time cutscene is finished to reload the scene via a respawn + // This is to ensure that no actors or scripts are processed with the new time on the fly, + // and everything is reloaded in a fresh state + onEnTest6KillHookId = GameInteractor::Instance->RegisterGameHookForID( + ACTOR_EN_TEST6, [](Actor* actor) { + Player* player = GET_PLAYER(gPlayState); + + gPlayState->nextEntrance = gSaveContext.save.entrance; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_INSTANT; + + gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance = gSaveContext.save.entrance; + gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = gPlayState->roomCtx.curRoom.num; + gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = player->actor.world.pos; + gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = player->actor.shape.rot.y; + gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = PLAYER_PARAMS(0xFF, PLAYER_INITMODE_D); + gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST; + gSaveContext.respawnFlag = -8; + + GameInteractor::Instance->UnregisterGameHookForID(onEnTest6KillHookId); + onEnTest6KillHookId = 0; + }); + + // Use a hook to apply the new day and time before respawning + onPlayDestroyHookId = GameInteractor::Instance->RegisterGameHook([]() { + gSaveContext.save.day = sSelectedDay; + gSaveContext.save.time = sSelectedTime; + + GameInteractor::Instance->UnregisterGameHook(onPlayDestroyHookId); + onPlayDestroyHookId = 0; + }); + } + + AdjustDirection adjustMode = ADJUST_DIRECTION_NONE; + f32 interval = (CLOCK_TIME_MINUTE_F * 30); + + // Check for DPad movement first, inheriting full speed + if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) { + adjustMode = ADJUST_DIRECTION_REVERSE; + } else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) { + adjustMode = ADJUST_DIRECTION_FORWARD; + } - gSaveContext.save.eventDayCount = CURRENT_DAY; + // Then analog stick direction, clamped to 30 minutes + if (input->rel.stick_x < -5) { + adjustMode = ADJUST_DIRECTION_REVERSE; + interval = CLOCK_TIME_MINUTE_F * CLAMP_MIN(input->rel.stick_x / -2, -30); + } else if (input->rel.stick_x > 5) { + adjustMode = ADJUST_DIRECTION_FORWARD; + interval = CLOCK_TIME_MINUTE_F * CLAMP_MAX(input->rel.stick_x / 2, 30); } - u16 INTERVAL = (CLOCK_TIME_MINUTE * 30); - if (CHECK_BTN_ALL(input->cur.button, BTN_Z)) { - INTERVAL = (CLOCK_TIME_MINUTE * 5); + if (CHECK_BTN_ALL(input->cur.button, BTN_Z)) { // Holding Z slows the interval + interval /= 6; + } else if (CHECK_BTN_ALL(input->cur.button, BTN_R)) { // Holding R speeds up the interval + interval *= 2; } - // Analog stick should change the time - if (input->cur.stick_x > 0) { // Advance time - u16 newTime = CLAMP(gSaveContext.save.time + INTERVAL, -INT_MAX, - (gSaveContext.save.day == 3 && gSaveContext.save.time < CLOCK_TIME(6, 0)) - ? (CLOCK_TIME(6, 0) - CLOCK_TIME_HOUR) - : INT_MAX); - if (newTime > CLOCK_TIME(6, 0) && gSaveContext.save.time < CLOCK_TIME(6, 0)) { - gSaveContext.save.day = CLAMP(gSaveContext.save.day + 1, originalDay, 3); - Interface_NewDay(gPlayState, CURRENT_DAY); - func_800FEAF4(&gPlayState->envCtx); + if (adjustMode == ADJUST_DIRECTION_FORWARD) { // Advance time + u16 newTime = sSelectedTime + interval; + if (sSelectedDay == 3 && sSelectedTime < CLOCK_TIME(6, 0) && newTime > CLOCK_TIME(6, 0) - CLOCK_TIME_HOUR) { + newTime = CLOCK_TIME(6, 0) - CLOCK_TIME_HOUR; + + // If BSoDT was played while already in the final hour, don't allow the clamp to place the new time behind + // the original time + if (sOriginalDay == 3 && CLOCK_TIME_NORMALIZED(newTime) < CLOCK_TIME_NORMALIZED(sOriginalTime)) { + newTime = sOriginalTime; + } + } + // Day incrementing + if (newTime > CLOCK_TIME(6, 0) && sSelectedTime < CLOCK_TIME(6, 0)) { + sSelectedDay = CLAMP(sSelectedDay + 1, sOriginalDay, 3); } - UpdateGameTime(newTime); - } else if (input->cur.stick_x < 0) { // Reverse time - u16 newTime = CLAMP(gSaveContext.save.time - INTERVAL, - (gSaveContext.save.day == originalDay && - ((gSaveContext.save.time > CLOCK_TIME(6, 0) && originalTime > CLOCK_TIME(6, 0)) || - (gSaveContext.save.time < CLOCK_TIME(6, 0) && originalTime < CLOCK_TIME(6, 0)))) - ? originalTime - : -INT_MAX, - INT_MAX); - if (newTime < CLOCK_TIME(6, 0) && gSaveContext.save.time > CLOCK_TIME(6, 0)) { - gSaveContext.save.day = CLAMP(gSaveContext.save.day - 1, originalDay, 3); - Interface_NewDay(gPlayState, CURRENT_DAY); - func_800FEAF4(&gPlayState->envCtx); + sSelectedTime = newTime; + } else if (adjustMode == ADJUST_DIRECTION_REVERSE) { // Reverse time + u16 newTime = sSelectedTime - interval; + if (sSelectedDay == sOriginalDay && CLOCK_TIME_NORMALIZED(newTime) < CLOCK_TIME_NORMALIZED(sOriginalTime)) { + newTime = sOriginalTime; } - UpdateGameTime(newTime); + // Day decrementing + if (newTime < CLOCK_TIME(6, 0) && sSelectedTime > CLOCK_TIME(6, 0)) { + sSelectedDay = CLAMP(sSelectedDay - 1, sOriginalDay, 3); + } + sSelectedTime = newTime; + } +} + +void UpdateDayTexture(PlayState* play, s16 day) { + s16 i = day - 1; + + // i is used to store dayMinusOne + if ((i < 0) || (i >= 3)) { + i = 0; } + + play->interfaceCtx.doActionSegment[DO_ACTION_SEG_CLOCK].mainTex = (char*)sDoWeekTableCopy[i]; } -void DrawTextRec(f32 x, f32 y, f32 z, s32 s, s32 t, f32 dx, f32 dy) { +Gfx* DrawTexRectI4(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, s16 rectLeft, s16 rectTop, + s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy) { + gDPLoadTextureBlock_4b(gfx++, texture, G_IM_FMT_I, textureWidth, textureHeight, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gSPWideTextureRectangle(gfx++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, + (rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + +Gfx* DrawTexRectIA8(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, s16 rectLeft, s16 rectTop, + s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy) { + gDPLoadTextureBlock(gfx++, texture, G_IM_FMT_IA, G_IM_SIZ_8b, textureWidth, textureHeight, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gSPWideTextureRectangle(gfx++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, + (rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + +void DrawIndicators() { OPEN_DISPS(gPlayState->state.gfxCtx); - gDPPipeSync(OVERLAY_DISP++); - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255); + Gfx_SetupDL39_Overlay(gPlayState->state.gfxCtx); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); - f32 w = 8.0f * z; - s32 ulx = (x - w) * 4.0f; - s32 lrx = (x + w) * 4.0f; + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sArrowAnimColor.r, sArrowAnimColor.g, sArrowAnimColor.b, sArrowAnimColor.a); - f32 h = 12.0f * z; - s32 uly = (y - h) * 4.0f; - s32 lry = (y + h) * 4.0f; + // Render the left/right arrows + OVERLAY_DISP = + DrawTexRectIA8(OVERLAY_DISP, (TexturePtr)gArrowCursorTex, 16, 24, 26, 191, 16, 24, -1 << 10, 1 << 10); + OVERLAY_DISP = + DrawTexRectIA8(OVERLAY_DISP, (TexturePtr)gArrowCursorTex, 16, 24, 278, 191, 16, 24, 1 << 10, 1 << 10); - f32 unk = 1024 * (1.0f / z); - s32 dsdx = unk * dx; - s32 dtdy = dy * unk; + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 200, 200, 200, 180); - gSPTextureRectangle(OVERLAY_DISP++, ulx, uly, lrx, lry, G_TX_RENDERTILE, s, t, dsdx, dtdy); + // Render the control stick icon with movement applied + OVERLAY_DISP = DrawTexRectIA8(OVERLAY_DISP, (TexturePtr)gControlStickTex, 16, 16, 42 - (8.0f * sStickAnimTween), + 195, 16, 16, -1 << 10, 1 << 10); + OVERLAY_DISP = DrawTexRectIA8(OVERLAY_DISP, (TexturePtr)gControlStickTex, 16, 16, 262 + (8.0f * sStickAnimTween), + 195, 16, 16, 1 << 10, 1 << 10); + + // Render two small black squares behind the Z/R buttons to fill in the cutout with black + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, 255); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_PRIMITIVE, G_CC_PRIMITIVE); + gDPFillRectangle(OVERLAY_DISP++, 58 + 2, 196 + 2, 58 + 2 + 9, 196 + 2 + 10); + gDPFillRectangle(OVERLAY_DISP++, 248 + 2, 196 + 2, 248 + 2 + 9, 196 + 2 + 10); + + Gfx_SetupDL39_Overlay(gPlayState->state.gfxCtx); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_CLD_SURF, G_RM_CLD_SURF2); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_NONE); + gDPSetCombineLERP(OVERLAY_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0, + PRIMITIVE, 0); + + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255); + + // Render a Z and R button on the left/right side + OVERLAY_DISP = + DrawTexRectI4(OVERLAY_DISP, (TexturePtr)gMsgCharB5ButtonZTex, 16, 16, 58, 196, 16, 16, 1 << 10, 1 << 10); + OVERLAY_DISP = + DrawTexRectI4(OVERLAY_DISP, (TexturePtr)gMsgCharB4ButtonRTex, 16, 16, 248, 196, 16, 16, 1 << 10, 1 << 10); CLOSE_DISPS(gPlayState->state.gfxCtx); } void RegisterBetterSongOfDoubleTime() { COND_HOOK(OnSceneInit, CVAR, [](s8 sceneId, s8 spawnNum) { - // In case we didn't properly reset this variable - activelyChangingTime = false; - originalTime = CLOCK_TIME(0, 0); - originalDay = 0; + // In case we didn't properly reset our variables + sActivelyChangingTime = false; + sOriginalTime = CLOCK_TIME(0, 0); + sOriginalDay = 0; + sSelectedTime = CLOCK_TIME(0, 0); + sSelectedDay = 0; + + GameInteractor::Instance->UnregisterGameHookForID(onEnTest6KillHookId); + GameInteractor::Instance->UnregisterGameHook(onPlayDestroyHookId); + + onEnTest6KillHookId = 0; + onPlayDestroyHookId = 0; + }); + + // Hijack the time and day values on the save before drawing the clock so that it renders our selected time + COND_HOOK(BeforeInterfaceClockDraw, CVAR, []() { + if (sActivelyChangingTime) { + gSaveContext.save.time = sSelectedTime; + gSaveContext.save.day = sSelectedDay; + UpdateDayTexture(gPlayState, CURRENT_DAY); + + HudEditor_OverrideNextElementMode(HUD_EDITOR_ELEMENT_MODE_VANILLA); + } + }); + + // Return everything back to normal after drawing the clock + COND_HOOK(AfterInterfaceClockDraw, CVAR, []() { + if (sActivelyChangingTime) { + gSaveContext.save.time = sOriginalTime; + gSaveContext.save.day = sOriginalDay; + UpdateDayTexture(gPlayState, CURRENT_DAY); + + HudEditor_OverrideNextElementMode(HUD_EDITOR_ELEMENT_MODE_NONE); + } }); COND_VB_SHOULD(VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, CVAR, { *should = false; + + if (gSaveContext.save.day >= 4) { + // On 4th day and beyond, just display the "can't go further" text + Message_StartTextbox(gPlayState, 0x1B94, NULL); + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_END; + return; + } else if (gSaveContext.save.day <= 0) { + // On 0th day, display the "notes echoed" text + Message_StartTextbox(gPlayState, 0x1B95, NULL); + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG; + return; + } + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; - activelyChangingTime = true; - originalTime = gSaveContext.save.time; - originalDay = gSaveContext.save.day; + sActivelyChangingTime = true; + sOriginalTime = gSaveContext.save.time; + sOriginalDay = gSaveContext.save.day; + sSelectedTime = gSaveContext.save.time; + sSelectedDay = gSaveContext.save.day; onPlayerUpdateHookId = GameInteractor::Instance->RegisterGameHookForID( ACTOR_PLAYER, OnPlayerUpdate); }); COND_VB_SHOULD(VB_PREVENT_CLOCK_DISPLAY, CVAR, { - if (!activelyChangingTime) { + if (!sActivelyChangingTime) { return; } - OPEN_DISPS(gPlayState->state.gfxCtx); - Gfx_SetupDL39_Overlay(gPlayState->state.gfxCtx); - gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); - gDPLoadTextureBlock(OVERLAY_DISP++, gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0, - G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD, - G_TX_NOLOD); - DrawTextRec(53.0f, 191.0f, 1.0f, 0, 0, -1.0f, 1.0f); - DrawTextRec(270.0f, 191.0f, 1.0f, 0, 0, 1.0f, 1.0f); - gDPLoadTextureBlock(OVERLAY_DISP++, gControlStickTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, 0, - G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD, - G_TX_NOLOD); - DrawTextRec(69.0f, 195.0f, 1.0f, 0, 0, -1.0f, 1.0f); - DrawTextRec(254.0f, 195.0f, 1.0f, 0, 0, 1.0f, 1.0f); - CLOSE_DISPS(gPlayState->state.gfxCtx); + DrawIndicators(); }); COND_VB_SHOULD(VB_ALLOW_SONG_DOUBLE_TIME_ON_FINAL_NIGHT, CVAR, { *should = true; }); From f5449787f0dc4153026a7a09a7b779d915e9cb2c Mon Sep 17 00:00:00 2001 From: Archez Date: Wed, 22 Jan 2025 23:35:05 -0500 Subject: [PATCH 6/6] DPad input repeated on hold and show A on HUD --- .../Songs/BetterSongOfDoubleTime.cpp | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp index 33784bfb5b..0927705ac3 100644 --- a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp +++ b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp @@ -109,6 +109,7 @@ void OnPlayerUpdate(Actor* actor) { Audio_PlaySfx_MessageCancel(); gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_END; sActivelyChangingTime = false; + return; } // Pressing A should confirm the song @@ -148,16 +149,47 @@ void OnPlayerUpdate(Actor* actor) { GameInteractor::Instance->UnregisterGameHook(onPlayDestroyHookId); onPlayDestroyHookId = 0; }); + + return; } AdjustDirection adjustMode = ADJUST_DIRECTION_NONE; f32 interval = (CLOCK_TIME_MINUTE_F * 30); + static s8 sDPadRepeatState = 0; + static s8 sDPadRepeatTimer = 0; + // Check for DPad movement first, inheriting full speed - if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) { - adjustMode = ADJUST_DIRECTION_REVERSE; - } else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) { - adjustMode = ADJUST_DIRECTION_FORWARD; + if (CHECK_BTN_ALL(input->cur.button, BTN_DLEFT)) { + if (sDPadRepeatState == -1) { + sDPadRepeatTimer--; + if (sDPadRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + sDPadRepeatTimer = 2; + adjustMode = ADJUST_DIRECTION_REVERSE; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + sDPadRepeatTimer = 10; + sDPadRepeatState = -1; + adjustMode = ADJUST_DIRECTION_REVERSE; + } + } else if (CHECK_BTN_ALL(input->cur.button, BTN_DRIGHT)) { + if (sDPadRepeatState == 1) { + sDPadRepeatTimer--; + if (sDPadRepeatTimer < 0) { + // Allow the input to register and apply the delay for all subsequent repeated inputs + sDPadRepeatTimer = 2; + adjustMode = ADJUST_DIRECTION_FORWARD; + } + } else { + // Allow the input to register and apply the delay for the first repeated input + sDPadRepeatTimer = 10; + sDPadRepeatState = 1; + adjustMode = ADJUST_DIRECTION_FORWARD; + } + } else { + sDPadRepeatState = 0; } // Then analog stick direction, clamped to 30 minutes @@ -335,6 +367,9 @@ void RegisterBetterSongOfDoubleTime() { return; } + Interface_SetAButtonDoAction(gPlayState, DO_ACTION_DECIDE); + Interface_SetHudVisibility(HUD_VISIBILITY_A_B); + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; sActivelyChangingTime = true; sOriginalTime = gSaveContext.save.time;