diff --git a/mm/2s2h/BenGui/SearchableMenuItems.h b/mm/2s2h/BenGui/SearchableMenuItems.h index f5b2ff2ba..759bfec73 100644 --- a/mm/2s2h/BenGui/SearchableMenuItems.h +++ b/mm/2s2h/BenGui/SearchableMenuItems.h @@ -1302,6 +1302,10 @@ void AddEnhancements() { "Eliminates the Cooldown between Blast Mask usage.", WIDGET_CVAR_CHECKBOX } }, // Song Enhancements { { .widgetName = "Ocarina", .widgetType = WIDGET_SEPARATOR_TEXT }, + { "Better Song of Double Time", "gEnhancements.Songs.BetterSongOfDoubleTime", + "When playing the Song of Double Time, you can now choose the exact time you want to go to, similar to " + "the 3DS version.", + WIDGET_CVAR_CHECKBOX }, { "Enable Sun's Song", "gEnhancements.Songs.EnableSunsSong", "Enables the partially implemented Sun's Song. RIGHT-DOWN-UP-RIGHT-DOWN-UP to play it. " "This song will make time move very fast until either Link moves to a different scene, " diff --git a/mm/2s2h/Enhancements/Enhancements.cpp b/mm/2s2h/Enhancements/Enhancements.cpp index 10390c43a..142eba7de 100644 --- a/mm/2s2h/Enhancements/Enhancements.cpp +++ b/mm/2s2h/Enhancements/Enhancements.cpp @@ -63,6 +63,7 @@ void InitEnhancements() { RegisterFierceDeityPutaway(); // Songs + RegisterBetterSongOfDoubleTime(); RegisterEnableSunsSong(); RegisterFasterSongPlayback(); RegisterPauseOwlWarp(); diff --git a/mm/2s2h/Enhancements/Graphics/3DSClock.cpp b/mm/2s2h/Enhancements/Graphics/3DSClock.cpp index ba5abb48e..86a79a626 100644 --- a/mm/2s2h/Enhancements/Graphics/3DSClock.cpp +++ b/mm/2s2h/Enhancements/Graphics/3DSClock.cpp @@ -77,7 +77,7 @@ void Register3DSClock() { static s32 sFinalHoursIntro = 0; - sThreeDayClockAlpha = gPlayState->interfaceCtx.aAlpha; + sThreeDayClockAlpha = gPlayState->interfaceCtx.bAlpha; OPEN_DISPS(gPlayState->state.gfxCtx); s16 posX = 160; diff --git a/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp new file mode 100644 index 000000000..b1a6c5a75 --- /dev/null +++ b/mm/2s2h/Enhancements/Songs/BetterSongOfDoubleTime.cpp @@ -0,0 +1,165 @@ +#include +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "assets/interface/week_static/week_static.h" +#include "objects/gameplay_keep/gameplay_keep.h" +extern PlayState* gPlayState; +extern SaveContext gSaveContext; + +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); +} + +static bool activelyChangingTime = false; +static u16 originalTime = CLOCK_TIME(0, 0); +static s32 originalDay = 0; + +extern void UpdateGameTime(u16 gameTime); + +static const char* sDoWeekTableCopy[] = { + gClockDay1stTex, + gClockDay2ndTex, + gClockDayFinalTex, +}; + +static HOOK_ID onPlayerUpdateHookId = 0; + +void OnPlayerUpdate(Actor* actor) { + if (!activelyChangingTime) { + GameInteractor::Instance->UnregisterGameHookForID(onPlayerUpdateHookId); + onPlayerUpdateHookId = 0; + return; + } + + gPlayState->interfaceCtx.bAlpha = 255; + + Input* input = &gPlayState->state.input[0]; + + // Pressing B should cancel the song + 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); + } + + // 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; + + gSaveContext.save.eventDayCount = CURRENT_DAY; + } + + u16 INTERVAL = (CLOCK_TIME_MINUTE * 30); + if (CHECK_BTN_ALL(input->cur.button, BTN_Z)) { + INTERVAL = (CLOCK_TIME_MINUTE * 5); + } + + // 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); + } + 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); + } + UpdateGameTime(newTime); + } +} + +void DrawTextRec(f32 x, f32 y, f32 z, s32 s, s32 t, f32 dx, f32 dy) { + OPEN_DISPS(gPlayState->state.gfxCtx); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255); + + f32 w = 8.0f * z; + s32 ulx = (x - w) * 4.0f; + s32 lrx = (x + w) * 4.0f; + + f32 h = 12.0f * z; + s32 uly = (y - h) * 4.0f; + s32 lry = (y + h) * 4.0f; + + f32 unk = 1024 * (1.0f / z); + s32 dsdx = unk * dx; + s32 dtdy = dy * unk; + + gSPTextureRectangle(OVERLAY_DISP++, ulx, uly, lrx, lry, G_TX_RENDERTILE, s, t, dsdx, dtdy); + + CLOSE_DISPS(gPlayState->state.gfxCtx); +} + +void RegisterBetterSongOfDoubleTime() { + GameInteractor::Instance->RegisterGameHook([](s8 sceneId, s8 spawnNum) { + // In case we didn't properly reset this variable + activelyChangingTime = false; + originalTime = CLOCK_TIME(0, 0); + originalDay = 0; + }); + + REGISTER_VB_SHOULD(VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, { + if (CVarGetInteger("gEnhancements.Songs.BetterSongOfDoubleTime", 0)) { + *should = false; + gPlayState->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; + activelyChangingTime = true; + originalTime = gSaveContext.save.time; + originalDay = gSaveContext.save.day; + + onPlayerUpdateHookId = GameInteractor::Instance->RegisterGameHookForID( + ACTOR_PLAYER, OnPlayerUpdate); + } + }); + + REGISTER_VB_SHOULD(VB_PREVENT_CLOCK_DISPLAY, { + if (!activelyChangingTime) { + 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); + }); +} diff --git a/mm/2s2h/Enhancements/Songs/Songs.h b/mm/2s2h/Enhancements/Songs/Songs.h index d4df3cc88..24e49496c 100644 --- a/mm/2s2h/Enhancements/Songs/Songs.h +++ b/mm/2s2h/Enhancements/Songs/Songs.h @@ -1,6 +1,7 @@ #ifndef SONGS_H #define SONGS_H +void RegisterBetterSongOfDoubleTime(); void RegisterEnableSunsSong(); void RegisterFasterSongPlayback(); void RegisterZoraEggCount(); diff --git a/mm/2s2h/GameInteractor/GameInteractor.h b/mm/2s2h/GameInteractor/GameInteractor.h index bd7af2b19..506fc004f 100644 --- a/mm/2s2h/GameInteractor/GameInteractor.h +++ b/mm/2s2h/GameInteractor/GameInteractor.h @@ -80,6 +80,7 @@ typedef enum { VB_PLAY_HEART_CONTAINER_GET_FANFARE, VB_BE_HOOKSHOT_SURFACE, VB_DEKU_GUARD_SHOW_SEARCH_BALLS, + VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, } GIVanillaBehavior; typedef enum { diff --git a/mm/src/code/z_message.c b/mm/src/code/z_message.c index 68d1fa5e7..0081bb5e7 100644 --- a/mm/src/code/z_message.c +++ b/mm/src/code/z_message.c @@ -6025,12 +6025,14 @@ void Message_Update(PlayState* play) { } else if (sLastPlayedSong == OCARINA_SONG_DOUBLE_TIME) { if (interfaceCtx->restrictions.songOfDoubleTime == 0) { 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); + if (GameInteractor_Should(VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT, true)) { + 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; } - play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME; } else { Message_StartTextbox(play, 0x1B94, NULL); play->msgCtx.ocarinaMode = OCARINA_MODE_END;