Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3968b67c5d | |||
| 119830c979 | |||
| 647394c875 | |||
| c50d777309 | |||
| 4954685cca | |||
| 39d43a8d8f | |||
| b4761cc54e | |||
| 9aee2e9cfe | |||
| 7c7c8b228e | |||
| ca7714dafd | |||
| 1ad5ab8632 | |||
| c4e7838089 | |||
| 7313712263 | |||
| aa48d95f24 | |||
| c5baabbe9c | |||
| 1d2716139f | |||
| 742ad2c150 | |||
| 18eb0692f0 | |||
| b5f98f69db | |||
| 47593d0eb4 | |||
| 4404fce369 | |||
| 72c20f4dd0 | |||
| 3240885bfd | |||
| 7f0955f022 | |||
| c21bce0093 | |||
| 4e23472ed5 | |||
| cfe1f2304b | |||
| de6568d750 | |||
| 3aafe7fa16 | |||
| c1e65d19e7 | |||
| 5fdf954994 | |||
| 230868af3c | |||
| 66154d9de8 | |||
| 93ec3c7dbd | |||
| 945ce3e4bc | |||
| 782a8573e9 | |||
| 4e0ca51159 | |||
| 3cd160e1b2 | |||
| 93a236a9d2 | |||
| eaf3bc2f40 | |||
| 5ca0a2ba06 | |||
| ccd2bdbaac | |||
| 08321699cd | |||
| 7300c0e0f5 | |||
| 7e562824fe | |||
| ed8b5c96b9 | |||
| 14bccdffa6 | |||
| 39e465bcab | |||
| e53bb3a12d | |||
| 50fccd393f | |||
| 7993740ac8 | |||
| 8e5bb8ae59 | |||
| 8fefdd4114 | |||
| 64c8cee21b | |||
| 25fe686573 | |||
| 1c1ea98fdd | |||
| e098104f8f | |||
| 3c5ade5565 | |||
| b2ad75027e | |||
| fdfbf83b88 | |||
| 1b9ca0949e | |||
| 827037f0fa | |||
| d84c5790f5 | |||
| 49eb2282af | |||
| 741f9ecfab | |||
| 37b8122962 | |||
| 5f2cf68e80 | |||
| 74f2c58b29 | |||
| 208433047a | |||
| 2c01430035 | |||
| 5121437bcf | |||
| f61bd3e5ad | |||
| 835e409b32 | |||
| 010bdb7e25 | |||
| 7ba22b7714 | |||
| efcb19a3d0 | |||
| 55455bb1b5 | |||
| e49be12297 | |||
| 75f4940f5e | |||
| 8047330952 | |||
| 9105dcb078 | |||
| b8e38e03e2 | |||
| 331352878e | |||
| 62a88f1e9a | |||
| 43b603e70b | |||
| 95e6ac54cf | |||
| c4b2e2e501 | |||
| dccba23980 | |||
| bf27d10519 | |||
| 6c27011e32 | |||
| 6220990dc5 | |||
| 93e9767c9f | |||
| c774f53dad | |||
| 7fbfe5ad88 | |||
| ef02037990 | |||
| 23cc18ba0e | |||
| 924dbc7715 | |||
| 742f4938f2 | |||
| 02e0f586d3 | |||
| 5717aeef85 | |||
| da9b99f650 | |||
| 901ce2ee4c | |||
| dd2b993cd5 | |||
| 83577d3b82 | |||
| eaae8b6137 | |||
| d9a0ef760f | |||
| 95470b830f |
@@ -297,8 +297,10 @@ 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)
|
||||||
|
|
||||||
@@ -320,46 +322,13 @@ if (DUSK_MOVIE_SUPPORT)
|
|||||||
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
|
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON)
|
set(DUSK_ENABLE_DISCORD_DEFAULT ON)
|
||||||
if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD)
|
||||||
|
set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
|
||||||
FetchContent_Populate(discord_rpc
|
endif ()
|
||||||
URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz
|
option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT})
|
||||||
URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855
|
if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||||
)
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -1,37 +1,55 @@
|
|||||||

|
<div align="center">
|
||||||
|
<img src="res/logo-mascot.png" alt="Logo" width="640">
|
||||||
|
|
||||||
- ### **[Official Website](https://twilitrealm.dev)**
|
<p align="center">
|
||||||
- ### **[Discord](https://discord.gg/QACynxeyna)**
|
<a href="https://twilitrealm.dev">Official Website</a>
|
||||||
|
•
|
||||||
|
<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.**
|
|
||||||
|
|
||||||
### 1. Verify your ROM dump
|
> [!IMPORTANT]
|
||||||
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.
|
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game.
|
||||||
|
|
||||||
| Version | sha1 hash |
|
### 1. Verify your dump
|
||||||
|--------------| ---------------------------------------- |
|
|
||||||
| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 |
|
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 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
|
|
||||||
- Launch Dusk
|
|
||||||
- Select Options, then set the ISO Path to your supported game dump
|
|
||||||
- Press Start Game to play!
|
|
||||||
|
|
||||||

