Added background music volume and low health beep settings, imported warp names

This commit is contained in:
Mr-Wiseguy 2024-03-10 16:40:41 -04:00
parent 0ceeeb04ea
commit eeeabba64d
11 changed files with 788 additions and 260 deletions

View File

@ -63,7 +63,7 @@
<div>Sound</div> <div>Sound</div>
<div class="tab__indicator"></div> <div class="tab__indicator"></div>
</tab> </tab>
<panel class="config" > <panel class="config" data-model="sound_options_model">
<template src="config-menu__sound" /> <template src="config-menu__sound" />
</panel> </panel>
<tab class="tab"> <tab class="tab">

View File

@ -4,7 +4,24 @@
<body> <body>
<form class="config__form"> <form class="config__form">
<div class="config__wrapper"> <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> </div>
</form> </form>
</body> </body>

12
include/recomp_sound.h Normal file
View 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
View 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
View 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;
}
}
}

View File

@ -16,3 +16,5 @@ recomp_get_pending_warp = 0x8F000020;
recomp_powf = 0x8F000024; recomp_powf = 0x8F000024;
recomp_get_target_framerate = 0x8F000028; recomp_get_target_framerate = 0x8F000028;
recomp_get_targeting_mode = 0x8F00002C; recomp_get_targeting_mode = 0x8F00002C;
recomp_get_bgm_volume = 0x8F000030;
recomp_get_low_health_beeps_enabled = 0x8F000034;

View File

