Compare commits

..

4 Commits

Author SHA1 Message Date
MelonSpeedruns 27274e7341 Merge remote-tracking branch 'origin/main' into feature/hyper-enemies
# Conflicts:
#	src/dusk/imgui/ImGuiMenuGame.cpp
#	src/dusk/settings.cpp
2026-05-03 17:23:54 -04:00
MelonSpeedruns ce0d89058a don't run hyper enemies while an event is running 2026-04-30 10:55:28 -04:00
MelonSpeedruns 36092f1fdb added setting 2026-04-27 18:29:14 -04:00
MelonSpeedruns 5eb3184174 Hyper Enemies (2x) 2026-04-27 16:55:43 -04:00
132 changed files with 5468 additions and 10233 deletions
+41 -10
View File
@@ -297,10 +297,8 @@ set(GAME_INCLUDE_DIRS
extern extern
${CMAKE_BINARY_DIR}) ${CMAKE_BINARY_DIR})
find_package(Threads REQUIRED)
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt)
Threads::Threads)
list(APPEND GAME_LIBS libzstd_static) list(APPEND GAME_LIBS libzstd_static)
@@ -322,13 +320,46 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
endif () endif ()
set(DUSK_ENABLE_DISCORD_DEFAULT ON) option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON)
if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD) if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS)
set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
endif () FetchContent_Populate(discord_rpc
option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT}) URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz
if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS) URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1) DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
# RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately.
FetchContent_Populate(rapidjson
URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz
URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
if (NOT TARGET discord-rpc)
set(_drpc ${discord_rpc_SOURCE_DIR}/src)
set(_drpc_src
${_drpc}/discord_rpc.cpp
${_drpc}/rpc_connection.cpp
${_drpc}/serialization.cpp
)
if (WIN32)
list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp)
elseif (APPLE)
list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m)
else ()
list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp)
endif ()
add_library(discord-rpc STATIC ${_drpc_src})
target_include_directories(discord-rpc PUBLIC
${discord_rpc_SOURCE_DIR}/include
${rapidjson_SOURCE_DIR}/include
)
if (UNIX)
target_link_libraries(discord-rpc PUBLIC pthread)
endif ()
endif ()
list(APPEND GAME_LIBS discord-rpc)
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1)
endif () endif ()
# Edit & Continue # Edit & Continue
+16 -34
View File
@@ -1,55 +1,37 @@
<div align="center"> ![DuskLogo](res/logo-mascot.png)
<img src="res/logo-mascot.png" alt="Logo" width="640">
<p align="center"> - ### **[Official Website](https://twilitrealm.dev)**
<a href="https://twilitrealm.dev">Official Website</a> - ### **[Discord](https://discord.gg/QACynxeyna)**
<a href="https://discord.gg/QACynxeyna">Discord</a>
</p>
</div>
# Overview # Overview
Dusk is a reverse-engineered reimplementation of Twilight Princess. Dusk is a reverse-engineered reimplementation of Twilight Princess.
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup # Setup
**⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.**
> [!IMPORTANT] ### 1. Verify your ROM dump
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game. First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions.
### 1. Verify your dump | Version | sha1 hash |
|--------------| ---------------------------------------- |
First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions: | GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 |
| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca |
| Version | SHA-1 hash |
|--------------| ------------------------------------------ |
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases) ### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
### 3. Setup the game ### 3. Setup the game
- Extract the zip folder
- Extract the .zip file
- Launch Dusk - Launch Dusk
- Press **Select Disc Image** and provide the path to your supported game dump. - Select Options, then set the ISO Path to your supported game dump
- Press **Play**! - Press Start Game to play!
![Dusk options](assets/dusk_options.png)
# Building # Building
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Pull Requests are welcomed! Note that we do not accept contributions that are primarily AI generated and will close your PR if we suspect as much.
# Credits # Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
<br/>
<div align="center">
<a href="https://github.com/encounter/aurora">
<img src="assets/aurora-powered.png" alt="Powered by Aurora" width="800">
</a>
</div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

-66
View File
@@ -1,66 +0,0 @@
<svg width="600" height="600" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<circle cx="150" cy="150" r="105" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="95" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="60" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="75" fill="none" stroke="white" stroke-width="4"/>
<defs>
<line id="ray" x1="150" y1="55" x2="150" y2="45"/>
<clipPath id="zigzag-clip">
<circle cx="150" cy="150" r="75"/>
</clipPath>
</defs>
<g stroke="white" stroke-width="3">
<use href="#ray"/>
<use href="#ray" transform="rotate(18 150 150)"/>
<use href="#ray" transform="rotate(36 150 150)"/>
<use href="#ray" transform="rotate(54 150 150)"/>
<use href="#ray" transform="rotate(72 150 150)"/>
<use href="#ray" transform="rotate(90 150 150)"/>
<use href="#ray" transform="rotate(108 150 150)"/>
<use href="#ray" transform="rotate(126 150 150)"/>
<use href="#ray" transform="rotate(144 150 150)"/>
<use href="#ray" transform="rotate(162 150 150)"/>
<use href="#ray" transform="rotate(180 150 150)"/>
<use href="#ray" transform="rotate(198 150 150)"/>
<use href="#ray" transform="rotate(216 150 150)"/>
<use href="#ray" transform="rotate(234 150 150)"/>
<use href="#ray" transform="rotate(252 150 150)"/>
<use href="#ray" transform="rotate(270 150 150)"/>
<use href="#ray" transform="rotate(288 150 150)"/>
<use href="#ray" transform="rotate(306 150 150)"/>
<use href="#ray" transform="rotate(324 150 150)"/>
<use href="#ray" transform="rotate(342 150 150)"/>
</g>
<polygon fill="none" stroke="white" stroke-width="4" opacity="1" clip-path="url(#zigzag-clip)"
points="
126.82,78.67
150,90
173.18,78.67
185.27,101.46
210.68,105.92
207.06,131.46
225,150
207.06,168.54
210.68,194.08
185.27,198.54
173.18,221.33
150,210
126.82,221.33
114.73,198.54
89.32,194.08
92.94,168.54
75,150
92.94,131.46
89.32,105.92
114.73,101.46
"/>
<g fill="none" stroke="white" stroke-width="4">
<polygon points="150,105 130,140 170,140"/>
<polygon points="130,140 110,175 150,175"/>
<polygon points="170,140 150,175 190,175"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