|
- Extract the .zip file
|
||||||
|
- Launch Dusk
|
||||||
|
- Press **Select Disc Image** and provide the path to your supported game dump.
|
||||||
|
- Press **Play**!
|
||||||
|
|
||||||
# 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>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 85 KiB |
@@ -0,0 +1,66 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1447,36 +1447,37 @@ 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/imgui/ImGuiAchievements.hpp
|
src/dusk/ui/achievements.cpp
|
||||||
src/dusk/imgui/ImGuiAchievements.cpp
|
src/dusk/ui/achievements.hpp
|
||||||
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
|
||||||
@@ -1484,12 +1485,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/popup.cpp
|
src/dusk/ui/menu_bar.cpp
|
||||||
src/dusk/ui/popup.hpp
|
src/dusk/ui/menu_bar.hpp
|
||||||
src/dusk/ui/prelaunch.cpp
|
src/dusk/ui/prelaunch.cpp
|
||||||
src/dusk/ui/prelaunch.hpp
|
src/dusk/ui/prelaunch.hpp
|
||||||
src/dusk/ui/prelaunch_options.cpp
|
src/dusk/ui/preset.cpp
|
||||||
src/dusk/ui/prelaunch_options.hpp
|
src/dusk/ui/preset.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
|
||||||
@@ -1510,6 +1511,8 @@ 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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ 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)
|
||||||
};
|
};
|
||||||
@@ -50,6 +54,13 @@ 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);
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ 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 {
|
||||||
@@ -1028,6 +1040,8 @@ 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);
|
||||||
@@ -1376,6 +1390,10 @@ 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();
|
||||||
|
|||||||
@@ -169,6 +169,12 @@ 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;
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ 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 {
|
||||||
@@ -68,7 +66,6 @@ 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
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef DUSK_DISCORD_RPC
|
#ifdef DUSK_DISCORD
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk::discord {
|
||||||
namespace discord {
|
|
||||||
|
|
||||||
void Initialize();
|
void initialize();
|
||||||
|
void run_callbacks();
|
||||||
|
void update_presence();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
void RunCallbacks();
|
} // namespace dusk::discord
|
||||||
|
|
||||||
void UpdatePresence();
|
#endif // DUSK_DISCORD
|
||||||
|
|
||||||
void Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // DUSK_DISCORD_RPC
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#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 {
|
||||||
@@ -8,7 +12,17 @@ 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
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
struct RoomEntry {
|
struct RoomEntry {
|
||||||
u8 roomNo;
|
u8 roomNo;
|
||||||
std::vector<s16> roomPoints = {};
|
std::vector<s16> roomPoints = {};
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
#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
|
||||||
@@ -19,139 +16,6 @@ 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_
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ 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> {
|
||||||
@@ -33,6 +39,12 @@ 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
|
||||||
@@ -56,6 +68,7 @@ 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
|
||||||
@@ -66,7 +79,6 @@ 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;
|
||||||
@@ -119,6 +131,7 @@ 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;
|
||||||
@@ -149,12 +162,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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#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:
|
||||||
@@ -132,6 +133,18 @@ 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) {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 48 KiB |
@@ -8,140 +8,230 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: "Fira Sans Condensed";
|
font-family: "Fira Sans";
|
||||||
font-size: 24dp;
|
font-weight: normal;
|
||||||
color: #FFFFFF;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay-root {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 45%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: stretch;
|
|
||||||
decorator: vertical-gradient(#00000000 #151610F2);
|
|
||||||
padding: 48dp 0 40dp 0;
|
|
||||||
filter: opacity(0);
|
|
||||||
transition: filter 0.2s linear-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay-root[open] {
|
|
||||||
filter: opacity(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1216dp;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
display: flex;
|
|
||||||
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;
|
|
||||||
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;
|
font-size: 20dp;
|
||||||
line-height: 24dp;
|
color: #E0DBC8;
|
||||||
text-transform: uppercase;
|
display: flex;
|
||||||
color: #FFFFFF;
|
flex-direction: column;
|
||||||
opacity: 1;
|
justify-content: flex-end;
|
||||||
|
align-items: stretch;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast {
|
||||||
|
position: absolute;
|
||||||
|
top: 40dp;
|
||||||
|
right: 40dp;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
border-radius: 14dp;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1dp #92875B;
|
||||||
|
backdrop-filter: blur(5dp);
|
||||||
|
box-shadow: 0 0 15dp 3dp;
|
||||||
|
background-color: rgba(21, 22, 16, 80%);
|
||||||
|
filter: opacity(0);
|
||||||
|
transform: scale(0.9);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: filter transform 0.2s cubic-in-out;
|
||||||
|
padding: 18dp 24dp;
|
||||||
|
gap: 8dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast[open] {
|
||||||
|
filter: opacity(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*toast:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background-color: rgba(61, 59, 36, 80%);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer-button.return {
|
toast:active {
|
||||||
text-align: left;
|
background-color: rgba(45, 43, 26, 80%);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
toast heading {
|
||||||
|
display: flex;
|
||||||
|
gap: 18dp;
|
||||||
|
align-items: center;
|
||||||
|
font-family: "Fira Sans Condensed";
|
||||||
|
font-size: 18dp;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #92875B;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer-button.reset {
|
toast heading > span {
|
||||||
text-align: right;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepped-carousel {
|
toast heading > row {
|
||||||
|
flex: 1 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 4dp;
|
||||||
gap: 16dp;
|
|
||||||
width: auto;
|
|
||||||
min-width: 246dp;
|
|
||||||
padding: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
font-family: "Fira Sans Condensed";
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepped-carousel-value {
|
toast message {
|
||||||
line-height: 29dp;
|
display: flex;
|
||||||
min-width: 166dp;
|
flex-flow: column;
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepped-carousel-arrow {
|
toast.menu-notification message row {
|
||||||
|
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;
|
||||||
min-width: 24dp;
|
font-size: 24dp;
|
||||||
padding: 0;
|
decorator: text("" center center);
|
||||||
border: 0;
|
}
|
||||||
background-color: transparent;
|
|
||||||
opacity: 1;
|
icon.trophy {
|
||||||
cursor: pointer;
|
width: 24dp;
|
||||||
|
height: 24dp;
|
||||||
|
font-size: 24dp;
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.controller {
|
||||||
|
width: 24dp;
|
||||||
|
height: 24dp;
|
||||||
|
font-size: 24dp;
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.warning {
|
||||||
|
width: 24dp;
|
||||||
|
height: 24dp;
|
||||||
|
font-size: 24dp;
|
||||||
|
decorator: text("" 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,44 @@ body {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 20dp;
|
font-size: 20dp;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
background-color: #000000;
|
|
||||||
decorator: image(../prelaunch-bg.png cover left center);
|
|
||||||
filter: opacity(0);
|
filter: opacity(0);
|
||||||
transition: filter 1s 0.1s linear-in-out;
|
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);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 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%;
|
||||||
@@ -34,6 +62,7 @@ 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 */
|
||||||
@@ -46,6 +75,11 @@ menu {
|
|||||||
gap: 48dp;
|
gap: 48dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.mirrored menu {
|
||||||
|
left: auto;
|
||||||
|
right: 96dp;
|
||||||
|
}
|
||||||
|
|
||||||
hero {
|
hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -54,6 +88,10 @@ hero {
|
|||||||
gap: 8dp;
|
gap: 8dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.mirrored hero {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
hero img {
|
hero img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -78,6 +116,7 @@ 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 {
|
||||||
@@ -85,6 +124,7 @@ 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;
|
||||||
@@ -94,8 +134,14 @@ 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 0.1s linear-in-out;
|
transition: decorator color opacity 0.1s linear-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-list button:hover,
|
#menu-list button:hover,
|
||||||
@@ -104,45 +150,136 @@ hero img {
|
|||||||
decorator: horizontal-gradient(#FEE685FF #FEE68500);
|
decorator: horizontal-gradient(#FEE685FF #FEE68500);
|
||||||
}
|
}
|
||||||
|
|
||||||
disk-status {
|
body.mirrored #menu-list {
|
||||||
|
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: 8dp;
|
gap: 12dp;
|
||||||
|
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: 8dp;
|
gap: 12dp;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 24dp;
|
||||||
|
font-effect: glow(0dp 4dp 0dp 4dp black);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status,
|
body.mirrored version-info {
|
||||||
.version {
|
right: auto;
|
||||||
font-size: 24dp;
|
left: 96dp;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status,
|
#disc-status {
|
||||||
.update {
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=good] {
|
||||||
color: #D8F999;
|
color: #D8F999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status[bad] {
|
#disc-status[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("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=bad] icon {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=verifying] icon {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=mismatch] icon {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=unknown] icon {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
#disc-status[status=pending] icon {
|
||||||
|
decorator: text("" 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,
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
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;
|
||||||
@@ -31,3 +42,40 @@ 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("" 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%);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
*, *: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;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 64dp;
|
padding: 64dp;
|
||||||
@@ -17,6 +18,7 @@ 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;
|
||||||
@@ -32,6 +34,15 @@ 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);
|
||||||
@@ -62,7 +73,7 @@ window tab-bar tab {
|
|||||||
|
|
||||||
window content {
|
window content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 0;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -72,7 +83,6 @@ 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;
|
||||||
@@ -90,6 +100,10 @@ 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. */
|
||||||
@@ -184,6 +198,11 @@ 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;
|
||||||
@@ -225,15 +244,241 @@ select-button key {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 18dp;
|
font-size: 18dp;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
flex: 1 0 auto;
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
select-button value {
|
select-button value {
|
||||||
margin-left: auto;
|
flex: 1 1 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("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.error {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.verifying {
|
||||||
|
decorator: text("" center center);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.celebration {
|
||||||
|
decorator: text("" 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#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"
|
||||||
@@ -705,6 +706,11 @@ 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) {
|
||||||
|
|||||||
@@ -44,16 +44,14 @@ 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 (!checkForestOldCentury()) {
|
if (checkMidnaRide()) {
|
||||||
if (checkMidnaRide()) {
|
if ((checkWolf() &&
|
||||||
if ((checkWolf() &&
|
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
|
||||||
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
|
(!checkWolf() &&
|
||||||
(!checkWolf() &&
|
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
|
||||||
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
|
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
|
||||||
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
|
{
|
||||||
{
|
canHowl = true;
|
||||||
canHowl = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,16 +122,14 @@ 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 (!checkForestOldCentury()) {
|
if (checkMidnaRide()) {
|
||||||
if (checkMidnaRide()) {
|
if ((checkWolf() &&
|
||||||
if ((checkWolf() &&
|
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
|
||||||
(checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
|
(!checkWolf() &&
|
||||||
(!checkWolf() &&
|
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
|
||||||
(checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
|
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
|
||||||
(checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
|
{
|
||||||
{
|
canTransform = true;
|
||||||
canTransform = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mProcVar2.field_0x300c == 0) {
|
if (mProcVar2.field_0x300c == 0) {
|
||||||
set3DStatus(BUTTON_STATUS_HOLD_ON, 4);
|
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 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, 1);
|
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 *= 2;
|
size *= 20; // Increase Link's heap size to prevent mods from crashing with higher-quality models.
|
||||||
#endif
|
#endif
|
||||||
JKRHeap* parent = mDoExt_getGameHeap();
|
JKRHeap* parent = mDoExt_getGameHeap();
|
||||||
|
|
||||||
|
|||||||
@@ -254,7 +254,11 @@ 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() {
|
||||||
|
|||||||
@@ -825,7 +825,11 @@ 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;
|
||||||
|
|||||||
@@ -348,7 +348,11 @@ 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;
|
||||||
|
|||||||
@@ -1317,8 +1317,12 @@ 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;
|
||||||
|
|||||||
@@ -517,6 +517,12 @@ 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;
|
||||||
|
|||||||
@@ -3519,7 +3519,15 @@ 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;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#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 = {
|
||||||
@@ -1398,6 +1399,7 @@ 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;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#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();
|
||||||
@@ -295,6 +297,11 @@ int daObjKLift00_c::Create() {
|
|||||||
if(getLock())
|
if(getLock())
|
||||||
mStopSwingingFrames = 5;
|
mStopSwingingFrames = 5;
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
mChainInterpPrevValid = false;
|
||||||
|
mChainInterpCurrValid = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +443,34 @@ 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, ¤t.pos, &tevStr);
|
g_env_light.settingTevStruct(16, ¤t.pos, &tevStr);
|
||||||
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
|
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
|
||||||
@@ -457,6 +492,22 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1040,6 +1040,11 @@ 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();
|
||||||
@@ -7474,7 +7479,104 @@ 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;
|
||||||
|
|||||||
@@ -11,6 +11,62 @@
|
|||||||
#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 {
|
||||||
@@ -1006,7 +1062,16 @@ 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) {
|
||||||
@@ -1034,6 +1099,10 @@ 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;
|
||||||
@@ -1054,6 +1123,10 @@ 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;
|
||||||
|
|||||||
@@ -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 += mPackX;
|
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) 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 += mPackX;
|
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) 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 += mPackX;
|
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) 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 += sp14;
|
mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp14 :) sp14;
|
||||||
mCenterZ -= sp10;
|
mCenterZ -= sp10;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,47 @@
|
|||||||
#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 kMapCircleSize = 16 * kMapResolutionMultiplier;
|
constexpr u16 kMapImageSide = 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, 80, 77, 78, 76, 81, 82,
|
79, // 0: im_map_icon_square_4i.bti
|
||||||
|
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++) {
|
||||||
@@ -35,45 +69,101 @@ void dMpath_n::dTexObjAggregate_c::create() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
auto hqCircle = JKR_NEW TGXTexObj();
|
static bool hqTexsDrawn = false;
|
||||||
|
|
||||||
static bool hqCircleDrawn = false;
|
static u8 hqCircleData[kMapImageTotalPixels];
|
||||||
static u8 hqCircleData[kMapCircleSize * kMapCircleSize];
|
static u8 hqCircleAltData[kMapImageTotalPixels];
|
||||||
|
static u8 hqNijumaruData[kMapImageTotalPixels];
|
||||||
|
static u8 hqEnterData[kMapImageTotalPixels];
|
||||||
|
static u8 hqTryForceData[kMapImageTotalPixels];
|
||||||
|
|
||||||
if (!hqCircleDrawn) {
|
if (!hqTexsDrawn) {
|
||||||
const auto center = kMapCircleSize / 2.0f;
|
constexpr auto center = kMapImageSide / 2.0f;
|
||||||
const auto radiusSq = center * center;
|
constexpr auto radiusSq = center * center;
|
||||||
const auto blocksAcross = kMapCircleSize >> 3;
|
|
||||||
const auto totalPixels = sizeof(hqCircleData);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < totalPixels; i++) {
|
// 6: map_icon_circle16x16_4i.bti - simple circle
|
||||||
// 8x4 block swizzling for I8
|
paint_i8(std::span{hqCircleData}, kMapImageSide, [=](auto x, auto y) {
|
||||||
const auto blockIdx = i >> 5;
|
const auto dx = (x + 0.5f) - center;
|
||||||
const auto localIdx = i & 31;
|
const auto dy = (y + 0.5f) - center;
|
||||||
|
return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
const auto blockY = blockIdx / blocksAcross;
|
// 4: im_map_icon_circle_4i.bti - outlined circle
|
||||||
const auto blockX = blockIdx % blocksAcross;
|
paint_i8(std::span{hqCircleAltData}, kMapImageSide, [=](auto x, auto y) {
|
||||||
|
constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f;
|
||||||
const auto localY = localIdx >> 3;
|
constexpr auto innerRadiusSq = innerRadius * innerRadius;
|
||||||
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;
|
||||||
|
|
||||||
// the original texture is in I4 format and uses 1 to indicate if inside the circle
|
return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0;
|
||||||
// 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
|
||||||
hqCircleDrawn = true;
|
paint_i8(std::span{hqNijumaruData}, kMapImageSide, [=](auto x, auto y) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP,
|
constexpr auto replacements = std::to_array<std::pair<size_t, const u8*> >({
|
||||||
GX_CLAMP, GX_FALSE);
|
{2, hqEnterData},
|
||||||
GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
|
{3, hqNijumaruData},
|
||||||
mp_texObj[6] = hqCircle;
|
{4, hqCircleAltData},
|
||||||
|
{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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -942,7 +942,10 @@ 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(), false, false, false);
|
mpBackTexture->getHeight(),
|
||||||
|
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(),
|
||||||
|
|||||||
@@ -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 param_0;
|
return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) 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 param_0;
|
return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) 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,
|
||||||
|
|||||||
@@ -919,9 +919,20 @@ 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) {
|
||||||
@@ -2464,6 +2475,13 @@ 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))
|
||||||
|
|||||||
@@ -1043,6 +1043,12 @@ 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];
|
||||||
@@ -1397,6 +1403,15 @@ 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,
|
||||||
@@ -1404,6 +1419,15 @@ 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,
|
||||||
|
|||||||
@@ -343,6 +343,11 @@ 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();
|
||||||
@@ -364,6 +369,12 @@ 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();
|
||||||
@@ -399,6 +410,12 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@@ -1987,6 +1987,13 @@ 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 &&
|
||||||
|
|||||||
@@ -427,6 +427,16 @@ 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);
|
||||||
@@ -1880,15 +1890,14 @@ 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
|
||||||
{5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, 5697, 5698,
|
{5181, 5182, 5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665,
|
||||||
5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, 5989, 5990,
|
5697, 5698, 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988,
|
||||||
5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999},
|
5989, 5990, 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, 5433, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452,
|
5432, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452, 5462},
|
||||||
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}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1120,26 +1120,12 @@ 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
|
||||||
if (dusk::getSettings().game.skipWarningScreen) {
|
mTimer = 0; // Possibly unnecessary but just in case
|
||||||
mTimer = 0; // Possibly unnecessary but just in case
|
mExecCommand = EXEC_DVD_WAIT;
|
||||||
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;
|
||||||
|
|||||||
@@ -40,8 +40,9 @@
|
|||||||
#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/autosave.h>
|
#include "dusk/ui/ui.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -794,7 +795,17 @@ 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();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "aurora/lib/logging.hpp"
|
#include "aurora/lib/logging.hpp"
|
||||||
#include "os_report.h"
|
#include "os_report.h"
|
||||||
|
|
||||||
@@ -21,10 +23,35 @@ 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);
|
||||||
std::string buf(ret, '\0');
|
if (ret <= 0) {
|
||||||
vsnprintf(buf.data(), buf.size(), msg, list);
|
return {};
|
||||||
buf.pop_back();
|
}
|
||||||
return buf;
|
++ret;
|
||||||
|
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, ...) {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#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 "f_pc/f_pc_name.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_op/f_op_actor_mng.h"
|
||||||
|
#include "f_pc/f_pc_name.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -454,12 +455,6 @@ 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());
|
||||||
@@ -559,7 +554,14 @@ 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;
|
||||||
m_pendingUnlocks.push(e.achievement.name);
|
if (getSettings().game.enableAchievementNotifications) {
|
||||||
|
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,4 +1,5 @@
|
|||||||
#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];
|
||||||
@@ -83,6 +84,9 @@ void waitingForWrite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void endAutoSave() {
|
void endAutoSave() {
|
||||||
dusk::g_imguiConsole.AddToast("Saving...", 2.0f);
|
dusk::ui::push_toast({
|
||||||
|
.type = "autosave",
|
||||||
|
.duration = std::chrono::milliseconds(1500),
|
||||||
|
});
|
||||||
mAutoSaveProc = 0;
|
mAutoSaveProc = 0;
|
||||||
}
|
}
|
||||||
@@ -154,6 +154,7 @@ 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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,867 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#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
|
||||||
@@ -1,38 +1,39 @@
|
|||||||
#ifdef DUSK_DISCORD_RPC
|
#ifdef DUSK_DISCORD
|
||||||
|
|
||||||
#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 {
|
namespace dusk::discord {
|
||||||
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 const char* APPLICATION_ID = "1495632471994405035";
|
static constexpr const char* kApplicationId = "1495632471994405035";
|
||||||
|
|
||||||
static void OnReady(const DiscordUser* user) {
|
static void on_ready(const rpc::User& user) {
|
||||||
DuskLog.info("Discord: Connected as {}", user->username);
|
DuskLog.info("Discord: Connected as {}", user.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnDisconnected(int errorCode, const char* message) {
|
static void on_disconnected(int errorCode, std::string_view message) {
|
||||||
DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message);
|
DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnError(int errorCode, const char* message) {
|
static void on_error(int errorCode, std::string_view message) {
|
||||||
DuskLog.warn("Discord: Error ({}: {})", errorCode, message);
|
DuskLog.warn("Discord: Error ({}: {})", errorCode, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* LookupMapName(const char* mapFile) {
|
static const char* lookup_map_name(const char* mapFile) {
|
||||||
if (!mapFile || mapFile[0] == '\0') return nullptr;
|
if (!mapFile || mapFile[0] == '\0')
|
||||||
|
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) {
|
||||||
@@ -43,80 +44,80 @@ static const char* LookupMapName(const char* mapFile) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Initialize() {
|
void initialize() {
|
||||||
g_startTime = static_cast<int64_t>(
|
g_startTime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::duration_cast<std::chrono::seconds>(
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
std::chrono::system_clock::now().time_since_epoch()
|
.count();
|
||||||
).count()
|
|
||||||
);
|
|
||||||
|
|
||||||
DiscordEventHandlers handlers{};
|
rpc::EventHandlers handlers{};
|
||||||
handlers.ready = OnReady;
|
handlers.ready = on_ready;
|
||||||
handlers.disconnected = OnDisconnected;
|
handlers.disconnected = on_disconnected;
|
||||||
handlers.errored = OnError;
|
handlers.error = on_error;
|
||||||
Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr);
|
rpc::initialize(kApplicationId, std::move(handlers));
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
|
|
||||||
DuskLog.info("Discord Rich Presence initialized");
|
DuskLog.info("Discord Rich Presence initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunCallbacks() {
|
void run_callbacks() {
|
||||||
if (!g_initialized) return;
|
if (!g_initialized)
|
||||||
Discord_RunCallbacks();
|
return;
|
||||||
|
rpc::run_callbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePresence() {
|
void update_presence() {
|
||||||
if (!g_initialized) return;
|
if (!g_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
static auto lastUpdate = std::chrono::steady_clock::time_point{};
|
static auto sLastUpdate = std::chrono::steady_clock::time_point{};
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
if (now - lastUpdate < std::chrono::seconds(15)) return;
|
if (now - sLastUpdate < std::chrono::seconds(15))
|
||||||
lastUpdate = now;
|
return;
|
||||||
|
sLastUpdate = now;
|
||||||
|
|
||||||
static std::string detailsBuf;
|
static std::string sDetailsBuf;
|
||||||
static std::string stateBuf;
|
static std::string sStateBuf;
|
||||||
|
|
||||||
DiscordRichPresence presence{};
|
rpc::Presence presence{};
|
||||||
presence.startTimestamp = g_startTime;
|
presence.startTimestamp = g_startTime;
|
||||||
presence.largeImageKey = "icon";
|
presence.largeImageKey = "icon";
|
||||||
presence.largeImageText = "Dusk";
|
presence.largeImageText = "Dusk";
|
||||||
|
|
||||||
if (dusk::IsGameLaunched) {
|
if (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 = LookupMapName(stageName);
|
const char* locationName = lookup_map_name(stageName);
|
||||||
|
|
||||||
if (locationName) {
|
if (locationName) {
|
||||||
detailsBuf = locationName;
|
sDetailsBuf = locationName;
|
||||||
}
|
} else {
|
||||||
else {
|
sDetailsBuf = "Twilight Princess";
|
||||||
detailsBuf = "Twilight Princess";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
presence.details = detailsBuf.c_str();
|
presence.details = sDetailsBuf;
|
||||||
|
|
||||||
stateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"),
|
sStateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"),
|
||||||
dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee());
|
dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee());
|
||||||
|
|
||||||
presence.state = stateBuf.c_str();
|
presence.state = sStateBuf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Discord_UpdatePresence(&presence);
|
rpc::update_presence(std::move(presence));
|
||||||
DuskLog.debug("Discord Rich Presence sent");
|
DuskLog.debug("Discord Rich Presence sent");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void shutdown() {
|
||||||
if (!g_initialized) return;
|
if (!g_initialized)
|
||||||
Discord_ClearPresence();
|
return;
|
||||||
Discord_Shutdown();
|
rpc::clear_presence();
|
||||||
|
rpc::shutdown();
|
||||||
g_initialized = false;
|
g_initialized = false;
|
||||||
DuskLog.info("Discord Rich Presence shut down");
|
DuskLog.info("Discord Rich Presence shut down");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord
|
} // namespace dusk::discord
|
||||||
} // namespace dusk
|
|
||||||
|
|
||||||
#endif // DUSK_DISCORD_RPC
|
#endif // DUSK_DISCORD
|
||||||
|
|||||||
@@ -1,245 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
#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() {
|
||||||
@@ -46,70 +49,25 @@ namespace dusk {
|
|||||||
|
|
||||||
ImGui::InputFloat("Camera FOV", &dCam->mFovy);
|
ImGui::InputFloat("Camera FOV", &dCam->mFovy);
|
||||||
|
|
||||||
ImGui::SeparatorText("Free-look Data");
|
ImGui::SeparatorText("Options");
|
||||||
|
|
||||||
static float eyeYawDeg = 0.0f;
|
bool eventRunning = (dComIfGp_event_runCheck() || dComIfGp_isPauseFlag()) && !getSettings().game.debugFlyCam;
|
||||||
static float moveSpeed = 5000.0f;
|
if (eventRunning) {
|
||||||
static float rotSpeed = 5.0f;
|
ImGui::BeginDisabled();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) {
|
config::ImGuiCheckbox("Fly Mode", getSettings().game.debugFlyCam);
|
||||||
eyeYawDeg -= rotSpeed;
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
||||||
if (eyeYawDeg < 0.0f)
|
if (eventRunning) {
|
||||||
eyeYawDeg += 360.0f;
|
ImGui::SetTooltip("Cannot enable while paused or during an active event.");
|
||||||
|
} else {
|
||||||
changed = true;
|
ImGui::SetTooltip("Detach camera and fly freely.\n"
|
||||||
|
"Left stick: move, C-stick: look\n"
|
||||||
|
"L/R triggers: up/down, Z: fast");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cSAngle yawAngle = cSAngle(eyeYawDeg);
|
if (eventRunning) {
|
||||||
cXyz frontDir = cXyz(yawAngle.Sin(), 0.0f, yawAngle.Cos());
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,9 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -22,6 +20,9 @@
|
|||||||
#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"
|
||||||
@@ -35,14 +36,6 @@ 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;
|
||||||
@@ -241,48 +234,7 @@ namespace dusk {
|
|||||||
ImGuiConsole::ImGuiConsole() {}
|
ImGuiConsole::ImGuiConsole() {}
|
||||||
|
|
||||||
void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) {
|
void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) {
|
||||||
if (!IsGameLaunched) {
|
(void)event;
|
||||||
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() {
|
||||||
@@ -301,16 +253,8 @@ namespace dusk {
|
|||||||
|
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
|
|
||||||
AchievementSystem::get().tick();
|
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) &&
|
||||||
while (AchievementSystem::get().hasPendingUnlock()) {
|
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
|
||||||
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;
|
||||||
@@ -320,23 +264,10 @@ 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
|
||||||
@@ -359,16 +290,7 @@ 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();
|
||||||
@@ -377,6 +299,67 @@ 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();
|
||||||
@@ -402,8 +385,6 @@ 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();
|
||||||
|
|||||||
@@ -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 "ImGuiPreLaunchWindow.hpp"
|
#include "dusk/main.h"
|
||||||
#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,17 +42,11 @@ 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;
|
||||||
@@ -79,6 +73,24 @@ bool ImGuiButtonCenter(std::string_view text);
|
|||||||
float ImGuiScale();
|
float ImGuiScale();
|
||||||
} // namespace dusk
|
} // namespace dusk
|
||||||
|
|
||||||
void DuskDebugPad();
|
#if defined(_WIN32) || \
|
||||||
|
(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
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.png");
|
ImGuiEngine::duskLogo = AddTexture("logo-mascot.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace dusk
|
} // namespace dusk
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace dusk {
|
|
||||||
|
|
||||||
class ImGuiFirstRunPreset {
|
|
||||||
public:
|
|
||||||
void draw();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_opened = false;
|
|
||||||
bool m_done = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace dusk
|
|
||||||
@@ -601,6 +601,7 @@ 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;
|
||||||
|
|||||||
@@ -23,24 +23,6 @@
|
|||||||
#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;
|
||||||
}
|
}
|
||||||
@@ -66,7 +48,6 @@ 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();
|
||||||
@@ -268,12 +249,4 @@ 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +27,6 @@ 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;
|
||||||
@@ -68,9 +66,6 @@ namespace dusk {
|
|||||||
|
|
||||||
bool m_showStateShare = false;
|
bool m_showStateShare = false;
|
||||||
ImGuiStateShare m_stateShare;
|
ImGuiStateShare m_stateShare;
|
||||||
|
|
||||||
bool m_showAchievements = false;
|
|
||||||
ImGuiAchievements m_achievementsWindow;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
#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 {
|
||||||
@@ -1354,38 +1353,30 @@ namespace dusk {
|
|||||||
// genCommonAreaFlags(membit);
|
// genCommonAreaFlags(membit);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename FlagIter, typename FlagTester>
|
||||||
concept FlagIter = requires(T t) {
|
requires requires(FlagIter a, FlagTester tester) {
|
||||||
++t;
|
--a; ++a; a < a; *a;
|
||||||
--t;
|
a + 1;
|
||||||
t + 1;
|
{ tester(*a) } -> std::convertible_to<bool>;
|
||||||
t < t;
|
}
|
||||||
{ t->flagID } -> std::convertible_to<u16>;
|
static void sortByFlags(FlagIter begin, FlagIter end, FlagTester&& flagTester) {
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
FlagIter auto fullEnd = end;
|
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->flagID)) {
|
while (begin < --end && flagTester(*end)) {
|
||||||
// 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->flagID)) {
|
if (flagTester(*begin)) {
|
||||||
// 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
|
||||||
@@ -1401,122 +1392,82 @@ namespace dusk {
|
|||||||
|
|
||||||
|
|
||||||
static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) {
|
static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) {
|
||||||
constexpr auto makeMask = [](uint8_t size) -> uint16_t { return (1 << size) - 1; };
|
const auto LoadFlag = [&](const EventAreaFlags& flag) -> bool {
|
||||||
constexpr auto getByteIndexFromFlag = [](uint16_t f) -> uint8_t { return f >> 8; };
|
switch (flag.flag.type) {
|
||||||
constexpr auto getBitMaskFromFlag = [](uint16_t f) -> uint8_t { return f & 0xff; };
|
case AreaFlagType::Item: {
|
||||||
constexpr auto getValueSize = [getBitMaskFromFlag](uint16_t f) -> uint8_t {
|
return membit.isItem(flag.flag.flagID);
|
||||||
return std::popcount(getBitMaskFromFlag(f));
|
} break;
|
||||||
};
|
case AreaFlagType::Switch: {
|
||||||
|
return membit.isSwitch(flag.flag.flagID);
|
||||||
constexpr auto makeEventFlag = [](uint8_t byteIndex, uint8_t bitIndices) -> uint16_t {
|
} break;
|
||||||
return (byteIndex << 8) | bitIndices;
|
case AreaFlagType::Tbox: {
|
||||||
};
|
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 = [&](uint16_t flag, bool set) -> void {
|
const auto SetFlag = [&](const AreaFlagInd& flag, bool set) -> void {
|
||||||
const auto byteIndex = getByteIndexFromFlag(flag);
|
|
||||||
if (set) {
|
if (set) {
|
||||||
if (byteIndex < validTbox) {
|
switch (flag.type) {
|
||||||
membit.onTbox(eventFlagToAreaFlag(flag - tboxConvert));
|
case AreaFlagType::Item: {
|
||||||
} else if (byteIndex < validSwitch) {
|
membit.onItem(flag.flagID);
|
||||||
membit.onSwitch(eventFlagToAreaFlag(flag - switchConvert));
|
} break;
|
||||||
} else if (byteIndex < validItem) {
|
case AreaFlagType::Switch: {
|
||||||
membit.onItem(eventFlagToAreaFlag(flag - itemConvert));
|
membit.onSwitch(flag.flagID);
|
||||||
|
} break;
|
||||||
|
case AreaFlagType::Tbox: {
|
||||||
|
membit.onTbox(flag.flagID);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (byteIndex < validTbox) {
|
switch (flag.type) {
|
||||||
membit.offTbox(eventFlagToAreaFlag(flag - tboxConvert));
|
case AreaFlagType::Item: {
|
||||||
} else if (byteIndex < validSwitch) {
|
membit.offItem(flag.flagID);
|
||||||
membit.offSwitch(eventFlagToAreaFlag(flag - switchConvert));
|
} break;
|
||||||
} else if (byteIndex < validItem) {
|
case AreaFlagType::Switch: {
|
||||||
membit.offItem(eventFlagToAreaFlag(flag - itemConvert));
|
membit.offSwitch(flag.flagID);
|
||||||
|
} break;
|
||||||
|
case AreaFlagType::Tbox: {
|
||||||
|
membit.offTbox(flag.flagID);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto LoadMultiByteFlag = [&](uint16_t flag) -> uint8_t {
|
const auto LoadMultiByteFlag = [&](const AreaFlagMultibit& flag) -> uint8_t {
|
||||||
const auto bitInds = getBitMaskFromFlag(flag);
|
BE(u32)* areaFlags = nullptr;
|
||||||
const auto byteIndex = getByteIndexFromFlag(flag);
|
switch (flag.type) {
|
||||||
|
case AreaFlagType::Item: {
|
||||||
const uint16_t startingMask = std::bit_floor(bitInds);
|
areaFlags = membit.mItem;
|
||||||
uint8_t val = 0;
|
} break;
|
||||||
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
|
case AreaFlagType::Switch: {
|
||||||
bitIndexMask >>= 1)
|
areaFlags = membit.mSwitch;
|
||||||
{
|
} break;
|
||||||
val <<= 1;
|
case AreaFlagType::Tbox: {
|
||||||
if (LoadFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask))) {
|
areaFlags = membit.mTbox;
|
||||||
val |= 1;
|
} break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return val;
|
assert(areaFlags != nullptr);
|
||||||
|
return (areaFlags[flag.index] & flag.mask) >> flag.shift;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto SetMultiByteFlag = [&](uint16_t flag, uint8_t val) -> void {
|
const auto SetMultiByteFlag = [&](const AreaFlagMultibit& flag, uint8_t val) -> void {
|
||||||
const auto bitInds = getBitMaskFromFlag(flag);
|
BE(u32)* areaFlags = nullptr;
|
||||||
const auto byteIndex = getByteIndexFromFlag(flag);
|
switch (flag.type) {
|
||||||
|
case AreaFlagType::Item: {
|
||||||
const uint16_t startingMask = std::bit_floor(bitInds);
|
areaFlags = membit.mItem;
|
||||||
uint16_t valueMask = 1 << (getValueSize(flag) - 1);
|
} break;
|
||||||
|
case AreaFlagType::Switch: {
|
||||||
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
|
areaFlags = membit.mSwitch;
|
||||||
bitIndexMask >>= 1, valueMask >>= 1)
|
} break;
|
||||||
{
|
case AreaFlagType::Tbox: {
|
||||||
SetFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask), (val & valueMask) != 0);
|
areaFlags = membit.mTbox;
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const auto LoadSpreadMultiByte = [&](uint16_t high, uint16_t low) -> uint8_t {
|
areaFlags[flag.index] &= ~flag.mask;
|
||||||
if (low == AREA_FLAG_NONE)
|
areaFlags[flag.index] |= (val << flag.shift) & flag.mask;
|
||||||
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);
|
||||||
@@ -1568,7 +1519,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.flagID < r.flagID;
|
return l.GetFlagID() < r.GetFlagID();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -1597,9 +1548,9 @@ namespace dusk {
|
|||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
bool flag = LoadFlag(e.flagID);
|
bool flag = LoadFlag(e);
|
||||||
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flagID).c_str(), &flag)) {
|
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flag.flagID).c_str(), &flag)) {
|
||||||
SetFlag(e.flagID, flag);
|
SetFlag(e.flag, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
@@ -1611,7 +1562,7 @@ namespace dusk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& multiByteFlag : areaFlags.multibyteFlags) {
|
for (const auto& multiByteFlag : areaFlags.multibyteFlags) {
|
||||||
auto flagValue = LoadSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag);
|
auto flagValue = LoadMultiByteFlag(multiByteFlag.flag);
|
||||||
|
|
||||||
const char* currentVal = "UNKNOWN";
|
const char* currentVal = "UNKNOWN";
|
||||||
|
|
||||||
@@ -1623,7 +1574,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)) {
|
||||||
SetSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag, val);
|
SetMultiByteFlag(multiByteFlag.flag, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
@@ -1756,7 +1707,9 @@ 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](u16 flag) -> bool { return event.isEventBit(flag); };
|
const auto testEventFunc = [&event](const duskImguiEventFlagEntry& flag) -> bool {
|
||||||
|
return event.isEventBit(flag.flagID);
|
||||||
|
};
|
||||||
|
|
||||||
if (direction == ImGuiSortDirection_Ascending) {
|
if (direction == ImGuiSortDirection_Ascending) {
|
||||||
sortByFlags(std::begin(duskImguiEventFlags),
|
sortByFlags(std::begin(duskImguiEventFlags),
|
||||||
|
|||||||
@@ -1,49 +1,99 @@
|
|||||||
#include "iso_validate.hpp"
|
#include "iso_validate.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL_iostream.h>
|
||||||
#include <nod.h>
|
#include <nod.h>
|
||||||
#include <span>
|
#include <xxhash.h>
|
||||||
|
|
||||||
#include "SDL3/SDL_iostream.h"
|
#include <array>
|
||||||
|
#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 {
|
||||||
|
|
||||||
constexpr const char* TP_GAME_IDS[] = {
|
enum class Platform : u8 {
|
||||||
"GZ2E01", // GCN USA
|
GameCube,
|
||||||
"GZ2P01", // GCN PAL
|
Wii,
|
||||||
"GZ2J01", // GCN JPN
|
|
||||||
"RZDE01", // Wii USA
|
|
||||||
"RZDP01", // Wii PAL
|
|
||||||
"RZDJ01", // Wii JPN
|
|
||||||
"RZDK01", // Wii KOR
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr const char* PAL_GAME_IDS[] = {
|
enum class Region : u8 {
|
||||||
"GZ2P01", // GCN PAL
|
NorthAmerica,
|
||||||
"RZDP01", // Wii PAL
|
Europe,
|
||||||
|
Japan,
|
||||||
|
Korea,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr const char* SUPPORTED_TP_GAME_IDS[] = {
|
struct KnownDisc {
|
||||||
"GZ2E01", // GCN USA
|
std::string_view id;
|
||||||
"GZ2P01", // GCN PAL
|
Platform platform;
|
||||||
|
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)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t N>
|
constexpr auto KNOWN_DISCS = std::to_array<KnownDisc>({
|
||||||
constexpr bool matches(const char (&id)[6], const char* const (&valid)[N]) {
|
{"GZ2E01", Platform::GameCube, Region::NorthAmerica, "14e886f08e548a000afde98a3195e788"},
|
||||||
for (auto elem : valid) {
|
{"GZ2J01", Platform::GameCube, Region::Japan},
|
||||||
if (strncmp(id, elem, 6) == 0) {
|
{"GZ2P01", Platform::GameCube, Region::Europe, "9ef597588b0035ca9e91b333fa9a8a7e"},
|
||||||
return true;
|
{"RZDE01", Platform::Wii, Region::NorthAmerica},
|
||||||
}
|
{"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);
|
||||||
@@ -52,7 +102,7 @@ struct NodHandleWrapper {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static ValidationError convertNodError(NodResult result) {
|
static ValidationError convert_nod_error(NodResult result) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case NOD_RESULT_ERR_IO:
|
case NOD_RESULT_ERR_IO:
|
||||||
return ValidationError::IOError;
|
return ValidationError::IOError;
|
||||||
@@ -67,96 +117,135 @@ 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);
|
||||||
auto io = static_cast<SDL_IOStream*>(user_data);
|
const auto ret = SDL_SeekIO(io, static_cast<s64>(offset), SDL_IO_SEEK_SET);
|
||||||
|
|
||||||
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) {
|
||||||
auto io = static_cast<SDL_IOStream*>(user_data);
|
return SDL_GetIOSize(static_cast<SDL_IOStream*>(user_data));
|
||||||
return SDL_GetIOSize(io);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamClose(void* user_data) {
|
void StreamClose(void* user_data) {
|
||||||
auto io = static_cast<SDL_IOStream*>(user_data);
|
SDL_CloseIO(static_cast<SDL_IOStream*>(user_data));
|
||||||
SDL_CloseIO(io);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidationError validate(const char* path) {
|
ValidationError verify_disc(NodHandle* disc, VerificationStatus& status) {
|
||||||
NodHandleWrapper disc;
|
std::unique_ptr<XXH3_state_t, decltype(&XXH3_freeState)> hashState(
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 convertNodError(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodDiscHeader header{};
|
|
||||||
result = nod_disc_header(disc.handle, &header);
|
|
||||||
if (result != NOD_RESULT_OK) {
|
|
||||||
return convertNodError(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matches(header.game_id, TP_GAME_IDS)) {
|
|
||||||
return ValidationError::WrongGame;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matches(header.game_id, SUPPORTED_TP_GAME_IDS)) {
|
|
||||||
return ValidationError::WrongVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ValidationError::Success;
|
|
||||||
}
|
|
||||||
bool isPal(const char* path) {
|
|
||||||
NodHandleWrapper disc;
|
NodHandleWrapper disc;
|
||||||
|
|
||||||
const auto sdlStream = SDL_IOFromFile(path, "rb");
|
|
||||||
if (sdlStream == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
|
||||||
|
return convert_nod_error(result);
|
||||||
|
}
|
||||||
|
|
||||||
if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK || disc.handle == nullptr) {
|
status.bytesTotal.store(nod_disc_size(disc.handle), std::memory_order_relaxed);
|
||||||
return false;
|
|
||||||
|
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{};
|
||||||
if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) {
|
result = nod_disc_header(disc.handle, &header);
|
||||||
return false;
|
if (result != NOD_RESULT_OK) {
|
||||||
|
return convert_nod_error(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches(header.game_id, PAL_GAME_IDS);
|
const auto knownDisc = find_disc(std::string_view(header.game_id, 6));
|
||||||
|
if (!knownDisc) {
|
||||||
|
return ValidationError::WrongGame;
|
||||||
|
}
|
||||||
|
info.isPal = knownDisc->region == Region::Europe;
|
||||||
|
if (!knownDisc->supported) {
|
||||||
|
return ValidationError::WrongVersion;
|
||||||
|
}
|
||||||
|
return ValidationError::Success;
|
||||||
}
|
}
|
||||||
} // namespace dusk::iso
|
|
||||||
|
bool isPal(const char* path) {
|
||||||
|
DiscInfo info{};
|
||||||
|
return inspect(path, info) == ValidationError::Success && info.isPal;
|
||||||
|
}
|
||||||
|
} // namespace dusk::iso
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
#ifndef DUSK_ISO_VALIDATE_HPP
|
#ifndef DUSK_ISO_VALIDATE_HPP
|
||||||
#define DUSK_ISO_VALIDATE_HPP
|
#define DUSK_ISO_VALIDATE_HPP
|
||||||
|
|
||||||
namespace dusk::iso {
|
#include <atomic>
|
||||||
enum class ValidationError : u8 {
|
|
||||||
Success = 0,
|
|
||||||
IOError,
|
|
||||||
InvalidImage,
|
|
||||||
WrongGame,
|
|
||||||
WrongVersion,
|
|
||||||
ExecutableMismatch,
|
|
||||||
Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
ValidationError validate(const char* path);
|
namespace dusk::iso {
|
||||||
bool isPal(const char* path);
|
struct KnownDisc;
|
||||||
}
|
|
||||||
|
enum class ValidationError : u8 {
|
||||||
|
Unknown = 0,
|
||||||
|
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
|
||||||
|
|||||||
@@ -5,17 +5,110 @@
|
|||||||
#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")) {
|
||||||
@@ -53,19 +146,25 @@ 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
|
consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||||
| ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DuskMain(int argc, char* argv[]) {
|
int DuskMain(int argc, char* argv[]) {
|
||||||
WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv));
|
WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv));
|
||||||
return game_main(argc, argv);
|
const int result = 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) {
|
||||||
@@ -81,8 +180,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(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr,
|
WideCharToMultiByte(
|
||||||
nullptr);
|
CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, nullptr);
|
||||||
utf8Args.emplace_back(utf8Buffer.data());
|
utf8Args.emplace_back(utf8Buffer.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +208,11 @@ int RunWindowsGuiEntryPoint() {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int DuskMain(int argc, char* argv[]) {
|
int DuskMain(int argc, char* argv[]) {
|
||||||
return game_main(argc, argv);
|
const int result = game_main(argc, argv);
|
||||||
|
if (dusk::RestartRequested && RestartProcess(argc, argv)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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 = {
|
||||||
@@ -25,8 +26,7 @@ UserSettings g_userSettings = {
|
|||||||
|
|
||||||
// Quality of Life
|
// Quality of Life
|
||||||
.enableQuickTransform {"game.enableQuickTransform", false},
|
.enableQuickTransform {"game.enableQuickTransform", false},
|
||||||
.hideTvSettingsScreen {"game.hideTvSettingsScreen", false},
|
.hideTvSettingsScreen {"game.hideTvSettingsScreen", true},
|
||||||
.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},
|
||||||
@@ -47,11 +47,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", false},
|
.enableAchievementNotifications {"game.enableAchievementNotifications", true},
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
.bloomMode {"game.bloomMode", BloomMode::Classic},
|
.bloomMode {"game.bloomMode", BloomMode::Dusk},
|
||||||
.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,6 +78,7 @@ 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},
|
||||||
@@ -108,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)}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -136,12 +137,12 @@ 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);
|
||||||
@@ -203,14 +204,15 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#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
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
#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, {std::move(props.key)}),
|
: BaseControlledSelectButton(parent,
|
||||||
|
{
|
||||||
|
.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)) {}
|
mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)) {}
|
||||||
|
|
||||||
|
bool BoolButton::modified() const {
|
||||||
|
if (mIsModified) {
|
||||||
|
return mIsModified();
|
||||||
|
}
|
||||||
|
return BaseControlledSelectButton::modified();
|
||||||
|
}
|
||||||
|
|
||||||
bool BoolButton::disabled() const {
|
bool BoolButton::disabled() const {
|
||||||
if (mIsDisabled) {
|
if (mIsDisabled) {
|
||||||
@@ -20,7 +34,9 @@ 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) {
|
||||||
mSetValue(!mGetValue());
|
const bool newValue = !mGetValue();
|
||||||
|
mSetValue(newValue);
|
||||||
|
mDoAud_seStartMenu(newValue ? kSoundItemEnable : kSoundItemDisable);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ 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:
|
||||||
@@ -24,6 +27,7 @@ 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
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#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 {
|
||||||
|
|||||||
@@ -59,16 +59,6 @@ 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) {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ 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;
|
||||||
@@ -66,15 +65,6 @@ 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)) {
|
||||||
|
|||||||
@@ -0,0 +1,941 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#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
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@@ -17,7 +20,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)
|
// Block events while hidden (except for Menu command); play nav sounds when visible
|
||||||
listen(
|
listen(
|
||||||
Rml::EventId::Keydown,
|
Rml::EventId::Keydown,
|
||||||
[this](Rml::Event& event) {
|
[this](Rml::Event& event) {
|
||||||
@@ -38,7 +41,10 @@ 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 && handle_nav_command(event, cmd)) {
|
if (cmd == NavCommand::None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (handle_nav_command(event, cmd)) {
|
||||||
event.StopPropagation();
|
event.StopPropagation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -100,6 +106,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ 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();
|
||||||
|
|||||||
@@ -10,9 +10,20 @@ 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) {
|
||||||
mElement->RemoveEventListener(mEvent, this, mCapture);
|
if (!mEventName.empty()) {
|
||||||
|
mElement->RemoveEventListener(mEventName, this, mCapture);
|
||||||
|
} else {
|
||||||
|
mElement->RemoveEventListener(mEvent, this, mCapture);
|
||||||
|
}
|
||||||
mElement = nullptr;
|
mElement = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ 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;
|
||||||
@@ -25,6 +27,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
#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", "");
|
||||||
|
mValueElem = append(mRoot, "div");
|
||||||
|
mValueElem->SetClass("stepped-carousel-value", true);
|
||||||
|
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", "");
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#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
|
||||||