mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp.git
synced 2024-12-26 09:16:05 +00:00
Added background music volume and low health beep settings, imported warp names
This commit is contained in:
parent
0ceeeb04ea
commit
eeeabba64d
@ -63,7 +63,7 @@
|
||||
<div>Sound</div>
|
||||
<div class="tab__indicator"></div>
|
||||
</tab>
|
||||
<panel class="config" >
|
||||
<panel class="config" data-model="sound_options_model">
|
||||
<template src="config-menu__sound" />
|
||||
</panel>
|
||||
<tab class="tab">
|
||||
|
@ -4,7 +4,24 @@
|
||||
<body>
|
||||
<form class="config__form">
|
||||
<div class="config__wrapper">
|
||||
Sound options!!!!!
|
||||
<div class="config__row">
|
||||
<div class="config-option">
|
||||
<label class="config-option__title">Background Music Volume</label>
|
||||
<div class="config-option__range-wrapper config-option__list">
|
||||
<label class="config-option__range-label">{{bgm_volume}}</label>
|
||||
<input class="nav-vert" id="bgm_volume_input" type="range" min="0" max="100" style="flex: 1; margin: 0dp;" data-value="bgm_volume"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-option">
|
||||
<label class="config-option__title">Low Health Beeps</label>
|
||||
<div class="config-option__list config-option__list">
|
||||
<input type="radio" name="lhb" data-checked="low_health_beeps_enabled" value="1" id="lhb_on"/>
|
||||
<label class="config-option__tab-label" for="lhb_on">On</label>
|
||||
<input type="radio" name="lhb" data-checked="low_health_beeps_enabled" value="0" id="lhb_off"/>
|
||||
<label class="config-option__tab-label" for="lhb_off">Off</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
|
12
include/recomp_sound.h
Normal file
12
include/recomp_sound.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef __RECOMP_SOUND_H__
|
||||
#define __RECOMP_SOUND_H__
|
||||
|
||||
namespace recomp {
|
||||
void reset_sound_settings();
|
||||
void set_bgm_volume(int volume);
|
||||
int get_bgm_volume();
|
||||
void set_low_health_beeps_enabled(bool enabled);
|
||||
bool get_low_health_beeps_enabled();
|
||||
}
|
||||
|
||||
#endif
|
9
patches/sound.h
Normal file
9
patches/sound.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __PATCH_AUDIO_H__
|
||||
#define __PATCH_AUDIO_H__
|
||||
|
||||
#include "patch_helpers.h"
|
||||
|
||||
DECLARE_FUNC(float, recomp_get_bgm_volume);
|
||||
DECLARE_FUNC(u32, recomp_get_low_health_beeps_enabled);
|
||||
|
||||
#endif
|
363
patches/sound_patches.c
Normal file
363
patches/sound_patches.c
Normal file
@ -0,0 +1,363 @@
|
||||
#include "patches.h"
|
||||
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
|
||||
#include "sound.h"
|
||||
|
||||
void AudioSeq_ProcessSeqCmd(u32 cmd);
|
||||
void AudioThread_QueueCmd(u32 opArgs, void** data);
|
||||
|
||||
// Direct audio command (skips the queueing system)
|
||||
#define SEQCMD_SET_SEQPLAYER_VOLUME_NOW(seqPlayerIndex, duration, volume) \
|
||||
AudioSeq_ProcessSeqCmd((SEQCMD_OP_SET_SEQPLAYER_VOLUME << 28) | ((u8)(seqPlayerIndex) << 24) | \
|
||||
((u8)(duration) << 16) | ((u8)((volume)*127.0f)));
|
||||
|
||||
bool is_bgm_player(u8 player_index) {
|
||||
return player_index == SEQ_PLAYER_BGM_MAIN || player_index == SEQ_PLAYER_BGM_SUB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update different commands and requests for active sequences
|
||||
*/
|
||||
void AudioSeq_UpdateActiveSequences(void) {
|
||||
u32 tempoCmd;
|
||||
u16 tempoPrev;
|
||||
u16 seqId;
|
||||
u16 channelMask;
|
||||
u16 tempoTarget;
|
||||
u8 setupOp;
|
||||
u8 targetSeqPlayerIndex;
|
||||
u8 setupVal2;
|
||||
u8 setupVal1;
|
||||
u8 tempoOp;
|
||||
s32 pad[2];
|
||||
u32 retMsg;
|
||||
f32 volume;
|
||||
u8 tempoTimer;
|
||||
u8 seqPlayerIndex;
|
||||
u8 j;
|
||||
u8 channelIndex;
|
||||
|
||||
for (seqPlayerIndex = 0; seqPlayerIndex < SEQ_PLAYER_MAX; seqPlayerIndex++) {
|
||||
|
||||
// The seqPlayer has finished initializing and is currently playing the active sequences
|
||||
if (gActiveSeqs[seqPlayerIndex].isSeqPlayerInit && gAudioCtx.seqPlayers[seqPlayerIndex].enabled) {
|
||||
gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = false;
|
||||
}
|
||||
|
||||
// The seqPlayer is no longer playing the active sequences
|
||||
if ((AudioSeq_GetActiveSeqId(seqPlayerIndex) != NA_BGM_DISABLED) &&
|
||||
!gAudioCtx.seqPlayers[seqPlayerIndex].enabled && (!gActiveSeqs[seqPlayerIndex].isSeqPlayerInit)) {
|
||||
gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED;
|
||||
}
|
||||
|
||||
// Check if the requested sequences is waiting for fonts to load
|
||||
if (gActiveSeqs[seqPlayerIndex].isWaitingForFonts) {
|
||||
switch ((s32)AudioThread_GetExternalLoadQueueMsg(&retMsg)) {
|
||||
case SEQ_PLAYER_BGM_MAIN + 1:
|
||||
case SEQ_PLAYER_FANFARE + 1:
|
||||
case SEQ_PLAYER_SFX + 1:
|
||||
case SEQ_PLAYER_BGM_SUB + 1:
|
||||
case SEQ_PLAYER_AMBIENCE + 1:
|
||||
// The fonts have been loaded successfully.
|
||||
gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false;
|
||||
// Queue the same command that was stored previously, but without the 0x8000
|
||||
AudioSeq_ProcessSeqCmd(gActiveSeqs[seqPlayerIndex].startAsyncSeqCmd);
|
||||
break;
|
||||
case 0xFF:
|
||||
// There was an error in loading the fonts
|
||||
gActiveSeqs[seqPlayerIndex].isWaitingForFonts = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update global volume
|
||||
if (gActiveSeqs[seqPlayerIndex].fadeVolUpdate) {
|
||||
volume = 1.0f;
|
||||
for (j = 0; j < VOL_SCALE_INDEX_MAX; j++) {
|
||||
volume *= (gActiveSeqs[seqPlayerIndex].volScales[j] / 127.0f);
|
||||
}
|
||||
|
||||
SEQCMD_SET_SEQPLAYER_VOLUME((u8)(seqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)),
|
||||
gActiveSeqs[seqPlayerIndex].volFadeTimer, (u8)(volume * 127.0f));
|
||||
gActiveSeqs[seqPlayerIndex].fadeVolUpdate = false;
|
||||
}
|
||||
|
||||
if (gActiveSeqs[seqPlayerIndex].volTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].volTimer--;
|
||||
|
||||
if (gActiveSeqs[seqPlayerIndex].volTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].volCur -= gActiveSeqs[seqPlayerIndex].volStep;
|
||||
} else {
|
||||
gActiveSeqs[seqPlayerIndex].volCur = gActiveSeqs[seqPlayerIndex].volTarget;
|
||||
}
|
||||
|
||||
}
|
||||
// @recomp Send a volume scale command regardless of whether volTimer is active and scale it for background music players.
|
||||
f32 cur_volume = gActiveSeqs[seqPlayerIndex].volCur;
|
||||
if (is_bgm_player(seqPlayerIndex)) {
|
||||
cur_volume *= recomp_get_bgm_volume();
|
||||
}
|
||||
AUDIOCMD_SEQPLAYER_FADE_VOLUME_SCALE(seqPlayerIndex, cur_volume);
|
||||
|
||||
// Process tempo
|
||||
if (gActiveSeqs[seqPlayerIndex].tempoCmd != 0) {
|
||||
tempoCmd = gActiveSeqs[seqPlayerIndex].tempoCmd;
|
||||
tempoTimer = (tempoCmd & 0xFF0000) >> 15;
|
||||
tempoTarget = tempoCmd & 0xFFF;
|
||||
if (tempoTimer == 0) {
|
||||
tempoTimer++;
|
||||
}
|
||||
|
||||
// Process tempo commands
|
||||
if (gAudioCtx.seqPlayers[seqPlayerIndex].enabled) {
|
||||
tempoPrev = gAudioCtx.seqPlayers[seqPlayerIndex].tempo / TATUMS_PER_BEAT;
|
||||
tempoOp = (tempoCmd & 0xF000) >> 12;
|
||||
switch (tempoOp) {
|
||||
case SEQCMD_SUB_OP_TEMPO_SPEED_UP:
|
||||
// Speed up tempo by `tempoTarget` amount
|
||||
tempoTarget += tempoPrev;
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_TEMPO_SLOW_DOWN:
|
||||
// Slow down tempo by `tempoTarget` amount
|
||||
if (tempoTarget < tempoPrev) {
|
||||
tempoTarget = tempoPrev - tempoTarget;
|
||||
}
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_TEMPO_SCALE:
|
||||
// Scale tempo by a multiplicative factor
|
||||
tempoTarget = tempoPrev * (tempoTarget / 100.0f);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_TEMPO_RESET:
|
||||
// Reset tempo to original tempo
|
||||
tempoTarget = (gActiveSeqs[seqPlayerIndex].tempoOriginal != 0)
|
||||
? gActiveSeqs[seqPlayerIndex].tempoOriginal
|
||||
: tempoPrev;
|
||||
break;
|
||||
|
||||
default: // `SEQCMD_SUB_OP_TEMPO_SET`
|
||||
// `tempoTarget` is the new tempo
|
||||
break;
|
||||
}
|
||||
|
||||
if (gActiveSeqs[seqPlayerIndex].tempoOriginal == 0) {
|
||||
gActiveSeqs[seqPlayerIndex].tempoOriginal = tempoPrev;
|
||||
}
|
||||
|
||||
gActiveSeqs[seqPlayerIndex].tempoTarget = tempoTarget;
|
||||
gActiveSeqs[seqPlayerIndex].tempoCur = gAudioCtx.seqPlayers[seqPlayerIndex].tempo / 0x30;
|
||||
gActiveSeqs[seqPlayerIndex].tempoStep =
|
||||
(gActiveSeqs[seqPlayerIndex].tempoCur - gActiveSeqs[seqPlayerIndex].tempoTarget) / tempoTimer;
|
||||
gActiveSeqs[seqPlayerIndex].tempoTimer = tempoTimer;
|
||||
gActiveSeqs[seqPlayerIndex].tempoCmd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Step tempo to target
|
||||
if (gActiveSeqs[seqPlayerIndex].tempoTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].tempoTimer--;
|
||||
if (gActiveSeqs[seqPlayerIndex].tempoTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].tempoCur -= gActiveSeqs[seqPlayerIndex].tempoStep;
|
||||
} else {
|
||||
gActiveSeqs[seqPlayerIndex].tempoCur = gActiveSeqs[seqPlayerIndex].tempoTarget;
|
||||
}
|
||||
|
||||
AUDIOCMD_SEQPLAYER_SET_TEMPO(seqPlayerIndex, gActiveSeqs[seqPlayerIndex].tempoCur);
|
||||
}
|
||||
|
||||
// Update channel volumes
|
||||
if (gActiveSeqs[seqPlayerIndex].volChannelFlags != 0) {
|
||||
for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) {
|
||||
if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer--;
|
||||
if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur -=
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volStep;
|
||||
} else {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur =
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volTarget;
|
||||
gActiveSeqs[seqPlayerIndex].volChannelFlags ^= (1 << channelIndex);
|
||||
}
|
||||
|
||||
AUDIOCMD_CHANNEL_SET_VOL_SCALE(seqPlayerIndex, channelIndex,
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].volCur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update frequencies
|
||||
if (gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags != 0) {
|
||||
for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) {
|
||||
if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer--;
|
||||
if (gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur -=
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleStep;
|
||||
} else {
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur =
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleTarget;
|
||||
gActiveSeqs[seqPlayerIndex].freqScaleChannelFlags ^= (1 << channelIndex);
|
||||
}
|
||||
|
||||
AUDIOCMD_CHANNEL_SET_FREQ_SCALE(seqPlayerIndex, channelIndex,
|
||||
gActiveSeqs[seqPlayerIndex].channelData[channelIndex].freqScaleCur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process setup commands
|
||||
if (gActiveSeqs[seqPlayerIndex].setupCmdNum != 0) {
|
||||
// If there is a SeqCmd to reset the audio heap queued, then drop all setup commands
|
||||
if (!AudioSeq_IsSeqCmdNotQueued(SEQCMD_OP_RESET_AUDIO_HEAP << 28, SEQCMD_OP_MASK)) {
|
||||
gActiveSeqs[seqPlayerIndex].setupCmdNum = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Only process setup commands once the timer reaches zero
|
||||
if (gActiveSeqs[seqPlayerIndex].setupCmdTimer != 0) {
|
||||
gActiveSeqs[seqPlayerIndex].setupCmdTimer--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only process setup commands if `seqPlayerIndex` if no longer playing
|
||||
// i.e. the `seqPlayer` is no longer enabled
|
||||
if (gAudioCtx.seqPlayers[seqPlayerIndex].enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; j < gActiveSeqs[seqPlayerIndex].setupCmdNum; j++) {
|
||||
setupOp = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xF00000) >> 20;
|
||||
targetSeqPlayerIndex = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xF0000) >> 16;
|
||||
setupVal2 = (gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF00) >> 8;
|
||||
setupVal1 = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFF;
|
||||
|
||||
switch (setupOp) {
|
||||
case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME:
|
||||
// Restore `targetSeqPlayerIndex` volume back to normal levels
|
||||
AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, setupVal1);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_IF_QUEUED:
|
||||
// Restore `targetSeqPlayerIndex` volume back to normal levels,
|
||||
// but only if the number of sequence queue requests from `sSeqRequests`
|
||||
// exactly matches the argument to the command
|
||||
if (setupVal1 == sNumSeqRequests[seqPlayerIndex]) {
|
||||
AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, setupVal2);
|
||||
}
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_SEQ_UNQUEUE:
|
||||
// Unqueue `seqPlayerIndex` from sSeqRequests
|
||||
//! @bug this command does not work as intended as unqueueing
|
||||
//! the sequence relies on `gActiveSeqs[seqPlayerIndex].seqId`
|
||||
//! However, `gActiveSeqs[seqPlayerIndex].seqId` is reset before the sequence on
|
||||
//! `seqPlayerIndex` is requested to stop, i.e. before the sequence is disabled and setup
|
||||
//! commands (including this command) can run. A simple fix would have been to unqueue based on
|
||||
//! `gActiveSeqs[seqPlayerIndex].prevSeqId` instead
|
||||
SEQCMD_UNQUEUE_SEQUENCE((u8)(seqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), 0,
|
||||
gActiveSeqs[seqPlayerIndex].seqId);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_RESTART_SEQ:
|
||||
// Restart the currently active sequence on `targetSeqPlayerIndex` with full volume.
|
||||
// Sequence on `targetSeqPlayerIndex` must still be active to play (can be muted)
|
||||
SEQCMD_PLAY_SEQUENCE((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), 1,
|
||||
gActiveSeqs[targetSeqPlayerIndex].seqId);
|
||||
gActiveSeqs[targetSeqPlayerIndex].fadeVolUpdate = true;
|
||||
gActiveSeqs[targetSeqPlayerIndex].volScales[1] = 0x7F;
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_TEMPO_SCALE:
|
||||
// Scale tempo by a multiplicative factor
|
||||
SEQCMD_SCALE_TEMPO((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal2,
|
||||
setupVal1);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_TEMPO_RESET:
|
||||
// Reset tempo to previous tempo
|
||||
SEQCMD_RESET_TEMPO((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal1);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_PLAY_SEQ:
|
||||
// Play the requested sequence
|
||||
// Uses the fade timer set by `SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER`
|
||||
seqId = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFFFF;
|
||||
SEQCMD_PLAY_SEQUENCE((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)),
|
||||
gActiveSeqs[targetSeqPlayerIndex].setupFadeTimer, seqId);
|
||||
AudioSeq_SetVolumeScale(targetSeqPlayerIndex, VOL_SCALE_INDEX_FANFARE, 0x7F, 0);
|
||||
gActiveSeqs[targetSeqPlayerIndex].setupFadeTimer = 0;
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_SET_FADE_TIMER:
|
||||
// A command specifically to support `SEQCMD_SUB_OP_SETUP_PLAY_SEQ`
|
||||
// Sets the fade timer for the sequence requested in `SEQCMD_SUB_OP_SETUP_PLAY_SEQ`
|
||||
gActiveSeqs[seqPlayerIndex].setupFadeTimer = setupVal2;
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_RESTORE_SEQPLAYER_VOLUME_WITH_SCALE_INDEX:
|
||||
// Restore the volume back to default levels
|
||||
// Allows a `scaleIndex` to be specified.
|
||||
AudioSeq_SetVolumeScale(targetSeqPlayerIndex, setupVal2, 0x7F, setupVal1);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_POP_PERSISTENT_CACHE:
|
||||
// Discard audio data by popping one more audio caches from the audio heap
|
||||
if (setupVal1 & (1 << SEQUENCE_TABLE)) {
|
||||
AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(SEQUENCE_TABLE);
|
||||
}
|
||||
if (setupVal1 & (1 << FONT_TABLE)) {
|
||||
AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(FONT_TABLE);
|
||||
}
|
||||
if (setupVal1 & (1 << SAMPLE_TABLE)) {
|
||||
AUDIOCMD_GLOBAL_POP_PERSISTENT_CACHE(SAMPLE_TABLE);
|
||||
}
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_SET_CHANNEL_DISABLE_MASK:
|
||||
// Disable (or reenable) specific channels of `targetSeqPlayerIndex`
|
||||
channelMask = gActiveSeqs[seqPlayerIndex].setupCmd[j] & 0xFFFF;
|
||||
SEQCMD_SET_CHANNEL_DISABLE_MASK((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)),
|
||||
channelMask);
|
||||
break;
|
||||
|
||||
case SEQCMD_SUB_OP_SETUP_SET_SEQPLAYER_FREQ:
|
||||
// Scale all channels of `targetSeqPlayerIndex`
|
||||
SEQCMD_SET_SEQPLAYER_FREQ((u8)(targetSeqPlayerIndex + (SEQCMD_ASYNC_ACTIVE >> 24)), setupVal2,
|
||||
setupVal1 * 10);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gActiveSeqs[seqPlayerIndex].setupCmdNum = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @recomp Patched to add the ability to turn off low health beeps.
|
||||
void LifeMeter_UpdateSizeAndBeep(PlayState* play) {
|
||||
InterfaceContext* interfaceCtx = &play->interfaceCtx;
|
||||
|
||||
if (interfaceCtx->lifeSizeChangeDirection != 0) {
|
||||
interfaceCtx->lifeSizeChange--;
|
||||
if (interfaceCtx->lifeSizeChange <= 0) {
|
||||
interfaceCtx->lifeSizeChange = 0;
|
||||
interfaceCtx->lifeSizeChangeDirection = 0;
|
||||
// @recomp Additional check for whether low health beeps are enabled.
|
||||
if (recomp_get_low_health_beeps_enabled() && !Player_InCsMode(play) && (play->pauseCtx.state == PAUSE_STATE_OFF) &&
|
||||
(play->pauseCtx.debugEditor == DEBUG_EDITOR_NONE) && LifeMeter_IsCritical() && !Play_InCsMode(play)) {
|
||||
Audio_PlaySfx(NA_SE_SY_HITPOINT_ALARM);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
interfaceCtx->lifeSizeChange++;
|
||||
if ((s32)interfaceCtx->lifeSizeChange >= 10) {
|
||||
interfaceCtx->lifeSizeChange = 10;
|
||||
interfaceCtx->lifeSizeChangeDirection = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,3 +16,5 @@ recomp_get_pending_warp = 0x8F000020;
|
||||
recomp_powf = 0x8F000024;
|
||||
recomp_get_target_framerate = 0x8F000028;
|
||||
recomp_get_targeting_mode = 0x8F00002C;
|
||||
recomp_get_bgm_volume = 0x8F000030;
|
||||
recomp_get_low_health_beeps_enabled = 0x8F000034;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@ -14,6 +15,7 @@
|
||||
|
||||
constexpr std::u8string_view graphics_filename = u8"graphics.json";
|
||||
constexpr std::u8string_view controls_filename = u8"controls.json";
|
||||
constexpr std::u8string_view sound_filename = u8"sound.json";
|
||||
|
||||
constexpr auto res_default = ultramodern::Resolution::Auto;
|
||||
constexpr auto wm_default = ultramodern::WindowMode::Windowed;
|
||||
@ -24,13 +26,26 @@ constexpr int rr_manual_default = 60;
|
||||
constexpr bool developer_mode_default = false;
|
||||
|
||||
template <typename T>
|
||||
void from_or_default(const json& j, const std::string& key, T& out, T default_value) {
|
||||
T from_or_default(const json& j, const std::string& key, T default_value) {
|
||||
T ret;
|
||||
auto find_it = j.find(key);
|
||||
if (find_it != j.end()) {
|
||||
find_it->get_to(out);
|
||||
find_it->get_to(ret);
|
||||
}
|
||||
else {
|
||||
out = default_value;
|
||||
ret = default_value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void call_if_key_exists(void (*func)(T), const json& j, const std::string& key) {
|
||||
auto find_it = j.find(key);
|
||||
if (find_it != j.end()) {
|
||||
T val;
|
||||
find_it->get_to(val);
|
||||
func(val);
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,13 +63,13 @@ namespace ultramodern {
|
||||
}
|
||||
|
||||
void from_json(const json& j, GraphicsConfig& config) {
|
||||
from_or_default(j, "res_option", config.res_option, res_default);
|
||||
from_or_default(j, "wm_option", config.wm_option, wm_default);
|
||||
from_or_default(j, "ar_option", config.ar_option, ar_default);
|
||||
from_or_default(j, "msaa_option", config.msaa_option, msaa_default);
|
||||
from_or_default(j, "rr_option", config.rr_option, rr_default);
|
||||
from_or_default(j, "rr_manual_value", config.rr_manual_value, rr_manual_default);
|
||||
from_or_default(j, "developer_mode", config.developer_mode, developer_mode_default);
|
||||
config.res_option = from_or_default(j, "res_option", res_default);
|
||||
config.wm_option = from_or_default(j, "wm_option", wm_default);
|
||||
config.ar_option = from_or_default(j, "ar_option", ar_default);
|
||||
config.msaa_option = from_or_default(j, "msaa_option", msaa_default);
|
||||
config.rr_option = from_or_default(j, "rr_option", rr_default);
|
||||
config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default);
|
||||
config.developer_mode = from_or_default(j, "developer_mode", developer_mode_default);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,13 +242,8 @@ void load_controls_config(const std::filesystem::path& path) {
|
||||
|
||||
config_file >> config_json;
|
||||
|
||||
recomp::TargetingMode targeting_mode;
|
||||
from_or_default(config_json["options"], "targeting_mode", targeting_mode, recomp::TargetingMode::Switch);
|
||||
recomp::set_targeting_mode(targeting_mode);
|
||||
|
||||
int rumble_strength;
|
||||
from_or_default(config_json["options"], "rumble_strength", rumble_strength, 25);
|
||||
recomp::set_rumble_strength(rumble_strength);
|
||||
recomp::set_targeting_mode(from_or_default(config_json["options"], "targeting_mode", recomp::TargetingMode::Switch));
|
||||
recomp::set_rumble_strength(from_or_default(config_json["options"], "rumble_strength", 25));
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
@ -244,10 +254,33 @@ void load_controls_config(const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
void save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
||||
|
||||
std::ofstream config_file{path};
|
||||
config_file << std::setw(4) << config_json;
|
||||
}
|
||||
|
||||
void load_sound_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
|
||||
|
||||
recomp::reset_sound_settings();
|
||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "set_low_health_beeps_enabled");
|
||||
}
|
||||
|
||||
void recomp::load_config() {
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||
std::filesystem::path sound_path = recomp_dir / sound_filename;
|
||||
|
||||
if (std::filesystem::exists(graphics_path)) {
|
||||
load_graphics_config(graphics_path);
|
||||
@ -264,6 +297,14 @@ void recomp::load_config() {
|
||||
recomp::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(sound_path)) {
|
||||
load_sound_config(sound_path);
|
||||
}
|
||||
else {
|
||||
recomp::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::save_config() {
|
||||
@ -277,4 +318,5 @@ void recomp::save_config() {
|
||||
|
||||
save_graphics_config(recomp_dir / graphics_filename);
|
||||
save_controls_config(recomp_dir / controls_filename);
|
||||
save_sound_config(recomp_dir / sound_filename);
|
||||
}
|
||||
|
@ -3,9 +3,11 @@
|
||||
#include "recomp.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "../patches/input.h"
|
||||
#include "../patches/graphics.h"
|
||||
#include "../patches/sound.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
|
||||
@ -66,3 +68,12 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
|
||||
}
|
||||
|
||||
|
||||
extern "C" void recomp_get_bgm_volume(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, recomp::get_bgm_volume() / 100.0f);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_low_health_beeps_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<u32>(recomp::get_low_health_beeps_enabled()));
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
"From Laundry Pool",
|
||||
"From East Clock Town (South entrance)",
|
||||
"Clock Tower balcony",
|
||||
"From Song of Soaring",
|
||||
"Owl Statue",
|
||||
"First song of time cutscene"
|
||||
}
|
||||
},
|
||||
@ -168,17 +168,17 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
{ "Swamp", {
|
||||
{
|
||||
12, "Southern Swamp (After Woodfall Temple)", {
|
||||
"-swamp road",
|
||||
"-boat house",
|
||||
"-woodfall",
|
||||
"-lower deku palace",
|
||||
"-upper deku palace",
|
||||
"-hags potion shop",
|
||||
"-boat cruise",
|
||||
"-woods of mystery",
|
||||
"-swamp spider house",
|
||||
"-ikana canyon",
|
||||
"-owl statue",
|
||||
"From Road",
|
||||
"In Front of Boat House",
|
||||
"Froom Woodfall",
|
||||
"From Lower Deku Palace",
|
||||
"From Upper Deku Palace",
|
||||
"From Magic Hags' Potion Shop",
|
||||
"Boat Ride",
|
||||
"From Woods of Mistery",
|
||||
"From Swamp Spider House",
|
||||
"From Ikanya Canyon",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -212,67 +212,67 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
80, "Deku Palace", {
|
||||
"From Southern Swamp",
|
||||
"After getting caught",
|
||||
"-deku king chamber",
|
||||
"-deku king chamber (upper)",
|
||||
"-deku shrine",
|
||||
"From Southern Swamp (Alternate)",
|
||||
"-jp grotto left, first room",
|
||||
"-jp grotto left, second room",
|
||||
"-jp grotto right, second room",
|
||||
"From Deku King Chamber",
|
||||
"From Upper Deku King Chamber",
|
||||
"From Deku Shrine",
|
||||
"From Southern Swamp (Upper tunnel)",
|
||||
"From Left Grotto (Japanese)",
|
||||
"From Left Grotto Second Room (Japanese)",
|
||||
"From Right Grotto Second Room (Japanese)",
|
||||
"From Bean Seller Grotto",
|
||||
"-jp grotto right, first room",
|
||||
"From Right Grotto First Room (Japanese)",
|
||||
}
|
||||
},
|
||||
{
|
||||
118, "Deku Palace Royal Chamber", {
|
||||
"-deku palace",
|
||||
"-deku palace (upper)",
|
||||
"-monkey released",
|
||||
"-front of king",
|
||||
"From Deku Palace",
|
||||
"From Upper Deku Palace",
|
||||
"After Releasing Monkey",
|
||||
"In Front of the King",
|
||||
}
|
||||
},
|
||||
{
|
||||
122, "Road to Southern Swamp", {
|
||||
"-termina field",
|
||||
"-southern swamp",
|
||||
"-swamp shooting gallery",
|
||||
"From Termina Field",
|
||||
"From Southern Swamp",
|
||||
"From Swamp Shooting Gallery",
|
||||
}
|
||||
},
|
||||
{
|
||||
132, "Southern Swamp (Before Woodfall Temple)", {
|
||||
"-road to southern swamp",
|
||||
"-boat house",
|
||||
"-woodfall",
|
||||
"-deku palace",
|
||||
"-deku palace (shortcut)",
|
||||
"-hags potion shop",
|
||||
"-boat ride",
|
||||
"-woods of mystery",
|
||||
"-swamp spider house",
|
||||
"-ikana canyon",
|
||||
"-owl statue",
|
||||
"From Road to Southern Swamp",
|
||||
"In Front of Boat House",
|
||||
"From Woodfall",
|
||||
"From Deku Palace",
|
||||
"From Deku Palace (Shortcut)",
|
||||
"From Hags' Potion Shop",
|
||||
"Boat Ride",
|
||||
"From Woods of Mistery",
|
||||
"From Swamp Spider House",
|
||||
"From Ikana Canyon",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
134, "Woodfall", {
|
||||
"-southern swamp",
|
||||
"-unknown",
|
||||
"-fairy fountain",
|
||||
"-unknown",
|
||||
"-owl statue",
|
||||
"From Southern Swamp",
|
||||
"In Mid-Air",
|
||||
"-From Fairy Mountain",
|
||||
"In Mid-Air (alternate)",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
158, "Deku Shrine", {
|
||||
"-deku palace",
|
||||
"-deku palace"
|
||||
"From Deku Palace",
|
||||
"From Deku Palace"
|
||||
}
|
||||
},
|
||||
{
|
||||
168, "Swamp Tourist Center", {
|
||||
"Entrance",
|
||||
"-koume",
|
||||
"-tingle's dad",
|
||||
"Talking to Koume",
|
||||
"Talking to Tingle's Dad",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -319,117 +319,117 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
138, "Goron Village (After Snowhead Temple)", {
|
||||
"-path to goron village (spring)",
|
||||
"-unknown",
|
||||
"-goron shrine",
|
||||
"-lens of truth",
|
||||
"-void out",
|
||||
"From Path to Goron Village",
|
||||
"In Mid-Air",
|
||||
"From Goron Shrine",
|
||||
"Over the Void",
|
||||
"In Front of Invisible Platforms",
|
||||
}
|
||||
},
|
||||
{
|
||||
148, "Goron Village (Before Snowhead Temple)", {
|
||||
"-path to goron village (winter)",
|
||||
"-deku flower",
|
||||
"-goron shrine",
|
||||
"-lens of truth",
|
||||
"-void out",
|
||||
"From Path to Goron Village",
|
||||
"In Front of Deku Flower",
|
||||
"From Goron Shrine",
|
||||
"From Lens of Truth",
|
||||
"In Front of Invisible Platforms",
|
||||
}
|
||||
},
|
||||
{
|
||||
150, "Goron Graveyard", {
|
||||
"-mountain village",
|
||||
"-receiving goron mask",
|
||||
"-From Mountain Village",
|
||||
"-After Receiving Goron Mask",
|
||||
}
|
||||
},
|
||||
{
|
||||
154, "Mountain Village (Before Snowhead Temple)", {
|
||||
"-after snowhead",
|
||||
"-mountain smithy",
|
||||
"-path to goron village (winter)",
|
||||
"-goron graveyard",
|
||||
"-path to snowhead",
|
||||
"-on ice",
|
||||
"-path to mountain village",
|
||||
"-unknown",
|
||||
"-owl statue",
|
||||
"In Front of Mountain Smithy",
|
||||
"Mountain Smithy",
|
||||
"From Path to Goron Village",
|
||||
"From Goron Graveyard",
|
||||
"From Path to Snowhead",
|
||||
"On the lake",
|
||||
"From Path to Mountain Village",
|
||||
"On the Lake (alternate)",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
174, "Mountain Village (After Snowhead Temple)", {
|
||||
"-after snowhead",
|
||||
"-mountain smithy",
|
||||
"-path to goron village (spring)",
|
||||
"-goron graveyard",
|
||||
"-path to snowhead",
|
||||
"-behind waterfall",
|
||||
"-path to mountain village",
|
||||
"-after snowhead (cutscene)",
|
||||
"-owl statue",
|
||||
"Next to Lake",
|
||||
"From Mountain Smithy",
|
||||
"From Path to Goron Village",
|
||||
"From Goron Graveyard",
|
||||
"From Path to Snowhead",
|
||||
"Behind Waterfall",
|
||||
"From Path to Mountain Village",
|
||||
"Next to Lake (After Snowhead Cutscene)",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
178, "Snowhead", {
|
||||
"-path to snowhead",
|
||||
"-snowhead temple",
|
||||
"-fairy fountain",
|
||||
"-owl statue",
|
||||
"From Path to Snowhead",
|
||||
"From Snowhead Temple",
|
||||
"From Fairy Fountain",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
180, "Road to Goron Village (Before Snowhead Temple)", {
|
||||
"-mountain village (winter)",
|
||||
"-goron village (winter)",
|
||||
"-goron racetrack",
|
||||
"From Mountain Village",
|
||||
"From Goron Village",
|
||||
"From Goron Racetrack",
|
||||
}
|
||||
},
|
||||
{
|
||||
182, "Road to Goron Village (After Snowhead Temple)", {
|
||||
"-mountain village (spring)",
|
||||
"-goron village (spring)",
|
||||
"-goron racetrack",
|
||||
"From Mountain Village",
|
||||
"From Goron Village",
|
||||
"From Goron Racetrack",
|
||||
}
|
||||
},
|
||||
{
|
||||
208, "Goron Racetrack", {
|
||||
"-path to mountain village",
|
||||
"-race start",
|
||||
"-race end",
|
||||
"From Path to Mountain Village",
|
||||
"Race Start",
|
||||
"Race End",
|
||||
}
|
||||
}
|
||||
}},
|
||||
{ "Great Bay", {
|
||||
{
|
||||
34, "Pirates' Fortress (Outdoors)", {
|
||||
"-exterior pirates fortress",
|
||||
"-lower hookshot room",
|
||||
"-upper hookshot room",
|
||||
"-silver rupee room",
|
||||
"-silver rupee room exit",
|
||||
"-barrel room",
|
||||
"-barrel room exit",
|
||||
"-twin barrel room",
|
||||
"-twin barrel room exit",
|
||||
"-oob near twin barrel",
|
||||
"-telescope",
|
||||
"-oob hookshot room",
|
||||
"-balcony",
|
||||
"-upper hookshot room",
|
||||
"From Exterior Pirate Fortress",
|
||||
"From Lower Hookshoot Room",
|
||||
"From Upper Hookshoot Room",
|
||||
"From Silver Rupee Room",
|
||||
"From Silver Rupee Room (alternate)",
|
||||
"From Room with Barrels",
|
||||
"From Room with Barrels (alternate)",
|
||||
"From Room with Barrels and Bridge",
|
||||
"From Room with Barrels and Bridge (alternate)",
|
||||
"Out of bounds",
|
||||
"Telescope",
|
||||
"Out of bounds (alternate)",
|
||||
"From Balcony in Exterior Pirate Fortress",
|
||||
"From Upper Hookshoot Room",
|
||||
}
|
||||
},
|
||||
{
|
||||
64, "Pirates' Fortress (Indoors)", {
|
||||
"-hookshot room",
|
||||
"-hookshot room upper",
|
||||
"-100 rupee room",
|
||||
"-100 rupee room (egg)",
|
||||
"-barrel room",
|
||||
"-barrel room (egg)",
|
||||
"-twin barrel room",
|
||||
"-twin barrel room (egg)",
|
||||
"-telescope",
|
||||
"-outside, underwater",
|
||||
"-outside, telescope",
|
||||
"-unknown",
|
||||
"Hookshoot Room",
|
||||
"Upper Hookshoot Room",
|
||||
"Silver Rupee Room",
|
||||
"100 Rupee Room (Next to Egg)",
|
||||
"Barrel Room",
|
||||
"Barrel Room (Next to Egg)",
|
||||
"Room with Barrels and Bridge",
|
||||
"Room with Barrels and Bridge (next to egg)",
|
||||
"Telescope",
|
||||
"Hidden Entrance ",
|
||||
"Telescope",
|
||||
"Unloaded Room",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -450,58 +450,58 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
96, "Zora Hall", {
|
||||
"-zora cape",
|
||||
"-zora cape (turtle)",
|
||||
"-zora shop",
|
||||
"-lulu's room",
|
||||
"-evan's room",
|
||||
"-japa's room",
|
||||
"-mikau & tijo's room",
|
||||
"-stage",
|
||||
"-after rehearsal",
|
||||
"From Zora Cape",
|
||||
"From Zora Cape with Turtle",
|
||||
"From Zora Shop",
|
||||
"From Lulu's Room",
|
||||
"From Evan's Room",
|
||||
"From Japa's Room",
|
||||
"From Mikau's & Tijo's Room",
|
||||
"Stage",
|
||||
"Stage (After Rehearsal)",
|
||||
}
|
||||
},
|
||||
{
|
||||
104, "Great Bay Coast", {
|
||||
"-termina field",
|
||||
"From Termina Field",
|
||||
"-zora cape",
|
||||
"-void respawn",
|
||||
"-pinnacle rock",
|
||||
"-fisherman hut",
|
||||
"-pirates fortress",
|
||||
"-void resapwn (murky water)",
|
||||
"-marine lab",
|
||||
"-oceanside spider house",
|
||||
"-during zora mask",
|
||||
"-after zora mask",
|
||||
"-owl statue",
|
||||
"-thrown out",
|
||||
"-after jumping game",
|
||||
"From Zora Cape",
|
||||
"From Pinnacle Rock",
|
||||
"From Fisherman's Hut",
|
||||
"From Pirates' Fortress",
|
||||
"Next to Chuchu",
|
||||
"From Marine Lab",
|
||||
"From Oceanside Spider House",
|
||||
"Beach (Zora Mask Cutscene)",
|
||||
"Beach (After Zora Mask Cutscene)",
|
||||
"Owl Statue",
|
||||
"Thrown Out Pirates' Fortress",
|
||||
"Island (After jumping game)",
|
||||
}
|
||||
},
|
||||
{
|
||||
106, "Zora Cape", {
|
||||
"-great bay coast",
|
||||
"-zora hall",
|
||||
"-zora hall (turtle)",
|
||||
"-void respawn",
|
||||
"-waterfall",
|
||||
"-fairy fountain",
|
||||
"-owl statue",
|
||||
"-great bay temple",
|
||||
"-after great bay temple",
|
||||
"-unknown",
|
||||
"From Great Bay Coast",
|
||||
"From Zora Hall",
|
||||
"From Zora Hall with Turtle",
|
||||
"Next to Zora Game Site",
|
||||
"From Waterfall Rapids",
|
||||
"From Fairy Fountain",
|
||||
"From Owl Statue",
|
||||
"From Great Bay Temple",
|
||||
"After Beating Great Bay Temple",
|
||||
"After Beating Great Bay Temple",
|
||||
}
|
||||
},
|
||||
{
|
||||
112, "Pirates' Fortress (Entrance)", {
|
||||
"-great bay coast",
|
||||
"-pirates fortress",
|
||||
"-underwater passage",
|
||||
"-underwater jet",
|
||||
"-kicked out",
|
||||
"-hookshot platform",
|
||||
"-passage door",
|
||||
"From Great Bay Coast",
|
||||
"From Pirates' Fortress",
|
||||
"From Secret Entrance",
|
||||
"From Underwater Jet",
|
||||
"Kicked out",
|
||||
"From Hookshot Platform in Pirates' Fortress",
|
||||
"From Telescope Room",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -517,53 +517,53 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
142, "Waterfall Rapids", {
|
||||
"-zora cape",
|
||||
"-race start",
|
||||
"-race end",
|
||||
"-game won",
|
||||
"From Zora Cape",
|
||||
"Race Start",
|
||||
"Race End",
|
||||
"Race Won",
|
||||
}
|
||||
},
|
||||
{
|
||||
146, "Zora Hall (Room)", {
|
||||
"-mikau from zora hall",
|
||||
"-japas from zora hall",
|
||||
"-lulu from zora hall",
|
||||
"-evan from zora hall",
|
||||
"-japa after jam session",
|
||||
"-zora shop from zora hall",
|
||||
"-evan after composing song",
|
||||
"Mikau's Room",
|
||||
"Japas' Room",
|
||||
"Lulu's Room",
|
||||
"Evan's Room",
|
||||
"Japa's Room (after jam session)",
|
||||
"Zora's Shop",
|
||||
"Evan's Room (after composing song)",
|
||||
}
|
||||
},
|
||||
{
|
||||
184, "Gyorg Arena", {
|
||||
"-great bay temple",
|
||||
"-falling cutscene",
|
||||
"Entrance",
|
||||
"Falling Cutscene",
|
||||
}
|
||||
},
|
||||
{
|
||||
190, "-great bay (cutscene)", {
|
||||
"zora cape",
|
||||
190, "Great Bay (Pirate and Turtle Cutscene)", {
|
||||
"From Zora Cape",
|
||||
}
|
||||
}
|
||||
}},
|
||||
{ "Ikana", {
|
||||
{
|
||||
32, "Ikana Canyon", {
|
||||
"-ikana road",
|
||||
"-ghost hut",
|
||||
"-music box house",
|
||||
"-stone tower",
|
||||
"-owl statue",
|
||||
"-beneath the well",
|
||||
"-sakon's hideout",
|
||||
"-after stone tower",
|
||||
"-ikana castle",
|
||||
"-after house opens",
|
||||
"-song of storms cave (house open)",
|
||||
"-fairy fountain",
|
||||
"-secret shrine",
|
||||
"-from song of storms cave",
|
||||
"-song of storms cave (house closed) ",
|
||||
"From Ikana Road",
|
||||
"From Ghost Hut",
|
||||
"From Music Box House",
|
||||
"From Stone Tower",
|
||||
"Owl Statue",
|
||||
"From Beneath the Well",
|
||||
"From Sakon's Hideout",
|
||||
"After Beating Stone Tower Temple",
|
||||
"From Ikana Castle",
|
||||
"House Opening Cutscene",
|
||||
"Spring Water Cave (played Song of Storms)",
|
||||
"From Fairy Fountain",
|
||||
"From Secret Shrine",
|
||||
"From Spring Water Cave",
|
||||
"Spring Water Cave",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -609,18 +609,18 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
128, "Ikana Graveyard", {
|
||||
"-road to ikana",
|
||||
"-grave 1",
|
||||
"-grave 2",
|
||||
"-grave 3",
|
||||
"-dampe's house",
|
||||
"-after keeta defeated",
|
||||
"Road to Ikana",
|
||||
"From Grave 1 ",
|
||||
"From Grave 2",
|
||||
"From Grave 3",
|
||||
"From Dampe's House",
|
||||
"Keeta Defeated Cutscene",
|
||||
}
|
||||
},
|
||||
{
|
||||
144, "Beneath the Well", {
|
||||
"-ikana canyon",
|
||||
"-ikana castle",
|
||||
"From Ikana Canyon",
|
||||
"From Ikana Castle",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -631,15 +631,15 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
{
|
||||
156, "Spirit House", {
|
||||
"Entrance",
|
||||
"-after minigame",
|
||||
"-beat minigame",
|
||||
"Minigame start",
|
||||
"After minigame",
|
||||
}
|
||||
},
|
||||
{
|
||||
160, "Road to Ikana", {
|
||||
"-termina field",
|
||||
"-ikana canyon",
|
||||
"-ikana graveyard",
|
||||
"From Termina Field",
|
||||
"From Ikana Canyon",
|
||||
"From Ikana Graveyard",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -654,16 +654,16 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
170, "Stone Tower", {
|
||||
"-ikana canyon",
|
||||
"-unknown",
|
||||
"-stone tower temple",
|
||||
"-owl statue",
|
||||
"From Ikana Canyon",
|
||||
"In Front of Temple",
|
||||
"From Stone Tower Temple",
|
||||
"Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
172, "Stone Tower (Inverted)", {
|
||||
"-after inverting",
|
||||
"-stone tower temple",
|
||||
"In Front of Temple (Inverting Cutscene)",
|
||||
"From Stone Tower Temple",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -682,22 +682,21 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
84, "Termina Field", {
|
||||
"-west clock town",
|
||||
"-road to southern swamp",
|
||||
"-great bay coast",
|
||||
"-path to mountain village",
|
||||
"-road to ikana",
|
||||
"-milk road",
|
||||
"-south clock town",
|
||||
"-east clock town",
|
||||
"-north clock town",
|
||||
"-observatory",
|
||||
"-observatory (telescope)",
|
||||
"-near ikana",
|
||||
"-moon crash",
|
||||
"-cremia hug",
|
||||
"-skullkid cutscene",
|
||||
"-west clock town",
|
||||
"From West Clock Town",
|
||||
"From Road to Southern Swapm",
|
||||
"From Great Bay Coast",
|
||||
"From Path to Mountain Village",
|
||||
"From Road to Ikana",
|
||||
"From Milk Road",
|
||||
"From South Clock Town",
|
||||
"From East Clock Town",
|
||||
"From North Clock Town",
|
||||
"From Observatory",
|
||||
"Use Telescope",
|
||||
"Near Ikana",
|
||||
"Moon Crash Cutscene (Game Over)",
|
||||
"Next to Ikana (After Cremia's Hug)",
|
||||
"Next to Road to Southern Swamp (After Skull Kid Cutscene)"
|
||||
}
|
||||
}
|
||||
}},
|
||||
@ -712,11 +711,11 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
62, "Milk Road", {
|
||||
"From Termina Field",
|
||||
"From Romani Ranch",
|
||||
"-gorman track (track exit)",
|
||||
"-gorman track (main exit)",
|
||||
"From Gorman's Track (Track Exit)",
|
||||
"From Gorman's Track (Main Exit)",
|
||||
"At Owl Statue",
|
||||
"5?",
|
||||
"6?",
|
||||
"Behind Giant Rock",
|
||||
"Next to Owl Statue",
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -737,25 +736,24 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
},
|
||||
{
|
||||
124, "Doggy Racetrack", {
|
||||
"-romani ranch",
|
||||
"-after race",
|
||||
"From Romani Ranch",
|
||||
"Next to Track (After Race)",
|
||||
}
|
||||
},
|
||||
{
|
||||
126, "Cucco Shack", {
|
||||
"-romani ranch",
|
||||
"-after bunny hood",
|
||||
"From Romani Ranch",
|
||||
"Talking to Grog (Getting Bunny Hood)",
|
||||
}
|
||||
},
|
||||
{
|
||||
206, "Gorman Track", {
|
||||
"-milk road",
|
||||
"-unknown",
|
||||
"-beat minigame",
|
||||
"-milk road behind fence",
|
||||
"-milk road fence cutscene",
|
||||
"-unknown",
|
||||
"-start minigame",
|
||||
"From Milk Road",
|
||||
"Next to Gorman",
|
||||
"Next to Gorman (After Beating Race)",
|
||||
"From Milk Road (Behind Fence)",
|
||||
"From Milk Road (After Fence Cutscene)",
|
||||
"In the Middle of the Track"
|
||||
}
|
||||
}
|
||||
}},
|
||||
@ -815,12 +813,12 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
}
|
||||
},
|
||||
{
|
||||
46, "-before clock town", {
|
||||
"-falling from cliff",
|
||||
"-inside clock tower",
|
||||
"-transformed to deku",
|
||||
"-void respawn",
|
||||
"-song of time flashback",
|
||||
46, "Intro Areas", {
|
||||
"Falling from Cliff Cutscene",
|
||||
"Before Entering Clock Tower",
|
||||
"After Being Transformed into Deku",
|
||||
"Before Entering Clock Tower (Void Respawn)",
|
||||
"South Clock Town (After First Song of Time)",
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_debug.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
@ -10,6 +11,7 @@ ultramodern::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle control_options_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
|
||||
bool configuring_controller = false;
|
||||
|
||||
@ -45,6 +47,21 @@ void bind_option(Rml::DataModelConstructor& constructor, const std::string& name
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void bind_atomic(Rml::DataModelConstructor& constructor, Rml::DataModelHandle handle, const char* name, std::atomic<T>* atomic_val) {
|
||||
constructor.BindFunc(name,
|
||||
[atomic_val](Rml::Variant& out) {
|
||||
out = atomic_val->load();
|
||||
printf("out: %s\n", out.Get<std::string>().c_str());
|
||||
},
|
||||
[atomic_val, handle, name](const Rml::Variant& in) mutable {
|
||||
printf("in: %s\n", in.Get<std::string>().c_str());
|
||||
atomic_val->store(in.Get<T>());
|
||||
handle.DirtyVariable(name);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static int scanned_binding_index = -1;
|
||||
static int scanned_input_index = -1;
|
||||
static int focused_input_index = -1;
|
||||
@ -100,6 +117,49 @@ void recomp::set_targeting_mode(recomp::TargetingMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
struct SoundOptionsContext {
|
||||
std::atomic<int> bgm_volume;
|
||||
std::atomic<int> low_health_beeps_enabled; // RmlUi doesn't seem to like "true"/"false" strings for setting variants so an int is used here instead.
|
||||
void reset() {
|
||||
bgm_volume = 100;
|
||||
low_health_beeps_enabled = (int)true;
|
||||
}
|
||||
SoundOptionsContext() {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
SoundOptionsContext sound_options_context;
|
||||
|
||||
void recomp::reset_sound_settings() {
|
||||
sound_options_context.reset();
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyAllVariables();
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_bgm_volume(int volume) {
|
||||
sound_options_context.bgm_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_bgm_volume() {
|
||||
return sound_options_context.bgm_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_low_health_beeps_enabled(bool enabled) {
|
||||
sound_options_context.low_health_beeps_enabled.store((int)enabled);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("low_health_beeps_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_low_health_beeps_enabled() {
|
||||
return (bool)sound_options_context.low_health_beeps_enabled.load();
|
||||
}
|
||||
|
||||
struct DebugContext {
|
||||
Rml::DataModelHandle model_handle;
|
||||
std::vector<std::string> area_names;
|
||||
@ -386,6 +446,18 @@ public:
|
||||
|
||||
control_options_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_sound_options_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("sound_options_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the sound options menu");
|
||||
}
|
||||
|
||||
sound_options_model_handle = constructor.GetModelHandle();
|
||||
|
||||
bind_atomic(constructor, sound_options_model_handle, "bgm_volume", &sound_options_context.bgm_volume);
|
||||
bind_atomic(constructor, sound_options_model_handle, "low_health_beeps_enabled", &sound_options_context.low_health_beeps_enabled);
|
||||
}
|
||||
|
||||
void make_debug_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
|
||||
@ -413,6 +485,7 @@ public:
|
||||
make_graphics_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
make_control_options_bindings(context);
|
||||
make_sound_options_bindings(context);
|
||||
make_debug_bindings(context);
|
||||
}
|
||||
};
|
||||
|
@ -1273,6 +1273,7 @@ void recomp::set_config_submenu(recomp::ConfigSubmenu submenu) {
|
||||
|
||||
void recomp::destroy_ui() {
|
||||
std::lock_guard lock {ui_context_mutex};
|
||||
Rml::Debugger::Shutdown();
|
||||
ui_context->rml.font_interface.reset();
|
||||
Rml::Shutdown();
|
||||
ui_context->rml.unload();
|
||||
|
Loading…
Reference in New Issue
Block a user