mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-05 03:29:45 -04:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93e9767c9f | |||
| c774f53dad | |||
| 7fbfe5ad88 | |||
| ef02037990 | |||
| 23cc18ba0e | |||
| 924dbc7715 | |||
| 742f4938f2 | |||
| 02e0f586d3 | |||
| 5717aeef85 | |||
| da9b99f650 | |||
| dd2b993cd5 | |||
| 83577d3b82 | |||
| 1cb8b19520 | |||
| d109cd891e | |||
| 63c4002ca6 | |||
| 6954efcd15 | |||
| 78c2147771 | |||
| c938d5468e | |||
| 35590c5312 | |||
| e976b10e2a | |||
| 3949706b28 | |||
| ce55916845 | |||
| 9b252cbdd2 | |||
| 97fa09f6ee | |||
| 0e7a7cccb9 | |||
| 1c0cdcc176 | |||
| 280305c2ba | |||
| d1f1d579bc | |||
| eeeb3ffe25 | |||
| 1ee0f862e1 | |||
| 6b327c9f61 | |||
| f148e0ebc1 | |||
| 6f20f4d629 | |||
| 94670270d6 | |||
| 8e21247a97 | |||
| aa84004cb4 | |||
| e1c201f4bd | |||
| 425a1d15a0 | |||
| e3ad41792a | |||
| c1ba10fc8b | |||
| 1c5686f71b | |||
| b9e0f2b9ca | |||
| ede175f141 | |||
| a301874e30 | |||
| 9b4a9a6bd9 | |||
| 04274b1884 | |||
| ca783b8424 | |||
| 2e4c2b5b46 | |||
| 4c09d8b910 | |||
| fb1b260d09 | |||
| 98eb8f718e | |||
| 68b2e0ee2d | |||
| 81771a0522 | |||
| 93c8bcc210 | |||
| 176bf5f0c4 | |||
| 65a5945778 | |||
| 8f7b9cdfdd | |||
| acecba7ff7 | |||
| 507e0aadbc | |||
| 8406d9b192 | |||
| 6f3170cb56 | |||
| 835582224e | |||
| 52879f50f0 | |||
| 2b505f1be4 | |||
| f089f9024d | |||
| 43fb421a93 | |||
| f06d6b50a9 | |||
| 2c987b0211 | |||
| 3d860ad454 | |||
| b5bf19569b | |||
| 5a7f5cb4a7 | |||
| 1affefbbfc | |||
| e4ff38a712 | |||
| f2ac4d6f44 | |||
| 48b98a5432 | |||
| a4752154f7 | |||
| 0d973a497b | |||
| ab4eccf1df | |||
| e0c449f28e | |||
| ce9a5c06d5 | |||
| 2bbba1e4e8 | |||
| 39298f8d8f | |||
| 32b4c0567a | |||
| 7ff1b5332e | |||
| 5e77a60bd6 | |||
| 9629c000bd | |||
| 9dc5fed686 | |||
| 2cc9db77dd | |||
| 8aa08c9443 | |||
| dca3e2eba6 | |||
| 57061fba38 | |||
| cee6a24309 | |||
| 62edb3abc6 | |||
| 1c9e1c0a66 | |||
| a06aeb10c1 | |||
| b84b309e00 | |||
| 62eecb3ccd | |||
| ec07572ced | |||
| b45e2fa34d | |||
| 289a718446 | |||
| 292a2a6c34 | |||
| 2e8415b950 | |||
| dbf1f6e354 | |||
| 9a2fe9745d | |||
| b5ca343fac | |||
| 88c7ff63ff | |||
| 7a438ad30f | |||
| 89649de7c3 | |||
| a1960eaa33 | |||
| d6820c9233 | |||
| 5c4bb8d33d | |||
| e3ce1f01c9 | |||
| 9b6b344ecf | |||
| 3db85d5b44 | |||
| b5871d72d9 | |||
| b70a714f88 | |||
| cad5a8d1bc | |||
| 93f8a5fa8f | |||
| b0809ea78c | |||
| b0e9033736 | |||
| 1ac6df8de7 | |||
| 5899b2157a | |||
| 3185f578fb | |||
| 4fc09799b6 | |||
| fe0e3cad72 | |||
| 37d1aa7f40 | |||
| 4a12554bf4 | |||
| fecd1d5683 | |||
| bce9bf6fd9 | |||
| fbf63b075a | |||
| b86d6e90e2 | |||
| 6425b452e7 | |||
| 1657fe8083 | |||
| ecd74a4cbd | |||
| 36dc43c602 | |||
| d92515f0d4 | |||
| f147dcac0c | |||
| ee4c84f39b | |||
| b8a83c6f59 | |||
| 4462c0ef69 | |||
| c803bfb545 | |||
| 2623c44cab | |||
| 24dd02fc81 | |||
| a97602b6dc | |||
| e2943e90dc | |||
| 3cb7fbd030 | |||
| afe54f22ab | |||
| e15f5bcee9 | |||
| 1e372a856d | |||
| b26896cad5 | |||
| f75faf6b06 | |||
| b48d9aa052 | |||
| d899706208 | |||
| 3e05789b58 | |||
| 9a7b62cbc6 | |||
| 8e0f0e878e | |||
| 79344edf0d | |||
| e59bfd1a9c | |||
| 4d12cc8ea2 | |||
| 2ed2268579 | |||
| 94a99e8da0 | |||
| ddaf50c01d | |||
| 7566949b42 | |||
| bb6db3caea | |||
| 2dc494dc1c | |||
| 3c25633ee9 | |||
| d99ed2729b | |||
| 782455d48b | |||
| 47863b34c2 | |||
| 92391d5eb8 | |||
| 0d37cb4e54 | |||
| ea528ed9d9 | |||
| f7b880c5ea | |||
| ff78bc8d6c | |||
| 6503b4e7eb | |||
| 8fb4ba8924 | |||
| 5f675c6f2b | |||
| b3333241c5 | |||
| e39079c0f8 | |||
| c3317d9232 | |||
| 025cb58493 | |||
| b3dee825e8 | |||
| f6c5aac3c8 | |||
| 25e9064d09 | |||
| 3e1e8f1244 | |||
| 0bf663141a | |||
| d7dced7ddf | |||
| 78b0563c0e | |||
| 871d18e294 | |||
| c157564da6 | |||
| ecc3b00c51 | |||
| 8afb1141ab | |||
| 8c5673d9b8 | |||
| 916dfcd9da | |||
| 842210e539 | |||
| 39d951d0cb | |||
| a4be0841e5 |
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
|||||||
endif ()
|
endif ()
|
||||||
set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
|
set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
|
||||||
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
|
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
|
||||||
|
set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
|
||||||
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
add_subdirectory(libs/freeverb)
|
add_subdirectory(libs/freeverb)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||

|

