Merge branch 'main' of https://github.com/TakaRikka/dusk into phong

This commit is contained in:
madeline
2026-05-12 20:37:35 -07:00
33 changed files with 784 additions and 199 deletions
+4
View File
@@ -41,6 +41,10 @@ compile_commands.json
# MacOS
.DS_Store
# direnv / nix
.direnv/
.envrc
# ISOs
*.iso
+2
View File
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
)
set(DUSK_FILES
include/dusk/action_bindings.h
include/dusk/endian_gx.hpp
include/dusk/config.hpp
include/dusk/dvd_asset.hpp
@@ -1524,6 +1525,7 @@ set(DUSK_FILES
src/dusk/discord.hpp
src/dusk/discord_presence.cpp
src/dusk/version.cpp
src/dusk/action_bindings.cpp
)
set(DUSK_HTTP_BACKEND_FILES
+205 -94
View File
@@ -4,101 +4,212 @@
};
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
# Dependencies that are not packaged in nixpkgs:
aurora-src = pkgs.fetchFromGitHub {
owner = "encounter";
repo = "aurora";
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
};
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
stripRoot = false;
};
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
# Dusklight Actual
dusklight = pkgs.stdenv.mkDerivation {
name = "dusklight";
src = ./.;
postUnpack = ''
mkdir -p $sourceRoot/extern/aurora
cp -r ${aurora-src}/. $sourceRoot/extern/aurora/
chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
cmakeFlags = [
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${dawn-src}"
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
"-DAURORA_SDL3_PROVIDER=system"
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${nod-src}"
"-DAURORA_NOD_PROVIDER=package"
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${sqlite-src}"
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${imgui-src}"
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${rmlui-src}"
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
];
installPhase = ''
mkdir -p $out/bin
cp dusklight $out/bin/dusklight
cp -r ./res $out/bin/res
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = system: import nixpkgs { inherit system; };
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
buildSources = pkgs: {
aurora-src = pkgs.fetchFromGitHub {
owner = "encounter";
repo = "aurora";
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
};
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
stripRoot = false;
};
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
};
# Dusklight Actual (Linux x86_64 only — relies on prebuilt dawn/nod binaries)
mkDusklight = pkgs:
let srcs = buildSources pkgs; in
pkgs.stdenv.mkDerivation {
name = "dusklight";
src = ./.;
postUnpack = ''
mkdir -p $sourceRoot/extern/aurora
cp -r ${srcs.aurora-src}/. $sourceRoot/extern/aurora/
chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
cmakeFlags = [
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${srcs.dawn-src}"
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
"-DAURORA_SDL3_PROVIDER=system"
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${srcs.nod-src}"
"-DAURORA_NOD_PROVIDER=package"
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${srcs.sqlite-src}"
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${srcs.imgui-src}"
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${srcs.rmlui-src}"
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
];
installPhase = ''
mkdir -p $out/bin
cp dusklight $out/bin/dusklight
cp -r ./res $out/bin/res
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
pkgs.wayland
];
buildInputs = [
pkgs.libGL
pkgs.libX11
pkgs.libXcursor
pkgs.libxi
pkgs.libxcb
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
};
# Tooling common to every supported host (Linux and macOS).
commonDevTools = pkgs: [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.git
pkgs.python3
pkgs.python3Packages.markupsafe
pkgs.rustc
pkgs.cargo
pkgs.sccache
];
# Linux-only system libraries — mirrors the apt deps from .github/workflows/build.yml
# so the cmake presets resolve the same set of headers as CI.
linuxDevDeps = pkgs: [
# Compilers / linkers
pkgs.clang
pkgs.lld
# C/C++ utilities
pkgs.curl
pkgs.openssl
pkgs.zlib
pkgs.libpng
pkgs.libjpeg_turbo
pkgs.freetype
pkgs.zstd
pkgs.fmt
pkgs.tracy
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.ncurses
pkgs.libunwind
pkgs.libusb1
pkgs.fuse
# Wayland / display server
pkgs.wayland
pkgs.wayland-protocols
pkgs.libxkbcommon
pkgs.libdecor
# OpenGL / Vulkan
pkgs.libGL
pkgs.libGLU
pkgs.libglvnd
pkgs.vulkan-headers
pkgs.vulkan-loader
# X11
pkgs.libX11
pkgs.libxcb
pkgs.libXcursor
pkgs.libxi
pkgs.libxrandr
pkgs.libxscrnsaver
pkgs.libxtst
pkgs.libxinerama
# Audio
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.pipewire
# System integration
pkgs.dbus
pkgs.udev
pkgs.gtk3
];
# On macOS we deliberately avoid pulling Nix's cc-wrapper so CMake picks up
# Apple Clang and the Xcode SDK directly, matching the macOS CI workflow.
mkDarwinShell = pkgs:
pkgs.mkShellNoCC {
packages = commonDevTools pkgs;
shellHook = ''
echo "Dusklight dev shell (macOS)"
echo "Requires Xcode Command Line Tools for Apple Clang and the macOS SDK."
echo "Configure: cmake --preset macos-default-relwithdebinfo"
echo "Build: cmake --build --preset macos-default-relwithdebinfo"
'';
};
mkLinuxShell = pkgs:
pkgs.mkShell {
packages = (commonDevTools pkgs) ++ (linuxDevDeps pkgs);
shellHook = ''
echo "Dusklight dev shell (Linux)"
echo "Configure: cmake --preset linux-default-relwithdebinfo"
echo " cmake --preset linux-clang-relwithdebinfo"
echo "Build: cmake --build --preset <preset>"
'';
};
mkDevShell = pkgs:
if pkgs.stdenv.isDarwin
then mkDarwinShell pkgs
else mkLinuxShell pkgs;
in {
packages.x86_64-linux.default = dusklight;
packages.x86_64-linux.default = mkDusklight (pkgsFor "x86_64-linux");
devShells = forAllSystems (system: {
default = mkDevShell (pkgsFor system);
});
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <unordered_map>
#include "dusk/config_var.hpp"
namespace dusk {
enum class ActionBinds {
FIRST_PERSON_CAMERA,
CALL_MIDNA,
OPEN_DUSKLIGHT_MENU,
TURBO_SPEED_BUTTON,
COUNT,
};
struct ActionBindData {
std::array<config::ActionBindConfigVar, 4>* configVars{};
std::string actionName{};
};
struct ActionBindPressData {
bool pressedCurFrame{false};
bool pressedPrevFrame{false};
};
using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
ActionBindsMap& getActionBinds();
bool isActionBound(ActionBinds action, u32 port);
void updateActionBindings();
bool getActionBindTrig(ActionBinds action, u32 port);
bool getActionBindHold(ActionBinds action, u32 port);
bool getActionBindHoldAnyPort(ActionBinds action);
int getActionBindButton(ActionBinds action, u32 port);
}
+1
View File
@@ -13,5 +13,6 @@ void enterAutoSave();
void autoSaving();
void waitingForWrite();
void endAutoSave();
void toggleAutoSave(bool enabled);
#endif
+7
View File
@@ -112,6 +112,13 @@ void Save();
*/
ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
+2
View File
@@ -287,6 +287,8 @@ public:
}
};
using ActionBindConfigVar = ConfigVar<int>;
}
#endif // DUSK_CONFIG_VAR_HPP
+14
View File
@@ -1,6 +1,8 @@
#ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H
#include <array>
#include "dusk/config_var.hpp"
namespace dusk {
@@ -115,6 +117,7 @@ struct UserSettings {
ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementToasts;
ConfigVar<bool> enableControllerToasts;
ConfigVar<bool> enableDiscordPresence;
// Graphics
ConfigVar<bool> enhancedLighting;
@@ -181,12 +184,15 @@ struct UserSettings {
// Controls
ConfigVar<bool> enableTurboKeybind;
ConfigVar<bool> enableResetKeybind;
// Tools
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
} game;
struct {
@@ -200,6 +206,14 @@ struct UserSettings {
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
};
UserSettings& getSettings();
+7
View File
@@ -4,6 +4,10 @@
#include <cmath>
#include "os_report.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
u32 JUTGamePad::CRumble::sChannelMask[4] = {
PAD_CHAN0_BIT,
PAD_CHAN1_BIT,
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
u32 JUTGamePad::read() {
sRumbleSupported = PADRead(mPadStatus);
#if TARGET_PC
dusk::updateActionBindings();
#endif
switch (sClampMode) {
case EClampStick:
+9
View File
@@ -51,10 +51,13 @@
#include "d/actor/d_a_ni.h"
#include "d/d_s_play.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#include "res/Object/Alink.h"
#include <cstring>
#endif
static int daAlink_Create(fopAc_ac_c* i_this);
static int daAlink_Delete(daAlink_c* i_this);
@@ -9363,6 +9366,12 @@ BOOL daAlink_c::spActionTrigger() {
}
BOOL daAlink_c::midnaTalkTrigger() const {
#if TARGET_PC
// If we have a custom bind for Midna, check that instead
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
}
#endif
return mItemTrigger & BTN_Z;
}
+6 -3
View File
@@ -12,6 +12,7 @@
#if TARGET_PC
#include "dusk/gyro.h"
#include "dusk/action_bindings.h"
#endif
bool daAlink_c::checkNoSubjectModeCamera() {
@@ -144,8 +145,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
f32 gy_pitch = 0.f;
dusk::gyro::getAimDeltas(gy_yaw, gy_pitch);
shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale * (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f));
sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale * (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f));
shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale);
sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale);
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
setDoStatus(BUTTON_STATUS_BACK);
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
// Allow pressing the first person binding to also leave first person
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
if (i_isPlaySe) {
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
}
+3
View File
@@ -13,6 +13,7 @@
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/speedrun.h"
#include "m_Do/m_Do_controller_pad.h"
#include <dusk/autosave.h>
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
mArchive = i_archive;
@@ -151,6 +152,8 @@ void dBrightCheck_c::modeMove() {
dusk::m_speedrunInfo.startRun();
}
}
toggleAutoSave(true);
#endif
mCompleteCheck = true;
mMode = MODE_WAIT_e;
+15 -5
View File
@@ -31,6 +31,7 @@
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/logging.h"
#include "dusk/action_bindings.h"
#include "imgui.h"
#endif
@@ -838,6 +839,12 @@ void dCamera_c::updatePad() {
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
#if TARGET_PC
// If our custom action binding is triggered, and we're not already in first person, go into first person
if (dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mGear != -1) {
setComStat(0x1000);
mGear = 0;
}
if (mCamParam.mManualMode) {
return;
}
@@ -877,7 +884,8 @@ void dCamera_c::updatePad() {
if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != -1) {
if (mGear == -1 && mCurMode == 4) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == -1 && mCurMode == 4 IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
mGear = 0;
setComStat(0x2000);
} else if (mGear == 0 && sp6C) {
@@ -888,7 +896,8 @@ void dCamera_c::updatePad() {
mCStickYState = -1;
} else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != 1) {
if (mGear == 0 && sp6B) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == 0 && sp6B IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
setComStat(0x1000);
} else if (mGear == 1) {
mGear = 0;
@@ -7649,9 +7658,10 @@ bool dCamera_c::freeCamera() {
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
// If we aren't in manual cam mode, don't trigger it if the player tries to hit C-up
// for first person
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 ||
(mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0)) {
// for first person unless they have first person bound to a custom binding
if ((dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mPadInfo.mCStick.mLastPosY != 0) ||
mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 || (mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0))
{
mCamParam.mManualMode = 1;
camMovement = camMovement.normalize();
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
+3
View File
@@ -20,6 +20,7 @@
#include "m_Do/m_Do_machine.h"
#include "m_Do/m_Do_main.h"
#include "m_Do/m_Do_mtx.h"
#include <dusk/autosave.h>
#if TARGET_PC
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
@@ -423,6 +424,8 @@ void dScnName_c::changeGameScene() {
dusk::m_speedrunInfo.startRun();
}
}
toggleAutoSave(true);
#endif
}
}
+4
View File
@@ -1042,6 +1042,10 @@ static BOOL heapSizeCheck() {
bool dScnPly_c::resetGame() {
if (fpcM_GetName(this) == fpcNm_OPENING_SCENE_e) {
#if TARGET_PC
toggleAutoSave(false);
#endif
if (!dStage_roomControl_c::resetArchiveBank(0)) {
return false;
}
+5
View File
@@ -53,6 +53,11 @@ static std::string FormatToString(const char* msg, va_list list) {
size *= 2;
}
}
while (!str.empty() && str[str.size()-1] == '\n') {
str.pop_back();
}
return str;
}
+6
View File
@@ -692,6 +692,12 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
return;
}
// prevent stuff like https://github.com/TwilitRealm/dusklight/issues/949
if (link->getDemoMode() != 0) {
inJump = false;
return;
}
if (!inJump) {
if (link->mProcID == daAlink_c::PROC_CUT_JUMP) {
inJump = true;
+96
View File
@@ -0,0 +1,96 @@
#include "dusk/action_bindings.h"
#include "aurora/lib/input.hpp"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
namespace dusk {
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
ActionBindsMap& getActionBinds() {
static ActionBindsMap actionBinds = {
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
};
return actionBinds;
}
bool isActionBound(ActionBinds action, u32 port) {
auto& actionBinds = getActionBinds();
// Check to make sure action is properly bound
if (!actionBinds.contains(action)) {
return false;
}
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
}
void updateActionBindings() {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
// Move the current press to the previous frame
for (auto& pressData : actionPressData[port]) {
pressData.pressedPrevFrame = pressData.pressedCurFrame;
pressData.pressedCurFrame = false;
}
// Update current frame with whether action button is pressed
for (auto& [action, boundAction] : getActionBinds()) {
// If the action isn't bound, or if documents are visible and the action isn't
// opening the dusklight menu, don't update. Otherwise, we may accidentally
// perform actions while the dusklight menu is open.
if (!isActionBound(action, port) ||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
continue;
}
int button = boundAction.configVars->at(port);
// If keyboard is active for this port
u32 count = 0;
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
int numKeys = 0;
const bool* kbState = SDL_GetKeyboardState(&numKeys);
if (kbState[button]) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
} else {
// If controller is active
auto controller = aurora::input::get_controller_for_player(port);
if (controller) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
}
}
}
}
}
bool getActionBindTrig(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHold(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHoldAnyPort(ActionBinds action) {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
if (getActionBindHold(action, port)) {
return true;
}
}
return false;
}
int getActionBindButton(ActionBinds action, u32 port) {
return (*getActionBinds()[action].configVars)[port];
}
}
+6 -1
View File
@@ -2,6 +2,7 @@
#include "dusk/ui/ui.hpp"
#include "imgui/ImGuiConsole.hpp"
bool shouldAutoSave = false;
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0;
@@ -14,7 +15,7 @@ static AutoSaveFuncs AutoSaveFuncsProc[] = {
void noAutoSave() {}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 &&
if (dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
{
mAutoSaveProc = 1;
@@ -89,4 +90,8 @@ void endAutoSave() {
.duration = std::chrono::milliseconds(1500),
});
mAutoSaveProc = 0;
}
void toggleAutoSave(bool enabled) {
shouldAutoSave = enabled;
}
+8
View File
@@ -11,6 +11,7 @@
#include <string>
#include "dusk/main.h"
#include "dusk/action_bindings.h"
using namespace dusk::config;
@@ -256,6 +257,13 @@ void dusk::config::Save() {
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
}
void dusk::config::ClearAllActionBindings(int port) {
for (auto& actionBinding : getActionBinds() | std::views::values) {
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
}
Save();
}
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
const auto configVar = RegisteredConfigVars.find(name);
if (configVar != RegisteredConfigVars.end()) {
+9 -1
View File
@@ -13,6 +13,7 @@
#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
#include "dusk/action_bindings.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/data.hpp"
@@ -239,7 +240,8 @@ namespace dusk {
}
void ImGuiConsole::UpdateSettings() {
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab);
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
@@ -260,6 +262,12 @@ namespace dusk {
config::Save();
}
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
ImGui::IsKeyPressed(ImGuiKey_R) && !fpcM_SearchByName(fpcNm_LOGO_SCENE_e))
{
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
if (getSettings().backend.enableAdvancedSettings) {
m_isHidden = !m_isHidden;
+5 -4
View File
@@ -3,12 +3,13 @@
#include "imgui.h"
#include <imgui_internal.h>
#include "ImGuiConsole.hpp"
#include "dusk/settings.h"
#include <dolphin/pad.h>
namespace dusk {
void ImGuiMenuTools::ShowInputViewer() {
if (!m_showInputViewer) {
if (!getSettings().game.showInputViewer) {
return;
}
@@ -259,10 +260,10 @@ namespace dusk {
size.y = 130 * scale;
ImGui::Dummy(size);
if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) {
if (getSettings().game.showInputViewerGyro)
{
ImGui::Separator();
ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro);
if (m_showInputViewerGyro) {
{
ImGui::TextUnformatted("Gyro");
constexpr float kBarScale = 4.0f;
-3
View File
@@ -51,9 +51,6 @@ namespace dusk {
ImGui::EndDisabled();
}
ImGui::Separator();
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
#if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator();
if (ImGui::MenuItem("Open Data Folder")) {
-2
View File
@@ -71,8 +71,6 @@ namespace dusk {
bool m_showStateShare = false;
ImGuiStateShare m_stateShare;
bool m_showInputViewer = false;
bool m_showInputViewerGyro = false;
bool m_showActorSpawner = false;
int m_inputOverlayCorner = 3;
std::string m_controllerName;
+3
View File
@@ -18,6 +18,7 @@
#include <unordered_set>
#include <zstd.h>
#include <dusk/autosave.h>
namespace dusk {
@@ -135,6 +136,8 @@ bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::s
return false;
}
toggleAutoSave(false);
StateSharePacket pkt;
memcpy(&pkt, raw.data(), sizeof(pkt));
pkt.stageName[7] = '\0';
+12 -10
View File
@@ -17,6 +17,8 @@ using namespace dusk::io;
#else
#define MODE(val) val
#endif
#define _SH_DENYNO 0
#define _SH_DENYWR 0
#endif
static FILE* ThrowIfNotOpen(const FileStream& file) {
@@ -31,19 +33,19 @@ static FILE* ThrowIfNotOpen(const FileStream& file) {
throw std::system_error(std::make_error_code(static_cast<std::errc>(code)));
}
static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode) {
static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode, int shareFlag) {
FILE* file;
int err;
errno = 0;
#if _WIN32
static_assert(std::is_same_v<std::filesystem::path::value_type, wchar_t>);
err = _wfopen_s(&file, path.c_str(), mode);
file = _wfsopen(path.c_str(), mode, shareFlag);
#else
errno = 0;
static_assert(std::is_same_v<std::filesystem::path::value_type, char>);
file = fopen(path.c_str(), mode);
err = errno;
#endif
err = errno;
if (!file) {
ThrowForError(err);
@@ -52,8 +54,8 @@ static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode)
return file;
}
static FILE* OpenCore(const char* path, const MODE_TYPE* mode) {
return OpenCore(reinterpret_cast<const char8_t*>(path), mode);
static FILE* OpenCore(const char* path, const MODE_TYPE* mode, int shareFlag) {
return OpenCore(reinterpret_cast<const char8_t*>(path), mode, shareFlag);
}
FileStream::FileStream() noexcept : file(nullptr) {
@@ -76,19 +78,19 @@ FileStream::~FileStream() {
}
FileStream FileStream::OpenRead(const char* utf8Path) {
return FileStream(OpenCore(utf8Path, MODE("rb")));
return FileStream(OpenCore(utf8Path, MODE("rb"), _SH_DENYWR));
}
FileStream FileStream::OpenRead(const std::filesystem::path& path) {
return FileStream(OpenCore(path, MODE("rb")));
return FileStream(OpenCore(path, MODE("rb"), _SH_DENYWR));
}
FileStream FileStream::Create(const char* utf8Path) {
return FileStream(OpenCore(utf8Path, MODE("wb")));
return FileStream(OpenCore(utf8Path, MODE("wb"), _SH_DENYWR));
}
FileStream FileStream::Create(const std::filesystem::path& path) {
return FileStream(OpenCore(path, MODE("wb")));
return FileStream(OpenCore(path, MODE("wb"), _SH_DENYWR));
}
std::vector<u8> FileStream::ReadFull() {
+58 -1
View File
@@ -52,6 +52,7 @@ UserSettings g_userSettings = {
.enableLinkDollRotation {"game.enableLinkDollRotation", false},
.enableAchievementToasts {"game.enableAchievementToasts", true},
.enableControllerToasts {"game.enableControllerToasts", true},
.enableDiscordPresence {"game.enableDiscordPresence", true},
// Graphics
.enhancedLighting {"game.enhancedLighting", false},
@@ -118,12 +119,15 @@ UserSettings g_userSettings = {
// Controls
.enableTurboKeybind {"game.enableTurboKeybind", false},
.enableResetKeybind {"game.enableResetKeybind", false},
// Tools
.speedrunMode {"game.speedrunMode", false},
.liveSplitEnabled {"game.liveSplitEnabled", false},
.showSpeedrunRTATimer {"game.showSpeedrunRTATimer", true},
.recordingMode {"game.recordingMode", false}
.recordingMode {"game.recordingMode", false},
.showInputViewer {"game.showInputViewer", false},
.showInputViewerGyro {"game.showInputViewerGyro", false}
},
.backend = {
@@ -136,6 +140,34 @@ UserSettings g_userSettings = {
.checkForUpdates {"backend.checkForUpdates", true},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)},
.enableAdvancedSettings {"backend.enableAdvancedSettings", false},
},
// Not sure if there's a better way to declare this
.actionBindings = {
.firstPersonCamera {
ActionBindConfigVar{"actionBindings.firstPersonCamera_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port3", PAD_NATIVE_BUTTON_INVALID},
},
.callMidna {
ActionBindConfigVar{"actionBindings.callMidna_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port3", PAD_NATIVE_BUTTON_INVALID},
},
.openDusklightMenu {
ActionBindConfigVar{"actionBindings.openDusklightMenu_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port3", PAD_NATIVE_BUTTON_INVALID},
},
.turboSpeedButton {
ActionBindConfigVar{"actionBindings.turboButton_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port3", PAD_NATIVE_BUTTON_INVALID},
},
}
};
@@ -187,6 +219,9 @@ void registerSettings() {
Register(g_userSettings.game.freeCameraSensitivity);
Register(g_userSettings.game.minimalHUD);
Register(g_userSettings.game.pauseOnFocusLost);
Register(g_userSettings.game.enableDiscordPresence);
// Enhanced lighting
Register(g_userSettings.game.enhancedLighting);
Register(g_userSettings.game.enableSpecularLighting);
Register(g_userSettings.game.enableRimLighting);
@@ -194,6 +229,8 @@ void registerSettings() {
Register(g_userSettings.game.rimIntensity);
Register(g_userSettings.game.ambientLightMultiplier);
Register(g_userSettings.game.diffuseLightMultiplier);
Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier);
Register(g_userSettings.game.disableWaterRefraction);
@@ -214,10 +251,13 @@ void registerSettings() {
Register(g_userSettings.game.noLowHpSound);
Register(g_userSettings.game.midnasLamentNonStop);
Register(g_userSettings.game.enableTurboKeybind);
Register(g_userSettings.game.enableResetKeybind);
Register(g_userSettings.game.speedrunMode);
Register(g_userSettings.game.liveSplitEnabled);
Register(g_userSettings.game.showSpeedrunRTATimer);
Register(g_userSettings.game.recordingMode);
Register(g_userSettings.game.showInputViewer);
Register(g_userSettings.game.showInputViewerGyro);
Register(g_userSettings.game.fastSpinner);
Register(g_userSettings.game.infiniteHearts);
Register(g_userSettings.game.infiniteArrows);
@@ -254,6 +294,23 @@ void registerSettings() {
Register(g_userSettings.backend.checkForUpdates);
Register(g_userSettings.backend.cardFileType);
Register(g_userSettings.backend.enableAdvancedSettings);
Register(g_userSettings.actionBindings.firstPersonCamera[0]);
Register(g_userSettings.actionBindings.firstPersonCamera[1]);
Register(g_userSettings.actionBindings.firstPersonCamera[2]);
Register(g_userSettings.actionBindings.firstPersonCamera[3]);
Register(g_userSettings.actionBindings.callMidna[0]);
Register(g_userSettings.actionBindings.callMidna[1]);
Register(g_userSettings.actionBindings.callMidna[2]);
Register(g_userSettings.actionBindings.callMidna[3]);
Register(g_userSettings.actionBindings.openDusklightMenu[0]);
Register(g_userSettings.actionBindings.openDusklightMenu[1]);
Register(g_userSettings.actionBindings.openDusklightMenu[2]);
Register(g_userSettings.actionBindings.openDusklightMenu[3]);
Register(g_userSettings.actionBindings.turboSpeedButton[0]);
Register(g_userSettings.actionBindings.turboSpeedButton[1]);
Register(g_userSettings.actionBindings.turboSpeedButton[2]);
Register(g_userSettings.actionBindings.turboSpeedButton[3]);
}
// Transient settings
+166 -65
View File
@@ -15,6 +15,9 @@
#include <utility>
#include <vector>
#include "dusk/action_bindings.h"
#include "dusk/config.hpp"
namespace dusk::ui {
namespace {
@@ -108,68 +111,6 @@ const std::vector<ButtonNames> kGamepadButtonNames = {
};
// clang-format on
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
if (gamepad != nullptr) {
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
case SDL_GAMEPAD_BUTTON_LABEL_A:
return "A";
case SDL_GAMEPAD_BUTTON_LABEL_B:
return "B";
case SDL_GAMEPAD_BUTTON_LABEL_X:
return "X";
case SDL_GAMEPAD_BUTTON_LABEL_Y:
return "Y";
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
return "Cross";
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
return "Circle";
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
return "Triangle";
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
return "Square";
default:
break;
}
}
const SDL_GamepadType type =
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
for (const auto& buttonNames : kGamepadButtonNames) {
if (buttonNames.button != button) {
continue;
}
for (const auto& name : buttonNames.names) {
if (name.type == type) {
return name.name;
}
}
}
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return "D-pad left";
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return "D-pad right";
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return "D-pad up";
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return "D-pad down";
default:
break;
}
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
return name;
}
return "Unknown";
}
Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad) {
if (mapping.nativeAxis.nativeAxis != -1) {
Rml::String value = PADGetNativeAxisName(mapping.nativeAxis);
@@ -367,6 +308,7 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; });
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
@@ -428,6 +370,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings();
ClearAllActionBindings(port);
});
pane.add_button({
@@ -440,6 +383,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings();
ClearAllActionBindings(port);
});
const u32 controllerCount = PADCount();
@@ -461,6 +405,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port);
PADSerializeMappings();
ClearAllActionBindings(port);
});
}
break;
@@ -946,6 +891,77 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
pane.add_text("Configure your desired rumble intensities, then run a test to check how they feel.");
break;
}
case Page::Actions: {
if (keyboard_active(port)) {
auto addActionBinding = [&](auto actionBind, const std::string& key) {
pane.add_select_button(
{
.key = key,
.getValue =
[this, actionBind] {
if (mPendingActionBinding == actionBind) {
return pending_key_label();
}
return keyboard_key_name(actionBind->getValue());
},
})
.on_pressed([this, port, actionBind] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingActionBinding = actionBind;
});
};
pane.add_section("Custom Action Bindings");
pane.add_text("A key bound to any action here will REPLACE the default control for"
" that action. Only bind buttons here that aren't used anywhere else.");
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
addActionBinding(&configVars->at(port), actionName);
}
break;
}
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
pane.add_section("Custom Action Bindings");
pane.add_text("A button bound to any action here will REPLACE the default control for"
" that action. Only bind buttons here that aren't used anywhere else. The glyphs"
" shown for in game actions will not change. This is not recommended for "
" regular Gamecube controllers.");
auto addActionBinding = [&](auto actionBind, const std::string& key) {
pane.add_select_button({
.key = key,
.getValue =
[this, gamepad, actionBind] {
if (mPendingActionBinding == actionBind) {
return pending_button_label();
}
return native_button_name(
gamepad, actionBind->getValue());
},
})
.on_pressed([this, port, actionBind] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingActionBinding = actionBind;
});
};
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
addActionBinding(&configVars->at(port), actionName);
}
break;
}
}
}
@@ -1020,12 +1036,31 @@ void ControllerConfigWindow::poll_pending_binding() {
mPendingAxisMapping->nativeButton = nativeButton;
finish_pending_binding(completedPort);
}
return;
}
if (mPendingActionBinding != nullptr) {
int button{};
if (keyboard_active(mPendingPort)) {
button = keyboard_key_pressed();
} else {
button = PADGetNativeButtonPressed(mPendingPort);
}
if (button != -1) {
const int completedPort = mPendingPort;
mPendingActionBinding->setValue(button);
config::Save();
finish_pending_binding(completedPort);
}
return;
}
}
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingActionBinding = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
@@ -1035,7 +1070,7 @@ void ControllerConfigWindow::finish_pending_binding(int completedPort) {
void ControllerConfigWindow::unmap_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
mPendingKeyButton < 0 && mPendingKeyAxis < 0)
mPendingActionBinding == nullptr && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
@@ -1048,6 +1083,9 @@ void ControllerConfigWindow::unmap_pending_binding() {
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
} else if (mPendingActionBinding != nullptr) {
mPendingActionBinding->setValue(PAD_NATIVE_BUTTON_INVALID);
finish_pending_binding(completedPort);
} else if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADButton>(mPendingKeyButton)});
@@ -1061,7 +1099,7 @@ void ControllerConfigWindow::unmap_pending_binding() {
bool ControllerConfigWindow::capture_active() const {
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr ||
mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
mPendingActionBinding != nullptr || mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
}
bool ControllerConfigWindow::pending_input_neutral() const {
@@ -1080,13 +1118,14 @@ Rml::String ControllerConfigWindow::pending_axis_label() const {
}
void ControllerConfigWindow::cancel_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr && mPendingActionBinding == nullptr &&
!mSuppressNavigationUntilNeutral && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingActionBinding = nullptr;
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
@@ -1118,4 +1157,66 @@ void ControllerConfigWindow::stop_rumble_test() {
mRumbleTestPort = -1;
}
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
if (gamepad != nullptr) {
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
case SDL_GAMEPAD_BUTTON_LABEL_A:
return "A";
case SDL_GAMEPAD_BUTTON_LABEL_B:
return "B";
case SDL_GAMEPAD_BUTTON_LABEL_X:
return "X";
case SDL_GAMEPAD_BUTTON_LABEL_Y:
return "Y";
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
return "Cross";
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
return "Circle";
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
return "Triangle";
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
return "Square";
default:
break;
}
}
const SDL_GamepadType type =
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
for (const auto& buttonNames : kGamepadButtonNames) {
if (buttonNames.button != button) {
continue;
}
for (const auto& name : buttonNames.names) {
if (name.type == type) {
return name.name;
}
}
}
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return "D-pad left";
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return "D-pad right";
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return "D-pad up";
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return "D-pad down";
default:
break;
}
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
return name;
}
return "Unknown";
}
} // namespace dusk::ui
+5
View File
@@ -1,6 +1,7 @@
#pragma once
#include "window.hpp"
#include "dusk/config_var.hpp"
#include <pad.h>
@@ -20,6 +21,7 @@ private:
Triggers,
Sticks,
Rumble,
Actions,
};
void build_port_tab(Rml::Element* content, int port);
@@ -50,6 +52,9 @@ private:
int mPendingKeyAxis = -1;
bool mRumbleTestActive = false;
int mRumbleTestPort = -1;
ActionBindConfigVar* mPendingActionBinding = nullptr;
};
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped);
} // namespace dusk::ui
+21 -5
View File
@@ -12,6 +12,8 @@
#include <algorithm>
#include <array>
#include "dusk/action_bindings.h"
namespace dusk::ui::input {
namespace {
@@ -203,6 +205,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
case SDL_GAMEPAD_BUTTON_SOUTH:
return Rml::Input::KI_RETURN;
case SDL_GAMEPAD_BUTTON_BACK:
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
return Rml::Input::KI_UNKNOWN;
}
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
@@ -216,6 +221,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
Rml::Input::KeyIdentifier map_raw_button_alias(SDL_GamepadButton button) noexcept {
switch (button) {
case SDL_GAMEPAD_BUTTON_BACK:
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
return Rml::Input::KI_UNKNOWN;
}
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
@@ -318,12 +326,20 @@ bool find_event_pad_button(
Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event) noexcept {
const auto nativeButton = static_cast<SDL_GamepadButton>(event.button);
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK) {
u32 port = 0;
bool foundEventPort = find_event_port(event.which, port);
if (foundEventPort) {
int openMenuButton = getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, port);
if (openMenuButton != PAD_NATIVE_BUTTON_INVALID && openMenuButton == nativeButton) {
return Rml::Input::KI_F1;
}
}
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port)) {
return Rml::Input::KI_F1;
}
u32 port = 0;
if (!find_event_port(event.which, port)) {
if (!foundEventPort) {
return map_raw_gamepad_button(nativeButton);
}
@@ -631,7 +647,7 @@ void process_axis_direction(
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
@@ -719,7 +735,7 @@ void handle_event(const SDL_Event& event) noexcept {
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
+13 -1
View File
@@ -2,6 +2,8 @@
#include "aurora/lib/logging.hpp"
#include "dusk/achievements.h"
#include "dusk/action_bindings.h"
#include "controller_config.hpp"
#include "dusk/livesplit.h"
#include "dusk/speedrun.h"
#include "fmt/format.h"
@@ -152,12 +154,22 @@ Rml::Element* create_menu_notification(Rml::Element* parent) {
auto* elem = append(parent, "toast");
elem->SetClass("menu-notification", true);
// Get name of button for action binding if the action is bound
Rml::String padButton{};
SDL_Gamepad* gamepad = gamepad_for_port(PAD_CHAN0);
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0) && gamepad != nullptr) {
padButton = native_button_name(gamepad,
getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0));
} else {
padButton = back_button_name();
}
auto* message = append(elem, "message");
auto* row = append(message, "row");
append(row, "span")->SetInnerRML(kMenuNotificationPrefix);
auto* icon = append(row, "icon");
icon->SetClass("controller", true);
append(row, "span")->SetInnerRML(escape(back_button_name()));
append(row, "span")->SetInnerRML(escape(padButton));
append(row, "span")->SetInnerRML("to open menu");
return elem;
+32 -3
View File
@@ -6,12 +6,14 @@
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/config.hpp"
#include "dusk/hotkeys.h"
#include "dusk/data.hpp"
#include "dusk/file_select.hpp"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/io.hpp"
#include "dusk/livesplit.h"
#include "dusk/main.h"
#include "dusk/discord_presence.hpp"
#include "graphics_tuner.hpp"
#include "m_Do/m_Do_main.h"
#include "menu_bar.hpp"
@@ -710,7 +712,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
pane.add_rml(
"<br/>Display the current framerate in a corner of the screen while playing.");
});
leftPane.add_section("Resolution");
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.internalResolutionScale,
@@ -815,9 +816,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
"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 to both stick and gyro aiming.");
"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 to both stick and gyro aiming.");
"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(
@@ -892,6 +893,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
"Hold Tab to increase game speed by up to 4x.",
[] { return getSettings().game.speedrunMode; });
addOption("Reset Key (" + Rml::String{hotkeys::DO_RESET} + ")",
getSettings().game.enableResetKeybind,
"Press " + Rml::String{hotkeys::DO_RESET} + " to reset the game.");
});
add_tab("Audio", [this](Rml::Element* content) {
@@ -1249,6 +1253,20 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.helpText = "Checks GitHub releases for a new Dusklight version on startup.<br/><br/>"
"No personal information is transmitted or collected.",
});
#ifdef DUSK_DISCORD
config_bool_select(leftPane, rightPane, getSettings().game.enableDiscordPresence,
{
.key = "Enable Discord Rich Presence",
.helpText = "Enable Dusk to integrate with Discord Rich Presence. This allows Discord to show your status in-game.",
.onChange = [](bool enabled) {
if (enabled) {
dusk::discord::initialize();
} else {
dusk::discord::shutdown();
}
},
});
#endif
config_bool_select(leftPane, rightPane, getSettings().backend.enableAdvancedSettings,
{
.key = "Enable Advanced Settings",
@@ -1267,6 +1285,17 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
});
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
{
.key = "Show Input Viewer",
.helpText = "Display a controller input overlay while playing.",
});
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewerGyro,
{
.key = "Show Gyro Input Viewer",
.helpText = "Show gyro sensor values in the input viewer.",
.isDisabled = [] { return !getSettings().game.showInputViewer; },
});
leftPane.add_section("Game");
config_bool_select(leftPane, rightPane, getSettings().game.hideTvSettingsScreen,
+14 -1
View File
@@ -72,6 +72,7 @@
#include <aurora/dvd.h>
#include <dolphin/dvd.h>
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_filesystem.h"
#include "SDL3/SDL_iostream.h"
#include "SDL3/SDL_misc.h"
@@ -462,6 +463,11 @@ static std::string asset_path(const char* assetName) {
return std::string("res/") + assetName;
}
static void log_build_info() {
DuskLog.info("Build: {} (rev {}, built {}, type {})", DUSK_WC_DESCRIBE, DUSK_WC_REVISION, DUSK_WC_DATE, DUSK_BUILD_TYPE);
DuskLog.info("Platform: {}", DUSK_PLATFORM_NAME);
}
// =========================================================================
// PC ENTRY POINT
// =========================================================================
@@ -511,6 +517,8 @@ int game_main(int argc, char* argv[]) {
dusk::ConfigPath = dusk::data::initialize_data();
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
log_build_info();
dusk::config::LoadFromUserPreferences();
ApplyCVarOverrides(parsed_arg_options["cvar"]);
dusk::crash_reporting::initialize();
@@ -525,6 +533,9 @@ int game_main(int argc, char* argv[]) {
}
}
// Set SDL metadata for audio mixers and macOS "About" menu
SDL_SetAppMetadata("Dusklight", DUSK_VERSION_STRING, "dev.twilitrealm.dusk");
{
const auto configPathString = dusk::ConfigPath.u8string();
AuroraConfig config{};
@@ -562,7 +573,9 @@ int game_main(int argc, char* argv[]) {
}
#ifdef DUSK_DISCORD
dusk::discord::initialize();
if (dusk::getSettings().game.enableDiscordPresence) {
dusk::discord::initialize();
}
#endif
VISetWindowTitle(