diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 61a931f..b0142a4 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(newcode game/static_context.cpp game/static_context.h game/sound.cpp + rst/fixes/ocarina.cpp rst/fixes/time.cpp rst/fixes.cpp rst/fixes.h diff --git a/source/common/utils.h b/source/common/utils.h index e0f21ce..1ec6147 100644 --- a/source/common/utils.h +++ b/source/common/utils.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -54,4 +55,14 @@ void Write(T* ptr, size_t offset, const ValueType& value) { std::memcpy(reinterpret_cast(ptr) + offset, &value, sizeof(value)); } +template +bool Contains(InputIt first, InputIt last, const T& value) { + return std::find(first, last, value) != last; +} + +template +bool Contains(const C& c, const T& value) { + return Contains(std::begin(c), std::end(c), value); +} + } // namespace rst::util diff --git a/source/game/context.cpp b/source/game/context.cpp index 688b268..08c433d 100644 --- a/source/game/context.cpp +++ b/source/game/context.cpp @@ -28,6 +28,10 @@ act::Actor* GlobalContext::SpawnActor(act::Actor* actor, act::Id id, u16 rx, u16 return spawn_actor_wrapper(&actors, actor, this, id, rx, ry, rz, param, pos_x, pos_y, pos_z); } +void GlobalContext::ShowMessage(u16 msgid, int unknown) { + rst::util::GetPointer(0x21BAFC)(this, msgid, unknown); +} + namespace { class Screen; diff --git a/source/game/context.h b/source/game/context.h index d7caade..1d71d22 100644 --- a/source/game/context.h +++ b/source/game/context.h @@ -123,6 +123,8 @@ struct GlobalContext { act::Actor* SpawnActor(act::Actor* actor, act::Id id, u16 rx, u16 ry, u16 rz, u16 param, float pos_x, float pos_y, float pos_z); + void ShowMessage(u16 msgid, int unknown = 0); + int field_0; u8 gap_4[36]; pad::State pad_state; diff --git a/source/rst/fixes/ocarina.cpp b/source/rst/fixes/ocarina.cpp new file mode 100644 index 0000000..f5b232f --- /dev/null +++ b/source/rst/fixes/ocarina.cpp @@ -0,0 +1,67 @@ +#include "common/context.h" +#include "common/debug.h" +#include "common/types.h" +#include "game/context.h" +#include "game/sound.h" + +namespace game::ui { +class MessageWindow; +}; + +namespace rst { + +static void EndOcarinaSession(game::ui::MessageWindow* window) { + const auto set_ocarina_fadeout = util::GetPointer(0x4FE0BC); + set_ocarina_fadeout(0, 30); + + const auto set_ocarina_mode = + util::GetPointer(0x1D1A18); + set_ocarina_mode(window, 1); + + auto* gctx = GetContext().gctx; + // Disable BGM fadeout + util::Write(gctx, 0x8422, 1); + gctx->ocarina_state = game::OcarinaState::StoppedPlaying; +} + +static bool IsElegyOfEmptinessAllowed() { + constexpr u16 allowed_maps[] = {0x58, 0x59, 0x53, 0x1D, 0x56, 0x13, 0x16, 0x18, 0x60, 0x4B, 0x51}; + return util::Contains(allowed_maps, GetContext().gctx->map_maybe); +} + +// Returns true to make the caller return, false to continue. +bool HandleOcarinaSong(game::ui::MessageWindow* self, game::OcarinaSong song) { + if (song == game::OcarinaSong::ElegyOfEmptiness) { + static bool s_played_once = false; + util::Print("%s: played the Elegy of Emptiness (once=%u)", __func__, s_played_once); + if (!s_played_once) { + s_played_once = true; + return false; + } + + EndOcarinaSession(self); + + auto* gctx = GetContext().gctx; + if (IsElegyOfEmptinessAllowed()) { + game::sound::PlayEffect(0x1000773); + gctx->ocarina_song = game::OcarinaSong::ElegyOfEmptiness; + gctx->ocarina_state = game::OcarinaState::PlayingAndReplayDone; + } else { + gctx->ShowMessage(0x1B95, 0); // "Your notes echoed far..." + gctx->ocarina_state = game::OcarinaState::StoppedPlaying; + util::GetPointer(0x1D8C5C)(0); + } + + return true; + } + + return false; +} + +} // namespace rst + +extern "C" { +RST_HOOK bool rst_HandleOcarinaSong(game::ui::MessageWindow* self, game::OcarinaSong song) { + return rst::HandleOcarinaSong(self, song); +} +} diff --git a/source/rst/trampolines.s b/source/rst/trampolines.s index 390c579..db514b9 100644 --- a/source/rst/trampolines.s +++ b/source/rst/trampolines.s @@ -50,3 +50,22 @@ TRAMPOLINE_DECLARE rst_ui_items_GetItemAssignIndex mov r3, r0 vpop {d0-d15} pop {r0-r2, r4-r12, pc} + +TRAMPOLINE_DECLARE rst_HandleOcarinaSong + push {lr} + + push {r0-r12} + vpush {d0-d15} + mov r1, r0 // song + mov r0, r4 // MessageWindow* this + bl rst_HandleOcarinaSong + cmp r0, #0 + vpop {d0-d15} + pop {r0-r12} + + // jump out of this trampoline and MessageWindow code directly + addne sp, sp, #0x70 + popne {r4-r11, pc} + + cmp r0, #0x16 // original instruction + pop {pc} diff --git a/v100/hooks.hks b/v100/hooks.hks index dda3193..c09f53a 100644 --- a/v100/hooks.hks +++ b/v100/hooks.hks @@ -207,3 +207,9 @@ ui_items_fast_assign_item: link: true func: rst_trampoline_rst_ui_items_GetItemAssignIndex addr: 0x5C161C + +messagewindow_handle_ocarina_song_hook: + type: branch + link: true + func: rst_trampoline_rst_HandleOcarinaSong + addr: 0x604D8C