Merge branch 'main' of ssh://github.com/TwilitRealm/dusklight into jpn

This commit is contained in:
TakaRikka
2026-07-16 15:09:23 -07:00
1815 changed files with 43740 additions and 14076 deletions
+405 -223
View File
@@ -6,6 +6,7 @@
#include "dusk/app_info.hpp"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/android_frame_rate.hpp"
#include "dusk/config.hpp"
#include "dusk/hotkeys.h"
#include "dusk/data.hpp"
@@ -13,8 +14,8 @@
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/io.hpp"
#include "dusk/livesplit.h"
#include "dusk/main.h"
#include "dusk/discord_presence.hpp"
#include "dusk/speedrun.h"
#include "graphics_tuner.hpp"
#include "m_Do/m_Do_main.h"
#include "menu_bar.hpp"
@@ -23,6 +24,7 @@
#include "menu_bar.hpp"
#include "pane.hpp"
#include "prelaunch.hpp"
#include "touch_controls_editor.hpp"
#include "ui.hpp"
#include <aurora/lib/window.hpp>
@@ -36,6 +38,17 @@
#include <algorithm>
#include <filesystem>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if defined(TARGET_ANDROID) || defined(__ANDROID__) || \
(defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
#define TOUCH_CONTROLS_AVAILABLE true
#else
#define TOUCH_CONTROLS_AVAILABLE false
#endif
namespace dusk::ui {
namespace {
@@ -59,11 +72,43 @@ constexpr std::array kFpsOverlayCornerNames = {
"Bottom Right",
};
constexpr std::array kInterpolationModes = {
"Off",
"Capped",
"Unlimited",
};
constexpr std::array kTouchTargetingLabels = {
"Hybrid",
"Hold",
"Switch",
};
constexpr std::array kTouchTargetingDescriptions = {
"Tap once to lock on when a target is found. Double-tap when none is found to hold L.",
"L stays held only while your finger is on the button.",
"Tap L to keep it held. Tap again to release it.",
};
constexpr std::array kGyroInputModeLabels = {
"Sensor",
"Mouse",
};
constexpr std::array kMenuScalingModeLabels = {
"GameCube",
"Wii",
"Dusklight",
};
constexpr std::array kMagicArmorModes = {
"Normal",
"On Damage",
"Double Defense",
"Invincible",
"Cosmetic",
};
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
if (backend == "auto") {
outBackend = BACKEND_AUTO;
@@ -157,8 +202,8 @@ std::vector<AuroraBackend> available_backends() {
size_t backendCount = 0;
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
// Do not expose NULL or D3D11
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
// Do not expose NULL
if (raw[i] != BACKEND_NULL) {
backends.emplace_back(raw[i]);
}
}
@@ -174,103 +219,8 @@ AuroraBackend configured_backend() {
return configuredBackend;
}
void reset_for_speedrun_mode() {
mDoMain::developmentMode = -1;
getSettings().game.enableTurboKeybind.setSpeedrunValue(false);
getSettings().game.damageMultiplier.setSpeedrunValue(1);
getSettings().game.instantDeath.setSpeedrunValue(false);
getSettings().game.noHeartDrops.setSpeedrunValue(false);
getSettings().game.autoSave.setSpeedrunValue(false);
getSettings().game.sunsSong.setSpeedrunValue(false);
getSettings().game.infiniteHearts.setSpeedrunValue(false);
getSettings().game.infiniteArrows.setSpeedrunValue(false);
getSettings().game.infiniteSeeds.setSpeedrunValue(false);
getSettings().game.infiniteBombs.setSpeedrunValue(false);
getSettings().game.infiniteOil.setSpeedrunValue(false);
getSettings().game.infiniteOxygen.setSpeedrunValue(false);
getSettings().game.infiniteRupees.setSpeedrunValue(false);
getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false);
getSettings().game.moonJump.setSpeedrunValue(false);
getSettings().game.superClawshot.setSpeedrunValue(false);
getSettings().game.alwaysGreatspin.setSpeedrunValue(false);
getSettings().game.enableFastIronBoots.setSpeedrunValue(false);
getSettings().game.canTransformAnywhere.setSpeedrunValue(false);
getSettings().game.fastRoll.setSpeedrunValue(false);
getSettings().game.fastSpinner.setSpeedrunValue(false);
getSettings().game.freeMagicArmor.setSpeedrunValue(false);
getSettings().game.invincibleEnemies.setSpeedrunValue(false);
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
aurora_set_pause_on_focus_lost(false);
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
getSettings().game.recordingMode.setSpeedrunValue(false);
getSettings().game.debugFlyCam.setSpeedrunValue(false);
}
void clear_speedrun_overrides() {
config::EnumerateRegistered([](config::ConfigVarBase& cvar) {
cvar.clearSpeedrunOverride();
});
}
void restore_from_speedrun_mode() {
clear_speedrun_overrides();
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
}
std::filesystem::path normalized_display_path(const std::filesystem::path& path) {
std::error_code ec;
auto normalized = std::filesystem::weakly_canonical(path, ec);
if (!ec) {
return normalized;
}
normalized = std::filesystem::absolute(path, ec);
if (!ec) {
return normalized.lexically_normal();
}
return path.lexically_normal();
}
std::filesystem::path user_home_path() {
const char* homePath = SDL_GetUserFolder(SDL_FOLDER_HOME);
if (homePath == nullptr || homePath[0] == '\0') {
return {};
}
return std::filesystem::path{reinterpret_cast<const char8_t*>(homePath)};
}
Rml::String abbreviated_data_path_string() {
const auto path = data::configured_data_path();
const auto homePath = user_home_path();
if (path.empty() || homePath.empty()) {
return io::fs_path_to_string(path);
}
const auto normalizedPath = normalized_display_path(path);
const auto normalizedHome = normalized_display_path(homePath);
if (normalizedPath == normalizedHome) {
return "~";
}
const auto relativePath = normalizedPath.lexically_relative(normalizedHome);
if (!relativePath.empty() && !relativePath.is_absolute()) {
const auto it = relativePath.begin();
if (it == relativePath.end() || *it != "..") {
return io::fs_path_to_string(std::filesystem::path{"~"} / relativePath);
}
}
return io::fs_path_to_string(path);
}
Rml::String configured_data_path_display_name() {
const auto path = abbreviated_data_path_string();
const auto path = data::abbreviated_path_string(data::configured_data_path());
if (path.empty()) {
return "(none)";
}
@@ -287,8 +237,9 @@ public:
explicit DataFolderPathText(Rml::Element* parent) : Component(append(parent, "div")) {}
void update() override {
const Rml::String rml = "<span class=\"data-folder-current\">Current data folder:<br/>" +
escape(abbreviated_data_path_string()) + "</span>";
const Rml::String rml =
"<span class=\"data-folder-current\">Current data folder:<br/>" +
escape(data::abbreviated_path_string(data::configured_data_path())) + "</span>";
if (rml != mCurrentRml) {
mRoot->SetInnerRML(rml);
mCurrentRml = rml;
@@ -351,23 +302,36 @@ const Rml::String kInternalResolutionHelpText =
const Rml::String kShadowResolutionHelpText =
"Configure the shadow-map resolution. Higher values improve shadow quality but increase GPU "
"and memory usage.";
const Rml::String kResamplerHelpText =
"Configure the sampling method used when scaling the internal resolution for final presentation.";
const Rml::String kBloomHelpText =
"Configure the post-processing bloom effect. Classic uses the original bloom pass; Dusklight uses "
"a higher-quality bloom pass.";
const Rml::String kBloomBrightnessHelpText =
"Configure bloom intensity. Higher values make bright areas glow more strongly.";
const Rml::String kDepthOfFieldHelpText =
"Configure the post-processing depth-of-field effect. Classic uses the original depth-of-field pass;"
" Dusklight uses a higher-quality depth-of-field pass.";
const Rml::String kUnlockFramerateHelpText =
"Uses inter-frame interpolation to enable higher frame rates.<br/><br/>May introduce minor "
"<br/>Uses inter-frame interpolation to enable higher frame rates.<br/><br/>May introduce minor "
"visual artifacts or animation glitches.";
const Rml::String kTextureReplacementHelpText =
"Enable installed texture replacements.";
int float_setting_percent(ConfigVar<float>& var) {
return static_cast<int>(var.getValue() * 100.0f + 0.5f);
}
bool gyro_enabled() {
return getSettings().game.enableGyroAim ||
(getSettings().game.enableGyroRollgoal &&
getSettings().game.gyroMode.getValue() != GyroMode::Mouse);
return getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal;
}
Rml::String touch_targeting_label(TouchTargeting targeting) {
const auto index = static_cast<std::size_t>(targeting);
if (index >= kTouchTargetingLabels.size()) {
return "Unknown";
}
return kTouchTargetingLabels[index];
}
struct ConfigBoolProps {
@@ -390,7 +354,7 @@ SelectButton& config_bool_select(
return;
}
var.setValue(value);
config::Save();
config::save();
if (callback) {
callback(value);
}
@@ -411,7 +375,7 @@ void add_speedrun_disabled_option(Pane& leftPane, Pane& rightPane, ConfigVar<boo
config_bool_select(leftPane, rightPane, var, {
.key = key,
.helpText = helpText,
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
});
}
@@ -424,7 +388,7 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
.setValue =
[&var, min, max](int value) {
var.setValue(std::clamp(value, min, max) / 100.0f);
config::Save();
config::save();
},
.isDisabled = std::move(isDisabled),
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
@@ -433,6 +397,36 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
.step = step,
.suffix = "%",
});
leftPane.register_control(button, rightPane, [helpText = std::move(helpText)](Pane& pane) {
pane.clear();
pane.add_rml(helpText);
});
return button;
}
SelectButton& config_int_select(Pane& leftPane, Pane& rightPane, ConfigVar<int>& var,
Rml::String key, Rml::String helpText, int min, int max, int step = 5,
std::function<bool()> isDisabled = {}, std::function<void(int)> onChange = {},
std::string suffix = "") {
auto& button = leftPane.add_child<NumberButton>(NumberButton::Props{
.key = std::move(key),
.getValue = [&var] { return var.getValue(); },
.setValue =
[&var, min, max, callback = std::move(onChange)](int value) {
const int clampedValue = std::clamp(value, min, max);
var.setValue(clampedValue);
config::save();
if (callback) {
callback(clampedValue);
}
},
.isDisabled = std::move(isDisabled),
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
.min = min,
.max = max,
.step = step,
.suffix = suffix,
});
leftPane.register_control(button, rightPane, [helpText = std::move(helpText)](Pane& pane) {
pane.clear();
pane.add_text(helpText);
@@ -442,7 +436,7 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
template <typename T>
void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& var,
const GraphicsTunerProps& props, bool prelaunch) {
const GraphicsTunerProps& props) {
leftPane.register_control(
leftPane
.add_select_button({
@@ -460,10 +454,10 @@ void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, Con
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
.submit = false,
})
.on_nav_command([&window, props, prelaunch](Rml::Event&, NavCommand cmd) {
.on_nav_command([&window, props](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
window.push(std::make_unique<GraphicsTuner>(props, prelaunch));
window.push(std::make_unique<GraphicsTuner>(props));
return true;
}
return false;
@@ -478,7 +472,6 @@ void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, Con
SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
if (prelaunch) {
mSuppressNavFallback = true;
add_tab("Prelaunch", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
@@ -593,7 +586,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
config::save();
});
}
pane.add_rml("<br/>Changes require a restart.");
@@ -601,7 +594,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
leftPane.register_control(
leftPane.add_select_button({
.key = "Graphics Backend",
.getValue = [] { return Rml::String{backend_name(configured_backend())}; },
.getValue = [] { return Rml::String{backend_name(aurora_get_backend())}; },
.isModified =
[] {
return getSettings().backend.graphicsBackend.getValue() !=
@@ -620,7 +613,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.graphicsBackend.setValue(
std::string{backend_id(backend)});
config::Save();
config::save();
});
}
pane.add_rml("<br/>Changes require a restart.");
@@ -651,7 +644,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.cardFileType.setValue(i);
config::Save();
config::save();
});
}
});
@@ -668,7 +661,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
config::save();
}),
rightPane, [](Pane& pane) { pane.clear(); });
leftPane.register_control(leftPane.add_button("Restore Default Window Size").on_pressed([] {
@@ -699,7 +692,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
{
.key = "Pause on Focus Lost",
.helpText = "Pause the game when window focus is lost.",
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
.isDisabled = [] { return IsMobile || getSettings().game.speedrunMode; },
});
leftPane.register_control(
@@ -731,7 +723,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFpsOverlay.setValue(false);
config::Save();
config::save();
});
for (int i = 0; i < static_cast<int>(kFpsOverlayCornerNames.size()); ++i) {
pane.add_button(
@@ -747,12 +739,27 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFpsOverlay.setValue(true);
getSettings().video.fpsOverlayCorner.setValue(i);
config::Save();
config::save();
});
}
pane.add_rml(
"<br/>Display the current framerate in a corner of the screen while playing.");
});
config_bool_select(leftPane, rightPane, getSettings().video.rememberWindowSize,
{
.key = "Remember Window Size",
.helpText = "Save and restore the previous session's window size when opening Dusklight.",
.onChange =
[](bool value) {
if (value && !dusk::getSettings().video.enableFullscreen) {
const auto windowSize = aurora::window::get_window_size();
dusk::getSettings().video.lastWindowWidth.setValue(windowSize.width);
dusk::getSettings().video.lastWindowHeight.setValue(windowSize.height);
dusk::config::save();
}
},
.isDisabled = [] { return IsMobile; },
});
leftPane.add_section("Resolution");
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.internalResolutionScale,
@@ -763,7 +770,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMin = 0,
.valueMax = 12,
.defaultValue = 0,
}, mPrelaunch);
});
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.shadowResolutionMultiplier,
GraphicsTunerProps{
@@ -773,7 +780,16 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMin = 1,
.valueMax = 8,
.defaultValue = 1,
}, mPrelaunch);
});
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.resampler,
GraphicsTunerProps{
.option = GraphicsOption::Resampler,
.title = "Output Resampling",
.helpText = kResamplerHelpText,
.valueMin = static_cast<int>(Resampler::Bilinear),
.valueMax = static_cast<int>(Resampler::Area),
.defaultValue = static_cast<int>(Resampler::Bilinear),
});
leftPane.add_section("Post-Processing");
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMode,
@@ -784,7 +800,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMin = static_cast<int>(BloomMode::Off),
.valueMax = static_cast<int>(BloomMode::Dusk),
.defaultValue = static_cast<int>(BloomMode::Classic),
}, mPrelaunch);
});
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier,
GraphicsTunerProps{
.option = GraphicsOption::BloomMultiplier,
@@ -793,25 +809,75 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMin = 0,
.valueMax = 100,
.defaultValue = 100,
}, mPrelaunch);
.step = 10,
});
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.depthOfFieldMode,
GraphicsTunerProps{
.option = GraphicsOption::DepthOfFieldMode,
.title = "Depth of Field",
.helpText = kDepthOfFieldHelpText,
.valueMin = static_cast<int>(DepthOfFieldMode::Off),
.valueMax = static_cast<int>(DepthOfFieldMode::Dusk),
.defaultValue = static_cast<int>(DepthOfFieldMode::Classic),
});
leftPane.add_section("Rendering");
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
{
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.enableTextureReplacements,
GraphicsTunerProps{
.option = GraphicsOption::TextureReplacements,
.title = "Enable Texture Replacements",
.helpText = kTextureReplacementHelpText,
.valueMin = static_cast<int>(false),
.valueMax = static_cast<int>(true),
.defaultValue = static_cast<int>(false),
});
leftPane.register_control(
leftPane.add_select_button({
.key = "Unlock Framerate",
.helpText = kUnlockFramerateHelpText,
});
config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField,
{
.key = "Enable Depth of Field",
.getValue =
[] {
return kInterpolationModes[static_cast<u8>(getSettings().game.enableFrameInterpolation.getValue())];
},
.isModified =
[] {
return getSettings().game.enableFrameInterpolation.getValue() !=
getSettings().game.enableFrameInterpolation.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < kInterpolationModes.size(); i++) {
pane.add_button({
.text = kInterpolationModes[i],
.isSelected =
[i] {
return getSettings().game.enableFrameInterpolation.getValue() == static_cast<FrameInterpMode>(i);
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableFrameInterpolation.setValue(static_cast<FrameInterpMode>(i));
android::update_surface_frame_rate();
config::save();
});
}
pane.add_rml(kUnlockFramerateHelpText);
});
config_int_select(leftPane, rightPane, getSettings().video.maxFrameRate,
"Framerate Cap", "Limit the framerate to the specified value.", 30, 540, 1,
[] { return getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Capped; },
[](int) { android::update_surface_frame_rate(); });
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
{
.key = "Enable Mini-Map Shadows",
.helpText = "Render a thick shadow around the mini-map. May impact performance."
});
config_bool_select(leftPane, rightPane, getSettings().game.disableCutscenePillarboxing,
{
.key = "Disable Cutscene Pillarboxing",
.helpText = "Disable black bars on the left and right sides of the screen "
"during some cutscenes, particularly on ultra-wide displays. "
"Visuals beyond the original intended framing may appear buggy."
});
});
@@ -829,83 +895,113 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
});
};
leftPane.add_section("Controller");
leftPane.register_control(leftPane.add_button("Configure Controller").on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>(mPrelaunch));
leftPane.add_section("Inputs");
leftPane.register_control(leftPane.add_button("Configure Inputs").on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>());
}),
rightPane, [](Pane& pane) {
pane.clear();
pane.add_text("Open controller binding configuration.");
pane.add_text("Open input binding configuration.");
});
config_bool_select(leftPane, rightPane, getSettings().game.allowBackgroundInput,
{
.key = "Allow Background Input",
.helpText = "Allow controller input even when the game window is not focused.",
.key = "Allow Background Inputs",
.helpText = "Allow inputs even when the game window is not focused.",
.onChange = [](bool value) { aurora_set_background_input(value); },
});
leftPane.add_section("Camera");
addOption("Free Camera", getSettings().game.freeCamera,
"Enables twin-stick camera control, letting the C-Stick move the camera vertically as "
"well as horizontally.");
addOption("Invert Camera X Axis", getSettings().game.invertCameraXAxis,
"Invert horizontal camera movement.");
addOption("Invert Camera Y Axis", getSettings().game.invertCameraYAxis,
"Invert vertical camera movement when Free Camera is enabled.",
[] { return !getSettings().game.freeCamera; });
config_percent_select(leftPane, rightPane, getSettings().game.freeCameraSensitivity,
"Free Camera Sensitivity", "Adjusts twin-stick camera sensitivity.", 50, 200, 5,
[] { return !getSettings().game.freeCamera; });
addOption("Invert First Person X Axis", getSettings().game.invertFirstPersonXAxis,
"Invert horizontal movement while aiming with items or first person camera. Applies only to the control stick (the gyroscope can be inverted in Input settings).");
addOption("Invert First Person Y Axis", getSettings().game.invertFirstPersonYAxis,
"Invert vertical movement while aiming with items or first person camera. Applies only to the control stick (the gyroscope can be inverted in Input settings).");
leftPane.add_section("Gyro");
leftPane.register_control(
leftPane.add_select_button({
.key = "Gyro Input Method",
.getValue =
[] {
const auto mode = getSettings().game.gyroMode.getValue();
const auto idx = static_cast<size_t>(mode);
return Rml::String{kGyroInputModeLabels[idx]};
},
.isModified =
[] {
return getSettings().game.gyroMode.getValue() !=
getSettings().game.gyroMode.getDefaultValue();
},
}),
#if TOUCH_CONTROLS_AVAILABLE
leftPane.add_section("Touch");
addOption("Touch Controls", getSettings().game.enableTouchControls,
"Enables controls overlay for touch screens.<br/><br/>Press and drag on the left side "
"of the screen to move, and on the right side of the screen to control the camera.");
auto& customizeTouchLayout = leftPane.add_button(ControlledButton::Props{
.text = "Customize Layout",
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
});
leftPane.register_control(customizeTouchLayout.on_pressed(
[this] { push(std::make_unique<TouchControlsEditor>()); }),
rightPane, [](Pane& pane) {
for (size_t i = 0; i < kGyroInputModeLabels.size(); i++) {
pane
.add_button({
.text = Rml::String{kGyroInputModeLabels[i]},
pane.clear();
pane.add_text("Open the touch controls layout editor.");
});
leftPane.register_control(leftPane.add_select_button({
.key = "Touch Targeting",
.getValue =
[] {
return touch_targeting_label(
getSettings().game.touchTargeting.getValue());
},
.isDisabled =
[] { return !getSettings().game.enableTouchControls; },
.isModified =
[] {
const auto& targeting =
getSettings().game.touchTargeting;
return targeting.getValue() !=
targeting.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
pane.clear();
for (int i = 0; i < static_cast<int>(kTouchTargetingLabels.size()); ++i) {
pane.add_button({
.text = kTouchTargetingLabels[i],
.isSelected =
[i] {
return getSettings().game.gyroMode.getValue() == static_cast<GyroMode>(i);
return getSettings().game.touchTargeting.getValue() ==
static_cast<TouchTargeting>(i);
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
const GyroMode mode = static_cast<GyroMode>(i);
getSettings().game.gyroMode.setValue(mode);
config::Save();
getSettings().game.touchTargeting.setValue(
static_cast<TouchTargeting>(i));
config::save();
});
}
pane.add_rml(
"<br/><b>Sensor</b> reads motion directly from a supported controller's gyro via SDL.<br/>"
"<br/><b>Mouse</b> treats mouse input as gyro, intended for use with the Steam Deck.<br/>"
"<br/>Mouse input cannot currently be used with Gyro Rollgoal.");
pane.add_rml(fmt::format("<br/>Hybrid: {}<br/>Hold: {}<br/>Switch: {}",
kTouchTargetingDescriptions[0], kTouchTargetingDescriptions[1],
kTouchTargetingDescriptions[2]));
});
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraXSensitivity,
"Touch Camera X Sensitivity",
"Adjusts touch camera horizontal sensitivity.<br/><br/>Applies to touch input only.",
25, 400, 5, [] { return !getSettings().game.enableTouchControls; });
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraYSensitivity,
"Touch Camera Y Sensitivity",
"Adjusts touch camera vertical sensitivity.<br/><br/>Applies to touch input only.", 25,
400, 5, [] { return !getSettings().game.enableTouchControls; });
#endif
leftPane.add_section("Camera");
addOption("Free Camera", getSettings().game.freeCamera,
"Enables free camera control, letting you control the camera fully with the C-Stick.");
config_percent_select(leftPane, rightPane, getSettings().game.freeCameraXSensitivity,
"Free Camera X Sensitivity",
"Adjusts horizontal free camera sensitivity.<br/><br/>Applies to the control stick only.",
50, 200, 5, [] { return !getSettings().game.freeCamera; });
config_percent_select(leftPane, rightPane, getSettings().game.freeCameraYSensitivity,
"Free Camera Y Sensitivity",
"Adjusts vertical free camera sensitivity.<br/><br/>Applies to the control stick only.",
50, 200, 5, [] { return !getSettings().game.freeCamera; });
addOption("Invert Camera X Axis", getSettings().game.invertCameraXAxis,
"Invert horizontal camera movement.<br/><br/>Applies to the control stick only.");
addOption("Invert Camera Y Axis", getSettings().game.invertCameraYAxis,
"Invert vertical camera movement.<br/><br/>Applies to the control stick only.",
[] { return !getSettings().game.freeCamera; });
addOption("Invert First Person X Axis", getSettings().game.invertFirstPersonXAxis,
"Invert horizontal movement while aiming with items or first person camera.<br/><br/>Applies to the control stick only.");
addOption("Invert First Person Y Axis", getSettings().game.invertFirstPersonYAxis,
"Invert vertical movement while aiming with items or first person camera.<br/><br/>Applies to the control stick only.");
leftPane.add_section("Gyro");
addOption("Gyro Aim", getSettings().game.enableGyroAim,
"Enables gyro controls while in look mode, aiming a hawk, and aiming "
"supported items.<br/><br/>Supported items include the Slingshot, Gale Boomerang, "
"Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod.");
addOption("Gyro Rollgoal", getSettings().game.enableGyroRollgoal,
"Enables gyro controls for Rollgoal in Hena's Cabin.",
[] { return getSettings().game.gyroMode.getValue() == GyroMode::Mouse; });
"Enables gyro controls for Rollgoal in Hena's Cabin.");
config_percent_select(leftPane, rightPane, getSettings().game.gyroSensitivityY,
"Gyro Pitch Sensitivity", "Controls vertical gyro aiming sensitivity.", 25, 400, 5,
[] { return !gyro_enabled(); });
@@ -915,10 +1011,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
config_percent_select(leftPane, rightPane, getSettings().game.gyroSensitivityRollgoal,
"Rollgoal Sensitivity", "Controls how strongly gyro input tilts the Rollgoal table.",
25, 400, 5,
[] {
return !getSettings().game.enableGyroRollgoal ||
getSettings().game.gyroMode.getValue() == GyroMode::Mouse;
});
[] { return !getSettings().game.enableGyroRollgoal; });
config_percent_select(leftPane, rightPane, getSettings().game.gyroDeadband, "Gyro Deadband",
"Ignores small gyro movement to reduce drift and jitter.", 0, 50, 1,
[] { return !gyro_enabled(); });
@@ -930,10 +1023,37 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addOption("Invert Gyro Yaw", getSettings().game.gyroInvertYaw,
"Invert horizontal gyro aiming.", [] { return !gyro_enabled(); });
leftPane.add_section("Mouse");
addOption("Mouse Aim", getSettings().game.enableMouseAim,
"Enables mouse input while in look mode, aiming a hawk, and aiming "
"supported items.<br/><br/>Supported items include the Slingshot, Gale Boomerang, "
"Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod.");
addOption("Mouse Camera", getSettings().game.enableMouseCamera,
"Enables mouse input for controlling the third-person camera.");
config_percent_select(leftPane, rightPane, getSettings().game.mouseAimSensitivity,
"Mouse Aim Sensitivity", "Controls mouse aim sensitivity.", 25, 400, 5,
[] { return !getSettings().game.enableMouseAim; });
config_percent_select(leftPane, rightPane, getSettings().game.mouseCameraSensitivity,
"Mouse Camera Sensitivity", "Controls mouse camera sensitivity.", 25, 400, 5,
[] { return !getSettings().game.enableMouseCamera; });
addOption("Invert Mouse Y", getSettings().game.invertMouseY,
"Invert vertical mouse control for both aiming and camera.",
[] { return !getSettings().game.enableMouseAim || !getSettings().game.enableMouseCamera; });
leftPane.add_section("Gameplay");
addOption("Mouse/Touch in Menus", getSettings().game.enableMenuPointer,
"Enables mouse and touch input for supported in-game menus.");
addOption("Invert Air/Swim X Axis", getSettings().game.invertAirSwimX,
"Invert horizontal movement while flying or swimming.");
addOption("Invert Air/Swim Y Axis", getSettings().game.invertAirSwimY,
"Invert vertical movement while flying or swimming.");
addOption("Swap Direct Select Input", getSettings().game.swapDirectSelect,
"Swap the controls for using Direct Select on the item wheel, making Direct Select the default and holding L to scroll the wheel.");
leftPane.add_section("Tools");
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
"Hold Tab to increase game speed by up to 4x.",
[] { return getSettings().game.speedrunMode; });
[] { return getSettings().game.speedrunMode.getValue(); });
addOption("Reset Key (" + Rml::String{hotkeys::DO_RESET} + ")",
getSettings().game.enableResetKeybind,
"Press " + Rml::String{hotkeys::DO_RESET} + " to reset the game.");
@@ -952,7 +1072,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.setValue =
[](int value) {
getSettings().audio.masterVolume.setValue(value);
config::Save();
config::save();
audio::SetMasterVolume(audio::MasterVolumeToLinear(value / 100.0f));
},
.isModified =
@@ -1024,10 +1144,17 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addOption("Minimal HUD", getSettings().game.minimalHUD,
"Disables the elements of the main HUD of the game.<br/>Useful for a more immersive "
"experience.");
config_percent_select(leftPane, rightPane, getSettings().game.hudScale,
"HUD Scale",
"Scales the size of the gameplay HUD (hearts, buttons, mini-map, etc.). Does not affect dialog boxes or menus.",
50, 200, 5,
[] { return getSettings().game.minimalHUD.getValue(); });
addOption("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches,
"Restores patched glitches from Wii USA 1.0, the first released version.");
addOption("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation,
"Enables rotating Link in the collection menu with the C-Stick.");
addOption("Hide Owl Statue Markers", getSettings().game.removeQuestMapMarkers,
"Removes completed Owl Statue markers from the map and Minimap.");
leftPane.add_section("Difficulty");
leftPane.register_control(
@@ -1037,9 +1164,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.setValue =
[](int value) {
getSettings().game.damageMultiplier.setValue(value);
config::Save();
config::save();
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
.isModified =
[] {
return getSettings().game.damageMultiplier.getValue() !=
@@ -1082,6 +1209,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
"Link will not recoil when his sword hits walls.");
addOption("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat,
"Skip needing to catch a second fish for Sera's cat.");
addOption("Button Fishing", getSettings().game.buttonFishing,
"Allow fishing with the Fishing Rod using the button the item is assigned to.");
addOption("Show Poe Count on Map", getSettings().game.enhancedMapMenus,
"Displays collected/total number of Poe Souls for a region on the map.");
addSpeedrunDisabledOption("Sun's Song (R+X)", getSettings().game.sunsSong,
"Allows Wolf Link to howl and change the time of day.");
addOption("Quick Transform (R+Y)", getSettings().game.enableQuickTransform,
@@ -1096,19 +1227,14 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.onChange =
[](bool enabled) {
if (enabled) {
reset_for_speedrun_mode();
resetForSpeedrunMode();
} else {
restore_from_speedrun_mode();
restoreFromSpeedrunMode();
if (getSettings().game.liveSplitEnabled) {
speedrun::disconnectLiveSplit();
}
}
for (auto& doc : get_document_stack()) {
if (dynamic_cast<MenuBar*>(doc.get())) {
doc = std::make_unique<MenuBar>();
break;
}
}
MenuBar::rebuild();
},
});
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
@@ -1172,8 +1298,38 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
"Makes Link's roll animation and movement twice as fast.");
addCheat("Fast Spinner", getSettings().game.fastSpinner,
"Speeds up Spinner movement while holding R.");
addCheat("Free Magic Armor", getSettings().game.freeMagicArmor,
"Lets the magic armor work without consuming rupees.");
leftPane.register_control(
leftPane.add_select_button({
.key = "Magic Armor Behavior",
.getValue =
[] {
return kMagicArmorModes[static_cast<u8>(getSettings().game.armorRupeeDrain.getValue())];
},
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
.isModified =
[] {
return getSettings().game.armorRupeeDrain.getValue() !=
getSettings().game.armorRupeeDrain.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < kMagicArmorModes.size(); i++) {
pane.add_button({
.text = kMagicArmorModes[i],
.isSelected =
[i] {
return getSettings().game.armorRupeeDrain.getValue() == static_cast<MagicArmorMode>(i);
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.armorRupeeDrain.setValue(static_cast<MagicArmorMode>(i));
config::save();
});
}
pane.add_rml(
"<br/>Control the behavior of the Magic Armor.");
});
addCheat("Invincible Enemies", getSettings().game.invincibleEnemies,
"Prevents enemies from taking damage.");
});
@@ -1221,13 +1377,13 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableAchievementToasts.setValue(true);
getSettings().game.enableControllerToasts.setValue(true);
config::Save();
config::save();
});
pane.add_button("Select None").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.enableAchievementToasts.setValue(false);
getSettings().game.enableControllerToasts.setValue(false);
config::Save();
config::save();
});
pane.add_section("Types");
@@ -1243,11 +1399,11 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
auto& v = getSettings().game.enableAchievementToasts;
v.setValue(!v.getValue());
config::Save();
config::save();
});
pane.add_button(
{
.text = "Controller",
.text = "Missing Device",
.isSelected =
[] { return getSettings().game.enableControllerToasts.getValue(); },
})
@@ -1255,7 +1411,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
mDoAud_seStartMenu(kSoundItemChange);
auto& v = getSettings().game.enableControllerToasts;
v.setValue(!v.getValue());
config::Save();
config::save();
});
pane.add_rml("<br/>Choose which notifications can be displayed.");
});
@@ -1285,11 +1441,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.helpText = "When starting Dusklight, skip the main menu and boot straight into the "
"game if a disc image is available.",
});
config_bool_select(leftPane, rightPane, getSettings().backend.showPipelineCompilation,
{
.key = "Show Pipeline Compilation",
.helpText = "Show an overlay when shaders are being compiled for your hardware.",
});
config_bool_select(leftPane, rightPane, getSettings().backend.checkForUpdates,
{
.key = "Check for Updates",
@@ -1317,16 +1468,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.helpText = "Show advanced settings and debugging tools with "
"Shift+F1.<br/><br/><icon class=\"warning\"/> WARNING: Debugging tools "
"can easily break your game. Do not use on a regular save!",
.onChange =
[](bool) {
for (auto& doc : get_document_stack()) {
if (dynamic_cast<MenuBar*>(doc.get())) {
doc = std::make_unique<MenuBar>();
break;
}
}
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.onChange = [](bool) { MenuBar::rebuild(); },
.isDisabled = [] { return getSettings().game.speedrunMode.getValue(); },
});
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
{
@@ -1339,8 +1482,42 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.helpText = "Show gyro sensor values in the input viewer.",
.isDisabled = [] { return !getSettings().game.showInputViewer; },
});
leftPane.add_section("Game");
leftPane.register_control(
leftPane.add_select_button({
.key = "Menu Scaling Mode",
.getValue =
[] {
return kMenuScalingModeLabels[static_cast<u8>(
getSettings().game.menuScalingMode.getValue())];
},
.isModified =
[] {
const auto& mode = getSettings().game.menuScalingMode;
return mode.getValue() != mode.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < static_cast<int>(kMenuScalingModeLabels.size()); ++i) {
pane
.add_button({
.text = kMenuScalingModeLabels[i],
.isSelected =
[i] {
return getSettings().game.menuScalingMode.getValue() ==
static_cast<MenuScaling>(i);
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.menuScalingMode.setValue(
static_cast<MenuScaling>(i));
config::save();
});
}
pane.add_rml("<br/>Changes how the Collection and File Select menus scale to your "
"aspect ratio.");
});
config_bool_select(leftPane, rightPane, getSettings().game.hideTvSettingsScreen,
{
.key = "Skip TV Settings Screen",
@@ -1360,4 +1537,9 @@ void SettingsWindow::update() {
Window::update();
}
void SettingsWindow::hide(bool close) {
config::save();
Window::hide(close);
}
} // namespace dusk::ui