Compare commits

...

165 Commits

Author SHA1 Message Date
MelonSpeedruns 27274e7341 Merge remote-tracking branch 'origin/main' into feature/hyper-enemies
# Conflicts:
#	src/dusk/imgui/ImGuiMenuGame.cpp
#	src/dusk/settings.cpp
2026-05-03 17:23:54 -04:00
TakaRikka 1cb8b19520 Merge pull request #612 from TwilitRealm/26-05-01-gameheap-size
Increase gameHeap size by an order of magnitude
2026-05-03 00:18:37 -07:00
TakaRikka d109cd891e add note 2026-05-03 00:07:37 -07:00
doop 63c4002ca6 Fix prelaunch nav cycling 2026-05-03 05:49:53 +00:00
Lurs 6954efcd15 Should fix #636 2026-05-03 06:46:49 +02:00
Jasper St. Pierre 78c2147771 midna eye lod fix 2026-05-02 15:36:00 -07:00
Irastris c938d5468e Add Interface tab to RmlUi, remove ImGui interface menu 2026-05-02 18:16:42 -04:00
Irastris 35590c5312 Increase font size for pane text 2026-05-02 17:24:10 -04:00
Luke Street e976b10e2a UI: Avoid overlapping ImGui menu bar 2026-05-02 15:22:24 -06:00
Irastris 3949706b28 Revise gyro aim help text, remove ImGui input menu 2026-05-02 17:19:41 -04:00
Irastris ce55916845 Add Autosave to RmlUi, remove ImGui gameplay menu, formatting consistency 2026-05-02 17:01:30 -04:00
Irastris 9b252cbdd2 Remove ImGui cheats menu 2026-05-02 16:33:50 -04:00
Irastris 97fa09f6ee Add spatial sound option to RmlUi, remove ImGui audio menu 2026-05-02 16:05:57 -04:00
TakaRikka 0e7a7cccb9 Merge pull request #458 from TwilitRealm/feature/autosave
Autosave Feature
2026-05-02 13:03:29 -07:00
Irastris 1c0cdcc176 Typo 2: Electric Boogaloo 2026-05-02 15:54:42 -04:00
Irastris 280305c2ba Typo 2026-05-02 15:33:44 -04:00
Luke Street d1f1d579bc Prevent macOS crash on shutdown by tracking logging dtor (#626) 2026-05-02 13:22:28 -06:00
Irastris eeeb3ffe25 Remove ImGui graphics menu 2026-05-02 14:45:34 -04:00
Jasper St. Pierre 1ee0f862e1 map highlight fix 2026-05-02 11:45:01 -07:00
Jasper St. Pierre 6b327c9f61 disable jpa interp for now
closes #618
2026-05-02 11:44:21 -07:00
Luke Street f148e0ebc1 UI: Rework Document close handling again 2026-05-02 12:39:07 -06:00
Jasper St. Pierre 6f20f4d629 compile fix 2026-05-02 10:54:45 -07:00
Irastris 94670270d6 Fix prelaunch menu transition post-startup 2026-05-02 13:52:20 -04:00
Luke Street 8e21247a97 UI: Prelaunch fade + BG persistence 2026-05-02 11:32:59 -06:00
Luke Street aa84004cb4 Merge branch 'rmlui-integration' 2026-05-02 10:50:18 -06:00
Luke Street e1c201f4bd Make prelaunch sticky & kill ImGui prelaunch 2026-05-02 10:44:59 -06:00
MelonSpeedruns 425a1d15a0 Merge remote-tracking branch 'origin/main' into feature/autosave 2026-05-02 10:56:36 -04:00
MelonSpeedruns e3ad41792a fix respawn stageinfo when autosaving 2026-05-02 10:42:20 -04:00
MelonSpeedruns c1ba10fc8b fix mistake 2026-05-02 10:29:23 -04:00
TakaRikka 1c5686f71b Merge pull request #616 from TwilitRealm/HRTF
Emulate Surround option
2026-05-01 22:51:25 -07:00
TakaRikka b9e0f2b9ca Merge pull request #611 from TwilitRealm/26-05-01-heap-crash-log
Improve heap crash logging
2026-05-01 22:19:33 -07:00
TakaRikka ede175f141 Merge pull request #600 from TwilitRealm/fix/fix-bow-aiming
Fix bow aiming in first person
2026-05-01 22:18:14 -07:00
TakaRikka a301874e30 Merge pull request #592 from TwilitRealm/fix/free-cam-fix
Freecam Fixes
2026-05-01 22:17:21 -07:00
Irastris 9b4a9a6bd9 Initial attempt at the prelaunch document 2026-05-02 00:10:11 -04:00
Luke Street 04274b1884 Make RmlUi F1, ImGui Shift+F1 2026-05-01 21:53:48 -06:00
Luke Street ca783b8424 Merge branch 'main' into rmlui-integration
# Conflicts:
#	extern/aurora
2026-05-01 21:24:24 -06:00
Luke Street 2e4c2b5b46 UI: Add controller input 2026-05-01 21:24:12 -06:00
Luke Street 4c09d8b910 Fix restoring scroll position on pop 2026-05-01 17:40:38 -06:00
Luke Street fb1b260d09 Add Input tab to settings 2026-05-01 16:52:08 -06:00
Luke Street 98eb8f718e Add Bloom settings 2026-05-01 16:32:04 -06:00
Luke Street 68b2e0ee2d Rework Settings components 2026-05-01 16:14:20 -06:00
Luke Street 81771a0522 Editor Collection, Minigame, Config tabs 2026-05-01 14:12:12 -06:00
madeline 93c8bcc210 hrtf 2026-05-01 13:04:08 -07:00
Luke Street 176bf5f0c4 Editor inventory tab 2026-05-01 13:11:43 -06:00
Luke Street 65a5945778 Location tab in editor 2026-05-01 12:37:40 -06:00
Luke Street 8f7b9cdfdd Dual pane navigation & more player editor 2026-05-01 12:06:05 -06:00
MelonSpeedruns acecba7ff7 Reset camera zoom to 0 if freecam is enabled mid-game 2026-05-01 12:54:40 -04:00
MelonSpeedruns 507e0aadbc Merge remote-tracking branch 'origin/main' into fix/free-cam-fix 2026-05-01 12:54:12 -04:00
Jasper St. Pierre 8406d9b192 Merge pull request #613 from TwilitRealm/widescreen/wide-hidden-village
Wide Bulblin count in Hidden Village
2026-05-01 09:04:23 -07:00
Jasper St. Pierre 6f3170cb56 Merge pull request #604 from TwilitRealm/widescreen/dmap-menu
Widescreen Dungeon Map
2026-05-01 09:04:08 -07:00
Jasper St. Pierre 835582224e Merge pull request #601 from TwilitRealm/fix/fmap-widescreen
Widescreen Field Map
2026-05-01 09:04:00 -07:00
MelonSpeedruns 52879f50f0 Wide Bulblin count in Hidden Village 2026-05-01 11:47:18 -04:00
Irastris 2b505f1be4 Revise floating scrollbar margin 2026-05-01 11:19:51 -04:00
Irastris f089f9024d Centralize tab styling and other tidying 2026-05-01 11:13:14 -04:00
roeming 43fb421a93 Merge pull request #608 from TwilitRealm/location-flags-ui
Area flags UI
2026-05-01 07:26:23 -04:00
PJB3005 f06d6b50a9 Fix Plumm game score reset
Fixes #609
2026-05-01 09:27:45 +02:00
Jasper St. Pierre 2c987b0211 build fix 2026-05-01 00:17:51 -07:00
Jasper St. Pierre 3d860ad454 jpa interp start 2026-05-01 00:12:26 -07:00
Luke Street b5bf19569b Button: Selected but not active style 2026-05-01 01:01:38 -06:00
PJB3005 5a7f5cb4a7 Increase gameHeap size by an order of magnitude
Why not?
2026-05-01 08:57:37 +02:00
Luke Street 1affefbbfc Pane: Focus selected child first 2026-05-01 00:56:53 -06:00
PJB3005 e4ff38a712 Disable spammy mDoGph_Painter log line
What was this even debugging? Who knows.
2026-05-01 08:54:32 +02:00
PJB3005 f2ac4d6f44 Leave OSReport enabled. 2026-05-01 08:52:41 +02:00
PJB3005 48b98a5432 Make JKRExpHeap OOM logging more verbose 2026-05-01 08:50:55 +02:00
PJB3005 a4752154f7 Nicely plumb OSReport error/warning/etc through Aurora's logger
This makes a new implementation in the dusk src folder, and makes the existing file containing these functions not compiled anymore.

A ton of dead code is now gone.
2026-05-01 08:50:42 +02:00
Luke Street 0d973a497b Editor: Add get_item_name 2026-05-01 00:40:57 -06:00
Luke Street ab4eccf1df Some more dual pane select work 2026-05-01 00:26:04 -06:00
Luke Street e0c449f28e Improve add_child and Button APIs 2026-04-30 23:36:30 -06:00
Jasper St. Pierre ce9a5c06d5 heap name 2026-04-30 22:19:19 -07:00
Jasper St. Pierre 2bbba1e4e8 couple small fixes 2026-04-30 21:37:28 -07:00
Luke Street 39298f8d8f Preserve focused element on pop 2026-04-30 22:31:24 -06:00
Luke Street 32b4c0567a Animate overlay open/close 2026-04-30 22:17:48 -06:00
Jasper St. Pierre 7ff1b5332e Merge pull request #606 from dooplecks/trim-correction
Correct trim height for arbitrary aspect ratios
2026-04-30 20:38:03 -07:00
Jasper St. Pierre 5e77a60bd6 Merge pull request #610 from dooplecks/shadow-interp
Interpolate shadow camera
2026-04-30 20:36:53 -07:00
doop 9629c000bd Interpolate shadow camera
Fixes #454.
2026-05-01 03:25:07 +00:00
Luke Street 9dc5fed686 Fix overlay CSS 2026-04-30 21:12:25 -06:00
Luke Street 2cc9db77dd Fix overlay 2026-04-30 21:10:48 -06:00
Luke Street 8aa08c9443 Window animations & tags instead of classes 2026-04-30 20:55:21 -06:00
Irastris dca3e2eba6 Overlay: I forgot about pop_document()
This in turn fixes a bug that I was about to bother encounter about, smh
2026-04-30 21:54:52 -04:00
Irastris 57061fba38 Initial attempt at the overlay document 2026-04-30 21:39:39 -04:00
Luke Street cee6a24309 UI: Defer document destruction 2026-04-30 17:46:36 -06:00
Luke Street 62edb3abc6 UI: Implement initial document stack logic 2026-04-30 17:21:55 -06:00
Luke Street 1c9e1c0a66 Minor clean 2026-04-30 16:15:27 -06:00
Luke Street a06aeb10c1 Extract TabBar component 2026-04-30 16:13:08 -06:00
roeming b84b309e00 compilation fix attempt 3 2026-04-30 17:51:52 -04:00
roeming 62eecb3ccd compilation fix attempt 2 2026-04-30 17:48:39 -04:00
roeming ec07572ced compilation fix attempt 2026-04-30 17:45:53 -04:00
roeming b45e2fa34d swap array for named variables 2026-04-30 17:25:24 -04:00
roeming 289a718446 fix comment wording 2026-04-30 17:10:33 -04:00
roeming 292a2a6c34 fix wrong formatting, fix duplicate ui object, fix wrong flags getting set 2026-04-30 17:03:47 -04:00
Luke Street 2e8415b950 Update aurora 2026-04-30 14:43:44 -06:00
roeming dbf1f6e354 implement multibyte area flags in UI 2026-04-30 16:25:56 -04:00
roeming 9a2fe9745d Draw area flags as table 2026-04-30 15:55:59 -04:00
Irastris b5ca343fac Fix apostrophes in strings 2026-04-30 15:50:01 -04:00
doop 88c7ff63ff Fix preprocessor conditional 2026-04-30 19:48:03 +00:00
Luke Street 7a438ad30f UI: Extract a Document class 2026-04-30 13:44:33 -06:00
doop 89649de7c3 Correct trim height for arbitrary aspect ratios
Fixes #543.
2026-04-30 19:39:36 +00:00
Irastris a1960eaa33 Initial attempt at the popup menu 2026-04-30 15:30:14 -04:00
MelonSpeedruns d6820c9233 fix toast call 2026-04-30 14:53:15 -04:00
MelonSpeedruns 5c4bb8d33d Merge remote-tracking branch 'origin/main' into feature/autosave
# Conflicts:
#	src/dusk/imgui/ImGuiConsole.cpp
2026-04-30 14:51:17 -04:00
MelonSpeedruns e3ce1f01c9 Widescreen Dungeon Map 2026-04-30 14:38:24 -04:00
Luke Street 9b6b344ecf Revert "mirror clip fix"
This reverts commit 1ac6df8de7.
2026-04-30 12:09:18 -06:00
Luke Street 3db85d5b44 Merge branch 'main' into rmlui-integration
# Conflicts:
#	extern/aurora
2026-04-30 12:04:21 -06:00
roeming b5871d72d9 fix build 2026-04-30 11:47:22 -04:00
roeming b70a714f88 Remove missed randomizer flag 2026-04-30 11:45:16 -04:00
roeming cad5a8d1bc Add multibyte flags to Area iterators 2026-04-30 11:43:57 -04:00
roeming 93f8a5fa8f Move area multi flag bits to combined flags, at most 2 2026-04-30 11:11:08 -04:00
roeming b0809ea78c Add area flags 2026-04-30 11:02:18 -04:00
MelonSpeedruns b0e9033736 Widescreen Field Map 2026-04-30 10:55:53 -04:00
MelonSpeedruns ce0d89058a don't run hyper enemies while an event is running 2026-04-30 10:55:28 -04:00
Jasper St. Pierre 1ac6df8de7 mirror clip fix
closes #581
2026-04-30 07:52:44 -07:00
MelonSpeedruns 5899b2157a fix bow aiming in first person 2026-04-30 09:29:42 -04:00
Luke Street 3185f578fb Update aurora 2026-04-30 01:45:54 -06:00
Luke Street 4fc09799b6 UI: Fix section heading font 2026-04-30 01:45:25 -06:00
Irastris fe0e3cad72 Remove unused editor functions 2026-04-30 03:27:48 -04:00
Luke Street 37d1aa7f40 UI: Run builder before moving it 2026-04-30 01:07:11 -06:00
Luke Street 4a12554bf4 UI: Mobile keyboard fixes, safe area padding, & more 2026-04-30 00:06:27 -06:00
Phillip Stephens fecd1d5683 Update aurora 2026-04-29 22:33:02 -07:00
doop bce9bf6fd9 Merge pull request #597 from dooplecks/haze-fix
Correct JPADrawInfo proj matrix on widescreen
2026-04-30 00:55:32 -04:00
doop fbf63b075a Correct JPADrawInfo proj matrix on widescreen
Fixes #337.
2026-04-30 04:28:47 +00:00
Luke Street b86d6e90e2 Add Gameplay settings & make Panes scrollable 2026-04-29 21:52:33 -06:00
Luke Street 6425b452e7 Make PgUp/PgDown change tabs 2026-04-29 20:25:59 -06:00
Luke Street 1657fe8083 Split out string/number components 2026-04-29 20:19:27 -06:00
Luke Street ecd74a4cbd More settings/editor components 2026-04-29 19:54:37 -06:00
MelonSpeedruns 36dc43c602 Fix changing tunics crash while on top of mirror (#596)
* Fix changing tunics while reflection is active

* Revert "Fix changing tunics while reflection is active"

This reverts commit 89927dc7a6.

* Really fix changing tunics while reflection is active

* Fix transforming on ice again

---------

Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
Co-authored-by: Irastris <irastris15@gmail.com>
2026-04-29 18:13:07 -06:00
Luke Street d92515f0d4 Begin scaffolding keyboard nav 2026-04-29 15:19:15 -06:00
MelonSpeedruns f147dcac0c Fix camera while crawling & disable freecam properly when not chasecam 2026-04-29 15:29:00 -04:00
MelonSpeedruns ee4c84f39b Fix slight spazz when changing cam type 2026-04-29 15:21:48 -04:00
MelonSpeedruns b8a83c6f59 disable freecam if not chasecamera 2026-04-29 14:37:32 -04:00
MelonSpeedruns 4462c0ef69 more optimizations to freecam code 2026-04-29 14:28:18 -04:00
Luke Street 3cb7fbd030 Create new component system & initial settings window 2026-04-29 00:38:26 -06:00
Irastris 1e372a856d Attempted to start making the save editor functional 2026-04-28 22:54:47 -04:00
Irastris b48d9aa052 Split window document and styles out to files for readability 2026-04-28 19:09:47 -04:00
Luke Street d899706208 Start UI over from scratch and add demo window 2026-04-28 16:20:45 -06:00
MelonSpeedruns 9a7b62cbc6 disable freecam if an event cam happens 2026-04-28 16:25:42 -04:00
MelonSpeedruns 8e0f0e878e optimize some code 2026-04-28 16:13:49 -04:00
MelonSpeedruns 79344edf0d maybe fix number 2 2026-04-28 15:19:32 -04:00
MelonSpeedruns e59bfd1a9c potential fix for freecam flashing 2026-04-28 15:01:57 -04:00
Luke Street f7b880c5ea Small tweak to rml_string 2026-04-28 00:27:18 -06:00
Luke Street ff78bc8d6c Start using Rml::PropertyId/Property instead of strings 2026-04-28 00:18:52 -06:00
Luke Street 6503b4e7eb Add blur and shadow to window component 2026-04-27 23:44:58 -06:00
Irastris 8fb4ba8924 Remove "Enable" from various options, and move DoF under post-processing 2026-04-27 22:51:56 -04:00
Irastris 5f675c6f2b Start deprecating ImGui 2026-04-27 22:44:26 -04:00
Irastris b3333241c5 Add right-pane for item descriptions 2026-04-27 22:44:26 -04:00
Irastris e39079c0f8 Initial game menu implementation 2026-04-27 22:44:26 -04:00
Irastris c3317d9232 Initial window class programming 2026-04-27 22:43:04 -04:00
MelonSpeedruns 36092f1fdb added setting 2026-04-27 18:29:14 -04:00
MelonSpeedruns 5eb3184174 Hyper Enemies (2x) 2026-04-27 16:55:43 -04:00
Luke Street 025cb58493 Forgor to commit 2026-04-27 01:15:19 -06:00
Luke Street b3dee825e8 Improve button/option style 2026-04-27 00:44:01 -06:00
Luke Street f6c5aac3c8 Improve disc selector 2026-04-27 00:36:56 -06:00
Luke Street 25e9064d09 Revamped prelaunch experiment w/ RmlUi 2026-04-27 00:18:31 -06:00
Luke Street 3e1e8f1244 Enable RmlUi 2026-04-26 21:53:39 -06:00
roeming 0bf663141a change filter to avoid allocations 2026-04-25 14:39:47 -04:00
MelonSpeedruns d7dced7ddf don't autosave if playing a cutscene 2026-04-25 08:25:50 -04:00
MelonSpeedruns 78b0563c0e Merge remote-tracking branch 'origin/main' into feature/autosave
# Conflicts:
#	src/dusk/imgui/ImGuiMenuEnhancements.cpp
#	src/dusk/settings.cpp
#	src/f_ap/f_ap_game.cpp
2026-04-24 12:54:40 -04:00
MelonSpeedruns 871d18e294 added experimental setting for autosave 2026-04-20 13:15:37 -04:00
MelonSpeedruns c157564da6 dungeon doors now autosave 2026-04-20 13:02:25 -04:00
MelonSpeedruns ecc3b00c51 Merge remote-tracking branch 'origin/main' into feature/autosave
# Conflicts:
#	src/f_ap/f_ap_game.cpp
2026-04-20 12:55:17 -04:00
MelonSpeedruns 8afb1141ab Autosave when changing rooms 2026-04-17 13:41:56 -04:00
MelonSpeedruns 8c5673d9b8 autosave when loading scene 2026-04-17 13:12:20 -04:00
MelonSpeedruns 916dfcd9da Merge remote-tracking branch 'origin/main' into feature/autosave
# Conflicts:
#	src/dusk/imgui/ImGuiConsole.cpp
2026-04-17 12:10:24 -04:00
MelonSpeedruns 842210e539 remove scene autosave cause that's buggy atm 2026-04-16 19:56:04 -04:00
MelonSpeedruns 39d951d0cb scene saving now works 2026-04-16 19:50:20 -04:00
MelonSpeedruns a4be0841e5 autosave WIP 2026-04-16 19:30:37 -04:00
111 changed files with 10934 additions and 810 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
Language: Cpp Language: Cpp
Standard: C++03 Standard: C++03
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignAfterOpenBracket: Align AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false AlignConsecutiveDeclarations: false
AlignOperands: true AlignOperands: true
+1
View File
@@ -100,6 +100,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux)
endif () endif ()
set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE) set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
add_subdirectory(libs/freeverb) add_subdirectory(libs/freeverb)
+1 -1
View File
@@ -1,4 +1,4 @@
![DuskLogo](res/logo-mascot.webp) ![DuskLogo](res/logo-mascot.png)
- ### **[Official Website](https://twilitrealm.dev)** - ### **[Official Website](https://twilitrealm.dev)**
- ### **[Discord](https://discord.gg/QACynxeyna)** - ### **[Discord](https://discord.gg/QACynxeyna)**
+1 -1
+42 -1
View File
@@ -1,7 +1,7 @@
set(DOLZEL_FILES set(DOLZEL_FILES
src/m_Do/m_Do_main.cpp src/m_Do/m_Do_main.cpp
src/m_Do/m_Do_printf.cpp #src/m_Do/m_Do_printf.cpp
src/m_Do/m_Do_audio.cpp src/m_Do/m_Do_audio.cpp
src/m_Do/m_Do_controller_pad.cpp src/m_Do/m_Do_controller_pad.cpp
#src/m_Do/m_Re_controller_pad.cpp #src/m_Do/m_Re_controller_pad.cpp
@@ -1429,6 +1429,7 @@ set(DUSK_FILES
src/dusk/globals.cpp src/dusk/globals.cpp
src/dusk/gyro.cpp src/dusk/gyro.cpp
src/dusk/gamepad_color.cpp src/dusk/gamepad_color.cpp
src/dusk/autosave.cpp
src/dusk/io.cpp src/dusk/io.cpp
src/dusk/layout.cpp src/dusk/layout.cpp
src/dusk/logging.cpp src/dusk/logging.cpp
@@ -1462,11 +1463,51 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiStateShare.cpp src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/imgui/ImGuiAchievements.hpp src/dusk/imgui/ImGuiAchievements.hpp
src/dusk/imgui/ImGuiAchievements.cpp src/dusk/imgui/ImGuiAchievements.cpp
src/dusk/ui/bool_button.cpp
src/dusk/ui/bool_button.hpp
src/dusk/ui/button.cpp
src/dusk/ui/button.hpp
src/dusk/ui/component.cpp
src/dusk/ui/component.hpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
src/dusk/ui/editor.cpp
src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp
src/dusk/ui/event.hpp
src/dusk/ui/input.cpp
src/dusk/ui/input.hpp
src/dusk/ui/nav_types.hpp
src/dusk/ui/number_button.cpp
src/dusk/ui/number_button.hpp
src/dusk/ui/overlay.cpp
src/dusk/ui/overlay.hpp
src/dusk/ui/pane.cpp
src/dusk/ui/pane.hpp
src/dusk/ui/popup.cpp
src/dusk/ui/popup.hpp
src/dusk/ui/prelaunch.cpp
src/dusk/ui/prelaunch.hpp
src/dusk/ui/prelaunch_options.cpp
src/dusk/ui/prelaunch_options.hpp
src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp
src/dusk/ui/settings.hpp
src/dusk/ui/string_button.cpp
src/dusk/ui/string_button.hpp
src/dusk/ui/tab_bar.cpp
src/dusk/ui/tab_bar.hpp
src/dusk/ui/ui.cpp
src/dusk/ui/ui.hpp
src/dusk/ui/window.cpp
src/dusk/ui/window.hpp
src/dusk/achievements.cpp src/dusk/achievements.cpp
src/dusk/iso_validate.cpp src/dusk/iso_validate.cpp
src/dusk/livesplit.cpp src/dusk/livesplit.cpp
src/dusk/offset_ptr.cpp src/dusk/offset_ptr.cpp
src/dusk/OSContext.cpp src/dusk/OSContext.cpp
src/dusk/OSReport.cpp
src/dusk/OSThread.cpp src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp src/dusk/OSMutex.cpp
src/dusk/discord_presence.cpp src/dusk/discord_presence.cpp
+1
View File
@@ -27,6 +27,7 @@ public:
/* 0x17C */ cXyz mViewScale; /* 0x17C */ cXyz mViewScale;
#if TARGET_PC #if TARGET_PC
bool mbReset = false; bool mbReset = false;
bool mbHadEntry = false;
#endif #endif
}; };
+4
View File
@@ -91,6 +91,10 @@ public:
void calcCursor(); void calcCursor();
void drawCursor(); void drawCursor();
#if TARGET_PC
void dMapBgWide();
#endif
void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; } void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; }
f32 getMapWidth() { return mMapWidth; } f32 getMapWidth() { return mMapWidth; }
+8
View File
@@ -81,6 +81,10 @@ public:
void calcDrawPriority(); void calcDrawPriority();
void setArrowPosAxis(f32, f32); void setArrowPosAxis(f32, f32);
#if TARGET_PC
void fMapBackWide();
#endif
virtual void draw(); virtual void draw();
virtual ~dMenu_Fmap2DBack_c(); virtual ~dMenu_Fmap2DBack_c();
@@ -330,6 +334,10 @@ public:
void setHIO(bool); void setHIO(bool);
bool isWarpAccept(); bool isWarpAccept();
#if TARGET_PC
void fMapTopWide();
#endif
virtual void draw(); virtual void draw();
virtual ~dMenu_Fmap2DTop_c(); virtual ~dMenu_Fmap2DTop_c();
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#ifndef AUTOSAVE_H
#define AUTOSAVE_H
#include <m_Do/m_Do_MemCardRWmng.h>
#include <m_Do/m_Do_MemCard.h>
void noAutoSave();
void triggerAutoSave();
void updateAutoSave();
void enterAutoSave();
void autoSaving();
void waitingForWrite();
void endAutoSave();
#endif
+3
View File
@@ -55,6 +55,7 @@ struct UserSettings {
ConfigVar<int> soundEffectsVolume; ConfigVar<int> soundEffectsVolume;
ConfigVar<int> fanfareVolume; ConfigVar<int> fanfareVolume;
ConfigVar<bool> enableReverb; ConfigVar<bool> enableReverb;
ConfigVar<bool> enableHrtf;
} audio; } audio;
// Game settings // Game settings
@@ -71,6 +72,7 @@ struct UserSettings {
ConfigVar<bool> disableRupeeCutscenes; ConfigVar<bool> disableRupeeCutscenes;
ConfigVar<bool> noSwordRecoil; ConfigVar<bool> noSwordRecoil;
ConfigVar<int> damageMultiplier; ConfigVar<int> damageMultiplier;
ConfigVar<bool> hyperEnemies;
ConfigVar<bool> noHeartDrops; ConfigVar<bool> noHeartDrops;
ConfigVar<bool> instantDeath; ConfigVar<bool> instantDeath;
ConfigVar<bool> fastClimbing; ConfigVar<bool> fastClimbing;
@@ -80,6 +82,7 @@ struct UserSettings {
ConfigVar<bool> instantSaves; ConfigVar<bool> instantSaves;
ConfigVar<bool> instantText; ConfigVar<bool> instantText;
ConfigVar<bool> sunsSong; ConfigVar<bool> sunsSong;
ConfigVar<bool> autoSave;
// Preferences // Preferences
ConfigVar<bool> enableMirrorMode; ConfigVar<bool> enableMirrorMode;
@@ -59,6 +59,9 @@ public:
bool isActive() const { return mSeqList.getNumLinks() != 0; } bool isActive() const { return mSeqList.getNumLinks() != 0; }
int getNumActiveSeqs() const { return mSeqList.getNumLinks(); } int getNumActiveSeqs() const { return mSeqList.getNumLinks(); }
void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; } void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; }
#if TARGET_PC
JSUList<JAISeq>* getSeqList() { return &mSeqList; }
#endif
private: private:
/* 0x08 */ JAIAudience* mAudience; /* 0x08 */ JAIAudience* mAudience;
@@ -207,4 +207,11 @@ void JPARegistAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*); void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*); void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
#if TARGET_PC
void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPAInterpRotBillboard(JPAEmitterWorkData*, JPABaseParticle*);
void JPAInterpDirection(JPAEmitterWorkData*, JPABaseParticle*);
void JPAInterpRotDirection(JPAEmitterWorkData*, JPABaseParticle*);
#endif
#endif /* JPABASESHAPE_H */ #endif /* JPABASESHAPE_H */
@@ -24,6 +24,9 @@ public:
void init_c(JPAEmitterWorkData*, JPABaseParticle*); void init_c(JPAEmitterWorkData*, JPABaseParticle*);
bool calc_p(JPAEmitterWorkData*); bool calc_p(JPAEmitterWorkData*);
bool calc_c(JPAEmitterWorkData*); bool calc_c(JPAEmitterWorkData*);
#if TARGET_PC
void interp(JPAEmitterWorkData*, void const* drawFunc);
#endif
bool canCreateChild(JPAEmitterWorkData*); bool canCreateChild(JPAEmitterWorkData*);
f32 getWidth(JPABaseEmitter const*) const; f32 getWidth(JPABaseEmitter const*) const;
f32 getHeight(JPABaseEmitter const*) const; f32 getHeight(JPABaseEmitter const*) const;
@@ -40,6 +40,9 @@ public:
JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); } JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); }
u16 getNumColors() const { return mNumColors; } u16 getNumColors() const { return mNumColors; }
ResTLUT* getColorTable() const { return mColorTable; } ResTLUT* getColorTable() const { return mColorTable; }
#if TARGET_PC
void dataUploaded();
#endif
private: private:
/* 0x00 */ GXTlutObj mTlutObj; /* 0x00 */ GXTlutObj mTlutObj;
@@ -75,6 +75,7 @@ public:
s32 getTransparency() const { return mTexInfo->alphaEnabled; } s32 getTransparency() const { return mTexInfo->alphaEnabled; }
s32 getWidth() const { return mTexInfo->width; } s32 getWidth() const { return mTexInfo->width; }
s32 getHeight() const { return mTexInfo->height; } s32 getHeight() const { return mTexInfo->height; }
JUTPalette* getPalette() const { return mPalette; }
void setCaptureFlag(bool flag) { mFlags &= 2 | flag; } void setCaptureFlag(bool flag) { mFlags &= 2 | flag; }
bool getCaptureFlag() const { return mFlags & 1; } bool getCaptureFlag() const { return mFlags & 1; }
bool getEmbPaletteDelFlag() const { return mFlags & 2; } bool getEmbPaletteDelFlag() const { return mFlags & 2; }
@@ -82,7 +83,7 @@ public:
int getTlutName() const { return mTlutName; } int getTlutName() const { return mTlutName; }
bool operator==(const JUTTexture& other) { bool operator==(const JUTTexture& other) {
return mTexInfo == other.mTexInfo return mTexInfo == other.mTexInfo
&& field_0x2c == other.field_0x2c && mPalette == other.mPalette
&& mWrapS == other.mWrapS && mWrapS == other.mWrapS
&& mWrapT == other.mWrapT && mWrapT == other.mWrapT
&& mMinFilter == other.mMinFilter && mMinFilter == other.mMinFilter
@@ -100,7 +101,7 @@ private:
/* 0x20 */ const ResTIMG* mTexInfo; /* 0x20 */ const ResTIMG* mTexInfo;
/* 0x24 */ void* mTexData; /* 0x24 */ void* mTexData;
/* 0x28 */ JUTPalette* mEmbPalette; /* 0x28 */ JUTPalette* mEmbPalette;
/* 0x2C */ JUTPalette* field_0x2c; /* 0x2C */ JUTPalette* mPalette;
/* 0x30 */ u8 mWrapS; /* 0x30 */ u8 mWrapS;
/* 0x31 */ u8 mWrapT; /* 0x31 */ u8 mWrapT;
/* 0x32 */ u8 mMinFilter; /* 0x32 */ u8 mMinFilter;
+9 -1
View File
@@ -1,6 +1,9 @@
#include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JSystem.h" // IWYU pragma: keep
#include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASChannel.h"
#if TARGET_PC
#include "dusk/audio/DuskDsp.hpp"
#endif
#include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASAiCtrl.h"
#include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASCalc.h"
#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASDriverIF.h"
@@ -170,7 +173,12 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo
f32 pan = 0.5f; f32 pan = 0.5f;
f32 dolby = 0.0f; f32 dolby = 0.0f;
switch (JASDriver::getOutputMode()) { #if TARGET_PC
u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode();
#else
u32 effectiveOutputMode = JASDriver::getOutputMode();
#endif
switch (effectiveOutputMode) {
case JAS_OUTPUT_MONO: case JAS_OUTPUT_MONO:
break; break;
case JAS_OUTPUT_STEREO: case JAS_OUTPUT_STEREO:
-1
View File
@@ -302,7 +302,6 @@ void JASKernel::setupRootHeap(JKRSolidHeap* heap, u32 size) {
JKRHEAP_NAME(sSystemHeap, "JASKernel::sSystemHeap"); JKRHEAP_NAME(sSystemHeap, "JASKernel::sSystemHeap");
JUT_ASSERT(787, sSystemHeap); JUT_ASSERT(787, sSystemHeap);
sCommandHeap = JKR_NEW_ARGS (heap, 0) JASMemChunkPool<1024, JASThreadingModel::ObjectLevelLockable>; sCommandHeap = JKR_NEW_ARGS (heap, 0) JASMemChunkPool<1024, JASThreadingModel::ObjectLevelLockable>;
JKRHEAP_NAME(sSystemHeap, "JASKernel::sCommandHeap");
JUT_ASSERT(790, sCommandHeap); JUT_ASSERT(790, sCommandHeap);
JASDram = heap; JASDram = heap;
} }
@@ -442,6 +442,7 @@ static JAUSectionHeap* JAUNewSectionHeap(JKRSolidHeap* heap, bool param_1) {
JAUSectionHeap* JAUNewSectionHeap(bool param_0) { JAUSectionHeap* JAUNewSectionHeap(bool param_0) {
s32 freeSize = JASDram->getFreeSize(); s32 freeSize = JASDram->getFreeSize();
JKRSolidHeap* sectionHeap = JKRCreateSolidHeap(freeSize, JASDram, true); JKRSolidHeap* sectionHeap = JKRCreateSolidHeap(freeSize, JASDram, true);
JKRHEAP_NAME(sectionHeap, "sectionHeap");
JUT_ASSERT(821, sectionHeap); JUT_ASSERT(821, sectionHeap);
return JAUNewSectionHeap(sectionHeap, param_0); return JAUNewSectionHeap(sectionHeap, param_0);
} }
+8 -5
View File
@@ -222,16 +222,11 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) {
OSReport_Error("Free block list as follows:\n"); OSReport_Error("Free block list as follows:\n");
OSReport_Error("Start | End | Size \n"); OSReport_Error("Start | End | Size \n");
int i = 0;
for (const CMemBlock* block = mHeadFreeList; block; block = block->mNext) { for (const CMemBlock* block = mHeadFreeList; block; block = block->mNext) {
if (block->mMagic) { if (block->mMagic) {
// Allocated, ignore. // Allocated, ignore.
continue; continue;
} }
if (i++ > 10) {
OSReport_Error("<more>\n");
break;
}
auto blockStart = (uintptr_t)block - (uintptr_t)mStart; auto blockStart = (uintptr_t)block - (uintptr_t)mStart;
auto blockEnd = (uintptr_t)block + block->size - (uintptr_t)mStart; auto blockEnd = (uintptr_t)block + block->size - (uintptr_t)mStart;
@@ -239,6 +234,14 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) {
OSReport_Error("%08X | %08X | %08X\n", (u32) blockStart, (u32) blockEnd, (u32) blockSize); OSReport_Error("%08X | %08X | %08X\n", (u32) blockStart, (u32) blockEnd, (u32) blockSize);
} }
OSReport_Error("Child heaps as follows:\n");
OSReport_Error("Start | End | Name \n");
const JSUTree<JKRHeap>& tree = getHeapTree();
for (JSUTreeIterator iter(tree.getFirstChild()); iter != tree.getEndChild(); ++iter) {
OSReport_Error("%08X | %08X | %s\n", iter->getStartAddr(), iter->getEndAddr(), iter->getName());
}
CRASH("Aborting due to allocation failure!"); CRASH("Aborting due to allocation failure!");
} }
#else #else
+255 -113
View File
@@ -9,6 +9,9 @@
#include <mtx.h> #include <mtx.h>
#include <gx.h> #include <gx.h>
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#endif
#include "tracy/Tracy.hpp" #include "tracy/Tracy.hpp"
void JPASetPointSize(JPAEmitterWorkData* work) { void JPASetPointSize(JPAEmitterWorkData* work) {
@@ -418,50 +421,95 @@ static projectionFunc p_prj[3] = {
loadPrjAnm, loadPrjAnm,
}; };
void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { #if TARGET_PC
if (param_1->checkStatus(JPAPtclStts_Invisible)) { void JPAInterpBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
Mtx ptclPosMtx;
MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl);
}
void JPAInterpRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
Mtx ptclPosMtx;
f32 sinRot = JMASSin(ptcl->mRotateAngle);
f32 cosRot = JMASCos(ptcl->mRotateAngle);
MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
ptclPosMtx[0][0] = cosRot;
ptclPosMtx[0][1] = -sinRot;
ptclPosMtx[1][0] = sinRot;
ptclPosMtx[1][1] = cosRot;
dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl);
}
#endif
void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return; return;
} }
JGeometry::TVec3<f32> local_48; JGeometry::TVec3<f32> pos;
MTXMultVec(work->mPosCamMtx, &param_1->mPosition, &local_48); #if TARGET_PC
Mtx local_38; Mtx ptclPosMtx;
local_38[0][0] = work->mGlobalPtclScl.x * param_1->mParticleScaleX; if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) {
local_38[0][3] = local_48.x; pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]);
local_38[1][1] = work->mGlobalPtclScl.y * param_1->mParticleScaleY; MTXMultVec(work->mPosCamMtx, &pos, &pos);
local_38[1][3] = local_48.y; } else
local_38[2][2] = 1.0f; #endif
local_38[2][3] = local_48.z; {
local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f; MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos);
GXLoadPosMtxImm(local_38, 0); }
p_prj[work->mPrjType](work, local_38); Mtx posMtx;
posMtx[0][0] = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
posMtx[0][3] = pos.x;
posMtx[1][1] = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
posMtx[1][3] = pos.y;
posMtx[2][2] = 1.0f;
posMtx[2][3] = pos.z;
posMtx[0][1] = posMtx[0][2] = posMtx[1][0] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
} }
void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) { if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return; return;
} }
JGeometry::TVec3<f32> local_48; if (work->mpRes->getUsrIdx() == 0x89d7) {
MTXMultVec(work->mPosCamMtx, &param_1->mPosition, &local_48); int a = 0;
f32 sinRot = JMASSin(param_1->mRotateAngle); }
f32 cosRot = JMASCos(param_1->mRotateAngle);
f32 particleX = work->mGlobalPtclScl.x * param_1->mParticleScaleX;
f32 particleY = work->mGlobalPtclScl.y * param_1->mParticleScaleY;
Mtx local_38; JGeometry::TVec3<f32> pos;
local_38[0][0] = cosRot * particleX; f32 sinRot, cosRot;
local_38[0][1] = -sinRot * particleY; #if TARGET_PC
local_38[0][3] = local_48.x; Mtx ptclPosMtx;
local_38[1][0] = sinRot * particleX; MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
local_38[1][1] = cosRot * particleY; if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) {
local_38[1][3] = local_48.y; pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]);
local_38[2][2] = 1.0f; sinRot = ptclPosMtx[1][0];
local_38[2][3] = local_48.z; cosRot = ptclPosMtx[0][0];
local_38[0][2] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f; MTXMultVec(work->mPosCamMtx, &pos, &pos);
GXLoadPosMtxImm(local_38, 0); } else
p_prj[work->mPrjType](work, local_38); #endif
{
MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos);
sinRot = JMASSin(ptcl->mRotateAngle);
cosRot = JMASCos(ptcl->mRotateAngle);
}
f32 particleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 particleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
Mtx posMtx;
posMtx[0][0] = cosRot * particleX;
posMtx[0][1] = -sinRot * particleY;
posMtx[0][3] = pos.x;
posMtx[1][0] = sinRot * particleX;
posMtx[1][1] = cosRot * particleY;
posMtx[1][3] = pos.y;
posMtx[2][2] = 1.0f;
posMtx[2][3] = pos.z;
posMtx[0][2] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
} }
@@ -484,7 +532,7 @@ void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][2] = work->mYBBCamMtx[2][2]; local_38[2][2] = work->mYBBCamMtx[2][2];
local_38[2][3] = local_48.z; local_38[2][3] = local_48.z;
local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[2][0] = 0.0f; local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[2][0] = 0.0f;
GXLoadPosMtxImm(local_38, 0); GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38); p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
} }
@@ -517,7 +565,7 @@ void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][1] = local_94 * fVar1; local_38[2][1] = local_94 * fVar1;
local_38[2][2] = local_90; local_38[2][2] = local_90;
local_38[2][3] = local_48.z; local_38[2][3] = local_48.z;
GXLoadPosMtxImm(local_38, 0); GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38); p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
} }
@@ -681,103 +729,197 @@ static u8* p_dl[2] = {
jpa_dl_x, jpa_dl_x,
}; };
void JPADrawDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { #if TARGET_PC
if (param_1->checkStatus(JPAPtclStts_Invisible)) { void JPAInterpDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
p_direction[work->mDirType](work, ptcl, &axisY);
if (axisY.isZero()) {
return; return;
} }
ZoneScoped; axisY.normalize();
axisZ.cross(ptcl->mBaseAxis, axisY);
JGeometry::TVec3<f32> local_6c; if (axisZ.isZero()) {
JGeometry::TVec3<f32> local_78;
p_direction[param_0->mDirType](param_0, param_1, &local_6c);
if (local_6c.isZero()) {
return; return;
} }
local_6c.normalize(); axisZ.normalize();
local_78.cross(param_1->mBaseAxis, local_6c); ptcl->mBaseAxis.cross(axisY, axisZ);
ptcl->mBaseAxis.normalize();
if (local_78.isZero()) { Mtx posMtx;
return; f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
} f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
posMtx[0][0] = ptcl->mBaseAxis.x;
local_78.normalize(); posMtx[0][1] = axisY.x;
param_1->mBaseAxis.cross(local_6c, local_78); posMtx[0][2] = axisZ.x;
param_1->mBaseAxis.normalize(); posMtx[0][3] = ptcl->mPosition.x;
Mtx local_60; posMtx[1][0] = ptcl->mBaseAxis.y;
f32 fVar1 = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX; posMtx[1][1] = axisY.y;
f32 fVar2 = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY; posMtx[1][2] = axisZ.y;
local_60[0][0] = param_1->mBaseAxis.x; posMtx[1][3] = ptcl->mPosition.y;
local_60[0][1] = local_6c.x; posMtx[2][0] = ptcl->mBaseAxis.z;
local_60[0][2] = local_78.x; posMtx[2][1] = axisY.z;
local_60[0][3] = param_1->mPosition.x; posMtx[2][2] = axisZ.z;
local_60[1][0] = param_1->mBaseAxis.y; posMtx[2][3] = ptcl->mPosition.z;
local_60[1][1] = local_6c.y; p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
local_60[1][2] = local_78.y; dusk::frame_interp::record_final_mtx(posMtx, ptcl);
local_60[1][3] = param_1->mPosition.y;
local_60[2][0] = param_1->mBaseAxis.z;
local_60[2][1] = local_6c.z;
local_60[2][2] = local_78.z;
local_60[2][3] = param_1->mPosition.z;
p_plane[param_0->mPlaneType](local_60, fVar1, fVar2);
MTXConcat(param_0->mPosCamMtx, local_60, local_60);
GXLoadPosMtxImm(local_60, 0);
p_prj[param_0->mPrjType](param_0, local_60);
GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl));
} }
void JPADrawRotDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { void JPAInterpRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
if (param_1->checkStatus(JPAPtclStts_Invisible)) { f32 sinRot = JMASSin(ptcl->mRotateAngle);
f32 cosRot = JMASCos(ptcl->mRotateAngle);
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
p_direction[work->mDirType](work, ptcl, &axisY);
if (axisY.isZero()) {
return;
}
axisY.normalize();
axisZ.cross(ptcl->mBaseAxis, axisY);
if (axisZ.isZero()) {
return;
}
axisZ.normalize();
ptcl->mBaseAxis.cross(axisY, axisZ);
ptcl->mBaseAxis.normalize();
f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
Mtx mtx1;
Mtx mtx2;
p_rot[work->mRotType](sinRot, cosRot, mtx1);
p_plane[work->mPlaneType](mtx1, scaleX, scaleY);
mtx2[0][0] = ptcl->mBaseAxis.x;
mtx2[0][1] = axisY.x;
mtx2[0][2] = axisZ.x;
mtx2[0][3] = ptcl->mPosition.x;
mtx2[1][0] = ptcl->mBaseAxis.y;
mtx2[1][1] = axisY.y;
mtx2[1][2] = axisZ.y;
mtx2[1][3] = ptcl->mPosition.y;
mtx2[2][0] = ptcl->mBaseAxis.z;
mtx2[2][1] = axisY.z;
mtx2[2][2] = axisZ.z;
mtx2[2][3] = ptcl->mPosition.z;
MTXConcat(mtx2, mtx1, mtx1);
dusk::frame_interp::record_final_mtx(mtx1, ptcl);
}
#endif
void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return; return;
} }
ZoneScoped; ZoneScoped;
f32 sinRot = JMASSin(param_1->mRotateAngle); Mtx posMtx;
f32 cosRot = JMASCos(param_1->mRotateAngle); #if TARGET_PC
JGeometry::TVec3<f32> local_6c; if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx))
JGeometry::TVec3<f32> local_78; #endif
p_direction[param_0->mDirType](param_0, param_1, &local_6c); {
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
p_direction[work->mDirType](work, ptcl, &axisY);
if (local_6c.isZero()) { if (axisY.isZero()) {
return;
}
axisY.normalize();
axisZ.cross(ptcl->mBaseAxis, axisY);
if (axisZ.isZero()) {
return;
}
axisZ.normalize();
ptcl->mBaseAxis.cross(axisY, axisZ);
ptcl->mBaseAxis.normalize();
f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
posMtx[0][0] = ptcl->mBaseAxis.x;
posMtx[0][1] = axisY.x;
posMtx[0][2] = axisZ.x;
posMtx[0][3] = ptcl->mPosition.x;
posMtx[1][0] = ptcl->mBaseAxis.y;
posMtx[1][1] = axisY.y;
posMtx[1][2] = axisZ.y;
posMtx[1][3] = ptcl->mPosition.y;
posMtx[2][0] = ptcl->mBaseAxis.z;
posMtx[2][1] = axisY.z;
posMtx[2][2] = axisZ.z;
posMtx[2][3] = ptcl->mPosition.z;
p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
}
MTXConcat(work->mPosCamMtx, posMtx, posMtx);
GXLoadPosMtxImm(posMtx, GX_PNMTX0);
p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
}
void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return; return;
} }
local_6c.normalize(); ZoneScoped;
local_78.cross(param_1->mBaseAxis, local_6c);
if (local_78.isZero()) { Mtx mtx1;
return; Mtx mtx2;
#if TARGET_PC
if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1))
#endif
{
f32 sinRot = JMASSin(ptcl->mRotateAngle);
f32 cosRot = JMASCos(ptcl->mRotateAngle);
JGeometry::TVec3<f32> axisY;
JGeometry::TVec3<f32> axisZ;
p_direction[work->mDirType](work, ptcl, &axisY);
if (axisY.isZero()) {
return;
}
axisY.normalize();
axisZ.cross(ptcl->mBaseAxis, axisY);
if (axisZ.isZero()) {
return;
}
axisZ.normalize();
ptcl->mBaseAxis.cross(axisY, axisZ);
ptcl->mBaseAxis.normalize();
f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
p_rot[work->mRotType](sinRot, cosRot, mtx1);
p_plane[work->mPlaneType](mtx1, scaleX, scaleY);
mtx2[0][0] = ptcl->mBaseAxis.x;
mtx2[0][1] = axisY.x;
mtx2[0][2] = axisZ.x;
mtx2[0][3] = ptcl->mPosition.x;
mtx2[1][0] = ptcl->mBaseAxis.y;
mtx2[1][1] = axisY.y;
mtx2[1][2] = axisZ.y;
mtx2[1][3] = ptcl->mPosition.y;
mtx2[2][0] = ptcl->mBaseAxis.z;
mtx2[2][1] = axisY.z;
mtx2[2][2] = axisZ.z;
mtx2[2][3] = ptcl->mPosition.z;
MTXConcat(mtx2, mtx1, mtx1);
} }
MTXConcat(work->mPosCamMtx, mtx1, mtx2);
local_78.normalize(); GXLoadPosMtxImm(mtx2, GX_PNMTX0);
param_1->mBaseAxis.cross(local_6c, local_78); p_prj[work->mPrjType](work, mtx2);
param_1->mBaseAxis.normalize(); GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
f32 particleX = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX;
f32 particleY = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY;
Mtx auStack_80;
Mtx local_60;
p_rot[param_0->mRotType](sinRot, cosRot, auStack_80);
p_plane[param_0->mPlaneType](auStack_80, particleX, particleY);
local_60[0][0] = param_1->mBaseAxis.x;
local_60[0][1] = local_6c.x;
local_60[0][2] = local_78.x;
local_60[0][3] = param_1->mPosition.x;
local_60[1][0] = param_1->mBaseAxis.y;
local_60[1][1] = local_6c.y;
local_60[1][2] = local_78.y;
local_60[1][3] = param_1->mPosition.y;
local_60[2][0] = param_1->mBaseAxis.z;
local_60[2][1] = local_6c.z;
local_60[2][2] = local_78.z;
local_60[2][3] = param_1->mPosition.z;
MTXConcat(local_60, auStack_80, auStack_80);
MTXConcat(param_0->mPosCamMtx, auStack_80, local_60);
GXLoadPosMtxImm(local_60, 0);
p_prj[param_0->mPrjType](param_0, local_60);
GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl));
} }
void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
@@ -204,6 +204,28 @@ void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent)
mTexAnmIdx = 0; mTexAnmIdx = 0;
} }
#if TARGET_PC
void JPABaseParticle::interp(JPAEmitterWorkData* work, void const* drawFunc) {
static bool enable = false;
if (!enable)
return;
// don't interpolate the first frame
if (mAge == 0)
return;
if (drawFunc == JPADrawBillboard) {
JPAInterpBillboard(work, this);
} else if (drawFunc == JPADrawRotBillboard) {
JPAInterpRotBillboard(work, this);
} else if (drawFunc == JPADrawDirection) {
JPAInterpDirection(work, this);
} else if (drawFunc == JPADrawRotDirection) {
JPAInterpRotDirection(work, this);
}
}
#endif
bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) { bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) {
if (++mAge >= mLifeTime) { if (++mAge >= mLifeTime) {
return true; return true;
@@ -247,6 +269,17 @@ bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) {
mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y,
mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z);
#if TARGET_PC
JPABaseShape* pBsp = work->mpRes->getBsp();
work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX();
work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY();
work->mDirType = pBsp->getDirType();
work->mRotType = pBsp->getRotType();
work->mDLType = pBsp->getType() == 4 || pBsp->getType() == 8;
work->mPlaneType = work->mDLType ? 2 : pBsp->getBasePlaneType();
interp(work, (void const*)work->mpRes->mpDrawParticleFuncList[0]);
#endif
return false; return false;
} }
@@ -289,6 +322,23 @@ bool JPABaseParticle::calc_c(JPAEmitterWorkData* work) {
mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y,
mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z);
#if TARGET_PC
JPABaseShape* pBsp = work->mpRes->getBsp();
JPAChildShape* pCsp = work->mpRes->getCsp();
if (pCsp->isScaleInherited()) {
work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX();
work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY();
} else {
work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pCsp->getScaleX();
work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pCsp->getScaleY();
}
work->mDirType = pCsp->getDirType();
work->mRotType = pCsp->getRotType();
work->mDLType = pCsp->getType() == 4 || pCsp->getType() == 8;
work->mPlaneType = work->mDLType ? 2 : pCsp->getBasePlaneType();
interp(work, (void const*)work->mpRes->mpDrawParticleChildFuncList[0]);
#endif
return false; return false;
} }
+6
View File
@@ -38,3 +38,9 @@ bool JUTPalette::load() {
return check; return check;
} }
#if TARGET_PC
void JUTPalette::dataUploaded() {
GXInitTlutObjData(&mTlutObj, (void*)mColorTable);
}
#endif
+9 -9
View File
@@ -27,7 +27,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, u8 param_1) {
mTexData = (void*)((intptr_t)mTexInfo + 0x20); mTexData = (void*)((intptr_t)mTexInfo + 0x20);
} }
field_0x2c = NULL; mPalette = NULL;
mTlutName = 0; mTlutName = 0;
mWrapS = mTexInfo->wrapS; mWrapS = mTexInfo->wrapS;
mWrapT = mTexInfo->wrapT; mWrapT = mTexInfo->wrapT;
@@ -95,7 +95,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p
} }
mEmbPalette = param_1; mEmbPalette = param_1;
setEmbPaletteDelFlag(false); setEmbPaletteDelFlag(false);
field_0x2c = NULL; mPalette = NULL;
if (param_1 != NULL) { if (param_1 != NULL) {
mTlutName = param_2; mTlutName = param_2;
if (param_2 != param_1->getTlutName()) { if (param_2 != param_1->getTlutName()) {
@@ -120,11 +120,11 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p
void JUTTexture::attachPalette(JUTPalette* param_0) { void JUTTexture::attachPalette(JUTPalette* param_0) {
if (mTexInfo->indexTexture) { if (mTexInfo->indexTexture) {
if (param_0 == NULL && mEmbPalette != NULL) { if (param_0 == NULL && mEmbPalette != NULL) {
field_0x2c = mEmbPalette; mPalette = mEmbPalette;
} else { } else {
field_0x2c = param_0; mPalette = param_0;
} }
initTexObj(field_0x2c->getTlutName()); initTexObj(mPalette->getTlutName());
} }
} }
@@ -133,9 +133,9 @@ void JUTTexture::init() {
initTexObj(); initTexObj();
} else { } else {
if (mEmbPalette != NULL) { if (mEmbPalette != NULL) {
field_0x2c = mEmbPalette; mPalette = mEmbPalette;
initTexObj(field_0x2c->getTlutName()); initTexObj(mPalette->getTlutName());
} else { } else {
OS_REPORT("This texture is CI-Format, but EmbPalette is NULL.\n"); OS_REPORT("This texture is CI-Format, but EmbPalette is NULL.\n");
} }
@@ -179,8 +179,8 @@ void JUTTexture::initTexObj(GXTlut param_0) {
} }
void JUTTexture::load(GXTexMapID param_0) { void JUTTexture::load(GXTexMapID param_0) {
if (field_0x2c) { if (mPalette) {
field_0x2c->load(); mPalette->load();
} }
GXLoadTexObj(&mTexObj, param_0); GXLoadTexObj(&mTexObj, param_0);
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+147
View File
@@ -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;
}
.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;
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;
}
+45
View File
@@ -0,0 +1,45 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
overflow: visible;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 18dp;
color: #E0DBC8;
}
button {
cursor: pointer;
focus: auto;
}
popup {
width: 100%;
display: flex;
align-items: stretch;
height: 64dp;
background-color: rgba(21, 22, 16, 80%);
border-bottom: 2dp #92875B;
backdrop-filter: blur(5dp);
transform: translateY(-64dp);
transition: transform 0.2s cubic-in-out;
}
popup[open] {
transform: translateY(0);
}
popup tab-bar {
flex: 1 1 0;
}
popup tab-bar tab {
opacity: 0.35;
color: #E0DBC8;
}
+187
View File
@@ -0,0 +1,187 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
width: 100%;
height: 100%;
font-family: "Fira Sans";
font-weight: normal;
font-size: 20dp;
color: #FFFFFF;
background-color: #000000;
decorator: image(../prelaunch-bg.png cover left center);
filter: opacity(0);
transition: filter 1s 0.1s linear-in-out;
}
body[open] {
filter: opacity(1);
}
content {
display: block;
width: 100%;
height: 100%;
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
content[open] {
filter: opacity(1);
}
menu {
position: absolute;
left: 96dp;
top: 50%;
transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */
width: 35.230264vw;
min-width: 428dp;
max-width: 856dp;
height: auto;
display: flex;
flex-direction: column;
gap: 48dp;
}
hero {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 8dp;
}
hero img {
width: 100%;
}
.eyebrow {
font-family: "Alegreya SC";
font-size: 32dp;
}
@media (min-width: 1216dp) {
.eyebrow {
/* Same logic as .menu, 32/1216 */
font-size: 2.631579vw;
}
}
.eyebrow span {
font-weight: bold;
}
#menu-list {
display: flex;
flex-direction: column;
gap: 12dp;
}
#menu-list button {
width: 428dp;
height: 54dp;
padding: 8dp 16dp;
border-radius: 8dp;
text-transform: uppercase;
font-family: "Fira Sans Condensed";
font-size: 32dp;
font-weight: normal;
cursor: pointer;
/* Define a fully transparent gradient as the default state, otherwise a white flash occurs */
decorator: horizontal-gradient(#00000000 #00000000);
}
#menu-list button.anim-done {
transition: decorator color 0.1s linear-in-out;
}
#menu-list button:hover,
#menu-list button:focus-visible {
color: black;
decorator: horizontal-gradient(#FEE685FF #FEE68500);
}
disk-status {
position: absolute;
left: 96dp;
bottom: 72dp;
display: flex;
flex-direction: column;
gap: 8dp;
}
version-info {
position: absolute;
right: 96dp;
bottom: 72dp;
display: flex;
flex-direction: column;
gap: 8dp;
text-align: right;
}
.status,
.version {
font-size: 24dp;
}
.status,
.update {
color: #D8F999;
}
.status[bad] {
color: #FFC9C9;
}
/* TODO: Hidden until an actual update checker is introduced */
.update {
display: none;
font-size: 16dp;
font-weight: bold;
cursor: pointer;
}
.detail,
.update span {
color: #A6A09B;
}
/* Startup animation */
.intro-item {
opacity: 0;
transform: translateY(10dp);
transition: opacity transform 0.3s 0.1s cubic-in-out;
}
body.animate-in .intro-item {
opacity: 1;
transform: translateY(0dp);
}
.delay-0 {
transition: opacity transform 0.3s 0.1s cubic-in-out;
}
.delay-1 {
transition: opacity transform 0.3s 0.2s cubic-in-out;
}
.delay-2 {
transition: opacity transform 0.3s 0.3s cubic-in-out;
}
.delay-3 {
transition: opacity transform 0.3s 0.4s cubic-in-out;
}
.delay-4 {
transition: opacity transform 0.3s 0.5s cubic-in-out;
}
.delay-5 {
transition: opacity transform 0.3s 0.6s cubic-in-out;
}
+33
View File
@@ -0,0 +1,33 @@
tab-bar {
display: flex;
min-width: 0;
overflow: auto hidden;
text-transform: uppercase;
}
tab-bar tab {
flex: 0 0 auto;
padding: 0 24dp;
line-height: 64dp;
white-space: nowrap;
decorator: vertical-gradient(#c2a42d00 #c2a42d00);
transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out;
cursor: pointer;
}
tab-bar tab:selected {
opacity: 1;
border-bottom: 4dp #C2A42D;
font-effect: glow(0dp 4dp 0dp 4dp black);
}
tab-bar tab:focus-visible,
tab-bar tab:hover {
opacity: 1;
font-effect: glow(0dp 4dp 0dp 4dp black);
decorator: vertical-gradient(#c2a42d00 #c2a42d26);
}
tab-bar tab:active {
decorator: vertical-gradient(#c2a42d10 #c2a42d40);
}
+239
View File
@@ -0,0 +1,239 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
width: 100%;
height: 100%;
padding: 64dp;
font-family: "Fira Sans";
font-weight: normal;
font-style: normal;
font-size: 15dp;
color: #E0DBC8;
}
window {
display: flex;
flex-flow: column;
height: 100%;
max-width: 1088dp;
max-height: 768dp;
margin: auto;
border-radius: 14dp;
overflow: hidden;
border: 2dp #92875B;
backdrop-filter: blur(5dp);
box-shadow: 0 0 25dp 5dp;
background-color: rgba(21, 22, 16, 90%);
filter: opacity(0);
transform: scale(0.9);
transform-origin: center;
transition: filter transform 0.2s cubic-in-out;
}
window[open] {
filter: opacity(1);
transform: scale(1);
}
@media (max-height: 640dp) {
body {
padding: 16dp;
}
window {
box-shadow: none;
}
}
window tab-bar {
flex: 0 0 64dp;
height: 64dp;
background-color: rgba(217, 217, 217, 10%);
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 18dp;
border-bottom: 2dp #92875B;
}
window tab-bar tab {
opacity: 0.25;
}
window content {
display: flex;
flex: 1 1 0;
min-width: 0;
min-height: 0;
overflow: hidden;
}
window content pane {
display: flex;
flex-flow: column;
flex: 1 1 0;
height: 100%;
min-width: 0;
min-height: 0;
padding: 24dp;
padding-bottom: 0dp;
gap: 8dp;
overflow: hidden auto;
font-size: 20dp;
}
window content pane:not(:last-of-type) {
border-right: 1dp #92875B;
}
window content pane > * {
flex: 0 0 auto;
}
window content pane > spacer {
display: block;
/* Completes the 24dp bottom inset after the pane's 8dp gap. */
flex: 0 0 16dp;
height: 16dp;
pointer-events: none;
}
scrollbarvertical {
width: 8dp;
margin: 4dp 4dp 4dp 0;
}
scrollbarvertical sliderarrowdec,
scrollbarvertical sliderarrowinc {
width: 0;
height: 0;
}
scrollbarvertical slidertrack {
width: 8dp;
}
scrollbarvertical sliderbar {
width: 8dp;
min-height: 24dp;
background-color: rgba(224, 219, 200, 45%);
border-radius: 2dp;
transition: background-color 0.2s cubic-in-out;
}
scrollbarvertical sliderbar:hover,
scrollbarvertical sliderbar:active {
background-color: rgba(194, 164, 45, 80%);
}
scrollbarhorizontal {
height: 0;
}
scrollbarhorizontal sliderarrowdec,
scrollbarhorizontal sliderarrowinc {
width: 0;
height: 0;
}
scrollbarhorizontal slidertrack,
scrollbarhorizontal sliderbar {
width: 0;
height: 0;
}
.section-heading {
font-family: "Fira Sans Condensed";
font-weight: bold;
text-transform: uppercase;
font-size: 22dp;
opacity: 0.25;
}
.section-heading:not(:first-of-type) {
padding-top: 12dp;
}
button {
text-align: center;
background-color: rgba(17, 16, 10, 20%);
opacity: 0.9;
padding: 8dp 16dp;
border-radius: 14dp;
box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
font-size: 20dp;
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
cursor: pointer;
focus: auto;
}
button:not(:disabled):hover,
button:not(:disabled):focus-visible {
background-color: rgba(204, 184, 119, 20%);
box-shadow: #C2A42D 0 0 0 2dp;
}
button:not(:disabled):selected {
opacity: 1;
background-color: rgba(204, 184, 119, 40%);
}
button:not(:disabled):active {
opacity: 1;
background-color: rgba(204, 184, 119, 40%);
box-shadow: #C2A42D 0 0 0 2dp;
}
select-button {
display: flex;
align-items: center;
gap: 8dp;
background-color: rgba(17, 16, 10, 20%);
opacity: 0.9;
padding: 8dp 16dp;
border-radius: 14dp;
box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
cursor: pointer;
focus: auto;
}
select-button:not(:disabled):hover,
select-button:not(:disabled):focus-visible {
background-color: rgba(204, 184, 119, 20%);
box-shadow: #C2A42D 0 0 0 2dp;
}
select-button:not(:disabled):selected {
opacity: 1;
background-color: rgba(204, 184, 119, 40%);
}
select-button:not(:disabled):active {
opacity: 1;
background-color: rgba(204, 184, 119, 40%);
box-shadow: #C2A42D 0 0 0 2dp;
}
select-button:disabled {
opacity: 0.35;
cursor: default;
}
select-button key {
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 18dp;
text-transform: uppercase;
flex: 1 0 auto;
}
select-button value {
margin-left: auto;
font-size: 20dp;
}
select-button input {
text-align: right;
font-size: 20dp;
}
+18 -1
View File
@@ -1,5 +1,9 @@
#include "Z2AudioLib/Z2Audience.h" #include "Z2AudioLib/Z2Audience.h"
#include "Z2AudioLib/Z2SoundInfo.h" #include "Z2AudioLib/Z2SoundInfo.h"
#if TARGET_PC
#include "dusk/audio/DuskDsp.hpp"
#include <cmath>
#endif
#include "Z2AudioLib/Z2Calc.h" #include "Z2AudioLib/Z2Calc.h"
#include "Z2AudioLib/Z2Param.h" #include "Z2AudioLib/Z2Param.h"
#include "JSystem/JAudio2/JAISound.h" #include "JSystem/JAudio2/JAISound.h"
@@ -734,9 +738,22 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) { f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) {
f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ(); f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ();
#if TARGET_PC
if (dusk::audio::EnableHrtf) {
// Normalize the direction so result is purely front/back orientation,
// independent of how far away the sound is
f32 lenSq = param_0.x * param_0.x + param_0.y * param_0.y + param_0.z * param_0.z;
if (lenSq < 0.0001f) {
return 0.5f;
}
f32 zNorm = param_0.z / sqrtf(lenSq);
f32 t = (zNorm + 1.0f) * 0.5f;
return 0.5f - 0.5f * cosf(t * static_cast<f32>(M_PI));
}
#endif
if (fVar1 > mSetting.field_0x48) { if (fVar1 > mSetting.field_0x48) {
return 1.0f; return 1.0f;
} }
if (fVar1 < mSetting.field_0x44) { if (fVar1 < mSetting.field_0x44) {
return 0.0f; return 0.0f;
+4 -1
View File
@@ -4962,13 +4962,16 @@ int daAlink_c::create() {
setArcName(checkWolf()); setArcName(checkWolf());
setOriginalHeap(&mpArcHeap, 0xA2800); setOriginalHeap(&mpArcHeap, 0xA2800);
JKRHEAP_NAME(mpArcHeap, "Alink ArcHeap");
if (dComIfG_resLoad(&mPhaseReq, mArcName, mpArcHeap) != cPhs_COMPLEATE_e) { if (dComIfG_resLoad(&mPhaseReq, mArcName, mpArcHeap) != cPhs_COMPLEATE_e) {
return cPhs_INIT_e; return cPhs_INIT_e;
} }
setShieldArcName(); setShieldArcName();
setOriginalHeap(&mpShieldArcHeap, 0x7000); setOriginalHeap(&mpShieldArcHeap, 0x7000);
if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e) { JKRHEAP_NAME(mpShieldArcHeap, "Alink ShieldArcHeap");
if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e)
{
return cPhs_INIT_e; return cPhs_INIT_e;
} }
-1
View File
@@ -46,7 +46,6 @@ void daAlink_c::setOriginalHeap(JKRExpHeap** i_ppheap, u32 i_size) {
JKRHeap* parent = mDoExt_getGameHeap(); JKRHeap* parent = mDoExt_getGameHeap();
JKRExpHeap* heap = JKRExpHeap::create(size + (var_r29 + var_r28), parent, true); JKRExpHeap* heap = JKRExpHeap::create(size + (var_r29 + var_r28), parent, true);
JKRHEAP_NAME(heap, "Alink original");
*i_ppheap = heap; *i_ppheap = heap;
} }
} }
+11
View File
@@ -17,6 +17,11 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#if TARGET_PC
#include <f_ap/f_ap_game.h>
#include <dusk/autosave.h>
#endif
char* daDoor20_c::getStopBmdName() { char* daDoor20_c::getStopBmdName() {
switch (door_param2_c::getKind(this)) { switch (door_param2_c::getKind(this)) {
case 3: case 3:
@@ -196,6 +201,7 @@ void daDoor20_c::setEventPrm() {
} else { } else {
roomNo = FRoomNo; roomNo = FRoomNo;
} }
if (dComIfGp_roomControl_checkStatusFlag(roomNo, 1)) { if (dComIfGp_roomControl_checkStatusFlag(roomNo, 1)) {
if (door_param2_c::getKind(this) == 9) { if (door_param2_c::getKind(this) == 9) {
if (daPy_py_c::checkNowWolf()) { if (daPy_py_c::checkNowWolf()) {
@@ -564,6 +570,11 @@ int daDoor20_c::openEnd(int param_1) {
openEnd_1(); openEnd_1();
break; break;
} }
#if TARGET_PC
triggerAutoSave();
#endif
return 1; return 1;
} }
+17
View File
@@ -463,6 +463,23 @@ int daMidna_c::createHeap() {
JKRReadIdxResource(mBckHeap[0].getBuffer(), mBckHeap[0].getBufferSize(), 0x1DC, dComIfGp_getAnmArchive()); JKRReadIdxResource(mBckHeap[0].getBuffer(), mBckHeap[0].getBufferSize(), 0x1DC, dComIfGp_getAnmArchive());
J3DAnmTransform* md_anm = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mBckHeap[0].getBuffer()); J3DAnmTransform* md_anm = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mBckHeap[0].getBuffer());
modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 14); modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 14);
#if TARGET_PC
J3DTexture* tex = modelData->getTexture();
JUTNameTab* nametable = modelData->getTextureName();
if (tex != NULL && nametable != NULL) {
for (u16 i = 0; i < tex->getNum(); i++) {
const char* name = nametable->getName(i);
if (name != NULL && strcmp(name, "midona_eye") == 0) {
ResTIMG* timg = tex->getResTIMG(i);
timg->mipmapEnabled = false;
tex->loadGXTexObj(i);
break;
}
}
}
#endif
JUT_ASSERT(852, modelData != NULL); JUT_ASSERT(852, modelData != NULL);
mpMorf = JKR_NEW mDoExt_McaMorfSO(modelData, &mMorfCB, NULL, md_anm, J3DFrameCtrl::EMode_LOOP, 1.0f, 0, -1, NULL, 0, 0x11000284); mpMorf = JKR_NEW mDoExt_McaMorfSO(modelData, &mMorfCB, NULL, md_anm, J3DFrameCtrl::EMode_LOOP, 1.0f, 0, -1, NULL, 0, 0x11000284);
if (mpMorf == NULL || mpMorf->getModel() == NULL) { if (mpMorf == NULL || mpMorf->getModel() == NULL) {
+17 -7
View File
@@ -40,6 +40,7 @@ dMirror_packet_c::dMirror_packet_c() {
void dMirror_packet_c::reset() { void dMirror_packet_c::reset() {
#if TARGET_PC #if TARGET_PC
mbReset = true; mbReset = true;
mbHadEntry = false;
#else #else
mModelCount = 0; mModelCount = 0;
#endif #endif
@@ -84,11 +85,21 @@ void dMirror_packet_c::calcMinMax() {
} }
int dMirror_packet_c::entryModel(J3DModel* i_model) { int dMirror_packet_c::entryModel(J3DModel* i_model) {
#if TARGET_PC
if (mbReset) {
mModelCount = 0;
mbReset = false;
}
#endif
if (mModelCount >= 0x40) { if (mModelCount >= 0x40) {
return 0; return 0;
} }
mModels[mModelCount++] = i_model; mModels[mModelCount++] = i_model;
#if TARGET_PC
mbHadEntry = true;
#endif
return 1; return 1;
} }
@@ -592,13 +603,6 @@ int daMirror_c::execute() {
return 1; return 1;
} }
#if TARGET_PC
if (mPacket.mbReset) {
mPacket.mModelCount = 0;
mPacket.mbReset = false;
}
#endif
daPy_py_c* player = daPy_getLinkPlayerActorClass(); daPy_py_c* player = daPy_getLinkPlayerActorClass();
JUT_ASSERT(0, player != NULL); JUT_ASSERT(0, player != NULL);
@@ -624,6 +628,12 @@ int daMirror_c::draw() {
mDoExt_modelUpdateDL(mpModel); mDoExt_modelUpdateDL(mpModel);
} }
#if TARGET_PC
if (mPacket.mbReset && !mPacket.mbHadEntry) {
mPacket.mModelCount = 0;
}
mPacket.mbHadEntry = true;
#endif
dComIfGd_getOpaListBG()->entryImm(&mPacket, 0); dComIfGd_getOpaListBG()->entryImm(&mPacket, 0);
return 1; return 1;
} }
+11 -7
View File
@@ -62,6 +62,16 @@ void daObj_Balloon_c::saveBestScore() {
dComIfGp_setMessageCountNumber(m_balloon_score); dComIfGp_setMessageCountNumber(m_balloon_score);
} }
#if TARGET_PC
static void minigameReset() {
// !@bug d_a_obj_balloon.rel unload used to zero these file-statics; with static linking they dangle across scenes.
m_combo_type = 0xFFFFFFFF;
m_combo_count = 0;
m_combo_next_score = 0;
m_balloon_score = 0;
}
#endif
static u8 hio_set; static u8 hio_set;
static daObj_Balloon_HIO_c l_HIO; static daObj_Balloon_HIO_c l_HIO;
@@ -205,13 +215,6 @@ int daObj_Balloon_c::_delete() {
Z2GetAudioMgr()->seStop(Z2SE_OBJ_WATERMILL_ROUND, 0); Z2GetAudioMgr()->seStop(Z2SE_OBJ_WATERMILL_ROUND, 0);
if (mHIOInit) { if (mHIOInit) {
hio_set = false; hio_set = false;
#ifdef TARGET_PC
// !@bug d_a_obj_balloon.rel unload used to zero these file-statics; with static linking they dangle across scenes.
m_combo_type = 0xFFFFFFFF;
m_combo_count = 0;
m_combo_next_score = 0;
m_balloon_score = 0;
#endif
} }
return 1; return 1;
} }
@@ -253,6 +256,7 @@ int daObj_Balloon_c::create() {
} }
if (!hio_set) { if (!hio_set) {
IF_DUSK(minigameReset());
mHIOInit = true; mHIOInit = true;
hio_set = true; hio_set = true;
l_HIO.field_0x04 = -1; l_HIO.field_0x04 = -1;
+55 -43
View File
@@ -1175,6 +1175,12 @@ bool dCamera_c::Run() {
clrFlag(0x200000); clrFlag(0x200000);
} }
} else { } else {
#if TARGET_PC
if (mCamParam.Algorythmn(mCamStyle) != 1) {
mCamParam.mManualMode = 0;
}
#endif
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle); sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
field_0x170++; field_0x170++;
@@ -1481,7 +1487,7 @@ void dCamera_c::CalcTrimSize() {
mTrimHeight += -mTrimHeight * 0.25f; mTrimHeight += -mTrimHeight * 0.25f;
break; break;
case 2: case 2:
#if WIDESCREEN_SUPPORT #if !TARGET_PC && WIDESCREEN_SUPPORT
if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) { if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) {
mTrimHeight += (16.0f - mTrimHeight) * 0.25f; mTrimHeight += (16.0f - mTrimHeight) * 0.25f;
break; break;
@@ -3089,10 +3095,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) {
field_0x968 *= mMonitor.field_0xc / 5.0f; field_0x968 *= mMonitor.field_0xc / 5.0f;
} }
#if TARGET_PC
if (!dusk::getSettings().game.freeCamera || !mCamParam.mManualMode) {
#endif
f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f); f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f);
center += vec3.norm() * (tmp * globe.V().Sin()); center += vec3.norm() * (tmp * globe.V().Sin());
cSGlobe globe2(vec2 - center); cSGlobe globe2(vec2 - center);
@@ -3106,10 +3108,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) {
vec = lin_chk1.GetCross(); vec = lin_chk1.GetCross();
} }
#if TARGET_PC
}
#endif
#if DEBUG #if DEBUG
if (mCamSetup.CheckFlag(0x8000)) { if (mCamSetup.CheckFlag(0x8000)) {
dDbVw_Report(20, 235, " U"); dDbVw_Report(20, 235, " U");
@@ -3520,12 +3518,6 @@ void dCamera_c::checkGroundInfo() {
} }
bool dCamera_c::chaseCamera(s32 param_0) { bool dCamera_c::chaseCamera(s32 param_0) {
#if TARGET_PC
if (freeCamera()) {
return 1;
}
#endif
static f32 JumpCushion = 0.9f; static f32 JumpCushion = 0.9f;
f32 charge_latitude = mCamSetup.ChargeLatitude(); f32 charge_latitude = mCamSetup.ChargeLatitude();
int charge_timer = mCamSetup.ChargeTimer(); int charge_timer = mCamSetup.ChargeTimer();
@@ -4207,6 +4199,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
chase->field_0x8 -= chase->field_0xc; chase->field_0x8 -= chase->field_0xc;
chase->field_0x8c = 0; chase->field_0x8c = 0;
chase->field_0x90 = false; chase->field_0x90 = false;
#if TARGET_PC
freeCamera();
#endif
return true; return true;
} }
@@ -4644,6 +4641,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
if (chase->field_0x1c != 0) { if (chase->field_0x1c != 0) {
chase->field_0x1c--; chase->field_0x1c--;
} }
#if TARGET_PC
freeCamera();
#endif
return true; return true;
} }
@@ -7091,10 +7093,12 @@ bool dCamera_c::subjectCamera(s32 param_0) {
cXyz sp1E0(val0, val2, val1); cXyz sp1E0(val0, val2, val1);
#if TARGET_PC #if TARGET_PC
f32 aspect = mDoGph_gInf_c::getAspect(); if (sp13) {
f32 baseAspect = FB_WIDTH / FB_HEIGHT; f32 aspect = mDoGph_gInf_c::getAspect();
if (aspect > baseAspect) { f32 baseAspect = FB_WIDTH / FB_HEIGHT;
sp1E0.z += (aspect - baseAspect) * 4; if (aspect > baseAspect) {
sp1E0.z += (aspect - baseAspect) * 4;
}
} }
#endif #endif
@@ -7472,52 +7476,47 @@ bool dCamera_c::test2Camera(s32 param_0) {
#if TARGET_PC #if TARGET_PC
bool dCamera_c::freeCamera() { bool dCamera_c::freeCamera() {
if (!dusk::getSettings().game.freeCamera) { if (dusk::getSettings().game.freeCamera && mGear == 1) {
mGear = 0;
}
if (!dusk::getSettings().game.freeCamera || mCamStyle == 70)
{
mCamParam.mManualMode = 0; mCamParam.mManualMode = 0;
return false; return false;
} }
mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree(); if (!mCamParam.mManualMode) {
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree(); mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
}
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f}; cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY); f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) { if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) {
if (!mCamParam.mManualMode) { mCamParam.mManualMode = 1;
mCamParam.mManualMode = 1;
mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
}
camMovement = camMovement.normalize(); camMovement = camMovement.normalize();
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f; camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f; mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f; mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
} }
if (!mCamParam.mManualMode) { fopAc_ac_c* player = dComIfGp_getPlayer(0);
if (!mCamParam.mManualMode || player == nullptr) {
return false; return false;
} }
f32 minYAngle = -10.0f; f32 minYAngle = -30.0f;
f32 maxAngle = 50.0f; f32 maxAngle = 50.0f;
mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle); mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle);
mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle);
mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle); mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
f32 currentLerp = (mCamParam.freeYAngle - minYAngle) / (maxAngle - minYAngle);
mViewCache.mDirection.mRadius = std::lerp(200.0f, 1000.0f, currentLerp);
cXyz finalCenter = mpPlayerActor->current.pos; cXyz finalEye = mViewCache.mCenter + mViewCache.mDirection.Xyz();
finalCenter.y += mIsWolf ? 90.0f : 100.0f;
mViewCache.mCenter = finalCenter;
cXyz finalEye = finalCenter + mViewCache.mDirection.Xyz();
mViewCache.mEye = finalEye; mViewCache.mEye = finalEye;
mViewCache.mFovy = 60.0f;
return true; return true;
} }
#endif #endif
@@ -11161,12 +11160,25 @@ static int camera_draw(camera_process_class* i_this) {
} }
#endif #endif
int trim_height = body->TrimHeight();
#if TARGET_PC #if TARGET_PC
auto trim_height = body->TrimHeight();
if (mDoGph_gInf_c::isWideZoom()) {
const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f);
const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF;
if (current_ar < target_ar) {
trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar);
} else {
trim_height = 0.0f;
}
}
trim_height *= viewport->height / FB_HEIGHT; trim_height *= viewport->height / FB_HEIGHT;
window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f); window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
#else #else
int trim_height = body->TrimHeight();
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f); window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
#endif #endif
+45 -9
View File
@@ -22,6 +22,10 @@
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include "dusk/gx_helper.h" #include "dusk/gx_helper.h"
#include "dusk/logging.h" #include "dusk/logging.h"
static const void* getInterpKey(const void* base, int idx) {
return reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(base) ^ idx);
}
#endif #endif
class dDlst_2Dm_c { class dDlst_2Dm_c {
@@ -1062,7 +1066,15 @@ void dDlst_shadowReal_c::reset() {
} }
void dDlst_shadowReal_c::imageDraw(Mtx param_0) { void dDlst_shadowReal_c::imageDraw(Mtx param_0) {
GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC); #ifdef TARGET_PC
Mtx render_proj_mtx;
if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 2), render_proj_mtx)) {
GXSetProjection(render_proj_mtx, GX_ORTHOGRAPHIC);
} else
#endif
{
GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC);
}
JUT_ASSERT(1916, mModelNum); JUT_ASSERT(1916, mModelNum);
J3DModelData* model_data; J3DModelData* model_data;
J3DModel** models = mpModels; J3DModel** models = mpModels;
@@ -1075,7 +1087,15 @@ void dDlst_shadowReal_c::imageDraw(Mtx param_0) {
for (u16 j = 0; j < model_data->getShapeNum(); j++) { for (u16 j = 0; j < model_data->getShapeNum(); j++) {
if (!model_data->getShapeNodePointer(j)->checkFlag(1)) { if (!model_data->getShapeNodePointer(j)->checkFlag(1)) {
shape_pkt = (*models)->getShapePacket(j); shape_pkt = (*models)->getShapePacket(j);
shape_pkt->setBaseMtxPtr(&mViewMtx); #ifdef TARGET_PC
Mtx view_mtx;
if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx)) {
shape_pkt->setBaseMtxPtr(&view_mtx);
} else
#endif
{
shape_pkt->setBaseMtxPtr(&mViewMtx);
}
shape_pkt->drawFast(); shape_pkt->drawFast();
shape_pkt->setBaseMtxPtr((Mtx*)param_0); shape_pkt->setBaseMtxPtr((Mtx*)param_0);
} }
@@ -1096,7 +1116,18 @@ void dDlst_shadowReal_c::draw() {
GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetCurrentMtx(GX_PNMTX0); GXSetCurrentMtx(GX_PNMTX0);
GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); #ifdef TARGET_PC
Mtx view_mtx, recv_proj_mtx;
const auto have_view_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx);
const auto have_recv_proj_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 3), recv_proj_mtx);
if (have_view_mtx && have_recv_proj_mtx) {
cMtx_concat(recv_proj_mtx, view_mtx, recv_proj_mtx);
GXLoadTexMtxImm(recv_proj_mtx, GX_TEXMTX0, GX_MTX3x4);
} else
#endif
{
GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4);
}
mShadowRealPoly.draw(); mShadowRealPoly.draw();
} }
@@ -1253,6 +1284,13 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_
cMtx_lookAt(mViewMtx, &local_64, param_1, 0); cMtx_lookAt(mViewMtx, &local_64, param_1, 0);
C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f); C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f);
C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f); C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f);
#ifdef TARGET_PC
const auto keybase = mpModels[0];
dusk::frame_interp::record_final_mtx(mViewMtx, getInterpKey(keybase, 1));
dusk::frame_interp::record_final_mtx(mRenderProjMtx, getInterpKey(keybase, 2));
dusk::frame_interp::record_final_mtx(mReceiverProjMtx, getInterpKey(keybase, 3));
#endif
cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx); cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx);
return r29; return r29;
} }
@@ -1277,6 +1315,10 @@ u32 dDlst_shadowReal_c::set(u32 i_key, J3DModel* i_model, cXyz* param_2, f32 par
} }
} }
#ifdef TARGET_PC
// provide a stable key for interpolation
mpModels[0] = i_model;
#endif
field_0x1 = setShadowRealMtx(&sp60, param_2, param_3, param_4, param_7, param_5); field_0x1 = setShadowRealMtx(&sp60, param_2, param_3, param_4, param_7, param_5);
if (!field_0x1) { if (!field_0x1) {
@@ -1370,12 +1412,6 @@ void dDlst_shadowSimple_c::draw() {
GXCallDisplayList(l_shadowVolumeDL, 0x40); GXCallDisplayList(l_shadowVolumeDL, 0x40);
} }
#if TARGET_PC
static const void* getInterpKey(const void* base, int idx) {
return reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(base) ^ idx);
}
#endif
void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* param_3, void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* param_3,
s16 param_4, f32 param_5, TGXTexObj* param_6) { s16 param_4, f32 param_5, TGXTexObj* param_6) {
if (param_5 < 0.0f) { if (param_5 < 0.0f) {
+39
View File
@@ -856,7 +856,46 @@ void dMenu_DmapBg_c::decGoldFrameAlphaRate() {
setGoldFrameAlphaRate(rate); setGoldFrameAlphaRate(rate);
} }
void dMenu_DmapBg_c::dMapBgWide() {
// Scale Base HUD
mBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
// Boss Key, Compass & Map icons
mBaseScreen->search(MULTI_CHAR('key_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mBaseScreen->search(MULTI_CHAR('con_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mBaseScreen->search(MULTI_CHAR('map_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// Text Header
mBaseScreen->search(MULTI_CHAR('t_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mBaseScreen->search(MULTI_CHAR('f_t_00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// C Button
mBaseScreen->search(MULTI_CHAR('c_btn2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// Scale Buttons HUD
mButtonScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mButtonScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
// Buttons
mButtonScreen->search(MULTI_CHAR('cont_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// C Button
mButtonScreen->search(MULTI_CHAR('c_btn'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mButtonScreen->search(MULTI_CHAR('c_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mButtonScreen->search(MULTI_CHAR('c_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mButtonScreen->search(MULTI_CHAR('f_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
mButtonScreen->search(MULTI_CHAR('f_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// Decorations
mButtonScreen->search(MULTI_CHAR('kazari_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
void dMenu_DmapBg_c::draw() { void dMenu_DmapBg_c::draw() {
#if TARGET_PC
dMapBgWide();
#endif
u32 scissor_left; u32 scissor_left;
u32 scissor_top; u32 scissor_top;
u32 scissor_width; u32 scissor_width;
+34 -1
View File
@@ -20,6 +20,15 @@
#include "dusk/frame_interpolation.h" #include "dusk/frame_interpolation.h"
#include <cstring> #include <cstring>
#if TARGET_PC
void dMenu_Fmap2DBack_c::fMapBackWide() {
mpBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mpBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
mpBackScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mpBackScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
}
#endif
dMenu_Fmap2DBack_c::dMenu_Fmap2DBack_c() { dMenu_Fmap2DBack_c::dMenu_Fmap2DBack_c() {
dMeter2Info_setMapDrugFlag(0); dMeter2Info_setMapDrugFlag(0);
@@ -267,6 +276,10 @@ dMenu_Fmap2DBack_c::~dMenu_Fmap2DBack_c() {
} }
void dMenu_Fmap2DBack_c::draw() { void dMenu_Fmap2DBack_c::draw() {
#if TARGET_PC
fMapBackWide();
#endif
calcBlink(); calcBlink();
J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort();
@@ -1199,7 +1212,7 @@ f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeX() {
} }
f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeRealX() { f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeRealX() {
#if PLATFORM_GCN && !TARGET_PC #if PLATFORM_GCN
return getMapScissorAreaSizeX(); return getMapScissorAreaSizeX();
#else #else
return getMapScissorAreaSizeX() * mDoGph_gInf_c::getScale(); return getMapScissorAreaSizeX() * mDoGph_gInf_c::getScale();
@@ -1407,6 +1420,11 @@ void dMenu_Fmap2DBack_c::stageTextureDraw() {
mpSpotTexture->setAlpha(mAlphaRate * 255.0f * field_0xfa8 * mSpotTextureFadeAlpha); mpSpotTexture->setAlpha(mAlphaRate * 255.0f * field_0xfa8 * mSpotTextureFadeAlpha);
} }
#if TARGET_PC
JUTPalette* pPalette = mpSpotTexture->getTexture(0)->getPalette();
pPalette->dataUploaded();
#endif
mpSpotTexture->draw(mTransX + getMapScissorAreaLX(), mTransZ + getMapScissorAreaLY(), mpSpotTexture->draw(mTransX + getMapScissorAreaLX(), mTransZ + getMapScissorAreaLY(),
getMapScissorAreaSizeRealX(), getMapScissorAreaSizeRealY(), false, false, getMapScissorAreaSizeRealX(), getMapScissorAreaSizeRealY(), false, false,
false); false);
@@ -2179,6 +2197,17 @@ void dMenu_Fmap2DBack_c::setArrowPosAxis(f32 i_posX, f32 i_posZ) {
control_ypos = 0.0f; control_ypos = 0.0f;
} }
#if TARGET_PC
void dMenu_Fmap2DTop_c::fMapTopWide() {
mpTitleScreen->search(MULTI_CHAR('spot0_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mpTitleScreen->search(MULTI_CHAR('spot2_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
mpTitleScreen->search(MULTI_CHAR('name_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-243.0f), -169.0f);
mpTitleScreen->search(MULTI_CHAR('sub_n_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-80.0f), -154.0f);
mpTitleScreen->search(MULTI_CHAR('btn_i_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-241.0f), 177.0f);
mpTitleScreen->search(MULTI_CHAR('cont_n'))->translate(mDoGph_gInf_c::ScaleHUDXRight(515.0f), 83.0f);
}
#endif
dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) { dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) {
mpHeap = i_heap; mpHeap = i_heap;
mTransX = 0.0f; mTransX = 0.0f;
@@ -2572,6 +2601,10 @@ void dMenu_Fmap2DTop_c::setAllAlphaRate(f32 i_rate, bool i_init) {
} }
void dMenu_Fmap2DTop_c::draw() { void dMenu_Fmap2DTop_c::draw() {
#if TARGET_PC
fMapTopWide();
#endif
u32 scissor_left, scissor_top, scissor_width, scissor_height; u32 scissor_left, scissor_top, scissor_width, scissor_height;
J2DOrthoGraph* ctx = static_cast<J2DOrthoGraph*>(dComIfGp_getCurrentGrafPort()); J2DOrthoGraph* ctx = static_cast<J2DOrthoGraph*>(dComIfGp_getCurrentGrafPort());
ctx->setup2D(); ctx->setup2D();
+4
View File
@@ -2306,6 +2306,10 @@ void dMeter_drawHIO_c::updateOnWide() {
// River Canoe Minigame // River Canoe Minigame
g_drawHIO.mMiniGame.mCounterPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[1]); g_drawHIO.mMiniGame.mCounterPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[1]);
g_drawHIO.mMiniGame.mIconPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[1]); g_drawHIO.mMiniGame.mIconPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[1]);
// Bulblin Count in Hidden Village
g_drawHIO.mMiniGame.mCounterPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[2]);
g_drawHIO.mMiniGame.mIconPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[2]);
#endif #endif
} }
+19
View File
@@ -41,6 +41,7 @@
#if TARGET_PC #if TARGET_PC
#include "dusk/memory.h" #include "dusk/memory.h"
#include <dusk/autosave.h>
#endif #endif
#if DEBUG #if DEBUG
@@ -700,6 +701,10 @@ static u8 lbl_8074CAE4;
static u32 l_sceneChangeStartTick; static u32 l_sceneChangeStartTick;
#endif #endif
#if TARGET_PC
static BOOL autoSaved;
#endif
static int dScnPly_Execute(dScnPly_c* i_this) { static int dScnPly_Execute(dScnPly_c* i_this) {
#if DEBUG #if DEBUG
fapGm_HIO_c::startCpuTimer(); fapGm_HIO_c::startCpuTimer();
@@ -742,6 +747,15 @@ static int dScnPly_Execute(dScnPly_c* i_this) {
} }
} }
#if TARGET_PC
if (!dComIfGp_event_runCheck() && !fopOvlpM_IsPeek() && !dComIfG_resetToOpening(i_this) &&
!dComIfGp_isEnableNextStage() && autoSaved == FALSE)
{
triggerAutoSave();
autoSaved = TRUE;
}
#endif
dKy_itudemo_se(); dKy_itudemo_se();
#if DEBUG #if DEBUG
@@ -1593,6 +1607,11 @@ static int dScnPly_Create(scene_class* i_this) {
dScnPly_c* a_this = (dScnPly_c*)i_this; dScnPly_c* a_this = (dScnPly_c*)i_this;
int phase_state = dComLbG_PhaseHandler(&a_this->field_0x1c4, l_method, a_this); int phase_state = dComLbG_PhaseHandler(&a_this->field_0x1c4, l_method, a_this);
#if TARGET_PC
autoSaved = FALSE;
#endif
return phase_state; return phase_state;
} }
+8
View File
@@ -27,7 +27,11 @@
#include "lingcod/lingcod.h" #include "lingcod/lingcod.h"
#endif #endif
#if TARGET_PC
#include "dusk/settings.h" #include "dusk/settings.h"
#include <f_ap/f_ap_game.h>
#include <dusk/autosave.h>
#endif
static u8 dSv_item_rename(u8 i_itemNo) { static u8 dSv_item_rename(u8 i_itemNo) {
switch (i_itemNo) { switch (i_itemNo) {
@@ -345,6 +349,10 @@ void dSv_player_item_c::setItem(int i_slotNo, u8 i_itemNo) {
dComIfGp_setSelectItem(i); dComIfGp_setSelectItem(i);
} }
} }
#if TARGET_PC
triggerAutoSave();
#endif
} }
u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const { u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const {
+91
View File
@@ -0,0 +1,91 @@
#include "aurora/lib/logging.hpp"
#include "os_report.h"
aurora::Module Log("dusk::osReport");
bool dusk::OSReportReallyForceEnable = false;
u8 __OSReport_disable;
void OSReportDisable() {
__OSReport_disable = true;
}
void OSReportEnable() {
__OSReport_disable = false;
}
static bool checkEnabled() {
return !__OSReport_disable || dusk::OSReportReallyForceEnable;
}
static std::string FormatToString(const char* msg, va_list list) {
int ret = vsnprintf(nullptr, 0, msg, list);
std::string buf(ret, '\0');
vsnprintf(buf.data(), buf.size(), msg, list);
buf.pop_back();
return buf;
}
void OSReport_Error(const char* fmt, ...) {
if (!checkEnabled()) {
return;
}
va_list args;
va_start(args, fmt);
const auto str = FormatToString(fmt, args);
va_end(args);
Log.error("{}", str);
}
void OSReport_FatalError(const char* fmt, ...) {
if (!checkEnabled()) {
return;
}
va_list args;
va_start(args, fmt);
const auto str = FormatToString(fmt, args);
va_end(args);
Log.fatal("{}", str);
}
void OSReport_Warning(const char* fmt, ...) {
if (!checkEnabled()) {
return;
}
va_list args;
va_start(args, fmt);
const auto str = FormatToString(fmt, args);
va_end(args);
Log.warn("{}", str);
}
void OSReport_System(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
OSVAttention(fmt, args);
va_end(args);
}
void OSVAttention(const char* fmt, va_list args) {
if (!checkEnabled()) {
return;
}
const auto str = FormatToString(fmt, args);
Log.info("{}", str);
}
void OSAttention(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
OSVAttention(fmt, args);
va_end(args);
}
+54
View File
@@ -48,6 +48,20 @@ f32 dusk::audio::MasterVolume = 1.0f;
f32 dusk::audio::PrevMasterVolume = 1.0f; f32 dusk::audio::PrevMasterVolume = 1.0f;
bool dusk::audio::EnableReverb = true; bool dusk::audio::EnableReverb = true;
bool dusk::audio::DumpAudio = false; bool dusk::audio::DumpAudio = false;
bool dusk::audio::EnableHrtf = false;
f32 dusk::audio::HrtfGain = 0.5f;
// 3dB at 5kHz.
static constexpr f32 HRTF_LP_K = 0.75f;
static constexpr f32 HRTF_ALLPASS_G = 0.3f;
// Front never drops below (1 - HRTF_EXTRACT_MAX).
static constexpr f32 HRTF_EXTRACT_MAX = 0.6f;
static f32 sHrtfLp1 = 0.0f;
static f32 sHrtfLp2 = 0.0f;
static f32 sHrtfApIn1 = 0.0f;
static f32 sHrtfApOut1 = 0.0f;
/** /**
* Validate that a DSP channel's format is actually something we know how to play. * Validate that a DSP channel's format is actually something we know how to play.
@@ -283,6 +297,9 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
DspSubframe reverbInputR = {}; DspSubframe reverbInputR = {};
bool anyReverbInput = false; bool anyReverbInput = false;
DspSubframe surroundBus = {};
bool anySurroundInput = false;
for (int i = 0; i < channels.size(); i++) { for (int i = 0; i < channels.size(); i++) {
auto& channel = channels[i]; auto& channel = channels[i];
auto& channelAux = ChannelAux[i]; auto& channelAux = ChannelAux[i];
@@ -324,6 +341,21 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
} }
} }
if (EnableHrtf && channel.mAutoMixerBeenSet) {
f32 dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.0f;
if (dolby > 0.0f) {
anySurroundInput = true;
f32 extract = dolby * HRTF_EXTRACT_MAX;
f32 frontScale = 1.0f - extract;
for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) {
f32 mono = (channelSubframe.channels[0][j] + channelSubframe.channels[1][j]) * 0.5f;
surroundBus[j] += mono * extract;
channelSubframe.channels[0][j] *= frontScale;
channelSubframe.channels[1][j] *= frontScale;
}
}
}
if (DumpAudio && sChannelDumpFiles[i]) { if (DumpAudio && sChannelDumpFiles[i]) {
f32 interleaved[DSP_SUBFRAME_SIZE * 2]; f32 interleaved[DSP_SUBFRAME_SIZE * 2];
for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) {
@@ -349,6 +381,28 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON;
} }
if (EnableHrtf && anySurroundInput) {
// Two-pole LPF: -12 dB/oct above 3 kHz
for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) {
sHrtfLp1 = (1.0f - HRTF_LP_K) * sHrtfLp1 + HRTF_LP_K * surroundBus[j];
sHrtfLp2 = (1.0f - HRTF_LP_K) * sHrtfLp2 + HRTF_LP_K * sHrtfLp1;
surroundBus[j] = sHrtfLp2;
}
// Mix into L and R
// L gets the filtered signal directly; R gets it allpass for mild decorrelation
for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) {
f32 s = surroundBus[j];
subframe.channels[0][j] += s * HrtfGain;
f32 r = -HRTF_ALLPASS_G * s + sHrtfApIn1 + HRTF_ALLPASS_G * sHrtfApOut1;
sHrtfApIn1 = s;
sHrtfApOut1 = r;
subframe.channels[1][j] += r * HrtfGain;
}
}
for (auto& channel : subframe.channels) { for (auto& channel : subframe.channels) {
ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume);
} }
+2
View File
@@ -133,4 +133,6 @@ namespace dusk::audio {
extern f32 PrevMasterVolume; extern f32 PrevMasterVolume;
extern bool EnableReverb; extern bool EnableReverb;
extern bool DumpAudio; extern bool DumpAudio;
extern bool EnableHrtf;
extern f32 HrtfGain;
} }
+88
View File
@@ -0,0 +1,88 @@
#include "dusk/autosave.h"
#include "imgui/ImGuiConsole.hpp"
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
u8 mAutoSaveProc = 0;
int autoSaveWriteState = 0;
typedef void (*AutoSaveFuncs)();
static AutoSaveFuncs AutoSaveFuncsProc[] = {
noAutoSave, enterAutoSave, autoSaving, waitingForWrite, endAutoSave,
};
void noAutoSave() {}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
{
mAutoSaveProc = 1;
}
}
void updateAutoSave() {
(AutoSaveFuncsProc[mAutoSaveProc])();
}
void writeAutoSave() {
int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo());
dComIfGs_putSave(stageNo);
dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum());
mDoMemCdRWm_SetCheckSumGameData(mSaveBuffer, dComIfGs_getDataNum());
u8* save = mSaveBuffer;
for (int i = 0; i < 3; i++) {
mDoMemCdRWm_TestCheckSumGameData(save);
save += QUEST_LOG_SIZE;
}
g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0);
}
void autoSaving() {
int cardState = g_mDoMemCd_control.LoadSync(mSaveBuffer, sizeof(mSaveBuffer), 0);
if (cardState != 0) {
if (cardState == 2) {
mAutoSaveProc = 1;
} else if (cardState == 1) {
writeAutoSave();
mAutoSaveProc = 3;
}
}
}
void enterAutoSave() {
u32 cardStatus = g_mDoMemCd_control.getStatus(0);
if (cardStatus != 14) {
switch (cardStatus) {
case 2:
g_mDoMemCd_control.load();
mAutoSaveProc = 2;
break;
case 3:
case 4:
case 5:
break;
default:
mAutoSaveProc = 0;
break;
}
}
}
void waitingForWrite() {
autoSaveWriteState = g_mDoMemCd_control.SaveSync();
if (autoSaveWriteState == 2) {
mAutoSaveProc = 0;
} else if (autoSaveWriteState == 1) {
mAutoSaveProc = 4;
}
}
void endAutoSave() {
dusk::g_imguiConsole.AddToast("Saving...", 2.0f);
mAutoSaveProc = 0;
}
+74 -1
View File
@@ -1,5 +1,7 @@
#include "ImGuiConsole.hpp" #include "ImGuiConsole.hpp"
#include "ImGuiMenuTools.hpp" #include "ImGuiMenuTools.hpp"
#include <cmath>
#include "JSystem/JAudio2/JAISeq.h"
#include "JSystem/JAudio2/JAISeMgr.h" #include "JSystem/JAudio2/JAISeMgr.h"
#include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAISeqMgr.h"
#include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h"
@@ -15,6 +17,24 @@ static std::array<u32, DSP_CHANNELS> lastResetCounts = {};
static bool sortUpdateCount = true; static bool sortUpdateCount = true;
static void DrawDirectionGauge(float pan, float dolby) {
constexpr float R = 20.0f;
constexpr float SIZE = R * 2.0f + 4.0f;
ImVec2 origin = ImGui::GetCursorScreenPos();
ImGui::Dummy(ImVec2(SIZE, SIZE));
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 c = ImVec2(origin.x + SIZE * 0.5f, origin.y + SIZE * 0.5f);
dl->AddCircle(c, R, IM_COL32(90, 90, 90, 255), 32);
float dx = (pan - 0.5f) * 2.0f;
float dy = dolby * 2.0f - 1.0f;
float len = sqrtf(dx * dx + dy * dy);
if (len > 1.0f) { dx /= len; dy /= len; }
dl->AddLine(c, ImVec2(c.x + dx * R, c.y + dy * R), IM_COL32(255, 200, 50, 255), 1.5f);
}
static void DisplayDspChannel(int i) { static void DisplayDspChannel(int i) {
using namespace dusk::audio; using namespace dusk::audio;
@@ -52,8 +72,10 @@ static void DisplayDspChannel(int i) {
auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f;
auto volume = VolumeFromU16(channel.mAutoMixerVolume); auto volume = VolumeFromU16(channel.mAutoMixerVolume);
auto pitch = channel.mPitch / 4096.0f; auto pitch = channel.mPitch / 4096.0f;
DrawDirectionGauge(pan, dolby);
ImGui::SameLine();
ImGui::Text( ImGui::Text(
"Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f, pitch %f)", "pan: %.2f dolby: %.2f\nfx: %.2f vol: %.2f pitch: %.2f",
pan, dolby, fxMix, volume, pitch); pan, dolby, fxMix, volume, pitch);
} else { } else {
ImGui::Text( ImGui::Text(
@@ -183,6 +205,10 @@ static void ShowAllJAISes() {
if (ImGui::Button("Pause All")) { if (ImGui::Button("Pause All")) {
category->pause(true); category->pause(true);
} }
ImGui::SameLine();
if (ImGui::Button("Resume All")) {
category->pause(false);
}
for (auto seLink = category->getSeList()->getFirst(); seLink != nullptr; seLink = seLink->getNext()) { for (auto seLink = category->getSeList()->getFirst(); seLink != nullptr; seLink = seLink->getNext()) {
const auto se = seLink->getObject(); const auto se = seLink->getObject();
@@ -196,6 +222,33 @@ static void ShowAllJAISes() {
} }
static void ShowSeqTracks(JAISeq& seq) {
JASTrack& root = seq.inner_.outputTrack;
for (int group = 0; group < 2; group++) {
JASTrack* groupTrack = root.getChild(group);
if (groupTrack == nullptr) {
continue;
}
for (int j = 0; j < JASTrack::MAX_CHILDREN; j++) {
JASTrack* track = groupTrack->getChild(j);
if (track == nullptr) {
continue;
}
int trackIdx = group * 16 + j;
char label[64];
snprintf(label, sizeof(label), "Track %d (bank %hu, prog %hu)##%p",
trackIdx, track->getBankNumber(), track->getProgNumber(), track);
bool muted = track->mFlags.mute;
if (ImGui::Checkbox(label, &muted)) {
track->mute(muted);
}
}
}
}
static void ShowAllJAISeqs() { static void ShowAllJAISeqs() {
auto& mgr = *JAISeqMgr::getInstance(); auto& mgr = *JAISeqMgr::getInstance();
@@ -206,6 +259,26 @@ static void ShowAllJAISeqs() {
if (ImGui::Button("Unpause")) { if (ImGui::Button("Unpause")) {
mgr.pause(false); mgr.pause(false);
} }
ImGui::Text("Active sequences: %d", mgr.getNumActiveSeqs());
auto* seqList = mgr.getSeqList();
for (auto* link = seqList->getFirst(); link != nullptr; link = link->getNext()) {
JAISeq* seq = link->getObject();
if (seq == nullptr) {
continue;
}
char buf[32];
snprintf(buf, sizeof(buf), "%p", seq);
if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
ImGui::Text("Seq [%p]", seq);
ShowSeqTracks(*seq);
}
ImGui::EndChild();
}
} }
void dusk::ImGuiMenuTools::ShowAudioDebug() { void dusk::ImGuiMenuTools::ShowAudioDebug() {
+12 -12
View File
@@ -324,18 +324,18 @@ namespace dusk {
ImGuiMenuGame::ToggleFullscreen(); ImGuiMenuGame::ToggleFullscreen();
} }
if (!dusk::IsGameLaunched) { // if (!dusk::IsGameLaunched) {
m_preLaunchWindow.draw(); // m_preLaunchWindow.draw();
} // }
m_isHidden = !getSettings().backend.duskMenuOpen; m_isHidden = !getSettings().backend.duskMenuOpen;
bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
if (dusk::IsGameLaunched) { m_isHidden = !m_isHidden;
const bool menuOpen = !m_isHidden; }
if (getSettings().backend.duskMenuOpen != menuOpen) { bool showMenu = !m_isHidden;
getSettings().backend.duskMenuOpen.setValue(menuOpen); if (getSettings().backend.duskMenuOpen != showMenu) {
Save(); 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,
@@ -365,10 +365,10 @@ namespace dusk {
} }
if (dusk::IsGameLaunched && !m_isLaunchInitialized) { if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
m_toasts.emplace_back(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
"Tap to toggle menu"s : "Tap to toggle menu"s :
"Press F1 to toggle menu"s, "Press F1 to toggle menu"s,
2.5f); 4.f);
m_isLaunchInitialized = true; m_isLaunchInitialized = true;
if (getSettings().game.liveSplitEnabled) { if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit(); dusk::speedrun::connectLiveSplit();
File diff suppressed because it is too large Load Diff
+1
View File
@@ -44,6 +44,7 @@ static void ApplyPresetDusk() {
s.game.enableFrameInterpolation.setValue(true); s.game.enableFrameInterpolation.setValue(true);
s.game.sunsSong.setValue(true); s.game.sunsSong.setValue(true);
s.game.bloomMode.setValue(BloomMode::Dusk); s.game.bloomMode.setValue(BloomMode::Dusk);
s.game.autoSave.setValue(true);
} }
// ========================================================================= // =========================================================================
+3 -3
View File
@@ -180,9 +180,9 @@ namespace dusk {
void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) {
char title[128]; char title[128];
const char* name = data.Safe ? heap->getName() : "INVALID"; const char* name = data.Safe ? heap->getName() : "INVALID";
snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast<const void*>(heap)); snprintf(title, sizeof(title), "Heap %s##%p", name, static_cast<const void*>(heap));
if (!ImGui::Begin(name, &open)) { if (!ImGui::Begin(title, &open)) {
ImGui::End(); ImGui::End();
return; return;
} }
@@ -195,7 +195,7 @@ namespace dusk {
heap->lock(); heap->lock();
ImGui::Text("Name: %s", heap->getName()); ImGui::Text("Name: %s", name);
const auto size = BytesToString(heap->getSize()); const auto size = BytesToString(heap->getSize());
const auto freeSize = BytesToString(heap->getFreeSize()); const auto freeSize = BytesToString(heap->getFreeSize());
ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str()); ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str());
+3 -478
View File
@@ -3,31 +3,13 @@
#include "ImGuiEngine.hpp" #include "ImGuiEngine.hpp"
#include "ImGuiConsole.hpp" #include "ImGuiConsole.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiConfig.hpp" #include "ImGuiConfig.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/main.h" #include "dusk/main.h"
#include "dusk/hotkeys.h" #include "dusk/hotkeys.h"
#include "dusk/settings.h"
#include "dusk/livesplit.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_graphic.h"
#include <aurora/gfx.h>
#include <SDL3/SDL_gamepad.h>
#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_main.h"
namespace { #include <SDL3/SDL_gamepad.h>
constexpr int kInternalResolutionScaleMax = 12;
} // namespace
namespace aurora::gx {
extern bool enableLodBias;
}
namespace dusk { namespace dusk {
void ImGuiMenuGame::ToggleFullscreen() { void ImGuiMenuGame::ToggleFullscreen() {
@@ -40,475 +22,17 @@ namespace dusk {
void ImGuiMenuGame::draw() { void ImGuiMenuGame::draw() {
if (ImGui::BeginMenu("Settings")) { if (ImGui::BeginMenu("Settings")) {
drawAudioMenu(); // TODO: Remove this once Controller Config exists in RmlUi
drawCheatsMenu();
drawGameplayMenu();
drawGraphicsMenu();
drawInputMenu();
drawInterfaceMenu();
ImGui::Separator();
if (ImGui::MenuItem("Reset", hotkeys::DO_RESET)) {
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}
if (!IsMobile && ImGui::MenuItem("Exit")) {
dusk::IsRunning = false;
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawGraphicsMenu() {
if (ImGui::BeginMenu("Graphics")) {
ImGui::SeparatorText("Display");
if (!IsMobile) {
if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) {
ToggleFullscreen();
}
if (ImGui::Button("Restore Default Window Size")) {
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
}
}
ImGui::Separator();
bool vsync = getSettings().video.enableVsync;
if (ImGui::Checkbox("Enable VSync", &vsync)) {
getSettings().video.enableVsync.setValue(vsync);
aurora_enable_vsync(vsync);
config::Save();
}
bool lockAspect = getSettings().video.lockAspectRatio;
if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) {
getSettings().video.lockAspectRatio.setValue(lockAspect);
if (lockAspect) {
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
} else {
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
}
config::Save();
}
ImGui::SeparatorText("Resolution");
u32 internalResolutionWidth = 0;
u32 internalResolutionHeight = 0;
AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight);
ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth,
internalResolutionHeight);
int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
kInternalResolutionScaleMax);
if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax,
scale == 0 ? "Auto" : "%dx"))
{
getSettings().game.internalResolutionScale.setValue(scale);
VISetFrameBufferScale(static_cast<float>(scale));
config::Save();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Auto renders at the native window resolution.\n"
"Higher values scale the game's internal framebuffer.");
}
config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Improves the shadow resolution, making them higher quality.");
}
ImGui::SeparatorText("Post-Processing");
constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"};
int bloomMode = static_cast<int>(getSettings().game.bloomMode.getValue());
if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) {
for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) {
const bool selected = bloomMode == i;
if (ImGui::Selectable(bloomModeNames[i], selected)) {
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(i));
config::Save();
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
bool bloomOff = bloomMode == static_cast<int>(BloomMode::Off);
if (bloomOff) ImGui::BeginDisabled();
float mult = getSettings().game.bloomMultiplier.getValue();
if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) {
getSettings().game.bloomMultiplier.setValue(mult);
config::Save();
}
if (bloomOff) ImGui::EndDisabled();
ImGui::SeparatorText("Rendering");
config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation);
const bool frameInterpolationHovered = ImGui::IsItemHovered();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
ImGui::TextUnformatted("[EXPERIMENTAL]");
ImGui::PopStyleColor();
if (frameInterpolationHovered || ImGui::IsItemHovered()) {
ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur.");
}
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField);
config::ImGuiCheckbox("Enable Mini-Map Shadows", getSettings().game.enableMapBackground);
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawGameplayMenu() {
if (ImGui::BeginMenu("Gameplay")) {
ImGui::SeparatorText("General");
config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game.");
}
config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disables the main HUD of the game.\n"
"Useful for recording or a more immersive experience!");
}
config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n"
"the first released version.");
}
config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick");
}
ImGui::SeparatorText("Difficulty");
ImGui::BeginDisabled(getSettings().game.speedrunMode);
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Any hit will instantly kill you.");
}
config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
"pots and various other places.");
}
ImGui::EndDisabled();
ImGui::SeparatorText("Quality of Life");
config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)");
}
config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time.");
}
config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version.");
}
config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
}
config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the delay when writing to the Memory Card.");
}
config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Make text scroll immediately by holding B.");
}
config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents Link from playing a struggle animation\n"
"when grabbing ledges or climbing on vines.");
}
config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full.");
}
config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Link won't recoil when his sword hits walls.");
}
config::ImGuiCheckbox("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Only need to fish once for Sera's cat to return.");
}
config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save.");
}
config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Skip the warning screen shown when starting the game.");
}
config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day.");
}
config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
}
ImGui::SeparatorText("Speedrunning");
if (config::ImGuiCheckbox("Speedrun Mode", getSettings().game.speedrunMode)) {
resetForSpeedrunMode();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables Speedrunning options, while restricting certain gameplay modifiers.");
}
ImGui::BeginDisabled(!getSettings().game.speedrunMode);
bool prevLiveSplit = getSettings().game.liveSplitEnabled;
config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834.");
}
ImGui::EndDisabled();
if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) {
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit();
} else {
dusk::speedrun::disconnectLiveSplit();
DuskToast("LiveSplit disconnected", 3.f);
}
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawCheatsMenu() {
if (ImGui::BeginMenu("Cheats")) {
ImGui::BeginDisabled(getSettings().game.speedrunMode);
ImGui::SeparatorText("Resources");
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs);
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
config::ImGuiCheckbox("No Item Timer", getSettings().game.enableIndefiniteItemDrops);
ImGui::SetItemTooltip("Item drops such as Rupees, Hearts, etc. will never disappear after they drop.");
ImGui::SeparatorText("Abilities");
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot);
config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin);
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Allows you to transform even if NPCs are looking.");
}
config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Speeds up Spinner movement when holding R.");
}
config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Makes the magic armor work without rupees.");
}
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawAudioMenu() {
if (ImGui::BeginMenu("Audio")) {
ImGui::SeparatorText("Volume");
ImGui::Text("Master Volume");
if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) {
dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f);
}
/*
// TODO: Implement additional settings
ImGui::Text("Main Music Volume");
ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100);
ImGui::Text("Sub Music Volume");
ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100);
ImGui::Text("Sound Effects Volume");
ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100);
ImGui::Text("Fanfare Volume");
ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100);
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
if (audioMgr != nullptr) {
}
*/
ImGui::SeparatorText("Effects");
if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) {
dusk::audio::SetEnableReverb(getSettings().audio.enableReverb);
}
ImGui::SeparatorText("Tweaks");
config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Disable the beeping sound when having low health.");
}
config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing.");
}
ImGui::EndMenu();
}
}
void ImGuiMenuGame::drawInputMenu() {
if (ImGui::BeginMenu("Input")) {
ImGui::SeparatorText("Controller");
if (ImGui::Button("Configure Controller")){ if (ImGui::Button("Configure Controller")){
m_showControllerConfig = !m_showControllerConfig; m_showControllerConfig = !m_showControllerConfig;
} }
ImGui::SeparatorText("Camera");
config::ImGuiCheckbox("Free Camera", getSettings().game.freeCamera);
if (getSettings().game.freeCamera) {
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis);
config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f");
} else {
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
}
ImGui::SeparatorText("Gyro");
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers\n"
"while in look mode (C-Up) and while aiming the\n"
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n"
"Ball and Chain, and Dominion Rod.");
}
config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n"
"tilt the Rollgoal table in Hena's Cabin.");
}
if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f");
config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f");
if (getSettings().game.enableGyroRollgoal) {
config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Additional multiplier for scaling how strongly\n"
"the gyroscope affects the Rollgoal table.");
}
}
config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n"
"reducing drift and jitter when the controller is still.");
}
config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Low values track raw gyro input more closely,\n"
"while higher values smooth out input over time.");
}
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch);
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw);
}
ImGui::SeparatorText("Tools");
ImGui::BeginDisabled(getSettings().game.speedrunMode);
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
}
ImGui::EndDisabled();
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
ImGui::EndMenu(); ImGui::EndMenu();
} }
} }
void ImGuiMenuGame::drawInterfaceMenu() {
if (ImGui::BeginMenu("Interface")) {
config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications);
config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI);
config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation);
#if DUSK_ENABLE_SENTRY_NATIVE
config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting);
#endif
if (!IsMobile) {
config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost);
}
ImGui::EndMenu();
}
}
static void drawVirtualStick(const char* id, const ImVec2& stick) { static void drawVirtualStick(const char* id, const ImVec2& stick) {
float scale = ImGuiScale(); float scale = ImGuiScale();
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10)); ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
@@ -1059,6 +583,7 @@ namespace dusk {
getSettings().game.damageMultiplier.setValue(1); getSettings().game.damageMultiplier.setValue(1);
getSettings().game.instantDeath.setValue(false); getSettings().game.instantDeath.setValue(false);
getSettings().game.noHeartDrops.setValue(false); getSettings().game.noHeartDrops.setValue(false);
getSettings().game.hyperEnemies.setValue(false);
getSettings().game.infiniteHearts.setValue(false); getSettings().game.infiniteHearts.setValue(false);
getSettings().game.infiniteArrows.setValue(false); getSettings().game.infiniteArrows.setValue(false);
-7
View File
@@ -55,13 +55,6 @@ namespace dusk {
static void resetForSpeedrunMode(); static void resetForSpeedrunMode();
private: private:
void drawAudioMenu();
void drawInputMenu();
void drawGraphicsMenu();
void drawGameplayMenu();
void drawCheatsMenu();
void drawInterfaceMenu();
struct { struct {
int m_selectedPort = 0; int m_selectedPort = 0;
bool m_isReading = false; bool m_isReading = false;
+5
View File
@@ -41,6 +41,10 @@ static void OpenDataFolder() {
#define DUSK_CAN_OPEN_DATA_FOLDER 0 #define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif #endif
namespace aurora::gx {
extern bool enableLodBias;
}
namespace dusk { namespace dusk {
ImGuiMenuTools::ImGuiMenuTools() {} ImGuiMenuTools::ImGuiMenuTools() {}
@@ -91,6 +95,7 @@ namespace dusk {
getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction);
config::Save(); config::Save();
} }
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
ImGui::EndMenu(); ImGui::EndMenu();
} }
+336 -75
View File
@@ -13,6 +13,7 @@
#include "d/actor/d_a_player.h" #include "d/actor/d_a_player.h"
#include <map> #include <map>
#include <bit>
namespace dusk { namespace dusk {
enum ItemType { enum ItemType {
@@ -1295,8 +1296,33 @@ namespace dusk {
membit.offDungeonItem(flag); membit.offDungeonItem(flag);
} }
} }
static void genCommonAreaFlags(dSv_memBit_c& membit) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f);
void genMembitFlags(const char* id, dSv_memBit_c& membit) { genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS);
genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO);
genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY);
genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE);
int keyTemp = membit.getKeyNum();
if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) {
membit.setKeyNum(keyTemp);
}
}
static void genMembitFlags(const char* id, dSv_memBit_c& membit) {
ImGuiBeginGroupPanel("Chest", { 100, 100 }); ImGuiBeginGroupPanel("Chest", { 100, 100 });
for (int j = 0; j < 2; j++) { for (int j = 0; j < 2; j++) {
drawFlagList(fmt::format("##_tbox{}", j).c_str(), membit.mTbox[j]); drawFlagList(fmt::format("##_tbox{}", j).c_str(), membit.mTbox[j]);
@@ -1322,29 +1348,10 @@ namespace dusk {
drawFlagList(fmt::format("##_item{}", j).c_str(), membit.mItem[j]); drawFlagList(fmt::format("##_item{}", j).c_str(), membit.mItem[j]);
} }
ImGuiEndGroupPanel(); ImGuiEndGroupPanel();
ImVec2 post_item_custor = ImGui::GetCursorPos();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); ImGui::SetCursorPos({post_item_custor.x, post_switch_cursor.y});
// genCommonAreaFlags(membit);
genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS);
genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO);
genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY);
genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2);
ImGui::SameLine(230.0f);
genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE);
int keyTemp = membit.getKeyNum();
if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) {
membit.setKeyNum(keyTemp);
}
} }
template <typename T> template <typename T>
@@ -1392,74 +1399,326 @@ namespace dusk {
} }
} }
void ImGuiSaveEditor::drawFlagsTab() {
if (ImGui::TreeNode("Current Region Flags")) { static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) {
dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; constexpr auto makeMask = [](uint8_t size) -> uint16_t { return (1 << size) - 1; };
genMembitFlags("##TempSceneFlags", membit); constexpr auto getByteIndexFromFlag = [](uint16_t f) -> uint8_t { return f >> 8; };
constexpr auto getBitMaskFromFlag = [](uint16_t f) -> uint8_t { return f & 0xff; };
constexpr auto getValueSize = [getBitMaskFromFlag](uint16_t f) -> uint8_t {
return std::popcount(getBitMaskFromFlag(f));
};
constexpr auto makeEventFlag = [](uint8_t byteIndex, uint8_t bitIndices) -> uint16_t {
return (byteIndex << 8) | bitIndices;
};
stage_stag_info_class* pstag = dComIfGp_getStageStagInfo(); const auto eventFlagToAreaFlag = [&](uint16_t areaFlag) -> int {
if (pstag != nullptr) { auto byteInd = getByteIndexFromFlag(areaFlag);
int stageNo = dStage_stagInfo_GetSaveTbl(pstag); constexpr size_t areaIndexSize = 5;
if (ImGui::Button("Save##SaveTempFlags")) { // if we're looking at 0x580, that would be byte 5, and check if 0x80 is set on that byte
dComIfGs_putSave(stageNo); // 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;
};
const auto SetFlag = [&](uint16_t flag, bool set) -> void {
const auto byteIndex = getByteIndexFromFlag(flag);
if (set) {
if (byteIndex < validTbox) {
membit.onTbox(eventFlagToAreaFlag(flag - tboxConvert));
} else if (byteIndex < validSwitch) {
membit.onSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
membit.onItem(eventFlagToAreaFlag(flag - itemConvert));
} }
} else {
ImGui::SameLine(); if (byteIndex < validTbox) {
membit.offTbox(eventFlagToAreaFlag(flag - tboxConvert));
if (ImGui::Button("Load##LoadSaveFlags")) { } else if (byteIndex < validSwitch) {
dComIfGs_getSave(stageNo); membit.offSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
membit.offItem(eventFlagToAreaFlag(flag - itemConvert));
} }
} }
};
const auto LoadMultiByteFlag = [&](uint16_t flag) -> uint8_t {
const auto bitInds = getBitMaskFromFlag(flag);
const auto byteIndex = getByteIndexFromFlag(flag);
const uint16_t startingMask = std::bit_floor(bitInds);
uint8_t val = 0;
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
bitIndexMask >>= 1)
{
val <<= 1;
if (LoadFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask))) {
val |= 1;
}
}
return val;
};
const auto SetMultiByteFlag = [&](uint16_t flag, uint8_t val) -> void {
const auto bitInds = getBitMaskFromFlag(flag);
const auto byteIndex = getByteIndexFromFlag(flag);
const uint16_t startingMask = std::bit_floor(bitInds);
uint16_t valueMask = 1 << (getValueSize(flag) - 1);
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
bitIndexMask >>= 1, valueMask >>= 1)
{
SetFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask), (val & valueMask) != 0);
}
};
const auto LoadSpreadMultiByte = [&](uint16_t high, uint16_t low) -> uint8_t {
if (low == AREA_FLAG_NONE)
return LoadMultiByteFlag(high);
return (LoadMultiByteFlag(high) << getValueSize(low)) | LoadMultiByteFlag(low);
};
const auto SetSpreadMultiByte = [&](uint16_t high, uint16_t low, uint8_t value) -> void {
if (low == AREA_FLAG_NONE)
return SetMultiByteFlag(high, value);
const auto lowerSize = getValueSize(low);
SetMultiByteFlag(high, value >> lowerSize);
SetMultiByteFlag(low, value & makeMask(lowerSize));
};
auto iter = imguiAreaFlagLookup.find(areaIndex);
if (iter == imguiAreaFlagLookup.end()) return;
auto& areaFlags = iter->second;
static ImGuiTextFilter filter;
filter.Draw(); // Search bar
ImVec2 flagTableSize = {700, 400};
if (ImGui::BeginTable("Area Flags", 3,
ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX |
ImGuiTableFlags_Sortable,
flagTableSize))
{
ImGui::TableSetupScrollFreeze(0, 1);
constexpr int COLUMN_FLAG = 0, COLUMN_BIT = 1, COLUMN_DESC = 2;
ImGui::TableSetupColumn("Flag");
ImGui::TableSetupColumn("Byte:Bit");
ImGui::TableSetupColumn("Description");
ImGui::TableHeadersRow();
// if we're sorting by whether the flag is set or not,
// we want to re-sort whenever a flag updates, which means every frame cuz we don't
// know when it changes. otherwise only re-sort when the sort is dirty
if (auto* sort = ImGui::TableGetSortSpecs();
sort != nullptr && sort->SpecsCount > 0 &&
(sort->SpecsDirty || sort->Specs[0].ColumnIndex == COLUMN_FLAG))
{
const auto column = sort->Specs[0].ColumnIndex;
const auto direction = sort->Specs[0].SortDirection;
// 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
if (column == COLUMN_FLAG) {
if (direction == ImGuiSortDirection_Ascending) {
sortByFlags(std::begin(areaFlags.bitFlags), std::end(areaFlags.bitFlags),
LoadFlag);
} else {
sortByFlags(std::rbegin(areaFlags.bitFlags), std::rend(areaFlags.bitFlags),
LoadFlag);
}
} else {
const auto cmp = [column](const EventAreaFlags& l,
const EventAreaFlags& r) -> bool {
switch (column) {
case COLUMN_DESC:
return l.description < r.description;
case COLUMN_BIT:
return l.flagID < r.flagID;
}
return false;
};
if (direction == ImGuiSortDirection_Ascending) {
std::sort(std::begin(areaFlags.bitFlags), std::end(areaFlags.bitFlags),
cmp);
} else {
std::sort(std::rbegin(areaFlags.bitFlags), std::rend(areaFlags.bitFlags),
cmp);
}
}
sort->SpecsDirty = false;
}
for (const auto& e : areaFlags.bitFlags) {
std::string formattedBitLocation =
fmt::format("{0:02X}:{1:02X}", e.byteIndex, e.bitIndex);
if (!filter.PassFilter(e.description.c_str()) &&
!filter.PassFilter(formattedBitLocation.c_str()))
{
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
bool flag = LoadFlag(e.flagID);
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flagID).c_str(), &flag)) {
SetFlag(e.flagID, flag);
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(formattedBitLocation.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(e.description.c_str());
}
ImGui::EndTable();
}
for (const auto& multiByteFlag : areaFlags.multibyteFlags) {
auto flagValue = LoadSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag);
const char* currentVal = "UNKNOWN";
auto enumValIter = multiByteFlag.enumValues.find(flagValue);
if (enumValIter != multiByteFlag.enumValues.end()) {
currentVal = enumValIter->second;
}
if (ImGui::BeginCombo(multiByteFlag.name, currentVal)) {
for (const auto& [val, name] : multiByteFlag.enumValues) {
if (ImGui::Selectable(name)) {
SetSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag, val);
}
}
ImGui::EndCombo();
}
}
genCommonAreaFlags(membit);
}
static void drawCurrentRegionFlags()
{
dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit;
auto* stageData = dComIfGp_getStageStagInfo();
if (!stageData)
return;
uint8_t stageIndex = dStage_stagInfo_GetSaveTbl(stageData);
genAreaFlagTable(stageIndex, membit);
if (ImGui::TreeNode("Flag Matrix")) {
genMembitFlags("##TempSceneFlags", membit);
ImGui::TreePop();
}
stage_stag_info_class* pstag = dComIfGp_getStageStagInfo();
if (pstag != nullptr) {
int stageNo = dStage_stagInfo_GetSaveTbl(pstag);
if (ImGui::Button("Save##SaveTempFlags")) {
dComIfGs_putSave(stageNo);
}
ImGui::SameLine();
if (ImGui::Button("Load##LoadSaveFlags")) {
dComIfGs_getSave(stageNo);
}
}
}
void ImGuiSaveEditor::drawFlagsTab() {
if (ImGui::TreeNode("Current Region Flags")) {
drawCurrentRegionFlags();
ImGui::TreePop(); ImGui::TreePop();
} }
if (ImGui::TreeNode("Region Saved Flags")) { if (ImGui::TreeNode("Region Saved Flags")) {
static std::array<const char*, 27> regionNames = { static const std::map<uint8_t, const char*> regionNames = {
"Ordon", { 0x00, "Ordon" },
"Hyrule Sewers", { 0x01, "Hyrule Sewers" },
"Faron", { 0x02, "Faron" },
"Eldin", { 0x03, "Eldin" },
"Lanayru", { 0x04, "Lanayru" },
"Reserved", { 0x06, "Hyrule Field" },
"Hyrule Field", { 0x07, "Sacred Grove" },
"Sacred Grove", { 0x08, "Snowpeak" },
"Snowpeak", { 0x09, "Castle Town" },
"Castle Town", { 0x0A, "Gerudo Desert" },
"Gerudo Desert", { 0x0B, "Fishing Pond" },
"Fishing Pond", { 0x10, "Forest Temple" },
"Reserved", { 0x11, "Goron Mines" },
"Reserved", { 0x12, "Lakebed Temple" },
"Reserved", { 0x13, "Arbiter's Grounds" },
"Reserved", { 0x14, "Snowpeak Ruins" },
"Forest Temple", { 0x15, "Temple of Time" },
"Goron Mines", { 0x16, "City in the Sky" },
"Lakebed Temple", { 0x17, "Palace of Twilight" },
"Arbiter's Grounds", { 0x18, "Hyrule Castle" },
"Snowpeak Ruins", { 0x19, "Caves" },
"Temple of Time", { 0x1A, "Lake Hylia Long Cave"},
"City in the Sky", { 0x1B, "Grottos" }
"Palace of Twilight",
"Hyrule Castle",
"Caves",
"Grottos",
}; };
if (ImGui::BeginCombo("Region", regionNames[m_selectedRegion])) { if (m_selectedRegion.name == nullptr)
for (int i = 0; i < regionNames.size(); i++) { {
if (strcmp(regionNames[i], "Reserved") == 0) continue; const auto& firstRegion = *regionNames.find(0);
m_selectedRegion = { firstRegion.first, firstRegion.second };
}
if (ImGui::Selectable(regionNames[i])) { if (ImGui::BeginCombo("Region", m_selectedRegion.name)) {
m_selectedRegion = i; for (const auto& [id, name] : regionNames) {
if (ImGui::Selectable(name)) {
m_selectedRegion = {id, name};
} }
} }
ImGui::EndCombo(); ImGui::EndCombo();
} }
dSv_memBit_c* membit = &dComIfGs_getSaveData()->mSave[m_selectedRegion].mBit; dSv_memBit_c& membit = dComIfGs_getSaveData()->mSave[m_selectedRegion.id].mBit;
if (membit != nullptr) {
genMembitFlags("##SaveSceneFlags", *membit); genAreaFlagTable(m_selectedRegion.id, membit);
if (ImGui::TreeNode("Flag Matrix")) {
genMembitFlags("##SaveSceneFlags", membit);
ImGui::TreePop();
} }
ImGui::TreePop(); ImGui::TreePop();
@@ -1530,7 +1789,9 @@ namespace dusk {
} }
for (const auto& e : duskImguiEventFlags) { for (const auto& e : duskImguiEventFlags) {
if (!filter.PassFilter((e.location + "\n" + e.description + "\n" + e.flagName).c_str())) if (!filter.PassFilter(e.location.c_str()) &&
!filter.PassFilter(e.description.c_str()) &&
!filter.PassFilter(e.flagName.c_str()))
{ {
continue; continue;
} }
+4 -1
View File
@@ -21,7 +21,10 @@ namespace dusk {
void drawConfigTab(); void drawConfigTab();
private: private:
int m_selectedRegion = 0; struct {
uint8_t id;
const char* name;
} m_selectedRegion = {0, nullptr};
}; };
} }
+4
View File
@@ -101,6 +101,10 @@ ValidationError validate(const char* path) {
NodHandleWrapper disc; NodHandleWrapper disc;
const auto sdlStream = SDL_IOFromFile(path, "rb"); const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return ValidationError::IOError;
}
const NodDiscStream nod_stream { const NodDiscStream nod_stream {
.user_data = sdlStream, .user_data = sdlStream,
.read_at = StreamReadAt, .read_at = StreamReadAt,
+48 -21
View File
@@ -1,5 +1,6 @@
#include "dusk/logging.h" #include "dusk/logging.h"
#include <array> #include <array>
#include <atomic>
#include <chrono> #include <chrono>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
@@ -32,9 +33,33 @@ static constexpr std::string_view StubFragments[] = {
}; };
namespace { namespace {
std::mutex g_logMutex; // On macOS, std::mutex becomes poisoned when its dtor is run.
FILE* g_logFile = nullptr; // We use this to check if the LogState is destroyed before attempting to acquire it.
std::string g_logFilePath; std::atomic g_logStateAlive(true);
struct LogState {
std::mutex mutex;
FILE* file = nullptr;
std::string filePath;
~LogState() {
CloseFile();
g_logStateAlive.store(false, std::memory_order_release);
}
void CloseFile() {
if (!g_logStateAlive.load(std::memory_order_acquire)) {
return;
}
std::lock_guard lock(mutex);
if (file != nullptr) {
std::fflush(file);
std::fclose(file);
file = nullptr;
}
}
};
LogState g_logState;
const char* LogLevelString(AuroraLogLevel level) { const char* LogLevelString(AuroraLogLevel level) {
switch (level) { switch (level) {
@@ -152,10 +177,10 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
FILE* out = LogStreamForLevel(level); FILE* out = LogStreamForLevel(level);
WriteLogLine(out, levelStr, module, message, len); WriteLogLine(out, levelStr, module, message, len);
{ if (g_logStateAlive.load(std::memory_order_acquire)) {
std::lock_guard lock(g_logMutex); std::lock_guard lock(g_logState.mutex);
if (g_logFile != nullptr) { if (g_logState.file != nullptr) {
WriteLogLine(g_logFile, levelStr, module, message, len); WriteLogLine(g_logState.file, levelStr, module, message, len);
} }
} }
@@ -169,8 +194,11 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
aurora::Module DuskLog("dusk"); aurora::Module DuskLog("dusk");
void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel) { void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel) {
std::lock_guard lock(g_logMutex); if (!g_logStateAlive.load(std::memory_order_acquire)) {
if (g_logFile != nullptr || configDir.empty()) { return;
}
std::lock_guard lock(g_logState.mutex);
if (g_logState.file != nullptr || configDir.empty()) {
return; return;
} }
@@ -184,31 +212,30 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL
} }
const std::filesystem::path logPath = logsDir / MakeTimestampedLogName(); const std::filesystem::path logPath = logsDir / MakeTimestampedLogName();
g_logFile = std::fopen(logPath.string().c_str(), "wb"); g_logState.file = std::fopen(logPath.string().c_str(), "wb");
if (g_logFile == nullptr) { if (g_logState.file == nullptr) {
std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n",
logPath.string().c_str()); logPath.string().c_str());
return; return;
} }
g_logFilePath = logPath.string(); g_logState.filePath = logPath.string();
aurora::g_config.logCallback = &aurora_log_callback; aurora::g_config.logCallback = &aurora_log_callback;
aurora::g_config.logLevel = logLevel; aurora::g_config.logLevel = logLevel;
WriteLogLine(g_logFile, "INFO", "dusk", "File logging initialized", 24); WriteLogLine(g_logState.file, "INFO", "dusk", "File logging initialized", 24);
} }
void dusk::ShutdownFileLogging() { void dusk::ShutdownFileLogging() {
std::lock_guard lock(g_logMutex); if (!g_logStateAlive.load(std::memory_order_acquire)) {
if (g_logFile == nullptr) {
return; return;
} }
g_logState.CloseFile();
std::fflush(g_logFile);
std::fclose(g_logFile);
g_logFile = nullptr;
} }
const char* dusk::GetLogFilePath() { const char* dusk::GetLogFilePath() {
std::lock_guard lock(g_logMutex); if (!g_logStateAlive.load(std::memory_order_acquire)) {
return g_logFilePath.empty() ? nullptr : g_logFilePath.c_str(); return nullptr;
}
std::lock_guard lock(g_logState.mutex);
return g_logState.filePath.empty() ? nullptr : g_logState.filePath.c_str();
} }
+8 -2
View File
@@ -17,6 +17,7 @@ UserSettings g_userSettings = {
.soundEffectsVolume {"audio.soundEffectsVolume", 100}, .soundEffectsVolume {"audio.soundEffectsVolume", 100},
.fanfareVolume {"audio.fanfareVolume", 100}, .fanfareVolume {"audio.fanfareVolume", 100},
.enableReverb {"audio.enableReverb", true}, .enableReverb {"audio.enableReverb", true},
.enableHrtf {"audio.enableHrtf", false},
}, },
.game = { .game = {
@@ -31,7 +32,8 @@ UserSettings g_userSettings = {
.disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false},
.noSwordRecoil {"game.noSwordRecoil", false}, .noSwordRecoil {"game.noSwordRecoil", false},
.damageMultiplier {"game.damageMultiplier", 1}, .damageMultiplier {"game.damageMultiplier", 1},
.noHeartDrops{"game.noHeartDrops", false}, .hyperEnemies {"game.hyperEnemies", false},
.noHeartDrops {"game.noHeartDrops", false},
.instantDeath {"game.instantDeath", false}, .instantDeath {"game.instantDeath", false},
.fastClimbing {"game.fastClimbing", false}, .fastClimbing {"game.fastClimbing", false},
.noMissClimbing {"game.noMissClimbing", false}, .noMissClimbing {"game.noMissClimbing", false},
@@ -40,6 +42,7 @@ UserSettings g_userSettings = {
.instantSaves {"game.instantSaves", false}, .instantSaves {"game.instantSaves", false},
.instantText {"game.instantText", false}, .instantText {"game.instantText", false},
.sunsSong {"game.sunsSong", false}, .sunsSong {"game.sunsSong", false},
.autoSave {"game.autoSave", false},
// Preferences // Preferences
.enableMirrorMode {"game.enableMirrorMode", false}, .enableMirrorMode {"game.enableMirrorMode", false},
@@ -52,7 +55,7 @@ UserSettings g_userSettings = {
.bloomMode {"game.bloomMode", BloomMode::Classic}, .bloomMode {"game.bloomMode", BloomMode::Classic},
.bloomMultiplier {"game.bloomMultiplier", 1.0f}, .bloomMultiplier {"game.bloomMultiplier", 1.0f},
.disableWaterRefraction {"game.disableWaterRefraction", false}, .disableWaterRefraction {"game.disableWaterRefraction", false},
.enableFrameInterpolation = {"game.enableFrameInterpolation", false}, .enableFrameInterpolation {"game.enableFrameInterpolation", false},
.internalResolutionScale {"game.internalResolutionScale", 0}, .internalResolutionScale {"game.internalResolutionScale", 0},
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
.enableDepthOfField {"game.enableDepthOfField", true}, .enableDepthOfField {"game.enableDepthOfField", true},
@@ -133,6 +136,7 @@ void registerSettings() {
Register(g_userSettings.audio.soundEffectsVolume); Register(g_userSettings.audio.soundEffectsVolume);
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);
// Game // Game
Register(g_userSettings.game.language); Register(g_userSettings.game.language);
@@ -144,6 +148,7 @@ void registerSettings() {
Register(g_userSettings.game.disableRupeeCutscenes); Register(g_userSettings.game.disableRupeeCutscenes);
Register(g_userSettings.game.noSwordRecoil); Register(g_userSettings.game.noSwordRecoil);
Register(g_userSettings.game.damageMultiplier); Register(g_userSettings.game.damageMultiplier);
Register(g_userSettings.game.hyperEnemies);
Register(g_userSettings.game.noHeartDrops); Register(g_userSettings.game.noHeartDrops);
Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.instantDeath);
Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastClimbing);
@@ -152,6 +157,7 @@ void registerSettings() {
Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.instantSaves);
Register(g_userSettings.game.instantText); Register(g_userSettings.game.instantText);
Register(g_userSettings.game.sunsSong); Register(g_userSettings.game.sunsSong);
Register(g_userSettings.game.autoSave);
Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.enableMirrorMode);
Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.invertCameraXAxis);
Register(g_userSettings.game.invertCameraYAxis); Register(g_userSettings.game.invertCameraYAxis);
+29
View File
@@ -0,0 +1,29 @@
#include "bool_button.hpp"
namespace dusk::ui {
BoolButton::BoolButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, {std::move(props.key)}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
mIsDisabled(std::move(props.isDisabled)) {}
bool BoolButton::disabled() const {
if (mIsDisabled) {
return mIsDisabled();
}
return BaseControlledSelectButton::disabled();
}
Rml::String BoolButton::format_value() {
return mGetValue() ? "On" : "Off";
}
bool BoolButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
mSetValue(!mGetValue());
return true;
}
return false;
}
} // namespace dusk::ui
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "select_button.hpp"
namespace dusk::ui {
class BoolButton : public BaseControlledSelectButton {
public:
struct Props {
Rml::String key;
std::function<bool()> getValue;
std::function<void(bool)> setValue;
std::function<bool()> isDisabled;
};
BoolButton(Rml::Element* parent, Props props);
bool disabled() const override;
protected:
Rml::String format_value() override;
bool handle_nav_command(NavCommand cmd) override;
private:
std::function<int()> mGetValue;
std::function<void(int)> mSetValue;
std::function<bool()> mIsDisabled;
};
} // namespace dusk::ui
+64
View File
@@ -0,0 +1,64 @@
#include "button.hpp"
#include "ui.hpp"
#include <utility>
namespace dusk::ui {
namespace {
Rml::Element* createRoot(Rml::Element* parent, const Rml::String& tagName) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement(tagName);
return parent->AppendChild(std::move(elem));
}
} // namespace
Button::Button(Rml::Element* parent, Props props, const Rml::String& tagName)
: FluentComponent(createRoot(parent, tagName)) {
update_props(std::move(props));
}
void Button::set_text(const Rml::String& text) {
if (mProps.text != text) {
mRoot->SetInnerRML(escape(text));
mProps.text = text;
}
}
Button& Button::on_pressed(ButtonCallback callback) {
if (!callback) {
return *this;
}
// TODO: convert this to a FluentComponent method?
on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
callback();
return true;
}
return false;
});
return *this;
}
void Button::update_props(Props props) {
set_text(props.text);
mProps = std::move(props);
}
void ControlledButton::update() {
if (mIsSelected) {
set_selected(mIsSelected());
}
Button::update();
}
bool ControlledButton::selected() const {
if (mIsSelected) {
return mIsSelected();
}
return Button::selected();
}
} // namespace dusk::ui
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "component.hpp"
namespace dusk::ui {
using ButtonCallback = std::function<void()>;
class Button : public FluentComponent<Button> {
public:
struct Props {
Rml::String text;
};
Button(Rml::Element* parent, Props props, const Rml::String& tagName = "button");
Button(Rml::Element* parent, Rml::String text, const Rml::String& tagName = "button")
: Button(parent, Props{std::move(text)}, tagName) {}
void set_text(const Rml::String& text);
Button& on_pressed(ButtonCallback callback);
const Rml::String& get_text() const { return mProps.text; }
private:
void update_props(Props props);
Props mProps;
};
class ControlledButton : public Button {
public:
struct Props {
Rml::String text;
std::function<bool()> isSelected;
};
ControlledButton(Rml::Element* parent, Props props, const Rml::String& tagName = "button")
: Button(parent, {std::move(props.text)}, tagName),
mIsSelected(std::move(props.isSelected)) {}
void update() override;
bool selected() const override;
private:
std::function<bool()> mIsSelected;
};
} // namespace dusk::ui
+97
View File
@@ -0,0 +1,97 @@
#include "component.hpp"
namespace dusk::ui {
Component::Component(Rml::Element* root) : mRoot(root) {}
Component::~Component() = default;
void Component::update() {
for (const auto& child : mChildren) {
child->update();
}
}
bool Component::focus() {
if (disabled()) {
return false;
}
// Can we focus self?
if (mRoot->Focus(true)) {
mRoot->ScrollIntoView(Rml::ScrollIntoViewOptions{
Rml::ScrollAlignment::Center,
Rml::ScrollAlignment::Center,
Rml::ScrollBehavior::Smooth,
Rml::ScrollParentage::Closest,
});
return true;
}
// Otherwise, try to focus a child
for (const auto& child : mChildren) {
if (child->focus()) {
return true;
}
}
return false;
}
void Component::set_selected(bool value) {
// Subclasses may override selected() to return a dynamic value, but
// we're only interested in if the pseudoclass is set or not, so we
// use Component::selected() directly rather than selected().
if (Component::selected() == value) {
return;
}
mRoot->SetPseudoClass("selected", value);
}
void Component::set_disabled(bool value) {
if (Component::disabled() == value) {
return;
}
if (value) {
mRoot->SetAttribute("disabled", "");
mRoot->SetPseudoClass("disabled", true);
mRoot->Blur();
} else {
mRoot->RemoveAttribute("disabled");
mRoot->SetPseudoClass("disabled", false);
}
}
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,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
element = mRoot;
}
mListeners.emplace_back(
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
bool Component::contains(Rml::Element* element) const {
for (const auto* node = element; node != nullptr; node = node->GetParentNode()) {
if (node == mRoot) {
return true;
}
}
return false;
}
void Component::clear_children() {
mChildren.clear();
while (mRoot->GetNumChildren() > 0) {
mRoot->RemoveChild(mRoot->GetFirstChild());
}
}
} // namespace dusk::ui
+97
View File
@@ -0,0 +1,97 @@
#pragma once
#include "event.hpp"
#include "ui.hpp"
#include <RmlUi/Core.h>
#include <memory>
#include <utility>
#include <vector>
namespace Rml {
class Element;
}
namespace dusk::ui {
class Component {
public:
Component() = default;
explicit Component(Rml::Element* root);
virtual ~Component();
Component(const Component&) = delete;
Component& operator=(const Component&) = delete;
virtual void update();
virtual bool focus();
virtual bool selected() const { return mRoot->IsPseudoClassSet("selected"); }
virtual void set_selected(bool selected);
virtual bool disabled() const { return mRoot->IsPseudoClassSet("disabled"); }
virtual void set_disabled(bool disabled);
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
bool capture = false);
bool contains(Rml::Element* element) const;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_child(Args&&... args) {
auto child = std::make_unique<T>(mRoot, std::forward<Args>(args)...);
T& ref = *child;
mChildren.emplace_back(std::move(child));
return ref;
}
Rml::Element* root() const { return mRoot; }
protected:
static Rml::Element* append(Rml::Element* parent, const Rml::String& tag);
void clear_children();
Rml::Element* mRoot = nullptr;
std::vector<std::unique_ptr<Component> > mChildren;
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
};
template <class Derived>
class FluentComponent : public Component {
public:
using Component::Component;
Derived& listen(
Rml::EventId event, ScopedEventListener::Callback callback, bool capture = false) {
Component::listen(mRoot, event, std::move(callback), capture);
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) {
listen(Rml::EventId::Click, [this, callback](Rml::Event& event) {
if (!disabled() && callback(event, NavCommand::Confirm)) {
event.StopPropagation();
}
});
listen(Rml::EventId::Keydown, [this, callback = std::move(callback)](Rml::Event& event) {
if (disabled()) {
return;
}
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && callback(event, cmd)) {
event.StopPropagation();
}
});
return static_cast<Derived&>(*this);
}
};
} // namespace dusk::ui
+109
View File
@@ -0,0 +1,109 @@
#include "document.hpp"
#include "aurora/rmlui.hpp"
#include "ui.hpp"
namespace dusk::ui {
namespace {
Rml::ElementDocument* load_document(const Rml::String& source) {
auto* context = aurora::rmlui::get_context();
if (context == nullptr) {
return nullptr;
}
return context->LoadDocumentFromMemory(source);
}
} // namespace
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
// Block events while hidden (except for Menu command)
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::Menu && !visible()) {
event.StopImmediatePropagation();
}
},
true);
const auto blockUnlessVisible = [this](Rml::Event& event) {
if (!visible()) {
event.StopImmediatePropagation();
}
};
listen(Rml::EventId::Mouseover, blockUnlessVisible, true);
listen(Rml::EventId::Click, blockUnlessVisible, true);
listen(Rml::EventId::Scroll, blockUnlessVisible, true);
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(event, cmd)) {
event.StopPropagation();
}
});
}
Document::~Document() {
mListeners.clear();
if (mDocument != nullptr) {
mDocument->Close();
mDocument = nullptr;
}
}
void Document::show() {
if (mDocument != nullptr) {
// Attempt to preserve the previously focused element
mDocument->Show(Rml::ModalFlag::None, Rml::FocusFlag::Keep, Rml::ScrollFlag::None);
// If nothing is focused, let the document decide the initial focus
auto* leaf = mDocument->GetFocusLeafNode();
if (leaf == nullptr || leaf == mDocument) {
focus();
}
}
}
void Document::hide(bool close) {
if (mDocument != nullptr) {
mDocument->Hide();
}
if (close) {
mClosed = true;
}
}
void Document::update() {}
bool Document::focus() {
return false;
}
void Document::listen(Rml::Element* element, Rml::EventId event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
element = mDocument;
}
if (element == nullptr || !callback) {
return;
}
mListeners.emplace_back(
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
bool Document::visible() const {
if (mDocument == nullptr) {
return false;
}
return *mDocument->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible;
}
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) {
toggle();
return true;
}
return false;
}
} // namespace dusk::ui
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "component.hpp"
#include "ui.hpp"
namespace dusk::ui {
class Document {
public:
Document(const Rml::String& source);
virtual ~Document();
Document(const Document&) = delete;
Document& operator=(const Document&) = delete;
virtual void show();
virtual void hide(bool close);
virtual void update();
virtual bool focus();
virtual bool visible() const;
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
bool capture = false);
void listen(Rml::EventId event, ScopedEventListener::Callback callback, bool capture = false) {
listen(mDocument, event, std::move(callback), capture);
}
void toggle() {
if (visible()) {
hide(false);
} else {
show();
}
}
void pop() {
hide(true);
show_top_document();
}
bool pending_close() const { return mPendingClose; }
bool closed() const { return mClosed; }
protected:
virtual bool handle_nav_command(Rml::Event& event, NavCommand cmd);
Rml::ElementDocument* mDocument;
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
bool mPendingClose = false;
bool mClosed = false;
};
} // namespace dusk::ui
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "window.hpp"
namespace dusk::ui {
class EditorWindow : public Window {
public:
EditorWindow();
};
} // namespace dusk::ui
+32
View File
@@ -0,0 +1,32 @@
#include "event.hpp"
#include <utility>
namespace dusk::ui {
ScopedEventListener::ScopedEventListener(
Rml::Element* element, Rml::EventId event, Callback callback, bool capture)
: mElement(element), mEvent(event), mCapture(capture), mCallback(std::move(callback)) {
mElement->AddEventListener(mEvent, this, mCapture);
}
ScopedEventListener::~ScopedEventListener() {
if (mElement != nullptr) {
mElement->RemoveEventListener(mEvent, this, mCapture);
mElement = nullptr;
}
}
void ScopedEventListener::ProcessEvent(Rml::Event& event) {
if (mCallback) {
mCallback(event);
}
}
void ScopedEventListener::OnDetach(Rml::Element* element) {
if (element == mElement) {
mElement = nullptr;
}
}
} // namespace dusk::ui
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <RmlUi/Core.h>
#include <functional>
namespace dusk::ui {
class ScopedEventListener final : public Rml::EventListener {
public:
using Callback = std::function<void(Rml::Event&)>;
ScopedEventListener(
Rml::Element* element, Rml::EventId event, Callback callback, bool capture = false);
~ScopedEventListener() override;
ScopedEventListener(const ScopedEventListener&) = delete;
ScopedEventListener& operator=(const ScopedEventListener&) = delete;
ScopedEventListener(ScopedEventListener&&) = delete;
ScopedEventListener& operator=(ScopedEventListener&&) = delete;
void ProcessEvent(Rml::Event& event) override;
void OnDetach(Rml::Element* element) override;
private:
Rml::Element* mElement = nullptr;
Rml::EventId mEvent = Rml::EventId::Invalid;
bool mCapture = false;
Callback mCallback;
};
} // namespace dusk::ui
+610
View File
@@ -0,0 +1,610 @@
#include "input.hpp"
#include "ui.hpp"
#include <RmlUi/Core.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_timer.h>
#include <aurora/rmlui.hpp>
#include <dolphin/pad.h>
#include <algorithm>
#include <array>
namespace dusk::ui {
namespace {
constexpr double kGamepadRepeatInitialDelay = 0.32;
constexpr double kGamepadRepeatStartInterval = 0.12;
constexpr double kGamepadRepeatMinInterval = 0.045;
constexpr double kGamepadRepeatRampDuration = 1.0;
constexpr double kGamepadMenuChordGraceDuration = 0.12;
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
struct GamepadRepeatState {
Rml::Input::KeyIdentifier key = Rml::Input::KI_UNKNOWN;
double pressedAt = 0.0;
double nextRepeatAt = 0.0;
bool held = false;
bool repeatable = false;
bool pending = false;
};
bool sPadInputBlocked = false;
std::array<GamepadRepeatState, SDL_GAMEPAD_BUTTON_COUNT> sGamepadButtonRepeats;
std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
double now_seconds() noexcept {
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
}
bool is_menu_chord_part(PADButton button) noexcept {
return button == PAD_TRIGGER_R || button == PAD_BUTTON_START;
}
bool has_menu_chord_part_held(u32 port) noexcept {
if (port >= sPadHoldMasks.size()) {
return false;
}
const u32 held = sPadHoldMasks[port];
return (held & (PAD_TRIGGER_R | PAD_BUTTON_START)) != 0;
}
bool should_block_pad_for_menu_chord() noexcept {
for (u32 port = 0; port < sPadHoldMasks.size(); ++port) {
if (sMenuChordConsumed[port] && has_menu_chord_part_held(port)) {
return true;
}
}
return false;
}
PADButton pad_button_from_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_TRIGGER_R:
return PAD_TRIGGER_R;
case PAD_AXIS_TRIGGER_L:
return PAD_TRIGGER_L;
default:
return 0;
}
}
void set_pad_button_held(u32 port, PADButton button, bool held) noexcept {
if (port >= sPadHoldMasks.size() || button == 0) {
return;
}
if (held) {
sPadHoldMasks[port] |= button;
} else {
sPadHoldMasks[port] &= ~button;
}
}
bool is_menu_chord(u32 port) noexcept {
if (port >= sPadHoldMasks.size()) {
return false;
}
const u32 held = sPadHoldMasks[port];
return (held & PAD_TRIGGER_R) != 0 && (held & PAD_BUTTON_START) != 0;
}
bool any_menu_chord() noexcept {
return std::any_of(sPadHoldMasks.begin(), sPadHoldMasks.end(),
[](u32 held) { return (held & PAD_TRIGGER_R) != 0 && (held & PAD_BUTTON_START) != 0; });
}
Rml::Input::KeyIdentifier map_pad_button(PADButton button) noexcept {
switch (button) {
case PAD_BUTTON_UP:
return Rml::Input::KI_UP;
case PAD_BUTTON_DOWN:
return Rml::Input::KI_DOWN;
case PAD_BUTTON_LEFT:
return Rml::Input::KI_LEFT;
case PAD_BUTTON_RIGHT:
return Rml::Input::KI_RIGHT;
case PAD_BUTTON_B:
return Rml::Input::KI_ESCAPE;
case PAD_BUTTON_A:
return Rml::Input::KI_RETURN;
case PAD_TRIGGER_R:
return Rml::Input::KI_NEXT;
case PAD_TRIGGER_L:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_pad_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_LEFT_X_POS:
return Rml::Input::KI_RIGHT;
case PAD_AXIS_LEFT_X_NEG:
return Rml::Input::KI_LEFT;
case PAD_AXIS_LEFT_Y_POS:
return Rml::Input::KI_UP;
case PAD_AXIS_LEFT_Y_NEG:
return Rml::Input::KI_DOWN;
case PAD_AXIS_TRIGGER_R:
return Rml::Input::KI_NEXT;
case PAD_AXIS_TRIGGER_L:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexcept {
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return Rml::Input::KI_UP;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return Rml::Input::KI_DOWN;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return Rml::Input::KI_LEFT;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return Rml::Input::KI_RIGHT;
case SDL_GAMEPAD_BUTTON_EAST:
return Rml::Input::KI_ESCAPE;
case SDL_GAMEPAD_BUTTON_SOUTH:
return Rml::Input::KI_RETURN;
case SDL_GAMEPAD_BUTTON_BACK:
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_button_alias(SDL_GamepadButton button) noexcept {
switch (button) {
case SDL_GAMEPAD_BUTTON_BACK:
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_gamepad_axis(SDL_GamepadAxis axis, PADAxisSign sign) noexcept {
switch (axis) {
case SDL_GAMEPAD_AXIS_LEFTX:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_RIGHT : Rml::Input::KI_LEFT;
case SDL_GAMEPAD_AXIS_LEFTY:
return sign == AXIS_SIGN_NEGATIVE ? Rml::Input::KI_UP : Rml::Input::KI_DOWN;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_NEXT : Rml::Input::KI_UNKNOWN;
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_PRIOR : Rml::Input::KI_UNKNOWN;
default:
return Rml::Input::KI_UNKNOWN;
}
}
bool find_event_port(SDL_JoystickID instance, u32& port) noexcept {
for (u32 candidate = 0; candidate < PAD_MAX_CONTROLLERS; ++candidate) {
const s32 index = PADGetIndexForPort(candidate);
if (index < 0) {
continue;
}
SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(static_cast<u32>(index));
if (gamepad == nullptr) {
continue;
}
SDL_Joystick* joystick = SDL_GetGamepadJoystick(gamepad);
if (joystick != nullptr && SDL_GetJoystickID(joystick) == instance) {
port = candidate;
return true;
}
}
return false;
}
bool find_mapped_pad_button(u32 port, SDL_GamepadButton nativeButton, PADButton& button) noexcept {
u32 buttonCount = 0;
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
if (buttons != nullptr) {
for (u32 i = 0; i < buttonCount; ++i) {
if (buttons[i].nativeButton == static_cast<u32>(nativeButton)) {
button = buttons[i].padButton;
return true;
}
}
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes != nullptr) {
for (u32 i = 0; i < axisCount; ++i) {
if (axes[i].nativeButton == nativeButton) {
button = pad_button_from_axis(axes[i].padAxis);
return button != 0;
}
}
}
return false;
}
bool find_mapped_pad_axis(
u32 port, SDL_GamepadAxis nativeAxis, PADAxisSign sign, PADAxis& axis) noexcept {
u32 buttonCount = 0;
PADGetButtonMappings(port, &buttonCount);
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
return false;
}
for (u32 i = 0; i < axisCount; ++i) {
const PADSignedNativeAxis mappedAxis = axes[i].nativeAxis;
if (mappedAxis.nativeAxis == nativeAxis && mappedAxis.sign == sign) {
axis = axes[i].padAxis;
return true;
}
}
return false;
}
bool find_event_pad_button(
const SDL_GamepadButtonEvent& event, u32& port, PADButton& button) noexcept {
return find_event_port(event.which, port) &&
find_mapped_pad_button(port, static_cast<SDL_GamepadButton>(event.button), button);
}
Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event) noexcept {
const auto nativeButton = static_cast<SDL_GamepadButton>(event.button);
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK) {
return Rml::Input::KI_F1;
}
u32 port = 0;
if (!find_event_port(event.which, port)) {
return map_raw_gamepad_button(nativeButton);
}
PADButton button = 0;
if (find_mapped_pad_button(port, nativeButton, button)) {
const auto key = map_pad_button(button);
return key == Rml::Input::KI_UNKNOWN ? map_raw_button_alias(nativeButton) : key;
}
return map_raw_button_alias(nativeButton);
}
Rml::Input::KeyIdentifier map_gamepad_axis(
const SDL_GamepadAxisEvent& event, PADAxisSign sign) noexcept {
u32 port = 0;
if (!find_event_port(event.which, port)) {
return map_raw_gamepad_axis(static_cast<SDL_GamepadAxis>(event.axis), sign);
}
PADAxis axis = 0;
if (find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, axis)) {
return map_pad_axis(axis);
}
return Rml::Input::KI_UNKNOWN;
}
bool is_repeatable_key(Rml::Input::KeyIdentifier key) noexcept {
switch (key) {
case Rml::Input::KI_UP:
case Rml::Input::KI_DOWN:
case Rml::Input::KI_LEFT:
case Rml::Input::KI_RIGHT:
case Rml::Input::KI_NEXT:
case Rml::Input::KI_PRIOR:
return true;
default:
return false;
}
}
double repeat_interval(double heldFor) noexcept {
const double ramp = std::clamp(heldFor / kGamepadRepeatRampDuration, 0.0, 1.0);
return kGamepadRepeatStartInterval +
(kGamepadRepeatMinInterval - kGamepadRepeatStartInterval) * ramp;
}
GamepadRepeatState* button_repeat_state(SDL_GamepadButton button) noexcept {
const auto index = static_cast<int>(button);
if (index < 0 || index >= static_cast<int>(sGamepadButtonRepeats.size())) {
return nullptr;
}
return &sGamepadButtonRepeats[index];
}
GamepadRepeatState* axis_repeat_state(SDL_GamepadAxis axis, PADAxisSign sign) noexcept {
const auto axisIndex = static_cast<int>(axis);
if (axisIndex < 0 || axisIndex >= SDL_GAMEPAD_AXIS_COUNT) {
return nullptr;
}
const int directionOffset = sign == AXIS_SIGN_POSITIVE ? 0 : 1;
return &sGamepadAxisRepeats[axisIndex * 2 + directionOffset];
}
void clear_gamepad_repeats() noexcept {
for (auto& repeat : sGamepadButtonRepeats) {
repeat = {};
}
for (auto& repeat : sGamepadAxisRepeats) {
repeat = {};
}
sPadHoldMasks.fill(0);
sMenuChordConsumed.fill(false);
}
void begin_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
if (repeat.held) {
return;
}
const double now = now_seconds();
repeat.key = key;
repeat.pressedAt = now;
repeat.held = true;
repeat.repeatable = is_repeatable_key(key);
repeat.nextRepeatAt = repeat.repeatable ? now + kGamepadRepeatInitialDelay : 0.0;
repeat.pending = false;
}
void begin_pending_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
if (repeat.held) {
return;
}
const double now = now_seconds();
repeat.key = key;
repeat.pressedAt = now;
repeat.held = true;
repeat.repeatable = is_repeatable_key(key);
repeat.nextRepeatAt = 0.0;
repeat.pending = true;
}
void consume_menu_chord(u32 port, Rml::Context& context) noexcept {
if (port < sMenuChordConsumed.size()) {
sMenuChordConsumed[port] = true;
}
auto cancel_next = [&context](GamepadRepeatState& repeat) {
if (!repeat.held || repeat.key != Rml::Input::KI_NEXT) {
return;
}
if (!repeat.pending) {
context.ProcessKeyUp(repeat.key, 0);
}
repeat = {};
};
for (auto& repeat : sGamepadButtonRepeats) {
cancel_next(repeat);
}
for (auto& repeat : sGamepadAxisRepeats) {
cancel_next(repeat);
}
}
void update_menu_chord_release(u32 port) noexcept {
if (port >= sMenuChordConsumed.size() || has_menu_chord_part_held(port)) {
return;
}
sMenuChordConsumed[port] = false;
}
bool should_defer_menu_chord_part(PADButton button, Rml::Input::KeyIdentifier key) noexcept {
return button == PAD_TRIGGER_R && key == Rml::Input::KI_NEXT;
}
void process_axis_direction(
Rml::Context& context, const SDL_GamepadAxisEvent& event, PADAxisSign sign) noexcept {
GamepadRepeatState* repeat = axis_repeat_state(static_cast<SDL_GamepadAxis>(event.axis), sign);
if (repeat == nullptr) {
return;
}
const bool active = sign == AXIS_SIGN_POSITIVE ? event.value >= kGamepadAxisPressThreshold :
event.value <= -kGamepadAxisPressThreshold;
const bool released = sign == AXIS_SIGN_POSITIVE ? event.value <= kGamepadAxisReleaseThreshold :
event.value >= -kGamepadAxisReleaseThreshold;
u32 port = 0;
PADAxis padAxis = 0;
const bool hasPadAxis =
find_event_port(event.which, port) &&
find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, padAxis);
const PADButton heldPadButton = hasPadAxis ? pad_button_from_axis(padAxis) : 0;
if (repeat->held) {
if (released) {
if (!repeat->pending) {
context.ProcessKeyUp(repeat->key, 0);
}
set_pad_button_held(port, heldPadButton, false);
*repeat = {};
update_menu_chord_release(port);
}
return;
}
if (!active) {
return;
}
set_pad_button_held(port, heldPadButton, true);
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port);
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
if (!chorded && should_defer_menu_chord_part(heldPadButton, key)) {
begin_pending_gamepad_key(*repeat, key);
return;
}
begin_gamepad_key(*repeat, key);
context.ProcessMouseLeave();
context.ProcessKeyDown(key, 0);
}
} // namespace
void sync_input_block() noexcept {
const bool shouldBlock = any_document_visible() || should_block_pad_for_menu_chord();
if (sPadInputBlocked == shouldBlock) {
return;
}
PADBlockInput(shouldBlock);
sPadInputBlocked = shouldBlock;
}
void release_input_block() noexcept {
if (!sPadInputBlocked) {
return;
}
PADBlockInput(false);
sPadInputBlocked = false;
}
void reset_input_state() noexcept {
clear_gamepad_repeats();
}
void handle_event(const SDL_Event& event) noexcept {
if (event.type == SDL_EVENT_GAMEPAD_REMOVED || event.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
reset_input_state();
sync_input_block();
return;
}
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
{
return;
}
auto* context = aurora::rmlui::get_context();
if (context == nullptr) {
return;
}
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
sync_input_block();
return;
}
auto* repeat = button_repeat_state(static_cast<SDL_GamepadButton>(event.gbutton.button));
u32 port = 0;
PADButton button = 0;
const bool hasPadButton = find_event_pad_button(event.gbutton, port, button);
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
set_pad_button_held(port, button, true);
const bool chorded = hasPadButton && is_menu_chord_part(button) && is_menu_chord(port);
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
if (!chorded && should_defer_menu_chord_part(button, key)) {
begin_pending_gamepad_key(*repeat, key);
deferred = true;
} else {
begin_gamepad_key(*repeat, key);
}
}
if (!deferred) {
context->ProcessMouseLeave();
context->ProcessKeyDown(key, 0);
}
}
} else {
const auto key = repeat != nullptr && repeat->held ? repeat->key : Rml::Input::KI_UNKNOWN;
const bool wasPending = repeat != nullptr && repeat->pending;
set_pad_button_held(port, button, false);
update_menu_chord_release(port);
if (key != Rml::Input::KI_UNKNOWN) {
if (repeat != nullptr) {
*repeat = {};
}
if (!wasPending) {
context->ProcessKeyUp(key, 0);
}
}
}
sync_input_block();
}
void update_input() noexcept {
auto* context = aurora::rmlui::get_context();
if (context != nullptr) {
const double now = now_seconds();
auto process_repeats = [context, now](auto& repeats) {
for (auto& repeat : repeats) {
if (!repeat.held) {
continue;
}
if (repeat.pending) {
if (now < repeat.pressedAt + kGamepadMenuChordGraceDuration) {
continue;
}
repeat.pending = false;
repeat.pressedAt = now;
repeat.nextRepeatAt =
repeat.repeatable ? now + kGamepadRepeatInitialDelay : 0.0;
context->ProcessMouseLeave();
context->ProcessKeyDown(repeat.key, 0);
continue;
}
if (!repeat.repeatable || now < repeat.nextRepeatAt ||
(repeat.key == Rml::Input::KI_NEXT && any_menu_chord()))
{
continue;
}
context->ProcessKeyDown(repeat.key, 0);
const double heldFor = now - repeat.pressedAt;
repeat.nextRepeatAt = now + repeat_interval(heldFor);
}
};
process_repeats(sGamepadButtonRepeats);
process_repeats(sGamepadAxisRepeats);
} else {
reset_input_state();
}
}
} // namespace dusk::ui
+10
View File
@@ -0,0 +1,10 @@
#pragma once
namespace dusk::ui {
void update_input() noexcept;
void reset_input_state() noexcept;
void sync_input_block() noexcept;
void release_input_block() noexcept;
} // namespace dusk::ui
+18
View File
@@ -0,0 +1,18 @@
#pragma once
namespace dusk::ui {
enum class NavCommand {
None,
Up,
Down,
Left,
Right,
Next, // R1
Previous, // L1
Confirm, // A
Cancel, // B
Menu, // Back/Minus, or R + Start
};
} // namespace dusk::ui
+56
View File
@@ -0,0 +1,56 @@
#include "number_button.hpp"
#include <charconv>
#include <fmt/format.h>
namespace dusk::ui {
NumberButton::NumberButton(Rml::Element* parent, Props props)
: BaseStringButton(parent, {.key = std::move(props.key), .type = "number"}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
mIsDisabled(std::move(props.isDisabled)), mMin(props.min), mMax(props.max), mStep(props.step),
mPrefix(std::move(props.prefix)), mSuffix(std::move(props.suffix)) {}
bool NumberButton::disabled() const {
if (mIsDisabled) {
return mIsDisabled();
}
return BaseStringButton::disabled();
}
Rml::String NumberButton::format_value() {
return fmt::format("{}{}{}", mPrefix, mGetValue(), mSuffix);
}
Rml::String NumberButton::input_value() {
return fmt::to_string(mGetValue());
}
void NumberButton::set_value(Rml::String value) {
if (!mSetValue) {
return;
}
int parsedValue = 0;
const char* begin = value.data();
const char* end = begin + value.size();
const auto result = std::from_chars(begin, end, parsedValue);
if (result.ec != std::errc() || result.ptr != end) {
return;
}
mSetValue(std::clamp(parsedValue, mMin, mMax));
}
bool NumberButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Left) {
mSetValue(std::clamp(mGetValue() - mStep, mMin, mMax));
return true;
} else if (cmd == NavCommand::Right) {
mSetValue(std::clamp(mGetValue() + mStep, mMin, mMax));
return true;
}
return BaseStringButton::handle_nav_command(cmd);
}
} // namespace dusk::ui
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "string_button.hpp"
namespace dusk::ui {
class NumberButton : public BaseStringButton {
public:
struct Props {
Rml::String key;
std::function<int()> getValue;
std::function<void(int)> setValue;
std::function<bool()> isDisabled;
int min = 0;
int max = INT_MAX;
int step = 1;
Rml::String prefix;
Rml::String suffix;
};
NumberButton(Rml::Element* parent, Props props);
bool disabled() const override;
protected:
Rml::String format_value() override;
Rml::String input_value() override;
void set_value(Rml::String value) override;
bool handle_nav_command(NavCommand cmd) override;
private:
std::function<int()> mGetValue;
std::function<void(int)> mSetValue;
std::function<bool()> mIsDisabled;
int mMin;
int mMax;
int mStep;
Rml::String mPrefix;
Rml::String mSuffix;
};
} // namespace dusk::ui
+275
View File
@@ -0,0 +1,275 @@
#include "overlay.hpp"
#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/overlay.rcss" />
</head>
<body>
<div id="root" class="overlay-root">
<div class="overlay">
<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(escape(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;
}
if (mProps.onChange) {
mProps.onChange(nextValue);
}
}
Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
if (value <= 0) {
return "Auto";
} else {
u32 width = 0;
u32 height = 0;
AuroraGetRenderSize(&width, &height);
return fmt::format("{}x ({}x{})", value, width, height);
}
case GraphicsOption::ShadowResolution:
return fmt::format("{}x", 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 "";
}
Overlay::Overlay(OverlayProps 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] {
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 Overlay::show() {
Document::show();
mRoot->SetAttribute("open", "");
}
void Overlay::hide(bool close) {
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
}
}
void Overlay::update() {
for (const auto& component : mComponents) {
component->update();
}
Document::update();
}
bool Overlay::focus() {
for (const auto& component : mComponents) {
if (component->focus()) {
return true;
}
}
return false;
}
bool Overlay::visible() const {
return mRoot->HasAttribute("open");
}
bool Overlay::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
pop();
return true;
}
return Document::handle_nav_command(event, cmd);
}
void Overlay::reset_default() {
set_value(mOption, mDefaultValue);
}
} // namespace dusk::ui
+90
View File
@@ -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 OverlayProps {
GraphicsOption option;
Rml::String title;
Rml::String helpText;
int valueMin = 0;
int valueMax = 0;
int defaultValue = 0;
};
class Overlay : public Document {
public:
explicit Overlay(OverlayProps 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
+151
View File
@@ -0,0 +1,151 @@
#include "pane.hpp"
#include "ui.hpp"
namespace dusk::ui {
namespace {
Rml::Element* createRoot(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement("pane");
return parent->AppendChild(std::move(elem));
}
} // namespace
Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)), mType(type) {
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
// If navigating to the next pane, select the focused item
if (mType == Type::Controlled && cmd == NavCommand::Right) {
auto* target = event.GetTargetElement();
int focusedChild = -1;
for (size_t i = 0; i < mChildren.size(); ++i) {
if (mChildren[i]->contains(target)) {
focusedChild = i;
break;
}
}
if (focusedChild == -1) {
return;
}
set_selected_item(focusedChild);
return;
}
int direction = 0;
if (cmd == NavCommand::Down) {
direction = 1;
} else if (cmd == NavCommand::Up) {
direction = -1;
} else {
return;
}
auto* target = event.GetTargetElement();
int focusedChild = -1;
for (size_t i = 0; i < mChildren.size(); ++i) {
if (mChildren[i]->contains(target)) {
focusedChild = i;
break;
}
}
if (focusedChild == -1) {
return;
}
int i = focusedChild + direction;
while (i >= 0 && i < mChildren.size()) {
if (mChildren[i]->focus()) {
event.StopPropagation();
break;
}
i += direction;
}
});
if (type == Type::Controlled) {
// For controlled panes, handle SelectButton Submit events for item selection
listen(Rml::EventId::Submit, [this](Rml::Event& event) {
int childIndex = -1;
for (int i = 0; i < mChildren.size(); ++i) {
if (event.GetTargetElement() == mChildren[i]->root()) {
childIndex = i;
}
}
set_selected_item(childIndex);
// If the selection was handled locally, don't allow it to bubble up to window
if (event.GetParameter("handled", false)) {
event.StopPropagation();
}
});
}
}
void Pane::update() {
finalize();
Component::update();
}
void Pane::set_selected_item(int index) {
if (mType == Type::Uncontrolled) {
return;
}
for (int i = 0; i < mChildren.size(); ++i) {
mChildren[i]->set_selected(i == index);
}
}
bool Pane::focus() {
// Focus the first selected child
for (const auto& child : mChildren) {
if (child->selected() && child->focus()) {
return true;
}
}
// Otherwise, focus the first focusable child
for (const auto& child : mChildren) {
if (child->focus()) {
return true;
}
}
return false;
}
Rml::Element* Pane::add_section(const Rml::String& text) {
auto* elem = append(mRoot, "div");
elem->SetClass("section-heading", true);
elem->SetInnerRML(escape(text));
return elem;
}
Rml::Element* Pane::add_text(const Rml::String& text) {
auto* elem = append(mRoot, "div");
elem->SetInnerRML(escape(text));
return elem;
}
Rml::Element* Pane::add_rml(const Rml::String& rml) {
auto* elem = append(mRoot, "div");
elem->SetInnerRML(rml);
return elem;
}
void Pane::finalize() {
if (finalized) {
return;
}
finalized = true;
// Append spacer element to the bottom. RmlUi does not properly handle
// padding-bottom or margin-bottom on a scrollable flex container, so
// we need to create a fake spacer with an actual layout height to get
// padding at the bottom of a scrollable container.
append(mRoot, "spacer");
}
void Pane::clear() {
clear_children();
finalized = false;
}
} // namespace dusk::ui
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include "button.hpp"
#include "component.hpp"
#include "select_button.hpp"
namespace dusk::ui {
class Pane : public FluentComponent<Pane> {
public:
enum class Type {
Controlled,
Uncontrolled,
};
explicit Pane(Rml::Element* parent, Type type);
bool focus() override;
void update() override;
void set_selected_item(int index);
Rml::Element* add_section(const Rml::String& text);
ControlledButton& add_button(ControlledButton::Props props) {
return add_child<ControlledButton>(std::move(props));
}
Button& add_button(Rml::String text) { return add_child<Button>(std::move(text)); }
ControlledSelectButton& add_select_button(ControlledSelectButton::Props props) {
return add_child<ControlledSelectButton>(std::move(props));
}
Rml::Element* add_text(const Rml::String& text);
Rml::Element* add_rml(const Rml::String& rml);
void finalize();
void clear();
private:
Type mType;
bool finalized = false;
};
} // namespace dusk::ui
+127
View File
@@ -0,0 +1,127 @@
#include "popup.hpp"
#include <RmlUi/Core.h>
#include "aurora/rmlui.hpp"
#include "dusk/main.h"
#include "editor.hpp"
#include "imgui.h"
#include "settings.hpp"
#include "ui.hpp"
#include "window.hpp"
#include <chrono>
#include <cmath>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/tabbing.rcss" />
<link type="text/rcss" href="res/rml/popup.rcss" />
</head>
<body>
<popup id="popup"></div>
</body>
</rml>
)RML";
}
Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("popup")) {
mTabBar = std::make_unique<TabBar>(mRoot, TabBar::Props{.autoSelect = false});
mTabBar->add_tab("Settings", [] { push_document(std::make_unique<SettingsWindow>()); });
mTabBar->add_tab("Warp", [] {
// TODO
});
mTabBar->add_tab("Editor", [] { push_document(std::make_unique<EditorWindow>()); });
mTabBar->add_tab("Reset", [this] {
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
mTabBar->set_active_tab(-1);
hide(false);
});
mTabBar->add_tab("Exit", [] { IsRunning = false; });
// Hide document after transition completion
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
// We start hidden, but want focus for an open nav event
mDocument->Focus();
}
void Popup::show() {
Document::show();
mRoot->SetAttribute("open", "");
mTabBar->set_active_tab(-1);
}
void Popup::hide(bool close) {
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
}
}
void Popup::update() {
update_safe_area();
Document::update();
}
void Popup::update_safe_area() noexcept {
if (mDocument == nullptr || mTabBar == nullptr) {
return;
}
// Avoid ImGui menu bar if shown
if (const auto* viewport = ImGui::GetMainViewport();
viewport != nullptr && mTopMargin != viewport->WorkPos.y)
{
mTopMargin = viewport->WorkPos.y;
mRoot->SetProperty(Rml::PropertyId::MarginTop, Rml::Property(mTopMargin, Rml::Unit::DP));
}
Rml::Context* context = mDocument->GetContext();
Insets safeInsets = safe_area_insets(context);
safeInsets = {
0.0f,
std::round(safeInsets.right),
0.0f,
std::round(safeInsets.left),
};
if (safeInsets == mTabBarPadding) {
return;
}
mTabBarPadding = safeInsets;
auto* tabBar = mTabBar->root();
tabBar->SetProperty(
Rml::PropertyId::PaddingRight, Rml::Property(safeInsets.right, Rml::Unit::PX));
tabBar->SetProperty(
Rml::PropertyId::PaddingLeft, Rml::Property(safeInsets.left, Rml::Unit::PX));
}
bool Popup::visible() const {
return mRoot->HasAttribute("open");
}
bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
hide(false);
return true;
}
return Document::handle_nav_command(event, cmd);
}
bool Popup::focus() {
return mTabBar->focus();
}
} // namespace dusk::ui
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "button.hpp"
#include "document.hpp"
#include "tab_bar.hpp"
#include <memory>
namespace dusk::ui {
class Popup : public Document {
public:
Popup();
Popup(const Popup&) = delete;
Popup& operator=(const Popup&) = delete;
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:
void update_safe_area() noexcept;
Rml::Element* mRoot;
std::unique_ptr<TabBar> mTabBar;
std::unique_ptr<Button> mCloseButton;
Insets mTabBarPadding;
float mTopMargin = 0.f;
};
} // namespace dusk::ui
+256
View File
@@ -0,0 +1,256 @@
#include "prelaunch.hpp"
#include "dusk/config.hpp"
#include "dusk/file_select.hpp"
#include "dusk/iso_validate.hpp"
#include "dusk/main.h"
#include "dusk/ui/prelaunch_options.hpp"
#include "version.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_filesystem.h>
#include <aurora/lib/window.hpp>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/prelaunch.rcss" />
</head>
<body>
<content id="root" open>
<menu>
<hero class="intro-item delay-0">
<div class="eyebrow"><span>Twilit Realm</span> presents</div>
<img src="res/logo-mascot.png" />
</hero>
<div id="menu-list" />
</menu>
<disk-status class="intro-item delay-4">
<span id="status" class="status" />
<span id="detail" class="detail" />
</disk-status>
<version-info class="intro-item delay-5">
<div class="version">Version <span id="version-text"></span></div>
<div class="update"><span>Update available!</span> Download</div>
</version-info>
</content>
</body>
</rml>
)RML";
constexpr std::array<SDL_DialogFileFilter, 2> kDiscFileFilters{{
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
{"All Files", "*"},
}};
void file_dialog_callback(void*, const char* path, const char* error) {
auto& state = prelaunch_state();
if (error != nullptr) {
return;
}
if (path == nullptr) {
return;
}
state.selectedIsoPath = path;
state.errorString.clear();
refresh_path_state();
getSettings().backend.isoPath.setValue(state.selectedIsoPath);
config::Save();
}
} // namespace
PrelaunchState sPrelaunchState;
PrelaunchState& prelaunch_state() noexcept {
return sPrelaunchState;
}
void refresh_path_state() noexcept {
auto& state = prelaunch_state();
state.isPal = !state.selectedIsoPath.empty() && iso::isPal(state.selectedIsoPath.c_str());
}
void ensure_initialized() noexcept {
auto& state = prelaunch_state();
if (state.initialized) {
return;
}
state.selectedIsoPath = getSettings().backend.isoPath;
state.initialGraphicsBackend = getSettings().backend.graphicsBackend;
state.errorString.clear();
state.initialized = true;
refresh_path_state();
}
bool is_selected_path_valid() noexcept {
return !prelaunch_state().selectedIsoPath.empty() &&
SDL_GetPathInfo(prelaunch_state().selectedIsoPath.c_str(), nullptr);
}
void open_iso_picker() noexcept {
ensure_initialized();
ShowFileSelect(&file_dialog_callback, nullptr, aurora::window::get_sdl_window(),
kDiscFileFilters.data(), kDiscFileFilters.size(), nullptr, false);
}
void apply_intro_animation(Rml::Element* element, const char* delay_class) {
if (element == nullptr || delay_class == nullptr) {
return;
}
element->SetClass("intro-item", true);
element->SetClass(delay_class, true);
}
Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementById("root")) {
ensure_initialized();
if (auto* menuList = mDocument->GetElementById("menu-list")) {
const bool hasValidPath = is_selected_path_valid();
mMenuButtons.push_back(
std::make_unique<Button>(menuList, hasValidPath ? "Start Game" : "Select Disk Image"));
mMenuButtons.back()->on_pressed([this] {
if (!is_selected_path_valid()) {
open_iso_picker();
return;
}
IsGameLaunched = true;
hide(true);
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Options"));
mMenuButtons.back()->on_pressed(
[] { push_document(std::make_unique<PrelaunchOptions>()); });
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit To Desktop"));
mMenuButtons.back()->on_pressed([] { IsRunning = false; });
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
}
mDiscStatus = mDocument->GetElementById("status");
mDiscDetail = mDocument->GetElementById("detail");
mVersion = mDocument->GetElementById("version-text");
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
auto* target = event.GetTargetElement();
if (target == nullptr) {
return;
}
if (target == mDocument && !mDocument->HasAttribute("open")) {
Document::hide(true);
} else if (target->GetTagName() == "button" && !target->IsClassSet("anim-done")) {
target->SetClass("anim-done", true);
}
});
}
void Prelaunch::show() {
Document::show();
mDocument->SetAttribute("open", "");
mRoot->SetAttribute("open", "");
}
void Prelaunch::hide(bool close) {
if (close) {
if (!mEntranceAnimationStarted) {
// Close document immediately
Document::hide(true);
}
mDocument->RemoveAttribute("open");
} else {
mRoot->RemoveAttribute("open");
}
}
void Prelaunch::update() {
ensure_initialized();
refresh_path_state();
auto& state = prelaunch_state();
const bool hasValidPath = is_selected_path_valid();
if (hasValidPath && getSettings().backend.skipPreLaunchUI) {
hide(true);
IsGameLaunched = true;
}
if (!mEntranceAnimationStarted && mDocument != nullptr) {
mDocument->SetClass("animate-in", true);
mEntranceAnimationStarted = true;
}
if (!mMenuButtons.empty()) {
mMenuButtons[0]->set_text(hasValidPath ? "Start Game" : "Select Disk Image");
}
if (mDiscStatus != nullptr) {
if (hasValidPath) {
mDiscStatus->RemoveAttribute("bad");
mDiscStatus->SetInnerRML("Disc Ready");
} else {
mDiscStatus->SetAttribute("bad", "");
mDiscStatus->SetInnerRML("Disk Not Found");
}
}
if (mDiscDetail != nullptr) {
if (hasValidPath) {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
mDiscDetail->SetInnerRML(state.isPal ? "GameCube • PAL" : "GameCube • USA");
} else {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
}
}
if (mVersion != nullptr) {
mVersion->SetInnerRML(escape(DUSK_WC_DESCRIBE));
}
Document::update();
}
bool Prelaunch::focus() {
if (mMenuButtons.empty()) {
return false;
}
return mMenuButtons[0]->focus();
}
bool Prelaunch::visible() const {
return mDocument->HasAttribute("open") && mRoot->HasAttribute("open");
}
bool Prelaunch::handle_nav_command(Rml::Event& event, NavCommand cmd) {
int direction = 0;
if (cmd == NavCommand::Down) {
direction = 1;
} else if (cmd == NavCommand::Up) {
direction = -1;
} else {
return false;
}
auto* target = event.GetTargetElement();
int focusedButton = -1;
for (int i = 0; i < mMenuButtons.size(); ++i) {
if (mMenuButtons[i]->contains(target)) {
focusedButton = i;
break;
}
}
const auto buttonCount = static_cast<int>(mMenuButtons.size());
int i = (focusedButton + direction) % buttonCount;
if (i < 0) i += buttonCount;
while (i >= 0 && i < mMenuButtons.size()) {
if (mMenuButtons[i]->focus()) {
event.StopPropagation();
return true;
}
i += direction;
}
return false;
}
} // namespace dusk::ui
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "button.hpp"
#include "document.hpp"
#include <memory>
#include <string>
#include <vector>
namespace dusk::ui {
class Prelaunch : public Document {
public:
Prelaunch();
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:
bool mEntranceAnimationStarted = false;
std::vector<std::unique_ptr<Button>> mMenuButtons;
Rml::Element* mRoot = nullptr;
Rml::Element* mDiscStatus = nullptr;
Rml::Element* mDiscDetail = nullptr;
Rml::Element* mVersion = nullptr;
};
class PrelaunchOptions;
struct PrelaunchState {
std::string selectedIsoPath;
std::string errorString;
std::string initialGraphicsBackend;
bool isPal = false;
bool initialized = false;
};
PrelaunchState& prelaunch_state() noexcept;
void ensure_initialized() noexcept;
void refresh_path_state() noexcept;
bool is_selected_path_valid() noexcept;
void open_iso_picker() noexcept;
} // namespace dusk::ui
+263
View File
@@ -0,0 +1,263 @@
#include "prelaunch_options.hpp"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "pane.hpp"
#include "prelaunch.hpp"
namespace dusk::ui {
namespace {
static constexpr std::array<const char*, 5> kLanguageNames = {
"English", "German", "French", "Spanish", "Italian",
};
// TODO: Copied from ImGui prelaunch. Needs a refactor?
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
if (backend == "auto") {
outBackend = BACKEND_AUTO;
return true;
}
if (backend == "d3d11") {
outBackend = BACKEND_D3D11;
return true;
}
if (backend == "d3d12") {
outBackend = BACKEND_D3D12;
return true;
}
if (backend == "metal") {
outBackend = BACKEND_METAL;
return true;
}
if (backend == "vulkan") {
outBackend = BACKEND_VULKAN;
return true;
}
if (backend == "opengl") {
outBackend = BACKEND_OPENGL;
return true;
}
if (backend == "opengles") {
outBackend = BACKEND_OPENGLES;
return true;
}
if (backend == "webgpu") {
outBackend = BACKEND_WEBGPU;
return true;
}
if (backend == "null") {
outBackend = BACKEND_NULL;
return true;
}
return false;
}
std::string_view backend_name(AuroraBackend backend) {
switch (backend) {
default:
return "Auto";
case BACKEND_D3D12:
return "D3D12";
case BACKEND_D3D11:
return "D3D11";
case BACKEND_METAL:
return "Metal";
case BACKEND_VULKAN:
return "Vulkan";
case BACKEND_OPENGL:
return "OpenGL";
case BACKEND_OPENGLES:
return "OpenGL ES";
case BACKEND_WEBGPU:
return "WebGPU";
case BACKEND_NULL:
return "Null";
}
}
std::string_view backend_id(AuroraBackend backend) {
switch (backend) {
default:
return "auto";
case BACKEND_D3D12:
return "d3d12";
case BACKEND_D3D11:
return "d3d11";
case BACKEND_METAL:
return "metal";
case BACKEND_VULKAN:
return "vulkan";
case BACKEND_OPENGL:
return "opengl";
case BACKEND_OPENGLES:
return "opengles";
case BACKEND_WEBGPU:
return "webgpu";
case BACKEND_NULL:
return "null";
}
}
std::vector<AuroraBackend> available_backends() {
std::vector<AuroraBackend> backends;
backends.emplace_back(BACKEND_AUTO);
size_t backendCount = 0;
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
// Do not expose NULL or D3D11
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
backends.emplace_back(raw[i]);
}
}
return backends;
}
class LanguageSelect final : public SelectButton {
public:
explicit LanguageSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Language"}) {}
void update() override {
ensure_initialized();
refresh_path_state();
const bool validPath = is_selected_path_valid();
const bool ntscDiscLocked = validPath && !prelaunch_state().isPal;
if (ntscDiscLocked) {
if (getSettings().game.language.getValue() != GameLanguage::English) {
getSettings().game.language.setValue(GameLanguage::English);
config::Save();
}
set_disabled(true);
} else {
set_disabled(false);
}
const auto lang = getSettings().game.language.getValue();
auto value = static_cast<u8>(lang);
if (value >= kLanguageNames.size()) {
getSettings().game.language.setValue(GameLanguage::English);
config::Save();
value = static_cast<u8>(getSettings().game.language.getValue());
}
set_value_label(kLanguageNames[value]);
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (disabled()) {
return false;
}
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
constexpr int n = static_cast<int>(kLanguageNames.size());
int idx = static_cast<int>(getSettings().game.language.getValue());
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
idx = ((idx + dir) % n + n) % n;
getSettings().game.language.setValue(static_cast<GameLanguage>(idx));
config::Save();
return true;
}
};
class BackendSelect final : public SelectButton {
public:
explicit BackendSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Graphics Backend"}) {}
void update() override {
AuroraBackend configuredBackend = BACKEND_AUTO;
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
if (!try_parse_backend(configuredId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
// Do not expose NULL or D3D11
if (configuredBackend == BACKEND_NULL || configuredBackend == BACKEND_D3D11) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
configuredBackend = BACKEND_AUTO;
}
const auto backend = getSettings().backend.graphicsBackend.getValue();
Rml::String value = backend_name(configuredBackend).data();
if (backend != prelaunch_state().initialGraphicsBackend) {
value += " (restart required)";
}
set_value_label(value);
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
const auto backends = available_backends();
const int n = static_cast<int>(backends.size());
if (n <= 0) {
return false;
}
AuroraBackend configuredBackend = BACKEND_AUTO;
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
if (!try_parse_backend(configuredId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
int idx = 0;
for (int i = 0; i < n; ++i) {
if (backends[static_cast<size_t>(i)] == configuredBackend) {
idx = i;
break;
}
}
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
idx = ((idx + dir) % n + n) % n;
getSettings().backend.graphicsBackend.setValue(std::string(backend_id(backends[static_cast<size_t>(idx)])));
config::Save();
return true;
}
};
class SaveTypeSelect final : public SelectButton {
public:
explicit SaveTypeSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Save File Type"}) {}
void update() override {
const CARDFileType cft = static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
set_value_label(cft == CARD_GCIFOLDER ? "GCI Folder" : "Card Image");
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
CARDFileType cft = static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
const CARDFileType newValue = cft == CARD_GCIFOLDER ? CARD_RAWIMAGE : CARD_GCIFOLDER;
getSettings().backend.cardFileType.setValue(newValue);
config::Save();
return true;
}
};
} // namespace
PrelaunchOptions::PrelaunchOptions() {
add_tab("Options", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
leftPane.add_child<LanguageSelect>();
leftPane.add_child<BackendSelect>();
leftPane.add_child<SaveTypeSelect>();
});
}
} // namespace dusk::ui
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "window.hpp"
namespace dusk::ui {
class PrelaunchOptions : public Window {
public:
PrelaunchOptions();
};
} // namespace dusk::ui
+69
View File
@@ -0,0 +1,69 @@
#include "select_button.hpp"
#include "ui.hpp"
#include <utility>
namespace dusk::ui {
namespace {
Rml::Element* createRoot(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement("select-button");
return parent->AppendChild(std::move(elem));
}
} // namespace
SelectButton::SelectButton(Rml::Element* parent, Props props)
: FluentComponent(createRoot(parent)) {
mKeyElem = append(mRoot, "key");
mValueElem = append(mRoot, "value");
update_props(std::move(props));
on_nav_command([this](Rml::Event&, NavCommand cmd) { return handle_nav_command(cmd); });
}
void SelectButton::set_value_label(const Rml::String& value) {
if (mProps.value != value) {
mValueElem->SetInnerRML(escape(value));
mProps.value = value;
}
}
void SelectButton::update_props(Props props) {
if (mProps.key != props.key) {
mKeyElem->SetInnerRML(escape(props.key));
}
set_value_label(props.value);
mProps = std::move(props);
}
bool SelectButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mRoot->DispatchEvent(Rml::EventId::Submit, {});
return true;
}
return false;
}
void BaseControlledSelectButton::update() {
set_disabled(disabled());
set_value_label(format_value());
SelectButton::update();
}
bool ControlledSelectButton::disabled() const {
if (mIsDisabled) {
return mIsDisabled();
}
return BaseControlledSelectButton::disabled();
}
Rml::String ControlledSelectButton::format_value() {
if (!mGetValue) {
return "";
}
return mGetValue();
}
} // namespace dusk::ui
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include "component.hpp"
#include "ui.hpp"
namespace dusk::ui {
class SelectButton : public FluentComponent<SelectButton> {
public:
struct Props {
Rml::String key;
Rml::String value;
};
SelectButton(Rml::Element* parent, Props props);
void set_value_label(const Rml::String& value);
protected:
void update_props(Props props);
virtual bool handle_nav_command(NavCommand cmd);
Props mProps;
Rml::Element* mKeyElem = nullptr;
Rml::Element* mValueElem = nullptr;
std::function<void()> mOnHover;
};
class BaseControlledSelectButton : public SelectButton {
public:
BaseControlledSelectButton(Rml::Element* parent, Props props)
: SelectButton(parent, std::move(props)) {}
void update() override;
protected:
virtual Rml::String format_value() = 0;
};
class ControlledSelectButton : public BaseControlledSelectButton {
public:
struct Props {
Rml::String key;
std::function<Rml::String()> getValue;
std::function<bool()> isDisabled;
};
ControlledSelectButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, {std::move(props.key)}),
mGetValue(std::move(props.getValue)), mIsDisabled(std::move(props.isDisabled)) {}
bool disabled() const override;
protected:
Rml::String format_value() override;
std::function<Rml::String()> mGetValue;
std::function<bool()> mIsDisabled;
};
} // namespace dusk::ui
+637
View File
@@ -0,0 +1,637 @@
#include "settings.hpp"
#include <fmt/format.h>
#include "aurora/gfx.h"
#include "bool_button.hpp"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/config.hpp"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/livesplit.h"
#include "m_Do/m_Do_main.h"
#include "number_button.hpp"
#include "overlay.hpp"
#include "pane.hpp"
#include "ui.hpp"
#include <algorithm>
namespace dusk::ui {
namespace {
void reset_for_speedrun_mode() {
mDoMain::developmentMode = -1;
getSettings().game.damageMultiplier.setValue(1);
getSettings().game.instantDeath.setValue(false);
getSettings().game.noHeartDrops.setValue(false);
getSettings().game.infiniteHearts.setValue(false);
getSettings().game.infiniteArrows.setValue(false);
getSettings().game.infiniteBombs.setValue(false);
getSettings().game.infiniteOil.setValue(false);
getSettings().game.infiniteOxygen.setValue(false);
getSettings().game.infiniteRupees.setValue(false);
getSettings().game.enableIndefiniteItemDrops.setValue(false);
getSettings().game.moonJump.setValue(false);
getSettings().game.superClawshot.setValue(false);
getSettings().game.alwaysGreatspin.setValue(false);
getSettings().game.enableFastIronBoots.setValue(false);
getSettings().game.canTransformAnywhere.setValue(false);
getSettings().game.fastSpinner.setValue(false);
getSettings().game.freeMagicArmor.setValue(false);
getSettings().game.enableTurboKeybind.setValue(false);
}
const Rml::String kInternalResolutionHelpText =
"Configure the resolution used for rendering the game. Higher values are more demanding on "
"your graphics hardware.";
const Rml::String kShadowResolutionHelpText =
"Configure the shadow-map resolution. Higher values improve shadow quality but increase GPU "
"and memory usage.";
const Rml::String kBloomHelpText =
"Configure the post-processing bloom effect. Classic uses the original bloom pass; Dusk uses "
"a higher-quality bloom pass.";
const Rml::String kBloomBrightnessHelpText =
"Configure bloom intensity. Higher values make bright areas glow more strongly.";
int bloom_multiplier_percent() {
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0, 100);
}
int float_setting_percent(ConfigVar<float>& var) {
return static_cast<int>(var.getValue() * 100.0f + 0.5f);
}
bool gyro_enabled() {
return getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal;
}
struct ConfigBoolProps {
Rml::String key;
Rml::String helpText;
std::function<void(bool)> onChange;
std::function<bool()> isDisabled;
};
SelectButton& config_bool_select(
Pane& leftPane, Pane& rightPane, ConfigVar<bool>& var, ConfigBoolProps props) {
return leftPane
.add_child<BoolButton>(BoolButton::Props{
.key = std::move(props.key),
.getValue = [&var] { return var.getValue(); },
.setValue =
[&var, callback = std::move(props.onChange)](bool value) {
if (value == var.getValue()) {
return;
}
var.setValue(value);
config::Save();
if (callback) {
callback(value);
}
},
.isDisabled = std::move(props.isDisabled),
})
.on_focus([&rightPane, helpText = std::move(props.helpText)](Rml::Event&) {
rightPane.clear();
rightPane.add_rml(helpText);
});
}
SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<float>& var,
Rml::String key, Rml::String helpText, int min, int max, int step = 5,
std::function<bool()> isDisabled = {}) {
return leftPane
.add_child<NumberButton>(NumberButton::Props{
.key = std::move(key),
.getValue = [&var] { return float_setting_percent(var); },
.setValue =
[&var, min, max](int value) {
var.setValue(std::clamp(value, min, max) / 100.0f);
config::Save();
},
.isDisabled = std::move(isDisabled),
.min = min,
.max = max,
.step = step,
.suffix = "%",
})
.on_focus([&rightPane, helpText = std::move(helpText)](Rml::Event&) {
rightPane.clear();
rightPane.add_text(helpText);
});
}
class ControllerConfigWindow : public Window {
public:
ControllerConfigWindow() {
for (int i = 0; i < 4; ++i) {
add_tab(fmt::format("Port {}", i + 1), [this](Rml::Element* content) {
auto& pane = add_child<Pane>(content, Pane::Type::Controlled);
pane.add_section("Coming soon");
});
}
}
};
} // namespace
SettingsWindow::SettingsWindow() {
add_tab("Audio", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
// TODO: Individual sliders for Main Music, Sub Music, Sound Effects, and Fanfare.
leftPane.add_section("Volume");
leftPane
.add_child<NumberButton>(NumberButton::Props{
.key = "Master Volume",
.getValue = [] { return getSettings().audio.masterVolume.getValue(); },
.setValue =
[](int value) {
getSettings().audio.masterVolume.setValue(value);
config::Save();
audio::SetMasterVolume(value / 100.f);
},
.max = 100,
.suffix = "%",
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text("Adjusts the volume of all sounds in the game.");
});
leftPane.add_section("Effects");
config_bool_select(leftPane, rightPane, getSettings().audio.enableReverb,
{
.key = "Enable Reverb",
.helpText = "Enables the reverb effect in game audio.",
.onChange = [](bool value) { audio::SetEnableReverb(value); },
});
config_bool_select(leftPane, rightPane, getSettings().audio.enableHrtf,
{
.key = "Enable Spatial Sound",
.helpText = "Emulate surround sound via HRTF. Recommended only for use with headphones!",
.onChange = [](bool value) { audio::EnableHrtf = value; },
});
leftPane.add_section("Tweaks");
config_bool_select(leftPane, rightPane, getSettings().game.noLowHpSound,
{
.key = "No Low HP Sound",
.helpText = "Disable the beeping sound when having low health.",
});
config_bool_select(leftPane, rightPane, getSettings().game.midnasLamentNonStop,
{
.key = "Non-Stop Midna's Lament",
.helpText = "Prevents enemy music while Midna's Lament is playing.",
});
});
add_tab("Cheats", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
auto addCheat = [&](const Rml::String& key, ConfigVar<bool>& value,
const Rml::String& helpText) {
config_bool_select(leftPane, rightPane, value,
{
.key = key,
.helpText = helpText,
.isDisabled = [] { return getSettings().game.speedrunMode; },
});
};
leftPane.add_section("Resources");
addCheat("Infinite Hearts", getSettings().game.infiniteHearts,
"Keeps your health full.");
addCheat("Infinite Arrows", getSettings().game.infiniteArrows,
"Keeps your arrow count full.");
addCheat("Infinite Bombs", getSettings().game.infiniteBombs,
"Keeps all bomb bags full.");
addCheat("Infinite Oil", getSettings().game.infiniteOil,
"Keeps your lantern oil full.");
addCheat("Infinite Oxygen", getSettings().game.infiniteOxygen,
"Keeps your underwater oxygen meter full.");
addCheat("Infinite Rupees", getSettings().game.infiniteRupees,
"Keeps your rupee count full.");
addCheat("No Item Timer", getSettings().game.enableIndefiniteItemDrops,
"Item drops such as rupees and hearts will never disappear after they drop.");
leftPane.add_section("Abilities");
addCheat("Moon Jump (R+A)", getSettings().game.moonJump,
"Hold R and A to rise into the air.");
addCheat("Super Clawshot", getSettings().game.superClawshot,
"Extends clawshot behavior beyond the normal game rules.");
addCheat("Always Greatspin", getSettings().game.alwaysGreatspin,
"Allows the Great Spin attack without requiring full health.");
addCheat("Fast Iron Boots", getSettings().game.enableFastIronBoots,
"Speeds up movement while wearing the Iron Boots.");
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
"Allows transforming even if NPCs are looking.");
addCheat("Fast Spinner", getSettings().game.fastSpinner,
"Speeds up Spinner movement while holding R.");
addCheat("Free Magic Armor", getSettings().game.freeMagicArmor,
"Lets the magic armor work without consuming rupees.");
});
add_tab("Gameplay", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
auto addOption = [&](const Rml::String& key, ConfigVar<bool>& value,
const Rml::String& helpText) {
config_bool_select(leftPane, rightPane, value,
{
.key = key,
.helpText = helpText,
});
};
auto addSpeedrunDisabledOption = [&](const Rml::String& key, ConfigVar<bool>& value,
const Rml::String& helpText) {
config_bool_select(leftPane, rightPane, value,
{
.key = key,
.helpText = helpText,
.isDisabled = [] { return getSettings().game.speedrunMode; },
});
};
leftPane.add_section("General");
addOption("Mirror Mode", getSettings().game.enableMirrorMode,
"Mirrors the world horizontally, matching the Wii version of the game.");
addOption("Disable Main HUD", getSettings().game.disableMainHUD,
"Disables the main HUD of the game.<br/>Useful for recording or a more immersive "
"experience.");
addOption("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches,
"Restores patched glitches from Wii USA 1.0, the first released version.");
addOption("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation,
"Enables rotating Link in the collection menu with the C-Stick.");
leftPane.add_section("Difficulty");
leftPane
.add_child<NumberButton>(NumberButton::Props{
.key = "Damage Multiplier",
.getValue = [] { return getSettings().game.damageMultiplier.getValue(); },
.setValue =
[](int value) {
getSettings().game.damageMultiplier.setValue(value);
config::Save();
},
.isDisabled = [] { return getSettings().game.speedrunMode; },
.min = 1,
.max = 8,
.prefix = "x",
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text("Multiplies incoming damage.");
});
addSpeedrunDisabledOption("Instant Death", getSettings().game.instantDeath,
"Any hit will instantly kill you.");
addSpeedrunDisabledOption("No Heart Drops", getSettings().game.noHeartDrops,
"Hearts will never drop from enemies, pots, and various other places.");
addOption("Hyper Enemies", getSettings().game.hyperEnemies,
"Enemies and Bosses are twice as fast.");
leftPane.add_section("Quality of Life");
addOption("Bigger Wallets", getSettings().game.biggerWallets,
"Wallet sizes are like in the HD version. (500, 1000, 2000)");
addOption("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes,
"Rupees will not play cutscenes after you have collected them the first time.");
addOption("Faster Climbing", getSettings().game.fastClimbing,
"Quicker climbing on ladders and vines like the HD version.");
addOption("Faster Tears of Light", getSettings().game.fastTears,
"Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
addOption("Autosave", getSettings().game.autoSave,
"Autosaves the game when going to a new area, opening a dungeon door, or getting "
"a new item.<br/><br/>This feature is currently experimental, use at your own risk.");
addOption("Instant Saves", getSettings().game.instantSaves,
"Skips the delay when writing to the Memory Card.");
addOption("Hold B for Instant Text", getSettings().game.instantText,
"Makes text scroll immediately by holding B.");
addOption("No Climbing Miss Animation", getSettings().game.noMissClimbing,
"Prevents Link from playing a struggle animation when grabbing ledges or "
"climbing on vines.");
addOption("No Rupee Returns", getSettings().game.noReturnRupees,
"Always collect Rupees even if your Wallet is too full.");
addOption("No Sword Recoil", getSettings().game.noSwordRecoil,
"Link will not recoil when his sword hits walls.");
addOption("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat,
"Skip needing to catch a second fish for Sera's cat.");
addOption("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen,
"Skips the TV calibration screen shown when loading a save.");
addOption("Skip Warning Screen", getSettings().game.skipWarningScreen,
"Skips the warning screen shown when starting the game.");
addOption("Sun's Song (R+X)", getSettings().game.sunsSong,
"Allows Wolf Link to howl and change the time of day.");
addOption("Quick Transform (R+Y)", getSettings().game.enableQuickTransform,
"Transform instantly by pressing R and Y simultaneously.");
leftPane.add_section("Speedrunning");
config_bool_select(leftPane, rightPane, getSettings().game.speedrunMode,
{
.key = "Speedrun Mode",
.helpText =
"Enables speedrunning options while restricting certain gameplay modifiers.",
.onChange = [](bool) { reset_for_speedrun_mode(); },
});
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
{
.key = "LiveSplit Connection",
.helpText = "Connect to LiveSplit server on localhost:16834.",
.onChange =
[](bool enabled) {
if (enabled) {
speedrun::connectLiveSplit();
} else {
speedrun::disconnectLiveSplit();
}
},
.isDisabled = [] { return !getSettings().game.speedrunMode; },
});
});
add_tab("Input", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
auto addOption = [&](const Rml::String& key, ConfigVar<bool>& value,
const Rml::String& helpText, std::function<bool()> isDisabled = {}) {
config_bool_select(leftPane, rightPane, value,
{
.key = key,
.helpText = helpText,
.isDisabled = std::move(isDisabled),
});
};
leftPane.add_section("Controller");
leftPane.add_button("Configure Controller")
.on_pressed([] { push_document(std::make_unique<ControllerConfigWindow>()); })
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text("Open controller binding configuration.");
});
leftPane.add_section("Camera");
addOption("Free Camera", getSettings().game.freeCamera,
"Enables twin-stick camera control, letting the C-Stick move the camera vertically as "
"well as horizontally.");
addOption("Invert Camera X Axis", getSettings().game.invertCameraXAxis,
"Invert horizontal camera movement.");
addOption("Invert Camera Y Axis", getSettings().game.invertCameraYAxis,
"Invert vertical camera movement when Free Camera is enabled.",
[] { return !getSettings().game.freeCamera; });
config_percent_select(leftPane, rightPane, getSettings().game.freeCameraSensitivity,
"Free Camera Sensitivity", "Adjusts twin-stick camera sensitivity.", 50, 200, 5,
[] { return !getSettings().game.freeCamera; });
leftPane.add_section("Gyro");
addOption("Gyro Aim", getSettings().game.enableGyroAim,
"Enables gyro controls while in look mode, aiming a hawk, and aiming "
"supported items.<br/><br/>Supported items include the Slingshot, Gale Boomerang, "
"Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod.");
addOption("Gyro Rollgoal", getSettings().game.enableGyroRollgoal,
"Enables gyro controls for Rollgoal in Hena's Cabin.");
config_percent_select(leftPane, rightPane, getSettings().game.gyroSensitivityY,
"Gyro Pitch Sensitivity", "Controls vertical gyro aiming sensitivity.", 25, 400, 5,
[] { return !gyro_enabled(); });
config_percent_select(leftPane, rightPane, getSettings().game.gyroSensitivityX,
"Gyro Yaw Sensitivity", "Controls horizontal gyro aiming sensitivity.", 25, 400, 5,
[] { return !gyro_enabled(); });
config_percent_select(leftPane, rightPane, getSettings().game.gyroSensitivityRollgoal,
"Rollgoal Sensitivity", "Controls how strongly gyro input tilts the Rollgoal table.",
25, 400, 5, [] { return !getSettings().game.enableGyroRollgoal; });
config_percent_select(leftPane, rightPane, getSettings().game.gyroDeadband, "Gyro Deadband",
"Ignores small gyro movement to reduce drift and jitter.", 0, 50, 1,
[] { return !gyro_enabled(); });
config_percent_select(leftPane, rightPane, getSettings().game.gyroSmoothing,
"Gyro Smoothing", "Higher values smooth gyro input over time.", 0, 100, 1,
[] { return !gyro_enabled(); });
addOption("Invert Gyro Pitch", getSettings().game.gyroInvertPitch,
"Invert vertical gyro aiming.", [] { return !gyro_enabled(); });
addOption("Invert Gyro Yaw", getSettings().game.gyroInvertYaw,
"Invert horizontal gyro aiming.", [] { return !gyro_enabled(); });
leftPane.add_section("Tools");
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
"Hold Tab to increase game speed by up to 4x.",
[] { return getSettings().game.speedrunMode; });
});
add_tab("Graphics", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
leftPane.add_section("Display");
leftPane.add_button("Toggle Fullscreen").on_pressed([] {
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
});
leftPane.add_button("Restore Default Window Size").on_pressed([] {
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
});
config_bool_select(leftPane, rightPane, getSettings().video.enableVsync,
{
.key = "Enable VSync",
.helpText = "Synchronizes the frame rate to your monitor's refresh rate.",
.onChange = [](bool value) { aurora_enable_vsync(value); },
});
config_bool_select(leftPane, rightPane, getSettings().video.lockAspectRatio,
{
.key = "Lock 4:3 Aspect Ratio",
.helpText = "Lock the game's aspect ratio to the original.",
.onChange =
[](bool value) {
AuroraSetViewportPolicy(
value ? AURORA_VIEWPORT_FIT : AURORA_VIEWPORT_STRETCH);
},
});
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
{
.key = "Pause on Focus Lost",
.isDisabled = [] { return IsMobile; },
});
leftPane.add_section("Resolution");
leftPane
.add_select_button({
.key = "Internal Resolution",
.getValue =
[] {
return format_graphics_setting_value(GraphicsOption::InternalResolution,
getSettings().game.internalResolutionScale.getValue());
},
})
.on_nav_command([](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
push_document(std::make_unique<Overlay>(OverlayProps{
.option = GraphicsOption::InternalResolution,
.title = "Internal Resolution",
.helpText = kInternalResolutionHelpText,
.valueMin = 0,
.valueMax = 12,
.defaultValue = 0,
}));
return true;
}
return false;
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text(kInternalResolutionHelpText);
});
leftPane
.add_select_button({
.key = "Shadow Resolution",
.getValue =
[] {
return format_graphics_setting_value(GraphicsOption::ShadowResolution,
getSettings().game.shadowResolutionMultiplier.getValue());
},
})
.on_nav_command([](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
push_document(std::make_unique<Overlay>(OverlayProps{
.option = GraphicsOption::ShadowResolution,
.title = "Shadow Resolution",
.helpText = kShadowResolutionHelpText,
.valueMin = 1,
.valueMax = 8,
.defaultValue = 1,
}));
return true;
}
return false;
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text(kShadowResolutionHelpText);
});
leftPane.add_section("Post-Processing");
leftPane
.add_select_button({
.key = "Bloom",
.getValue =
[] {
return format_graphics_setting_value(GraphicsOption::BloomMode,
static_cast<int>(getSettings().game.bloomMode.getValue()));
},
})
.on_nav_command([](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
push_document(std::make_unique<Overlay>(OverlayProps{
.option = GraphicsOption::BloomMode,
.title = "Bloom",
.helpText = kBloomHelpText,
.valueMin = static_cast<int>(BloomMode::Off),
.valueMax = static_cast<int>(BloomMode::Dusk),
.defaultValue = static_cast<int>(BloomMode::Classic),
}));
return true;
}
return false;
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text(kBloomHelpText);
});
leftPane
.add_select_button({
.key = "Bloom Brightness",
.getValue =
[] {
return format_graphics_setting_value(
GraphicsOption::BloomMultiplier, bloom_multiplier_percent());
},
.isDisabled =
[] { return getSettings().game.bloomMode.getValue() == BloomMode::Off; },
})
.on_nav_command([](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
push_document(std::make_unique<Overlay>(OverlayProps{
.option = GraphicsOption::BloomMultiplier,
.title = "Bloom Brightness",
.helpText = kBloomBrightnessHelpText,
.valueMin = 0,
.valueMax = 100,
.defaultValue = 100,
}));
return true;
}
return false;
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text(kBloomBrightnessHelpText);
});
leftPane.add_section("Rendering");
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
{
.key = "Unlock Framerate",
.helpText =
"Uses inter-frame interpolation to enable higher frame rates.<br/><br/>Visual "
"artifacts, animation glitches, or instability may occur.",
});
config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField,
{
.key = "Enable Depth of Field",
});
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
{
.key = "Enable Mini-Map Shadows",
});
});
// TODO: Reorganize all of this?
add_tab("Interface", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
config_bool_select(leftPane, rightPane, getSettings().game.enableAchievementNotifications,
{
.key = "Enable Achievement Notifications",
.helpText = "Display a toast when an achievement is unlocked.",
});
#if DUSK_ENABLE_SENTRY_NATIVE
config_bool_select(leftPane, rightPane, getSettings().backend.enableCrashReporting,
{
.key = "Enable Crash Reporting",
.helpText = "Enable automatic reporting of crashes to the developers.<br/><br/>"
"Submissions include logs which may contain sensitive information. Refrain from "
"enabling reporting if you do not agree with the following inclusions:<br/><br/> "
"- Operating System<br/>- CPU Architecture<br/>- GPU Model & Driver Version<br/>"
"- Account Username"
});
#endif
leftPane.add_section("Advanced");
config_bool_select(leftPane, rightPane, getSettings().backend.skipPreLaunchUI,
{
.key = "Skip Pre-Launch UI",
});
config_bool_select(leftPane, rightPane, getSettings().backend.showPipelineCompilation,
{
.key = "Show Pipeline Compilation",
.helpText = "Show an overlay when shaders are being compiled for your hardware."
});
});
}
} // namespace dusk::ui
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "window.hpp"
namespace dusk::ui {
class SettingsWindow : public Window {
public:
SettingsWindow();
};
} // namespace dusk::ui
+137
View File
@@ -0,0 +1,137 @@
#include "string_button.hpp"
#include <aurora/rmlui.hpp>
namespace dusk::ui {
BaseStringButton::BaseStringButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, {std::move(props.key)}), mType(std::move(props.type)),
mMaxLength(props.maxLength) {
mInputListeners.reserve(3);
}
void BaseStringButton::update() {
if (mPendingStopEditing) {
stop_editing(mPendingCommit, mPendingRefocusRoot);
}
if (mPendingInputFocusFrames > 0) {
--mPendingInputFocusFrames;
if (mPendingInputFocusFrames == 0) {
focus_input();
}
}
BaseControlledSelectButton::update();
}
void BaseStringButton::start_editing() {
if (mInputElem != nullptr) {
return;
}
// Create input element
auto* doc = mRoot->GetOwnerDocument();
auto elemPtr = doc->CreateElement("input");
mInputElem = rmlui_dynamic_cast<Rml::ElementFormControlInput*>(elemPtr.get());
if (mInputElem == nullptr) {
return;
}
mInputElem->SetAttribute("type", mType);
mInputElem->SetAttribute("value", input_value());
if (mMaxLength > -1) {
mInputElem->SetAttribute("maxlength", mMaxLength);
}
mRoot->AppendChild(std::move(elemPtr));
// Hide value element
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
// RmlUi lays out the new input during render. Wait one full frame before focusing it so
// mobile keyboard placement gets a valid caret rectangle.
mPendingInputFocusFrames = 2;
// Dispatch a submit event so the pane can handle item selection
// However, mark it as "handled" to ensure that we don't steal focus away
mRoot->DispatchEvent(Rml::EventId::Submit, {{"handled", Rml::Variant{true}}});
// Register input listeners
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd == NavCommand::Confirm) {
request_stop_editing(true, true);
event.StopImmediatePropagation();
} else if (cmd == NavCommand::Cancel) {
request_stop_editing(false, true);
event.StopImmediatePropagation();
}
}));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Click, [](Rml::Event& event) { event.StopPropagation(); }));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(mInputElem,
Rml::EventId::Blur, [this](Rml::Event&) { request_stop_editing(true, false); }));
}
void BaseStringButton::request_stop_editing(bool commit, bool refocusRoot) {
mPendingStopEditing = true;
mPendingCommit = commit;
mPendingRefocusRoot = refocusRoot;
}
bool BaseStringButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mInputElem == nullptr) {
start_editing();
} else {
request_stop_editing(true, true);
}
return true;
} else if (cmd == NavCommand::Cancel) {
if (mInputElem != nullptr) {
request_stop_editing(false, true);
return true;
}
}
return false;
}
void BaseStringButton::focus_input() {
if (mInputElem == nullptr) {
return;
}
aurora::rmlui::set_input_type(
mType == "number" ? aurora::rmlui::InputType::Number : aurora::rmlui::InputType::Text);
if (mInputElem->Focus(true)) {
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInputElem->GetValue()));
mInputElem->SetSelectionRange(0, end);
}
}
void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
mPendingStopEditing = false;
mPendingInputFocusFrames = 0;
if (mInputElem == nullptr) {
return;
}
if (commit) {
set_value(mInputElem->GetValue());
}
mInputListeners.clear();
mRoot->RemoveChild(mInputElem);
mInputElem = nullptr;
// Restore value element
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
set_selected(false);
if (refocusRoot) {
mRoot->Focus(true);
}
}
StringButton::StringButton(Rml::Element* parent, Props props)
: BaseStringButton(parent, {.key = std::move(props.key), .maxLength = props.maxLength}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)) {}
} // namespace dusk::ui
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "select_button.hpp"
#include <RmlUi/Config/Config.h>
namespace dusk::ui {
class BaseStringButton : public BaseControlledSelectButton {
public:
struct Props {
Rml::String key;
Rml::String type = "text";
int maxLength = -1;
};
BaseStringButton(Rml::Element* parent, Props props);
void update() override;
void start_editing();
void request_stop_editing(bool commit, bool refocusRoot);
protected:
bool handle_nav_command(NavCommand cmd) override;
virtual void set_value(Rml::String value) = 0;
virtual Rml::String input_value() { return format_value(); }
private:
void focus_input();
void stop_editing(bool commit = true, bool refocusRoot = false);
Rml::ElementFormControlInput* mInputElem = nullptr;
std::vector<std::unique_ptr<ScopedEventListener> > mInputListeners;
Rml::String mType;
int mMaxLength;
int mPendingInputFocusFrames = 0;
bool mPendingStopEditing = false;
bool mPendingCommit = true;
bool mPendingRefocusRoot = false;
};
class StringButton : public BaseStringButton {
public:
struct Props {
Rml::String key;
std::function<Rml::String()> getValue;
std::function<void(Rml::String)> setValue;
int maxLength = -1;
};
StringButton(Rml::Element* parent, Props props);
protected:
Rml::String format_value() override { return mGetValue(); }
void set_value(Rml::String value) override {
if (mSetValue) {
mSetValue(std::move(value));
}
}
private:
std::function<Rml::String()> mGetValue;
std::function<void(Rml::String)> mSetValue;
};
} // namespace dusk::ui

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