Skip to content

Commit

Permalink
Skip Elegy of Emptiness playback after it's been played once
Browse files Browse the repository at this point in the history
Makes Stone Tower a bit less tedious, considering Link needs to play
it 10+ times to climb the tower and complete the dungeon normally.

The song playback is entirely skipped after the song has been
played once during the current play session.

Fixes #12
  • Loading branch information
leoetlino committed Jul 27, 2019
1 parent 71ab8eb commit 68e6f0f
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 0 deletions.
1 change: 1 addition & 0 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions source/common/utils.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <algorithm>
#include <cstring>
#include <tuple>
#include <type_traits>
Expand Down Expand Up @@ -54,4 +55,14 @@ void Write(T* ptr, size_t offset, const ValueType& value) {
std::memcpy(reinterpret_cast<u8*>(ptr) + offset, &value, sizeof(value));
}

template <class InputIt, class T>
bool Contains(InputIt first, InputIt last, const T& value) {
return std::find(first, last, value) != last;
}

template <class C, class T>
bool Contains(const C& c, const T& value) {
return Contains(std::begin(c), std::end(c), value);
}

} // namespace rst::util
4 changes: 4 additions & 0 deletions source/game/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(GlobalContext*, int msgid, int)>(0x21BAFC)(this, msgid, unknown);
}

namespace {

class Screen;
Expand Down
2 changes: 2 additions & 0 deletions source/game/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
67 changes: 67 additions & 0 deletions source/rst/fixes/ocarina.cpp
Original file line number Diff line number Diff line change
@@ -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<void(int zero, int duration)>(0x4FE0BC);
set_ocarina_fadeout(0, 30);

const auto set_ocarina_mode =
util::GetPointer<void(game::ui::MessageWindow*, int mode)>(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<void(int)>(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);
}
}
19 changes: 19 additions & 0 deletions source/rst/trampolines.s
Original file line number Diff line number Diff line change
Expand Up @@ -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}
6 changes: 6 additions & 0 deletions v100/hooks.hks
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 68e6f0f

Please sign in to comment.