Compare commits

..

195 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
Phillip Stephens c803bfb545 Update aurora 2026-04-29 09:14:51 -07:00
qwertyquerty 2623c44cab Merge pull request #583 from TheLastPocket/achievement/email-me
Email me and Heavy Hitter Achievements
2026-04-29 05:29:50 -07:00
madeline 24dd02fc81 better back in time condition 2026-04-29 05:28:28 -07:00
TakaRikka a97602b6dc Merge pull request #589 from TwilitRealm/instant-text-shop-fix
Fix instant text clearing some shop dialogue too quickly
2026-04-29 00:37:51 -07:00
TakaRikka e2943e90dc Merge pull request #590 from dooplecks/map-outline
Improve map outline rendering
2026-04-29 00:37:04 -07:00
Luke Street 3cb7fbd030 Create new component system & initial settings window 2026-04-29 00:38:26 -06:00
doop afe54f22ab Write circle pixels linearly 2026-04-29 04:16:24 +00:00
doop e15f5bcee9 Improve map outline rendering 2026-04-29 03:51:14 +00:00
Irastris 1e372a856d Attempted to start making the save editor functional 2026-04-28 22:54:47 -04:00
gymnast86 b26896cad5 includes 2026-04-28 18:05:40 -07:00
gymnast86 f75faf6b06 fix instant text clearing shop messages too early 2026-04-28 18:03:06 -07: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
TakaRikka 3e05789b58 Merge pull request #586 from TwilitRealm/fix/fix-super-clawshot
Fix draw crash with Super Clawshot enabled
2026-04-28 13:34:24 -07: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
MelonSpeedruns 4d12cc8ea2 Fix draw crash with Super Clawshot enabled 2026-04-28 11:05:29 -04:00
Julian Maynes 2ed2268579 fix newline 2026-04-28 07:19:18 -07:00
Julian Maynes 94a99e8da0 Add iron boots achievement 2026-04-28 07:12:29 -07:00
madeline ddaf50c01d long jump attack achievement 2026-04-28 06:53:20 -07:00
madeline 7566949b42 fix friendly fire achievement 2026-04-28 06:21:41 -07:00
Julian Maynes bb6db3caea remove newlines 2026-04-28 06:18:40 -07:00
Julian Maynes 2dc494dc1c Finish achievement 2026-04-28 06:14:56 -07:00
madeline 3c25633ee9 speed_target avoid ub 2026-04-28 06:08:04 -07:00
madeline d99ed2729b fix achievements window sizing 2026-04-28 05:57:58 -07:00
madeline 782455d48b Merge branch 'main' of https://github.com/TakaRikka/dusk 2026-04-28 05:54:12 -07:00
madeline 47863b34c2 achievement signal system 2026-04-28 05:54:09 -07:00
TakaRikka 92391d5eb8 add overview and ai notice to readme 2026-04-28 04:06:29 -07:00
madeline 0d37cb4e54 thank you berry much achievement 2026-04-28 04:01:22 -07:00
madeline ea528ed9d9 clear single achievement and fix achievement tab moving around 2026-04-28 03:41:04 -07: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
TakaRikka 30b7087f30 add option for only fishing once for sera's cat 2026-04-27 22:04:42 -07:00
Luke Street 0c1372f986 Revert "Kinda crappy initial controller support"
This reverts commit 23a91a37be.
2026-04-27 21:55:57 -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
TakaRikka a4777045fe Merge pull request #578 from TwilitRealm/fix/fix-disable-hud
Fix bugs with Disable Main HUD
2026-04-27 15:29:02 -07:00
TakaRikka bbe8ea6aa6 Merge pull request #577 from TwilitRealm/better-freecam-code
Better Freecam code + Allow Freecam in Target mode
2026-04-27 15:28:39 -07:00
MelonSpeedruns d662db69f0 Fix bugs with Disable Main HUD 2026-04-27 17:04:42 -04:00
MelonSpeedruns 5eb3184174 Hyper Enemies (2x) 2026-04-27 16:55:43 -04:00
MelonSpeedruns 9b259143be Better Freecam code + Allow Freecam in Target mode 2026-04-27 12:05:26 -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
125 changed files with 11248 additions and 947 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)
+7 -1
View File
@@ -1,8 +1,12 @@
![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)**
# Overview
Dusk is a reverse-engineered reimplementation of Twilight Princess.
It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup # Setup
**⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** **⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.**
@@ -27,5 +31,7 @@ First make sure your dump of the game is clean and supported by Dusk. You can do
# Building # Building
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
Pull Requests are welcomed! Note that we do not accept contributions that are primarily AI generated and will close your PR if we suspect as much.
# Credits # Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
+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();
+3
View File
@@ -67,6 +67,9 @@ public:
bool isStaffMessage(); bool isStaffMessage();
bool isSaveMessage(); bool isSaveMessage();
bool isTalkMessage(); bool isTalkMessage();
#if TARGET_PC
bool isShopItemMessage();
#endif
const char* getSmellName(); const char* getSmellName();
const char* getPortalName(); const char* getPortalName();
const char* getBombName(); const char* getBombName();
+9
View File
@@ -4,6 +4,8 @@
#include <functional> #include <functional>
#include <queue> #include <queue>
#include <string> #include <string>
#include <string_view>
#include <unordered_set>
#include <vector> #include <vector>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
@@ -14,6 +16,7 @@ enum class AchievementCategory : uint8_t {
Collection, Collection,
Challenge, Challenge,
Minigame, Minigame,
Misc,
Glitched Glitched
}; };
@@ -40,6 +43,11 @@ public:
void save(); void save();
void tick(); void tick();
void clearAll(); void clearAll();
void clearOne(const char* key);
// Signals are visible to all achievement checks within the same tick, then cleared.
void signal(const char* key);
bool hasSignal(const char* key) const;
std::vector<Achievement> getAchievements() const; std::vector<Achievement> getAchievements() const;
bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); } bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
@@ -57,6 +65,7 @@ private:
void processEntry(Entry& e); void processEntry(Entry& e);
std::vector<Entry> m_entries; std::vector<Entry> m_entries;
std::unordered_set<std::string_view> m_signals;
bool m_loaded = false; bool m_loaded = false;
bool m_dirty = false; bool m_dirty = false;
std::queue<std::string> m_pendingUnlocks; std::queue<std::string> m_pendingUnlocks;
+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
+4
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,14 +72,17 @@ 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;
ConfigVar<bool> noMissClimbing; ConfigVar<bool> noMissClimbing;
ConfigVar<bool> fastTears; ConfigVar<bool> fastTears;
ConfigVar<bool> no2ndFishForCat;
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;
} }
+13
View File
@@ -8,6 +8,10 @@
#include "d/actor/d_a_horse.h" #include "d/actor/d_a_horse.h"
#include "d/actor/d_a_crod.h" #include "d/actor/d_a_crod.h"
#include "d/d_msg_object.h" #include "d/d_msg_object.h"
#ifdef TARGET_PC
#include "d/actor/d_a_obj_carry.h"
#include "dusk/achievements.h"
#endif
#if DEBUG #if DEBUG
#include "d/d_s_menu.h" #include "d/d_s_menu.h"
@@ -677,6 +681,15 @@ BOOL daAlink_c::checkDamageAction() {
} }
setDamagePoint(dmg, at_mtrl == dCcD_MTRL_FIRE || at_mtrl == dCcD_MTRL_ICE, TRUE, 0); setDamagePoint(dmg, at_mtrl == dCcD_MTRL_FIRE || at_mtrl == dCcD_MTRL_ICE, TRUE, 0);
#ifdef TARGET_PC
if (tghit_ac_name == fpcNm_Obj_Carry_e) {
auto* carry = static_cast<daObjCarry_c*>(tghit_ac);
if (carry->prm_chk_type_ironball() && carry->checkCannon()) {
dusk::AchievementSystem::get().signal("iron_ball_hit_player");
}
}
#endif
if (armor_no_dmg && at_mtrl != dCcD_MTRL_ELECTRIC && at_mtrl != dCcD_MTRL_ICE) { if (armor_no_dmg && at_mtrl != dCcD_MTRL_ELECTRIC && at_mtrl != dCcD_MTRL_ICE) {
setGuardSe(var_r29); setGuardSe(var_r29);
+4
View File
@@ -18,6 +18,10 @@ enum {
}; };
void daAlink_c::hsChainShape_c::draw() { void daAlink_c::hsChainShape_c::draw() {
if (dusk::getSettings().game.superClawshot) {
return;
}
static const int dummy = 0; static const int dummy = 0;
daAlink_c* alink = (daAlink_c*)getUserArea(); daAlink_c* alink = (daAlink_c*)getUserArea();
-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;
} }
+5
View File
@@ -282,6 +282,11 @@ static void e_th_spin_B(e_th_class* i_this) {
i_this->current.pos += spC; i_this->current.pos += spC;
f32 speed_target; f32 speed_target;
#if AVOID_UB
speed_target = 0;
#endif
f32 anm_frame = i_this->mpModelMorf->getFrame(); f32 anm_frame = i_this->mpModelMorf->getFrame();
switch (i_this->mMode) { switch (i_this->mMode) {
+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;
} }
+2 -3
View File
@@ -956,7 +956,7 @@ static void npc_ne_tame(npc_ne_class* i_this) {
i_this->mpMorf->setPlaySpeed(i_this->mAnmSpeed); i_this->mpMorf->setPlaySpeed(i_this->mAnmSpeed);
/* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */ /* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */
if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) { if (IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) {
if (fpcEx_Search(s_fish_sub, _this) != NULL) { if (fpcEx_Search(s_fish_sub, _this) != NULL) {
i_this->mAction = npc_ne_class::ACT_HOME; i_this->mAction = npc_ne_class::ACT_HOME;
i_this->mMode = 10; i_this->mMode = 10;
@@ -2948,8 +2948,7 @@ static int daNpc_Ne_Execute(npc_ne_class* i_this) {
if (i_this->mWantsFish && (i_this->mCounter & 0xf) == 0) { if (i_this->mWantsFish && (i_this->mCounter & 0xf) == 0) {
/* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */ /* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */
if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470]) if ((IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) && i_this->mDistToTarget < 1500.0f) {
&& i_this->mDistToTarget < 1500.0f) {
if (fopAcM_SearchByName(fpcNm_MG_ROD_e) != NULL) { if (fopAcM_SearchByName(fpcNm_MG_ROD_e) != NULL) {
i_this->mNoFollow = false; i_this->mNoFollow = false;
} else { } else {
+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;
+65 -40
View File
@@ -794,16 +794,15 @@ void dCamera_c::updatePad() {
if (mTriggerLeftLast > mCamSetup.ManualEndVal()) { if (mTriggerLeftLast > mCamSetup.ManualEndVal()) {
if (mLockLActive == 0) { if (mLockLActive == 0) {
#if TARGET_PC
mCamParam.mManualMode = 0;
#endif
mLockLJustActivated = 1; mLockLJustActivated = 1;
} else { } else {
mLockLJustActivated = 0; mLockLJustActivated = 0;
} }
mLockLActive = 1; mLockLActive = 1;
#if TARGET_PC
mCamParam.mManualMode = 0;
#endif
} else { } else {
mLockLJustActivated = 0; mLockLJustActivated = 0;
mLockLActive = 0; mLockLActive = 0;
@@ -1176,14 +1175,14 @@ bool dCamera_c::Run() {
clrFlag(0x200000); clrFlag(0x200000);
} }
} else { } else {
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
#if TARGET_PC #if TARGET_PC
if (mCamParam.Algorythmn(mCamStyle) != 1) { if (mCamParam.Algorythmn(mCamStyle) != 1) {
mCamParam.mManualMode = 0; mCamParam.mManualMode = 0;
} }
#endif #endif
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
field_0x170++; field_0x170++;
field_0x160++; field_0x160++;
mCurCamStyleTimer++; mCurCamStyleTimer++;
@@ -1488,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;
@@ -3096,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);
@@ -3113,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");
@@ -4208,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;
} }
@@ -4631,10 +4627,6 @@ bool dCamera_c::chaseCamera(s32 param_0) {
sp110 = mViewCache.mDirection.R(); sp110 = mViewCache.mDirection.R();
mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74); mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74);
#if TARGET_PC
freeCamera();
#endif
chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz(); chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz();
mViewCache.mEye = chase->field_0x64; mViewCache.mEye = chase->field_0x64;
@@ -4649,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;
} }
@@ -7096,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
@@ -7477,35 +7476,48 @@ 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;
} }
if (!mCamParam.mManualMode) {
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);
mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, -35.0f, 60.0f); if (!mCamParam.mManualMode || player == nullptr) {
mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); return false;
mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
mViewCache.mDirection.mRadius = std::clamp((mCamParam.freeYAngle + 35.0f) * 10.0f, 300.0f, 10000.0f);
} }
return mCamParam.mManualMode; f32 minYAngle = -30.0f;
f32 maxAngle = 50.0f;
mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle);
mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle);
mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
cXyz finalEye = mViewCache.mCenter + mViewCache.mDirection.Xyz();
mViewCache.mEye = finalEye;
return true;
} }
#endif #endif
@@ -11148,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) {
+43
View File
@@ -16,6 +16,7 @@
#ifdef TARGET_PC #ifdef TARGET_PC
constexpr u16 kMapResolutionMultiplier = 4; constexpr u16 kMapResolutionMultiplier = 4;
constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier;
#endif #endif
void dMpath_n::dTexObjAggregate_c::create() { void dMpath_n::dTexObjAggregate_c::create() {
@@ -32,6 +33,48 @@ void dMpath_n::dTexObjAggregate_c::create() {
JUT_ASSERT(74, image->magFilter == GX_NEAR); JUT_ASSERT(74, image->magFilter == GX_NEAR);
mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL); mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL);
} }
#if TARGET_PC
auto hqCircle = JKR_NEW TGXTexObj();
static bool hqCircleDrawn = false;
static u8 hqCircleData[kMapCircleSize * kMapCircleSize];
if (!hqCircleDrawn) {
const auto center = kMapCircleSize / 2.0f;
const auto radiusSq = center * center;
const auto blocksAcross = kMapCircleSize >> 3;
const auto totalPixels = sizeof(hqCircleData);
for (size_t i = 0; i < totalPixels; i++) {
// 8x4 block swizzling for I8
const auto blockIdx = i >> 5;
const auto localIdx = i & 31;
const auto blockY = blockIdx / blocksAcross;
const auto blockX = blockIdx % blocksAcross;
const auto localY = localIdx >> 3;
const auto localX = localIdx & 7;
const auto x = (blockX << 3) + localX;
const auto y = (blockY << 2) + localY;
const auto dx = (x + 0.5f) - center;
const auto dy = (y + 0.5f) - center;
// the original texture is in I4 format and uses 1 to indicate if inside the circle
// so we scale to I8 range: 255 / 15 = 17
hqCircleData[i] = (dx * dx + dy * dy < radiusSq) ? 17 : 0;
}
hqCircleDrawn = true;
}
GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP,
GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
mp_texObj[6] = hqCircle;
#endif
} }
void dMpath_n::dTexObjAggregate_c::remove() { void dMpath_n::dTexObjAggregate_c::remove() {
+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();
+8
View File
@@ -17,6 +17,10 @@
#include "d/d_msg_scrn_arrow.h" #include "d/d_msg_scrn_arrow.h"
#include "d/d_lib.h" #include "d/d_lib.h"
#ifdef TARGET_PC
#include "dusk/achievements.h"
#endif
#if VERSION == VERSION_GCN_JPN #if VERSION == VERSION_GCN_JPN
#define D_MENU_LETTER_LINE_MAX 9 #define D_MENU_LETTER_LINE_MAX 9
#else #else
@@ -514,6 +518,10 @@ void dMenu_Letter_c::read_open_init() {
setAButtonString(0); setAButtonString(0);
setBButtonString(0); setBButtonString(0);
mpBlackTex->setAlpha(0); mpBlackTex->setAlpha(0);
#ifdef TARGET_PC
dusk::AchievementSystem::get().signal("open_letter");
#endif
} }
void dMenu_Letter_c::read_open_move() { void dMenu_Letter_c::read_open_move() {
+6 -6
View File
@@ -316,6 +316,12 @@ int dMeter2_c::_execute() {
} }
int dMeter2_c::_draw() { int dMeter2_c::_draw() {
#if TARGET_PC
if (dusk::getSettings().game.disableMainHUD) {
return 1;
}
#endif
if (mpMap != NULL) { if (mpMap != NULL) {
mpMap->_draw(); mpMap->_draw();
} }
@@ -424,12 +430,6 @@ void dMeter2_c::setLifeZero() {
void dMeter2_c::checkStatus() { void dMeter2_c::checkStatus() {
mStatus = 0; mStatus = 0;
#if TARGET_PC
if (dusk::getSettings().game.disableMainHUD) {
mStatus |= 0xF0000000;
}
#endif
field_0x12c = field_0x128; field_0x12c = field_0x128;
field_0x128 = daPy_py_c::checkNowWolf(); field_0x128 = daPy_py_c::checkNowWolf();
+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
} }
+1 -8
View File
@@ -1987,13 +1987,6 @@ bool jmessage_tSequenceProcessor::do_isReady() {
} }
#endif #endif
#if TARGET_PC
if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) {
field_0xb2 = 1;
pReference->setSendTimer(0);
}
#endif
if (dComIfGp_checkMesgBgm()) { if (dComIfGp_checkMesgBgm()) {
bool isItemMusicPlaying = true; bool isItemMusicPlaying = true;
if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET && if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET &&
@@ -2066,7 +2059,7 @@ bool jmessage_tSequenceProcessor::do_isReady() {
case 0: case 0:
case 5: case 5:
case 6: case 6:
if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0) { if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0 IF_DUSK(|| (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)))) {
field_0xa4 = 0; field_0xa4 = 0;
pReference->onBatchFlag(); pReference->onBatchFlag();
pReference->setCharCnt(D_MSG_CLASS_CHAR_CNT_MAX); pReference->setCharCnt(D_MSG_CLASS_CHAR_CNT_MAX);
+38 -1
View File
@@ -32,6 +32,9 @@
#if TARGET_PC #if TARGET_PC
#include "dusk/settings.h" #include "dusk/settings.h"
#include <vector>
#include <array>
#include <algorithm>
#endif #endif
static void dMsgObject_addFundRaising(s16 param_0); static void dMsgObject_addFundRaising(s16 param_0);
@@ -1594,7 +1597,7 @@ u8 dMsgObject_c::isSend() {
return 2; return 2;
} }
} else { } else {
if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0) && !isShopItemMessage()) ||)
mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) {
return 2; return 2;
} }
@@ -1866,6 +1869,40 @@ bool dMsgObject_c::isTalkMessage() {
return true; return true;
} }
#if TARGET_PC
bool dMsgObject_c::isShopItemMessage() {
// Probably a better way to do this than just listing every message id, but this works for now
// Note: Keep contents sorted so we can use binary search
const auto shopMsgIds = std::to_array<std::vector<s16>>({
{},
// zel_01.bmg - Seras Shop
{7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029,
7044, 7045, 7053},
// zel_02.bmg - Kakariko Shops
{5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, 5697, 5698,
5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, 5989, 5990,
5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999},
// zel_03.bmg - Death Mountain Shop
{5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499},
// zel_04.bmg - Castle Town Shops
{5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431,
5432, 5433, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452,
5462},
// zel_05.bmg - Oocca Shop
{9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459}
});
u16 id = mMessageID;
s16 group = dMsgObject_getGroupID();
if (group < shopMsgIds.size()) {
return std::ranges::binary_search(shopMsgIds[group], id);
}
return false;
}
#endif
const char* dMsgObject_c::getSmellName() { const char* dMsgObject_c::getSmellName() {
JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20); JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20);
char* data_ptr = (char*)info_header_p + info_header_p->header.size; char* data_ptr = (char*)info_header_p + info_header_p->header.size;
+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);
}
+127 -8
View File
@@ -8,6 +8,7 @@
#include "d/actor/d_a_player.h" #include "d/actor/d_a_player.h"
#include "d/d_demo.h" #include "d/d_demo.h"
#include "f_pc/f_pc_name.h" #include "f_pc/f_pc_name.h"
#include "f_op/f_op_actor_mng.h"
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
@@ -46,6 +47,21 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
}, },
{} {}
}, },
{
{
"plumm_max",
"Thank You Berry Much",
"Score 61,454 points in the Plumm minigame.",
AchievementCategory::Minigame,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (dComIfGs_getBalloonScore() >= 61454) {
a.progress = 1;
}
},
{}
},
{ {
{ {
"rollgoal_8", "rollgoal_8",
@@ -258,6 +274,58 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
}, },
{} {}
}, },
{
{
"friendly_fire",
"Friendly Fire",
"Get hit by your own cannonball.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (AchievementSystem::get().hasSignal("iron_ball_hit_player")) {
a.progress = 1;
}
},
{}
},
{
{
"long_jump_attack",
"Long Jump Attack",
"Travel more than 20 meters in a single jump attack before landing.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
static bool inJump = false;
static float startX = 0.0f, startZ = 0.0f;
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr) {
inJump = false;
return;
}
if (!inJump) {
if (link->mProcID == daAlink_c::PROC_CUT_JUMP) {
inJump = true;
startX = link->current.pos.x;
startZ = link->current.pos.z;
}
} else if (link->mProcID == daAlink_c::PROC_CUT_JUMP_LAND) {
inJump = false;
const float dx = link->current.pos.x - startX;
const float dz = link->current.pos.z - startZ;
if (dx * dx + dz * dz >= 2000.0f * 2000.0f) {
a.progress = 1;
}
} else if (link->mProcID != daAlink_c::PROC_CUT_JUMP) {
inJump = false;
}
},
{}
},
{ {
{ {
"back_in_time", "back_in_time",
@@ -267,18 +335,13 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
false, 0, 0, false false, 0, 0, false
}, },
[](Achievement& a, json&) { [](Achievement& a, json&) {
static int titleNoDemoFrames = 0;
if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) { if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) {
titleNoDemoFrames = 0;
return; return;
} }
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass()); const auto* player = static_cast<const daPy_py_c*>(daPy_getPlayerActorClass());
if (link != nullptr && dDemo_c::getMode() == 0) {
if (++titleNoDemoFrames >= 60) { if (player != nullptr && player->mDemo.getDemoMode() == 1) {
a.progress = 1; a.progress = 1;
}
} else {
titleNoDemoFrames = 0;
} }
}, },
{} {}
@@ -345,6 +408,41 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
} }
}, },
{} {}
},
{
{
"email_me",
"Email Me",
"Read a letter during the Dark Beast Ganon fight.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
void* dbgExists = fopAcM_SearchByName(fpcNm_B_MGN_e);
if (dbgExists && AchievementSystem::get().hasSignal("open_letter")) {
a.progress = 1;
}
},
{}
},
{
{
"heavy-hitter",
"Heavy Hitter",
"Wear the Iron Boots during the end credits.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
if (daPy_getPlayerActorClass()->checkEquipHeavyBoots()) {
a.progress = 1;
}
},
{}
} }
}; };
} }
@@ -426,6 +524,26 @@ void AchievementSystem::clearAll() {
save(); save();
} }
void AchievementSystem::signal(const char* key) {
m_signals.insert(key);
}
bool AchievementSystem::hasSignal(const char* key) const {
return m_signals.count(key) > 0;
}
void AchievementSystem::clearOne(const char* key) {
for (auto& e : m_entries) {
if (std::string(e.achievement.key) == key) {
e.achievement.progress = 0;
e.achievement.unlocked = false;
e.extra = {};
break;
}
}
save();
}
void AchievementSystem::processEntry(Entry& e) { void AchievementSystem::processEntry(Entry& e) {
if (e.achievement.unlocked) { if (e.achievement.unlocked) {
return; return;
@@ -458,6 +576,7 @@ void AchievementSystem::tick() {
for (auto& e : m_entries) { for (auto& e : m_entries) {
processEntry(e); processEntry(e);
} }
m_signals.clear();
if (m_dirty) { if (m_dirty) {
save(); save();
m_dirty = false; m_dirty = false;
+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;
}
+20 -3
View File
@@ -76,8 +76,8 @@ void ImGuiAchievements::draw(bool& open) {
return; return;
} }
ImGui::SetNextWindowSizeConstraints(ImVec2(640, 200), ImVec2(800, 900)); ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900));
ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver);
if (!ImGui::Begin( if (!ImGui::Begin(
"Achievements", &open, "Achievements", &open,
@@ -111,6 +111,7 @@ void ImGuiAchievements::draw(bool& open) {
{AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)}, {AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)},
{AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)}, {AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)},
{AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)}, {AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)},
{AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)},
{AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, {AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)},
}; };
@@ -131,7 +132,7 @@ void ImGuiAchievements::draw(bool& open) {
continue; continue;
} }
const std::string tabLabel = fmt::format("{} ({}/{})", catInfo.label, catUnlocked, catTotal); const std::string tabLabel = fmt::format("{} ({}/{})###{}", catInfo.label, catUnlocked, catTotal, catInfo.label);
ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color); ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color);
const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str()); const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str());
@@ -152,6 +153,7 @@ void ImGuiAchievements::draw(bool& open) {
continue; continue;
} }
ImGui::PushID(a.key); ImGui::PushID(a.key);
ImGui::BeginGroup();
ImGui::PushStyleColor( ImGui::PushStyleColor(
ImGuiCol_Text, ImGuiCol_Text,
@@ -190,6 +192,21 @@ void ImGuiAchievements::draw(bool& open) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
ImGui::EndGroup();
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup("##ctx");
}
if (ImGui::BeginPopup("##ctx")) {
ImGui::TextDisabled("%s", a.name);
ImGui::Separator();
if (ImGui::MenuItem("Clear Achievement")) {
AchievementSystem::get().clearOne(a.key);
}
ImGui::EndPopup();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::PopID(); ImGui::PopID();
} }
+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 -41
View File
@@ -56,21 +56,6 @@ ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) {
} }
return nullptr; return nullptr;
} }
void FocusLastMenuBarItem() {
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = ImGui::GetCurrentWindow();
const ImGuiID itemId = g.LastItemData.ID;
if (window == nullptr || itemId == 0) {
return;
}
ImGui::FocusWindow(window);
ImGui::SetNavID(itemId, ImGuiNavLayer_Menu, g.CurrentFocusScopeId,
ImGui::WindowRectAbsToRel(window, g.LastItemData.NavRect));
ImGui::SetNavCursorVisibleAfterMove();
g.NavHighlightItemUnderNav = true;
}
} // namespace } // namespace
namespace dusk { namespace dusk {
@@ -339,28 +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;
if (dusk::IsGameLaunched) { if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
if (ImGui::IsKeyPressed(ImGuiKey_F1)) { m_isHidden = !m_isHidden;
m_isHidden = !m_isHidden;
}
if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack)) {
m_isHidden = !m_isHidden;
m_focusMenuBar = !m_isHidden;
}
} }
bool showMenu = !m_isHidden;
bool showMenu = !dusk::IsGameLaunched || !m_isHidden; if (getSettings().backend.duskMenuOpen != showMenu) {
if (dusk::IsGameLaunched) { getSettings().backend.duskMenuOpen.setValue(showMenu);
const bool menuOpen = !m_isHidden; Save();
if (getSettings().backend.duskMenuOpen != menuOpen) {
getSettings().backend.duskMenuOpen.setValue(menuOpen);
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,
@@ -368,10 +343,6 @@ namespace dusk {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (showMenu && ImGui::BeginMainMenuBar()) { if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw(); m_menuGame.draw();
if (m_focusMenuBar) {
FocusLastMenuBarItem();
m_focusMenuBar = false;
}
m_menuTools.draw(); m_menuTools.draw();
const auto fpsLabel = const auto fpsLabel =
@@ -394,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 or Minus/Back 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();
-1
View File
@@ -41,7 +41,6 @@ private:
float mouseHideTimer = 0.0f; float mouseHideTimer = 0.0f;
bool m_isHidden = true; bool m_isHidden = true;
bool m_focusMenuBar = false;
bool m_isLaunchInitialized = false; bool m_isLaunchInitialized = false;
bool m_touchTapActive = false; bool m_touchTapActive = false;
bool m_touchTapMoved = false; bool m_touchTapMoved = false;
File diff suppressed because it is too large Load Diff
+2
View File
@@ -30,6 +30,7 @@ static void ApplyPresetHD() {
s.game.biggerWallets.setValue(true); s.game.biggerWallets.setValue(true);
s.game.invertCameraXAxis.setValue(true); s.game.invertCameraXAxis.setValue(true);
s.game.freeCamera.setValue(true); s.game.freeCamera.setValue(true);
s.game.no2ndFishForCat.setValue(true);
} }
static void ApplyPresetDusk() { static void ApplyPresetDusk() {
@@ -43,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());
+18 -556
View File
@@ -3,40 +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;
bool is_controller_neutral(int port) {
if (port < 0) {
return true;
}
return PADGetNativeButtonPressed(port) == -1 &&
PADGetNativeAxisPulled(port).nativeAxis == -1;
}
} // namespace
namespace aurora::gx {
extern bool enableLodBias;
}
namespace dusk { namespace dusk {
void ImGuiMenuGame::ToggleFullscreen() { void ImGuiMenuGame::ToggleFullscreen() {
@@ -49,470 +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("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));
@@ -651,90 +171,39 @@ namespace dusk {
void ImGuiMenuGame::windowControllerConfig() { void ImGuiMenuGame::windowControllerConfig() {
if (!m_showControllerConfig) { if (!m_showControllerConfig) {
if (m_controllerConfig.m_isReading ||
m_controllerConfig.m_suppressRemapActivationUntilRelease)
{
m_controllerConfig.m_isReading = false;
m_controllerConfig.m_pendingButtonMapping = nullptr;
m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1;
m_controllerConfig.m_waitForInputRelease = false;
m_controllerConfig.m_suppressRemapActivationUntilRelease = false;
m_controllerConfig.m_suppressRemapActivationPort = -1;
PADBlockInput(false);
}
return; return;
} }
bool suppressRemapActivationThisFrame = m_controllerConfig.m_suppressRemapActivationUntilRelease;
if (m_controllerConfig.m_suppressRemapActivationUntilRelease &&
is_controller_neutral(m_controllerConfig.m_suppressRemapActivationPort))
{
m_controllerConfig.m_suppressRemapActivationUntilRelease = false;
m_controllerConfig.m_suppressRemapActivationPort = -1;
PADBlockInput(false);
}
if ((m_controllerConfig.m_pendingButtonMapping != nullptr ||
m_controllerConfig.m_pendingAxisMapping != nullptr) &&
m_controllerConfig.m_waitForInputRelease)
{
m_controllerConfig.m_waitForInputRelease =
!is_controller_neutral(m_controllerConfig.m_pendingPort);
}
// if pending for a button mapping, check to set new input // if pending for a button mapping, check to set new input
if (m_controllerConfig.m_pendingButtonMapping != nullptr && if (m_controllerConfig.m_pendingButtonMapping != nullptr) {
!m_controllerConfig.m_waitForInputRelease)
{
s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
if (nativeButton != -1) { if (nativeButton != -1) {
const int suppressPort = m_controllerConfig.m_pendingPort;
m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton; m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton;
m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingButtonMapping = nullptr;
m_controllerConfig.m_pendingPort = -1; m_controllerConfig.m_pendingPort = -1;
m_controllerConfig.m_isReading = false; PADBlockInput(false);
m_controllerConfig.m_waitForInputRelease = false;
m_controllerConfig.m_suppressRemapActivationUntilRelease = true;
m_controllerConfig.m_suppressRemapActivationPort = suppressPort;
suppressRemapActivationThisFrame = true;
PADBlockInput(true);
PADSerializeMappings(); PADSerializeMappings();
} }
} }
// if pending for an axis mapping, check to set new input // if pending for an axis mapping, check to set new input
if (m_controllerConfig.m_pendingAxisMapping != nullptr && if (m_controllerConfig.m_pendingAxisMapping != nullptr) {
!m_controllerConfig.m_waitForInputRelease)
{
auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort); auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort);
if (nativeAxis.nativeAxis != -1) { if (nativeAxis.nativeAxis != -1) {
const int suppressPort = m_controllerConfig.m_pendingPort;
m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis; m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis;
m_controllerConfig.m_pendingAxisMapping->nativeButton = -1; m_controllerConfig.m_pendingAxisMapping->nativeButton = -1;
m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1; m_controllerConfig.m_pendingPort = -1;
m_controllerConfig.m_isReading = false; PADBlockInput(false);
m_controllerConfig.m_waitForInputRelease = false;
m_controllerConfig.m_suppressRemapActivationUntilRelease = true;
m_controllerConfig.m_suppressRemapActivationPort = suppressPort;
suppressRemapActivationThisFrame = true;
PADBlockInput(true);
PADSerializeMappings(); PADSerializeMappings();
} else { } else {
auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
if (nativeButton != -1) { if (nativeButton != -1) {
const int suppressPort = m_controllerConfig.m_pendingPort;
m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE}; m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton; m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton;
m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1; m_controllerConfig.m_pendingPort = -1;
m_controllerConfig.m_isReading = false; PADBlockInput(false);
m_controllerConfig.m_waitForInputRelease = false;
m_controllerConfig.m_suppressRemapActivationUntilRelease = true;
m_controllerConfig.m_suppressRemapActivationPort = suppressPort;
suppressRemapActivationThisFrame = true;
PADBlockInput(true);
PADSerializeMappings(); PADSerializeMappings();
} }
} }
@@ -770,10 +239,6 @@ namespace dusk {
m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingButtonMapping = nullptr;
m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1; m_controllerConfig.m_pendingPort = -1;
m_controllerConfig.m_waitForInputRelease = false;
m_controllerConfig.m_isReading = false;
m_controllerConfig.m_suppressRemapActivationUntilRelease = false;
m_controllerConfig.m_suppressRemapActivationPort = -1;
PADBlockInput(false); PADBlockInput(false);
} }
@@ -850,7 +315,7 @@ namespace dusk {
std::string dispName; std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) { if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) {
dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", btnName); dispName = fmt::format("Press a Key...##{}", btnName);
} else { } else {
const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton); const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton);
if (nativeName == nullptr) { if (nativeName == nullptr) {
@@ -861,11 +326,10 @@ namespace dusk {
bool pressed = ImGui::Button(dispName.c_str(), bool pressed = ImGui::Button(dispName.c_str(),
btnSize); btnSize);
if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { if (pressed) {
m_controllerConfig.m_isReading = true; m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i]; m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i];
m_controllerConfig.m_waitForInputRelease = true;
PADBlockInput(true); PADBlockInput(true);
} }
} }
@@ -895,18 +359,17 @@ namespace dusk {
std::string dispName; std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) { if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) {
dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", axisName); dispName = fmt::format("Press a Key...##{}", axisName);
} else { } else {
dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger); dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger);
} }
bool pressed = ImGui::Button(dispName.c_str(), bool pressed = ImGui::Button(dispName.c_str(),
btnSize); btnSize);
if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { if (pressed) {
m_controllerConfig.m_isReading = true; m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger]; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger];
m_controllerConfig.m_waitForInputRelease = true;
PADBlockInput(true); PADBlockInput(true);
} }
} }
@@ -963,7 +426,7 @@ namespace dusk {
std::string dispName; std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", label); dispName = fmt::format("Press a Key...##{}", label);
} else { } else {
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
const char* signStr; const char* signStr;
@@ -982,11 +445,10 @@ namespace dusk {
} }
bool pressed = ImGui::Button(dispName.c_str(), btnSize); bool pressed = ImGui::Button(dispName.c_str(), btnSize);
if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { if (pressed) {
m_controllerConfig.m_isReading = true; m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
m_controllerConfig.m_waitForInputRelease = true;
PADBlockInput(true); PADBlockInput(true);
} }
} }
@@ -1027,7 +489,7 @@ namespace dusk {
std::string dispName; std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
dispName = fmt::format("{}##sub{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", label); dispName = fmt::format("Press a Key...##sub{}", label);
} else { } else {
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
const char* signStr; const char* signStr;
@@ -1046,11 +508,10 @@ namespace dusk {
} }
bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize); bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize);
if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { if (pressed) {
m_controllerConfig.m_isReading = true; m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
m_controllerConfig.m_waitForInputRelease = true;
PADBlockInput(true); PADBlockInput(true);
} }
} }
@@ -1081,7 +542,7 @@ namespace dusk {
PADSerializeMappings(); PADSerializeMappings();
} }
} }
if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) { if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) {
ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1)); ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1));
u16 low; u16 low;
@@ -1100,7 +561,7 @@ namespace dusk {
if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) { if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) {
PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD); PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD);
m_controllerConfig.m_isRumbling ^= 1; m_controllerConfig.m_isRumbling ^= 1;
} }
ImGuiEndGroupPanel(); ImGuiEndGroupPanel();
} }
ImGuiEndGroupPanel(); ImGuiEndGroupPanel();
@@ -1122,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);
-10
View File
@@ -55,22 +55,12 @@ 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;
PADButtonMapping* m_pendingButtonMapping = nullptr; PADButtonMapping* m_pendingButtonMapping = nullptr;
PADAxisMapping* m_pendingAxisMapping = nullptr; PADAxisMapping* m_pendingAxisMapping = nullptr;
int m_pendingPort = -1; int m_pendingPort = -1;
bool m_waitForInputRelease = false;
bool m_suppressRemapActivationUntilRelease = false;
int m_suppressRemapActivationPort = -1;
bool m_isRumbling = false; bool m_isRumbling = false;
} m_controllerConfig; } m_controllerConfig;
+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();
} }
+10 -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,14 +32,17 @@ 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},
.fastTears {"game.fastTears", false}, .fastTears {"game.fastTears", false},
.no2ndFishForCat {"game.no2ndFishForCat", false},
.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},
@@ -51,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},
@@ -132,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);
@@ -143,13 +148,16 @@ 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);
Register(g_userSettings.game.fastTears); Register(g_userSettings.game.fastTears);
Register(g_userSettings.game.no2ndFishForCat);
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

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