@ -1,5 +1,6 @@
#include "recomp_config.h" #include "recomp_config.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_sound.h"
#include "../../ultramodern/config.hpp" #include "../../ultramodern/config.hpp"
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@ -14,6 +15,7 @@
constexpr std::u8string_view graphics_filename = u8"graphics.json"; constexpr std::u8string_view graphics_filename = u8"graphics.json";
constexpr std::u8string_view controls_filename = u8"controls.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 res_default = ultramodern::Resolution::Auto;
constexpr auto wm_default = ultramodern::WindowMode::Windowed; constexpr auto wm_default = ultramodern::WindowMode::Windowed;
@ -24,13 +26,26 @@ constexpr int rr_manual_default = 60;
constexpr bool developer_mode_default = false; constexpr bool developer_mode_default = false;
template <typename T> 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); auto find_it = j.find(key);
if (find_it != j.end()) { if (find_it != j.end()) {
find_it->get_to(out); find_it->get_to(ret);
} }
else { 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) { void from_json(const json& j, GraphicsConfig& config) {
from_or_default(j, "res_option", config.res_option, res_default); config.res_option = from_or_default(j, "res_option", res_default);
from_or_default(j, "wm_option", config.wm_option, wm_default); config.wm_option = from_or_default(j, "wm_option", wm_default);
from_or_default(j, "ar_option", config.ar_option, ar_default); config.ar_option = from_or_default(j, "ar_option", ar_default);
from_or_default(j, "msaa_option", config.msaa_option, msaa_default); config.msaa_option = from_or_default(j, "msaa_option", msaa_default);
from_or_default(j, "rr_option", config.rr_option, rr_default); config.rr_option = from_or_default(j, "rr_option", rr_default);
from_or_default(j, "rr_manual_value", config.rr_manual_value, rr_manual_default); config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default);
from_or_default(j, "developer_mode", config.developer_mode, developer_mode_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; config_file >> config_json;
recomp::TargetingMode targeting_mode; recomp::set_targeting_mode(from_or_default(config_json["options"], "targeting_mode", recomp::TargetingMode::Switch));
from_or_default(config_json["options"], "targeting_mode", targeting_mode, recomp::TargetingMode::Switch); recomp::set_rumble_strength(from_or_default(config_json["options"], "rumble_strength", 25));
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);
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) { if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings); 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() { void recomp::load_config() {
std::filesystem::path recomp_dir = recomp::get_app_folder_path(); std::filesystem::path recomp_dir = recomp::get_app_folder_path();
std::filesystem::path graphics_path = recomp_dir / graphics_filename; std::filesystem::path graphics_path = recomp_dir / graphics_filename;
std::filesystem::path controls_path = recomp_dir / controls_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)) { if (std::filesystem::exists(graphics_path)) {
load_graphics_config(graphics_path); load_graphics_config(graphics_path);
@ -264,6 +297,14 @@ void recomp::load_config() {
recomp::reset_input_bindings(); recomp::reset_input_bindings();
save_controls_config(controls_path); 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() { void recomp::save_config() {
@ -277,4 +318,5 @@ void recomp::save_config() {
save_graphics_config(recomp_dir / graphics_filename); save_graphics_config(recomp_dir / graphics_filename);
save_controls_config(recomp_dir / controls_filename); save_controls_config(recomp_dir / controls_filename);
save_sound_config(recomp_dir / sound_filename);
} }

View File

@ -3,9 +3,11 @@
#include "recomp.h" #include "recomp.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_sound.h"
#include "recomp_helpers.h" #include "recomp_helpers.h"
#include "../patches/input.h" #include "../patches/input.h"
#include "../patches/graphics.h" #include "../patches/graphics.h"
#include "../patches/sound.h"
#include "../ultramodern/ultramodern.hpp" #include "../ultramodern/ultramodern.hpp"
#include "../ultramodern/config.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) { extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<int>(recomp::get_targeting_mode())); _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()));
}

View File

@ -154,7 +154,7 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
"From Laundry Pool", "From Laundry Pool",
"From East Clock Town (South entrance)", "From East Clock Town (South entrance)",
"Clock Tower balcony", "Clock Tower balcony",
"From Song of Soaring", "Owl Statue",
"First song of time cutscene" "First song of time cutscene"
} }
}, },
@ -168,17 +168,17 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
{ "Swamp", { { "Swamp", {
{ {
12, "Southern Swamp (After Woodfall Temple)", { 12, "Southern Swamp (After Woodfall Temple)", {
"-swamp road", "From Road",
"-boat house", "In Front of Boat House",
"-woodfall", "Froom Woodfall",
"-lower deku palace", "From Lower Deku Palace",
"-upper deku palace", "From Upper Deku Palace",
"-hags potion shop", "From Magic Hags' Potion Shop",
"-boat cruise", "Boat Ride",
"-woods of mystery", "From Woods of Mistery",
"-swamp spider house", "From Swamp Spider House",
"-ikana canyon", "From Ikanya Canyon",
"-owl statue", "Owl Statue",
} }
}, },
{ {
@ -212,67 +212,67 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
80, "Deku Palace", { 80, "Deku Palace", {
"From Southern Swamp", "From Southern Swamp",
"After getting caught", "After getting caught",
"-deku king chamber", "From Deku King Chamber",
"-deku king chamber (upper)", "From Upper Deku King Chamber",
"-deku shrine", "From Deku Shrine",
"From Southern Swamp (Alternate)", "From Southern Swamp (Upper tunnel)",
"-jp grotto left, first room", "From Left Grotto (Japanese)",
"-jp grotto left, second room", "From Left Grotto Second Room (Japanese)",
"-jp grotto right, second room", "From Right Grotto Second Room (Japanese)",
"From Bean Seller Grotto", "From Bean Seller Grotto",
"-jp grotto right, first room", "From Right Grotto First Room (Japanese)",
} }
}, },
{ {
118, "Deku Palace Royal Chamber", { 118, "Deku Palace Royal Chamber", {
"-deku palace", "From Deku Palace",
"-deku palace (upper)", "From Upper Deku Palace",
"-monkey released", "After Releasing Monkey",
"-front of king", "In Front of the King",
} }
}, },
{ {
122, "Road to Southern Swamp", { 122, "Road to Southern Swamp", {
"-termina field", "From Termina Field",
"-southern swamp", "From Southern Swamp",
"-swamp shooting gallery", "From Swamp Shooting Gallery",
} }
}, },
{ {
132, "Southern Swamp (Before Woodfall Temple)", { 132, "Southern Swamp (Before Woodfall Temple)", {
"-road to southern swamp", "From Road to Southern Swamp",
"-boat house", "In Front of Boat House",
"-woodfall", "From Woodfall",
"-deku palace", "From Deku Palace",
"-deku palace (shortcut)", "From Deku Palace (Shortcut)",
"-hags potion shop", "From Hags' Potion Shop",
"-boat ride", "Boat Ride",
"-woods of mystery", "From Woods of Mistery",
"-swamp spider house", "From Swamp Spider House",
"-ikana canyon", "From Ikana Canyon",
"-owl statue", "Owl Statue",
} }
}, },
{ {
134, "Woodfall", { 134, "Woodfall", {
"-southern swamp", "From Southern Swamp",
"-unknown", "In Mid-Air",
"-fairy fountain", "-From Fairy Mountain",
"-unknown", "In Mid-Air (alternate)",
"-owl statue", "Owl Statue",
} }
}, },
{ {
158, "Deku Shrine", { 158, "Deku Shrine", {
"-deku palace", "From Deku Palace",
"-deku palace" "From Deku Palace"
} }
}, },
{ {
168, "Swamp Tourist Center", { 168, "Swamp Tourist Center", {
"Entrance", "Entrance",
"-koume", "Talking to Koume",
"-tingle's dad", "Talking to Tingle's Dad",
} }
}, },
{ {
@ -319,117 +319,117 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
138, "Goron Village (After Snowhead Temple)", { 138, "Goron Village (After Snowhead Temple)", {
"-path to goron village (spring)", "From Path to Goron Village",
"-unknown", "In Mid-Air",
"-goron shrine", "From Goron Shrine",
"-lens of truth", "Over the Void",
"-void out", "In Front of Invisible Platforms",
} }
}, },
{ {
148, "Goron Village (Before Snowhead Temple)", { 148, "Goron Village (Before Snowhead Temple)", {
"-path to goron village (winter)", "From Path to Goron Village",
"-deku flower", "In Front of Deku Flower",
"-goron shrine", "From Goron Shrine",
"-lens of truth", "From Lens of Truth",
"-void out", "In Front of Invisible Platforms",
} }
}, },
{ {
150, "Goron Graveyard", { 150, "Goron Graveyard", {
"-mountain village", "-From Mountain Village",
"-receiving goron mask", "-After Receiving Goron Mask",
} }
}, },
{ {
154, "Mountain Village (Before Snowhead Temple)", { 154, "Mountain Village (Before Snowhead Temple)", {
"-after snowhead", "In Front of Mountain Smithy",
"-mountain smithy", "Mountain Smithy",
"-path to goron village (winter)", "From Path to Goron Village",
"-goron graveyard", "From Goron Graveyard",
"-path to snowhead", "From Path to Snowhead",
"-on ice", "On the lake",
"-path to mountain village", "From Path to Mountain Village",
"-unknown", "On the Lake (alternate)",
"-owl statue", "Owl Statue",
} }
}, },
{ {
174, "Mountain Village (After Snowhead Temple)", { 174, "Mountain Village (After Snowhead Temple)", {
"-after snowhead", "Next to Lake",
"-mountain smithy", "From Mountain Smithy",
"-path to goron village (spring)", "From Path to Goron Village",
"-goron graveyard", "From Goron Graveyard",
"-path to snowhead", "From Path to Snowhead",
"-behind waterfall", "Behind Waterfall",
"-path to mountain village", "From Path to Mountain Village",
"-after snowhead (cutscene)", "Next to Lake (After Snowhead Cutscene)",
"-owl statue", "Owl Statue",
} }
}, },
{ {
178, "Snowhead", { 178, "Snowhead", {
"-path to snowhead", "From Path to Snowhead",
"-snowhead temple", "From Snowhead Temple",
"-fairy fountain", "From Fairy Fountain",
"-owl statue", "Owl Statue",
} }
}, },
{ {
180, "Road to Goron Village (Before Snowhead Temple)", { 180, "Road to Goron Village (Before Snowhead Temple)", {
"-mountain village (winter)", "From Mountain Village",
"-goron village (winter)", "From Goron Village",
"-goron racetrack", "From Goron Racetrack",
} }
}, },
{ {
182, "Road to Goron Village (After Snowhead Temple)", { 182, "Road to Goron Village (After Snowhead Temple)", {
"-mountain village (spring)", "From Mountain Village",
"-goron village (spring)", "From Goron Village",
"-goron racetrack", "From Goron Racetrack",
} }
}, },
{ {
208, "Goron Racetrack", { 208, "Goron Racetrack", {
"-path to mountain village", "From Path to Mountain Village",
"-race start", "Race Start",
"-race end", "Race End",
} }
} }
}}, }},
{ "Great Bay", { { "Great Bay", {
{ {
34, "Pirates' Fortress (Outdoors)", { 34, "Pirates' Fortress (Outdoors)", {
"-exterior pirates fortress", "From Exterior Pirate Fortress",
"-lower hookshot room", "From Lower Hookshoot Room",
"-upper hookshot room", "From Upper Hookshoot Room",
"-silver rupee room", "From Silver Rupee Room",
"-silver rupee room exit", "From Silver Rupee Room (alternate)",
"-barrel room", "From Room with Barrels",
"-barrel room exit", "From Room with Barrels (alternate)",
"-twin barrel room", "From Room with Barrels and Bridge",
"-twin barrel room exit", "From Room with Barrels and Bridge (alternate)",
"-oob near twin barrel", "Out of bounds",
"-telescope", "Telescope",
"-oob hookshot room", "Out of bounds (alternate)",
"-balcony", "From Balcony in Exterior Pirate Fortress",
"-upper hookshot room", "From Upper Hookshoot Room",
} }
}, },
{ {
64, "Pirates' Fortress (Indoors)", { 64, "Pirates' Fortress (Indoors)", {
"-hookshot room", "Hookshoot Room",
"-hookshot room upper", "Upper Hookshoot Room",
"-100 rupee room", "Silver Rupee Room",
"-100 rupee room (egg)", "100 Rupee Room (Next to Egg)",
"-barrel room", "Barrel Room",
"-barrel room (egg)", "Barrel Room (Next to Egg)",
"-twin barrel room", "Room with Barrels and Bridge",
"-twin barrel room (egg)", "Room with Barrels and Bridge (next to egg)",
"-telescope", "Telescope",
"-outside, underwater", "Hidden Entrance ",
"-outside, telescope", "Telescope",
"-unknown", "Unloaded Room",
} }
}, },
{ {
@ -450,58 +450,58 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
96, "Zora Hall", { 96, "Zora Hall", {
"-zora cape", "From Zora Cape",
"-zora cape (turtle)", "From Zora Cape with Turtle",
"-zora shop", "From Zora Shop",
"-lulu's room", "From Lulu's Room",
"-evan's room", "From Evan's Room",
"-japa's room", "From Japa's Room",
"-mikau & tijo's room", "From Mikau's & Tijo's Room",
"-stage", "Stage",
"-after rehearsal", "Stage (After Rehearsal)",
} }
}, },
{ {
104, "Great Bay Coast", { 104, "Great Bay Coast", {
"-termina field", "From Termina Field",
"-zora cape", "-zora cape",
"-void respawn", "From Zora Cape",
"-pinnacle rock", "From Pinnacle Rock",
"-fisherman hut", "From Fisherman's Hut",
"-pirates fortress", "From Pirates' Fortress",
"-void resapwn (murky water)", "Next to Chuchu",
"-marine lab", "From Marine Lab",
"-oceanside spider house", "From Oceanside Spider House",
"-during zora mask", "Beach (Zora Mask Cutscene)",
"-after zora mask", "Beach (After Zora Mask Cutscene)",
"-owl statue", "Owl Statue",
"-thrown out", "Thrown Out Pirates' Fortress",
"-after jumping game", "Island (After jumping game)",
} }
}, },
{ {
106, "Zora Cape", { 106, "Zora Cape", {
"-great bay coast", "From Great Bay Coast",
"-zora hall", "From Zora Hall",
"-zora hall (turtle)", "From Zora Hall with Turtle",
"-void respawn", "Next to Zora Game Site",
"-waterfall", "From Waterfall Rapids",
"-fairy fountain", "From Fairy Fountain",
"-owl statue", "From Owl Statue",
"-great bay temple", "From Great Bay Temple",
"-after great bay temple", "After Beating Great Bay Temple",
"-unknown", "After Beating Great Bay Temple",
} }
}, },
{ {
112, "Pirates' Fortress (Entrance)", { 112, "Pirates' Fortress (Entrance)", {
"-great bay coast", "From Great Bay Coast",
"-pirates fortress", "From Pirates' Fortress",
"-underwater passage", "From Secret Entrance",
"-underwater jet", "From Underwater Jet",
"-kicked out", "Kicked out",
"-hookshot platform", "From Hookshot Platform in Pirates' Fortress",
"-passage door", "From Telescope Room",
} }
}, },
{ {
@ -517,53 +517,53 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
142, "Waterfall Rapids", { 142, "Waterfall Rapids", {
"-zora cape", "From Zora Cape",
"-race start", "Race Start",
"-race end", "Race End",
"-game won", "Race Won",
} }
}, },
{ {
146, "Zora Hall (Room)", { 146, "Zora Hall (Room)", {
"-mikau from zora hall", "Mikau's Room",
"-japas from zora hall", "Japas' Room",
"-lulu from zora hall", "Lulu's Room",
"-evan from zora hall", "Evan's Room",
"-japa after jam session", "Japa's Room (after jam session)",
"-zora shop from zora hall", "Zora's Shop",
"-evan after composing song", "Evan's Room (after composing song)",
} }
}, },
{ {
184, "Gyorg Arena", { 184, "Gyorg Arena", {
"-great bay temple", "Entrance",
"-falling cutscene", "Falling Cutscene",
} }
}, },
{ {
190, "-great bay (cutscene)", { 190, "Great Bay (Pirate and Turtle Cutscene)", {
"zora cape", "From Zora Cape",
} }
} }
}}, }},
{ "Ikana", { { "Ikana", {
{ {
32, "Ikana Canyon", { 32, "Ikana Canyon", {
"-ikana road", "From Ikana Road",
"-ghost hut", "From Ghost Hut",
"-music box house", "From Music Box House",
"-stone tower", "From Stone Tower",
"-owl statue", "Owl Statue",
"-beneath the well", "From Beneath the Well",
"-sakon's hideout", "From Sakon's Hideout",
"-after stone tower", "After Beating Stone Tower Temple",
"-ikana castle", "From Ikana Castle",
"-after house opens", "House Opening Cutscene",
"-song of storms cave (house open)", "Spring Water Cave (played Song of Storms)",
"-fairy fountain", "From Fairy Fountain",
"-secret shrine", "From Secret Shrine",
"-from song of storms cave", "From Spring Water Cave",
"-song of storms cave (house closed) ", "Spring Water Cave",
} }
}, },
{ {
@ -609,18 +609,18 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
128, "Ikana Graveyard", { 128, "Ikana Graveyard", {
"-road to ikana", "Road to Ikana",
"-grave 1", "From Grave 1 ",
"-grave 2", "From Grave 2",
"-grave 3", "From Grave 3",
"-dampe's house", "From Dampe's House",
"-after keeta defeated", "Keeta Defeated Cutscene",
} }
}, },
{ {
144, "Beneath the Well", { 144, "Beneath the Well", {
"-ikana canyon", "From Ikana Canyon",
"-ikana castle", "From Ikana Castle",
} }
}, },
{ {
@ -631,15 +631,15 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
{ {
156, "Spirit House", { 156, "Spirit House", {
"Entrance", "Entrance",
"-after minigame", "Minigame start",
"-beat minigame", "After minigame",
} }
}, },
{ {
160, "Road to Ikana", { 160, "Road to Ikana", {
"-termina field", "From Termina Field",
"-ikana canyon", "From Ikana Canyon",
"-ikana graveyard", "From Ikana Graveyard",
} }
}, },
{ {
@ -654,16 +654,16 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
170, "Stone Tower", { 170, "Stone Tower", {
"-ikana canyon", "From Ikana Canyon",
"-unknown", "In Front of Temple",
"-stone tower temple", "From Stone Tower Temple",
"-owl statue", "Owl Statue",
} }
}, },
{ {
172, "Stone Tower (Inverted)", { 172, "Stone Tower (Inverted)", {
"-after inverting", "In Front of Temple (Inverting Cutscene)",
"-stone tower temple", "From Stone Tower Temple",
} }
}, },
{ {
@ -682,22 +682,21 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
84, "Termina Field", { 84, "Termina Field", {
"-west clock town", "From West Clock Town",
"-road to southern swamp", "From Road to Southern Swapm",
"-great bay coast", "From Great Bay Coast",
"-path to mountain village", "From Path to Mountain Village",
"-road to ikana", "From Road to Ikana",
"-milk road", "From Milk Road",
"-south clock town", "From South Clock Town",
"-east clock town", "From East Clock Town",
"-north clock town", "From North Clock Town",
"-observatory", "From Observatory",
"-observatory (telescope)", "Use Telescope",
"-near ikana", "Near Ikana",
"-moon crash", "Moon Crash Cutscene (Game Over)",
"-cremia hug", "Next to Ikana (After Cremia's Hug)",
"-skullkid cutscene", "Next to Road to Southern Swamp (After Skull Kid Cutscene)"
"-west clock town",
} }
} }
}}, }},
@ -712,11 +711,11 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
62, "Milk Road", { 62, "Milk Road", {
"From Termina Field", "From Termina Field",
"From Romani Ranch", "From Romani Ranch",
"-gorman track (track exit)", "From Gorman's Track (Track Exit)",
"-gorman track (main exit)", "From Gorman's Track (Main Exit)",
"At Owl Statue", "At Owl Statue",
"5?", "Behind Giant Rock",
"6?", "Next to Owl Statue",
} }
}, },
{ {
@ -737,25 +736,24 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
}, },
{ {
124, "Doggy Racetrack", { 124, "Doggy Racetrack", {
"-romani ranch", "From Romani Ranch",
"-after race", "Next to Track (After Race)",
} }
}, },
{ {
126, "Cucco Shack", { 126, "Cucco Shack", {
"-romani ranch", "From Romani Ranch",
"-after bunny hood", "Talking to Grog (Getting Bunny Hood)",
} }
}, },
{ {
206, "Gorman Track", { 206, "Gorman Track", {
"-milk road", "From Milk Road",
"-unknown", "Next to Gorman",
"-beat minigame", "Next to Gorman (After Beating Race)",
"-milk road behind fence", "From Milk Road (Behind Fence)",
"-milk road fence cutscene", "From Milk Road (After Fence Cutscene)",
"-unknown", "In the Middle of the Track"
"-start minigame",
} }
} }
}}, }},
@ -815,12 +813,12 @@ std::vector<recomp::AreaWarps> recomp::game_warps {
} }
}, },
{ {
46, "-before clock town", { 46, "Intro Areas", {
"-falling from cliff", "Falling from Cliff Cutscene",
"-inside clock tower", "Before Entering Clock Tower",
"-transformed to deku", "After Being Transformed into Deku",
"-void respawn", "Before Entering Clock Tower (Void Respawn)",
"-song of time flashback", "South Clock Town (After First Song of Time)",
} }
}, },
{ {

View File

@ -1,5 +1,6 @@
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_sound.h"
#include "recomp_config.h" #include "recomp_config.h"
#include "recomp_debug.h" #include "recomp_debug.h"
#include "../../ultramodern/config.hpp" #include "../../ultramodern/config.hpp"
@ -10,6 +11,7 @@ ultramodern::GraphicsConfig new_options;
Rml::DataModelHandle graphics_model_handle; Rml::DataModelHandle graphics_model_handle;
Rml::DataModelHandle controls_model_handle; Rml::DataModelHandle controls_model_handle;
Rml::DataModelHandle control_options_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 // True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
bool configuring_controller = false; 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_binding_index = -1;
static int scanned_input_index = -1; static int scanned_input_index = -1;
static int focused_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 { struct DebugContext {
Rml::DataModelHandle model_handle; Rml::DataModelHandle model_handle;
std::vector<std::string> area_names; std::vector<std::string> area_names;
@ -387,6 +447,18 @@ public:
control_options_model_handle = constructor.GetModelHandle(); 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) { void make_debug_bindings(Rml::Context* context) {
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model"); Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
if (!constructor) { if (!constructor) {
@ -413,6 +485,7 @@ public:
make_graphics_bindings(context); make_graphics_bindings(context);
make_controls_bindings(context); make_controls_bindings(context);
make_control_options_bindings(context); make_control_options_bindings(context);
make_sound_options_bindings(context);
make_debug_bindings(context); make_debug_bindings(context);
} }
}; };

View File

@ -1273,6 +1273,7 @@ void recomp::set_config_submenu(recomp::ConfigSubmenu submenu) {
void recomp::destroy_ui() { void recomp::destroy_ui() {
std::lock_guard lock {ui_context_mutex}; std::lock_guard lock {ui_context_mutex};
Rml::Debugger::Shutdown();
ui_context->rml.font_interface.reset(); ui_context->rml.font_interface.reset();
Rml::Shutdown(); Rml::Shutdown();
ui_context->rml.unload(); ui_context->rml.unload();