+1 -1
+11 -14
View File
@@ -1447,37 +1447,36 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiPreLaunchWindow.cpp
src/dusk/imgui/ImGuiPreLaunchWindow.hpp
src/dusk/imgui/ImGuiFirstRunPreset.hpp
src/dusk/imgui/ImGuiFirstRunPreset.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp
src/dusk/imgui/ImGuiDebugPad.cpp
src/dusk/imgui/ImGuiControllerOverlay.cpp src/dusk/imgui/ImGuiControllerOverlay.cpp
src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiMapLoader.cpp src/dusk/imgui/ImGuiMapLoader.cpp
src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/ui/achievements.cpp src/dusk/imgui/ImGuiAchievements.hpp
src/dusk/ui/achievements.hpp src/dusk/imgui/ImGuiAchievements.cpp
src/dusk/ui/bool_button.cpp src/dusk/ui/bool_button.cpp
src/dusk/ui/bool_button.hpp src/dusk/ui/bool_button.hpp
src/dusk/ui/button.cpp src/dusk/ui/button.cpp
src/dusk/ui/button.hpp src/dusk/ui/button.hpp
src/dusk/ui/component.cpp src/dusk/ui/component.cpp
src/dusk/ui/component.hpp src/dusk/ui/component.hpp
src/dusk/ui/controller_config.cpp
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp src/dusk/ui/document.cpp
src/dusk/ui/document.hpp src/dusk/ui/document.hpp
src/dusk/ui/editor.cpp src/dusk/ui/editor.cpp
src/dusk/ui/editor.hpp src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp src/dusk/ui/event.cpp
src/dusk/ui/event.hpp src/dusk/ui/event.hpp
src/dusk/ui/graphics_tuner.cpp
src/dusk/ui/graphics_tuner.hpp
src/dusk/ui/input.cpp src/dusk/ui/input.cpp
src/dusk/ui/input.hpp src/dusk/ui/input.hpp
src/dusk/ui/modal.cpp
src/dusk/ui/modal.hpp
src/dusk/ui/nav_types.hpp src/dusk/ui/nav_types.hpp
src/dusk/ui/number_button.cpp src/dusk/ui/number_button.cpp
src/dusk/ui/number_button.hpp src/dusk/ui/number_button.hpp
@@ -1485,12 +1484,12 @@ set(DUSK_FILES
src/dusk/ui/overlay.hpp src/dusk/ui/overlay.hpp
src/dusk/ui/pane.cpp src/dusk/ui/pane.cpp
src/dusk/ui/pane.hpp src/dusk/ui/pane.hpp
src/dusk/ui/menu_bar.cpp src/dusk/ui/popup.cpp
src/dusk/ui/menu_bar.hpp src/dusk/ui/popup.hpp
src/dusk/ui/prelaunch.cpp src/dusk/ui/prelaunch.cpp
src/dusk/ui/prelaunch.hpp src/dusk/ui/prelaunch.hpp
src/dusk/ui/preset.cpp src/dusk/ui/prelaunch_options.cpp
src/dusk/ui/preset.hpp src/dusk/ui/prelaunch_options.hpp
src/dusk/ui/select_button.cpp src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp src/dusk/ui/settings.cpp
@@ -1511,8 +1510,6 @@ set(DUSK_FILES
src/dusk/OSReport.cpp src/dusk/OSReport.cpp
src/dusk/OSThread.cpp src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp src/dusk/OSMutex.cpp
src/dusk/discord.cpp
src/dusk/discord.hpp
src/dusk/discord_presence.cpp src/dusk/discord_presence.cpp
src/dusk/version.cpp src/dusk/version.cpp
) )
-11
View File
@@ -25,10 +25,6 @@ public:
int Draw(); int Draw();
int Delete(); int Delete();
#if TARGET_PC
void onInterpCallback();
#endif
enum Param_e { enum Param_e {
LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7) LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7)
}; };
@@ -54,13 +50,6 @@ private:
/* 0x1020 */ dCcD_Cyl mCylinderCollider; /* 0x1020 */ dCcD_Cyl mCylinderCollider;
/* 0x115C */ s32 mStopSwingingFrames; /* 0x115C */ s32 mStopSwingingFrames;
#if TARGET_PC
cXyz mChainInterpPrev[64];
cXyz mChainInterpCurr[64];
bool mChainInterpPrevValid;
bool mChainInterpCurrValid;
#endif
// Number of chain models // Number of chain models
u32 getArg0() { u32 getArg0() {
return fopAcM_GetParamBit(this, 0, 6); return fopAcM_GetParamBit(this, 0, 6);
-18
View File
@@ -118,18 +118,6 @@ class camera_class;
class dCamera_c; class dCamera_c;
typedef bool (dCamera_c::*engine_fn)(s32); typedef bool (dCamera_c::*engine_fn)(s32);
#if TARGET_PC
struct DebugFlyCam {
bool initialized;
f32 pitch;
f32 yaw;
cXyz savedCenter;
cXyz savedEye;
f32 savedFovy;
cSAngle savedBank;
};
#endif
class dCamera_c { class dCamera_c {
public: public:
class dCamInfo_c { class dCamInfo_c {
@@ -1040,8 +1028,6 @@ public:
bool test2Camera(s32); bool test2Camera(s32);
#if TARGET_PC #if TARGET_PC
bool freeCamera(); bool freeCamera();
bool executeDebugFlyCam();
void deactivateDebugFlyCam();
#endif #endif
bool towerCamera(s32); bool towerCamera(s32);
bool hookshotCamera(s32); bool hookshotCamera(s32);
@@ -1390,10 +1376,6 @@ public:
/* 0x970 */ dCamSetup_c mCamSetup; /* 0x970 */ dCamSetup_c mCamSetup;
/* 0xAEC */ dCamParam_c mCamParam; /* 0xAEC */ dCamParam_c mCamParam;
/* 0xB0C */ u8 field_0xb0c; /* 0xB0C */ u8 field_0xb0c;
#if TARGET_PC
DebugFlyCam mDebugFlyCam;
#endif
}; // Size: 0xB10 }; // Size: 0xB10
dCamera_c* dCam_getBody(); dCamera_c* dCam_getBody();
-6
View File
@@ -169,12 +169,6 @@ public:
void mapBlink() {} void mapBlink() {}
#if PLATFORM_WII || TARGET_PC
f32 getMirrorPosX(f32 param_0, f32 param_1) {
return (field_0x11dc * 2.0f - (param_0 + param_1)) - param_1;
}
#endif
// Unknown name // Unknown name
struct RegionTexData { struct RegionTexData {
/* 0x00 */ float mMinX; /* 0x00 */ float mMinX;
-10
View File
@@ -66,16 +66,6 @@ public:
_c90 = param_2; _c90 = param_2;
} }
#if PLATFORM_WII || TARGET_PC
f32 getMirrorCenterPosX(f32 param_0, f32 param_1) {
if (_c90) {
return (mCenterPosX * 2.0f - (param_0 + param_1)) - param_1;
}
return param_0;
}
#endif
struct Stage_c { struct Stage_c {
// Incomplete class // Incomplete class
+3
View File
@@ -50,6 +50,8 @@ public:
bool hasSignal(const char* key) const; bool hasSignal(const char* key) const;
std::vector<Achievement> getAchievements() const; std::vector<Achievement> getAchievements() const;
bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
std::string consumePendingUnlock();
private: private:
struct Entry { struct Entry {
@@ -66,6 +68,7 @@ private:
std::unordered_set<std::string_view> m_signals; std::unordered_set<std::string_view> m_signals;
bool m_loaded = false; bool m_loaded = false;
bool m_dirty = false; bool m_dirty = false;
std::queue<std::string> m_pendingUnlocks;
}; };
} // namespace dusk } // namespace dusk
+12 -8
View File
@@ -1,14 +1,18 @@
#pragma once #pragma once
#ifdef DUSK_DISCORD #ifdef DUSK_DISCORD_RPC
namespace dusk::discord { namespace dusk {
namespace discord {
void initialize(); void Initialize();
void run_callbacks();
void update_presence();
void shutdown();
} // namespace dusk::discord void RunCallbacks();
#endif // DUSK_DISCORD void UpdatePresence();
void Shutdown();
}
}
#endif // DUSK_DISCORD_RPC
-14
View File
@@ -1,10 +1,6 @@
#ifndef DUSK_MAIN_H #ifndef DUSK_MAIN_H
#define DUSK_MAIN_H #define DUSK_MAIN_H
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <filesystem> #include <filesystem>
namespace dusk { namespace dusk {
@@ -12,17 +8,7 @@ namespace dusk {
extern bool IsShuttingDown; extern bool IsShuttingDown;
extern bool IsGameLaunched; extern bool IsGameLaunched;
extern bool IsFocusPaused; extern bool IsFocusPaused;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath; extern std::filesystem::path ConfigPath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false;
#else
inline constexpr bool SupportsProcessRestart = true;
#endif
void RequestRestart() noexcept;
} }
#endif // DUSK_MAIN_H #endif // DUSK_MAIN_H
-2
View File
@@ -1,7 +1,5 @@
#pragma once #pragma once
#include <array>
struct RoomEntry { struct RoomEntry {
u8 roomNo; u8 roomNo;
std::vector<s16> roomPoints = {}; std::vector<s16> roomPoints = {};
+137 -1
View File
@@ -2,6 +2,9 @@
#define _SRC_DUSK_MATH_H_ #define _SRC_DUSK_MATH_H_
#include <cmath> #include <cmath>
#include <array>
#include <limits>
#include <bit>
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846f #define M_PI 3.14159265358979323846f
@@ -16,6 +19,139 @@ inline float i_cosf(float x) { return cos(x); }
inline float i_tanf(float x) { return tan(x); } inline float i_tanf(float x) { return tan(x); }
inline float i_acosf(float x) { return acos(x); } inline float i_acosf(float x) { return acos(x); }
#include <dolphin/ppc_math.h>
// frsqrte matching courtesy of Geotale, with reference to https://achurch.org/cpu-tests/ppc750cl.s
struct BaseAndDec32 {
uint32_t base;
int32_t dec;
};
struct BaseAndDec64 {
uint64_t base;
int64_t dec;
};
union c32 {
constexpr c32(const float p) {
f = p;
}
constexpr c32(const uint32_t p) {
u = p;
}
uint32_t u;
float f;
};
union c64 {
constexpr c64(const double p) {
f = p;
}
constexpr c64(const uint64_t p) {
u = p;
}
uint64_t u;
double f;
};
static constexpr uint64_t EXPONENT_SHIFT_F64 = 52;
static constexpr uint64_t MANTISSA_MASK_F64 = 0x000fffffffffffffULL;
static constexpr uint64_t EXPONENT_MASK_F64 = 0x7ff0000000000000ULL;
static constexpr uint64_t SIGN_MASK_F64 = 0x8000000000000000ULL;
static constexpr std::array<BaseAndDec64, 32> RSQRTE_TABLE = {{
{0x69fa000000000ULL, -0x15a0000000LL},
{0x5f2e000000000ULL, -0x13cc000000LL},
{0x554a000000000ULL, -0x1234000000LL},
{0x4c30000000000ULL, -0x10d4000000LL},
{0x43c8000000000ULL, -0x0f9c000000LL},
{0x3bfc000000000ULL, -0x0e88000000LL},
{0x34b8000000000ULL, -0x0d94000000LL},
{0x2df0000000000ULL, -0x0cb8000000LL},
{0x2794000000000ULL, -0x0bf0000000LL},
{0x219c000000000ULL, -0x0b40000000LL},
{0x1bfc000000000ULL, -0x0aa0000000LL},
{0x16ae000000000ULL, -0x0a0c000000LL},
{0x11a8000000000ULL, -0x0984000000LL},
{0x0ce6000000000ULL, -0x090c000000LL},
{0x0862000000000ULL, -0x0898000000LL},
{0x0416000000000ULL, -0x082c000000LL},
{0xffe8000000000ULL, -0x1e90000000LL},
{0xf0a4000000000ULL, -0x1c00000000LL},
{0xe2a8000000000ULL, -0x19c0000000LL},
{0xd5c8000000000ULL, -0x17c8000000LL},
{0xc9e4000000000ULL, -0x1610000000LL},
{0xbedc000000000ULL, -0x1490000000LL},
{0xb498000000000ULL, -0x1330000000LL},
{0xab00000000000ULL, -0x11f8000000LL},
{0xa204000000000ULL, -0x10e8000000LL},
{0x9994000000000ULL, -0x0fe8000000LL},
{0x91a0000000000ULL, -0x0f08000000LL},
{0x8a1c000000000ULL, -0x0e38000000LL},
{0x8304000000000ULL, -0x0d78000000LL},
{0x7c48000000000ULL, -0x0cc8000000LL},
{0x75e4000000000ULL, -0x0c28000000LL},
{0x6fd0000000000ULL, -0x0b98000000LL},
}};
[[nodiscard]] static inline double frsqrte(const double val) {
c64 bits(val);
uint64_t mantissa = bits.u & MANTISSA_MASK_F64;
int64_t exponent = bits.u & EXPONENT_MASK_F64;
bool sign = (bits.u & SIGN_MASK_F64) != 0;
// Handle 0 case
if (mantissa == 0 && exponent == 0) {
return std::copysign(std::numeric_limits<double>::infinity(), bits.f);
}
// Handle NaN-like
if (exponent == EXPONENT_MASK_F64) {
if (mantissa == 0) {
return sign ? std::numeric_limits<double>::quiet_NaN() : 0.0;
}
return val;
}
// Handle negative inputs
if (sign) {
return std::numeric_limits<double>::quiet_NaN();
}
if (exponent == 0) {
// Shift so one bit goes to where the exponent would be,
// then clear that bit to mimic a not-subnormal number!
// Aka, if there are 12 leading zeroes, shift left once
uint32_t shift = std::countl_zero(mantissa) - static_cast<uint32_t>(63 - EXPONENT_SHIFT_F64);
mantissa <<= shift;
mantissa &= MANTISSA_MASK_F64;
// The shift is subtracted by 1 because denormals by default
// are offset by 1 (exponent 0 doesn't have implied 1 bit)
exponent -= static_cast<int64_t>(shift - 1) << EXPONENT_SHIFT_F64;
}
// In reality this doesn't get the full exponent -- Only the least significant bit
// Only that's needed because square roots of higher exponent bits simply multiply the
// result by 2!!
uint32_t key = static_cast<uint32_t>((static_cast<uint64_t>(exponent) | mantissa) >> 37);
uint64_t new_exp =
(static_cast<uint64_t>((0xbfcLL << EXPONENT_SHIFT_F64) - exponent) >> 1) & EXPONENT_MASK_F64;
// Remove the bits relating to anything higher than the LSB of the exponent
const auto &entry = RSQRTE_TABLE[0x1f & (key >> 11)];
// The result is given by an estimate then an adjustment based on the original
// key that was computed
uint64_t new_mantissa = static_cast<uint64_t>(entry.base + entry.dec * static_cast<int64_t>(key & 0x7ff));
return c64(new_exp | new_mantissa).f;
}
#endif // _SRC_DUSK_MATH_H_ #endif // _SRC_DUSK_MATH_H_
+3 -15
View File
@@ -21,12 +21,6 @@ enum class GameLanguage : u8 {
Italian = OS_LANGUAGE_ITALIAN, Italian = OS_LANGUAGE_ITALIAN,
}; };
enum class DiscVerificationState : u8 {
Unknown = 0,
Success,
HashMismatch,
};
namespace config { namespace config {
template <> template <>
struct ConfigEnumRange<BloomMode> { struct ConfigEnumRange<BloomMode> {
@@ -39,12 +33,6 @@ struct ConfigEnumRange<GameLanguage> {
static constexpr auto min = GameLanguage::English; static constexpr auto min = GameLanguage::English;
static constexpr auto max = GameLanguage::Italian; static constexpr auto max = GameLanguage::Italian;
}; };
template <>
struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto min = DiscVerificationState::Unknown;
static constexpr auto max = DiscVerificationState::HashMismatch;
};
} }
// Persistent user settings // Persistent user settings
@@ -68,7 +56,6 @@ struct UserSettings {
ConfigVar<int> fanfareVolume; ConfigVar<int> fanfareVolume;
ConfigVar<bool> enableReverb; ConfigVar<bool> enableReverb;
ConfigVar<bool> enableHrtf; ConfigVar<bool> enableHrtf;
ConfigVar<bool> menuSounds;
} audio; } audio;
// Game settings // Game settings
@@ -79,11 +66,13 @@ struct UserSettings {
// QoL // QoL
ConfigVar<bool> enableQuickTransform; ConfigVar<bool> enableQuickTransform;
ConfigVar<bool> hideTvSettingsScreen; ConfigVar<bool> hideTvSettingsScreen;
ConfigVar<bool> skipWarningScreen;
ConfigVar<bool> biggerWallets; ConfigVar<bool> biggerWallets;
ConfigVar<bool> noReturnRupees; ConfigVar<bool> noReturnRupees;
ConfigVar<bool> disableRupeeCutscenes; ConfigVar<bool> disableRupeeCutscenes;
ConfigVar<bool> noSwordRecoil; ConfigVar<bool> noSwordRecoil;
ConfigVar<int> damageMultiplier; ConfigVar<int> damageMultiplier;
ConfigVar<bool> hyperEnemies;
ConfigVar<bool> noHeartDrops; ConfigVar<bool> noHeartDrops;
ConfigVar<bool> instantDeath; ConfigVar<bool> instantDeath;
ConfigVar<bool> fastClimbing; ConfigVar<bool> fastClimbing;
@@ -131,7 +120,6 @@ struct UserSettings {
ConfigVar<bool> invertCameraXAxis; ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis; ConfigVar<bool> invertCameraYAxis;
ConfigVar<float> freeCameraSensitivity; ConfigVar<float> freeCameraSensitivity;
ConfigVar<bool> debugFlyCam;
// Cheats // Cheats
ConfigVar<bool> infiniteHearts; ConfigVar<bool> infiniteHearts;
@@ -162,12 +150,12 @@ struct UserSettings {
struct { struct {
ConfigVar<std::string> isoPath; ConfigVar<std::string> isoPath;
ConfigVar<DiscVerificationState> isoVerification;
ConfigVar<std::string> graphicsBackend; ConfigVar<std::string> graphicsBackend;
ConfigVar<bool> skipPreLaunchUI; ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation; ConfigVar<bool> showPipelineCompilation;
ConfigVar<bool> wasPresetChosen; ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> enableCrashReporting; ConfigVar<bool> enableCrashReporting;
ConfigVar<bool> duskMenuOpen;
ConfigVar<int> cardFileType; ConfigVar<int> cardFileType;
} backend; } backend;
}; };
-13
View File
@@ -5,7 +5,6 @@
#include "Z2AudioLib/Z2EnvSeMgr.h" #include "Z2AudioLib/Z2EnvSeMgr.h"
#include "Z2AudioLib/Z2LinkMgr.h" #include "Z2AudioLib/Z2LinkMgr.h"
#include "dusk/audio.h" #include "dusk/audio.h"
#include "dusk/settings.h"
class mDoAud_zelAudio_c : public Z2AudioMgr { class mDoAud_zelAudio_c : public Z2AudioMgr {
public: public:
@@ -133,18 +132,6 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re
-1.0f, -1.0f, 0); -1.0f, -1.0f, 0);
} }
#if TARGET_PC
inline void mDoAud_seStartMenu(u32 i_sfxID) {
if (!mDoAud_zelAudio_c::isInitFlag()) {
return;
}
if (!dusk::getSettings().audio.menuSounds.getValue()) {
return;
}
mDoAud_seStart(i_sfxID, nullptr, 0, 0);
}
#endif
inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) { inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) {
DUSK_AUDIO_SKIP() DUSK_AUDIO_SKIP()
Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f, Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f,
@@ -1,9 +1,9 @@
#ifndef J3DSTRUCT_H #ifndef J3DSTRUCT_H
#define J3DSTRUCT_H #define J3DSTRUCT_H
#include <cstring>
#include <gx.h> #include <gx.h>
#include <mtx.h> #include <mtx.h>
#include <mtx.h>
#include "global.h" #include "global.h"
#include "JSystem/JMath/JMath.h" #include "JSystem/JMath/JMath.h"
@@ -11,7 +11,7 @@
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DLightInfo { struct J3DLightInfo {
bool operator==(J3DLightInfo& other) const; bool operator==(J3DLightInfo& other) const;
@@ -28,7 +28,7 @@ struct J3DLightInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTextureSRTInfo { struct J3DTextureSRTInfo {
// NOTE: Big endian when loaded from file! // NOTE: Big endian when loaded from file!
@@ -79,7 +79,7 @@ enum J3DTexMtxMode {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTexMtxInfo { struct J3DTexMtxInfo {
bool operator==(J3DTexMtxInfo& other) const; bool operator==(J3DTexMtxInfo& other) const;
@@ -97,7 +97,7 @@ struct J3DTexMtxInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DIndTexMtxInfo { struct J3DIndTexMtxInfo {
J3DIndTexMtxInfo& operator=(J3DIndTexMtxInfo const&); J3DIndTexMtxInfo& operator=(J3DIndTexMtxInfo const&);
@@ -107,7 +107,7 @@ struct J3DIndTexMtxInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DFogInfo { struct J3DFogInfo {
bool operator==(J3DFogInfo&) const; bool operator==(J3DFogInfo&) const;
@@ -126,7 +126,7 @@ struct J3DFogInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DNBTScaleInfo { struct J3DNBTScaleInfo {
bool operator==(const J3DNBTScaleInfo& other) const; bool operator==(const J3DNBTScaleInfo& other) const;
@@ -153,7 +153,7 @@ struct J3DIndTexOrderInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTevSwapModeInfo { struct J3DTevSwapModeInfo {
/* 0x0 */ u8 mRasSel; /* 0x0 */ u8 mRasSel;
@@ -164,7 +164,7 @@ struct J3DTevSwapModeInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTevSwapModeTableInfo { struct J3DTevSwapModeTableInfo {
/* 0x0 */ u8 field_0x0; /* 0x0 */ u8 field_0x0;
@@ -175,7 +175,7 @@ struct J3DTevSwapModeTableInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTevStageInfo { struct J3DTevStageInfo {
/* 0x0 */ u8 field_0x0; /* 0x0 */ u8 field_0x0;
@@ -202,7 +202,7 @@ struct J3DTevStageInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DIndTevStageInfo { struct J3DIndTevStageInfo {
/* 0x0 */ u8 mIndStage; /* 0x0 */ u8 mIndStage;
@@ -219,7 +219,7 @@ struct J3DIndTevStageInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTexCoordInfo { struct J3DTexCoordInfo {
/* 0x0 */ u8 mTexGenType; /* 0x0 */ u8 mTexGenType;
@@ -265,7 +265,7 @@ struct J3DBlendInfo {
/** /**
* @ingroup jsystem-j3d * @ingroup jsystem-j3d
* *
*/ */
struct J3DTevOrderInfo { struct J3DTevOrderInfo {
void operator=(const J3DTevOrderInfo& other) { void operator=(const J3DTevOrderInfo& other) {
@@ -556,8 +556,8 @@ void J3DModelLoader::readVertexData(const J3DVertexBlock& block, J3DVertexData&
if (attr == GX_VA_POS) { if (attr == GX_VA_POS) {
// can be a little off due to 0x20 alignment, account for that // can be a little off due to 0x20 alignment, account for that
// u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F; u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F;
// JUT_ASSERT(1234, expect == addrDiff); JUT_ASSERT(1234, expect == addrDiff);
} else if (attr == GX_VA_NRM) { } else if (attr == GX_VA_NRM) {
data.mNrmNum = num; data.mNrmNum = num;
} else if (attr == GX_VA_CLR0) { } else if (attr == GX_VA_CLR0) {
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 95 KiB

+105 -195
View File
@@ -8,230 +8,140 @@ body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: "Fira Sans"; font-family: "Fira Sans Condensed";
font-weight: normal; font-size: 24dp;
font-size: 20dp; color: #FFFFFF;
color: #E0DBC8;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
align-items: stretch; align-items: stretch;
z-index: 1;
pointer-events: none;
} }
toast { .overlay-root {
position: absolute; width: 100%;
top: 40dp; min-height: 45%;
right: 40dp;
display: flex; display: flex;
flex-flow: column; flex-direction: column;
border-radius: 14dp; justify-content: flex-end;
overflow: hidden; align-items: stretch;
border: 1dp #92875B; decorator: vertical-gradient(#00000000 #151610F2);
backdrop-filter: blur(5dp); padding: 48dp 0 40dp 0;
box-shadow: 0 0 15dp 3dp;
background-color: rgba(21, 22, 16, 80%);
filter: opacity(0); filter: opacity(0);
transform: scale(0.9); transition: filter 0.2s linear-in-out;
transform-origin: center;
transition: filter transform 0.2s cubic-in-out;
padding: 18dp 24dp;
gap: 8dp;
} }
toast[open] { .overlay-root[open] {
filter: opacity(1); filter: opacity(1);
transform: scale(1);
} }
/*toast:hover { .overlay {
cursor: pointer; width: 100%;
background-color: rgba(61, 59, 36, 80%); max-width: 1216dp;
} margin-left: auto;
margin-right: auto;
toast:active {
background-color: rgba(45, 43, 26, 80%);
}*/
toast heading {
display: flex; display: flex;
gap: 18dp; flex-direction: column;
gap: 24dp;
padding: 0 32dp;
}
@media (max-height: 800dp) {
.overlay-root {
min-height: 38%;
padding: 32dp 0 28dp 0;
}
.overlay {
gap: 16dp;
padding: 0 24dp;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
font-family: "Fira Sans Condensed"; gap: 24dp;
}
.carousel-container {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
min-width: 0;
}
.description {
font-size: 18dp; font-size: 18dp;
line-height: 22dp;
color: rgba(255, 255, 255, 50%);
}
.divider {
margin: 1dp 0;
border-top: 1dp rgba(217, 217, 217, 50%);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
footer-button {
display: block;
width: 100%;
max-width: 220dp;
border: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold; font-weight: bold;
font-size: 20dp;
line-height: 24dp;
text-transform: uppercase; text-transform: uppercase;
color: #92875B; color: #FFFFFF;
opacity: 1;
cursor: pointer;
} }
toast heading > span { footer-button.return {
flex: 1 0 auto; text-align: left;
} }
toast heading > row { footer-button.reset {
flex: 1 0 auto; text-align: right;
}
.stepped-carousel {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4dp; justify-content: center;
gap: 16dp;
width: auto;
min-width: 246dp;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
} }
toast message { .stepped-carousel-value {
display: flex; line-height: 29dp;
flex-flow: column; min-width: 166dp;
gap: 8dp;
}
toast message row {
display: flex;
}
toast message row.muted {
opacity: 0.5;
}
toast progress {
height: 4dp;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
toast progress fill {
background-color: rgba(194, 164, 45, 80%);
}
toast.achievement {
border: 1dp #C2A42D;
}
toast.achievement heading {
color: #C2A42D;
}
toast.controller-warning {
top: auto;
right: auto;
bottom: 40dp;
left: 50%;
width: 440dp;
max-width: 90%;
transform: translateX(-50%) scale(0.9);
}
toast.controller-warning[open] {
transform: translateX(-50%) scale(1);
}
toast.controller-warning heading {
color: #C2A42D;
}
toast.menu-notification {
top: 40dp;
right: auto;
bottom: auto;
left: 50%;
max-width: 90%;
transform: translateX(-50%) scale(0.9);
}
toast.menu-notification[open] {
transform: translateX(-50%) scale(1);
}
toast.menu-notification message {
align-items: center;
text-align: center; text-align: center;
white-space: nowrap;
opacity: 0.9;
} }
toast.menu-notification message row { .stepped-carousel-arrow {
align-items: center;
gap: 6dp;
}
icon {
font-family: "Material Symbols Rounded";
font-weight: normal;
display: inline-block;
vertical-align: middle;
}
icon.arrow-forward {
width: 24dp; width: 24dp;
height: 24dp; height: 24dp;
font-size: 24dp; min-width: 24dp;
decorator: text("&#xe5c8;" center center); padding: 0;
} border: 0;
background-color: transparent;
icon.trophy { opacity: 1;
width: 24dp; cursor: pointer;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe71a;" center center);
}
icon.controller {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xf135;" center center);
}
icon.warning {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe002;" center center);
}
logo {
position: absolute;
width: 100dp;
height: 100dp;
bottom: 40dp;
left: 40dp;
opacity: 0;
transition: opacity 0.5s linear-in-out;
}
logo[open] {
opacity: 0.65;
}
logo img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
filter: drop-shadow(#0008 0 0 14dp);
transform-origin: center;
}
logo img.inner {
animation: 24s linear-in-out infinite logo-inner-spin;
}
logo img.outer {
animation: 8s linear-in-out infinite logo-outer-spin;
}
@keyframes logo-inner-spin {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
@keyframes logo-outer-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
} }
+15 -152
View File
@@ -9,44 +9,16 @@ body {
font-weight: normal; font-weight: normal;
font-size: 20dp; font-size: 20dp;
color: #FFFFFF; color: #FFFFFF;
filter: opacity(0); background-color: #000000;
transition: filter 1s 0.2s linear-in-out;
z-index: -1;
}
.gradient {
position: absolute;
width: 100%;
height: 100%;
/* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */
decorator: horizontal-gradient(#000000FF #00000000);
}
body.mirrored .gradient {
decorator: horizontal-gradient(#00000000 #000000FF);
}
.background {
position: absolute;
width: 100%;
height: 100%;
decorator: image(../prelaunch-bg.png cover left center); decorator: image(../prelaunch-bg.png cover left center);
opacity: 0; filter: opacity(0);
transition: opacity 1s linear-in-out; transition: filter 1s 0.1s linear-in-out;
} }
body[open] { body[open] {
filter: opacity(1); filter: opacity(1);
} }
body[open] .background {
opacity: 1;
}
body.disc-ready .background {
opacity: 0;
}
content { content {
display: block; display: block;
width: 100%; width: 100%;
@@ -62,7 +34,6 @@ content[open] {
menu { menu {
position: absolute; position: absolute;
left: 96dp; left: 96dp;
right: auto;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */ /* Scale based on a reference screen width, 428/1216 */
@@ -75,11 +46,6 @@ menu {
gap: 48dp; gap: 48dp;
} }
body.mirrored menu {
left: auto;
right: 96dp;
}
hero { hero {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -88,10 +54,6 @@ hero {
gap: 8dp; gap: 8dp;
} }
body.mirrored hero {
align-items: flex-end;
}
hero img { hero img {
width: 100%; width: 100%;
} }
@@ -116,7 +78,6 @@ hero img {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12dp; gap: 12dp;
align-items: flex-start;
} }
#menu-list button { #menu-list button {
@@ -124,7 +85,6 @@ hero img {
height: 54dp; height: 54dp;
padding: 8dp 16dp; padding: 8dp 16dp;
border-radius: 8dp; border-radius: 8dp;
text-align: left;
text-transform: uppercase; text-transform: uppercase;
font-family: "Fira Sans Condensed"; font-family: "Fira Sans Condensed";
font-size: 32dp; font-size: 32dp;
@@ -134,14 +94,8 @@ hero img {
decorator: horizontal-gradient(#00000000 #00000000); decorator: horizontal-gradient(#00000000 #00000000);
} }
#menu-list button:disabled {
opacity: 0.75;
cursor: default;
decorator: horizontal-gradient(#00000000 #00000000);
}
#menu-list button.anim-done { #menu-list button.anim-done {
transition: decorator color opacity 0.1s linear-in-out; transition: decorator color 0.1s linear-in-out;
} }
#menu-list button:hover, #menu-list button:hover,
@@ -150,136 +104,45 @@ hero img {
decorator: horizontal-gradient(#FEE685FF #FEE68500); decorator: horizontal-gradient(#FEE685FF #FEE68500);
} }
body.mirrored #menu-list { disk-status {
align-items: flex-end;
}
body.mirrored #menu-list button {
text-align: right;
}
body.mirrored #menu-list button:hover,
body.mirrored #menu-list button:focus-visible {
decorator: horizontal-gradient(#FEE68500 #FEE685FF);
}
disc-info {
position: absolute; position: absolute;
left: 96dp; left: 96dp;
right: auto;
bottom: 72dp; bottom: 72dp;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12dp; gap: 8dp;
font-size: 24dp;
font-effect: glow(0dp 4dp 0dp 4dp black);
text-align: left;
}
body.mirrored disc-info {
left: auto;
right: 96dp;
text-align: right;
} }
version-info { version-info {
position: absolute; position: absolute;
right: 96dp; right: 96dp;
left: auto;
bottom: 72dp; bottom: 72dp;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12dp;
text-align: right;
font-size: 24dp;
font-effect: glow(0dp 4dp 0dp 4dp black);
text-align: right;
}
body.mirrored version-info {
right: auto;
left: 96dp;
text-align: left;
}
#disc-status {
display: flex;
align-items: center;
gap: 8dp; gap: 8dp;
text-align: right;
} }
#disc-status[status=good] { .status,
.version {
font-size: 24dp;
}
.status,
.update {
color: #D8F999; color: #D8F999;
} }
#disc-status[status=bad] { .status[bad] {
color: #FFC9C9; color: #FFC9C9;
} }
#disc-status[status=verifying] {
color: #FFFFFF;
}
#disc-status[status=mismatch] {
color: #FFD6A7;
}
#disc-status[status=unknown] {
color: rgba(224, 219, 200, 65%);
}
#disc-status[status=pending] {
color: #FEE685;
}
#disc-status icon {
display: none;
width: 24dp;
height: 24dp;
font-family: "Material Symbols Rounded";
font-weight: normal;
font-size: 24dp;
}
#disc-status[status] icon {
display: block;
}
#disc-status[status=good] icon {
decorator: text("&#xe5ca;" center center);
}
#disc-status[status=bad] icon {
decorator: text("&#xe5cd;" center center);
}
#disc-status[status=verifying] icon {
decorator: text("&#xe8b5;" center center);
}
#disc-status[status=mismatch] icon {
decorator: text("&#xe002;" center center);
}
#disc-status[status=unknown] icon {
decorator: text("&#xe887;" center center);
}
#disc-status[status=pending] icon {
decorator: text("&#xe863;" center center);
}
#disc-version {
font-size: 20dp;
}
/* TODO: Hidden until an actual update checker is introduced */ /* TODO: Hidden until an actual update checker is introduced */
.update { .update {
display: none; display: none;
font-size: 16dp; font-size: 16dp;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
color: #D8F999;
} }
.detail, .detail,
-48
View File
@@ -1,21 +1,10 @@
tab-bar { tab-bar {
display: flex; display: flex;
position: relative;
min-width: 0; min-width: 0;
overflow: auto hidden; overflow: auto hidden;
clip: always;
text-transform: uppercase; text-transform: uppercase;
} }
tab-bar scrollbarhorizontal,
tab-bar scrollbarhorizontal sliderarrowdec,
tab-bar scrollbarhorizontal sliderarrowinc,
tab-bar scrollbarhorizontal slidertrack,
tab-bar scrollbarhorizontal sliderbar {
width: 0;
height: 0;
}
tab-bar tab { tab-bar tab {
flex: 0 0 auto; flex: 0 0 auto;
padding: 0 24dp; padding: 0 24dp;
@@ -42,40 +31,3 @@ tab-bar tab:hover {
tab-bar tab:active { tab-bar tab:active {
decorator: vertical-gradient(#c2a42d10 #c2a42d40); decorator: vertical-gradient(#c2a42d10 #c2a42d40);
} }
tab-bar[closable] tab-end-spacer {
display: block;
flex: 0 0 64dp;
width: 64dp;
pointer-events: none;
}
tab-bar[closable] close {
display: block;
position: fixed;
top: 8dp;
right: 8dp;
z-index: 1;
width: 48dp;
height: 48dp;
font-family: "Material Symbols Rounded";
font-weight: normal;
font-size: 24dp;
color: rgba(224, 219, 200, 70%);
backdrop-filter: blur(2dp);
border-radius: 6dp;
decorator: text("&#xe5cd;" center center);
transition: color background-color 0.12s linear-in-out;
cursor: pointer;
}
tab-bar[closable] close:hover,
tab-bar[closable] close:focus-visible {
color: #fff;
background-color: rgba(194, 164, 45, 24%);
}
tab-bar[closable] close:active {
color: #fff;
background-color: rgba(194, 164, 45, 40%);
}
-147
View File
@@ -1,147 +0,0 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
overflow: visible;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: "Fira Sans Condensed";
font-size: 24dp;
color: #FFFFFF;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
}
.tuner-root {
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
decorator: vertical-gradient(#00000000 #151610F2);
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
.tuner-root[open] {
filter: opacity(1);
}
.tuner {
width: 100%;
max-width: 1216dp;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
gap: 24dp;
padding: 48dp 64dp;
}
@media (max-height: 800dp) {
.tuner-root {
min-height: 38%;
}
.tuner {
gap: 16dp;
padding: 32dp 48dp;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
.carousel-container {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
min-width: 0;
}
.description {
font-size: 18dp;
line-height: 22dp;
color: rgba(255, 255, 255, 50%);
}
.divider {
margin: 1dp 0;
border-top: 1dp rgba(217, 217, 217, 50%);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
footer-button {
display: block;
width: 100%;
max-width: 220dp;
border: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 20dp;
line-height: 24dp;
text-transform: uppercase;
color: #FFFFFF;
opacity: 1;
cursor: pointer;
}
footer-button.return {
text-align: left;
}
footer-button.reset {
text-align: right;
}
.stepped-carousel {
display: flex;
align-items: center;
justify-content: center;
gap: 16dp;
width: auto;
min-width: 246dp;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
}
.stepped-carousel-value {
line-height: 29dp;
min-width: 166dp;
text-align: center;
white-space: nowrap;
opacity: 0.9;
}
.stepped-carousel-arrow {
width: 24dp;
height: 24dp;
min-width: 24dp;
padding: 0;
border: 0;
background-color: transparent;
opacity: 1;
cursor: pointer;
font-family: "Material Symbols Rounded";
font-weight: normal;
}
+4 -249
View File
@@ -3,7 +3,6 @@
} }
body { body {
display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 64dp; padding: 64dp;
@@ -18,7 +17,6 @@ window {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
height: 100%; height: 100%;
width: 100%;
max-width: 1088dp; max-width: 1088dp;
max-height: 768dp; max-height: 768dp;
margin: auto; margin: auto;
@@ -34,15 +32,6 @@ window {
transition: filter transform 0.2s cubic-in-out; transition: filter transform 0.2s cubic-in-out;
} }
window.small {
height: auto;
width: auto;
}
window.modal {
max-width: 640dp;
}
window[open] { window[open] {
filter: opacity(1); filter: opacity(1);
transform: scale(1); transform: scale(1);
@@ -73,7 +62,7 @@ window tab-bar tab {
window content { window content {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 0;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
@@ -83,6 +72,7 @@ window content pane {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
flex: 1 1 0; flex: 1 1 0;
height: 100%;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
padding: 24dp; padding: 24dp;
@@ -100,10 +90,6 @@ window content pane > * {
flex: 0 0 auto; flex: 0 0 auto;
} }
window content pane:last-of-type > div {
line-height: 1.625;
}
window content pane > spacer { window content pane > spacer {
display: block; display: block;
/* Completes the 24dp bottom inset after the pane's 8dp gap. */ /* Completes the 24dp bottom inset after the pane's 8dp gap. */
@@ -198,11 +184,6 @@ button:not(:disabled):active {
box-shadow: #C2A42D 0 0 0 2dp; box-shadow: #C2A42D 0 0 0 2dp;
} }
button.modal-btn {
flex: 1 1 0;
text-align: center;
}
select-button { select-button {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -244,241 +225,15 @@ select-button key {
font-weight: bold; font-weight: bold;
font-size: 18dp; font-size: 18dp;
text-transform: uppercase; text-transform: uppercase;
flex: 0 1 auto; flex: 1 0 auto;
} }
select-button value { select-button value {
flex: 1 1 auto; margin-left: auto;
text-align: right;
font-size: 20dp; font-size: 20dp;
} }
select-button value.modified {
font-weight: bold;
}
select-button input { select-button input {
text-align: right; text-align: right;
font-size: 20dp; font-size: 20dp;
} }
icon {
width: 1em;
height: 1em;
font-family: "Material Symbols Rounded";
font-weight: normal;
display: inline-block;
vertical-align: middle;
}
icon.warning {
decorator: text("&#xe002;" center center);
}
icon.error {
decorator: text("&#xe000;" center center);
}
icon.verifying {
decorator: text("&#xe8b5;" center center);
}
icon.celebration {
decorator: text("&#xea65;" center center);
}
.achievement-row {
display: flex;
align-items: flex-start;
gap: 10dp;
padding: 12dp 0;
border-bottom: 1dp rgba(146, 135, 91, 30%);
}
.achievement-info {
display: block;
flex: 1 1 0;
min-width: 0;
}
.achievement-header {
display: flex;
align-items: center;
}
.achievement-name {
flex: 1;
font-weight: bold;
}
.achievement-name.unlocked {
color: #ffa826;
}
.achievement-badge {
font-size: 14dp;
opacity: 0.7;
}
.achievement-badge.unlocked {
color: #44cc55;
opacity: 1;
}
.achievement-badge.locked {
color: #cc4444;
opacity: 1;
}
.achievement-desc {
display: block;
color: rgba(224, 219, 200, 55%);
font-size: 16dp;
margin: 4dp 0 0 0;
}
.achievement-progress {
display: block;
font-size: 13dp;
color: rgba(224, 219, 200, 45%);
}
progressbar {
display: block;
width: 100%;
height: 6dp;
border-radius: 3dp;
background-color: rgba(255, 255, 255, 10%);
margin: 6dp 0 2dp 0;
}
progressbar.progress-done fill {
background-color: #44aa22;
border-radius: 3dp;
}
progressbar.progress-ongoing fill {
background-color: #2255bb;
border-radius: 3dp;
}
button.achievement-clear {
flex: 0 0 auto;
align-self: center;
font-size: 14dp;
padding: 2dp 8dp;
opacity: 0.45;
}
.preset-grid {
display: flex;
flex-direction: row;
gap: 20dp;
flex: 0 1 auto;
align-items: flex-start;
width: 100%;
}
.preset-col {
display: flex;
flex-flow: column;
gap: 12dp;
flex: 1 1 0;
}
.preset-desc {
display: block;
font-size: 16dp;
text-align: center;
}
.modal-dialog {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 24dp;
gap: 20dp;
flex: 0 1 auto;
width: 100%;
text-align: left;
}
window.modal.danger {
border: 2dp #852221;
}
.modal-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
flex: 0 0 auto;
gap: 16dp;
}
.modal-header icon {
font-size: 24dp;
color: #92875B;
}
.modal-title {
display: block;
font-family: "Fira Sans Condensed";
font-weight: bold;
text-transform: uppercase;
font-size: 18dp;
color: #92875B;
flex: 1 1 auto;
}
window.modal.danger .modal-title,
window.modal.danger .modal-header icon {
color: #B3261E;
}
.modal-body {
display: block;
width: 100%;
flex: 0 1 auto;
min-width: 0;
font-size: 20dp;
color: #FFFFFF;
font-weight: normal;
}
.verification-progress {
display: flex;
flex-direction: column;
gap: 10dp;
width: 100%;
}
.verification-file {
display: block;
font-size: 17dp;
color: #FFFFFF;
}
progressbar.verification-progress-bar {
height: 8dp;
margin: 2dp 0 0 0;
}
.verification-detail {
display: block;
font-size: 14dp;
color: rgba(224, 219, 200, 65%);
}
.modal-actions {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: stretch;
align-items: stretch;
gap: 12dp;
width: 100%;
flex: 0 0 auto;
padding-top: 4dp;
}
-6
View File
@@ -2,7 +2,6 @@
#include "Z2AudioLib/Z2SoundInfo.h" #include "Z2AudioLib/Z2SoundInfo.h"
#if TARGET_PC #if TARGET_PC
#include "dusk/audio/DuskDsp.hpp" #include "dusk/audio/DuskDsp.hpp"
#include "dusk/settings.h"
#include <cmath> #include <cmath>
#endif #endif
#include "Z2AudioLib/Z2Calc.h" #include "Z2AudioLib/Z2Calc.h"
@@ -706,11 +705,6 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) {
f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
Vec local_54 = param_0; Vec local_54 = param_0;
local_54.y = 0.0f; local_54.y = 0.0f;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
local_54.x = -local_54.x;
}
#endif
f32 dVar6 = VECMag(&local_54); f32 dVar6 = VECMag(&local_54);
if (dVar6 < 0.1f) { if (dVar6 < 0.1f) {
+20 -16
View File
@@ -44,14 +44,16 @@ void daAlink_c::handleWolfHowl() {
bool canHowl = false; bool canHowl = false;
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) { if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) {
if (checkMidnaRide()) { if (!checkForestOldCentury()) {
if ((checkWolf() && if (checkMidnaRide()) {
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) || if ((checkWolf() &&
(!checkWolf() && (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) && (!checkWolf() &&
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10)))) (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
{ (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
canHowl = true; {
canHowl = true;
}
} }
} }
} }
@@ -122,14 +124,16 @@ void daAlink_c::handleQuickTransform() {
bool canTransform = false; bool canTransform = false;
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) { if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) {
if (checkMidnaRide()) { if (!checkForestOldCentury()) {
if ((checkWolf() && if (checkMidnaRide()) {
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) || if ((checkWolf() &&
(!checkWolf() && (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) && (!checkWolf() &&
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10)))) (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
{ (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
canTransform = true; {
canTransform = true;
}
} }
} }
} }
+2 -2
View File
@@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() {
} }
if (mProcVar2.field_0x300c == 0) { if (mProcVar2.field_0x300c == 0) {
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 4); set3DStatus(BUTTON_STATUS_HOLD_ON, 4);
} }
} else { } else {
if (mProcVar3.field_0x300e != 0) { if (mProcVar3.field_0x300e != 0) {
@@ -2731,7 +2731,7 @@ int daAlink_c::procHorseRun() {
} }
if (mProcVar2.field_0x300c == 0) { if (mProcVar2.field_0x300c == 0) {
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1); set3DStatus(BUTTON_STATUS_HOLD_ON, 1);
} }
} }
+1 -1
View File
@@ -41,7 +41,7 @@ void daAlink_c::setOriginalHeap(JKRExpHeap** i_ppheap, u32 i_size) {
u32 var_r28 = 0x10; u32 var_r28 = 0x10;
u32 size = ROUND(i_size, 16); u32 size = ROUND(i_size, 16);
#if TARGET_PC #if TARGET_PC
size *= 20; // Increase Link's heap size to prevent mods from crashing with higher-quality models. size *= 2;
#endif #endif
JKRHeap* parent = mDoExt_getGameHeap(); JKRHeap* parent = mDoExt_getGameHeap();
-4
View File
@@ -254,11 +254,7 @@ BOOL daBdoor_c::checkArea() {
if (fabsf(vec.z) > 100.0f) { if (fabsf(vec.z) > 100.0f) {
return false; return false;
} }
#ifdef TARGET_PC
return (s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000 ? 1 : 0;
#else
return (s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000 ? 1 : 0; return (s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000 ? 1 : 0;
#endif
} }
BOOL daBdoor_c::checkFront() { BOOL daBdoor_c::checkFront() {
-4
View File
@@ -825,11 +825,7 @@ int daBdoorL1_c::checkArea() {
if (fabsf(local_48.z) > 100.0f) { if (fabsf(local_48.z) > 100.0f) {
return 0; return 0;
} }
#ifdef TARGET_PC
if ((s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000) {
#else
if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) { if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) {
#endif
return 1; return 1;
} else { } else {
return 0; return 0;
-4
View File
@@ -348,11 +348,7 @@ int daBdoorL5_c::checkArea() {
if (fabsf(local_48.z) > 100.0f) { if (fabsf(local_48.z) > 100.0f) {
return 0; return 0;
} }
#ifdef TARGET_PC
if ((s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000) {
#else
if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) { if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) {
#endif
return 1; return 1;
} else { } else {
return 0; return 0;
+1 -5
View File
@@ -1317,12 +1317,8 @@ int daMBdoorL1_c::checkArea() {
if (fabsf(local_48.z) > 110.0f) { if (fabsf(local_48.z) > 110.0f) {
return 0; return 0;
} }
#ifdef TARGET_PC
if ((s16)((s32)fabs(angle - 0x7fff - player->current.angle.y) & 0xffff) > 0x4000) {
#else
if ((s16)fabs((f64)(angle - 0x7fff - player->current.angle.y)) > 0x4000) { if ((s16)fabs((f64)(angle - 0x7fff - player->current.angle.y)) > 0x4000) {
#endif
return 0; return 0;
} else { } else {
return 1; return 1;
-6
View File
@@ -517,12 +517,6 @@ void daE_OctBg_c::core_fish_attack() {
field_0xbaf = cM_rndFX(80.0f) + 100.0f; field_0xbaf = cM_rndFX(80.0f) + 100.0f;
} }
} }
#if AVOID_UB
else {
in_f31 = cM_rndF(400.0f) + 80.0f;
field_0xbaf = cM_rndFX(80.0f) + 100.0f;
}
#endif
} else if (current.pos.abs(cStack_5c) < 400.0f) { } else if (current.pos.abs(cStack_5c) < 400.0f) {
in_f31 = cM_rndF(50.0f) + 20.0f; in_f31 = cM_rndF(50.0f) + 20.0f;
field_0xbaf = cM_rndFX(20.0f) + 40.0f; field_0xbaf = cM_rndFX(20.0f) + 40.0f;
-8
View File
@@ -3519,15 +3519,7 @@ void daKago_c::action() {
checkSizeBg(); checkSizeBg();
setFlyEffect(); setFlyEffect();
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
mStickX = -mDoCPd_c::getStickX3D(PAD_1);
} else {
mStickX = mDoCPd_c::getStickX3D(PAD_1);
}
#else
mStickX = mDoCPd_c::getStickX3D(PAD_1); mStickX = mDoCPd_c::getStickX3D(PAD_1);
#endif
mStickY = mDoCPd_c::getStickY(PAD_1); mStickY = mDoCPd_c::getStickY(PAD_1);
u8 prevIsWaterfall = mIsWaterfall; u8 prevIsWaterfall = mIsWaterfall;
-2
View File
@@ -14,7 +14,6 @@
#include "d/actor/d_a_obj_automata.h" #include "d/actor/d_a_obj_automata.h"
#include "d/d_msg_object.h" #include "d/d_msg_object.h"
#include "d/actor/d_a_obj_scannon.h" #include "d/actor/d_a_obj_scannon.h"
#include "dusk/frame_interpolation.h"
#include <cstring> #include <cstring>
const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = { const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = {
@@ -1399,7 +1398,6 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) {
old.pos = current.pos; old.pos = current.pos;
setAngle(cM_deg2s(5.0f * f32(mPath.getArg0()))); setAngle(cM_deg2s(5.0f * f32(mPath.getArg0())));
mEventTimer = mPath.getArg2(); mEventTimer = mPath.getArg2();
dusk::frame_interp::request_presentation_sync();
} }
} else if (!mHide) { } else if (!mHide) {
mHide = 1; mHide = 1;
-51
View File
@@ -11,8 +11,6 @@
#include "d/d_bg_w.h" #include "d/d_bg_w.h"
#include "d/d_cc_uty.h" #include "d/d_cc_uty.h"
#include "d/d_com_inf_game.h" #include "d/d_com_inf_game.h"
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
struct daObjKLift00_HIO_c : public mDoHIO_entry_c { struct daObjKLift00_HIO_c : public mDoHIO_entry_c {
daObjKLift00_HIO_c(); daObjKLift00_HIO_c();
@@ -297,11 +295,6 @@ int daObjKLift00_c::Create() {
if(getLock()) if(getLock())
mStopSwingingFrames = 5; mStopSwingingFrames = 5;
#if TARGET_PC
mChainInterpPrevValid = false;
mChainInterpCurrValid = false;
#endif
return 1; return 1;
} }
@@ -443,34 +436,6 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) {
return 1; return 1;
} }
#if TARGET_PC
static void klift00_interp_callback(bool isSimFrame, void* pUserWork) {
static_cast<daObjKLift00_c*>(pUserWork)->onInterpCallback();
}
void daObjKLift00_c::onInterpCallback() {
if (!mChainInterpPrevValid || !mChainInterpCurrValid) {
return;
}
const f32 alpha = dusk::frame_interp::get_interpolation_step();
cXyz savedPositions[64];
for (int i = 0; i < mNumChains; i++) {
savedPositions[i] = mChainPositions[i].mCurrentPos;
const cXyz& p0 = mChainInterpPrev[i];
const cXyz& p1 = mChainInterpCurr[i];
mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha;
}
setMtx();
for (int i = 0; i < mNumChains; i++) {
mChainPositions[i].mCurrentPos = savedPositions[i];
}
}
#endif
int daObjKLift00_c::Draw() { int daObjKLift00_c::Draw() {
g_env_light.settingTevStruct(16, &current.pos, &tevStr); g_env_light.settingTevStruct(16, &current.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr); g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
@@ -492,22 +457,6 @@ int daObjKLift00_c::Draw() {
dComIfGd_setList(); dComIfGd_setList();
#if TARGET_PC
if (dusk::getSettings().game.enableFrameInterpolation) {
if (mChainInterpCurrValid) {
memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz));
mChainInterpPrevValid = true;
}
for (int i = 0; i < mNumChains; i++) {
mChainInterpCurr[i] = mChainPositions[i].mCurrentPos;
}
mChainInterpCurrValid = true;
dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this);
}
#endif
return 1; return 1;
} }
-102
View File
@@ -1040,11 +1040,6 @@ void dCamera_c::debugDrawInit() {
bool dCamera_c::Run() { bool dCamera_c::Run() {
#if TARGET_PC #if TARGET_PC
ResetView(); ResetView();
if (executeDebugFlyCam()) {
mFrameCounter++;
mTicks++;
return true;
}
#endif #endif
daAlink_c* link = daAlink_getAlinkActorClass(); daAlink_c* link = daAlink_getAlinkActorClass();
@@ -7479,104 +7474,7 @@ bool dCamera_c::test2Camera(s32 param_0) {
return false; return false;
} }
static constexpr f32 FLYCAM_SPEED = 0.5f;
static constexpr f32 FLYCAM_FAST_SPEED = 4.0f;
static constexpr f32 FLYCAM_ROTATION_SPEED = 0.002f;
static constexpr f32 FLYCAM_TRIGGER_DEADZONE = 20.0f;
#if TARGET_PC #if TARGET_PC
bool dCamera_c::executeDebugFlyCam() {
if (!dusk::getSettings().game.debugFlyCam) {
if (mDebugFlyCam.initialized) {
deactivateDebugFlyCam();
}
return false;
}
dEvt_control_c* event = dComIfGp_getEvent();
if (event == nullptr) {
return false;
}
if (!mDebugFlyCam.initialized && (event->mEventStatus != 0 || dComIfGp_isPauseFlag())) {
dusk::getSettings().game.debugFlyCam.setValue(false);
return false;
}
if (!mDebugFlyCam.initialized) {
mDebugFlyCam.savedCenter = mCenter;
mDebugFlyCam.savedEye = mEye;
mDebugFlyCam.savedFovy = mFovy;
mDebugFlyCam.savedBank = mBank;
f32 dx = mCenter.x - mEye.x;
f32 dy = mCenter.y - mEye.y;
f32 dz = mCenter.z - mEye.z;
mDebugFlyCam.yaw = atan2f(dz, dx);
f32 horizontal = sqrtf(dx * dx + dz * dz);
mDebugFlyCam.pitch = atan2f(dy, horizontal);
mDebugFlyCam.initialized = true;
}
event->mEventStatus = 1;
dComIfGp_getEventManager().setCameraPlay(1);
interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0);
f32 stickY = pad.mMainStickPosY * 72.0f;
f32 stickX = pad.mMainStickPosX * 72.0f;
f32 cStickY = pad.mCStickPosY * 59.0f;
f32 cStickX = pad.mCStickPosX * 59.0f;
f32 trigL = pad.mTriggerLeft * 150.0f;
f32 trigR = pad.mTriggerRight * 150.0f;
f32 verticalDisp = 0.0f;
if (trigR >= FLYCAM_TRIGGER_DEADZONE) {
verticalDisp += trigR;
}
if (trigL >= FLYCAM_TRIGGER_DEADZONE) {
verticalDisp -= trigL;
}
f32 moveDy = stickY * sinf(mDebugFlyCam.pitch) + verticalDisp;
f32 moveDx = stickY * cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) - stickX * sinf(mDebugFlyCam.yaw);
f32 moveDz = stickY * sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) + stickX * cosf(mDebugFlyCam.yaw);
f32 speed = mDoCPd_c::getHoldZ(PAD_1) ? FLYCAM_FAST_SPEED : FLYCAM_SPEED;
mEye.x += speed * moveDx;
mEye.y += speed * moveDy;
mEye.z += speed * moveDz;
static constexpr f32 FLYCAM_TARGET_DIST = 100.0f;
mCenter.x = mEye.x + cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
mCenter.z = mEye.z + sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
mCenter.y = mEye.y + sinf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
Reset(mCenter, mEye);
f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? cStickX : -cStickX;
mDebugFlyCam.yaw += yawInput * FLYCAM_ROTATION_SPEED;
mDebugFlyCam.yaw = fmodf(mDebugFlyCam.yaw + 2.0f * (f32)M_PI, 2.0f * (f32)M_PI);
f32 maxPitch = (f32)M_PI / 2.0f - 0.1f;
f32 minPitch = -(f32)M_PI / 2.0f + 0.1f;
mDebugFlyCam.pitch = std::clamp(mDebugFlyCam.pitch + cStickY * FLYCAM_ROTATION_SPEED, minPitch, maxPitch);
return true;
}
void dCamera_c::deactivateDebugFlyCam() {
Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val());
dEvt_control_c* event = dComIfGp_getEvent();
if (event != nullptr) {
event->mEventStatus = 0;
}
dComIfGp_getEventManager().setCameraPlay(0);
mDebugFlyCam.initialized = false;
}
bool dCamera_c::freeCamera() { bool dCamera_c::freeCamera() {
if (dusk::getSettings().game.freeCamera && mGear == 1) { if (dusk::getSettings().game.freeCamera && mGear == 1) {
mGear = 0; mGear = 0;
-73
View File
@@ -11,62 +11,6 @@
#include "JSystem/JGadget/define.h" #include "JSystem/JGadget/define.h"
#include <cstring> #include <cstring>
#include "dusk/logging.h"
#if TARGET_PC
#include "dusk/ui/ui.hpp"
namespace {
static int sJaiSkip = -1;
static JSUList<JAIStream>* get_stream_list() {
return Z2GetSoundMgr()->getStreamMgr()->getStreamList();
}
static int get_stream_count(JSUList<JAIStream>* list) {
int i = 0;
for (JSULink<JAIStream>* l = list != nullptr ? list->getFirst() : nullptr; l != nullptr;
l = l->getNext()) {
i++;
}
return i;
}
static void pause_stream(int skip_first, bool paused) {
int i = 0;
JSUList<JAIStream>* list = get_stream_list();
for (JSULink<JAIStream>* l = list->getFirst(); l != nullptr; l = l->getNext(), ++i) {
if (i >= skip_first) {
l->getObject()->pause(paused);
}
}
}
static void pause_streams(int skip_first) {
if (!dusk::ui::is_prelaunch_open()) {
return;
}
JSUList<JAIStream>* list = get_stream_list();
if (list == nullptr || get_stream_count(list) <= skip_first) {
return;
}
pause_stream(skip_first, true);
sJaiSkip = skip_first;
}
static void unpause_streams(bool require_prelaunch_hidden) {
if (sJaiSkip < 0) {
return;
}
if (require_prelaunch_hidden && dusk::ui::is_prelaunch_open()) {
return;
}
pause_stream(sJaiSkip, false);
sJaiSkip = -1;
}
} // namespace
#endif
s16 dDemo_c::m_branchId = -1; s16 dDemo_c::m_branchId = -1;
namespace { namespace {
@@ -1062,16 +1006,7 @@ int dDemo_c::start(u8 const* p_data, cXyz* p_translation, f32 rotationY) {
m_control->setSuspend(0); m_control->setSuspend(0);
} }
#if TARGET_PC
const int existing_streams = get_stream_count(get_stream_list());
#endif
m_control->forward(0); m_control->forward(0);
#if TARGET_PC
pause_streams(existing_streams);
#endif
m_translation = p_translation; m_translation = p_translation;
if (m_translation != NULL) { if (m_translation != NULL) {
@@ -1099,10 +1034,6 @@ static void dummyString2() {
void dDemo_c::end() { void dDemo_c::end() {
JUT_ASSERT(1956, m_system != NULL); JUT_ASSERT(1956, m_system != NULL);
#if TARGET_PC
unpause_streams(false);
#endif
m_control->destroyObject_all(); m_control->destroyObject_all();
m_object->remove(); m_object->remove();
m_data = NULL; m_data = NULL;
@@ -1123,10 +1054,6 @@ void dDemo_c::branch() {
int dDemo_c::update() { int dDemo_c::update() {
JUT_ASSERT(2064, m_system != NULL); JUT_ASSERT(2064, m_system != NULL);
#if TARGET_PC
unpause_streams(true);
#endif
if (m_data == NULL) { if (m_data == NULL) {
if (m_branchData == NULL) { if (m_branchData == NULL) {
return 0; return 0;
+4 -4
View File
@@ -1595,7 +1595,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58); calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ); getPack(field_0x80, &mPackX, &mPackZ);
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX; mCenterX += mPackX;
mCenterZ -= mPackZ; mCenterZ -= mPackZ;
mCenterX += field_0x64; mCenterX += field_0x64;
mCenterZ += mPackPlusZ; mCenterZ += mPackPlusZ;
@@ -1657,7 +1657,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58); calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ); getPack(field_0x80, &mPackX, &mPackZ);
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX; mCenterX += mPackX;
mCenterZ -= mPackZ; mCenterZ -= mPackZ;
} }
break; break;
@@ -1737,7 +1737,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58); calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ); getPack(field_0x80, &mPackX, &mPackZ);
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX; mCenterX += mPackX;
mCenterZ -= mPackZ; mCenterZ -= mPackZ;
field_0x8f = 4; field_0x8f = 4;
#if DEBUG #if DEBUG
@@ -1829,7 +1829,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
sp14 += temp_f31_2 * (spC - sp14); sp14 += temp_f31_2 * (spC - sp14);
sp10 += temp_f31_2 * (sp8 - sp10); sp10 += temp_f31_2 * (sp8 - sp10);
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp14 :) sp14; mCenterX += sp14;
mCenterZ -= sp10; mCenterZ -= sp10;
break; break;
} }
+31 -121
View File
@@ -15,47 +15,13 @@
#include <cstring> #include <cstring>
#ifdef TARGET_PC #ifdef TARGET_PC
#include <span>
#include <numbers>
#include <array>
constexpr u16 kMapResolutionMultiplier = 4; constexpr u16 kMapResolutionMultiplier = 4;
constexpr u16 kMapImageSide = 16 * kMapResolutionMultiplier; constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier;
constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide;
typedef std::function<u8(size_t, size_t)> PaintI8Fn;
void paint_i8(std::span<u8> dst, size_t width, PaintI8Fn paint) {
const auto blocksAcross = width >> 3;
for (size_t i = 0; i < dst.size(); i++) {
// 8x4 block swizzling for I8
const auto blockIdx = i >> 5;
const auto localIdx = i & 31;
const auto blockY = blockIdx / blocksAcross;
const auto blockX = blockIdx % blocksAcross;
const auto localY = localIdx >> 3;
const auto localX = localIdx & 7;
const auto x = (blockX << 3) + localX;
const auto y = (blockY << 2) + localY;
dst[i] = paint(x, y);
}
}
#endif #endif
void dMpath_n::dTexObjAggregate_c::create() { void dMpath_n::dTexObjAggregate_c::create() {
static int const data[7] = { static int const data[7] = {
79, // 0: im_map_icon_square_4i.bti 79, 80, 77, 78, 76, 81, 82,
80, // 1: im_map_icon_tresurebox_4i.bti
77, // 2: im_map_icon_enter_4i.bti
78, // 3: im_map_icon_nijumaru_4i.bti
76, // 4: im_map_icon_circle_4i.bti
81, // 5: im_map_icon_try_force_4i.bti
82, // 6: map_icon_circle16x16_4i.bti
}; };
for (int lp1 = 0; lp1 < 7; lp1++) { for (int lp1 = 0; lp1 < 7; lp1++) {
@@ -69,101 +35,45 @@ void dMpath_n::dTexObjAggregate_c::create() {
} }
#if TARGET_PC #if TARGET_PC
static bool hqTexsDrawn = false; auto hqCircle = JKR_NEW TGXTexObj();
static u8 hqCircleData[kMapImageTotalPixels]; static bool hqCircleDrawn = false;
static u8 hqCircleAltData[kMapImageTotalPixels]; static u8 hqCircleData[kMapCircleSize * kMapCircleSize];
static u8 hqNijumaruData[kMapImageTotalPixels];
static u8 hqEnterData[kMapImageTotalPixels];
static u8 hqTryForceData[kMapImageTotalPixels];
if (!hqTexsDrawn) { if (!hqCircleDrawn) {
constexpr auto center = kMapImageSide / 2.0f; const auto center = kMapCircleSize / 2.0f;
constexpr auto radiusSq = center * center; const auto radiusSq = center * center;
const auto blocksAcross = kMapCircleSize >> 3;
const auto totalPixels = sizeof(hqCircleData);
// 6: map_icon_circle16x16_4i.bti - simple circle for (size_t i = 0; i < totalPixels; i++) {
paint_i8(std::span{hqCircleData}, kMapImageSide, [=](auto x, auto y) { // 8x4 block swizzling for I8
const auto dx = (x + 0.5f) - center; const auto blockIdx = i >> 5;
const auto dy = (y + 0.5f) - center; const auto localIdx = i & 31;
return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0;
});
// 4: im_map_icon_circle_4i.bti - outlined circle const auto blockY = blockIdx / blocksAcross;
paint_i8(std::span{hqCircleAltData}, kMapImageSide, [=](auto x, auto y) { const auto blockX = blockIdx % blocksAcross;
constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f;
constexpr auto innerRadiusSq = innerRadius * innerRadius; const auto localY = localIdx >> 3;
const auto localX = localIdx & 7;
const auto x = (blockX << 3) + localX;
const auto y = (blockY << 2) + localY;
const auto dx = (x + 0.5f) - center; const auto dx = (x + 0.5f) - center;
const auto dy = (y + 0.5f) - center; const auto dy = (y + 0.5f) - center;
const auto dSq = dx * dx + dy * dy;
return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0; // the original texture is in I4 format and uses 1 to indicate if inside the circle
}); // so we scale to I8 range: 255 / 15 = 17
hqCircleData[i] = (dx * dx + dy * dy < radiusSq) ? 17 : 0;
// 3: im_map_icon_nijumaru_4i.bti - concentric rings }
paint_i8(std::span{hqNijumaruData}, kMapImageSide, [=](auto x, auto y) { hqCircleDrawn = true;
constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22};
const auto dx = (x + 0.5f) - center;
const auto dy = (y + 0.5f) - center;
const auto dSq = dx * dx + dy * dy;
if (dSq < radiusSq) {
const auto ringIndex =
static_cast<size_t>(std::trunc(std::sqrt(dSq) / kMapImageSide * 12));
return nijumaruRings[ringIndex];
}
return u8{0};
});
// 2: im_map_icon_enter_4i.bti - outlined octagram
paint_i8(std::span{hqEnterData}, kMapImageSide, [=](auto x, auto y) {
constexpr auto outlineWidth = kMapImageSide / 6.0f;
const auto adx = std::abs((x + 0.5f) - center);
const auto ady = std::abs((y + 0.5f) - center);
const auto dist =
std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v<float>) -
kMapImageSide / 2.0f;
return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33);
});
// 5: im_map_icon_try_force_4i.bti - outlined circle with triangle
paint_i8(std::span{hqTryForceData}, kMapImageSide, [=](auto x, auto y) {
constexpr auto innerRadiusNorm = 5.0f / 12.0f;
constexpr auto innerRadius = kMapImageSide * innerRadiusNorm;
constexpr auto innerRadiusSq = innerRadius * innerRadius;
constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f;
const auto dx = (x + 0.5f) - center;
const auto dy = (y + 0.5f) - center;
const auto dSq = dx * dx + dy * dy;
const auto triSideDist = (std::numbers::sqrt3_v<float> * std::abs(dx) - dy) * 0.5f;
const auto insideTri = std::max(dy, triSideDist) < triRadius;
return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0);
});
hqTexsDrawn = true;
} }
constexpr auto replacements = std::to_array<std::pair<size_t, const u8*> >({ GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP,
{2, hqEnterData}, GX_CLAMP, GX_FALSE);
{3, hqNijumaruData}, GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
{4, hqCircleAltData}, mp_texObj[6] = hqCircle;
{5, hqTryForceData},
{6, hqCircleData},
});
for (const auto& [idx, data] : replacements) {
JKR_DELETE(mp_texObj[idx]);
const auto texobj = JKR_NEW TGXTexObj();
GXInitTexObj(
texobj, data, kMapImageSide, kMapImageSide, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(texobj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
mp_texObj[idx] = texobj;
}
#endif #endif
} }
+1 -4
View File
@@ -942,10 +942,7 @@ void dMenu_DmapBg_c::draw() {
f32 local_28c = mpBackTexture->getBounds().i.x; f32 local_28c = mpBackTexture->getBounds().i.x;
mpBackTexture->setBlackWhite(color_black, color_white); mpBackTexture->setBlackWhite(color_black, color_white);
mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(), mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(),
mpBackTexture->getHeight(), mpBackTexture->getHeight(), false, false, false);
IF_DUSK(dusk::getSettings().game.enableMirrorMode ? true :) false,
false,
false);
grafContext->scissor(field_0xd94 + mDoGph_gInf_c::getMinXF(), grafContext->scissor(field_0xd94 + mDoGph_gInf_c::getMinXF(),
scissor_top, mDoGph_gInf_c::getWidthF(), scissor_top, mDoGph_gInf_c::getWidthF(),
+2 -2
View File
@@ -368,7 +368,7 @@ void dMenu_StageMapCtrl_c::initGetTreasureList(u8 param_0, s8 param_1) {
} }
inline static s16 rightModeCnvRot(s16 param_0) { inline static s16 rightModeCnvRot(s16 param_0) {
return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) param_0; return param_0;
} }
bool dMenu_StageMapCtrl_c::getTreasureList(f32* o_posX, f32* o_posY, s8* param_2, u8* o_swbit, bool dMenu_StageMapCtrl_c::getTreasureList(f32* o_posX, f32* o_posY, s8* param_2, u8* o_swbit,
@@ -405,7 +405,7 @@ bool dMenu_StageMapCtrl_c::getTreasureList(f32* o_posX, f32* o_posY, s8* param_2
} }
inline static f32 rightModeCnvPos(f32 param_0) { inline static f32 rightModeCnvPos(f32 param_0) {
return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) param_0; return param_0;
} }
void dMenu_StageMapCtrl_c::cnvPosTo2Dpos(f32 param_0, f32 param_1, f32* param_2, void dMenu_StageMapCtrl_c::cnvPosTo2Dpos(f32 param_0, f32 param_1, f32* param_2,
-18
View File
@@ -919,20 +919,9 @@ void dMenu_Fmap_c::region_map_proc() {
} }
mpDraw2DBack->regionMapMove(mpStick); mpDraw2DBack->regionMapMove(mpStick);
int stage_no, room_no; int stage_no, room_no;
#if TARGET_PC
f32 arrow_pos_x = mpDraw2DBack->getArrowPos2DX();
if (dusk::getSettings().game.enableMirrorMode) {
arrow_pos_x = mpDraw2DBack->getMirrorPosX(arrow_pos_x, 0.0f);
}
f32 pos_x = arrow_pos_x - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f;
#else
f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF() f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF()
- mDoGph_gInf_c::getWidthF() * 0.5f; - mDoGph_gInf_c::getWidthF() * 0.5f;
#endif
f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f; f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f;
mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y, mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y,
mStayStageNo, &stage_no, &room_no); mStayStageNo, &stage_no, &room_no);
if (mStageCursor != stage_no || mRoomCursor != room_no || mResetAreaName) { if (mStageCursor != stage_no || mRoomCursor != room_no || mResetAreaName) {
@@ -2475,13 +2464,6 @@ void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) {
f32 arrow_y = mpDraw2DBack->getArrowPos2DY(); f32 arrow_y = mpDraw2DBack->getArrowPos2DY();
u8 uVar6 = 0xff; u8 uVar6 = 0xff;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
arrow_x = mpDraw2DBack->getMirrorPosX(arrow_x, 0.0f);
}
#endif
for (int i = 0; i < portal_dat->mCount; i++) { for (int i = 0; i < portal_dat->mCount; i++) {
if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1 if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1
&& checkDrawPortalIcon(portals[i].mStageNo, portals[i].mSwitchNo)) && checkDrawPortalIcon(portals[i].mStageNo, portals[i].mSwitchNo))
-24
View File
@@ -1043,12 +1043,6 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX, calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX,
(mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10); (mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
sp14 = getMirrorPosX(sp14, 0.0f);
}
#endif
mSelectRegion = 0xff; mSelectRegion = 0xff;
for (int i = 7; i >= 0; i--) { for (int i = 7; i >= 0; i--) {
int val = field_0x1230[i]; int val = field_0x1230[i];
@@ -1403,15 +1397,6 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
if (uVar10 != uVar9) { if (uVar10 != uVar9) {
bool b = 0; bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])); f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10]));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
b = true;
v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])),
mRegionMapSizeX[uVar10] * mZoom * 0.5f);
}
#endif
mpAreaTex[uVar10]->draw( mpAreaTex[uVar10]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar10] + field_0xf2c[uVar10])), v, mTransZ + (dVar13 + (mRegionMinMapY[uVar10] + field_0xf2c[uVar10])),
mRegionMapSizeX[uVar10] * mZoom, mRegionMapSizeY[uVar10] * mZoom, b, false, mRegionMapSizeX[uVar10] * mZoom, mRegionMapSizeY[uVar10] * mZoom, b, false,
@@ -1419,15 +1404,6 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
} else { } else {
bool b = 0; bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])); f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9]));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
b = true;
v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])),
mRegionMapSizeX[uVar9] * mZoom * 0.5f);
}
#endif
mpAreaTex[uVar9]->draw( mpAreaTex[uVar9]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar9] + field_0xf2c[uVar9])), v, mTransZ + (dVar13 + (mRegionMinMapY[uVar9] + field_0xf2c[uVar9])),
mRegionMapSizeX[uVar9] * mZoom, mRegionMapSizeY[uVar9] * mZoom, b, false, mRegionMapSizeX[uVar9] * mZoom, mRegionMapSizeY[uVar9] * mZoom, b, false,
-17
View File
@@ -343,11 +343,6 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
} }
f32 pos_x = icon_pos_x + i_posX; f32 pos_x = icon_pos_x + i_posX;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(pos_x, 0.0f);
}
#endif
mpDrawCursor->setPos(pos_x, icon_pos_y + i_posY); mpDrawCursor->setPos(pos_x, icon_pos_y + i_posY);
mpDrawCursor->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalCursorScale); mpDrawCursor->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalCursorScale);
mpDrawCursor->draw(); mpDrawCursor->draw();
@@ -369,12 +364,6 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
} }
f32 pos_x = (icon_pos_x + i_posX); f32 pos_x = (icon_pos_x + i_posX);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(pos_x, 0.0f);
}
#endif
mpPortalIcon->setPos(pos_x, icon_pos_y + i_posY); mpPortalIcon->setPos(pos_x, icon_pos_y + i_posY);
mpPortalIcon->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalIconScale); mpPortalIcon->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalIconScale);
mpPortalIcon->draw(); mpPortalIcon->draw();
@@ -410,12 +399,6 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
} }
f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2)); f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2);
}
#endif
mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)), mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)),
icon_size_x, icon_size_y, false, false, false); icon_size_x, icon_size_y, false, false, false);
-7
View File
@@ -1987,13 +1987,6 @@ bool jmessage_tSequenceProcessor::do_isReady() {
} }
#endif #endif
#if TARGET_PC
if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) {
field_0xb2 = 1;
pReference->setSendTimer(0);
}
#endif
if (dComIfGp_checkMesgBgm()) { if (dComIfGp_checkMesgBgm()) {
bool isItemMusicPlaying = true; bool isItemMusicPlaying = true;
if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET && if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET &&
+5 -14
View File
@@ -427,16 +427,6 @@ static void dummyStrings() {
dMsgObject_HIO_c g_MsgObject_HIO_c; dMsgObject_HIO_c g_MsgObject_HIO_c;
int dMsgObject_c::_execute() { int dMsgObject_c::_execute() {
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
// enable wii message index override
g_MsgObject_HIO_c.mMessageDisplay = 1;
} else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) {
g_MsgObject_HIO_c.mMessageDisplay = 0;
}
#endif
field_0x4c7 = 0; field_0x4c7 = 0;
if (mpTalkHeap != NULL) { if (mpTalkHeap != NULL) {
field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap); field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap);
@@ -1890,14 +1880,15 @@ bool dMsgObject_c::isShopItemMessage() {
{7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029, {7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029,
7044, 7045, 7053}, 7044, 7045, 7053},
// zel_02.bmg - Kakariko Shops // zel_02.bmg - Kakariko Shops
{5181, 5182, 5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, {5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, 5697, 5698,
5697, 5698, 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, 5989, 5990,
5989, 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999}, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999},
// zel_03.bmg - Death Mountain Shop // zel_03.bmg - Death Mountain Shop
{5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499}, {5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499},
// zel_04.bmg - Castle Town Shops // zel_04.bmg - Castle Town Shops
{5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431, {5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431,
5432, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452, 5462}, 5432, 5433, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452,
5462},
// zel_05.bmg - Oocca Shop // zel_05.bmg - Oocca Shop
{9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459} {9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459}
}); });
+16 -2
View File
@@ -1120,12 +1120,26 @@ int dScnLogo_c::create() {
checkProgSelect(); checkProgSelect();
if (field_0x20a != 0) { if (field_0x20a != 0) {
mExecCommand = EXEC_PROG_IN; mExecCommand = EXEC_PROG_IN;
#if TARGET_PC
mTimer = dusk::getSettings().game.skipWarningScreen ? 1 : 30;
#else
mTimer = 30; mTimer = 30;
#endif
field_0x218 = getProgressiveMode(); field_0x218 = getProgressiveMode();
} else { } else {
#if TARGET_PC #if TARGET_PC
mTimer = 0; // Possibly unnecessary but just in case if (dusk::getSettings().game.skipWarningScreen) {
mExecCommand = EXEC_DVD_WAIT; mTimer = 0; // Possibly unnecessary but just in case
mExecCommand = EXEC_DVD_WAIT;
} else {
if (mDoRst::getWarningDispFlag()) {
mTimer = 90;
mExecCommand = EXEC_NINTENDO_IN;
} else {
mTimer = 120;
mExecCommand = EXEC_WARNING_IN;
}
}
#else #else
if (mDoRst::getWarningDispFlag()) { if (mDoRst::getWarningDispFlag()) {
mTimer = 90; mTimer = 90;
+1 -12
View File
@@ -40,9 +40,8 @@
#include "JSystem/JKernel/JKRAramArchive.h" #include "JSystem/JKernel/JKRAramArchive.h"
#if TARGET_PC #if TARGET_PC
#include "dusk/autosave.h"
#include "dusk/memory.h" #include "dusk/memory.h"
#include "dusk/ui/ui.hpp" #include <dusk/autosave.h>
#endif #endif
#if DEBUG #if DEBUG
@@ -795,17 +794,7 @@ static int dScnPly_Execute(dScnPly_c* i_this) {
dJprev_c::get()->update(); dJprev_c::get()->update();
#endif #endif
#if TARGET_PC
if (!dusk::ui::is_prelaunch_open()) {
dDemo_c::update();
} else if (dusk::getSettings().audio.menuSounds) {
s8 reverb = dComIfGp_getReverb(dComIfGp_roomControl_getStayNo());
f32 fxMix = reverb / 127.0f;
g_mEnvSeMgr.field_0x144.startEnvSeDirLevel(JA_SE_ATM_WIND_1, fxMix, 1.0f);
}
#else
dDemo_c::update(); dDemo_c::update();
#endif
#if DEBUG #if DEBUG
dJcame_c::get()->update(); dJcame_c::get()->update();
+4 -31
View File
@@ -1,5 +1,3 @@
#include <memory>
#include "aurora/lib/logging.hpp" #include "aurora/lib/logging.hpp"
#include "os_report.h" #include "os_report.h"
@@ -23,35 +21,10 @@ static bool checkEnabled() {
static std::string FormatToString(const char* msg, va_list list) { static std::string FormatToString(const char* msg, va_list list) {
int ret = vsnprintf(nullptr, 0, msg, list); int ret = vsnprintf(nullptr, 0, msg, list);
if (ret <= 0) { std::string buf(ret, '\0');
return {}; vsnprintf(buf.data(), buf.size(), msg, list);
} buf.pop_back();
++ret; return buf;
std::unique_ptr<char[]> buf(new char[ret]);
vsnprintf(buf.get(), ret, msg, list);
buf[ret - 1] = '\0';
return {buf.get()};
}
void OSReport(const char* fmt, ...) {
if (!checkEnabled()) {
return;
}
va_list args;
va_start(args, fmt);
const auto str = FormatToString(fmt, args);
va_end(args);
Log.info("{}", str);
}
void OSPanic(const char* file, int line, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
const auto str = FormatToString(fmt, args);
va_end(args);
Log.fatal("[{}:{}] {}", file, line, str);
} }
void OSReport_Error(const char* fmt, ...) { void OSReport_Error(const char* fmt, ...) {
+12 -14
View File
@@ -1,15 +1,14 @@
#include "dusk/achievements.h" #include "dusk/achievements.h"
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
#include "d/actor/d_a_alink.h" #include "d/actor/d_a_alink.h"
#include "d/actor/d_a_npc4.h" #include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_player.h" #include "d/actor/d_a_player.h"
#include "d/d_com_inf_game.h"
#include "d/d_demo.h" #include "d/d_demo.h"
#include "d/d_meter2_info.h"
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "dusk/ui/ui.hpp"
#include "f_op/f_op_actor_mng.h"
#include "f_pc/f_pc_name.h" #include "f_pc/f_pc_name.h"
#include "f_op/f_op_actor_mng.h"
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
@@ -455,6 +454,12 @@ AchievementSystem& AchievementSystem::get() {
return instance; return instance;
} }
std::string AchievementSystem::consumePendingUnlock() {
std::string msg = std::move(m_pendingUnlocks.front());
m_pendingUnlocks.pop();
return msg;
}
std::vector<Achievement> AchievementSystem::getAchievements() const { std::vector<Achievement> AchievementSystem::getAchievements() const {
std::vector<Achievement> result; std::vector<Achievement> result;
result.reserve(m_entries.size()); result.reserve(m_entries.size());
@@ -554,14 +559,7 @@ void AchievementSystem::processEntry(Entry& e) {
if (nowUnlocked) { if (nowUnlocked) {
e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1; e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1;
e.achievement.unlocked = true; e.achievement.unlocked = true;
if (getSettings().game.enableAchievementNotifications) { m_pendingUnlocks.push(e.achievement.name);
ui::push_toast({
.type = "achievement",
.title = "Achievement Unlocked!",
.content = e.achievement.name,
.duration = std::chrono::seconds(5),
});
}
m_dirty = true; m_dirty = true;
} else if (progressChanged) { } else if (progressChanged) {
m_dirty = true; m_dirty = true;
+1 -5
View File
@@ -1,5 +1,4 @@
#include "dusk/autosave.h" #include "dusk/autosave.h"
#include "dusk/ui/ui.hpp"
#include "imgui/ImGuiConsole.hpp" #include "imgui/ImGuiConsole.hpp"
u8 mSaveBuffer[QUEST_LOG_SIZE * 3]; u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
@@ -84,9 +83,6 @@ void waitingForWrite() {
} }
void endAutoSave() { void endAutoSave() {
dusk::ui::push_toast({ dusk::g_imguiConsole.AddToast("Saving...", 2.0f);
.type = "autosave",
.duration = std::chrono::milliseconds(1500),
});
mAutoSaveProc = 0; mAutoSaveProc = 0;
} }
-1
View File
@@ -154,7 +154,6 @@ namespace dusk::config {
template class ConfigImpl<f64>; template class ConfigImpl<f64>;
template class ConfigImpl<std::string>; template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>; template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>; template class ConfigImpl<dusk::GameLanguage>;
} }
-867
View File
@@ -1,867 +0,0 @@
#ifdef DUSK_DISCORD
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "discord.hpp"
#include "dusk/logging.h"
#include "nlohmann/json.hpp"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <deque>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOSERVICE
#define NOIME
#include <windows.h>
#else
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#endif
namespace dusk::discord::rpc {
namespace {
using json = nlohmann::json;
constexpr uint32_t kRpcVersion = 1;
constexpr size_t kFrameHeaderSize = sizeof(uint32_t) * 2;
constexpr size_t kMaxFramePayloadSize = 64 * 1024;
constexpr auto kIoWait = std::chrono::milliseconds(500);
constexpr auto kShutdownClearTimeout = std::chrono::milliseconds(200);
constexpr auto kInitialReconnectDelay = std::chrono::milliseconds(500);
constexpr auto kMaxReconnectDelay = std::chrono::milliseconds(60 * 1000);
enum class Opcode : uint32_t {
Handshake = 0,
Frame = 1,
Close = 2,
Ping = 3,
Pong = 4,
};
enum class ConnectionState {
Disconnected,
SentHandshake,
Connected,
};
enum class DisconnectCode : int {
PipeClosed = 1,
ReadCorrupt = 2,
BadFrame = 3,
};
struct Frame {
Opcode opcode = Opcode::Frame;
std::string payload;
};
struct QueuedEvent {
enum class Type {
Ready,
Disconnected,
Error,
};
Type type = Type::Ready;
User user;
int code = 0;
std::string message;
};
class Backoff {
public:
std::chrono::milliseconds next_delay() {
const auto delay = currentDelay;
currentDelay = std::min(currentDelay * 2, kMaxReconnectDelay);
return delay;
}
void reset() { currentDelay = kInitialReconnectDelay; }
private:
std::chrono::milliseconds currentDelay = kInitialReconnectDelay;
};
class IpcConnection {
public:
IpcConnection() = default;
~IpcConnection() { close(); }
IpcConnection(const IpcConnection&) = delete;
IpcConnection& operator=(const IpcConnection&) = delete;
bool open() {
#ifdef _WIN32
wchar_t pipeName[] = L"\\\\?\\pipe\\discord-ipc-0";
constexpr size_t kPipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
for (wchar_t pipeNumber = L'0'; pipeNumber <= L'9'; ++pipeNumber) {
pipeName[kPipeDigit] = pipeNumber;
pipe = CreateFileW(
pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (pipe != INVALID_HANDLE_VALUE) {
return true;
}
if (GetLastError() == ERROR_PIPE_BUSY && WaitNamedPipeW(pipeName, 10000)) {
--pipeNumber;
}
}
return false;
#else
const auto tempPaths = get_temp_paths();
for (const std::string& tempPath : tempPaths) {
for (int pipeNumber = 0; pipeNumber < 10; ++pipeNumber) {
socketFd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketFd == -1) {
return false;
}
sockaddr_un pipeAddress{};
pipeAddress.sun_family = AF_UNIX;
const std::string socketPath =
tempPath + "/discord-ipc-" + std::to_string(pipeNumber);
if (socketPath.size() >= sizeof(pipeAddress.sun_path)) {
close();
continue;
}
std::strncpy(
pipeAddress.sun_path, socketPath.c_str(), sizeof(pipeAddress.sun_path) - 1);
if (connect(socketFd, reinterpret_cast<const sockaddr*>(&pipeAddress),
sizeof(pipeAddress)) == 0)
{
fcntl(socketFd, F_SETFL, O_NONBLOCK);
#ifdef SO_NOSIGPIPE
int optval = 1;
setsockopt(socketFd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
#endif
return true;
}
close();
}
}
return false;
#endif
}
void close() {
#ifdef _WIN32
if (pipe != INVALID_HANDLE_VALUE) {
CloseHandle(pipe);
pipe = INVALID_HANDLE_VALUE;
}
#else
if (socketFd != -1) {
::close(socketFd);
socketFd = -1;
}
#endif
pendingRead.clear();
}
bool is_open() const {
#ifdef _WIN32
return pipe != INVALID_HANDLE_VALUE;
#else
return socketFd != -1;
#endif
}
bool write_frame(const Frame& frame) {
std::array<uint8_t, kFrameHeaderSize> header{};
write_u32(header.data(), static_cast<uint32_t>(frame.opcode));
write_u32(header.data() + sizeof(uint32_t), static_cast<uint32_t>(frame.payload.size()));
return write_all(header.data(), header.size()) &&
write_all(
reinterpret_cast<const uint8_t*>(frame.payload.data()), frame.payload.size());
}
enum class ReadStatus {
None,
Frame,
Closed,
Corrupt,
};
ReadStatus read_frame(Frame& frame) {
if (!read_available()) {
return is_open() ? ReadStatus::None : ReadStatus::Closed;
}
if (pendingRead.size() < kFrameHeaderSize) {
return ReadStatus::None;
}
const uint32_t payloadLength = read_u32(pendingRead.data() + sizeof(uint32_t));
if (payloadLength > kMaxFramePayloadSize) {
return ReadStatus::Corrupt;
}
const size_t frameLength = kFrameHeaderSize + payloadLength;
if (pendingRead.size() < frameLength) {
return ReadStatus::None;
}
frame.opcode = static_cast<Opcode>(read_u32(pendingRead.data()));
frame.payload.assign(
reinterpret_cast<const char*>(pendingRead.data() + kFrameHeaderSize), payloadLength);
pendingRead.erase(
pendingRead.begin(), pendingRead.begin() + static_cast<std::ptrdiff_t>(frameLength));
return ReadStatus::Frame;
}
private:
#ifndef _WIN32
static std::vector<std::string> get_temp_paths() {
std::vector<std::string> paths;
for (const char* name : {"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"}) {
if (const char* value = std::getenv(name); value && value[0] != '\0') {
if (std::find(paths.begin(), paths.end(), value) == paths.end()) {
paths.emplace_back(value);
}
}
}
if (std::find(paths.begin(), paths.end(), "/tmp") == paths.end()) {
paths.emplace_back("/tmp");
}
return paths;
}
#endif
static void write_u32(uint8_t* out, uint32_t value) {
out[0] = static_cast<uint8_t>(value & 0xff);
out[1] = static_cast<uint8_t>((value >> 8) & 0xff);
out[2] = static_cast<uint8_t>((value >> 16) & 0xff);
out[3] = static_cast<uint8_t>((value >> 24) & 0xff);
}
static uint32_t read_u32(const uint8_t* in) {
return static_cast<uint32_t>(in[0]) | (static_cast<uint32_t>(in[1]) << 8) |
(static_cast<uint32_t>(in[2]) << 16) | (static_cast<uint32_t>(in[3]) << 24);
}
bool write_all(const uint8_t* data, size_t length) {
size_t written = 0;
while (written < length) {
#ifdef _WIN32
DWORD bytesWritten = 0;
if (WriteFile(pipe, data + written, static_cast<DWORD>(length - written), &bytesWritten,
nullptr) == FALSE ||
bytesWritten == 0)
{
close();
return false;
}
written += bytesWritten;
#else
#ifdef MSG_NOSIGNAL
constexpr int kMsgFlags = MSG_NOSIGNAL;
#else
constexpr int kMsgFlags = 0;
#endif
const ssize_t bytesWritten =
send(socketFd, data + written, length - written, kMsgFlags);
if (bytesWritten < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
close();
return false;
}
if (bytesWritten == 0) {
close();
return false;
}
written += static_cast<size_t>(bytesWritten);
#endif
}
return true;
}
bool read_available() {
std::array<uint8_t, 4096> buffer{};
bool readAny = false;
for (;;) {
#ifdef _WIN32
DWORD bytesAvailable = 0;
if (PeekNamedPipe(pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr) == FALSE) {
close();
return readAny;
}
if (bytesAvailable == 0) {
return readAny;
}
const DWORD bytesToRead =
std::min<DWORD>(bytesAvailable, static_cast<DWORD>(buffer.size()));
DWORD bytesRead = 0;
if (ReadFile(pipe, buffer.data(), bytesToRead, &bytesRead, nullptr) == FALSE) {
close();
return readAny;
}
if (bytesRead == 0) {
close();
return readAny;
}
pendingRead.insert(pendingRead.end(), buffer.begin(), buffer.begin() + bytesRead);
readAny = true;
#else
#ifdef MSG_NOSIGNAL
constexpr int kMsgFlags = MSG_NOSIGNAL;
#else
constexpr int kMsgFlags = 0;
#endif
const ssize_t bytesRead = recv(socketFd, buffer.data(), buffer.size(), kMsgFlags);
if (bytesRead < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return readAny;
}
close();
return readAny;
}
if (bytesRead == 0) {
close();
return readAny;
}
pendingRead.insert(pendingRead.end(), buffer.begin(), buffer.begin() + bytesRead);
readAny = true;
#endif
}
}
#ifdef _WIN32
HANDLE pipe = INVALID_HANDLE_VALUE;
#else
int socketFd = -1;
#endif
std::vector<uint8_t> pendingRead;
};
int current_process_id() {
#ifdef _WIN32
return static_cast<int>(GetCurrentProcessId());
#else
return static_cast<int>(getpid());
#endif
}
std::string next_nonce() {
static std::atomic_uint64_t sNonce{1};
return std::to_string(sNonce.fetch_add(1));
}
const json* find_member(const json& object, const char* key) {
if (!object.is_object()) {
return nullptr;
}
const auto member = object.find(key);
if (member == object.end()) {
return nullptr;
}
return &*member;
}
std::string json_string_member(const json& object, const char* key) {
const json* member = find_member(object, key);
if (!member || !member->is_string()) {
return {};
}
return member->get<std::string>();
}
int json_int_member(const json& object, const char* key, int defaultValue) {
const json* member = find_member(object, key);
if (!member || !member->is_number_integer()) {
return defaultValue;
}
try {
return member->get<int>();
} catch (const json::exception&) {
return defaultValue;
}
}
json make_presence_activity(const Presence& presence) {
json activity = json::object();
if (!presence.state.empty()) {
activity["state"] = presence.state;
}
if (!presence.details.empty()) {
activity["details"] = presence.details;
}
if (presence.startTimestamp != 0) {
activity["timestamps"] = {{"start", presence.startTimestamp}};
}
if (!presence.largeImageKey.empty() || !presence.largeImageText.empty()) {
json assets = json::object();
if (!presence.largeImageKey.empty()) {
assets["large_image"] = presence.largeImageKey;
}
if (!presence.largeImageText.empty()) {
assets["large_text"] = presence.largeImageText;
}
activity["assets"] = std::move(assets);
}
return activity;
}
Frame make_handshake_frame(std::string_view applicationId) {
return {
Opcode::Handshake,
json{
{"v", kRpcVersion},
{"client_id", std::string(applicationId)},
}
.dump(),
};
}
Frame make_set_activity_frame(std::string nonce, int pid, std::optional<Presence> presence) {
json args = {{"pid", pid}};
if (presence) {
args["activity"] = make_presence_activity(*presence);
} else {
args["activity"] = nullptr;
}
return {
Opcode::Frame,
json{
{"cmd", "SET_ACTIVITY"},
{"nonce", std::move(nonce)},
{"args", std::move(args)},
}
.dump(),
};
}
class Client {
public:
void initialize(std::string applicationId, EventHandlers handlers) {
shutdown();
{
std::lock_guard lock(mutex);
this->applicationId = std::move(applicationId);
this->handlers = std::move(handlers);
shouldRun = true;
queuedPresence.reset();
hasQueuedPresence = false;
clearRequested = false;
sentInitialConnectLog = false;
}
ioThread = std::thread([this] { io_loop(); });
}
void run_callbacks() {
std::deque<QueuedEvent> events;
EventHandlers localHandlers;
{
std::lock_guard lock(mutex);
events.swap(queuedEvents);
localHandlers = handlers;
}
for (const QueuedEvent& event : events) {
switch (event.type) {
case QueuedEvent::Type::Ready:
if (localHandlers.ready) {
localHandlers.ready(event.user);
}
break;
case QueuedEvent::Type::Disconnected:
if (localHandlers.disconnected) {
localHandlers.disconnected(event.code, event.message);
}
break;
case QueuedEvent::Type::Error:
if (localHandlers.error) {
localHandlers.error(event.code, event.message);
}
break;
}
}
}
void update_presence(Presence presence) {
{
std::lock_guard lock(mutex);
if (!shouldRun) {
return;
}
queuedPresence = std::move(presence);
hasQueuedPresence = true;
}
cv.notify_all();
}
void clear_presence() {
{
std::lock_guard lock(mutex);
if (!shouldRun) {
return;
}
queuedPresence.reset();
hasQueuedPresence = false;
clearRequested = true;
}
cv.notify_all();
}
void shutdown() {
{
std::lock_guard lock(mutex);
if (!shouldRun && !ioThread.joinable()) {
return;
}
shouldRun = false;
clearRequested = true;
}
cv.notify_all();
if (ioThread.joinable()) {
ioThread.join();
}
std::lock_guard lock(mutex);
queuedPresence.reset();
hasQueuedPresence = false;
clearRequested = false;
queuedEvents.clear();
handlers = {};
applicationId.clear();
}
private:
void io_loop() {
IpcConnection connection;
ConnectionState state = ConnectionState::Disconnected;
Backoff reconnectBackoff;
auto nextConnect = std::chrono::steady_clock::now();
const int pid = current_process_id();
std::string localApplicationId;
for (;;) {
{
std::unique_lock lock(mutex);
if (!shouldRun) {
break;
}
localApplicationId = applicationId;
}
const auto now = std::chrono::steady_clock::now();
if (state == ConnectionState::Disconnected && now >= nextConnect) {
if (connection.open()) {
if (connection.write_frame(make_handshake_frame(localApplicationId))) {
state = ConnectionState::SentHandshake;
} else {
connection.close();
}
}
if (state == ConnectionState::Disconnected) {
log_waiting_for_discord_once();
nextConnect = now + reconnectBackoff.next_delay();
}
}
if (state != ConnectionState::Disconnected) {
process_reads(connection, state, reconnectBackoff, nextConnect);
}
if (state == ConnectionState::Connected) {
flush_pending_presence(connection, pid);
}
std::unique_lock lock(mutex);
if (!shouldRun) {
break;
}
cv.wait_for(lock, kIoWait);
}
flush_shutdown_clear(connection, state, pid);
connection.close();
}
void process_reads(IpcConnection& connection, ConnectionState& state, Backoff& reconnectBackoff,
std::chrono::steady_clock::time_point& nextConnect) {
for (;;) {
Frame frame;
const auto status = connection.read_frame(frame);
if (status == IpcConnection::ReadStatus::None) {
return;
}
if (status == IpcConnection::ReadStatus::Closed) {
handle_disconnect(connection, state, reconnectBackoff, nextConnect,
static_cast<int>(DisconnectCode::PipeClosed), "Pipe closed");
return;
}
if (status == IpcConnection::ReadStatus::Corrupt) {
handle_disconnect(connection, state, reconnectBackoff, nextConnect,
static_cast<int>(DisconnectCode::ReadCorrupt), "Oversized Discord IPC frame");
return;
}
switch (frame.opcode) {
case Opcode::Frame:
process_json_frame(frame.payload, state, reconnectBackoff);
break;
case Opcode::Close:
process_close_frame(
frame.payload, connection, state, reconnectBackoff, nextConnect);
return;
case Opcode::Ping:
connection.write_frame({Opcode::Pong, frame.payload});
break;
case Opcode::Pong:
break;
case Opcode::Handshake:
default:
handle_disconnect(connection, state, reconnectBackoff, nextConnect,
static_cast<int>(DisconnectCode::BadFrame), "Bad Discord IPC frame");
return;
}
}
}
void process_json_frame(
const std::string& payload, ConnectionState& state, Backoff& reconnectBackoff) {
json message;
try {
message = json::parse(payload);
} catch (const json::parse_error&) {
enqueue_error(
static_cast<int>(DisconnectCode::ReadCorrupt), "Invalid Discord IPC JSON");
return;
}
const std::string cmd = json_string_member(message, "cmd");
const std::string evt = json_string_member(message, "evt");
if (state == ConnectionState::SentHandshake && cmd == "DISPATCH" && evt == "READY") {
state = ConnectionState::Connected;
reconnectBackoff.reset();
enqueue_ready(message);
return;
}
if (evt == "ERROR") {
const json* data = find_member(message, "data");
enqueue_error(data ? json_int_member(*data, "code", 0) : 0,
data ? json_string_member(*data, "message") : std::string{});
}
}
void process_close_frame(const std::string& payload, IpcConnection& connection,
ConnectionState& state, Backoff& reconnectBackoff,
std::chrono::steady_clock::time_point& nextConnect) {
int code = static_cast<int>(DisconnectCode::PipeClosed);
std::string message = "Discord closed IPC connection";
try {
const json closePayload = json::parse(payload);
code = json_int_member(closePayload, "code", code);
const std::string closeMessage = json_string_member(closePayload, "message");
if (!closeMessage.empty()) {
message = closeMessage;
}
} catch (const json::exception&) {
}
handle_disconnect(connection, state, reconnectBackoff, nextConnect, code, message);
}
void handle_disconnect(IpcConnection& connection, ConnectionState& state,
Backoff& reconnectBackoff, std::chrono::steady_clock::time_point& nextConnect, int code,
std::string_view message) {
const bool wasConnected =
state == ConnectionState::Connected || state == ConnectionState::SentHandshake;
connection.close();
state = ConnectionState::Disconnected;
nextConnect = std::chrono::steady_clock::now() + reconnectBackoff.next_delay();
if (wasConnected) {
enqueue_disconnected(code, message);
}
}
void flush_pending_presence(IpcConnection& connection, int pid) {
std::optional<Presence> presence;
bool shouldClear = false;
{
std::lock_guard lock(mutex);
if (hasQueuedPresence) {
presence = queuedPresence;
hasQueuedPresence = false;
} else if (clearRequested) {
shouldClear = true;
clearRequested = false;
}
}
if (presence) {
if (!connection.write_frame(
make_set_activity_frame(next_nonce(), pid, std::move(presence))))
{
requeue_presence();
}
} else if (shouldClear) {
connection.write_frame(make_set_activity_frame(next_nonce(), pid, std::nullopt));
}
}
void flush_shutdown_clear(IpcConnection& connection, ConnectionState state, int pid) {
if (state != ConnectionState::Connected || !connection.is_open()) {
return;
}
connection.write_frame(make_set_activity_frame(next_nonce(), pid, std::nullopt));
const auto deadline = std::chrono::steady_clock::now() + kShutdownClearTimeout;
while (std::chrono::steady_clock::now() < deadline) {
Frame frame;
const auto status = connection.read_frame(frame);
if (status == IpcConnection::ReadStatus::None) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
if (status != IpcConnection::ReadStatus::Frame) {
break;
}
if (frame.opcode == Opcode::Ping) {
connection.write_frame({Opcode::Pong, frame.payload});
}
}
}
void requeue_presence() {
std::lock_guard lock(mutex);
if (queuedPresence) {
hasQueuedPresence = true;
}
}
void enqueue_ready(const json& readyMessage) {
User user;
const auto data = readyMessage.find("data");
if (data != readyMessage.end() && data->is_object()) {
const auto userIt = data->find("user");
if (userIt != data->end() && userIt->is_object()) {
user.id = json_string_member(*userIt, "id");
user.username = json_string_member(*userIt, "username");
user.discriminator = json_string_member(*userIt, "discriminator");
user.avatar = json_string_member(*userIt, "avatar");
}
}
std::lock_guard lock(mutex);
queuedEvents.push_back({QueuedEvent::Type::Ready, std::move(user)});
}
void enqueue_disconnected(int code, std::string_view message) {
std::lock_guard lock(mutex);
QueuedEvent event;
event.type = QueuedEvent::Type::Disconnected;
event.code = code;
event.message = message;
queuedEvents.push_back(std::move(event));
}
void enqueue_error(int code, std::string_view message) {
std::lock_guard lock(mutex);
QueuedEvent event;
event.type = QueuedEvent::Type::Error;
event.code = code;
event.message = message;
queuedEvents.push_back(std::move(event));
}
void log_waiting_for_discord_once() {
bool shouldLog = false;
{
std::lock_guard lock(mutex);
if (!sentInitialConnectLog) {
sentInitialConnectLog = true;
shouldLog = true;
}
}
if (shouldLog) {
DuskLog.info("Discord: Waiting for local Discord IPC");
}
}
std::mutex mutex;
std::condition_variable cv;
std::thread ioThread;
std::string applicationId;
EventHandlers handlers;
std::deque<QueuedEvent> queuedEvents;
std::optional<Presence> queuedPresence;
bool hasQueuedPresence = false;
bool clearRequested = false;
bool shouldRun = false;
bool sentInitialConnectLog = false;
};
Client& client() {
static Client sClient;
return sClient;
}
} // namespace
void initialize(std::string applicationId, EventHandlers handlers) {
client().initialize(std::move(applicationId), std::move(handlers));
}
void run_callbacks() {
client().run_callbacks();
}
void update_presence(Presence presence) {
client().update_presence(std::move(presence));
}
void clear_presence() {
client().clear_presence();
}
void shutdown() {
client().shutdown();
}
} // namespace dusk::discord::rpc
#endif // DUSK_DISCORD
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#ifdef DUSK_DISCORD
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
namespace dusk::discord::rpc {
struct User {
std::string id;
std::string username;
std::string discriminator;
std::string avatar;
};
struct Presence {
std::string state;
std::string details;
int64_t startTimestamp = 0;
std::string largeImageKey;
std::string largeImageText;
};
struct EventHandlers {
std::function<void(const User&)> ready;
std::function<void(int, std::string_view)> disconnected;
std::function<void(int, std::string_view)> error;
};
void initialize(std::string applicationId, EventHandlers handlers);
void run_callbacks();
void update_presence(Presence presence);
void clear_presence();
void shutdown();
} // namespace dusk::discord::rpc
#endif // DUSK_DISCORD
+52 -53
View File
@@ -1,39 +1,38 @@
#ifdef DUSK_DISCORD #ifdef DUSK_DISCORD_RPC
#include "dusk/discord_presence.hpp" #include "dusk/discord_presence.hpp"
#include "d/d_com_inf_game.h"
#include "discord.hpp"
#include "dusk/logging.h" #include "dusk/logging.h"
#include "dusk/main.h" #include "dusk/main.h"
#include "dusk/map_loader_definitions.h" #include "dusk/map_loader_definitions.h"
#include "d/d_com_inf_game.h"
#include "discord_rpc.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <chrono> #include <chrono>
#include <cstring>
#include <string> #include <string>
#include <string_view>
#include <utility>
namespace dusk::discord { namespace dusk {
namespace discord {
static int64_t g_startTime = 0; static int64_t g_startTime = 0;
static bool g_initialized = false; static bool g_initialized = false;
static constexpr const char* kApplicationId = "1495632471994405035"; static const char* APPLICATION_ID = "1495632471994405035";
static void on_ready(const rpc::User& user) { static void OnReady(const DiscordUser* user) {
DuskLog.info("Discord: Connected as {}", user.username); DuskLog.info("Discord: Connected as {}", user->username);
} }
static void on_disconnected(int errorCode, std::string_view message) { static void OnDisconnected(int errorCode, const char* message) {
DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message); DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message);
} }
static void on_error(int errorCode, std::string_view message) { static void OnError(int errorCode, const char* message) {
DuskLog.warn("Discord: Error ({}: {})", errorCode, message); DuskLog.warn("Discord: Error ({}: {})", errorCode, message);
} }
static const char* lookup_map_name(const char* mapFile) { static const char* LookupMapName(const char* mapFile) {
if (!mapFile || mapFile[0] == '\0') if (!mapFile || mapFile[0] == '\0') return nullptr;
return nullptr;
for (const auto& region : gameRegions) { for (const auto& region : gameRegions) {
for (const auto& map : region.maps) { for (const auto& map : region.maps) {
if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) { if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) {
@@ -44,80 +43,80 @@ static const char* lookup_map_name(const char* mapFile) {
return nullptr; return nullptr;
} }
void initialize() { void Initialize() {
g_startTime = std::chrono::duration_cast<std::chrono::seconds>( g_startTime = static_cast<int64_t>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::duration_cast<std::chrono::seconds>(
.count(); std::chrono::system_clock::now().time_since_epoch()
).count()
);
rpc::EventHandlers handlers{}; DiscordEventHandlers handlers{};
handlers.ready = on_ready; handlers.ready = OnReady;
handlers.disconnected = on_disconnected; handlers.disconnected = OnDisconnected;
handlers.error = on_error; handlers.errored = OnError;
rpc::initialize(kApplicationId, std::move(handlers)); Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr);
g_initialized = true; g_initialized = true;
DuskLog.info("Discord Rich Presence initialized"); DuskLog.info("Discord Rich Presence initialized");
} }
void run_callbacks() { void RunCallbacks() {
if (!g_initialized) if (!g_initialized) return;
return; Discord_RunCallbacks();
rpc::run_callbacks();
} }
void update_presence() { void UpdatePresence() {
if (!g_initialized) if (!g_initialized) return;
return;
static auto sLastUpdate = std::chrono::steady_clock::time_point{}; static auto lastUpdate = std::chrono::steady_clock::time_point{};
const auto now = std::chrono::steady_clock::now(); const auto now = std::chrono::steady_clock::now();
if (now - sLastUpdate < std::chrono::seconds(15)) if (now - lastUpdate < std::chrono::seconds(15)) return;
return; lastUpdate = now;
sLastUpdate = now;
static std::string sDetailsBuf; static std::string detailsBuf;
static std::string sStateBuf; static std::string stateBuf;
rpc::Presence presence{}; DiscordRichPresence presence{};
presence.startTimestamp = g_startTime; presence.startTimestamp = g_startTime;
presence.largeImageKey = "icon"; presence.largeImageKey = "icon";
presence.largeImageText = "Dusk"; presence.largeImageText = "Dusk";
if (IsGameLaunched) { if (dusk::IsGameLaunched) {
const char* stageName = dComIfGp_getLastPlayStageName(); const char* stageName = dComIfGp_getLastPlayStageName();
// stageName is empty until a room is actually entered // stageName is empty until a room is actually entered
if (stageName[0] != '\0') { if (stageName[0] != '\0') {
const char* locationName = lookup_map_name(stageName); const char* locationName = LookupMapName(stageName);
if (locationName) { if (locationName) {
sDetailsBuf = locationName; detailsBuf = locationName;
} else { }
sDetailsBuf = "Twilight Princess"; else {
detailsBuf = "Twilight Princess";
} }
presence.details = sDetailsBuf; presence.details = detailsBuf.c_str();
sStateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"), stateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"),
dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee()); dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee());
presence.state = sStateBuf; presence.state = stateBuf.c_str();
} }
} }
rpc::update_presence(std::move(presence)); Discord_UpdatePresence(&presence);
DuskLog.debug("Discord Rich Presence sent"); DuskLog.debug("Discord Rich Presence sent");
} }
void shutdown() { void Shutdown() {
if (!g_initialized) if (!g_initialized) return;
return; Discord_ClearPresence();
rpc::clear_presence(); Discord_Shutdown();
rpc::shutdown();
g_initialized = false; g_initialized = false;
DuskLog.info("Discord Rich Presence shut down"); DuskLog.info("Discord Rich Presence shut down");
} }
} // namespace dusk::discord } // namespace discord
} // namespace dusk
#endif // DUSK_DISCORD #endif // DUSK_DISCORD_RPC
+245
View File
@@ -0,0 +1,245 @@
#include "ImGuiAchievements.hpp"
#include "ImGuiConfig.hpp"
#include "dusk/achievements.h"
#include "dusk/settings.h"
#include "fmt/format.h"
#include "imgui.h"
namespace dusk {
void ImGuiAchievements::notify(std::string name) {
if (m_notifyTimer <= 0.f) {
m_notifyName = std::move(name);
m_notifyTimer = NOTIFY_DURATION;
} else {
m_notifyQueue.push(std::move(name));
}
}
void ImGuiAchievements::showNotification() {
if (!getSettings().game.enableAchievementNotifications.getValue()) {
return;
}
if (m_notifyTimer <= 0.f) {
if (m_notifyQueue.empty()) {
return;
}
m_notifyName = std::move(m_notifyQueue.front());
m_notifyQueue.pop();
m_notifyTimer = NOTIFY_DURATION;
}
m_notifyTimer -= ImGui::GetIO().DeltaTime;
const float alpha = std::min({
m_notifyTimer / NOTIFY_FADE_TIME,
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
1.0f
});
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 12.0f;
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
);
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
constexpr ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
ImGui::TextUnformatted("Achievement Unlocked!");
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextUnformatted(m_notifyName.c_str());
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
}
void ImGuiAchievements::draw(bool& open) {
showNotification();
if (!open) {
return;
}
ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900));
ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(
"Achievements", &open,
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)
)
{
ImGui::End();
return;
}
const auto achievements = AchievementSystem::get().getAchievements();
int unlocked = 0;
for (const auto& a : achievements) {
if (a.unlocked) {
++unlocked;
}
}
ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size());
ImGui::SameLine();
config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications);
ImGui::Separator();
static const struct {
AchievementCategory cat;
const char* label;
ImVec4 color;
} ACHIEVEMENT_CATEGORIES[] = {
{AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)},
{AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)},
{AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)},
{AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)},
{AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)},
{AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)},
};
const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) {
for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) {
int catTotal = 0, catUnlocked = 0;
for (const auto& a : achievements) {
if (a.category == catInfo.cat) {
++catTotal;
if (a.unlocked) {
++catUnlocked;
}
}
}
if (catTotal == 0) {
continue;
}
const std::string tabLabel = fmt::format("{} ({}/{})###{}", catInfo.label, catUnlocked, catTotal, catInfo.label);
ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color);
const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str());
ImGui::PopStyleColor();
if (tabOpen) {
ImGui::BeginChild(
"##cat_list",
ImVec2(0, -footerHeight),
ImGuiChildFlags_None,
ImGuiWindowFlags_NoBackground
);
ImGui::Spacing();
for (const auto& a : achievements) {
if (a.category != catInfo.cat) {
continue;
}
ImGui::PushID(a.key);
ImGui::BeginGroup();
ImGui::PushStyleColor(
ImGuiCol_Text,
a.unlocked ?
ImVec4(1.0f, 0.65f, 0.15f, 1.0f) :
ImGui::GetStyleColorVec4(ImGuiCol_Text)
);
ImGui::TextUnformatted(a.name);
ImGui::PopStyleColor();
const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]";
ImGui::SameLine(
ImGui::GetContentRegionMax().x -
ImGui::CalcTextSize(statusLabel).x
);
if (a.unlocked) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel);
} else {
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel);
}
ImGui::TextDisabled("%s", a.description);
if (a.isCounter) {
const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f;
const std::string overlay = fmt::format("{} / {}", a.progress, a.goal);
ImGui::PushStyleColor(
ImGuiCol_PlotHistogram,
a.unlocked ?
ImVec4(0.4f, 0.7f, 0.1f, 1.0f) :
ImVec4(0.2f, 0.45f, 0.8f, 1.0f)
);
ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str());
ImGui::PopStyleColor();
}
ImGui::EndGroup();
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup("##ctx");
}
if (ImGui::BeginPopup("##ctx")) {
ImGui::TextDisabled("%s", a.name);
ImGui::Separator();
if (ImGui::MenuItem("Clear Achievement")) {
AchievementSystem::get().clearOne(a.key);
}
ImGui::EndPopup();
}
ImGui::Spacing();
ImGui::PopID();
}
ImGui::EndChild();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button("Clear All Achievements")) {
ImGui::OpenPopup("##confirm_clear");
}
if (ImGui::BeginPopup("##confirm_clear")) {
ImGui::Text("Reset all achievement progress?");
ImGui::Spacing();
if (ImGui::Button("Yes, reset all")) {
AchievementSystem::get().clearAll();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::End();
}
} // namespace dusk
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <queue>
#include <string>
namespace dusk {
class ImGuiAchievements {
public:
void draw(bool& open);
void notify(std::string name);
private:
void showNotification();
std::string m_notifyName;
float m_notifyTimer = 0.f;
std::queue<std::string> m_notifyQueue;
static constexpr float NOTIFY_DURATION = 4.0f;
static constexpr float NOTIFY_FADE_TIME = 0.5f;
};
} // namespace dusk
+60 -18
View File
@@ -1,12 +1,9 @@
#include "f_op/f_op_camera_mng.h" #include "f_op/f_op_camera_mng.h"
#include "SSystem/SComponent/c_xyz.h" #include "SSystem/SComponent/c_xyz.h"
#include "d/d_com_inf_game.h"
#include "imgui.h" #include "imgui.h"
#include "ImGuiConfig.hpp"
#include "ImGuiConsole.hpp" #include "ImGuiConsole.hpp"
#include "ImGuiMenuTools.hpp" #include "ImGuiMenuTools.hpp"
#include "dusk/settings.h"
namespace dusk { namespace dusk {
void ImGuiMenuTools::ShowCameraOverlay() { void ImGuiMenuTools::ShowCameraOverlay() {
@@ -49,25 +46,70 @@ namespace dusk {
ImGui::InputFloat("Camera FOV", &dCam->mFovy); ImGui::InputFloat("Camera FOV", &dCam->mFovy);
ImGui::SeparatorText("Options"); ImGui::SeparatorText("Free-look Data");
bool eventRunning = (dComIfGp_event_runCheck() || dComIfGp_isPauseFlag()) && !getSettings().game.debugFlyCam; static float eyeYawDeg = 0.0f;
if (eventRunning) { static float moveSpeed = 5000.0f;
ImGui::BeginDisabled(); static float rotSpeed = 5.0f;
static cXyz freeLookPos = cXyz::Zero;
static bool freeLookActive = false;
bool changed = false;
if (ImGui::IsKeyDown(ImGuiKey_LeftArrow)) {
eyeYawDeg += rotSpeed;
if (eyeYawDeg >= 360.0f)
eyeYawDeg -= 360.0f;
changed = true;
} }
config::ImGuiCheckbox("Fly Mode", getSettings().game.debugFlyCam); else if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { eyeYawDeg -= rotSpeed;
if (eventRunning) { if (eyeYawDeg < 0.0f)
ImGui::SetTooltip("Cannot enable while paused or during an active event."); eyeYawDeg += 360.0f;
} else {
ImGui::SetTooltip("Detach camera and fly freely.\n" changed = true;
"Left stick: move, C-stick: look\n"
"L/R triggers: up/down, Z: fast");
}
} }
if (eventRunning) { cSAngle yawAngle = cSAngle(eyeYawDeg);
ImGui::EndDisabled(); cXyz frontDir = cXyz(yawAngle.Sin(), 0.0f, yawAngle.Cos());
if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) {
freeLookPos -= frontDir * moveSpeed * ImGui::GetIO().DeltaTime;
changed = true;
} }
else if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) {
freeLookPos += frontDir * moveSpeed * ImGui::GetIO().DeltaTime;
changed = true;
}
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
freeLookPos += cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime;
changed = true;
}
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
freeLookPos -= cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime;
changed = true;
}
if (!freeLookActive && changed) {
freeLookPos += dCam->Center();
freeLookActive = true;
}
if (ImGui::IsKeyDown(ImGuiKey_R)) {
freeLookPos = cXyz::Zero;
freeLookActive = false;
}
if (freeLookActive) {
dCam->Reset(freeLookPos, freeLookPos + (frontDir * 100.0f));
}
ImGui::InputFloat("Free-look Yaw", &eyeYawDeg);
ImGui::InputFloat3("Free-look Position", &freeLookPos.x);
ImGui::InputFloat("Free-look Move Speed", &moveSpeed);
ImGui::InputFloat("Free-look Rotation Speed", &rotSpeed);
ShowCornerContextMenu(m_cameraOverlayCorner, 0); ShowCornerContextMenu(m_cameraOverlayCorner, 0);
+87 -68
View File
@@ -10,9 +10,11 @@
#include "fmt/format.h" #include "fmt/format.h"
#include "ImGuiConsole.hpp" #include "ImGuiConsole.hpp"
#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h" #include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_mouse.h" #include "SDL3/SDL_mouse.h"
#include "aurora/lib/window.hpp"
#include "dusk/achievements.h"
#include "dusk/audio/DuskAudioSystem.h" #include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp" #include "dusk/config.hpp"
#include "dusk/dusk.h" #include "dusk/dusk.h"
@@ -20,9 +22,6 @@
#include "dusk/livesplit.h" #include "dusk/livesplit.h"
#include "dusk/main.h" #include "dusk/main.h"
#include "dusk/settings.h" #include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
#include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_main.h"
#include "tracy/Tracy.hpp" #include "tracy/Tracy.hpp"
@@ -36,6 +35,14 @@ using namespace std::string_literals;
using namespace std::string_view_literals; using namespace std::string_view_literals;
namespace { namespace {
ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) {
const AuroraWindowSize size = aurora::window::get_window_size();
return ImVec2{
touch.x * static_cast<float>(size.width),
touch.y * static_cast<float>(size.height),
};
}
ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) {
while (window != nullptr) { while (window != nullptr) {
const bool canScrollX = window->ScrollMax.x > 0.0f; const bool canScrollX = window->ScrollMax.x > 0.0f;
@@ -234,7 +241,48 @@ namespace dusk {
ImGuiConsole::ImGuiConsole() {} ImGuiConsole::ImGuiConsole() {}
void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) { void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) {
(void)event; if (!IsGameLaunched) {
return;
}
switch (event.type) {
case SDL_EVENT_FINGER_DOWN:
if (!m_touchTapActive) {
m_touchTapActive = true;
m_touchTapMoved = false;
m_touchTapFingerId = event.tfinger.fingerID;
m_touchTapStartPos = TouchEventToScreenPos(event.tfinger);
}
break;
case SDL_EVENT_FINGER_MOTION:
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
const auto currentPos = TouchEventToScreenPos(event.tfinger);
const auto delta = currentPos - m_touchTapStartPos;
if (ImLengthSqr(delta) > 144.0f) {
m_touchTapMoved = true;
}
}
break;
case SDL_EVENT_FINGER_UP:
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
const bool shouldToggle =
!m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse);
m_touchTapActive = false;
m_touchTapMoved = false;
if (shouldToggle) {
m_isHidden = !m_isHidden;
getSettings().backend.duskMenuOpen.setValue(!m_isHidden);
Save();
}
}
break;
case SDL_EVENT_FINGER_CANCELED:
m_touchTapActive = false;
m_touchTapMoved = false;
break;
default:
break;
}
} }
void ImGuiConsole::UpdateSettings() { void ImGuiConsole::UpdateSettings() {
@@ -253,8 +301,16 @@ namespace dusk {
UpdateSettings(); UpdateSettings();
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) && AchievementSystem::get().tick();
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && while (AchievementSystem::get().hasPendingUnlock()) {
if (getSettings().game.enableAchievementNotifications) {
m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock());
} else {
AchievementSystem::get().consumePendingUnlock();
}
}
if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_R)) ImGui::IsKeyPressed(ImGuiKey_R))
{ {
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
@@ -264,10 +320,23 @@ namespace dusk {
ImGuiMenuGame::ToggleFullscreen(); ImGuiMenuGame::ToggleFullscreen();
} }
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) {
ImGuiMenuGame::ToggleFullscreen();
}
// if (!dusk::IsGameLaunched) {
// m_preLaunchWindow.draw();
// }
m_isHidden = !getSettings().backend.duskMenuOpen;
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
m_isHidden = !m_isHidden; m_isHidden = !m_isHidden;
} }
bool showMenu = !m_isHidden; bool showMenu = !m_isHidden;
if (getSettings().backend.duskMenuOpen != showMenu) {
getSettings().backend.duskMenuOpen.setValue(showMenu);
Save();
}
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
// so make the window bg fully transparent temporarily // so make the window bg fully transparent temporarily
@@ -290,7 +359,16 @@ namespace dusk {
} }
ImGui::PopStyleColor(); ImGui::PopStyleColor();
if (!getSettings().backend.wasPresetChosen) {
m_firstRunPreset.draw();
return;
}
if (dusk::IsGameLaunched && !m_isLaunchInitialized) { if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
"Tap to toggle menu"s :
"Press F1 to toggle menu"s,
4.f);
m_isLaunchInitialized = true; m_isLaunchInitialized = true;
if (getSettings().game.liveSplitEnabled) { if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit(); dusk::speedrun::connectLiveSplit();
@@ -299,67 +377,6 @@ namespace dusk {
UpdateDragScroll(); UpdateDragScroll();
// Show message when Aurora backend is Null
if (aurora_get_backend() == BACKEND_NULL) {
auto& io = ImGui::GetIO();
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y));
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowBgAlpha(0.65f);
ImGui::Begin("Pre Launch Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::NewLine();
if (ImGuiEngine::duskLogo) {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::NewLine();
float iconSize = 150.f;
float width = iconSize * 2.5f;
ImGui::SameLine(windowSize.x / 2 - width + (width / 2));
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
} else {
ImGui::PushFont(ImGuiEngine::fontExtraLarge);
ImGuiTextCenter("Dusk");
ImGui::PopFont();
}
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Failed to initialize any graphics backend");
const auto& style = ImGui::GetStyle();
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
const auto quitSize = ImGui::CalcTextSize("Quit");
float buttonsWidth = quitSize.x + style.FramePadding.x * 2.0f;
if constexpr (SupportsProcessRestart) {
buttonsWidth += retrySize.x + style.FramePadding.x * 2.0f + style.ItemSpacing.x;
}
#if DUSK_CAN_OPEN_DATA_FOLDER
const auto openSize = ImGui::CalcTextSize("Open Data Folder");
buttonsWidth += openSize.x + style.FramePadding.x * 2.0f + style.ItemSpacing.x;
#endif
ImGui::NewLine();
ImGui::SetCursorPosX(
ImMax(style.WindowPadding.x, (ImGui::GetWindowSize().x - buttonsWidth) * 0.5f));
if constexpr (SupportsProcessRestart) {
if (ImGui::Button("Retry (Auto backend)")) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
RestartRequested = true;
IsRunning = false;
}
ImGui::SameLine();
}
#if DUSK_CAN_OPEN_DATA_FOLDER
if (ImGui::Button("Open Data Folder")) {
OpenDataFolder();
}
ImGui::SameLine();
#endif
if (ImGui::Button("Quit")) {
IsRunning = false;
}
ImGui::PopFont();
ImGui::End();
}
m_menuGame.windowControllerConfig(); m_menuGame.windowControllerConfig();
m_menuGame.windowInputViewer(); m_menuGame.windowInputViewer();
m_menuGame.drawSpeedrunTimerOverlay(); m_menuGame.drawSpeedrunTimerOverlay();
@@ -385,6 +402,8 @@ namespace dusk {
m_menuTools.ShowSaveEditor(); m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare(); m_menuTools.ShowStateShare();
} }
m_menuTools.ShowAchievements();
DuskDebugPad(); // temporary, remove later
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
+11 -23
View File
@@ -2,16 +2,16 @@
#define DUSK_IMGUI_HPP #define DUSK_IMGUI_HPP
#include <deque> #include <deque>
#include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <SDL3/SDL_misc.h>
#include <aurora/aurora.h> #include <aurora/aurora.h>
#include <SDL3/SDL_touch.h>
#include "ImGuiFirstRunPreset.hpp"
#include "ImGuiMenuGame.hpp" #include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp" #include "ImGuiMenuTools.hpp"
#include "dusk/main.h" #include "ImGuiPreLaunchWindow.hpp"
#include "imgui.h" #include "imgui.h"
union SDL_Event; union SDL_Event;
@@ -26,7 +26,7 @@ public:
void PreDraw(); void PreDraw();
void PostDraw(); void PostDraw();
static bool CheckMenuViewToggle(ImGuiKey key, bool& active); static bool CheckMenuViewToggle(ImGuiKey key, bool& active);
void AddToast(std::string_view message, float duration = 3.f); void AddToast(std::string_view message, float duration = 3.f);
private: private:
@@ -42,11 +42,17 @@ private:
bool m_isHidden = true; bool m_isHidden = true;
bool m_isLaunchInitialized = false; bool m_isLaunchInitialized = false;
bool m_touchTapActive = false;
bool m_touchTapMoved = false;
SDL_FingerID m_touchTapFingerId = 0;
ImVec2 m_touchTapStartPos = {};
ImGuiWindow* m_dragScrollWindow = nullptr; ImGuiWindow* m_dragScrollWindow = nullptr;
ImVec2 m_dragScrollLastMousePos = {}; ImVec2 m_dragScrollLastMousePos = {};
std::deque<Toast> m_toasts; std::deque<Toast> m_toasts;
ImGuiFirstRunPreset m_firstRunPreset;
ImGuiMenuGame m_menuGame; ImGuiMenuGame m_menuGame;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last // Keep always last
ImGuiMenuTools m_menuTools; ImGuiMenuTools m_menuTools;
@@ -73,24 +79,6 @@ bool ImGuiButtonCenter(std::string_view text);
float ImGuiScale(); float ImGuiScale();
} // namespace dusk } // namespace dusk
#if defined(_WIN32) || \ void DuskDebugPad();
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
(defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
namespace fs = std::filesystem;
static void OpenDataFolder() {
const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
#if defined(_WIN32)
const std::string url = std::string("file:///") + path;
#else
const std::string url = std::string("file://") + path;
#endif
(void)SDL_OpenURL(url.c_str());
}
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
#endif // DUSK_IMGUI_HPP #endif // DUSK_IMGUI_HPP
+61
View File
@@ -0,0 +1,61 @@
#include "m_Do/m_Do_controller_pad.h"
#include "imgui.h"
#include "ImGuiConsole.hpp"
void DuskDebugPad() {
auto& pad = mDoCPd_c::getCpadInfo(PAD_1);
auto KeyFlag = [&](ImGuiKey key, u32 padFlag) {
if (ImGui::IsKeyDown(key))
pad.mButtonFlags |= padFlag;
if (ImGui::IsKeyPressed(key))
pad.mPressedButtonFlags |= padFlag;
};
KeyFlag(ImGuiKey_K, PAD_BUTTON_A);
KeyFlag(ImGuiKey_J, PAD_BUTTON_B);
KeyFlag(ImGuiKey_L, PAD_BUTTON_X);
KeyFlag(ImGuiKey_I, PAD_BUTTON_Y);
KeyFlag(ImGuiKey_H, PAD_BUTTON_START);
KeyFlag(ImGuiKey_O, PAD_TRIGGER_Z);
KeyFlag(ImGuiKey_Keypad8, PAD_BUTTON_UP);
KeyFlag(ImGuiKey_Keypad2, PAD_BUTTON_DOWN);
KeyFlag(ImGuiKey_Keypad6, PAD_BUTTON_RIGHT);
KeyFlag(ImGuiKey_Keypad4, PAD_BUTTON_LEFT);
if (ImGui::IsKeyDown(ImGuiKey_W)) {
pad.mMainStickPosY = 1.0f;
pad.mMainStickValue = 1.0f;
pad.mMainStickAngle = 0x8000;
}
if (ImGui::IsKeyDown(ImGuiKey_S)) {
pad.mMainStickPosY = -1.0f;
pad.mMainStickValue = 1.0f;
pad.mMainStickAngle = 0;
}
if (ImGui::IsKeyDown(ImGuiKey_D)) {
pad.mMainStickPosX = 1.0f;
pad.mMainStickValue = 1.0f;
pad.mMainStickAngle = 0x4000;
}
if (ImGui::IsKeyDown(ImGuiKey_A)) {
pad.mMainStickPosX = -1.0f;
pad.mMainStickValue = 1.0f;
pad.mMainStickAngle = -0x4000;
}
if (ImGui::IsKeyDown(ImGuiKey_Q)) {
pad.mTriggerLeft = 1.0;
pad.mTrigLockL = 1;
pad.mHoldLockL = 1;
}
if (ImGui::IsKeyDown(ImGuiKey_E)) {
pad.mTriggerRight = 1.0;
pad.mTrigLockR = 1;
pad.mHoldLockR = 1;
}
}
+1 -1
View File
@@ -219,7 +219,7 @@ void ImGuiEngine_AddTextures() {
ImGuiEngine::orgIcon = AddTexture("org-icon.png"); ImGuiEngine::orgIcon = AddTexture("org-icon.png");
} }
if (ImGuiEngine::duskLogo == 0) { if (ImGuiEngine::duskLogo == 0) {
ImGuiEngine::duskLogo = AddTexture("logo-mascot.png"); ImGuiEngine::duskLogo = AddTexture("logo.png");
} }
} }
} // namespace dusk } // namespace dusk
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
#include "ImGuiFirstRunPreset.hpp"
#include "imgui.h"
#include "ImGuiConsole.hpp"
#include "ImGuiEngine.hpp"
#include "dusk/settings.h"
#include "dusk/config.hpp"
#include <dusk/dusk.h>
namespace dusk {
static void ApplyPresetClassic() {
auto& s = getSettings();
s.video.lockAspectRatio.setValue(true);
s.game.bloomMode.setValue(BloomMode::Classic);
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
}
static void ApplyPresetHD() {
auto& s = getSettings();
s.game.bloomMode.setValue(BloomMode::Classic);
s.game.hideTvSettingsScreen.setValue(true);
s.game.skipWarningScreen.setValue(true);
s.game.noReturnRupees.setValue(true);
s.game.disableRupeeCutscenes.setValue(true);
s.game.noSwordRecoil.setValue(true);
s.game.fastClimbing.setValue(true);
s.game.noMissClimbing.setValue(true);
s.game.fastTears.setValue(true);
s.game.biggerWallets.setValue(true);
s.game.invertCameraXAxis.setValue(true);
s.game.freeCamera.setValue(true);
s.game.no2ndFishForCat.setValue(true);
}
static void ApplyPresetDusk() {
ApplyPresetHD();
auto& s = getSettings();
s.game.enableAchievementNotifications.setValue(true);
s.game.enableQuickTransform.setValue(true);
s.game.instantSaves.setValue(true);
s.game.midnasLamentNonStop.setValue(true);
s.game.enableFrameInterpolation.setValue(true);
s.game.sunsSong.setValue(true);
s.game.bloomMode.setValue(BloomMode::Dusk);
s.game.autoSave.setValue(true);
}
// =========================================================================
void ImGuiFirstRunPreset::draw() {
const char* modalTitle = "Welcome to Dusk!";
if (m_done) return;
if (!m_opened) {
ImGui::OpenPopup(modalTitle);
m_opened = true;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always);
if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
// force the user to actually pick one, and not just hit escape to skip the dialog
m_opened = false;
return;
}
ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
int chosen = -1;
if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) {
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGui::TableSetColumnIndex(0);
if (ImGui::Button("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
chosen = 0;
}
ImGui::TableSetColumnIndex(2);
if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
chosen = 1;
}
ImGui::TableSetColumnIndex(4);
if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale())))
{
chosen = 2;
}
ImGui::PopFont();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Spacing();
ImGui::TextWrapped("All enhancements disabled to match the GameCube version. Good for speedrunning or simple nostalgia!");
ImGui::TableSetColumnIndex(2);
ImGui::Spacing();
ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!");
ImGui::TableSetColumnIndex(4);
ImGui::Spacing();
ImGui::TextWrapped("More enhancements enabled than the HD preset. Veteran players will appreciate the additional tweaks!");
ImGui::EndTable();
}
if (chosen >= 0) {
if (chosen == 0) ApplyPresetClassic();
if (chosen == 1) ApplyPresetHD();
if (chosen == 2) ApplyPresetDusk();
getSettings().backend.wasPresetChosen.setValue(true);
config::Save();
m_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
} // namespace dusk
+14
View File
@@ -0,0 +1,14 @@
#pragma once
namespace dusk {
class ImGuiFirstRunPreset {
public:
void draw();
private:
bool m_opened = false;
bool m_done = false;
};
} // namespace dusk
+1 -1
View File
@@ -583,6 +583,7 @@ namespace dusk {
getSettings().game.damageMultiplier.setValue(1); getSettings().game.damageMultiplier.setValue(1);
getSettings().game.instantDeath.setValue(false); getSettings().game.instantDeath.setValue(false);
getSettings().game.noHeartDrops.setValue(false); getSettings().game.noHeartDrops.setValue(false);
getSettings().game.hyperEnemies.setValue(false);
getSettings().game.infiniteHearts.setValue(false); getSettings().game.infiniteHearts.setValue(false);
getSettings().game.infiniteArrows.setValue(false); getSettings().game.infiniteArrows.setValue(false);
@@ -601,7 +602,6 @@ namespace dusk {
getSettings().game.freeMagicArmor.setValue(false); getSettings().game.freeMagicArmor.setValue(false);
getSettings().game.enableTurboKeybind.setValue(false); getSettings().game.enableTurboKeybind.setValue(false);
getSettings().game.debugFlyCam.setValue(false);
} }
SpeedrunInfo m_speedrunInfo; SpeedrunInfo m_speedrunInfo;
+27
View File
@@ -23,6 +23,24 @@
#include <TargetConditionals.h> #include <TargetConditionals.h>
#endif #endif
#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || (defined(__linux__) && !defined(__ANDROID__))
#define DUSK_CAN_OPEN_DATA_FOLDER 1
namespace fs = std::filesystem;
static void OpenDataFolder() {
const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
#if defined(_WIN32)
const std::string url = std::string("file:///") + path;
#else
const std::string url = std::string("file://") + path;
#endif
(void)SDL_OpenURL(url.c_str());
}
#else
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
namespace aurora::gx { namespace aurora::gx {
extern bool enableLodBias; extern bool enableLodBias;
} }
@@ -48,6 +66,7 @@ namespace dusk {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
ImGui::MenuItem("Achievements", nullptr, &m_showAchievements);
#if DUSK_CAN_OPEN_DATA_FOLDER #if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator(); ImGui::Separator();
@@ -249,4 +268,12 @@ namespace dusk {
ImGui::End(); ImGui::End();
ImGui::PopFont(); ImGui::PopFont();
} }
void ImGuiMenuTools::ShowAchievements() {
m_achievementsWindow.draw(m_showAchievements);
}
void ImGuiMenuTools::notifyAchievement(std::string name) {
m_achievementsWindow.notify(std::move(name));
}
} }
+6 -1
View File
@@ -2,10 +2,10 @@
#define DUSK_IMGUI_MENUTOOLS_HPP #define DUSK_IMGUI_MENUTOOLS_HPP
#include <aurora/aurora.h> #include <aurora/aurora.h>
#include <queue>
#include <string> #include <string>
#include "imgui.h" #include "imgui.h"
#include "ImGuiAchievements.hpp"
#include "ImGuiSaveEditor.hpp" #include "ImGuiSaveEditor.hpp"
#include "ImGuiStateShare.hpp" #include "ImGuiStateShare.hpp"
@@ -27,6 +27,8 @@ namespace dusk {
void ShowAudioDebug(); void ShowAudioDebug();
void ShowSaveEditor(); void ShowSaveEditor();
void ShowStateShare(); void ShowStateShare();
void ShowAchievements();
void notifyAchievement(std::string name);
private: private:
bool m_showDebugOverlay = false; bool m_showDebugOverlay = false;
@@ -66,6 +68,9 @@ namespace dusk {
bool m_showStateShare = false; bool m_showStateShare = false;
ImGuiStateShare m_stateShare; ImGuiStateShare m_stateShare;
bool m_showAchievements = false;
ImGuiAchievements m_achievementsWindow;
}; };
} }
+282
View File
@@ -0,0 +1,282 @@
#include "imgui.h"
#include "ImGuiConfig.hpp"
#include "ImGuiEngine.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "../file_select.hpp"
#include "../iso_validate.hpp"
#include "ImGuiConsole.hpp"
#include "dusk/main.h"
#include "dusk/settings.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_filesystem.h>
#include "aurora/lib/internal.hpp"
#include "aurora/lib/window.hpp"
namespace dusk {
typedef void (ImGuiPreLaunchWindow::*drawFunc)();
drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions};
static constexpr std::array<const char*, 5> skLanguageNames = {
"English", "German", "French", "Spanish", "Italian"
};
static constexpr std::array<SDL_DialogFileFilter, 2> skGameDiscFileFilters{{
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
{"All Files", "*"},
}};
static std::string ShowIsoInvalidError(const iso::ValidationError code) {
using namespace std::literals::string_literals;
switch (code) {
case iso::ValidationError::IOError:
return "Unknown IO error occurred"s;
case iso::ValidationError::InvalidImage:
return "Unable to interpret selected file as a disc image"s;
case iso::ValidationError::WrongGame:
return "Selected disc image is for a different game"s;
case iso::ValidationError::WrongVersion:
return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s;
case iso::ValidationError::ExecutableMismatch:
return "Selected disc image contains modified executable files."s;
default:
return "Unknown error"s;
}
}
static std::string_view card_type_name(CARDFileType type) {
switch (type) {
case CARD_GCIFOLDER:
return "GCI Folder"sv;
case CARD_RAWIMAGE:
return "Card Image"sv;
default:
return ""sv;
}
}
void fileDialogCallback(void* userdata, const char* path, const char* error) {
auto* self = static_cast<ImGuiPreLaunchWindow*>(userdata);
if (error != nullptr) {
self->m_selectedIsoPath.clear();
self->m_errorString = fmt::format("File dialog error: {}", error);
return;
}
if (path == nullptr) {
self->m_selectedIsoPath.clear();
return;
}
self->m_selectedIsoPath = path;
self->m_isPal = iso::isPal(path);
getSettings().backend.isoPath.setValue(self->m_selectedIsoPath);
config::Save();
}
ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default;
bool ImGuiPreLaunchWindow::isSelectedPathValid() const {
#if TARGET_ANDROID
return !m_selectedIsoPath.empty(); // unsure why SDL_GetPathInfo doesnt work here
#else
return !m_selectedIsoPath.empty() && SDL_GetPathInfo(m_selectedIsoPath.c_str(), nullptr);
#endif
}
void ImGuiPreLaunchWindow::draw() {
if (m_IsFirstDraw) {
m_selectedIsoPath = getSettings().backend.isoPath;
m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str());
m_initialGraphicsBackend = getSettings().backend.graphicsBackend;
m_IsFirstDraw = false;
}
if (isSelectedPathValid() && getSettings().backend.skipPreLaunchUI) {
dusk::IsGameLaunched = true;
return;
}
auto& io = ImGui::GetIO();
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y));
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowBgAlpha(0.65f);
ImGui::Begin("Pre Launch Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBringToFrontOnFocus);
const auto& windowSize = ImGui::GetWindowSize();
for (int i = 0; i < 5; i++)
ImGui::NewLine();
float iconSize = 150.f;
ImGui::SameLine(windowSize.x / 2 - iconSize + (iconSize / 2));
if (ImGuiEngine::orgIcon != 0) {
ImGui::Image(ImGuiEngine::orgIcon, ImVec2{iconSize, iconSize});
}
ImGuiTextCenter("Twilit Realm presents");
if (ImGuiEngine::duskLogo) {
ImGui::NewLine();
float width = iconSize * 2.5f;
ImGui::SameLine(windowSize.x / 2 - width + (width / 2));
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
} else {
ImGui::PushFont(ImGuiEngine::fontExtraLarge);
ImGuiTextCenter("Dusk");
ImGui::PopFont();
}
(this->*drawTable[m_CurMenu])();
ImGui::End();
}
void ImGuiPreLaunchWindow::drawMainMenu() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::SetCursorPosY(windowSize.y - 200);
ImGui::PushFont(ImGuiEngine::fontLarge);
if (!isSelectedPathValid()) {
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
if (ImGuiButtonCenter("Select disc image...")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
} else {
if (ImGuiButtonCenter("Start game")) {
dusk::IsGameLaunched = true;
}
}
if (ImGuiButtonCenter("Options")) {
m_CurMenu = 1;
}
ImGui::PopFont();
}
void ImGuiPreLaunchWindow::drawOptions() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::NewLine();
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Options");
ImGui::Separator();
ImGui::PopFont();
auto cursorY = ImGui::GetCursorPosY();
float endCursorY = windowSize.y - 100;
float childWidth = windowSize.x - 400;
ImGui::SetCursorPosX(windowSize.x / 2 - (childWidth / 2));
if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY),
ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground))
{
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
if (m_isPal) {
auto selectedLanguage = getSettings().game.language.getValue();
if (ImGui::BeginCombo("Language", skLanguageNames[static_cast<u8>(selectedLanguage)])) {
for (u8 i = 0; i < skLanguageNames.size(); ++i) {
if (ImGui::Selectable(skLanguageNames[i])) {
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
}
}
ImGui::EndCombo();
}
}
AuroraBackend configuredBackend = BACKEND_AUTO;
const std::string& configuredBackendId = getSettings().backend.graphicsBackend;
if (!try_parse_backend(configuredBackendId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
if (ImGui::BeginCombo("Graphics Backend", backend_name(configuredBackend).data())) {
if (ImGui::Selectable("Auto", configuredBackend == BACKEND_AUTO)) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
}
size_t backendCount = 0;
const AuroraBackend* availableBackends = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
const AuroraBackend backend = availableBackends[i];
const bool isSelected = configuredBackend == backend;
if (ImGui::Selectable(backend_name(backend).data(), isSelected)) {
getSettings().backend.graphicsBackend.setValue(
std::string(backend_id(backend)));
config::Save();
}
if (isSelected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
if (configuredBackendId != m_initialGraphicsBackend) {
ImGui::TextDisabled("Restart Required");
}
auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue();
if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) {
if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) {
getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER);
config::Save();
}
if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) {
getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE);
config::Save();
}
ImGui::EndCombo();
}
ImGui::EndChild();
}
ImGui::SetCursorPosY(endCursorY);
ImGui::NewLine();
ImGui::Separator();
ImGui::PushFont(ImGuiEngine::fontLarge);
if (ImGuiButtonCenter("Back")) {
m_CurMenu = 0;
}
ImGui::PopFont();
}
} // namespace dusk
+23
View File
@@ -0,0 +1,23 @@
#pragma once
namespace dusk {
class ImGuiPreLaunchWindow {
private:
int m_CurMenu = 0;
bool m_IsFirstDraw = true;
std::string m_initialGraphicsBackend;
bool isSelectedPathValid() const;
public:
ImGuiPreLaunchWindow();
void draw();
void drawMainMenu();
void drawOptions();
std::string m_selectedIsoPath;
std::string m_errorString;
bool m_isPal = false;
};
} // namespace dusk
+127 -80
View File
@@ -13,6 +13,7 @@
#include "d/actor/d_a_player.h" #include "d/actor/d_a_player.h"
#include <map> #include <map>
#include <bit>
namespace dusk { namespace dusk {
enum ItemType { enum ItemType {
@@ -1353,30 +1354,38 @@ namespace dusk {
// genCommonAreaFlags(membit); // genCommonAreaFlags(membit);
} }
template <typename FlagIter, typename FlagTester> template <typename T>
requires requires(FlagIter a, FlagTester tester) { concept FlagIter = requires(T t) {
--a; ++a; a < a; *a; ++t;
a + 1; --t;
{ tester(*a) } -> std::convertible_to<bool>; t + 1;
} t < t;
static void sortByFlags(FlagIter begin, FlagIter end, FlagTester&& flagTester) { { t->flagID } -> std::convertible_to<u16>;
};
template <typename T>
concept FlagTester = requires(T t, u16 flagID) {
{ t(flagID) } -> std::convertible_to<bool>;
};
static void sortByFlags(FlagIter auto begin, FlagIter auto end, FlagTester auto&& flagTester) {
if (begin == end) return; if (begin == end) return;
auto fullEnd = end; FlagIter auto fullEnd = end;
// We want to find the location of where we can swap our `On` flags to. // We want to find the location of where we can swap our `On` flags to.
// We're gonna put the `Off` bits first, and the `On` bits last. 0 < 1 // We're gonna put the `Off` bits first, and the `On` bits last. 0 < 1
// We can achieve this by skipping all the `On` bits at the end. // We can achieve this by skipping all the `On` bits at the end.
// backtrack until we find a bit that is off // backtrack until we find a bit that is off
while (begin < --end && flagTester(*end)) { while (begin < --end && flagTester(end->flagID)) {
// move the end pointer back while we find on bits // move the end pointer back while we find on bits
} }
// end should now be pointing to a bit that is off // end should now be pointing to a bit that is off
while (begin < end) { while (begin < end) {
// if there's a flag that's on // if there's a flag that's on
if (flagTester(*begin)) { if (flagTester(begin->flagID)) {
// move it to the end // move it to the end
std::rotate(begin, begin + 1, fullEnd); std::rotate(begin, begin + 1, fullEnd);
// move back the end of where we're checking // move back the end of where we're checking
@@ -1392,82 +1401,122 @@ namespace dusk {
static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) { static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) {
const auto LoadFlag = [&](const EventAreaFlags& flag) -> bool { constexpr auto makeMask = [](uint8_t size) -> uint16_t { return (1 << size) - 1; };
switch (flag.flag.type) { constexpr auto getByteIndexFromFlag = [](uint16_t f) -> uint8_t { return f >> 8; };
case AreaFlagType::Item: { constexpr auto getBitMaskFromFlag = [](uint16_t f) -> uint8_t { return f & 0xff; };
return membit.isItem(flag.flag.flagID); constexpr auto getValueSize = [getBitMaskFromFlag](uint16_t f) -> uint8_t {
} break; return std::popcount(getBitMaskFromFlag(f));
case AreaFlagType::Switch: { };
return membit.isSwitch(flag.flag.flagID);
} break; constexpr auto makeEventFlag = [](uint8_t byteIndex, uint8_t bitIndices) -> uint16_t {
case AreaFlagType::Tbox: { return (byteIndex << 8) | bitIndices;
return membit.isTbox(flag.flag.flagID); };
} break;
const auto eventFlagToAreaFlag = [&](uint16_t areaFlag) -> int {
auto byteInd = getByteIndexFromFlag(areaFlag);
constexpr size_t areaIndexSize = 5;
// if we're looking at 0x580, that would be byte 5, and check if 0x80 is set on that byte
// the event flags are structured differently than area flags
// B is byte index, b is the flag mask to check
// event flags are BBBBBBBB bbbbbbbb
// for area flags, they check bitIndex, not mask, i is index
// also area uses u32 index, not byte index
// area flags are BBBiiiii
// so we need to convert from bit mask to index
// also our byte index has to become a u32 index
// dividing byte index by sizeof(u32) gets us the u32 index
// but in big endian, the first byte is the highest order byte of the u32
// so we skip 24 bytes for the first byte, 16 for the second, etc
// essentially (3 - (x % 4)), reversing the modulus, 0=3, 1=2
auto bitsToSkip = 8 * ((sizeof(u32) - 1) - (byteInd % sizeof(u32)));
return ((byteInd / sizeof(u32)) << areaIndexSize) | ((std::countr_zero(areaFlag) + bitsToSkip) & makeMask(areaIndexSize));
};
constexpr uint8_t validTbox = sizeof(membit.mTbox);
constexpr uint8_t validSwitch = validTbox + sizeof(membit.mSwitch);
constexpr uint8_t validItem = validSwitch + sizeof(membit.mItem);
constexpr uint16_t tboxConvert = 0;
constexpr uint16_t switchConvert = sizeof(membit.mTbox) << 8;
constexpr uint16_t itemConvert = switchConvert + (sizeof(membit.mItem) << 8);
const auto LoadFlag = [&](uint16_t flag) -> bool {
const auto byteIndex = getByteIndexFromFlag(flag);
if (byteIndex < validTbox) {
return membit.isTbox(eventFlagToAreaFlag(flag - tboxConvert));
} else if (byteIndex < validSwitch) {
return membit.isSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
return membit.isItem(eventFlagToAreaFlag(flag - itemConvert));
} }
return false; return false;
}; };
const auto SetFlag = [&](const AreaFlagInd& flag, bool set) -> void { const auto SetFlag = [&](uint16_t flag, bool set) -> void {
const auto byteIndex = getByteIndexFromFlag(flag);
if (set) { if (set) {
switch (flag.type) { if (byteIndex < validTbox) {
case AreaFlagType::Item: { membit.onTbox(eventFlagToAreaFlag(flag - tboxConvert));
membit.onItem(flag.flagID); } else if (byteIndex < validSwitch) {
} break; membit.onSwitch(eventFlagToAreaFlag(flag - switchConvert));
case AreaFlagType::Switch: { } else if (byteIndex < validItem) {
membit.onSwitch(flag.flagID); membit.onItem(eventFlagToAreaFlag(flag - itemConvert));
} break;
case AreaFlagType::Tbox: {
membit.onTbox(flag.flagID);
} break;
} }
} else { } else {
switch (flag.type) { if (byteIndex < validTbox) {
case AreaFlagType::Item: { membit.offTbox(eventFlagToAreaFlag(flag - tboxConvert));
membit.offItem(flag.flagID); } else if (byteIndex < validSwitch) {
} break; membit.offSwitch(eventFlagToAreaFlag(flag - switchConvert));
case AreaFlagType::Switch: { } else if (byteIndex < validItem) {
membit.offSwitch(flag.flagID); membit.offItem(eventFlagToAreaFlag(flag - itemConvert));
} break;
case AreaFlagType::Tbox: {
membit.offTbox(flag.flagID);
} break;
} }
} }
}; };
const auto LoadMultiByteFlag = [&](const AreaFlagMultibit& flag) -> uint8_t { const auto LoadMultiByteFlag = [&](uint16_t flag) -> uint8_t {
BE(u32)* areaFlags = nullptr; const auto bitInds = getBitMaskFromFlag(flag);
switch (flag.type) { const auto byteIndex = getByteIndexFromFlag(flag);
case AreaFlagType::Item: {
areaFlags = membit.mItem; const uint16_t startingMask = std::bit_floor(bitInds);
} break; uint8_t val = 0;
case AreaFlagType::Switch: { for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
areaFlags = membit.mSwitch; bitIndexMask >>= 1)
} break; {
case AreaFlagType::Tbox: { val <<= 1;
areaFlags = membit.mTbox; if (LoadFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask))) {
} break; val |= 1;
}
} }
assert(areaFlags != nullptr); return val;
return (areaFlags[flag.index] & flag.mask) >> flag.shift;
}; };
const auto SetMultiByteFlag = [&](const AreaFlagMultibit& flag, uint8_t val) -> void { const auto SetMultiByteFlag = [&](uint16_t flag, uint8_t val) -> void {
BE(u32)* areaFlags = nullptr; const auto bitInds = getBitMaskFromFlag(flag);
switch (flag.type) { const auto byteIndex = getByteIndexFromFlag(flag);
case AreaFlagType::Item: {
areaFlags = membit.mItem;
} break;
case AreaFlagType::Switch: {
areaFlags = membit.mSwitch;
} break;
case AreaFlagType::Tbox: {
areaFlags = membit.mTbox;
} break;
}
areaFlags[flag.index] &= ~flag.mask; const uint16_t startingMask = std::bit_floor(bitInds);
areaFlags[flag.index] |= (val << flag.shift) & flag.mask; uint16_t valueMask = 1 << (getValueSize(flag) - 1);
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
bitIndexMask >>= 1, valueMask >>= 1)
{
SetFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask), (val & valueMask) != 0);
}
};
const auto LoadSpreadMultiByte = [&](uint16_t high, uint16_t low) -> uint8_t {
if (low == AREA_FLAG_NONE)
return LoadMultiByteFlag(high);
return (LoadMultiByteFlag(high) << getValueSize(low)) | LoadMultiByteFlag(low);
};
const auto SetSpreadMultiByte = [&](uint16_t high, uint16_t low, uint8_t value) -> void {
if (low == AREA_FLAG_NONE)
return SetMultiByteFlag(high, value);
const auto lowerSize = getValueSize(low);
SetMultiByteFlag(high, value >> lowerSize);
SetMultiByteFlag(low, value & makeMask(lowerSize));
}; };
auto iter = imguiAreaFlagLookup.find(areaIndex); auto iter = imguiAreaFlagLookup.find(areaIndex);
@@ -1519,7 +1568,7 @@ namespace dusk {
case COLUMN_DESC: case COLUMN_DESC:
return l.description < r.description; return l.description < r.description;
case COLUMN_BIT: case COLUMN_BIT:
return l.GetFlagID() < r.GetFlagID(); return l.flagID < r.flagID;
} }
return false; return false;
}; };
@@ -1548,9 +1597,9 @@ namespace dusk {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
bool flag = LoadFlag(e); bool flag = LoadFlag(e.flagID);
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flag.flagID).c_str(), &flag)) { if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flagID).c_str(), &flag)) {
SetFlag(e.flag, flag); SetFlag(e.flagID, flag);
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@@ -1562,7 +1611,7 @@ namespace dusk {
} }
for (const auto& multiByteFlag : areaFlags.multibyteFlags) { for (const auto& multiByteFlag : areaFlags.multibyteFlags) {
auto flagValue = LoadMultiByteFlag(multiByteFlag.flag); auto flagValue = LoadSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag);
const char* currentVal = "UNKNOWN"; const char* currentVal = "UNKNOWN";
@@ -1574,7 +1623,7 @@ namespace dusk {
if (ImGui::BeginCombo(multiByteFlag.name, currentVal)) { if (ImGui::BeginCombo(multiByteFlag.name, currentVal)) {
for (const auto& [val, name] : multiByteFlag.enumValues) { for (const auto& [val, name] : multiByteFlag.enumValues) {
if (ImGui::Selectable(name)) { if (ImGui::Selectable(name)) {
SetMultiByteFlag(multiByteFlag.flag, val); SetSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag, val);
} }
} }
ImGui::EndCombo(); ImGui::EndCombo();
@@ -1707,9 +1756,7 @@ namespace dusk {
// if we're sorting by flags, do special sort, regular sort is bad for sorting bools // if we're sorting by flags, do special sort, regular sort is bad for sorting bools
// it can swap values that are the same, and that causes constant reordering // it can swap values that are the same, and that causes constant reordering
if (column == COLUMN_FLAG) { if (column == COLUMN_FLAG) {
const auto testEventFunc = [&event](const duskImguiEventFlagEntry& flag) -> bool { const auto testEventFunc = [&event](u16 flag) -> bool { return event.isEventBit(flag); };
return event.isEventBit(flag.flagID);
};
if (direction == ImGuiSortDirection_Ascending) { if (direction == ImGuiSortDirection_Ascending) {
sortByFlags(std::begin(duskImguiEventFlags), sortByFlags(std::begin(duskImguiEventFlags),
+75 -164
View File
@@ -1,99 +1,49 @@
#include "iso_validate.hpp" #include "iso_validate.hpp"
#include <SDL3/SDL_iostream.h>
#include <nod.h> #include <nod.h>
#include <xxhash.h> #include <span>
#include <array> #include "SDL3/SDL_iostream.h"
#include <memory>
#include <stdexcept>
#include <string_view>
namespace {
constexpr uint8_t hex_nibble_to_u8(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
throw std::invalid_argument("invalid hex character");
}
constexpr uint64_t parse_u64_hex(std::string_view s) {
if (s.size() != 16)
throw std::invalid_argument("expected 16 hex chars for uint64");
uint64_t value = 0;
for (char c : s) {
value = (value << 4) | hex_nibble_to_u8(c);
}
return value;
}
constexpr XXH128_hash_t parse_xxh128(std::string_view hex) {
if (hex.size() != 32)
throw std::invalid_argument("expected 32 hex chars for XXH128");
return XXH128_hash_t{
.low64 = parse_u64_hex(hex.substr(16, 16)),
.high64 = parse_u64_hex(hex.substr(0, 16)),
};
}
} // namespace
namespace dusk::iso { namespace dusk::iso {
enum class Platform : u8 { constexpr const char* TP_GAME_IDS[] = {
GameCube, "GZ2E01", // GCN USA
Wii, "GZ2P01", // GCN PAL
"GZ2J01", // GCN JPN
"RZDE01", // Wii USA
"RZDP01", // Wii PAL
"RZDJ01", // Wii JPN
"RZDK01", // Wii KOR
}; };
enum class Region : u8 { constexpr const char* PAL_GAME_IDS[] = {
NorthAmerica, "GZ2P01", // GCN PAL
Europe, "RZDP01", // Wii PAL
Japan,
Korea,
}; };
struct KnownDisc { constexpr const char* SUPPORTED_TP_GAME_IDS[] = {
std::string_view id; "GZ2E01", // GCN USA
Platform platform; "GZ2P01", // GCN PAL
Region region;
bool supported = false;
XXH128_hash_t hash{};
constexpr KnownDisc(std::string_view id, Platform platform, Region region)
: id(id), platform(platform), region(region) {}
constexpr KnownDisc(
std::string_view id, Platform platform, Region region, const std::string_view hash)
: id(id), platform(platform), region(region), supported(true), hash(parse_xxh128(hash)) {}
}; };
constexpr auto KNOWN_DISCS = std::to_array<KnownDisc>({ template <size_t N>
{"GZ2E01", Platform::GameCube, Region::NorthAmerica, "14e886f08e548a000afde98a3195e788"}, constexpr bool matches(const char (&id)[6], const char* const (&valid)[N]) {
{"GZ2J01", Platform::GameCube, Region::Japan}, for (auto elem : valid) {
{"GZ2P01", Platform::GameCube, Region::Europe, "9ef597588b0035ca9e91b333fa9a8a7e"}, if (strncmp(id, elem, 6) == 0) {
{"RZDE01", Platform::Wii, Region::NorthAmerica}, return true;
{"RZDJ01", Platform::Wii, Region::Japan}, }
{"RZDK01", Platform::Wii, Region::Korea},
{"RZDP01", Platform::Wii, Region::Europe},
});
constexpr const KnownDisc* find_disc(std::string_view id) {
for (const auto& disc : KNOWN_DISCS) {
if (disc.id == id)
return &disc;
} }
return nullptr;
return false;
} }
struct NodHandleWrapper { struct NodHandleWrapper {
NodHandle* handle; NodHandle* handle;
NodHandleWrapper() : handle(nullptr) {} NodHandleWrapper() : handle(nullptr) {
}
~NodHandleWrapper() { ~NodHandleWrapper() {
if (handle != nullptr) { if (handle != nullptr) {
nod_free(handle); nod_free(handle);
@@ -102,7 +52,7 @@ struct NodHandleWrapper {
} }
}; };
static ValidationError convert_nod_error(NodResult result) { static ValidationError convertNodError(NodResult result) {
switch (result) { switch (result) {
case NOD_RESULT_ERR_IO: case NOD_RESULT_ERR_IO:
return ValidationError::IOError; return ValidationError::IOError;
@@ -117,135 +67,96 @@ s64 StreamReadAt(void* user_data, u64 offset, void* out, size_t len) {
if (len == 0) { if (len == 0) {
return 0; return 0;
} }
auto* io = static_cast<SDL_IOStream*>(user_data);
const auto ret = SDL_SeekIO(io, static_cast<s64>(offset), SDL_IO_SEEK_SET); auto io = static_cast<SDL_IOStream*>(user_data);
auto ret = SDL_SeekIO(io, static_cast<s64>(offset), SDL_IO_SEEK_SET);
if (ret < 0) { if (ret < 0) {
return -1; return -1;
} }
const auto read = SDL_ReadIO(io, out, len);
auto read = SDL_ReadIO(io, out, len);
if (read == 0) { if (read == 0) {
if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) { if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) {
return 0; return 0;
} }
return -1; return -1;
} }
return static_cast<s64>(read); return static_cast<s64>(read);
} }
s64 StreamLength(void* user_data) { s64 StreamLength(void* user_data) {
return SDL_GetIOSize(static_cast<SDL_IOStream*>(user_data)); auto io = static_cast<SDL_IOStream*>(user_data);
return SDL_GetIOSize(io);
} }
void StreamClose(void* user_data) { void StreamClose(void* user_data) {
SDL_CloseIO(static_cast<SDL_IOStream*>(user_data)); auto io = static_cast<SDL_IOStream*>(user_data);
SDL_CloseIO(io);
} }
ValidationError verify_disc(NodHandle* disc, VerificationStatus& status) { ValidationError validate(const char* path) {
std::unique_ptr<XXH3_state_t, decltype(&XXH3_freeState)> hashState( NodHandleWrapper disc;
XXH3_createState(), XXH3_freeState);
if (!hashState) {
return ValidationError::Unknown;
}
XXH3_128bits_reset(hashState.get());
while (true) {
if (status.shouldCancel.load(std::memory_order_relaxed)) {
return ValidationError::Canceled;
}
size_t bytesAvail;
const auto buf = nod_buf_read(disc, &bytesAvail);
if (!bytesAvail)
break;
XXH3_128bits_update(hashState.get(), buf, bytesAvail);
status.bytesRead.fetch_add(bytesAvail, std::memory_order_relaxed);
nod_buf_consume(disc, bytesAvail);
}
const auto hash = XXH3_128bits_digest(hashState.get());
if (!XXH128_isEqual(hash, status.knownDisc->hash)) {
return ValidationError::HashMismatch;
}
return ValidationError::Success;
}
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info) {
const auto sdlStream = SDL_IOFromFile(path, "rb"); const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) { if (sdlStream == nullptr) {
return ValidationError::IOError; return ValidationError::IOError;
} }
NodHandleWrapper disc; const NodDiscStream nod_stream {
const NodDiscStream nod_stream{
.user_data = sdlStream, .user_data = sdlStream,
.read_at = StreamReadAt, .read_at = StreamReadAt,
.stream_len = StreamLength, .stream_len = StreamLength,
.close = StreamClose, .close = StreamClose,
}; };
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle); auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) { if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convert_nod_error(result); return convertNodError(result);
}
status.bytesTotal.store(nod_disc_size(disc.handle), std::memory_order_relaxed);
NodDiscHeader header{};
result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) {
return convert_nod_error(result);
}
const auto knownDisc = find_disc(std::string_view(header.game_id, 6));
if (!knownDisc) {
return ValidationError::WrongGame;
}
status.knownDisc = knownDisc;
info.isPal = knownDisc->region == Region::Europe;
if (!knownDisc->supported) {
return ValidationError::WrongVersion;
}
return verify_disc(disc.handle, status);
}
ValidationError inspect(const char* path, DiscInfo& info) {
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return ValidationError::IOError;
}
NodHandleWrapper disc;
const NodDiscStream nod_stream{
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
.close = StreamClose,
};
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convert_nod_error(result);
} }
NodDiscHeader header{}; NodDiscHeader header{};
result = nod_disc_header(disc.handle, &header); result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) { if (result != NOD_RESULT_OK) {
return convert_nod_error(result); return convertNodError(result);
} }
const auto knownDisc = find_disc(std::string_view(header.game_id, 6)); if (!matches(header.game_id, TP_GAME_IDS)) {
if (!knownDisc) {
return ValidationError::WrongGame; return ValidationError::WrongGame;
} }
info.isPal = knownDisc->region == Region::Europe;
if (!knownDisc->supported) { if (!matches(header.game_id, SUPPORTED_TP_GAME_IDS)) {
return ValidationError::WrongVersion; return ValidationError::WrongVersion;
} }
return ValidationError::Success; return ValidationError::Success;
} }
bool isPal(const char* path) { bool isPal(const char* path) {
DiscInfo info{}; NodHandleWrapper disc;
return inspect(path, info) == ValidationError::Success && info.isPal;
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return false;
}
const NodDiscStream nod_stream{
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
.close = StreamClose,
};
if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK || disc.handle == nullptr) {
return false;
}
NodDiscHeader header{};
if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) {
return false;
}
return matches(header.game_id, PAL_GAME_IDS);
} }
} // namespace dusk::iso } // namespace dusk::iso
+12 -30
View File
@@ -1,37 +1,19 @@
#ifndef DUSK_ISO_VALIDATE_HPP #ifndef DUSK_ISO_VALIDATE_HPP
#define DUSK_ISO_VALIDATE_HPP #define DUSK_ISO_VALIDATE_HPP
#include <atomic>
namespace dusk::iso { namespace dusk::iso {
struct KnownDisc; enum class ValidationError : u8 {
Success = 0,
IOError,
InvalidImage,
WrongGame,
WrongVersion,
ExecutableMismatch,
Unknown
};
enum class ValidationError : u8 { ValidationError validate(const char* path);
Unknown = 0, bool isPal(const char* path);
IOError, }
InvalidImage,
WrongGame,
WrongVersion,
Canceled,
HashMismatch,
Success
};
struct VerificationStatus {
std::atomic_size_t bytesRead = 0;
std::atomic_size_t bytesTotal = 0;
const KnownDisc* knownDisc = nullptr;
std::atomic_bool shouldCancel = false;
};
struct DiscInfo {
bool isPal = false;
};
ValidationError inspect(const char* path, DiscInfo& info);
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info);
bool isPal(const char* path);
} // namespace dusk::iso
#endif // DUSK_ISO_VALIDATE_HPP #endif // DUSK_ISO_VALIDATE_HPP
+7 -110
View File
@@ -5,110 +5,17 @@
#endif #endif
#include <aurora/main.h> #include <aurora/main.h>
#include "dusk/main.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#if !defined(_WIN32)
#include <unistd.h>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif
#endif
int game_main(int argc, char* argv[]); int game_main(int argc, char* argv[]);
namespace { namespace {
bool RestartProcess(int argc, char* argv[]) {
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
(void)argc;
(void)argv;
return false;
#elif _WIN32
std::wstring commandLine = GetCommandLineW();
STARTUPINFOW startupInfo{};
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo{};
if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
&startupInfo, &processInfo))
{
fprintf(stderr, "Failed to restart Dusk: CreateProcessW error %lu\n", GetLastError());
return false;
}
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
return true;
#else
std::filesystem::path executablePath;
#if defined(__APPLE__)
uint32_t pathSize = 0;
_NSGetExecutablePath(nullptr, &pathSize);
if (pathSize > 0) {
std::string path(pathSize, '\0');
if (_NSGetExecutablePath(path.data(), &pathSize) == 0) {
path.resize(std::strlen(path.c_str()));
std::error_code ec;
executablePath = std::filesystem::weakly_canonical(path, ec);
if (ec) {
executablePath = path;
}
}
}
#elif defined(__linux__)
std::array<char, 4096> path{};
const ssize_t len = readlink("/proc/self/exe", path.data(), path.size() - 1);
if (len > 0) {
path[static_cast<size_t>(len)] = '\0';
executablePath = path.data();
}
#endif
if (executablePath.empty() && argc > 0 && argv[0] != nullptr && argv[0][0] != '\0') {
std::error_code ec;
executablePath = std::filesystem::absolute(argv[0], ec);
if (ec) {
executablePath = argv[0];
}
}
if (executablePath.empty()) {
fprintf(stderr, "Failed to restart Dusk: unable to resolve executable path\n");
return false;
}
std::vector<std::string> args;
args.reserve(static_cast<size_t>(std::max(argc, 1)));
args.push_back(executablePath.string());
for (int i = 1; i < argc; ++i) {
args.emplace_back(argv[i] != nullptr ? argv[i] : "");
}
std::vector<char*> execArgv;
execArgv.reserve(args.size() + 1);
for (auto& arg : args) {
execArgv.push_back(arg.data());
}
execArgv.push_back(nullptr);
execv(executablePath.c_str(), execArgv.data());
fprintf(stderr, "Failed to restart Dusk: execv failed: %s\n", std::strerror(errno));
return false;
#endif
}
#if _WIN32 #if _WIN32
bool ShouldShowWindowsConsole(int argc, char* argv[]) { bool ShouldShowWindowsConsole(int argc, char* argv[]) {
if (const auto* env = std::getenv("DUSK_CONSOLE")) { if (const auto* env = std::getenv("DUSK_CONSOLE")) {
@@ -146,25 +53,19 @@ void WindowsSetupConsole(bool showConsole) {
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) {
{
DWORD consoleMode = 0; DWORD consoleMode = 0;
if (GetConsoleMode(stdoutHandle, &consoleMode)) { if (GetConsoleMode(stdoutHandle, &consoleMode)) {
SetConsoleMode(stdoutHandle, SetConsoleMode(stdoutHandle,
consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); consoleMode | ENABLE_PROCESSED_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING);
} }
} }
} }
int DuskMain(int argc, char* argv[]) { int DuskMain(int argc, char* argv[]) {
WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv)); WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv));
const int result = game_main(argc, argv); return game_main(argc, argv);
if constexpr (dusk::SupportsProcessRestart) {
if (dusk::RestartRequested) {
return RestartProcess(argc, argv) ? 0 : result;
}
}
return result;
} }
std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) { std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
@@ -180,8 +81,8 @@ std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
} }
std::vector<char> utf8Buffer(static_cast<size_t>(requiredSize)); std::vector<char> utf8Buffer(static_cast<size_t>(requiredSize));
WideCharToMultiByte( WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr,
CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, nullptr); nullptr);
utf8Args.emplace_back(utf8Buffer.data()); utf8Args.emplace_back(utf8Buffer.data());
} }
@@ -208,11 +109,7 @@ int RunWindowsGuiEntryPoint() {
} }
#else #else
int DuskMain(int argc, char* argv[]) { int DuskMain(int argc, char* argv[]) {
const int result = game_main(argc, argv); return game_main(argc, argv);
if (dusk::RestartRequested && RestartProcess(argc, argv)) {
return 0;
}
return result;
} }
#endif #endif
+10 -10
View File
@@ -18,7 +18,6 @@ UserSettings g_userSettings = {
.fanfareVolume {"audio.fanfareVolume", 100}, .fanfareVolume {"audio.fanfareVolume", 100},
.enableReverb {"audio.enableReverb", true}, .enableReverb {"audio.enableReverb", true},
.enableHrtf {"audio.enableHrtf", false}, .enableHrtf {"audio.enableHrtf", false},
.menuSounds {"audio.menuSounds", true},
}, },
.game = { .game = {
@@ -26,12 +25,14 @@ UserSettings g_userSettings = {
// Quality of Life // Quality of Life
.enableQuickTransform {"game.enableQuickTransform", false}, .enableQuickTransform {"game.enableQuickTransform", false},
.hideTvSettingsScreen {"game.hideTvSettingsScreen", true}, .hideTvSettingsScreen {"game.hideTvSettingsScreen", false},
.skipWarningScreen {"game.skipWarningScreen", false},
.biggerWallets {"game.biggerWallets", false}, .biggerWallets {"game.biggerWallets", false},
.noReturnRupees {"game.noReturnRupees", false}, .noReturnRupees {"game.noReturnRupees", false},
.disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false},
.noSwordRecoil {"game.noSwordRecoil", false}, .noSwordRecoil {"game.noSwordRecoil", false},
.damageMultiplier {"game.damageMultiplier", 1}, .damageMultiplier {"game.damageMultiplier", 1},
.hyperEnemies {"game.hyperEnemies", false},
.noHeartDrops {"game.noHeartDrops", false}, .noHeartDrops {"game.noHeartDrops", false},
.instantDeath {"game.instantDeath", false}, .instantDeath {"game.instantDeath", false},
.fastClimbing {"game.fastClimbing", false}, .fastClimbing {"game.fastClimbing", false},
@@ -47,11 +48,11 @@ UserSettings g_userSettings = {
.enableMirrorMode {"game.enableMirrorMode", false}, .enableMirrorMode {"game.enableMirrorMode", false},
.disableMainHUD {"game.disableMainHUD", false}, .disableMainHUD {"game.disableMainHUD", false},
.pauseOnFocusLost {"game.pauseOnFocusLost", false}, .pauseOnFocusLost {"game.pauseOnFocusLost", false},
.enableLinkDollRotation = {"game.enableLinkDollRotation", false}, .enableLinkDollRotation = {"game.enableLinkDollRotation", false },
.enableAchievementNotifications {"game.enableAchievementNotifications", true}, .enableAchievementNotifications {"game.enableAchievementNotifications", false},
// Graphics // Graphics
.bloomMode {"game.bloomMode", BloomMode::Dusk}, .bloomMode {"game.bloomMode", BloomMode::Classic},
.bloomMultiplier {"game.bloomMultiplier", 1.0f}, .bloomMultiplier {"game.bloomMultiplier", 1.0f},
.disableWaterRefraction {"game.disableWaterRefraction", false}, .disableWaterRefraction {"game.disableWaterRefraction", false},
.enableFrameInterpolation {"game.enableFrameInterpolation", false}, .enableFrameInterpolation {"game.enableFrameInterpolation", false},
@@ -78,7 +79,6 @@ UserSettings g_userSettings = {
.invertCameraXAxis {"game.invertCameraXAxis", false}, .invertCameraXAxis {"game.invertCameraXAxis", false},
.invertCameraYAxis {"game.invertCameraYAxis", false}, .invertCameraYAxis {"game.invertCameraYAxis", false},
.freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f}, .freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f},
.debugFlyCam {"game.debugFlyCam", false},
// Cheats // Cheats
.infiniteHearts {"game.infiniteHearts", false}, .infiniteHearts {"game.infiniteHearts", false},
@@ -109,12 +109,12 @@ UserSettings g_userSettings = {
.backend = { .backend = {
.isoPath {"backend.isoPath", ""}, .isoPath {"backend.isoPath", ""},
.isoVerification {"backend.isoVerification", DiscVerificationState::Unknown},
.graphicsBackend {"backend.graphicsBackend", "auto"}, .graphicsBackend {"backend.graphicsBackend", "auto"},
.skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .skipPreLaunchUI {"backend.skipPreLaunchUI", false},
.showPipelineCompilation {"backend.showPipelineCompilation", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false},
.wasPresetChosen {"backend.wasPresetChosen", false}, .wasPresetChosen {"backend.wasPresetChosen", false},
.enableCrashReporting {"backend.enableCrashReporting", true}, .enableCrashReporting {"backend.enableCrashReporting", true},
.duskMenuOpen {"backend.duskMenuOpen", false},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)} .cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)}
} }
}; };
@@ -137,17 +137,18 @@ void registerSettings() {
Register(g_userSettings.audio.fanfareVolume); Register(g_userSettings.audio.fanfareVolume);
Register(g_userSettings.audio.enableReverb); Register(g_userSettings.audio.enableReverb);
Register(g_userSettings.audio.enableHrtf); Register(g_userSettings.audio.enableHrtf);
Register(g_userSettings.audio.menuSounds);
// Game // Game
Register(g_userSettings.game.language); Register(g_userSettings.game.language);
Register(g_userSettings.game.enableQuickTransform); Register(g_userSettings.game.enableQuickTransform);
Register(g_userSettings.game.hideTvSettingsScreen); Register(g_userSettings.game.hideTvSettingsScreen);
Register(g_userSettings.game.skipWarningScreen);
Register(g_userSettings.game.biggerWallets); Register(g_userSettings.game.biggerWallets);
Register(g_userSettings.game.noReturnRupees); Register(g_userSettings.game.noReturnRupees);
Register(g_userSettings.game.disableRupeeCutscenes); Register(g_userSettings.game.disableRupeeCutscenes);
Register(g_userSettings.game.noSwordRecoil); Register(g_userSettings.game.noSwordRecoil);
Register(g_userSettings.game.damageMultiplier); Register(g_userSettings.game.damageMultiplier);
Register(g_userSettings.game.hyperEnemies);
Register(g_userSettings.game.noHeartDrops); Register(g_userSettings.game.noHeartDrops);
Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.instantDeath);
Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastClimbing);
@@ -204,15 +205,14 @@ void registerSettings() {
Register(g_userSettings.game.gyroInvertPitch); Register(g_userSettings.game.gyroInvertPitch);
Register(g_userSettings.game.gyroInvertYaw); Register(g_userSettings.game.gyroInvertYaw);
Register(g_userSettings.game.freeCamera); Register(g_userSettings.game.freeCamera);
Register(g_userSettings.game.debugFlyCam);
Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.isoPath);
Register(g_userSettings.backend.isoVerification);
Register(g_userSettings.backend.graphicsBackend); Register(g_userSettings.backend.graphicsBackend);
Register(g_userSettings.backend.skipPreLaunchUI); Register(g_userSettings.backend.skipPreLaunchUI);
Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.showPipelineCompilation);
Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.wasPresetChosen);
Register(g_userSettings.backend.enableCrashReporting); Register(g_userSettings.backend.enableCrashReporting);
Register(g_userSettings.backend.duskMenuOpen);
Register(g_userSettings.backend.cardFileType); Register(g_userSettings.backend.cardFileType);
} }
-208
View File
@@ -1,208 +0,0 @@
#include "achievements.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "dusk/achievements.h"
#include "fmt/format.h"
#include "m_Do/m_Do_audio.h"
#include "nav_types.hpp"
#include "pane.hpp"
namespace dusk::ui {
namespace {
struct CategoryInfo {
AchievementCategory cat;
const char* label;
};
constexpr CategoryInfo kCategories[] = {
{AchievementCategory::Story, "Story"},
{AchievementCategory::Collection, "Collection"},
{AchievementCategory::Challenge, "Challenge"},
{AchievementCategory::Minigame, "Minigame"},
{AchievementCategory::Misc, "Misc"},
{AchievementCategory::Glitched, "Glitched"},
};
Rml::String build_achievement_info_rml(const Achievement& a) {
Rml::String s = fmt::format(
R"(<div class="achievement-header">)"
R"(<span class="achievement-name{}">{}</span>)"
R"(<span class="achievement-badge{}">{}</span>)"
R"(</div>)"
R"(<p class="achievement-desc">{}</p>)",
a.unlocked ? " unlocked" : "",
a.name,
a.unlocked ? " unlocked" : " locked",
a.unlocked ? "Unlocked" : "Locked",
a.description
);
if (a.isCounter) {
float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f;
s += fmt::format(
R"(<progressbar value="{:.3f}" class="{}"/>)"
R"(<span class="achievement-progress">{} / {}</span>)",
fraction,
a.unlocked ? "progress-done" : "progress-ongoing",
a.progress,
a.goal
);
}
return s;
}
class AchievementRow : public FluentComponent<AchievementRow> {
public:
AchievementRow(Rml::Element* parent, const Achievement& a)
: FluentComponent(createRowRoot(parent))
{
auto& btn = add_child<Button>(Button::Props{"×"});
mClearButton = &btn;
btn.root()->SetClass("achievement-clear", true);
btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mConfirming) {
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearOne(key.c_str());
resetConfirm();
} else {
mConfirming = true;
mClearButton->set_text("Clear?");
}
return true;
}
if (cmd == NavCommand::Cancel && mConfirming) {
resetConfirm();
return true;
}
return false;
});
Component::listen(btn.root(), Rml::EventId::Blur, [this](Rml::Event&) {
resetConfirm();
});
auto* infoDiv = append(mRoot, "div");
infoDiv->SetClass("achievement-info", true);
infoDiv->SetInnerRML(build_achievement_info_rml(a));
}
bool focus() override { return mClearButton->focus(); }
private:
static Rml::Element* createRowRoot(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement("div");
elem->SetClass("achievement-row", true);
return parent->AppendChild(std::move(elem));
}
void resetConfirm() {
mConfirming = false;
mClearButton->set_text("×");
}
Button* mClearButton = nullptr;
bool mConfirming = false;
};
} // namespace
AchievementsWindow::AchievementsWindow() {
const auto all = AchievementSystem::get().getAchievements();
for (const auto& catInfo : kCategories) {
int catTotal = 0;
for (const auto& a : all) {
if (a.category == catInfo.cat) {
++catTotal;
}
}
if (catTotal == 0) {
continue;
}
add_tab(catInfo.label, [this, cat = catInfo.cat](Rml::Element* content) {
const auto achievements = AchievementSystem::get().getAchievements();
int total = 0, unlocked = 0;
for (const auto& a : achievements) {
if (a.category == cat) {
++total;
if (a.unlocked) {
++unlocked;
}
}
}
auto& pane = add_child<Pane>(content, Pane::Type::Controlled);
pane.add_section(fmt::format("{} / {} unlocked", unlocked, total));
for (const auto& a : achievements) {
if (a.category != cat) {
continue;
}
pane.add_child<AchievementRow>(a);
}
pane.add_section("Actions");
auto& clearAllBtn = pane.add_button("Clear All Achievements");
auto* clearAllPtr = &clearAllBtn;
auto confirmingAll = std::make_shared<bool>(false);
clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (*confirmingAll) {
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearAll();
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
} else {
*confirmingAll = true;
clearAllPtr->set_text("Are you sure?");
}
return true;
}
if (cmd == NavCommand::Cancel && *confirmingAll) {
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
return true;
}
return false;
});
clearAllBtn.listen(Rml::EventId::Blur, [clearAllPtr, confirmingAll](Rml::Event&) {
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
});
pane.finalize();
});
}
}
void AchievementsWindow::update() {
const auto current = AchievementSystem::get().getAchievements();
bool dirty = current.size() != mSnapshot.size();
if (!dirty) {
for (size_t i = 0; i < current.size(); ++i) {
if (current[i].progress != mSnapshot[i].progress ||
current[i].unlocked != mSnapshot[i].unlocked)
{
dirty = true;
break;
}
}
}
if (dirty) {
mSnapshot = current;
refresh_active_tab();
}
Window::update();
}
} // namespace dusk::ui
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "dusk/achievements.h"
#include "window.hpp"
#include <vector>
namespace dusk::ui {
class AchievementsWindow : public Window {
public:
AchievementsWindow();
void update() override;
private:
std::vector<Achievement> mSnapshot;
};
} // namespace dusk::ui
+3 -19
View File
@@ -1,25 +1,11 @@
#include "bool_button.hpp" #include "bool_button.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui { namespace dusk::ui {
BoolButton::BoolButton(Rml::Element* parent, Props props) BoolButton::BoolButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, : BaseControlledSelectButton(parent, {std::move(props.key)}),
{
.key = std::move(props.key),
.icon = std::move(props.icon),
}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)), mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)) {} mIsDisabled(std::move(props.isDisabled)) {}
bool BoolButton::modified() const {
if (mIsModified) {
return mIsModified();
}
return BaseControlledSelectButton::modified();
}
bool BoolButton::disabled() const { bool BoolButton::disabled() const {
if (mIsDisabled) { if (mIsDisabled) {
@@ -34,9 +20,7 @@ Rml::String BoolButton::format_value() {
bool BoolButton::handle_nav_command(NavCommand cmd) { bool BoolButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) { if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
const bool newValue = !mGetValue(); mSetValue(!mGetValue());
mSetValue(newValue);
mDoAud_seStartMenu(newValue ? kSoundItemEnable : kSoundItemDisable);
return true; return true;
} }
return false; return false;
-4
View File
@@ -7,16 +7,13 @@ class BoolButton : public BaseControlledSelectButton {
public: public:
struct Props { struct Props {
Rml::String key; Rml::String key;
Rml::String icon;
std::function<bool()> getValue; std::function<bool()> getValue;
std::function<void(bool)> setValue; std::function<void(bool)> setValue;
std::function<bool()> isDisabled; std::function<bool()> isDisabled;
std::function<bool()> isModified;
}; };
BoolButton(Rml::Element* parent, Props props); BoolButton(Rml::Element* parent, Props props);
bool modified() const override;
bool disabled() const override; bool disabled() const override;
protected: protected:
@@ -27,7 +24,6 @@ private:
std::function<int()> mGetValue; std::function<int()> mGetValue;
std::function<void(int)> mSetValue; std::function<void(int)> mSetValue;
std::function<bool()> mIsDisabled; std::function<bool()> mIsDisabled;
std::function<bool()> mIsModified;
}; };
} // namespace dusk::ui } // namespace dusk::ui
-3
View File
@@ -2,9 +2,6 @@
#include "ui.hpp" #include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <utility> #include <utility>
namespace dusk::ui { namespace dusk::ui {
+10
View File
@@ -59,6 +59,16 @@ void Component::set_disabled(bool value) {
} }
} }
Rml::Element* Component::append(Rml::Element* parent, const Rml::String& tag) {
if (parent == nullptr) {
return nullptr;
}
auto* doc = parent->GetOwnerDocument();
if (doc == nullptr) {
return nullptr;
}
return parent->AppendChild(doc->CreateElement(tag));
}
void Component::listen(Rml::Element* element, Rml::EventId event, void Component::listen(Rml::Element* element, Rml::EventId event,
ScopedEventListener::Callback callback, bool capture) { ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) { if (element == nullptr) {
+10
View File
@@ -47,6 +47,7 @@ public:
Rml::Element* root() const { return mRoot; } Rml::Element* root() const { return mRoot; }
protected: protected:
static Rml::Element* append(Rml::Element* parent, const Rml::String& tag);
void clear_children(); void clear_children();
Rml::Element* mRoot = nullptr; Rml::Element* mRoot = nullptr;
@@ -65,6 +66,15 @@ public:
return static_cast<Derived&>(*this); return static_cast<Derived&>(*this);
} }
Derived& on_focus(ScopedEventListener::Callback callback) {
return listen(
Rml::EventId::Focus, [this, callback = std::move(callback)](Rml::Event& event) {
if (!disabled()) {
callback(event);
}
});
}
Derived& on_nav_command(std::function<bool(Rml::Event&, NavCommand)> callback) { Derived& on_nav_command(std::function<bool(Rml::Event&, NavCommand)> callback) {
listen(Rml::EventId::Click, [this, callback](Rml::Event& event) { listen(Rml::EventId::Click, [this, callback](Rml::Event& event) {
if (!disabled() && callback(event, NavCommand::Confirm)) { if (!disabled() && callback(event, NavCommand::Confirm)) {
-941
View File
@@ -1,941 +0,0 @@
#include "controller_config.hpp"
#include "bool_button.hpp"
#include "button.hpp"
#include "pane.hpp"
#include "select_button.hpp"
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <fmt/format.h>
#include <array>
#include <string>
#include <utility>
#include <vector>
namespace dusk::ui {
namespace {
bool keyboard_active(int port) {
u32 count = 0;
return PADGetKeyButtonBindings(static_cast<u32>(port), &count) != nullptr;
}
Rml::String current_controller_name(int port) {
const char* name = PADGetName(port);
if (name != nullptr) {
return name;
}
return keyboard_active(port) ? "Keyboard" : "None";
}
Rml::String controller_index_name(u32 index) {
const char* name = PADGetNameForControllerIndex(index);
if (name == nullptr) {
return fmt::format("Controller {}", index + 1);
}
return name;
}
SDL_Gamepad* gamepad_for_port(int port) {
const s32 index = PADGetIndexForPort(port);
if (index < 0) {
return nullptr;
}
return PADGetSDLGamepadForIndex(static_cast<u32>(index));
}
struct SpecificButtonName {
SDL_GamepadType type;
const char* name;
};
struct ButtonNames {
SDL_GamepadButton button;
std::vector<SpecificButtonName> names;
};
// clang-format off
const std::vector<ButtonNames> kGamepadButtonNames = {
{ SDL_GAMEPAD_BUTTON_LEFT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "L3"},
{SDL_GAMEPAD_TYPE_PS4, "L3"},
{SDL_GAMEPAD_TYPE_PS5, "L3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "R3"},
{SDL_GAMEPAD_TYPE_PS4, "R3"},
{SDL_GAMEPAD_TYPE_PS5, "R3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"},
}},
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "L1"},
{SDL_GAMEPAD_TYPE_PS4, "L1"},
{SDL_GAMEPAD_TYPE_PS5, "L1"},
{SDL_GAMEPAD_TYPE_XBOX360, "LB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "LB"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "R1"},
{SDL_GAMEPAD_TYPE_PS4, "R1"},
{SDL_GAMEPAD_TYPE_PS5, "R1"},
{SDL_GAMEPAD_TYPE_XBOX360, "RB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "RB"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Z"},
}},
{ SDL_GAMEPAD_BUTTON_BACK, {
{SDL_GAMEPAD_TYPE_PS3, "Select"},
{SDL_GAMEPAD_TYPE_PS4, "Share"},
{SDL_GAMEPAD_TYPE_PS5, "Create"},
{SDL_GAMEPAD_TYPE_XBOX360, "Back"},
{SDL_GAMEPAD_TYPE_XBOXONE, "View"},
}},
{ SDL_GAMEPAD_BUTTON_START, {
{SDL_GAMEPAD_TYPE_PS3, "Start"},
{SDL_GAMEPAD_TYPE_PS4, "Options"},
{SDL_GAMEPAD_TYPE_PS5, "Options"},
{SDL_GAMEPAD_TYPE_XBOX360, "Start"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Menu"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"},
}},
};
// 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);
if (mapping.padAxis != PAD_AXIS_TRIGGER_L && mapping.padAxis != PAD_AXIS_TRIGGER_R) {
value += mapping.nativeAxis.sign == AXIS_SIGN_POSITIVE ? "+" : "-";
}
return value;
}
if (mapping.nativeButton != -1) {
return native_button_name(gamepad, static_cast<u32>(mapping.nativeButton));
}
return "Not bound";
}
bool is_dpad_button(PADButton button) {
return button == PAD_BUTTON_UP || button == PAD_BUTTON_DOWN || button == PAD_BUTTON_LEFT ||
button == PAD_BUTTON_RIGHT;
}
bool is_action_button(PADButton button) {
return button == PAD_BUTTON_A || button == PAD_BUTTON_B || button == PAD_BUTTON_X ||
button == PAD_BUTTON_Y || button == PAD_BUTTON_START || button == PAD_TRIGGER_Z;
}
bool input_neutral(int port) {
if (port < 0) {
return true;
}
return PADGetNativeButtonPressed(port) == -1 && PADGetNativeAxisPulled(port).nativeAxis == -1;
}
// A Keydown event with KI_ESCAPE may have been dispatched from the controller bindings,
// so instead poll the keyboard input directly for Escape-to-unbind
bool keyboard_escape_pressed() {
int keyCount = 0;
const bool* keys = SDL_GetKeyboardState(&keyCount);
return keys != nullptr && SDL_SCANCODE_ESCAPE < keyCount && keys[SDL_SCANCODE_ESCAPE];
}
Rml::String keyboard_key_name(s32 scancode) {
if (scancode == PAD_KEY_INVALID) {
return "Not bound";
}
switch (scancode) {
case PAD_KEY_MOUSE_LEFT:
return "Mouse Left";
case PAD_KEY_MOUSE_MIDDLE:
return "Mouse Middle";
case PAD_KEY_MOUSE_RIGHT:
return "Mouse Right";
case PAD_KEY_MOUSE_X1:
return "Mouse X1";
case PAD_KEY_MOUSE_X2:
return "Mouse X2";
default:
break;
}
if (scancode < 0) {
return "Unknown";
}
const char* name = SDL_GetScancodeName(static_cast<SDL_Scancode>(scancode));
if (name == nullptr || name[0] == '\0') {
return "Unknown";
}
return name;
}
bool keyboard_neutral() {
int keyCount = 0;
const bool* keys = SDL_GetKeyboardState(&keyCount);
if (keys != nullptr) {
for (int i = 0; i < keyCount; ++i) {
if (keys[i]) {
return false;
}
}
}
float x, y;
if (SDL_GetMouseState(&x, &y) != 0) {
return false;
}
return true;
}
s32 keyboard_key_pressed() {
int keyCount = 0;
const bool* keys = SDL_GetKeyboardState(&keyCount);
if (keys != nullptr) {
for (int i = 1; i < keyCount; ++i) {
if (i == SDL_SCANCODE_ESCAPE) {
continue;
}
if (keys[i]) {
return static_cast<s32>(i);
}
}
}
float x, y;
const auto mouseButtons = SDL_GetMouseState(&x, &y);
for (int btn = 1; btn <= 5; ++btn) {
if (mouseButtons & (1u << (btn - 1))) {
return -(btn + 1); // maps to PAD_KEY_MOUSE_LEFT (-2), etc.
}
}
return PAD_KEY_INVALID;
}
} // namespace
ControllerConfigWindow::ControllerConfigWindow() {
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
if (capture_active() || mSuppressNavigationUntilNeutral) {
event.StopPropagation();
}
},
true);
if (auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr) {
if (auto* root = context->GetRootElement()) {
mListeners.emplace_back(std::make_unique<ScopedEventListener>(
root, "controllerchange", [this](Rml::Event&) { refresh_controller_page(); }));
}
}
for (int port = PAD_CHAN0; port < PAD_CHANMAX; ++port) {
add_tab(fmt::format("Port {}", port + 1), [this, port](Rml::Element* content) {
if (mPendingPort != -1 && mPendingPort != port) {
cancel_pending_binding();
}
build_port_tab(content, port);
});
}
}
void ControllerConfigWindow::hide(bool close) {
cancel_pending_binding();
Window::hide(close);
}
void ControllerConfigWindow::update() {
poll_pending_binding();
Window::update();
}
void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
mRightPane = &rightPane;
mActivePort = port;
auto addPageButton = [this, &leftPane, &rightPane, port](
Page page, Rml::String key, auto getValue) {
leftPane.register_control(leftPane.add_select_button({
.key = std::move(key),
.getValue = std::move(getValue),
}),
rightPane, [this, port, page](Pane& pane) {
mPage = page;
render_page(pane, port, page);
});
};
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); });
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
.key = "Enable Dead Zones",
.getValue =
[port] {
PADDeadZones* deadZones = PADGetDeadZones(port);
return deadZones != nullptr && deadZones->useDeadzones;
},
.setValue =
[port](bool value) {
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
deadZones->useDeadzones = value;
PADSerializeMappings();
}
},
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
}),
rightPane, [](Pane& pane) {
pane.add_text("Apply configured dead zones to the sticks and analog triggers.");
});
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
.key = "Emulate Triggers",
.getValue =
[port] {
PADDeadZones* deadZones = PADGetDeadZones(port);
return deadZones != nullptr && deadZones->emulateTriggers;
},
.setValue =
[port](bool value) {
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
deadZones->emulateTriggers = value;
PADSerializeMappings();
}
},
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
}),
rightPane, [](Pane& pane) {
pane.add_text("Treat analog trigger movement as digital L and R button input.");
});
render_page(rightPane, port, mPage);
}
void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
pane.clear();
switch (page) {
case Page::Controller: {
pane.add_button(
{
.text = "None",
.isSelected =
[port] { return PADGetIndexForPort(port) < 0 && !keyboard_active(port); },
})
.on_pressed([this, port] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings();
});
pane.add_button({
.text = "Keyboard",
.isSelected = [port] { return keyboard_active(port); },
})
.on_pressed([this, port] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings();
});
const u32 controllerCount = PADCount();
if (controllerCount == 0) {
pane.add_text("No controllers detected");
break;
}
for (u32 i = 0; i < controllerCount; ++i) {
pane.add_button(
{
.text = controller_index_name(i),
.isSelected =
[port, i] { return PADGetIndexForPort(port) == static_cast<s32>(i); },
})
.on_pressed([this, port, i] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port);
PADSerializeMappings();
});
}
break;
}
case Page::Buttons: {
if (keyboard_active(port)) {
auto addKeyButton = [&](PADButton button) {
pane.add_select_button(
{
.key = PADGetButtonName(button),
.getValue =
[this, port, button] {
if (mPendingKeyButton == static_cast<int>(button)) {
return pending_key_label();
}
u32 count = 0;
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, button] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyButton = static_cast<int>(button);
});
};
pane.add_section("Buttons");
addKeyButton(PAD_BUTTON_A);
addKeyButton(PAD_BUTTON_B);
addKeyButton(PAD_BUTTON_X);
addKeyButton(PAD_BUTTON_Y);
addKeyButton(PAD_BUTTON_START);
addKeyButton(PAD_TRIGGER_Z);
pane.add_section("D-Pad");
addKeyButton(PAD_BUTTON_UP);
addKeyButton(PAD_BUTTON_DOWN);
addKeyButton(PAD_BUTTON_LEFT);
addKeyButton(PAD_BUTTON_RIGHT);
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("Buttons");
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = mappings[i];
if (!is_action_button(mapping.padButton)) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
pane.add_section("D-Pad");
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = mappings[i];
if (!is_dpad_button(mapping.padButton)) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
break;
}
case Page::Triggers: {
if (keyboard_active(port)) {
auto addKeyButton = [&](PADButton button) {
pane.add_select_button(
{
.key = PADGetButtonName(button),
.getValue =
[this, port, button] {
if (mPendingKeyButton == static_cast<int>(button)) {
return pending_key_label();
}
u32 count = 0;
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, button] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyButton = static_cast<int>(button);
});
};
auto addKeyAxis = [&](PADAxis axis) {
pane.add_select_button(
{
.key = PADGetAxisName(axis),
.getValue =
[this, port, axis] {
if (mPendingKeyAxis == static_cast<int>(axis)) {
return pending_key_label();
}
u32 count = 0;
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, axis] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyAxis = static_cast<int>(axis);
});
};
pane.add_section("Analog");
addKeyAxis(PAD_AXIS_TRIGGER_L);
addKeyAxis(PAD_AXIS_TRIGGER_R);
pane.add_section("Digital");
addKeyButton(PAD_TRIGGER_L);
addKeyButton(PAD_TRIGGER_R);
break;
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
u32 buttonCount = 0;
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
if (axes == nullptr && buttons == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
pane.add_section("Analog");
constexpr std::array<PADAxis, 2> kTriggerAxes = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R};
if (axes != nullptr) {
for (PADAxis axis : kTriggerAxes) {
if (axis >= axisCount) {
continue;
}
PADAxisMapping& mapping = axes[axis];
pane.add_select_button({
.key = PADGetAxisName(mapping.padAxis),
.getValue =
[this, &mapping, gamepad] {
if (mPendingAxisMapping == &mapping) {
return pending_axis_label();
}
return native_axis_name(mapping, gamepad);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingAxisMapping = &mapping;
});
}
}
pane.add_section("Digital");
if (buttons != nullptr) {
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = buttons[i];
if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
}
break;
}
case Page::Sticks: {
if (keyboard_active(port)) {
auto addKeyAxis = [&](PADAxis axis) {
pane.add_select_button(
{
.key = PADGetAxisDirectionLabel(axis),
.getValue =
[this, port, axis] {
if (mPendingKeyAxis == static_cast<int>(axis)) {
return pending_key_label();
}
u32 count = 0;
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, axis] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyAxis = static_cast<int>(axis);
});
};
pane.add_section("Control Stick");
addKeyAxis(PAD_AXIS_LEFT_Y_POS);
addKeyAxis(PAD_AXIS_LEFT_Y_NEG);
addKeyAxis(PAD_AXIS_LEFT_X_NEG);
addKeyAxis(PAD_AXIS_LEFT_X_POS);
pane.add_section("C Stick");
addKeyAxis(PAD_AXIS_RIGHT_Y_POS);
addKeyAxis(PAD_AXIS_RIGHT_Y_NEG);
addKeyAxis(PAD_AXIS_RIGHT_X_NEG);
addKeyAxis(PAD_AXIS_RIGHT_X_POS);
break;
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
auto addAxis = [&](PADAxis axis) {
if (axis >= axisCount) {
return;
}
PADAxisMapping& mapping = axes[axis];
pane.add_select_button({
.key = PADGetAxisDirectionLabel(mapping.padAxis),
.getValue =
[this, &mapping, gamepad] {
if (mPendingAxisMapping == &mapping) {
return pending_axis_label();
}
return native_axis_name(mapping, gamepad);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingAxisMapping = &mapping;
});
};
pane.add_section("Control Stick");
addAxis(PAD_AXIS_LEFT_Y_POS);
addAxis(PAD_AXIS_LEFT_Y_NEG);
addAxis(PAD_AXIS_LEFT_X_NEG);
addAxis(PAD_AXIS_LEFT_X_POS);
pane.add_section("C Stick");
addAxis(PAD_AXIS_RIGHT_Y_POS);
addAxis(PAD_AXIS_RIGHT_Y_NEG);
addAxis(PAD_AXIS_RIGHT_X_NEG);
addAxis(PAD_AXIS_RIGHT_X_POS);
break;
}
}
}
void ControllerConfigWindow::refresh_controller_page() {
if (!visible() || mPage != Page::Controller || mRightPane == nullptr) {
return;
}
render_page(*mRightPane, mActivePort, Page::Controller);
}
void ControllerConfigWindow::poll_pending_binding() {
if (mSuppressNavigationUntilNeutral && input_neutral(mSuppressNavigationPort)) {
mSuppressNavigationUntilNeutral = false;
mSuppressNavigationPort = -1;
}
if (!capture_active()) {
return;
}
if (keyboard_escape_pressed()) {
unmap_pending_binding();
return;
}
if (!mPendingBindingArmed) {
if (pending_input_neutral()) {
mPendingBindingArmed = true;
}
return;
}
if (mPendingKeyButton >= 0 || mPendingKeyAxis >= 0) {
const s32 scancode = keyboard_key_pressed();
if (scancode != PAD_KEY_INVALID) {
if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(mPendingPort),
{scancode, static_cast<PADButton>(mPendingKeyButton)});
} else {
PADSetKeyAxisBinding(static_cast<u32>(mPendingPort),
{scancode, static_cast<PADAxis>(mPendingKeyAxis), 0});
}
finish_pending_key_binding();
}
return;
}
if (mPendingButtonMapping != nullptr) {
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
const int completedPort = mPendingPort;
mPendingButtonMapping->nativeButton = static_cast<u32>(nativeButton);
finish_pending_binding(completedPort);
}
return;
}
if (mPendingAxisMapping != nullptr) {
const PADSignedNativeAxis nativeAxis = PADGetNativeAxisPulled(mPendingPort);
if (nativeAxis.nativeAxis != -1) {
const int completedPort = mPendingPort;
mPendingAxisMapping->nativeAxis = nativeAxis;
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
return;
}
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
const int completedPort = mPendingPort;
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = nativeButton;
finish_pending_binding(completedPort);
}
}
}
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
mSuppressNavigationPort = completedPort;
PADSerializeMappings();
}
void ControllerConfigWindow::unmap_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
const int completedPort = mPendingPort;
if (mPendingButtonMapping != nullptr) {
mPendingButtonMapping->nativeButton = PAD_NATIVE_BUTTON_INVALID;
finish_pending_binding(completedPort);
} else if (mPendingAxisMapping != nullptr) {
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
} else if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADButton>(mPendingKeyButton)});
finish_pending_key_binding();
} else if (mPendingKeyAxis >= 0) {
PADSetKeyAxisBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADAxis>(mPendingKeyAxis), 0});
finish_pending_key_binding();
}
}
bool ControllerConfigWindow::capture_active() const {
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr ||
mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
}
bool ControllerConfigWindow::pending_input_neutral() const {
if (mPendingKeyButton >= 0 || mPendingKeyAxis >= 0) {
return keyboard_neutral();
}
return input_neutral(mPendingPort);
}
Rml::String ControllerConfigWindow::pending_button_label() const {
return mPendingBindingArmed ? "Press a button..." : "Waiting...";
}
Rml::String ControllerConfigWindow::pending_axis_label() const {
return mPendingBindingArmed ? "Move axis or press a button..." : "Waiting...";
}
void ControllerConfigWindow::cancel_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
!mSuppressNavigationUntilNeutral && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = false;
mSuppressNavigationPort = -1;
}
void ControllerConfigWindow::finish_pending_key_binding() {
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
mPendingBindingArmed = false;
PADSerializeMappings();
}
Rml::String ControllerConfigWindow::pending_key_label() const {
return mPendingBindingArmed ? "Press a key or mouse button..." : "Waiting...";
}
} // namespace dusk::ui
-51
View File
@@ -1,51 +0,0 @@
#pragma once
#include "window.hpp"
#include <pad.h>
namespace dusk::ui {
class ControllerConfigWindow : public Window {
public:
ControllerConfigWindow();
void update() override;
void hide(bool close) override;
private:
enum class Page {
Controller,
Buttons,
Triggers,
Sticks,
};
void build_port_tab(Rml::Element* content, int port);
void render_page(class Pane& pane, int port, Page page);
void refresh_controller_page();
void poll_pending_binding();
void finish_pending_binding(int completedPort);
void unmap_pending_binding();
bool capture_active() const;
bool pending_input_neutral() const;
Rml::String pending_button_label() const;
Rml::String pending_axis_label() const;
void cancel_pending_binding();
void finish_pending_key_binding();
Rml::String pending_key_label() const;
Page mPage = Page::Controller;
Pane* mRightPane = nullptr;
int mActivePort = 0;
int mPendingPort = -1;
bool mPendingBindingArmed = false;
bool mSuppressNavigationUntilNeutral = false;
int mSuppressNavigationPort = -1;
PADButtonMapping* mPendingButtonMapping = nullptr;
PADAxisMapping* mPendingAxisMapping = nullptr;
int mPendingKeyButton = -1;
int mPendingKeyAxis = -1;
};
} // namespace dusk::ui
+2 -9
View File
@@ -3,9 +3,6 @@
#include "aurora/rmlui.hpp" #include "aurora/rmlui.hpp"
#include "ui.hpp" #include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui { namespace dusk::ui {
namespace { namespace {
@@ -20,7 +17,7 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
} // namespace } // namespace
Document::Document(const Rml::String& source) : mDocument(load_document(source)) { Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
// Block events while hidden (except for Menu command); play nav sounds when visible // Block events while hidden (except for Menu command)
listen( listen(
Rml::EventId::Keydown, Rml::EventId::Keydown,
[this](Rml::Event& event) { [this](Rml::Event& event) {
@@ -41,10 +38,7 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source))
listen(Rml::EventId::Keydown, [this](Rml::Event& event) { listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event); const auto cmd = map_nav_event(event);
if (cmd == NavCommand::None) { if (cmd != NavCommand::None && handle_nav_command(event, cmd)) {
return;
}
if (handle_nav_command(event, cmd)) {
event.StopPropagation(); event.StopPropagation();
} }
}); });
@@ -106,7 +100,6 @@ bool Document::visible() const {
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) { bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) { if (cmd == NavCommand::Menu) {
mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen);
toggle(); toggle();
return true; return true;
} }
-4
View File
@@ -31,10 +31,6 @@ public:
show(); show();
} }
} }
void push(std::unique_ptr<Document> document) {
push_document(std::move(document));
hide(false);
}
void pop() { void pop() {
hide(true); hide(true);
show_top_document(); show_top_document();
+326 -373
View File
File diff suppressed because it is too large Load Diff
+1 -12
View File
@@ -10,20 +10,9 @@ ScopedEventListener::ScopedEventListener(
mElement->AddEventListener(mEvent, this, mCapture); mElement->AddEventListener(mEvent, this, mCapture);
} }
ScopedEventListener::ScopedEventListener(
Rml::Element* element, Rml::String event, Callback callback, bool capture)
: mElement(element), mEventName(std::move(event)), mCapture(capture),
mCallback(std::move(callback)) {
mElement->AddEventListener(mEventName, this, mCapture);
}
ScopedEventListener::~ScopedEventListener() { ScopedEventListener::~ScopedEventListener() {
if (mElement != nullptr) { if (mElement != nullptr) {
if (!mEventName.empty()) { mElement->RemoveEventListener(mEvent, this, mCapture);
mElement->RemoveEventListener(mEventName, this, mCapture);
} else {
mElement->RemoveEventListener(mEvent, this, mCapture);
}
mElement = nullptr; mElement = nullptr;
} }
} }
-3
View File
@@ -12,8 +12,6 @@ public:
ScopedEventListener( ScopedEventListener(
Rml::Element* element, Rml::EventId event, Callback callback, bool capture = false); Rml::Element* element, Rml::EventId event, Callback callback, bool capture = false);
ScopedEventListener(
Rml::Element* element, Rml::String event, Callback callback, bool capture = false);
~ScopedEventListener() override; ~ScopedEventListener() override;
ScopedEventListener(const ScopedEventListener&) = delete; ScopedEventListener(const ScopedEventListener&) = delete;
@@ -27,7 +25,6 @@ public:
private: private:
Rml::Element* mElement = nullptr; Rml::Element* mElement = nullptr;
Rml::EventId mEvent = Rml::EventId::Invalid; Rml::EventId mEvent = Rml::EventId::Invalid;
Rml::String mEventName;
bool mCapture = false; bool mCapture = false;
Callback mCallback; Callback mCallback;
}; };
-283
View File
@@ -1,283 +0,0 @@
#include "graphics_tuner.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <dolphin/gx/GXAurora.h>
#include <dolphin/vi.h>
#include <fmt/format.h>
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include <algorithm>
#include <string>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/tuner.rcss" />
</head>
<body>
<div id="root" class="tuner-root">
<div class="tuner">
<div class="header">
<div id="title"></div>
<div id="carousel-container" class="carousel-container"></div>
</div>
<div id="description" class="description"></div>
<div class="divider"></div>
<div id="footer" class="footer"></div>
</div>
</div>
</body>
</rml>
)RML";
int get_value(GraphicsOption option) {
switch (option) {
case GraphicsOption::InternalResolution:
return getSettings().game.internalResolutionScale.getValue();
case GraphicsOption::ShadowResolution:
return getSettings().game.shadowResolutionMultiplier.getValue();
case GraphicsOption::BloomMode:
return static_cast<int>(getSettings().game.bloomMode.getValue());
case GraphicsOption::BloomMultiplier:
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0,
100);
}
return 0;
}
void set_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
getSettings().game.internalResolutionScale.setValue(value);
VISetFrameBufferScale(static_cast<float>(value));
break;
case GraphicsOption::ShadowResolution:
getSettings().game.shadowResolutionMultiplier.setValue(value);
break;
case GraphicsOption::BloomMode:
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::BloomMultiplier:
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
break;
}
config::Save();
}
Rml::Element* create_stepped_carousel_root(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto root = doc->CreateElement("div");
root->SetClass("stepped-carousel", true);
root->SetAttribute("tabindex", "0");
return parent->AppendChild(std::move(root));
}
Rml::Element* create_stepped_carousel_arrow(
Rml::Element* parent, const Rml::String& className, const Rml::String& label) {
auto* doc = parent->GetOwnerDocument();
auto button = doc->CreateElement("button");
button->SetClass("stepped-carousel-arrow", true);
button->SetClass(className, true);
button->SetInnerRML(label);
return parent->AppendChild(std::move(button));
}
} // namespace
SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props)
: Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) {
Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", "&#xe5cb;");
mValueElem = append(mRoot, "div");
mValueElem->SetClass("stepped-carousel-value", true);
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", "&#xe5cc;");
listen(prevElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Left); });
listen(nextElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Right); });
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(cmd)) {
event.StopPropagation();
}
});
}
bool SteppedCarousel::focus() {
return Component::focus();
}
void SteppedCarousel::update() {
if (mValueElem == nullptr) {
return;
}
const int value = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (mProps.formatValue) {
mValueElem->SetInnerRML(mProps.formatValue(value));
} else {
mValueElem->SetInnerRML(std::to_string(value));
}
}
bool SteppedCarousel::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Left) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value - mProps.step, mProps.min, mProps.max));
return true;
}
if (cmd == NavCommand::Right) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value + mProps.step, mProps.min, mProps.max));
return true;
}
return false;
}
void SteppedCarousel::apply(int value) {
const int nextValue = std::clamp(value, mProps.min, mProps.max);
const int currentValue =
std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (nextValue == currentValue) {
return;
}
mDoAud_seStartMenu(kSoundItemChange);
if (mProps.onChange) {
mProps.onChange(nextValue);
}
}
Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution: {
u32 width = 0;
u32 height = 0;
AuroraGetRenderSize(&width, &height);
if (value <= 0) {
return fmt::format("Auto ({}×{})", width, height);
} else {
return fmt::format("{}× ({}×{})", value, width, height);
}
}
case GraphicsOption::ShadowResolution:
return fmt::format("{}×", value);
case GraphicsOption::BloomMode:
switch (static_cast<BloomMode>(value)) {
case BloomMode::Off:
return "Off";
case BloomMode::Classic:
return "Classic";
case BloomMode::Dusk:
return "Dusk";
}
break;
case GraphicsOption::BloomMultiplier:
return fmt::format("{}%", value);
}
return "";
}
GraphicsTuner::GraphicsTuner(GraphicsTunerProps props)
: Document(kDocumentSource), mOption(props.option), mValueMin(props.valueMin),
mValueMax(props.valueMax), mDefaultValue(props.defaultValue) {
if (mDocument == nullptr) {
return;
}
if (auto* title = mDocument->GetElementById("title")) {
title->SetInnerRML(escape(props.title));
}
if (auto* description = mDocument->GetElementById("description")) {
description->SetInnerRML(escape(props.helpText));
}
if (auto* carouselParent = mDocument->GetElementById("carousel-container")) {
add_component<SteppedCarousel>(carouselParent,
SteppedCarousel::Props{
.min = mValueMin,
.max = mValueMax,
.step = 1,
.getValue = [this] { return get_value(mOption); },
.onChange = [this](int value) { set_value(mOption, value); },
.formatValue =
[this](int value) { return format_graphics_setting_value(mOption, value); },
});
}
if (auto* footer = mDocument->GetElementById("footer")) {
auto& returnButton = add_component<Button>(footer, "\xE2\x86\x90 Return", "footer-button")
.on_pressed([this] { pop(); });
returnButton.root()->SetClass("return", true);
auto& resetButton =
add_component<Button>(footer, "Reset to default", "footer-button").on_pressed([this] {
mDoAud_seStartMenu(kSoundItemChange);
reset_default();
});
resetButton.root()->SetClass("reset", true);
}
// Hide document after transition completion
mRoot = mDocument->GetElementById("root");
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
}
void GraphicsTuner::show() {
Document::show();
mRoot->SetAttribute("open", "");
mDoAud_seStartMenu(kSoundWindowOpen);
}
void GraphicsTuner::hide(bool close) {
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
mDoAud_seStartMenu(kSoundWindowClose);
}
}
void GraphicsTuner::update() {
for (const auto& component : mComponents) {
component->update();
}
Document::update();
}
bool GraphicsTuner::focus() {
for (const auto& component : mComponents) {
if (component->focus()) {
return true;
}
}
return false;
}
bool GraphicsTuner::visible() const {
return mRoot->HasAttribute("open");
}
bool GraphicsTuner::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
pop();
return true;
}
return Document::handle_nav_command(event, cmd);
}
void GraphicsTuner::reset_default() {
set_value(mOption, mDefaultValue);
}
} // namespace dusk::ui
-90
View File
@@ -1,90 +0,0 @@
#pragma once
#include "button.hpp"
#include "component.hpp"
#include "document.hpp"
#include "ui.hpp"
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace dusk::ui {
class SteppedCarousel : public Component {
public:
struct Props {
int min = 0;
int max = 0;
int step = 1;
std::function<int()> getValue;
std::function<void(int)> onChange;
std::function<Rml::String(int)> formatValue;
};
SteppedCarousel(Rml::Element* parent, Props props);
bool focus() override;
void update() override;
private:
bool handle_nav_command(NavCommand cmd);
void apply(int value);
Props mProps;
Rml::Element* mValueElem = nullptr;
};
enum class GraphicsOption {
InternalResolution,
ShadowResolution,
BloomMode,
BloomMultiplier,
};
Rml::String format_graphics_setting_value(GraphicsOption option, int value);
struct GraphicsTunerProps {
GraphicsOption option;
Rml::String title;
Rml::String helpText;
int valueMin = 0;
int valueMax = 0;
int defaultValue = 0;
};
class GraphicsTuner : public Document {
public:
explicit GraphicsTuner(GraphicsTunerProps props);
void show() override;
void hide(bool close) override;
void update() override;
bool focus() override;
bool visible() const override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_component(Args&&... args) {
auto child = std::make_unique<T>(std::forward<Args>(args)...);
T& ref = *child;
mComponents.emplace_back(std::move(child));
return ref;
}
void reset_default();
GraphicsOption mOption;
int mValueMin = 0;
int mValueMax = 0;
int mDefaultValue = 0;
std::vector<std::unique_ptr<Component> > mComponents;
Rml::Element* mRoot;
};
} // namespace dusk::ui

Some files were not shown because too many files have changed in this diff Show More