|
||||||
|
|
||||||
- ### **[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).
|
||||||
|
|||||||
Vendored
+1
-1
Submodule extern/aurora updated: 7784b6fc95...77ad549530
+44
-1
@@ -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,53 @@ 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/controller_config.cpp
|
||||||
|
src/dusk/ui/controller_config.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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ public:
|
|||||||
int Draw();
|
int Draw();
|
||||||
int Delete();
|
int Delete();
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
void onInterpCallback();
|
||||||
|
#endif
|
||||||
|
|
||||||
enum Param_e {
|
enum Param_e {
|
||||||
LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7)
|
LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7)
|
||||||
};
|
};
|
||||||
@@ -50,6 +54,13 @@ private:
|
|||||||
/* 0x1020 */ dCcD_Cyl mCylinderCollider;
|
/* 0x1020 */ dCcD_Cyl mCylinderCollider;
|
||||||
/* 0x115C */ s32 mStopSwingingFrames;
|
/* 0x115C */ s32 mStopSwingingFrames;
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
cXyz mChainInterpPrev[64];
|
||||||
|
cXyz mChainInterpCurr[64];
|
||||||
|
bool mChainInterpPrevValid;
|
||||||
|
bool mChainInterpCurrValid;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Number of chain models
|
// Number of chain models
|
||||||
u32 getArg0() {
|
u32 getArg0() {
|
||||||
return fopAcM_GetParamBit(this, 0, 6);
|
return fopAcM_GetParamBit(this, 0, 6);
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -80,6 +81,7 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> instantSaves;
|
ConfigVar<bool> instantSaves;
|
||||||
ConfigVar<bool> instantText;
|
ConfigVar<bool> instantText;
|
||||||
ConfigVar<bool> sunsSong;
|
ConfigVar<bool> sunsSong;
|
||||||
|
ConfigVar<bool> autoSave;
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
ConfigVar<bool> enableMirrorMode;
|
ConfigVar<bool> enableMirrorMode;
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ public:
|
|||||||
bool isActive() const { return mSeqList.getNumLinks() != 0; }
|
bool isActive() const { return mSeqList.getNumLinks() != 0; }
|
||||||
int getNumActiveSeqs() const { return mSeqList.getNumLinks(); }
|
int getNumActiveSeqs() const { return mSeqList.getNumLinks(); }
|
||||||
void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; }
|
void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; }
|
||||||
|
#if TARGET_PC
|
||||||
|
JSUList<JAISeq>* getSeqList() { return &mSeqList; }
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* 0x08 */ JAIAudience* mAudience;
|
/* 0x08 */ JAIAudience* mAudience;
|
||||||
|
|||||||
@@ -207,4 +207,11 @@ void JPARegistAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
|
|||||||
void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*);
|
void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
|
void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
|
void JPAInterpRotBillboard(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
|
void JPAInterpDirection(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
|
void JPAInterpRotDirection(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* JPABASESHAPE_H */
|
#endif /* JPABASESHAPE_H */
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ public:
|
|||||||
void init_c(JPAEmitterWorkData*, JPABaseParticle*);
|
void init_c(JPAEmitterWorkData*, JPABaseParticle*);
|
||||||
bool calc_p(JPAEmitterWorkData*);
|
bool calc_p(JPAEmitterWorkData*);
|
||||||
bool calc_c(JPAEmitterWorkData*);
|
bool calc_c(JPAEmitterWorkData*);
|
||||||
|
#if TARGET_PC
|
||||||
|
void interp(JPAEmitterWorkData*, void const* drawFunc);
|
||||||
|
#endif
|
||||||
bool canCreateChild(JPAEmitterWorkData*);
|
bool canCreateChild(JPAEmitterWorkData*);
|
||||||
f32 getWidth(JPABaseEmitter const*) const;
|
f32 getWidth(JPABaseEmitter const*) const;
|
||||||
f32 getHeight(JPABaseEmitter const*) const;
|
f32 getHeight(JPABaseEmitter const*) const;
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ public:
|
|||||||
JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); }
|
JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); }
|
||||||
u16 getNumColors() const { return mNumColors; }
|
u16 getNumColors() const { return mNumColors; }
|
||||||
ResTLUT* getColorTable() const { return mColorTable; }
|
ResTLUT* getColorTable() const { return mColorTable; }
|
||||||
|
#if TARGET_PC
|
||||||
|
void dataUploaded();
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* 0x00 */ GXTlutObj mTlutObj;
|
/* 0x00 */ GXTlutObj mTlutObj;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public:
|
|||||||
s32 getTransparency() const { return mTexInfo->alphaEnabled; }
|
s32 getTransparency() const { return mTexInfo->alphaEnabled; }
|
||||||
s32 getWidth() const { return mTexInfo->width; }
|
s32 getWidth() const { return mTexInfo->width; }
|
||||||
s32 getHeight() const { return mTexInfo->height; }
|
s32 getHeight() const { return mTexInfo->height; }
|
||||||
|
JUTPalette* getPalette() const { return mPalette; }
|
||||||
void setCaptureFlag(bool flag) { mFlags &= 2 | flag; }
|
void setCaptureFlag(bool flag) { mFlags &= 2 | flag; }
|
||||||
bool getCaptureFlag() const { return mFlags & 1; }
|
bool getCaptureFlag() const { return mFlags & 1; }
|
||||||
bool getEmbPaletteDelFlag() const { return mFlags & 2; }
|
bool getEmbPaletteDelFlag() const { return mFlags & 2; }
|
||||||
@@ -82,7 +83,7 @@ public:
|
|||||||
int getTlutName() const { return mTlutName; }
|
int getTlutName() const { return mTlutName; }
|
||||||
bool operator==(const JUTTexture& other) {
|
bool operator==(const JUTTexture& other) {
|
||||||
return mTexInfo == other.mTexInfo
|
return mTexInfo == other.mTexInfo
|
||||||
&& field_0x2c == other.field_0x2c
|
&& mPalette == other.mPalette
|
||||||
&& mWrapS == other.mWrapS
|
&& mWrapS == other.mWrapS
|
||||||
&& mWrapT == other.mWrapT
|
&& mWrapT == other.mWrapT
|
||||||
&& mMinFilter == other.mMinFilter
|
&& mMinFilter == other.mMinFilter
|
||||||
@@ -100,7 +101,7 @@ private:
|
|||||||
/* 0x20 */ const ResTIMG* mTexInfo;
|
/* 0x20 */ const ResTIMG* mTexInfo;
|
||||||
/* 0x24 */ void* mTexData;
|
/* 0x24 */ void* mTexData;
|
||||||
/* 0x28 */ JUTPalette* mEmbPalette;
|
/* 0x28 */ JUTPalette* mEmbPalette;
|
||||||
/* 0x2C */ JUTPalette* field_0x2c;
|
/* 0x2C */ JUTPalette* mPalette;
|
||||||
/* 0x30 */ u8 mWrapS;
|
/* 0x30 */ u8 mWrapS;
|
||||||
/* 0x31 */ u8 mWrapT;
|
/* 0x31 */ u8 mWrapT;
|
||||||
/* 0x32 */ u8 mMinFilter;
|
/* 0x32 */ u8 mMinFilter;
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, ¶m_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, ¶m_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,9 @@ bool JUTPalette::load() {
|
|||||||
|
|
||||||
return check;
|
return check;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
void JUTPalette::dataUploaded() {
|
||||||
|
GXInitTlutObjData(&mTlutObj, (void*)mColorTable);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -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.
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 |
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
tab-bar {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: auto hidden;
|
||||||
|
clip: always;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar scrollbarhorizontal,
|
||||||
|
tab-bar scrollbarhorizontal sliderarrowdec,
|
||||||
|
tab-bar scrollbarhorizontal sliderarrowinc,
|
||||||
|
tab-bar scrollbarhorizontal slidertrack,
|
||||||
|
tab-bar scrollbarhorizontal sliderbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar tab {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar[closable] tab-end-spacer {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 64dp;
|
||||||
|
width: 64dp;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar[closable] close {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 8dp;
|
||||||
|
right: 8dp;
|
||||||
|
z-index: 1;
|
||||||
|
width: 48dp;
|
||||||
|
height: 48dp;
|
||||||
|
font-family: "Material Symbols Rounded";
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 24dp;
|
||||||
|
line-height: 48dp;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: none;
|
||||||
|
color: rgba(224, 219, 200, 70%);
|
||||||
|
backdrop-filter: blur(2dp);
|
||||||
|
border-radius: 6dp;
|
||||||
|
transition: color background-color 0.12s linear-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar[closable] close:hover,
|
||||||
|
tab-bar[closable] close:focus-visible {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(194, 164, 45, 24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-bar[closable] close:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(194, 164, 45, 40%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
*, *: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 value.modified {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
select-button input {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 20dp;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "d/actor/d_a_obj_automata.h"
|
#include "d/actor/d_a_obj_automata.h"
|
||||||
#include "d/d_msg_object.h"
|
#include "d/d_msg_object.h"
|
||||||
#include "d/actor/d_a_obj_scannon.h"
|
#include "d/actor/d_a_obj_scannon.h"
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = {
|
const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = {
|
||||||
@@ -1398,6 +1399,7 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) {
|
|||||||
old.pos = current.pos;
|
old.pos = current.pos;
|
||||||
setAngle(cM_deg2s(5.0f * f32(mPath.getArg0())));
|
setAngle(cM_deg2s(5.0f * f32(mPath.getArg0())));
|
||||||
mEventTimer = mPath.getArg2();
|
mEventTimer = mPath.getArg2();
|
||||||
|
dusk::frame_interp::request_presentation_sync();
|
||||||
}
|
}
|
||||||
} else if (!mHide) {
|
} else if (!mHide) {
|
||||||
mHide = 1;
|
mHide = 1;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "d/d_bg_w.h"
|
#include "d/d_bg_w.h"
|
||||||
#include "d/d_cc_uty.h"
|
#include "d/d_cc_uty.h"
|
||||||
#include "d/d_com_inf_game.h"
|
#include "d/d_com_inf_game.h"
|
||||||
|
#include "dusk/frame_interpolation.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
|
|
||||||
struct daObjKLift00_HIO_c : public mDoHIO_entry_c {
|
struct daObjKLift00_HIO_c : public mDoHIO_entry_c {
|
||||||
daObjKLift00_HIO_c();
|
daObjKLift00_HIO_c();
|
||||||
@@ -295,6 +297,11 @@ int daObjKLift00_c::Create() {
|
|||||||
if(getLock())
|
if(getLock())
|
||||||
mStopSwingingFrames = 5;
|
mStopSwingingFrames = 5;
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
mChainInterpPrevValid = false;
|
||||||
|
mChainInterpCurrValid = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +443,34 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
static void klift00_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||||
|
static_cast<daObjKLift00_c*>(pUserWork)->onInterpCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void daObjKLift00_c::onInterpCallback() {
|
||||||
|
if (!mChainInterpPrevValid || !mChainInterpCurrValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||||
|
cXyz savedPositions[64];
|
||||||
|
|
||||||
|
for (int i = 0; i < mNumChains; i++) {
|
||||||
|
savedPositions[i] = mChainPositions[i].mCurrentPos;
|
||||||
|
const cXyz& p0 = mChainInterpPrev[i];
|
||||||
|
const cXyz& p1 = mChainInterpCurr[i];
|
||||||
|
mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMtx();
|
||||||
|
|
||||||
|
for (int i = 0; i < mNumChains; i++) {
|
||||||
|
mChainPositions[i].mCurrentPos = savedPositions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int daObjKLift00_c::Draw() {
|
int daObjKLift00_c::Draw() {
|
||||||
g_env_light.settingTevStruct(16, ¤t.pos, &tevStr);
|
g_env_light.settingTevStruct(16, ¤t.pos, &tevStr);
|
||||||
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
|
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
|
||||||
@@ -457,6 +492,22 @@ int daObjKLift00_c::Draw() {
|
|||||||
|
|
||||||
dComIfGd_setList();
|
dComIfGd_setList();
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||||
|
if (mChainInterpCurrValid) {
|
||||||
|
memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz));
|
||||||
|
mChainInterpPrevValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < mNumChains; i++) {
|
||||||
|
mChainInterpCurr[i] = mChainPositions[i].mCurrentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
mChainInterpCurrValid = true;
|
||||||
|
dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+55
-43
@@ -1175,6 +1175,12 @@ bool dCamera_c::Run() {
|
|||||||
clrFlag(0x200000);
|
clrFlag(0x200000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
#if TARGET_PC
|
||||||
|
if (mCamParam.Algorythmn(mCamStyle) != 1) {
|
||||||
|
mCamParam.mManualMode = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
|
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
|
||||||
|
|
||||||
field_0x170++;
|
field_0x170++;
|
||||||
@@ -1481,7 +1487,7 @@ void dCamera_c::CalcTrimSize() {
|
|||||||
mTrimHeight += -mTrimHeight * 0.25f;
|
mTrimHeight += -mTrimHeight * 0.25f;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
#if WIDESCREEN_SUPPORT
|
#if !TARGET_PC && WIDESCREEN_SUPPORT
|
||||||
if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) {
|
if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) {
|
||||||
mTrimHeight += (16.0f - mTrimHeight) * 0.25f;
|
mTrimHeight += (16.0f - mTrimHeight) * 0.25f;
|
||||||
break;
|
break;
|
||||||
@@ -3089,10 +3095,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) {
|
|||||||
field_0x968 *= mMonitor.field_0xc / 5.0f;
|
field_0x968 *= mMonitor.field_0xc / 5.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_PC
|
|
||||||
if (!dusk::getSettings().game.freeCamera || !mCamParam.mManualMode) {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f);
|
f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f);
|
||||||
center += vec3.norm() * (tmp * globe.V().Sin());
|
center += vec3.norm() * (tmp * globe.V().Sin());
|
||||||
cSGlobe globe2(vec2 - center);
|
cSGlobe globe2(vec2 - center);
|
||||||
@@ -3106,10 +3108,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) {
|
|||||||
vec = lin_chk1.GetCross();
|
vec = lin_chk1.GetCross();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_PC
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (mCamSetup.CheckFlag(0x8000)) {
|
if (mCamSetup.CheckFlag(0x8000)) {
|
||||||
dDbVw_Report(20, 235, " U");
|
dDbVw_Report(20, 235, " U");
|
||||||
@@ -3520,12 +3518,6 @@ void dCamera_c::checkGroundInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool dCamera_c::chaseCamera(s32 param_0) {
|
bool dCamera_c::chaseCamera(s32 param_0) {
|
||||||
#if TARGET_PC
|
|
||||||
if (freeCamera()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static f32 JumpCushion = 0.9f;
|
static f32 JumpCushion = 0.9f;
|
||||||
f32 charge_latitude = mCamSetup.ChargeLatitude();
|
f32 charge_latitude = mCamSetup.ChargeLatitude();
|
||||||
int charge_timer = mCamSetup.ChargeTimer();
|
int charge_timer = mCamSetup.ChargeTimer();
|
||||||
@@ -4207,6 +4199,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
|
|||||||
chase->field_0x8 -= chase->field_0xc;
|
chase->field_0x8 -= chase->field_0xc;
|
||||||
chase->field_0x8c = 0;
|
chase->field_0x8c = 0;
|
||||||
chase->field_0x90 = false;
|
chase->field_0x90 = false;
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
freeCamera();
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4644,6 +4641,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
|
|||||||
if (chase->field_0x1c != 0) {
|
if (chase->field_0x1c != 0) {
|
||||||
chase->field_0x1c--;
|
chase->field_0x1c--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
freeCamera();
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7091,10 +7093,12 @@ bool dCamera_c::subjectCamera(s32 param_0) {
|
|||||||
cXyz sp1E0(val0, val2, val1);
|
cXyz sp1E0(val0, val2, val1);
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
f32 aspect = mDoGph_gInf_c::getAspect();
|
if (sp13) {
|
||||||
f32 baseAspect = FB_WIDTH / FB_HEIGHT;
|
f32 aspect = mDoGph_gInf_c::getAspect();
|
||||||
if (aspect > baseAspect) {
|
f32 baseAspect = FB_WIDTH / FB_HEIGHT;
|
||||||
sp1E0.z += (aspect - baseAspect) * 4;
|
if (aspect > baseAspect) {
|
||||||
|
sp1E0.z += (aspect - baseAspect) * 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -7472,52 +7476,47 @@ bool dCamera_c::test2Camera(s32 param_0) {
|
|||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
bool dCamera_c::freeCamera() {
|
bool dCamera_c::freeCamera() {
|
||||||
if (!dusk::getSettings().game.freeCamera) {
|
if (dusk::getSettings().game.freeCamera && mGear == 1) {
|
||||||
|
mGear = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dusk::getSettings().game.freeCamera || mCamStyle == 70)
|
||||||
|
{
|
||||||
mCamParam.mManualMode = 0;
|
mCamParam.mManualMode = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
|
if (!mCamParam.mManualMode) {
|
||||||
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
|
mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
|
||||||
|
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
|
||||||
|
}
|
||||||
|
|
||||||
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
|
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
|
||||||
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
|
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
|
||||||
|
|
||||||
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) {
|
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) {
|
||||||
if (!mCamParam.mManualMode) {
|
mCamParam.mManualMode = 1;
|
||||||
mCamParam.mManualMode = 1;
|
|
||||||
mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
|
|
||||||
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
|
|
||||||
}
|
|
||||||
|
|
||||||
camMovement = camMovement.normalize();
|
camMovement = camMovement.normalize();
|
||||||
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
|
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
|
||||||
mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f;
|
mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
|
||||||
mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f;
|
mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mCamParam.mManualMode) {
|
fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||||
|
if (!mCamParam.mManualMode || player == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
f32 minYAngle = -10.0f;
|
f32 minYAngle = -30.0f;
|
||||||
f32 maxAngle = 50.0f;
|
f32 maxAngle = 50.0f;
|
||||||
|
|
||||||
mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle);
|
mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle);
|
||||||
mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle);
|
mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle);
|
||||||
mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
|
mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
|
||||||
f32 currentLerp = (mCamParam.freeYAngle - minYAngle) / (maxAngle - minYAngle);
|
|
||||||
mViewCache.mDirection.mRadius = std::lerp(200.0f, 1000.0f, currentLerp);
|
|
||||||
|
|
||||||
cXyz finalCenter = mpPlayerActor->current.pos;
|
cXyz finalEye = mViewCache.mCenter + mViewCache.mDirection.Xyz();
|
||||||
finalCenter.y += mIsWolf ? 90.0f : 100.0f;
|
|
||||||
mViewCache.mCenter = finalCenter;
|
|
||||||
|
|
||||||
cXyz finalEye = finalCenter + mViewCache.mDirection.Xyz();
|
|
||||||
mViewCache.mEye = finalEye;
|
mViewCache.mEye = finalEye;
|
||||||
|
|
||||||
mViewCache.mFovy = 60.0f;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -11161,12 +11160,25 @@ static int camera_draw(camera_process_class* i_this) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int trim_height = body->TrimHeight();
|
|
||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
|
auto trim_height = body->TrimHeight();
|
||||||
|
|
||||||
|
if (mDoGph_gInf_c::isWideZoom()) {
|
||||||
|
const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f);
|
||||||
|
const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF;
|
||||||
|
|
||||||
|
if (current_ar < target_ar) {
|
||||||
|
trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar);
|
||||||
|
} else {
|
||||||
|
trim_height = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trim_height *= viewport->height / FB_HEIGHT;
|
trim_height *= viewport->height / FB_HEIGHT;
|
||||||
window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
|
window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
|
||||||
#else
|
#else
|
||||||
|
int trim_height = body->TrimHeight();
|
||||||
|
|
||||||
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
|
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+45
-9
@@ -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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
if (ret <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
++ret;
|
||||||
|
std::unique_ptr<char[]> buf(new char[ret]);
|
||||||
|
vsnprintf(buf.get(), ret, msg, list);
|
||||||
|
buf[ret - 1] = '\0';
|
||||||
|
return {buf.get()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSReport(const char* fmt, ...) {
|
||||||
|
if (!checkEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
const auto str = FormatToString(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
Log.info("{}", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSPanic(const char* file, int line, const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
const auto str = FormatToString(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
Log.fatal("[{}:{}] {}", file, line, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSReport_Error(const char* fmt, ...) {
|
||||||
|
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
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -320,22 +320,18 @@ namespace dusk {
|
|||||||
ImGuiMenuGame::ToggleFullscreen();
|
ImGuiMenuGame::ToggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) {
|
// if (!dusk::IsGameLaunched) {
|
||||||
ImGuiMenuGame::ToggleFullscreen();
|
// m_preLaunchWindow.draw();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!dusk::IsGameLaunched) {
|
|
||||||
m_preLaunchWindow.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_isHidden = !getSettings().backend.duskMenuOpen;
|
m_isHidden = !getSettings().backend.duskMenuOpen;
|
||||||
bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden);
|
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||||
if (dusk::IsGameLaunched) {
|
m_isHidden = !m_isHidden;
|
||||||
const bool menuOpen = !m_isHidden;
|
}
|
||||||
if (getSettings().backend.duskMenuOpen != menuOpen) {
|
bool showMenu = !m_isHidden;
|
||||||
getSettings().backend.duskMenuOpen.setValue(menuOpen);
|
if (getSettings().backend.duskMenuOpen != showMenu) {
|
||||||
Save();
|
getSettings().backend.duskMenuOpen.setValue(showMenu);
|
||||||
}
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
|
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
|
||||||
@@ -365,10 +361,10 @@ namespace dusk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||||
m_toasts.emplace_back(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
|
AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
|
||||||
"Tap to toggle menu"s :
|
"Tap to toggle menu"s :
|
||||||
"Press F1 to toggle menu"s,
|
"Press F1 to toggle menu"s,
|
||||||
2.5f);
|
4.f);
|
||||||
m_isLaunchInitialized = true;
|
m_isLaunchInitialized = true;
|
||||||
if (getSettings().game.liveSplitEnabled) {
|
if (getSettings().game.liveSplitEnabled) {
|
||||||
dusk::speedrun::connectLiveSplit();
|
dusk::speedrun::connectLiveSplit();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ static void ApplyPresetDusk() {
|
|||||||
s.game.enableFrameInterpolation.setValue(true);
|
s.game.enableFrameInterpolation.setValue(true);
|
||||||
s.game.sunsSong.setValue(true);
|
s.game.sunsSong.setValue(true);
|
||||||
s.game.bloomMode.setValue(BloomMode::Dusk);
|
s.game.bloomMode.setValue(BloomMode::Dusk);
|
||||||
|
s.game.autoSave.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ namespace dusk {
|
|||||||
void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) {
|
void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) {
|
||||||
char title[128];
|
char title[128];
|
||||||
const char* name = data.Safe ? heap->getName() : "INVALID";
|
const char* name = data.Safe ? heap->getName() : "INVALID";
|
||||||
snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast<const void*>(heap));
|
snprintf(title, sizeof(title), "Heap %s##%p", name, static_cast<const void*>(heap));
|
||||||
|
|
||||||
if (!ImGui::Begin(name, &open)) {
|
if (!ImGui::Begin(title, &open)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ namespace dusk {
|
|||||||
|
|
||||||
heap->lock();
|
heap->lock();
|
||||||
|
|
||||||
ImGui::Text("Name: %s", heap->getName());
|
ImGui::Text("Name: %s", name);
|
||||||
const auto size = BytesToString(heap->getSize());
|
const auto size = BytesToString(heap->getSize());
|
||||||
const auto freeSize = BytesToString(heap->getFreeSize());
|
const auto freeSize = BytesToString(heap->getFreeSize());
|
||||||
ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str());
|
ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str());
|
||||||
|
|||||||
@@ -3,31 +3,13 @@
|
|||||||
|
|
||||||
#include "ImGuiEngine.hpp"
|
#include "ImGuiEngine.hpp"
|
||||||
#include "ImGuiConsole.hpp"
|
#include "ImGuiConsole.hpp"
|
||||||
#include "ImGuiMenuGame.hpp"
|
|
||||||
#include "ImGuiConfig.hpp"
|
#include "ImGuiConfig.hpp"
|
||||||
|
|
||||||
#include "JSystem/JUtility/JUTGamePad.h"
|
|
||||||
#include "dusk/audio/DuskAudioSystem.h"
|
|
||||||
#include "dusk/audio/DuskDsp.hpp"
|
|
||||||
#include "dusk/main.h"
|
#include "dusk/main.h"
|
||||||
#include "dusk/hotkeys.h"
|
#include "dusk/hotkeys.h"
|
||||||
#include "dusk/settings.h"
|
|
||||||
#include "dusk/livesplit.h"
|
|
||||||
#include "m_Do/m_Do_controller_pad.h"
|
|
||||||
#include "m_Do/m_Do_graphic.h"
|
|
||||||
|
|
||||||
#include <aurora/gfx.h>
|
|
||||||
#include <SDL3/SDL_gamepad.h>
|
|
||||||
|
|
||||||
#include "m_Do/m_Do_main.h"
|
#include "m_Do/m_Do_main.h"
|
||||||
|
|
||||||
namespace {
|
#include <SDL3/SDL_gamepad.h>
|
||||||
constexpr int kInternalResolutionScaleMax = 12;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace aurora::gx {
|
|
||||||
extern bool enableLodBias;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk {
|
||||||
void ImGuiMenuGame::ToggleFullscreen() {
|
void ImGuiMenuGame::ToggleFullscreen() {
|
||||||
@@ -40,475 +22,17 @@ namespace dusk {
|
|||||||
|
|
||||||
void ImGuiMenuGame::draw() {
|
void ImGuiMenuGame::draw() {
|
||||||
if (ImGui::BeginMenu("Settings")) {
|
if (ImGui::BeginMenu("Settings")) {
|
||||||
drawAudioMenu();
|
// TODO: Remove this once Controller Config exists in RmlUi
|
||||||
drawCheatsMenu();
|
|
||||||
drawGameplayMenu();
|
|
||||||
drawGraphicsMenu();
|
|
||||||
drawInputMenu();
|
|
||||||
drawInterfaceMenu();
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Reset", hotkeys::DO_RESET)) {
|
|
||||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsMobile && ImGui::MenuItem("Exit")) {
|
|
||||||
dusk::IsRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGuiMenuGame::drawGraphicsMenu() {
|
|
||||||
if (ImGui::BeginMenu("Graphics")) {
|
|
||||||
ImGui::SeparatorText("Display");
|
|
||||||
|
|
||||||
if (!IsMobile) {
|
|
||||||
if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) {
|
|
||||||
ToggleFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Button("Restore Default Window Size")) {
|
|
||||||
getSettings().video.enableFullscreen.setValue(false);
|
|
||||||
VISetWindowFullscreen(false);
|
|
||||||
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
|
||||||
VICenterWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
bool vsync = getSettings().video.enableVsync;
|
|
||||||
if (ImGui::Checkbox("Enable VSync", &vsync)) {
|
|
||||||
getSettings().video.enableVsync.setValue(vsync);
|
|
||||||
aurora_enable_vsync(vsync);
|
|
||||||
config::Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lockAspect = getSettings().video.lockAspectRatio;
|
|
||||||
if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) {
|
|
||||||
getSettings().video.lockAspectRatio.setValue(lockAspect);
|
|
||||||
|
|
||||||
if (lockAspect) {
|
|
||||||
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
|
||||||
} else {
|
|
||||||
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
config::Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Resolution");
|
|
||||||
|
|
||||||
u32 internalResolutionWidth = 0;
|
|
||||||
u32 internalResolutionHeight = 0;
|
|
||||||
AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight);
|
|
||||||
ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth,
|
|
||||||
internalResolutionHeight);
|
|
||||||
|
|
||||||
int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
|
|
||||||
kInternalResolutionScaleMax);
|
|
||||||
if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax,
|
|
||||||
scale == 0 ? "Auto" : "%dx"))
|
|
||||||
{
|
|
||||||
getSettings().game.internalResolutionScale.setValue(scale);
|
|
||||||
VISetFrameBufferScale(static_cast<float>(scale));
|
|
||||||
config::Save();
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Auto renders at the native window resolution.\n"
|
|
||||||
"Higher values scale the game's internal framebuffer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d");
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Improves the shadow resolution, making them higher quality.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Post-Processing");
|
|
||||||
|
|
||||||
constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"};
|
|
||||||
int bloomMode = static_cast<int>(getSettings().game.bloomMode.getValue());
|
|
||||||
if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) {
|
|
||||||
for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) {
|
|
||||||
const bool selected = bloomMode == i;
|
|
||||||
if (ImGui::Selectable(bloomModeNames[i], selected)) {
|
|
||||||
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(i));
|
|
||||||
config::Save();
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
ImGui::SetItemDefaultFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bloomOff = bloomMode == static_cast<int>(BloomMode::Off);
|
|
||||||
if (bloomOff) ImGui::BeginDisabled();
|
|
||||||
float mult = getSettings().game.bloomMultiplier.getValue();
|
|
||||||
if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) {
|
|
||||||
getSettings().game.bloomMultiplier.setValue(mult);
|
|
||||||
config::Save();
|
|
||||||
}
|
|
||||||
if (bloomOff) ImGui::EndDisabled();
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Rendering");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation);
|
|
||||||
const bool frameInterpolationHovered = ImGui::IsItemHovered();
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
|
|
||||||
ImGui::TextUnformatted("[EXPERIMENTAL]");
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
if (frameInterpolationHovered || ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField);
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Enable Mini-Map Shadows", getSettings().game.enableMapBackground);
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGuiMenuGame::drawGameplayMenu() {
|
|
||||||
if (ImGui::BeginMenu("Gameplay")) {
|
|
||||||
ImGui::SeparatorText("General");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Disables the main HUD of the game.\n"
|
|
||||||
"Useful for recording or a more immersive experience!");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n"
|
|
||||||
"the first released version.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Difficulty");
|
|
||||||
|
|
||||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
||||||
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Any hit will instantly kill you.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
|
|
||||||
"pots and various other places.");
|
|
||||||
}
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Quality of Life");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Skip the delay when writing to the Memory Card.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Make text scroll immediately by holding B.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Prevents Link from playing a struggle animation\n"
|
|
||||||
"when grabbing ledges or climbing on vines.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Link won't recoil when his sword hits walls.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Only need to fish once for Sera's cat to return.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Skip the warning screen shown when starting the game.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Speedrunning");
|
|
||||||
if (config::ImGuiCheckbox("Speedrun Mode", getSettings().game.speedrunMode)) {
|
|
||||||
resetForSpeedrunMode();
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Enables Speedrunning options, while restricting certain gameplay modifiers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::BeginDisabled(!getSettings().game.speedrunMode);
|
|
||||||
bool prevLiveSplit = getSettings().game.liveSplitEnabled;
|
|
||||||
config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834.");
|
|
||||||
}
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) {
|
|
||||||
if (getSettings().game.liveSplitEnabled) {
|
|
||||||
dusk::speedrun::connectLiveSplit();
|
|
||||||
} else {
|
|
||||||
dusk::speedrun::disconnectLiveSplit();
|
|
||||||
DuskToast("LiveSplit disconnected", 3.f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGuiMenuGame::drawCheatsMenu() {
|
|
||||||
if (ImGui::BeginMenu("Cheats")) {
|
|
||||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Resources");
|
|
||||||
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
|
|
||||||
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
|
|
||||||
config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs);
|
|
||||||
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
|
|
||||||
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
|
|
||||||
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
|
|
||||||
config::ImGuiCheckbox("No Item Timer", getSettings().game.enableIndefiniteItemDrops);
|
|
||||||
ImGui::SetItemTooltip("Item drops such as Rupees, Hearts, etc. will never disappear after they drop.");
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Abilities");
|
|
||||||
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
|
|
||||||
config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot);
|
|
||||||
config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin);
|
|
||||||
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Allows you to transform even if NPCs are looking.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Speeds up Spinner movement when holding R.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Makes the magic armor work without rupees.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGuiMenuGame::drawAudioMenu() {
|
|
||||||
if (ImGui::BeginMenu("Audio")) {
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Volume");
|
|
||||||
|
|
||||||
ImGui::Text("Master Volume");
|
|
||||||
if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) {
|
|
||||||
dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: Implement additional settings
|
|
||||||
ImGui::Text("Main Music Volume");
|
|
||||||
ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100);
|
|
||||||
|
|
||||||
ImGui::Text("Sub Music Volume");
|
|
||||||
ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100);
|
|
||||||
|
|
||||||
ImGui::Text("Sound Effects Volume");
|
|
||||||
ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100);
|
|
||||||
|
|
||||||
ImGui::Text("Fanfare Volume");
|
|
||||||
ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100);
|
|
||||||
|
|
||||||
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
|
|
||||||
if (audioMgr != nullptr) {
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Effects");
|
|
||||||
|
|
||||||
if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) {
|
|
||||||
dusk::audio::SetEnableReverb(getSettings().audio.enableReverb);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Tweaks");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Disable the beeping sound when having low health.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImGuiMenuGame::drawInputMenu() {
|
|
||||||
if (ImGui::BeginMenu("Input")) {
|
|
||||||
ImGui::SeparatorText("Controller");
|
|
||||||
|
|
||||||
if (ImGui::Button("Configure Controller")){
|
if (ImGui::Button("Configure Controller")){
|
||||||
m_showControllerConfig = !m_showControllerConfig;
|
m_showControllerConfig = !m_showControllerConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SeparatorText("Camera");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Free Camera", getSettings().game.freeCamera);
|
|
||||||
|
|
||||||
if (getSettings().game.freeCamera) {
|
|
||||||
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
|
|
||||||
config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis);
|
|
||||||
config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f");
|
|
||||||
} else {
|
|
||||||
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Gyro");
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Enables the gyroscope on supported controllers\n"
|
|
||||||
"while in look mode (C-Up) and while aiming the\n"
|
|
||||||
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n"
|
|
||||||
"Ball and Chain, and Dominion Rod.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n"
|
|
||||||
"tilt the Rollgoal table in Hena's Cabin.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) {
|
|
||||||
config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f");
|
|
||||||
config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f");
|
|
||||||
|
|
||||||
if (getSettings().game.enableGyroRollgoal) {
|
|
||||||
config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f");
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Additional multiplier for scaling how strongly\n"
|
|
||||||
"the gyroscope affects the Rollgoal table.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f");
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n"
|
|
||||||
"reducing drift and jitter when the controller is still.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f");
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Low values track raw gyro input more closely,\n"
|
|
||||||
"while higher values smooth out input over time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch);
|
|
||||||
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SeparatorText("Tools");
|
|
||||||
|
|
||||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
||||||
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
|
|
||||||
}
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
|
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
|
||||||
|
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiMenuGame::drawInterfaceMenu() {
|
|
||||||
if (ImGui::BeginMenu("Interface")) {
|
|
||||||
config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications);
|
|
||||||
config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI);
|
|
||||||
config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation);
|
|
||||||
#if DUSK_ENABLE_SENTRY_NATIVE
|
|
||||||
config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting);
|
|
||||||
#endif
|
|
||||||
if (!IsMobile) {
|
|
||||||
config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void drawVirtualStick(const char* id, const ImVec2& stick) {
|
static void drawVirtualStick(const char* id, const ImVec2& stick) {
|
||||||
float scale = ImGuiScale();
|
float scale = ImGuiScale();
|
||||||
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
|
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
|
||||||
|
|||||||
@@ -55,13 +55,6 @@ namespace dusk {
|
|||||||
static void resetForSpeedrunMode();
|
static void resetForSpeedrunMode();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawAudioMenu();
|
|
||||||
void drawInputMenu();
|
|
||||||
void drawGraphicsMenu();
|
|
||||||
void drawGameplayMenu();
|
|
||||||
void drawCheatsMenu();
|
|
||||||
void drawInterfaceMenu();
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int m_selectedPort = 0;
|
int m_selectedPort = 0;
|
||||||
bool m_isReading = false;
|
bool m_isReading = false;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ UserSettings g_userSettings = {
|
|||||||
.soundEffectsVolume {"audio.soundEffectsVolume", 100},
|
.soundEffectsVolume {"audio.soundEffectsVolume", 100},
|
||||||
.fanfareVolume {"audio.fanfareVolume", 100},
|
.fanfareVolume {"audio.fanfareVolume", 100},
|
||||||
.enableReverb {"audio.enableReverb", true},
|
.enableReverb {"audio.enableReverb", true},
|
||||||
|
.enableHrtf {"audio.enableHrtf", false},
|
||||||
},
|
},
|
||||||
|
|
||||||
.game = {
|
.game = {
|
||||||
@@ -31,7 +32,7 @@ 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},
|
.noHeartDrops {"game.noHeartDrops", false},
|
||||||
.instantDeath {"game.instantDeath", false},
|
.instantDeath {"game.instantDeath", false},
|
||||||
.fastClimbing {"game.fastClimbing", false},
|
.fastClimbing {"game.fastClimbing", false},
|
||||||
.noMissClimbing {"game.noMissClimbing", false},
|
.noMissClimbing {"game.noMissClimbing", false},
|
||||||
@@ -40,6 +41,7 @@ UserSettings g_userSettings = {
|
|||||||
.instantSaves {"game.instantSaves", false},
|
.instantSaves {"game.instantSaves", false},
|
||||||
.instantText {"game.instantText", false},
|
.instantText {"game.instantText", false},
|
||||||
.sunsSong {"game.sunsSong", false},
|
.sunsSong {"game.sunsSong", false},
|
||||||
|
.autoSave {"game.autoSave", false},
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
.enableMirrorMode {"game.enableMirrorMode", false},
|
.enableMirrorMode {"game.enableMirrorMode", false},
|
||||||
@@ -52,7 +54,7 @@ UserSettings g_userSettings = {
|
|||||||
.bloomMode {"game.bloomMode", BloomMode::Classic},
|
.bloomMode {"game.bloomMode", BloomMode::Classic},
|
||||||
.bloomMultiplier {"game.bloomMultiplier", 1.0f},
|
.bloomMultiplier {"game.bloomMultiplier", 1.0f},
|
||||||
.disableWaterRefraction {"game.disableWaterRefraction", false},
|
.disableWaterRefraction {"game.disableWaterRefraction", false},
|
||||||
.enableFrameInterpolation = {"game.enableFrameInterpolation", false},
|
.enableFrameInterpolation {"game.enableFrameInterpolation", false},
|
||||||
.internalResolutionScale {"game.internalResolutionScale", 0},
|
.internalResolutionScale {"game.internalResolutionScale", 0},
|
||||||
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
|
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
|
||||||
.enableDepthOfField {"game.enableDepthOfField", true},
|
.enableDepthOfField {"game.enableDepthOfField", true},
|
||||||
@@ -133,6 +135,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);
|
||||||
@@ -152,6 +155,7 @@ void registerSettings() {
|
|||||||
Register(g_userSettings.game.instantSaves);
|
Register(g_userSettings.game.instantSaves);
|
||||||
Register(g_userSettings.game.instantText);
|
Register(g_userSettings.game.instantText);
|
||||||
Register(g_userSettings.game.sunsSong);
|
Register(g_userSettings.game.sunsSong);
|
||||||
|
Register(g_userSettings.game.autoSave);
|
||||||
Register(g_userSettings.game.enableMirrorMode);
|
Register(g_userSettings.game.enableMirrorMode);
|
||||||
Register(g_userSettings.game.invertCameraXAxis);
|
Register(g_userSettings.game.invertCameraXAxis);
|
||||||
Register(g_userSettings.game.invertCameraYAxis);
|
Register(g_userSettings.game.invertCameraYAxis);
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#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)), mIsModified(std::move(props.isModified)) {}
|
||||||
|
|
||||||
|
bool BoolButton::modified() const {
|
||||||
|
if (mIsModified) {
|
||||||
|
return mIsModified();
|
||||||
|
}
|
||||||
|
return BaseControlledSelectButton::modified();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#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;
|
||||||
|
std::function<bool()> isModified;
|
||||||
|
};
|
||||||
|
|
||||||
|
BoolButton(Rml::Element* parent, Props props);
|
||||||
|
|
||||||
|
bool modified() const override;
|
||||||
|
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;
|
||||||
|
std::function<bool()> mIsModified;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
#include "controller_config.hpp"
|
||||||
|
|
||||||
|
#include "bool_button.hpp"
|
||||||
|
#include "button.hpp"
|
||||||
|
#include "pane.hpp"
|
||||||
|
#include "select_button.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gamepad.h>
|
||||||
|
#include <SDL3/SDL_keyboard.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dusk::ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Rml::String current_controller_name(int port) {
|
||||||
|
const char* name = PADGetName(port);
|
||||||
|
return name == nullptr ? "None" : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::String controller_index_name(u32 index) {
|
||||||
|
const char* name = PADGetNameForControllerIndex(index);
|
||||||
|
if (name == nullptr) {
|
||||||
|
return fmt::format("Controller {}", index + 1);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Gamepad* gamepad_for_port(int port) {
|
||||||
|
const s32 index = PADGetIndexForPort(port);
|
||||||
|
if (index < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return PADGetSDLGamepadForIndex(static_cast<u32>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpecificButtonName {
|
||||||
|
SDL_GamepadType type;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ButtonNames {
|
||||||
|
SDL_GamepadButton button;
|
||||||
|
std::vector<SpecificButtonName> names;
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
const std::vector<ButtonNames> kGamepadButtonNames = {
|
||||||
|
{ SDL_GAMEPAD_BUTTON_LEFT_STICK, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "L3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "L3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "L3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"},
|
||||||
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"},
|
||||||
|
}},
|
||||||
|
{ SDL_GAMEPAD_BUTTON_RIGHT_STICK, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "R3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "R3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "R3"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"},
|
||||||
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"},
|
||||||
|
}},
|
||||||
|
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "L1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "L1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "L1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "LB"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "LB"},
|
||||||
|
}},
|
||||||
|
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "R1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "R1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "R1"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "RB"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "RB"},
|
||||||
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Z"},
|
||||||
|
}},
|
||||||
|
{ SDL_GAMEPAD_BUTTON_BACK, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "Select"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "Share"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "Create"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "Back"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "View"},
|
||||||
|
}},
|
||||||
|
{ SDL_GAMEPAD_BUTTON_START, {
|
||||||
|
{SDL_GAMEPAD_TYPE_PS3, "Start"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS4, "Options"},
|
||||||
|
{SDL_GAMEPAD_TYPE_PS5, "Options"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOX360, "Start"},
|
||||||
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Menu"},
|
||||||
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"},
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
|
||||||
|
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
|
||||||
|
return "Not bound";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
|
||||||
|
if (gamepad != nullptr) {
|
||||||
|
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_A:
|
||||||
|
return "A";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_B:
|
||||||
|
return "B";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_X:
|
||||||
|
return "X";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_Y:
|
||||||
|
return "Y";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
|
||||||
|
return "Cross";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
|
||||||
|
return "Circle";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
|
||||||
|
return "Triangle";
|
||||||
|
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
|
||||||
|
return "Square";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GamepadType type =
|
||||||
|
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||||
|
for (const auto& buttonNames : kGamepadButtonNames) {
|
||||||
|
if (buttonNames.button != button) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& name : buttonNames.names) {
|
||||||
|
if (name.type == type) {
|
||||||
|
return name.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (button) {
|
||||||
|
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||||
|
return "D-pad left";
|
||||||
|
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||||
|
return "D-pad right";
|
||||||
|
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||||
|
return "D-pad up";
|
||||||
|
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||||
|
return "D-pad down";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad) {
|
||||||
|
if (mapping.nativeAxis.nativeAxis != -1) {
|
||||||
|
Rml::String value = PADGetNativeAxisName(mapping.nativeAxis);
|
||||||
|
if (mapping.padAxis != PAD_AXIS_TRIGGER_L && mapping.padAxis != PAD_AXIS_TRIGGER_R) {
|
||||||
|
value += mapping.nativeAxis.sign == AXIS_SIGN_POSITIVE ? "+" : "-";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.nativeButton != -1) {
|
||||||
|
return native_button_name(gamepad, static_cast<u32>(mapping.nativeButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Not bound";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_dpad_button(PADButton button) {
|
||||||
|
return button == PAD_BUTTON_UP || button == PAD_BUTTON_DOWN || button == PAD_BUTTON_LEFT ||
|
||||||
|
button == PAD_BUTTON_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_action_button(PADButton button) {
|
||||||
|
return button == PAD_BUTTON_A || button == PAD_BUTTON_B || button == PAD_BUTTON_X ||
|
||||||
|
button == PAD_BUTTON_Y || button == PAD_BUTTON_START || button == PAD_TRIGGER_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool input_neutral(int port) {
|
||||||
|
if (port < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return PADGetNativeButtonPressed(port) == -1 && PADGetNativeAxisPulled(port).nativeAxis == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Keydown event with KI_ESCAPE may have been dispatched from the controller bindings,
|
||||||
|
// so instead poll the keyboard input directly for Escape-to-unbind
|
||||||
|
bool keyboard_escape_pressed() {
|
||||||
|
int keyCount = 0;
|
||||||
|
const bool* keys = SDL_GetKeyboardState(&keyCount);
|
||||||
|
return keys != nullptr && SDL_SCANCODE_ESCAPE < keyCount && keys[SDL_SCANCODE_ESCAPE];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ControllerConfigWindow::ControllerConfigWindow() {
|
||||||
|
listen(
|
||||||
|
Rml::EventId::Keydown,
|
||||||
|
[this](Rml::Event& event) {
|
||||||
|
if (capture_active() || mSuppressNavigationUntilNeutral) {
|
||||||
|
event.StopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
if (auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr) {
|
||||||
|
if (auto* root = context->GetRootElement()) {
|
||||||
|
mListeners.emplace_back(std::make_unique<ScopedEventListener>(
|
||||||
|
root, "controllerchange", [this](Rml::Event&) { refresh_controller_page(); }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int port = PAD_CHAN0; port < PAD_CHANMAX; ++port) {
|
||||||
|
add_tab(fmt::format("Port {}", port + 1), [this, port](Rml::Element* content) {
|
||||||
|
if (mPendingPort != -1 && mPendingPort != port) {
|
||||||
|
cancel_pending_binding();
|
||||||
|
}
|
||||||
|
build_port_tab(content, port);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::hide(bool close) {
|
||||||
|
cancel_pending_binding();
|
||||||
|
Window::hide(close);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::update() {
|
||||||
|
poll_pending_binding();
|
||||||
|
Window::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
|
||||||
|
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||||
|
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||||
|
mRightPane = &rightPane;
|
||||||
|
mActivePort = port;
|
||||||
|
|
||||||
|
auto showPage = [this, &rightPane, port](Page page) {
|
||||||
|
mPage = page;
|
||||||
|
render_page(rightPane, port, page);
|
||||||
|
};
|
||||||
|
auto addPageButton = [&leftPane, showPage](Page page, Rml::String key, auto getValue) {
|
||||||
|
leftPane
|
||||||
|
.add_select_button({
|
||||||
|
.key = std::move(key),
|
||||||
|
.getValue = std::move(getValue),
|
||||||
|
})
|
||||||
|
.on_focus([showPage, page](Rml::Event&) { showPage(page); })
|
||||||
|
.on_pressed([showPage, page] { showPage(page); });
|
||||||
|
};
|
||||||
|
|
||||||
|
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); });
|
||||||
|
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); });
|
||||||
|
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); });
|
||||||
|
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); });
|
||||||
|
|
||||||
|
leftPane.add_section("Options");
|
||||||
|
leftPane
|
||||||
|
.add_child<BoolButton>(BoolButton::Props{
|
||||||
|
.key = "Enable Dead Zones",
|
||||||
|
.getValue =
|
||||||
|
[port] {
|
||||||
|
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||||
|
return deadZones != nullptr && deadZones->useDeadzones;
|
||||||
|
},
|
||||||
|
.setValue =
|
||||||
|
[port](bool value) {
|
||||||
|
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||||
|
deadZones->useDeadzones = value;
|
||||||
|
PADSerializeMappings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||||
|
})
|
||||||
|
.on_focus([&rightPane](Rml::Event&) {
|
||||||
|
rightPane.clear();
|
||||||
|
rightPane.add_text("Apply configured dead zones to the sticks and analog triggers.");
|
||||||
|
});
|
||||||
|
leftPane
|
||||||
|
.add_child<BoolButton>(BoolButton::Props{
|
||||||
|
.key = "Emulate Triggers",
|
||||||
|
.getValue =
|
||||||
|
[port] {
|
||||||
|
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||||
|
return deadZones != nullptr && deadZones->emulateTriggers;
|
||||||
|
},
|
||||||
|
.setValue =
|
||||||
|
[port](bool value) {
|
||||||
|
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||||
|
deadZones->emulateTriggers = value;
|
||||||
|
PADSerializeMappings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||||
|
})
|
||||||
|
.on_focus([&rightPane](Rml::Event&) {
|
||||||
|
rightPane.clear();
|
||||||
|
rightPane.add_text("Treat analog trigger movement as digital L and R button input.");
|
||||||
|
});
|
||||||
|
|
||||||
|
render_page(rightPane, port, mPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||||
|
pane.clear();
|
||||||
|
|
||||||
|
switch (page) {
|
||||||
|
case Page::Controller: {
|
||||||
|
const u32 controllerCount = PADCount();
|
||||||
|
if (controllerCount == 0) {
|
||||||
|
pane.add_text("No controllers detected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pane.add_button({
|
||||||
|
.text = "None",
|
||||||
|
.isSelected = [port] { return PADGetIndexForPort(port) < 0; },
|
||||||
|
})
|
||||||
|
.on_pressed([this, port] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
PADClearPort(port);
|
||||||
|
PADSerializeMappings();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (u32 i = 0; i < controllerCount; ++i) {
|
||||||
|
pane.add_button(
|
||||||
|
{
|
||||||
|
.text = controller_index_name(i),
|
||||||
|
.isSelected =
|
||||||
|
[port, i] { return PADGetIndexForPort(port) == static_cast<s32>(i); },
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, i] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
PADSetPortForIndex(i, port);
|
||||||
|
PADSerializeMappings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Page::Buttons: {
|
||||||
|
u32 buttonCount = 0;
|
||||||
|
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
|
||||||
|
if (mappings == nullptr) {
|
||||||
|
pane.add_text("No controller selected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Gamepad* gamepad = gamepad_for_port(port);
|
||||||
|
pane.add_section("Buttons");
|
||||||
|
for (u32 i = 0; i < buttonCount; ++i) {
|
||||||
|
PADButtonMapping& mapping = mappings[i];
|
||||||
|
if (!is_action_button(mapping.padButton)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pane.add_select_button({
|
||||||
|
.key = PADGetButtonName(mapping.padButton),
|
||||||
|
.getValue =
|
||||||
|
[this, &mapping, gamepad] {
|
||||||
|
if (mPendingButtonMapping == &mapping) {
|
||||||
|
return pending_button_label();
|
||||||
|
}
|
||||||
|
return native_button_name(
|
||||||
|
gamepad, mapping.nativeButton);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, &mapping] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
mPendingPort = port;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mPendingButtonMapping = &mapping;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pane.add_section("D-Pad");
|
||||||
|
for (u32 i = 0; i < buttonCount; ++i) {
|
||||||
|
PADButtonMapping& mapping = mappings[i];
|
||||||
|
if (!is_dpad_button(mapping.padButton)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pane.add_select_button({
|
||||||
|
.key = PADGetButtonName(mapping.padButton),
|
||||||
|
.getValue =
|
||||||
|
[this, &mapping, gamepad] {
|
||||||
|
if (mPendingButtonMapping == &mapping) {
|
||||||
|
return pending_button_label();
|
||||||
|
}
|
||||||
|
return native_button_name(
|
||||||
|
gamepad, mapping.nativeButton);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, &mapping] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
mPendingPort = port;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mPendingButtonMapping = &mapping;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Page::Triggers: {
|
||||||
|
u32 axisCount = 0;
|
||||||
|
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
|
||||||
|
u32 buttonCount = 0;
|
||||||
|
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
|
||||||
|
if (axes == nullptr && buttons == nullptr) {
|
||||||
|
pane.add_text("No controller selected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Gamepad* gamepad = gamepad_for_port(port);
|
||||||
|
pane.add_section("Analog");
|
||||||
|
constexpr std::array<PADAxis, 2> kTriggerAxes = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R};
|
||||||
|
if (axes != nullptr) {
|
||||||
|
for (PADAxis axis : kTriggerAxes) {
|
||||||
|
if (axis >= axisCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PADAxisMapping& mapping = axes[axis];
|
||||||
|
pane.add_select_button({
|
||||||
|
.key = PADGetAxisName(mapping.padAxis),
|
||||||
|
.getValue =
|
||||||
|
[this, &mapping, gamepad] {
|
||||||
|
if (mPendingAxisMapping == &mapping) {
|
||||||
|
return pending_axis_label();
|
||||||
|
}
|
||||||
|
return native_axis_name(mapping, gamepad);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, &mapping] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
mPendingPort = port;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mPendingAxisMapping = &mapping;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pane.add_section("Digital");
|
||||||
|
if (buttons != nullptr) {
|
||||||
|
for (u32 i = 0; i < buttonCount; ++i) {
|
||||||
|
PADButtonMapping& mapping = buttons[i];
|
||||||
|
if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pane.add_select_button({
|
||||||
|
.key = PADGetButtonName(mapping.padButton),
|
||||||
|
.getValue =
|
||||||
|
[this, &mapping, gamepad] {
|
||||||
|
if (mPendingButtonMapping == &mapping) {
|
||||||
|
return pending_button_label();
|
||||||
|
}
|
||||||
|
return native_button_name(
|
||||||
|
gamepad, mapping.nativeButton);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, &mapping] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
mPendingPort = port;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mPendingButtonMapping = &mapping;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Page::Sticks: {
|
||||||
|
u32 axisCount = 0;
|
||||||
|
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
|
||||||
|
if (axes == nullptr) {
|
||||||
|
pane.add_text("No controller selected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Gamepad* gamepad = gamepad_for_port(port);
|
||||||
|
auto addAxis = [&](PADAxis axis) {
|
||||||
|
if (axis >= axisCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PADAxisMapping& mapping = axes[axis];
|
||||||
|
pane.add_select_button({
|
||||||
|
.key = PADGetAxisDirectionLabel(mapping.padAxis),
|
||||||
|
.getValue =
|
||||||
|
[this, &mapping, gamepad] {
|
||||||
|
if (mPendingAxisMapping == &mapping) {
|
||||||
|
return pending_axis_label();
|
||||||
|
}
|
||||||
|
return native_axis_name(mapping, gamepad);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on_pressed([this, port, &mapping] {
|
||||||
|
cancel_pending_binding();
|
||||||
|
mPendingPort = port;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mPendingAxisMapping = &mapping;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pane.add_section("Control Stick");
|
||||||
|
addAxis(PAD_AXIS_LEFT_Y_POS);
|
||||||
|
addAxis(PAD_AXIS_LEFT_Y_NEG);
|
||||||
|
addAxis(PAD_AXIS_LEFT_X_NEG);
|
||||||
|
addAxis(PAD_AXIS_LEFT_X_POS);
|
||||||
|
|
||||||
|
pane.add_section("C Stick");
|
||||||
|
addAxis(PAD_AXIS_RIGHT_Y_POS);
|
||||||
|
addAxis(PAD_AXIS_RIGHT_Y_NEG);
|
||||||
|
addAxis(PAD_AXIS_RIGHT_X_NEG);
|
||||||
|
addAxis(PAD_AXIS_RIGHT_X_POS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::refresh_controller_page() {
|
||||||
|
if (!visible() || mPage != Page::Controller || mRightPane == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
render_page(*mRightPane, mActivePort, Page::Controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::poll_pending_binding() {
|
||||||
|
if (mSuppressNavigationUntilNeutral && input_neutral(mSuppressNavigationPort)) {
|
||||||
|
mSuppressNavigationUntilNeutral = false;
|
||||||
|
mSuppressNavigationPort = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!capture_active()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboard_escape_pressed()) {
|
||||||
|
unmap_pending_binding();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPendingBindingArmed) {
|
||||||
|
if (pending_input_neutral()) {
|
||||||
|
mPendingBindingArmed = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPendingButtonMapping != nullptr) {
|
||||||
|
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
|
||||||
|
if (nativeButton != -1) {
|
||||||
|
const int completedPort = mPendingPort;
|
||||||
|
mPendingButtonMapping->nativeButton = static_cast<u32>(nativeButton);
|
||||||
|
finish_pending_binding(completedPort);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPendingAxisMapping != nullptr) {
|
||||||
|
const PADSignedNativeAxis nativeAxis = PADGetNativeAxisPulled(mPendingPort);
|
||||||
|
if (nativeAxis.nativeAxis != -1) {
|
||||||
|
const int completedPort = mPendingPort;
|
||||||
|
mPendingAxisMapping->nativeAxis = nativeAxis;
|
||||||
|
mPendingAxisMapping->nativeButton = -1;
|
||||||
|
finish_pending_binding(completedPort);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
|
||||||
|
if (nativeButton != -1) {
|
||||||
|
const int completedPort = mPendingPort;
|
||||||
|
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
|
||||||
|
mPendingAxisMapping->nativeButton = nativeButton;
|
||||||
|
finish_pending_binding(completedPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
|
||||||
|
mPendingButtonMapping = nullptr;
|
||||||
|
mPendingAxisMapping = nullptr;
|
||||||
|
mPendingPort = -1;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mSuppressNavigationUntilNeutral = true;
|
||||||
|
mSuppressNavigationPort = completedPort;
|
||||||
|
PADSerializeMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::unmap_pending_binding() {
|
||||||
|
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int completedPort = mPendingPort;
|
||||||
|
if (mPendingButtonMapping != nullptr) {
|
||||||
|
mPendingButtonMapping->nativeButton = PAD_NATIVE_BUTTON_INVALID;
|
||||||
|
}
|
||||||
|
if (mPendingAxisMapping != nullptr) {
|
||||||
|
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
|
||||||
|
mPendingAxisMapping->nativeButton = -1;
|
||||||
|
}
|
||||||
|
finish_pending_binding(completedPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ControllerConfigWindow::capture_active() const {
|
||||||
|
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ControllerConfigWindow::pending_input_neutral() const {
|
||||||
|
return input_neutral(mPendingPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::String ControllerConfigWindow::pending_button_label() const {
|
||||||
|
return mPendingBindingArmed ? "Press a button..." : "Waiting...";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::String ControllerConfigWindow::pending_axis_label() const {
|
||||||
|
return mPendingBindingArmed ? "Move axis or press a button..." : "Waiting...";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerConfigWindow::cancel_pending_binding() {
|
||||||
|
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
|
||||||
|
!mSuppressNavigationUntilNeutral)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mPendingButtonMapping = nullptr;
|
||||||
|
mPendingAxisMapping = nullptr;
|
||||||
|
mPendingPort = -1;
|
||||||
|
mPendingBindingArmed = false;
|
||||||
|
mSuppressNavigationUntilNeutral = false;
|
||||||
|
mSuppressNavigationPort = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <pad.h>
|
||||||
|
|
||||||
|
namespace dusk::ui {
|
||||||
|
|
||||||
|
class ControllerConfigWindow : public Window {
|
||||||
|
public:
|
||||||
|
ControllerConfigWindow();
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void hide(bool close) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Page {
|
||||||
|
Controller,
|
||||||
|
Buttons,
|
||||||
|
Triggers,
|
||||||
|
Sticks,
|
||||||
|
};
|
||||||
|
|
||||||
|
void build_port_tab(Rml::Element* content, int port);
|
||||||
|
void render_page(class Pane& pane, int port, Page page);
|
||||||
|
void refresh_controller_page();
|
||||||
|
void poll_pending_binding();
|
||||||
|
void finish_pending_binding(int completedPort);
|
||||||
|
void unmap_pending_binding();
|
||||||
|
bool capture_active() const;
|
||||||
|
bool pending_input_neutral() const;
|
||||||
|
Rml::String pending_button_label() const;
|
||||||
|
Rml::String pending_axis_label() const;
|
||||||
|
void cancel_pending_binding();
|
||||||
|
|
||||||
|
Page mPage = Page::Controller;
|
||||||
|
Pane* mRightPane = nullptr;
|
||||||
|
int mActivePort = 0;
|
||||||
|
int mPendingPort = -1;
|
||||||
|
bool mPendingBindingArmed = false;
|
||||||
|
bool mSuppressNavigationUntilNeutral = false;
|
||||||
|
int mSuppressNavigationPort = -1;
|
||||||
|
PADButtonMapping* mPendingButtonMapping = nullptr;
|
||||||
|
PADAxisMapping* mPendingAxisMapping = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -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
|
||||||
@@ -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
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
namespace dusk::ui {
|
||||||
|
|
||||||
|
class EditorWindow : public Window {
|
||||||
|
public:
|
||||||
|
EditorWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#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(
|
||||||
|
Rml::Element* element, Rml::String event, Callback callback, bool capture)
|
||||||
|
: mElement(element), mEventName(std::move(event)), mCapture(capture),
|
||||||
|
mCallback(std::move(callback)) {
|
||||||
|
mElement->AddEventListener(mEventName, this, mCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedEventListener::~ScopedEventListener() {
|
||||||
|
if (mElement != nullptr) {
|
||||||
|
if (!mEventName.empty()) {
|
||||||
|
mElement->RemoveEventListener(mEventName, this, mCapture);
|
||||||
|
} else {
|
||||||
|
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
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#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(
|
||||||
|
Rml::Element* element, Rml::String 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;
|
||||||
|
Rml::String mEventName;
|
||||||
|
bool mCapture = false;
|
||||||
|
Callback mCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -0,0 +1,647 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* controller_change_type(Uint32 eventType) noexcept {
|
||||||
|
switch (eventType) {
|
||||||
|
case SDL_EVENT_GAMEPAD_ADDED:
|
||||||
|
return "added";
|
||||||
|
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||||
|
return "removed";
|
||||||
|
case SDL_EVENT_GAMEPAD_REMAPPED:
|
||||||
|
return "remapped";
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch_controller_change_event(const SDL_Event& event) noexcept {
|
||||||
|
const char* type = controller_change_type(event.type);
|
||||||
|
if (type == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* context = aurora::rmlui::get_context();
|
||||||
|
if (context == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* root = context->GetRootElement();
|
||||||
|
if (root == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::Dictionary parameters;
|
||||||
|
parameters["type"] = Rml::String(type);
|
||||||
|
parameters["which"] = static_cast<int>(event.gdevice.which);
|
||||||
|
root->DispatchEvent("controllerchange", parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (event.type != SDL_EVENT_GAMEPAD_REMOVED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch_controller_change_event(event);
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user