From 079c969bb4502f90b494ae3dda8a72744474b54f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:15:15 +0200 Subject: [PATCH 001/100] Some JAudio markdown docs, WSYS filled out. --- docs/imhex/jaudio/wsys.hexpat | 228 ++++++++++++++++++++++++++++++++++ docs/jaudio.md | 90 ++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 docs/imhex/jaudio/wsys.hexpat create mode 100644 docs/jaudio.md diff --git a/docs/imhex/jaudio/wsys.hexpat b/docs/imhex/jaudio/wsys.hexpat new file mode 100644 index 0000000000..88027ade04 --- /dev/null +++ b/docs/imhex/jaudio/wsys.hexpat @@ -0,0 +1,228 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/* +WSYS file +The WSYS file contains roughly two sections: WBCT and WINF. +WBCT contains the mapping of Wave ID -> wave archives. +WINF are the wave archives themselves and their metadata. +*/ + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +/** + * Format that audio data is in.. + */ +enum WaveFormat : u8 { + /** + * 16-samples-per-9-bytes custom Nintendo ADPCM. + */ + ADPCM4 = 0, + + /** + * 16-samples-per-5-bytes custom Nintendo ADPCM. + */ + ADPCM2 = 1, + + /** + * 8-bit-per-sample PCM. + */ + PCM8 = 2, + + /** + * 16-bit-per-sample PCM. + */ + PCM16 = 3, +}; + +/** + * Defines playback info for a single audio "wave". + * This data is stored per archive, making it duplicated (except for mAWOffsetStart) + */ +struct TWave { + padding[1]; // unknown + WaveFormat mWaveFormat; + + /** + * Key (as in like, the musical term) this sample is in. + */ + u8 mBaseKey; + padding[1]; + + /** + * Sample rate of the audio in Hz. + */ + float mSampleRate; + + /** + * Position where the sample data starts. + * This is in the .aw file for the archive containing this TWave. + */ + u32 mAWOffsetStart; + + /** + * Byte length of the sample data. + */ + u32 mAWLength; + + /** + * Indicates whether the sample should loop or not. All bits appear set if so. + */ + u32 mLoopFlags; + + /** + * Audio sample at which the loop starts. + */ + u32 mLoopStartSample; + + /** + * Audio sample at which the loop ends (and goes back to mLoopStartSample). + */ + u32 mLoopEndSample; + + /** + * Total sample count in this wave. + */ + u32 mSampleCount; + + /** + * Last sample for continuing ADPCM decode after loop. + */ + s16 mpLast; + + /** + * Penult sample for continuing ADPCM decode after loop. + */ + s16 mpPenult; +}; + +/** + * A single wave archive on disk. + * These are paired 1:1 with TCtrlScene objects. + */ +struct TWaveArchive { + /** + * Filename of the raw sample data on disc. Relative to /Audiores/Waves/ + */ + char mFileName[0x70]; + + /** + * Amount of waves in this archive. + * Matches the count in the paired TCtrl object. + */ + u32 mWaveCount; + + /** + * Offsets to the wave metadata (not sample data) in the WSYS. + * These are paired 1:1 to the TCtrlWaves, and the TCtrlWave contains the actual + * "Wave ID" used by the game for lookups. + */ + Offset waveOffsets[mWaveCount]; +}; + +/** + * Header containing data for wave archives and their metadata. + */ +struct TWaveArchiveBank { + type::Magic<"WINF"> mMagic; + + /** + * Amount of archives in this wave bank. + * Matches the value in TCtrlGroup. + */ + u32 mArchiveCounts; + Offset mArchiveOffsets[mArchiveCounts]; +}; + +/** + * Definition for a single wave in a control group. + */ +struct TCtrlWave { + /** + * Group ID matches the index of the control group this item is referenced by. + */ + u16 mGroupId; + + /** + * Wave ID used by the game to look this wave up. + */ + u16 mWaveId; +}; + +/** + * Contains the actual data for a TCtrlScene. + * Why is this separate? Who knows. + */ +struct TCtrl { + // Other versions of this struct with different magic (C-EX and C-ST) also exist in the file. + // They aren't pointed to so we don't need to worry about them. + type::Magic<"C-DF"> mMagic; + + /** + * Amount of waves in this group. + * Matches the value in TWaveArchive. + */ + u32 waveCount; + Offset mWaveOffsets[waveCount]; +}; + +/** + * A single scene or "group" of waves that are loaded at once. + */ +struct TCtrlScene { + type::Magic<"SCNE"> mMagic; + + padding[8]; // unknown + TCtrl* mCtrlOffset : u32 [[inline]]; +}; + +/** + * Contains the "control" section of the WSYS. + */ +struct TCtrlGroup { + type::Magic<"WBCT"> mMagic; + + padding[4]; // unknown + u32 mGroupCount; + + Offset mCtrlSceneOffsets[mGroupCount]; +}; + +struct THeader { + type::Magic<"WSYS"> mMagic; + + /** + * Size of WSYS in bytes. + */ + u32 mSize; + + /** + * ID of wave bank. + * This matches the value passed to the BAA load command. + * The game does not use this value itself. + */ + u32 mId; + + /** + * Total amount of waves in this wave bank. + * (not groups! Waves!) + */ + u32 mWaveTableSize; + + TWaveArchiveBank* archiveBankOffset : u32; + TCtrlGroup* ctrlGroupOffset : u32; +}; + +THeader header @ 0x0; + +std::assert( + header.archiveBankOffset.mArchiveCounts == header.ctrlGroupOffset.mGroupCount, + "Control group and archive count does not match!"); \ No newline at end of file diff --git a/docs/jaudio.md b/docs/jaudio.md new file mode 100644 index 0000000000..a42df217b2 --- /dev/null +++ b/docs/jaudio.md @@ -0,0 +1,90 @@ +# JAudio + +**JAudio** is the name for the audio engine used by Twilight Princess (along with many other Nintendo games from the era). TP uses exclusively JAudio v2, while other games use v1 or a mix of v1 and v2 infrastructure. This document will primarily focus on TP's use case, but best efforts will be made to document where behavior is TP-specific. + +## Common concepts + +Audio is almost exclusively + +### `JAISoundID` + +A "sound", be that a _sound effect_ or music, is referenced in-code through the `JAISoundID` type. This is a `u32` that the game uses to look up the relevant sound. The actual value is bitpacked (BE) from the following fields: + +| Type | Field | Description | +|-------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `u8` | Section ID | Section of the Sound Table this sound effect is located in. 0 for sound effects, 1 for sequenced music, 2 for streamed music.[^sectionids] | +| `u8` | Group ID | ID of group further used to organize inside the Sound Table's Section. Sound effects are grouped into things like "SYSTEM" and "ENEMY", other sections leave this at 0. | +| `u16` | "Wave ID" | Index inside the group to look up the sound at.
**Note** that the term "wave" in code is extremely confusing[^waveterm]; it does **not** refer to audio samples ("waves") in the wave banks directly. | + +[^sectionids]: JAudio itself can seemingly work outside this convention, however it is enforced by some TP-specific game code. + +[^waveterm]: Possibly vestigial from JAudio v1, where I believe there were less layers of indirection. + +## Disc files + +All audio data is stored in `/Audiores` on the disc. Files are as follows: + +### `/Audiores/Seqs/Z2SoundSeqs.arc` + +Contains the BMS instructions for all BMS-based music and sound effects. Not all data is kept in memory at once. + +### `/Audiores/Stream/*.ast` + +Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. + +### `/Audiores/Waves/*.aw` + +Contains audio sample data for sequenced music and sound effects. These files are pure meat, no bone: they are loaded directly into ARAM and all metadata is stored in the BAA WSYS sections. + +Each file contains audio samples for one "scene", with factors like the current level determining what "scenes" are made resident in memory. There is tons of duplicate data between scenes, presumably to increase simplicity and loading performance. + +### `/Audiores/Z2Sound.baa` + +Contains all remaining metadata for the audio system. This is effectively a container for a bunch of different sub-sections. The file starts with a bunch of "commands" that indicate where other data in the file is. Each command has a 4-character identifier and depending on the command will be followed by some extra arguments before the next command. + +The commands used by TP's BAA file are as follows (note that the decompiled code has support for more load commands, which are unused): + +| Command/Argument | Value | Description | +|------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| Command | `AA_<` | Start of BAA commands. Must be at the start of the file and only appear once. | +| Command | `>_AA` | End of BAA commands. | +| Command | `ws `[^spaces] | Wave bank/WSYS data. Defines where audio samples are located on disc. | +| Argument | u32 | Wave bank ID, max 255. In TP, 0 is sound effects, 1 is music samples. | +| Argument | u32 | File offset for start of `WSYS` data | +| Argument | u32 | Bit field selecting which groups (= `.aw` files) to load immediately. TP leaves this at zero.[^32ws] | +| Command | `bnk `[^spaces] | Instrument bank/IBNK | +| Argument | u32 | Target wave bank ID | +| Argument | u32 | File offset for start of `IBNK` data | +| Command | `bsc `[^spaces] | Sound effect sequence collection. | +| Argument | u32 | File offset for start of `SC` data. | +| Argument | u32 | File offset for end of `SC` data. | +| Command | `bst `[^spaces] | Sound table. Defines parameters for music and sound effects. | +| Argument | u32 | File offset for start of `BST `[^spaces] data. | +| Argument | u32 | File offset for end of `BST `[^spaces] data. | +| Command | `bstn` | Sound name table. Defines names of all music and sound effects. Present on disc, but loading is disabled on release versions of the game. | +| Argument | u32 | File offset for start of `BSTN` data. | +| Argument | u32 | File offset for end of `BSTN` data. | +| Command | `bfca` | Unknown, something related to initialization of DSP FX data. | +| Argument | u32 | File offset for start of `RARC` data. | + +[^spaces]: Padded with spaces. + +[^32ws]: Both of TP's wave banks have far more than 32 groups, and seemingly this mechanism would not be able to deal with that. + +Following is data descriptions for the remaining data in the `.BAA`, as pointed to by the above commands. + +### `WSYS` / Wave banks + +`WSYS` / Wave bank data defines where a set of audio samples can be found on disc. Each wave bank is made of multiple "groups", where each group corresponds to one `.aw` file on disc. Each group has a set of "wave IDs" it contains, along with the metadata (e.g. sample rate) and data offset in the `.aw` file. + +Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk. + +For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) + +*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* + +## Further reading & credits + +* https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP) +* XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/) +* The decompiled source code, duh. \ No newline at end of file From 226a1fc4827eaad96e244fe369704a83b7ef2308 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:18:37 +0200 Subject: [PATCH 002/100] read_unaligned helper "Things C++ should've had built-in 2 decades ago" --- include/helpers/alignment.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 include/helpers/alignment.hpp diff --git a/include/helpers/alignment.hpp b/include/helpers/alignment.hpp new file mode 100644 index 0000000000..7ce0ead69a --- /dev/null +++ b/include/helpers/alignment.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace dusk::helpers::alignment { + +/** + * Read data from an address that may not be aligned properly. + * @tparam T Type of data to read. + * @param ptr Address to read from. + * @return The copied value. + */ +template requires std::is_trivially_copyable_v +[[nodiscard]] constexpr T read_unaligned(u8 const* ptr) { + T copy; + memcpy(©, ptr, sizeof(T)); + return copy; +} + +} From a6c4419da6f559cace4a0ee8a363f5f341cc9873 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:26:15 +0200 Subject: [PATCH 003/100] cast.hpp helper I was tired of manually bounds-checking my integer casts. --- files.cmake | 1 + include/helpers/cast.hpp | 48 ++++++++++++++++++++++++++++++++++++++++ src/helpers/cast.cpp | 14 ++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 include/helpers/cast.hpp create mode 100644 src/helpers/cast.cpp diff --git a/files.cmake b/files.cmake index c8c92c9b75..51b2bdf95e 100644 --- a/files.cmake +++ b/files.cmake @@ -1581,6 +1581,7 @@ set(DUSK_FILES src/helpers/endian.cpp src/helpers/offset_ptr.cpp src/helpers/string.cpp + src/helpers/cast.cpp ) set(DUSK_HTTP_BACKEND_FILES diff --git a/include/helpers/cast.hpp b/include/helpers/cast.hpp new file mode 100644 index 0000000000..206bc1e3f3 --- /dev/null +++ b/include/helpers/cast.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +/** + * Helper functions for performing casts. + */ +namespace dusk::helpers::cast { + /** + * Implementation details of dusk::helpers::cast. + */ + namespace _impl { + [[noreturn]] void overrun_high(); + [[noreturn]] void overrun_low(); + } + + template + concept IntCastable = std::is_integral_v && std::is_trivially_copyable_v; + + /** + * Helper type that allows easily casting between integer types, + * that safely aborts if an overflow were to occur. + * Usage: + * @code + * size_t foobar = 20; + * int real = bounded_cast(foobar); + * @endcode + */ + template + struct bounded_cast { + Source src; + + template + [[nodiscard]] constexpr operator Target() const { + if (std::cmp_greater(src, std::numeric_limits::max())) [[unlikely]] { + _impl::overrun_high(); + } + + if (std::cmp_less(src, std::numeric_limits::min())) [[unlikely]] { + _impl::overrun_low(); + } + + return static_cast(src); + } + }; +} diff --git a/src/helpers/cast.cpp b/src/helpers/cast.cpp new file mode 100644 index 0000000000..71eb3160de --- /dev/null +++ b/src/helpers/cast.cpp @@ -0,0 +1,14 @@ +#include "helpers/cast.hpp" +#include "global.h" + +namespace dusk::helpers::cast::_impl { + +void overrun_high() { + CRASH("bounded cast overran!"); +} + +void overrun_low() { + CRASH("bounded cast overran!"); +} + +} // namespace dusk::helpers::cast::_impl From 6ded3ac467204c524ceb2a29ffadd1cbe7245608 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:32:02 +0200 Subject: [PATCH 004/100] JAudio naming/docs pass --- .../JSystem/JAudio2/JASBasicWaveBank.h | 14 +++--- .../include/JSystem/JAudio2/JASWSParser.h | 9 ++-- .../JSystem/JAudio2/JASWaveArcLoader.h | 2 +- .../include/JSystem/JAudio2/JASWaveInfo.h | 4 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 44 ++++++++++--------- libs/JSystem/src/JAudio2/JASAramStream.cpp | 2 +- libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp | 30 ++++++------- libs/JSystem/src/JAudio2/JASChannel.cpp | 4 +- .../JSystem/src/JAudio2/JASSimpleWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASWSParser.cpp | 8 ++-- libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp | 8 ++-- 11 files changed, 66 insertions(+), 61 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index f6a10fa0c7..5a84750e3b 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -12,21 +12,21 @@ struct JASBasicWaveBank : public JASWaveBank { struct TWaveHandle : public JASWaveHandle { TWaveHandle() { mHeap = NULL; } virtual intptr_t getWavePtr() const; - virtual const JASWaveInfo* getWaveInfo() const { return &field_0x4; } + virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; } bool compareHeap(JASHeap* heap) const { return mHeap == heap;} - /* 0x04 */ JASWaveInfo field_0x4; + /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; }; struct TGroupWaveInfo { TGroupWaveInfo() { - field_0x0 = 0xffff; - field_0x4 = -1; + waveId = 0xffff; + mOffsetStart = -1; } - /* 0x00 */ u16 field_0x0; - /* 0x04 */ int field_0x4; + /* 0x00 */ u16 waveId; + /* 0x04 */ int mOffsetStart; }; struct TWaveGroup : JASWaveArc { @@ -56,7 +56,7 @@ struct JASBasicWaveBank : public JASWaveBank { JASWaveArc* getWaveArc(u32 param_0) { return getWaveGroup(param_0); } u32 getArcCount() const { return mGroupCount; } - /* 0x04 */ OSMutex field_0x4; + /* 0x04 */ OSMutex mWaveTableMutex; /* 0x1C */ TWaveHandle* mWaveTable; /* 0x20 */ TWaveGroup* mWaveGroupArray; /* 0x24 */ u16 mHandleCount; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h index 9fe201308c..da9af7c37c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h @@ -26,7 +26,9 @@ public: }; struct TCtrlWave { - /* 0x0 */ BE(u32) _00; + // Wave ID in lower half. + // Upper bits appear to be group ID, which is unused by the game code. + /* 0x0 */ BE(u32) mWaveAndGroupId; }; struct TWave { @@ -57,13 +59,14 @@ public: }; struct TCtrl { - /* 0x0 */ u8 _00[4]; + /* 0x0 */ BE(u32) mMagic; // 'C-DF'. /* 0x4 */ BE(u32) mWaveCount; /* 0x8 */ TOffset mCtrlWaveOffsets[0]; }; struct TCtrlScene { - /* 0x0 */ u8 _00[0xC]; + /* 0x0 */ BE(u32) mMagic; // 'SCNE' + /* 0x4 */ u8 _00[0x8]; /* 0xC */ TOffset mCtrlOffset; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h index 0ad4e044c8..7caa344c8f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h @@ -66,7 +66,7 @@ struct JASWaveArc : JASDisposer { }; /* 0x04 */ mutable JASHeap mHeap; - /* 0x48 */ u32 _48; + /* 0x48 */ u32 mCurrentlyLoaded; /* 0x4C */ volatile s32 mStatus; /* 0x50 */ int mEntryNum; /* 0x54 */ u32 mFileLength; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 04bd541204..9d1c16d775 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -17,7 +17,7 @@ struct JASWaveArc; struct JASWaveInfo { JASWaveInfo() { mBaseKey = 0x3c; - field_0x20 = &one; + mpLoaded = &one; } /* 0x00 */ u8 mWaveFormat; @@ -31,7 +31,7 @@ struct JASWaveInfo { /* 0x18 */ int mSampleCount; /* 0x1C */ s16 mpLast; /* 0x1E */ s16 mpPenult; - /* 0x20 */ const u32* field_0x20; + /* 0x20 */ const u32* mpLoaded; static DUSK_GAME_DATA u32 one; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index fbd3427649..3571e386b6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -24,23 +24,25 @@ struct JAUSoundTableItem { template struct JAUSoundTable_ { JAUSoundTable_() { - field_0x0 = NULL; - field_0x4 = 0; + mData = NULL; + mRoot = 0; } void reset() { - field_0x0 = NULL; - field_0x4 = NULL; + mData = NULL; + mRoot = NULL; } void init(const void* param_0) { - field_0x0 = param_0; + mData = param_0; // magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?) // I also do not know how it is different between JAUSoundTable and JAUSoundNameTable - if (*(BE(u32)*)field_0x0 + 0xbdad0000 != Root::magicNumber()) { - field_0x0 = NULL; + // Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber(). + // Idk why the operations here are all weird but I can't use objdiff right now. + if (*(BE(u32)*)mData + 0xbdad0000 != Root::magicNumber()) { + mData = NULL; } else { - field_0x4 = (Root*)((u8*)field_0x0 + *((BE(u32)*)field_0x0 + 3)); + mRoot = (Root*)((u8*)mData + *((BE(u32)*)mData + 3)); } } @@ -48,14 +50,14 @@ struct JAUSoundTable_ { if (index < 0) { return NULL; } - if ((u32)index >= field_0x4->mSectionNumber) { + if ((u32)index >= mRoot->mSectionNumber) { return NULL; } - u32 offset = field_0x4->mSectionOffsets[index]; + u32 offset = mRoot->mSectionOffsets[index]; if (offset == 0) { return NULL; } - return (Section*)((u8*)field_0x0 + offset); + return (Section*)((u8*)mData + offset); } Group* getGroup(Section* param_1, int index) const { @@ -71,11 +73,11 @@ struct JAUSoundTable_ { if (offset == 0) { return NULL; } - return (Group*)((u8*)field_0x0 + offset); + return (Group*)((u8*)mData + offset); } - const void* field_0x0; - Root* field_0x4; + const void* mData; + Root* mRoot; }; /** @@ -83,7 +85,7 @@ struct JAUSoundTable_ { * */ struct JAUSoundTableRoot { - static inline u32 magicNumber() { return 0x5420; } + static inline u32 magicNumber() { return 'T '; } // Second half of "BST " BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; @@ -134,7 +136,7 @@ struct JAUSoundTableGroup { BE(u32) mNumItems; BE(u32) field_0x4; - u8 mTypeIds[0]; + u8 mTypeIds[0]; // TODO: Should probably be BE(u32), but I can't objdiff rn. }; /** @@ -142,7 +144,7 @@ struct JAUSoundTableGroup { * */ struct JAUSoundTable : public JASGlobalInstance { - JAUSoundTable(bool param_0) : JASGlobalInstance(param_0) { + JAUSoundTable(bool setInstance) : JASGlobalInstance(setInstance) { } ~JAUSoundTable() {} @@ -157,11 +159,11 @@ struct JAUSoundTable : public JASGlobalInstance { if (offset == 0) { return NULL; } - return (JAUSoundTableItem*)((u8*)field_0x0.field_0x0 + offset); + return (JAUSoundTableItem*)((u8*)field_0x0.mData + offset); } - const void* getResource() const { return field_0x0.field_0x0; } - bool isValid() const { return field_0x0.field_0x0 != NULL; } + const void* getResource() const { return field_0x0.mData; } + bool isValid() const { return field_0x0.mData != NULL; } JAUSoundTable_ field_0x0; }; @@ -171,7 +173,7 @@ struct JAUSoundTable : public JASGlobalInstance { * */ struct JAUSoundNameTableRoot { - static inline u32 magicNumber() { return 0x544e; } + static inline u32 magicNumber() { return 'TN'; } // Second half of "BSTN" BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 6a15869a7c..2ff00b6a14 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -713,7 +713,7 @@ void JASAramStream::channelStart() { wave_info.mpPenult = 0; // probably a fake match, this should be set in the JASWaveInfo constructor static u32 const one = 1; - wave_info.field_0x20 = &one; + wave_info.mpLoaded = &one; JASChannel* jc = JKR_NEW JASChannel(channelCallback, this); JUT_ASSERT(963, jc); jc->setPriority(0x7f7f); diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index ac11742e6b..80bda99ad9 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -11,7 +11,7 @@ JASBasicWaveBank::JASBasicWaveBank() { mWaveGroupArray = NULL; mHandleCount = 0; mGroupCount = 0; - OSInitMutex(&field_0x4); + OSInitMutex(&mWaveTableMutex); } JASBasicWaveBank::~JASBasicWaveBank() { @@ -44,13 +44,13 @@ void JASBasicWaveBank::setWaveTableSize(u32 param_0, JKRHeap* param_1) { } void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (!handle->mHeap) { handle->mHeap = ¶m_0->mHeap; - handle->field_0x4.field_0x20 = ¶m_0->_48; - handle->field_0x4.mOffsetStart = param_0->mCtrlWaveArray[i].field_0x4; + handle->mWaveInfo.mpLoaded = ¶m_0->mCurrentlyLoaded; + handle->mWaveInfo.mOffsetStart = param_0->mCtrlWaveArray[i].mOffsetStart; } } } @@ -58,13 +58,13 @@ void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) DUSK_GAME_DATA u32 JASBasicWaveBank::mNoLoad; void JASBasicWaveBank::decWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (handle->mHeap == ¶m_0->mHeap) { handle->mHeap = NULL; - handle->field_0x4.field_0x20 = &mNoLoad; - handle->field_0x4.mOffsetStart = -1; + handle->mWaveInfo.mpLoaded = &mNoLoad; + handle->mWaveInfo.mOffsetStart = -1; } } } @@ -80,15 +80,15 @@ JASWaveHandle* JASBasicWaveBank::getWaveHandle(u32 param_0) const { } void JASBasicWaveBank::setWaveInfo(JASBasicWaveBank::TWaveGroup* wgrp, int index, - u16 param_2, JASWaveInfo const& param_3) { + u16 waveId, JASWaveInfo const& param_3) { JUT_ASSERT(204, wgrp); JUT_ASSERT(205, index < wgrp->mWaveCount); JUT_ASSERT(206, index >= 0); - mWaveTable[param_2].field_0x4 = param_3; - mWaveTable[param_2].field_0x4.field_0x20 = &mNoLoad; - mWaveTable[param_2].field_0x4.mOffsetStart = -1; - wgrp->mCtrlWaveArray[index].field_0x0 = param_2; - wgrp->mCtrlWaveArray[index].field_0x4 = param_3.mOffsetStart; + mWaveTable[waveId].mWaveInfo = param_3; + mWaveTable[waveId].mWaveInfo.mpLoaded = &mNoLoad; + mWaveTable[waveId].mWaveInfo.mOffsetStart = -1; + wgrp->mCtrlWaveArray[index].waveId = waveId; + wgrp->mCtrlWaveArray[index].mOffsetStart = param_3.mOffsetStart; } JASBasicWaveBank::TWaveGroup::TWaveGroup() { @@ -122,7 +122,7 @@ void JASBasicWaveBank::TWaveGroup::onEraseDone() { u32 JASBasicWaveBank::TWaveGroup::getWaveID(int index) const { JUT_ASSERT(298, index < mWaveCount); JUT_ASSERT(299, index >= 0); - return mCtrlWaveArray[index].field_0x0; + return mCtrlWaveArray[index].waveId; } intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { @@ -131,5 +131,5 @@ intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { if (base == 0) { return 0; } - return (intptr_t)base + field_0x4.mOffsetStart; + return (intptr_t)base + mWaveInfo.mOffsetStart; } diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index a99c13ce63..d2d31d1e6a 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -237,7 +237,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -315,7 +315,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index 52935324d8..fc49df148d 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -28,7 +28,7 @@ JASWaveHandle* JASSimpleWaveBank::getWaveHandle(u32 no) const { void JASSimpleWaveBank::setWaveInfo(u32 no, JASWaveInfo const& waveInfo) { mWaveTable[no].mWaveInfo = waveInfo; - mWaveTable[no].mWaveInfo.field_0x20 = &_48; + mWaveTable[no].mWaveInfo.mpLoaded = &mCurrentlyLoaded; mWaveTable[no].mHeap = &mHeap; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index 519eaa3307..f097e93289 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -60,7 +60,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[j].ptr(header); - u16 local_74 = JSULoHalf(ctrl_wave->_00); + u16 local_74 = JSULoHalf(ctrl_wave->mWaveAndGroupId); wave_bank->setWaveInfo(wave_group, j, local_74, wave_info); } wave_group->setFileName(archive->mFileName); @@ -94,7 +94,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap TWaveArchive* archive = archiveBank->mArchiveOffsets[0].ptr(header); for (int i = 0; i < ctrl->mWaveCount; i++) { TCtrlWave* ctrlWave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrlWave->_00); + u32 tmp = JSULoHalf(ctrlWave->mWaveAndGroupId); if (max < tmp) { max = tmp; } @@ -116,8 +116,8 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrl_wave->_00); - wave_bank->setWaveInfo(tmp, wave_info); + u32 waveId = JSULoHalf(ctrl_wave->mWaveAndGroupId); + wave_bank->setWaveInfo(waveId, wave_info); } wave_bank->setFileName(archive->mFileName); diff --git a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp index 02730f46b4..5ba257c423 100644 --- a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp +++ b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp @@ -38,7 +38,7 @@ char* JASWaveArcLoader::getCurrentDir() { } JASWaveArc::JASWaveArc() : mHeap(this) { - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; mEntryNum = -1; mFileLength = 0; @@ -57,7 +57,7 @@ bool JASWaveArc::loadSetup(u32 param_0) { if (mStatus != 1) { return false; } - _48 = 1; + mCurrentlyLoaded = 1; mStatus = 2; return true; } @@ -71,7 +71,7 @@ bool JASWaveArc::eraseSetup() { mStatus = 0; return false; } - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; return true; } @@ -91,7 +91,7 @@ void JASWaveArc::loadToAramCallback(void* this_) { bool JASWaveArc::sendLoadCmd() { JASMutexLock mutexLock(&mMutex); - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 1; loadToAramCallbackParams commandInfo; commandInfo.mWavArc = this; From 30c36df76b50ff577f17bfa5a8cb6ff9a613cac4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:40:32 +0200 Subject: [PATCH 005/100] Add LE(T) macro I figured since I was messing with some parsing code might as well be prudent. In case somebody revives big-endian CPUs. --- include/helpers/endian.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/helpers/endian.h b/include/helpers/endian.h index 689f9f0dab..da0c8a4a36 100644 --- a/include/helpers/endian.h +++ b/include/helpers/endian.h @@ -285,6 +285,7 @@ inline void be_swap(Mtx& val) { } } +#define LE(T) T #define BE(T) BE #define BE_HOST(T) (T.host()) #else From ad80933ccffe3877d9eaaea161acf7ca065720cb Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:49:13 +0200 Subject: [PATCH 006/100] Allow audio samples to be played back from anywhere in memory rather than ARAM. This is accomplished through a new mAramBaseAddress field on the DSP channels, which effectively relocates ARAM for that channel. --- .../include/JSystem/JAudio2/JASBasicWaveBank.h | 6 ++++++ libs/JSystem/include/JSystem/JAudio2/JASChannel.h | 12 ++++++++++++ .../include/JSystem/JAudio2/JASDSPInterface.h | 14 ++++++++++++++ .../include/JSystem/JAudio2/JASSimpleWaveBank.h | 6 ++++++ libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h | 6 ++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 5 ++++- libs/JSystem/src/JAudio2/JASChannel.cpp | 6 ++++++ src/dusk/audio/DuskDsp.cpp | 5 +++-- 8 files changed, 57 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index 5a84750e3b..cf739b18a2 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -13,6 +13,12 @@ struct JASBasicWaveBank : public JASWaveBank { TWaveHandle() { mHeap = NULL; } virtual intptr_t getWavePtr() const; virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; } +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif bool compareHeap(JASHeap* heap) const { return mHeap == heap;} /* 0x04 */ JASWaveInfo mWaveInfo; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index af922638da..59512fbadd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -166,6 +166,18 @@ public: u32 field_0x104; }; +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; +#endif + static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ; static DUSK_GAME_DATA OSMessage sBankDisposeMsg[16]; static DUSK_GAME_DATA OSMessage sBankDisposeList[16]; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index b413052da7..7f830b6469 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -179,6 +179,20 @@ namespace JASDsp { /* 0x130 */ u8 _unused9[0x148 - 0x130]; /* 0x148 */ s16 iir_filter_params[8]; /* 0x158 */ u8 _unused10[0x180 - 0x158]; +#endif + +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * This means fields like mWaveAramAddress will be relative to this pointer instead. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; +#endif }; void boot(void (*)(void*)); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h index 4fbeee32b9..1dce0968a9 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h @@ -10,6 +10,12 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc { intptr_t getWavePtr() const; TWaveHandle(); const JASWaveInfo* getWaveInfo() const; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 9d1c16d775..20b4be89fd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -45,6 +45,12 @@ public: virtual ~JASWaveHandle() {} virtual const JASWaveInfo* getWaveInfo() const = 0; virtual intptr_t getWavePtr() const = 0; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] virtual void const* getAramBaseAddress() const = 0; +#endif }; /** diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 29ebe11cb5..69dd05795f 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -33,7 +33,7 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } intptr_t wavePtr = waveHandle->getWavePtr(); - if (!wavePtr) { + if (!wavePtr IF_DUSK(&& !waveHandle->getAramBaseAddress())) { return NULL; } @@ -44,6 +44,9 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 channel->setPriority(param_4); channel->field_0xdc.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; +#if TARGET_PC + channel->mAramBaseAddress = waveHandle->getAramBaseAddress(); +#endif channel->field_0xdc.mChannelType = stack_60.field_0x1c; channel->setBankDisposeID(param_0); channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index d2d31d1e6a..d227ad3fc1 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -26,6 +26,9 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mCallback(i_callback), mCallbackData(i_callbackData), mUpdateTimer(0), +#if TARGET_PC + mAramBaseAddress(nullptr), +#endif mBankDisposeID(NULL), mKey(0), mVelocity(0x7f), @@ -256,6 +259,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { switch (field_0xdc.mChannelType) { case 0: i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); +#if TARGET_PC + i_channel->mAramBaseAddress = mAramBaseAddress; +#endif break; case 2: i_channel->setOscInfo(mOscillatorSomething); diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 4009b37e39..3240c0f810 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -317,7 +317,7 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } OutputSubframe channelSubframe = {}; - if (channel.mWaveAramAddress == 0) { + if (channel.mWaveAramAddress == 0 && !channel.mAramBaseAddress) { RenderOscChannel(channel, channelAux, channelSubframe); } else { ValidateChannel(channel); @@ -455,7 +455,8 @@ static int ReadChannelSamplesChunk( assert(desiredSamples >= 0); - auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + auto aramBase = static_cast(channel.mAramBaseAddress ? channel.mAramBaseAddress : ARGetStorageAddress()); + aramBase += channel.mWaveAramAddress; auto curSamplePosition = channel.mSamplePosition; u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock; From 0c0bc7c9e2b76eb7790182170e4bd32662347cc2 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:49:49 +0200 Subject: [PATCH 007/100] Compile out unused fields in DSP TChannel definition --- .../include/JSystem/JAudio2/JASDSPInterface.h | 17 +++++++++++++++++ libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index 7f830b6469..c45b72f2d8 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -109,7 +109,9 @@ namespace JASDsp { * Pitch shift via changing playback speed. */ /* 0x004 */ u16 mPitch; +#if !TARGET_PC /* 0x006 */ short _unused1; +#endif /** * Set to 1 when playback starts, cleared by DSP later, @@ -118,19 +120,27 @@ namespace JASDsp { * (Corroborated by fields JASAramStream checks never being cleared explicitly by CPU.) */ /* 0x008 */ u16 mResetFlag; +#if !TARGET_PC /* 0x00A */ u8 _unused2[0x00C - 0x00A]; +#endif /* 0x00C */ s16 mPauseFlag; +#if !TARGET_PC /* 0x00E */ short _unused3; +#endif /* 0x010 */ OutputChannelConfig mOutputChannels[DSP_OUTPUT_CHANNELS]; +#if !TARGET_PC /* 0x040 */ u8 _unused4[0x050 - 0x040]; +#endif /* 0x050 */ u16 mAutoMixerPanDolby; // pan is upper 8 bits, dolby lower 8. /* 0x052 */ u16 mAutoMixerFxMix; /* 0x054 */ u16 mAutoMixerInitVolume; /* 0x056 */ u16 mAutoMixerVolume; /* 0x058 */ u16 mAutoMixerBeenSet; +#if !TARGET_PC /* 0x05A */ u8 _unused5[0x060 - 0x05A]; /* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP. /* 0x062 */ u8 _unused6[0x064 - 0x062]; +#endif /** * Samples per ADPCM frame for ADPCM audio. Seems just set to 1 for PCM formats. @@ -139,7 +149,9 @@ namespace JASDsp { /* 0x064 */ u16 mSamplesPerBlock; /* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP. /* 0x068 */ u32 mSamplePosition; // Only ever initialized by code, name is guess. +#if !TARGET_PC /* 0x06C */ u8 _unused7[0x070 - 0x06C]; +#endif /** * Current audio read position in ARAM. Updated by DSP. @@ -151,11 +163,13 @@ namespace JASDsp { * Gets written by DSP, but also CPU. */ /* 0x074 */ u32 mSamplesLeft; // Never directly cleared to zero. Seems sus. Cleared by DSP? +#if !TARGET_PC /* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP. /* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP. /* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP. /* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP. /* 0x0D0 */ u8 _unused8[0x100 - 0x0D0]; +#endif /* 0x100 */ u16 mBytesPerBlock; /* 0x102 */ u16 mLoopFlag; @@ -176,8 +190,11 @@ namespace JASDsp { /* 0x118 */ u32 mWaveAramAddress; /* 0x11C */ int mSampleCount; /* 0x120 */ s16 fir_filter_params[8]; +#if !TARGET_PC /* 0x130 */ u8 _unused9[0x148 - 0x130]; +#endif /* 0x148 */ s16 iir_filter_params[8]; +#if !TARGET_PC /* 0x158 */ u8 _unused10[0x180 - 0x158]; #endif diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index 554660f6dc..04bec5dcc7 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -523,9 +523,12 @@ void JASDsp::TChannel::init() { void JASDsp::TChannel::playStart() { JUT_ASSERT(508, dspMutex); field_0x10c = 0; +#if !TARGET_PC field_0x060 = 0; +#endif mResetFlag = 1; field_0x066 = 0; +#if !TARGET_PC int i; for (i = 0; i < 4; i++) { field_0x078[i] = 0; @@ -534,6 +537,7 @@ void JASDsp::TChannel::playStart() { for (i = 0; i < 20; i++) { field_0x080[i] = 0; } +#endif mIsActive = 1; } @@ -601,9 +605,11 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress, break; } } +#if !TARGET_PC for (i = 0; i < 16; i++) { field_0x0b0[i] = 0; } +#endif } } From 98b203fd269aab2479a6c89982011349bf29cec1 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 16:02:20 +0200 Subject: [PATCH 008/100] Mod api audio_res service for replacing audio samples (waves) More things planned for the future, this is a basic prototype and far from finished. Still needs lots of work --- CMakeLists.txt | 25 +++ files.cmake | 4 + libs/JSystem/src/JAudio2/JASBank.cpp | 11 ++ sdk/include/mods/svc/audio_res.h | 65 +++++++ src/dusk/mods/svc/audio_res/audio_res.cpp | 227 ++++++++++++++++++++++ src/dusk/mods/svc/audio_res/audio_res.hpp | 93 +++++++++ src/dusk/mods/svc/audio_res/opus.cpp | 93 +++++++++ src/dusk/mods/svc/audio_res/wave.cpp | 145 ++++++++++++++ src/dusk/mods/svc/registry.cpp | 1 + src/dusk/mods/svc/registry.hpp | 1 + 10 files changed, 665 insertions(+) create mode 100644 sdk/include/mods/svc/audio_res.h create mode 100644 src/dusk/mods/svc/audio_res/audio_res.cpp create mode 100644 src/dusk/mods/svc/audio_res/audio_res.hpp create mode 100644 src/dusk/mods/svc/audio_res/opus.cpp create mode 100644 src/dusk/mods/svc/audio_res/wave.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a8d01151..9dc478ce2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF) option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF) +option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" ON) # Edit & Continue if (MSVC) @@ -219,6 +220,25 @@ FetchContent_Declare(miniz ) set(_fetch_content_deps cxxopts json miniz) + +if (DUSK_ENABLE_OPUS) + message(STATUS "dusklight: Fetching opusfile") + + # Opusfile options + SET(OP_DISABLE_HTTP ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_DOCS ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE) + + message(STATUS "dusklight: Fetching opusfile") + FetchContent_Declare( + opusfile + GIT_REPOSITORY https://github.com/xiph/opusfile.git + GIT_TAG 6dfd29e7adb87f2e193575fc3fa88cbf1a0b27df + ) + + list(APPEND _fetch_content_deps opusfile) +endif () + if (DUSK_ENABLE_CODE_MODS) message(STATUS "dusklight: Fetching funchook") # cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a @@ -311,6 +331,10 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p if (DUSK_ENABLE_CODE_MODS) list(APPEND GAME_LIBS funchook-static) endif () +if (DUSK_ENABLE_OPUS) + list(APPEND GAME_LIBS OpusFile::opusfile) + list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1) +endif () if (DUSK_ENABLE_SENTRY_NATIVE) list(APPEND GAME_LIBS sentry) @@ -568,6 +592,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR add_subdirectory(mods/template_mod) add_subdirectory(mods/ao_mod) add_subdirectory(mods/shadow_mod) + add_subdirectory(mods/audio_mod) endif () if (APPLE) diff --git a/files.cmake b/files.cmake index 51b2bdf95e..74e6ef0e69 100644 --- a/files.cmake +++ b/files.cmake @@ -1483,6 +1483,10 @@ set(DUSK_FILES src/dusk/mods/log_buffer.hpp src/dusk/mods/manifest.cpp src/dusk/mods/manifest.hpp + src/dusk/mods/svc/audio_res/audio_res.hpp + src/dusk/mods/svc/audio_res/audio_res.cpp + src/dusk/mods/svc/audio_res/wave.cpp + src/dusk/mods/svc/audio_res/opus.cpp src/dusk/mods/svc/camera.cpp src/dusk/mods/svc/config.cpp src/dusk/mods/svc/config.hpp diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 69dd05795f..e96b4df174 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -1,11 +1,15 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASBank.h" + +#include "dusk/mods/svc/audio_res/audio_res.hpp" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASBasicInst.h" #include "JSystem/JAudio2/JASBasicWaveBank.h" #include "JSystem/JAudio2/JASChannel.h" +using namespace dusk::mods::svc::audio_res; + // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), @@ -28,6 +32,13 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 if (!waveHandle) { return NULL; } +#if TARGET_PC + auto const found_replacement = s_replacements.find(AudioWaveKey(SoundEffects, stack_60.field_0x1a)); + if (found_replacement != s_replacements.end()) { + waveHandle = found_replacement->second.get(); + } +#endif + const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); if (!waveInfo) { return NULL; diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h new file mode 100644 index 0000000000..f5ec3693dc --- /dev/null +++ b/sdk/include/mods/svc/audio_res.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#define AUDIO_RES_SERVICE_ID "dev.twilitrealm.dusklight.audio_res" +#define AUDIO_RES_SERVICE_MAJOR 1u +#define AUDIO_RES_SERVICE_MINOR 0u + +#define AUDIO_RES_DEFAULT_KEY 0x3C; + +typedef enum AudioWaveBank : uint8_t { + SoundEffects = 0, + MusicSamples = 1, +} AudioWaveBank; + +typedef enum AudioWaveFormat : uint8_t { + Adpcm4 = 0, + Adpcm2 = 1, + Pcm8 = 2, + Pcm16 = 3, +} AudioWaveFormat; + +typedef uint64_t AudioWaveHandle; + +/** + * + */ +typedef struct AudioRawWave { + AudioWaveFormat format; + float sample_rate; + int16_t sample_value_last; + int16_t sample_value_penult; +} AudioRawWave; + +typedef struct AudioWaveInfo { + uint8_t base_key; + bool loop; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + + AudioRawWave const* raw_wave; +} AudioWaveInfo; + +typedef struct AudioResService { + ServiceHeader header; + + ModResult (*replace_wave)( + ModContext* ctx, + AudioWaveBank bank, + uint16_t wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); +} AudioResService; + +#ifdef __cplusplus +#include "mods/service.hpp" + +template <> +struct mods::ServiceTraits { + static constexpr const char* id = AUDIO_RES_SERVICE_ID; + static constexpr uint16_t major_version = AUDIO_RES_SERVICE_MAJOR; + static constexpr uint16_t minor_version = AUDIO_RES_SERVICE_MINOR; +}; +#endif diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp new file mode 100644 index 0000000000..20b3e8a95f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -0,0 +1,227 @@ +#include "audio_res.hpp" + +#include "helpers/cast.hpp" +#include "mods/svc/audio_res.h" + +#include "../registry.hpp" +#include "../slot_map.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" + +namespace dusk::mods::svc { + +namespace audio_res { +namespace { + +using namespace dusk::helpers::cast; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap s_waveReplacements; + +constexpr ContainerLoadFunction s_containerLoadFunctions[] = { + load_wav, +#if DUSK_OPUS + load_opus, +#endif +}; + +bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { + u32 samples_per_block; + u32 bytes_per_block; + switch (raw.format) { + case Adpcm4: + samples_per_block = 16; + bytes_per_block = 9; + break; + case Pcm16: + samples_per_block = 1; + bytes_per_block = 2; + break; + default: + Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path); + return false; + } + + if (actual_size % bytes_per_block != 0) { + Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); + return false; + } + + sample_count = (actual_size / bytes_per_block) * samples_per_block; + return true; +} + +ModResult load_raw( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + u32 sample_count = 0; + + if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) { + Log.error( + "[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified", + mod.metadata.id, slot.bundle_path); + + return MOD_INVALID_ARGUMENT; + } + + if (slot.format == Pcm16) { + SampleDataPcm16 pcm16; + pcm16.data.resize(sample_count); + memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); + slot.data = std::make_shared(std::move(pcm16)); + } else { + slot.data = std::make_shared(std::move(file_contents)); + } + + slot.sample_count = sample_count; + slot.sample_rate = raw.sample_rate; + slot.format = raw.format; + slot.sample_value_penult = raw.sample_value_penult; + slot.sample_value_last = raw.sample_value_last; + return MOD_OK; +} + +/** + * Load an audio file stored in some sort of container format. + * File type is determined by looking at contents, not extension. + */ +ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + + for (auto const loader : s_containerLoadFunctions) { + auto const result = loader(mod, slot, file_contents); + if (result == MOD_OK) { + return MOD_OK; + } + + if (result != MOD_UNSUPPORTED) { + return result; + } + } + + return MOD_UNSUPPORTED; +} + +JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { + JASWaveInfo info; + info.mWaveFormat = static_cast(slot.format); + info.mBaseKey = slot.base_key; + info.mLoopFlag = slot.loop ? 0xFF : 0; + info.mSampleRate = slot.sample_rate; + info.mOffsetStart = 0; // Unused but just init it ig. + info.mOffsetLength = bounded_cast(slot.data->size()); + info.mLoopStartSample = slot.loop_start_sample; + info.mLoopEndSample = bounded_cast(slot.loop_end_sample); + info.mSampleCount = bounded_cast(slot.sample_count); + info.mpLast = slot.sample_value_last; + info.mpPenult = slot.sample_value_penult; + // mpLoaded is initialized to point to a 1, so we're good there. + return info; +} + +ModResult insert_replace_wave( + ModContext* ctx, + AudioWaveBank bank, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) { + return MOD_INVALID_ARGUMENT; + } + + RuntimeWaveReplacementSlot slot{}; + slot.bundle_path = file_name; + slot.bank = bank; + slot.wave_id = wave_id; + + ModResult result; + if (wave_info && wave_info->raw_wave) { + result = load_raw(*mod, slot, *wave_info->raw_wave); + } else { + result = load_container(*mod, slot); + } + if (result != MOD_OK) { + return result; + } + + if (wave_info != nullptr) { + slot.base_key = wave_info->base_key; + slot.loop = wave_info->loop; + slot.loop_start_sample = wave_info->loop_start_sample; + slot.loop_end_sample = wave_info->loop_end_sample; + } else { + slot.base_key = AUDIO_RES_DEFAULT_KEY; + slot.loop = false; + slot.loop_start_sample = 0; + slot.loop_end_sample = slot.sample_count; + } + + // TODO: Handle conflicts + + audio_res::AudioWaveReplacementValue value; + value.data = slot.data; + value.wave_info = wave_info_from_slot(slot); + audio_res::s_replacements.emplace( + audio_res::AudioWaveKey{.bank = bank, .wave_id = wave_id}, + std::make_unique(std::move(value))); + + const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +constexpr AudioResService s_audioResService{ + .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .replace_wave = &audio_res::insert_replace_wave}; + +} + +absl::flat_hash_map> s_replacements; + +AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; +const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { + return &wave_info; +} + +void const* AudioWaveReplacementValue::getAramBaseAddress() const { + return data->get_data().data(); +} + +intptr_t AudioWaveReplacementValue::getWavePtr() const { + return 0; +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + +} // namespace audio_res + +constinit const ServiceModule g_audioResModule{ + .id = AUDIO_RES_SERVICE_ID, + .majorVersion = AUDIO_RES_SERVICE_MAJOR, + .minorVersion = AUDIO_RES_SERVICE_MINOR, + .service = &audio_res::s_audioResService, + // .modDetached = overlay_remove_mod, + // .lifecycleApplied = overlay_sync_files, + // .frameEnd = + // [] { + // if (consume_overlays_dirty()) { + // overlay_sync_files(); + // } + // }, +}; + +} diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp new file mode 100644 index 0000000000..b336dbeda8 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include "JSystem/JAudio2/JASWaveInfo.h" +#include "absl/container/flat_hash_map.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} +namespace dusk::mods::svc::audio_res { + +struct SampleDataBuf { + virtual ~SampleDataBuf() = default; + [[nodiscard]] virtual std::span get_data() const = 0; + [[nodiscard]] size_t size() const { + return get_data().size(); + } +}; + +// Making a separate type for this rather than just using a vector +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + template + friend H AbslHashValue(H h, const AudioWaveKey& k) { + return H::combine(std::move(h), k.bank, k.wave_id); + } + + [[nodiscard]] bool operator==(const AudioWaveKey& other) const { + return other.bank == bank && other.wave_id == wave_id; + } +}; + +struct AudioWaveReplacementValue : JASWaveHandle { + ~AudioWaveReplacementValue() override; + [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; + [[nodiscard]] intptr_t getWavePtr() const override; + [[nodiscard]] void const* getAramBaseAddress() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map> s_replacements; + +struct RuntimeWaveReplacementSlot { + std::string bundle_path; + AudioWaveBank bank; + u16 wave_id; + + u8 base_key; + bool loop; + u32 loop_start_sample; + u32 loop_end_sample; + + AudioWaveFormat format; + f32 sample_rate; + u32 sample_count; + s16 sample_value_last; + s16 sample_value_penult; + + std::shared_ptr data; +}; + +using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +} diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp new file mode 100644 index 0000000000..3541f6be81 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -0,0 +1,93 @@ +#if DUSK_OPUS + +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/cast.hpp" +#include "opusfile.h" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::opus"); + +struct OpusHandle { + OggOpusFile* file; + + ~OpusHandle() { + op_free(file); + } + + operator OggOpusFile*() const { + return file; + } +}; + +} + +namespace dusk::mods::svc::audio_res { +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + OpusHead head; + const int result = op_test(&head, fileData.data(), fileData.size()); + if (result != 0) { + return MOD_UNSUPPORTED; + } + + if (head.channel_count != 1) { + Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", mod.metadata.id, slot.bundle_path, head.channel_count); + return MOD_INVALID_ARGUMENT; + } + + if (head.stream_count != 1) { + Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + int error; + auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error); + if (error != 0) { + Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, slot.bundle_path, error); + return MOD_ERROR; + } + + const OpusHandle handle(handle_raw); + auto const length = op_pcm_total(handle, -1); + if (length < 0) { + Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, slot.bundle_path); + return MOD_ERROR; + } + + SampleDataPcm16 pcm_buffer; + pcm_buffer.data.resize(length); + + std::span span_pcm(pcm_buffer.data); + + while (!span_pcm.empty()) { + auto read_result = op_read( + handle, + span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), + nullptr); + + if (read_result <= 0) { + Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, slot.bundle_path, read_result); + return MOD_ERROR; + } + + if (read_result > span_pcm.size()) { + Log.fatal("What teh fuck?"); + } + + span_pcm = span_pcm.subspan(read_result); + } + + pcm_buffer.be_swap(); + + slot.data = std::make_shared(std::move(pcm_buffer)); + slot.sample_rate = 48000; // Opus always decodes at 48 kHz. + slot.sample_count = length; + slot.format = Pcm16; + + return MOD_OK; +} +} + +#endif diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp new file mode 100644 index 0000000000..69e1e2e10f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -0,0 +1,145 @@ +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/alignment.hpp" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::wav"); + +struct ChunkHeader { + char magic[4]; + LE(u32) size; +}; + +struct RiffChunk { + ChunkHeader header; // magic "RIFF" + char id[4]; // "WAVE" + // Data after chunk. +}; + +constexpr u16 WAVE_FORMAT_PCM = 0x0001; + +struct FmtPcmChunkData { + ChunkHeader header; + + u16 wFormatTag; + u16 nChannels; + u32 nSamplesPerSec; + u32 nAvgBytesPerSec; + u16 nBlockAlign; + u16 wBitsPerSample; +}; + +bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { + return memcmp(field, expected, 4) == 0; +} + +} + +namespace dusk::mods::svc::audio_res { + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + using namespace helpers::alignment; + + if (fileData.size() < sizeof(RiffChunk)) { + return MOD_UNSUPPORTED; + } + + auto const riffChunk = read_unaligned(&fileData[0]); + if (!check_four_cc(riffChunk.header.magic, "RIFF")) { + return MOD_UNSUPPORTED; + } + + if (!check_four_cc(riffChunk.id, "WAVE")) { + return MOD_UNSUPPORTED; + } + + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size()) { + Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + bool has_read_fmt = false; + SampleDataPcm16 pcmData; + + auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size); + while (waveData.size() > sizeof(ChunkHeader)) { + auto const chunkHeader = read_unaligned(&waveData[0]); + + if (check_four_cc(chunkHeader.magic, "fmt ")) { + if (has_read_fmt) { + Log.error("[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (waveData.size() < sizeof(FmtPcmChunkData)) { + Log.error("[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const fmtChunk = read_unaligned(&waveData[0]); + + if (fmtChunk.nChannels != 1) { + Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wBitsPerSample != 16) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + has_read_fmt = true; + slot.sample_rate = static_cast(fmtChunk.nSamplesPerSec); + + } else if (check_four_cc(chunkHeader.magic, "data")) { + if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) { + Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const sampleData = waveData.subspan(sizeof(chunkHeader), chunkHeader.size); + for (size_t i = 0; i + 1 < sampleData.size(); i += 2) { + pcmData.data.push_back(read_unaligned(&sampleData[i])); + } + } + + auto skip = sizeof(chunkHeader) + chunkHeader.size; + if (skip % 2 != 0) { + skip += 1; + } + + if (waveData.size() < skip) { + Log.error("[{}] wav file {}: unexpected EOF", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + waveData = waveData.subspan(skip); + } + + if (!has_read_fmt) { + Log.error("[{}] wav file {}: missing fmt chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (pcmData.data.empty()) { + Log.error("[{}] wav file {}: missing data chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + pcmData.be_swap(); + + slot.format = Pcm16; + slot.sample_count = pcmData.size() / sizeof(u16); + slot.data = std::make_unique(std::move(pcmData)); + + return MOD_OK; +} + +} diff --git a/src/dusk/mods/svc/registry.cpp b/src/dusk/mods/svc/registry.cpp index 2033d47475..a9d82e0993 100644 --- a/src/dusk/mods/svc/registry.cpp +++ b/src/dusk/mods/svc/registry.cpp @@ -210,6 +210,7 @@ void ModLoader::init_services() { &svc::g_gameModule, &svc::g_cameraModule, &svc::g_gfxModule, + &svc::g_audioResModule, }) { svc::register_module(*module); diff --git a/src/dusk/mods/svc/registry.hpp b/src/dusk/mods/svc/registry.hpp index 191626e0ef..a2f597def4 100644 --- a/src/dusk/mods/svc/registry.hpp +++ b/src/dusk/mods/svc/registry.hpp @@ -72,5 +72,6 @@ extern const ServiceModule g_uiModule; extern const ServiceModule g_gameModule; extern const ServiceModule g_cameraModule; extern const ServiceModule g_gfxModule; +extern const ServiceModule g_audioResModule; } // namespace dusk::mods::svc From de44bbaa268e91dbcd2ec2463abcb5b08c2991f9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 16:02:42 +0200 Subject: [PATCH 009/100] Example mod for audio_res service --- mods/audio_mod/CMakeLists.txt | 19 ++++++++++++++ mods/audio_mod/mod.json | 7 ++++++ mods/audio_mod/res/attribution.txt | 1 + mods/audio_mod/res/go.opus | Bin 0 -> 6249 bytes mods/audio_mod/res/go.wav | Bin 0 -> 62814 bytes mods/audio_mod/src/mod.cpp | 39 +++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+) create mode 100644 mods/audio_mod/CMakeLists.txt create mode 100644 mods/audio_mod/mod.json create mode 100644 mods/audio_mod/res/attribution.txt create mode 100644 mods/audio_mod/res/go.opus create mode 100644 mods/audio_mod/res/go.wav create mode 100644 mods/audio_mod/src/mod.cpp diff --git a/mods/audio_mod/CMakeLists.txt b/mods/audio_mod/CMakeLists.txt new file mode 100644 index 0000000000..083a348ed2 --- /dev/null +++ b/mods/audio_mod/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.25) +project(audio_mod CXX) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") + option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if (DUSK_MOD_USE_FULL_TREE) + add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL) + else () + add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL) + endif () +endif () + +add_mod(audio_mod + SOURCES src/mod.cpp + MOD_JSON mod.json + RES_DIR res +) diff --git a/mods/audio_mod/mod.json b/mods/audio_mod/mod.json new file mode 100644 index 0000000000..7151999aa6 --- /dev/null +++ b/mods/audio_mod/mod.json @@ -0,0 +1,7 @@ +{ + "id": "dev.twilitrealm.audio_mod", + "name": "[DEMO] Audio Mod", + "version": "1.0.0", + "author": "You, the listener", + "description": "An example Dusklight mod" +} diff --git a/mods/audio_mod/res/attribution.txt b/mods/audio_mod/res/attribution.txt new file mode 100644 index 0000000000..5c9d15d0fd --- /dev/null +++ b/mods/audio_mod/res/attribution.txt @@ -0,0 +1 @@ +go.opus and go.wav are licensed CC0, from https://kenney.nl/assets/voiceover-pack \ No newline at end of file diff --git a/mods/audio_mod/res/go.opus b/mods/audio_mod/res/go.opus new file mode 100644 index 0000000000000000000000000000000000000000..d5373b60b19c60a8c5099fa94f569993f2d352cb GIT binary patch literal 6249 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

l7mlO`x87hVw}jC|mBf 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 literal 0 HcmV?d00001 diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp new file mode 100644 index 0000000000..a68e6bb98e --- /dev/null +++ b/mods/audio_mod/src/mod.cpp @@ -0,0 +1,39 @@ +#include "mods/service.hpp" +#include "mods/svc/audio_res.h" +#include "mods/svc/log.h" + +DEFINE_MOD(); +IMPORT_SERVICE(LogService, svc_log); +IMPORT_SERVICE(AudioResService, svc_audio_res); + +extern "C" { + +MOD_EXPORT ModResult mod_initialize(ModError*) { + svc_log->info(mod_ctx, "template_mod initialized"); + AudioWaveHandle handle; + svc_audio_res->replace_wave( + mod_ctx, + SoundEffects, + 4238, + "res/go.opus", + nullptr, + &handle); + svc_audio_res->replace_wave( + mod_ctx, + SoundEffects, + 4237, + "res/go.opus", + nullptr, + &handle); + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + svc_log->info(mod_ctx, "template_mod unloaded"); + return MOD_OK; +} +} From f6e05eb04fc178d423b0dc57d18bbab852222d8c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 26 Jul 2026 14:25:28 +0200 Subject: [PATCH 010/100] Unhardcode sound effects wave bank in replacements --- libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h | 2 +- libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h | 2 +- libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h | 6 ++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 3 ++- libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASWSParser.cpp | 4 ++-- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index cf739b18a2..ed4ddbf4cd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -50,7 +50,7 @@ struct JASBasicWaveBank : public JASWaveBank { u32 getWaveCount() const { return mWaveCount; } }; - JASBasicWaveBank(); + JASBasicWaveBank(IF_DUSK(u32 bankId)); ~JASBasicWaveBank(); TWaveGroup* getWaveGroup(u32); void setGroupCount(u32, JKRHeap*); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h index 1dce0968a9..8ccbc659bd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h @@ -21,7 +21,7 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc { /* 0x28 */ JASHeap* mHeap; }; - JASSimpleWaveBank(); + JASSimpleWaveBank(IF_DUSK(u32 bankId)); ~JASSimpleWaveBank(); void setWaveTableSize(u32, JKRHeap*); JASWaveHandle* getWaveHandle(u32) const; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 20b4be89fd..f559e86c4c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -59,6 +59,12 @@ public: */ class JASWaveBank { public: +#if TARGET_PC + const u32 bankId; + + JASWaveBank(u32 bankId) : bankId(bankId) { } +#endif + virtual ~JASWaveBank() {} virtual JASWaveHandle* getWaveHandle(u32) const = 0; virtual JASWaveArc* getWaveArc(u32) = 0; diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index e96b4df174..9491204cc7 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -33,7 +33,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } #if TARGET_PC - auto const found_replacement = s_replacements.find(AudioWaveKey(SoundEffects, stack_60.field_0x1a)); + auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + auto const found_replacement = s_replacements.find(key); if (found_replacement != s_replacements.end()) { waveHandle = found_replacement->second.get(); } diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index 80bda99ad9..9f9e16325c 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -6,7 +6,7 @@ #include "JSystem/JUtility/JUTAssert.h" #include -JASBasicWaveBank::JASBasicWaveBank() { +JASBasicWaveBank::JASBasicWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveGroupArray = NULL; mHandleCount = 0; diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index fc49df148d..f9e4382512 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -3,7 +3,7 @@ #include "JSystem/JAudio2/JASSimpleWaveBank.h" #include -JASSimpleWaveBank::JASSimpleWaveBank() { +JASSimpleWaveBank::JASSimpleWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveTableSize = 0; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index f097e93289..91552b24c5 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -30,7 +30,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* u32 free_size = heap->getFreeSize(); THeader* header = (THeader*)stream; - JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank; + JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } @@ -82,7 +82,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap return NULL; } - JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank; + JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } From 4e46c04c919b4960e58fd2a8c6f4923106ffbf77 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 26 Jul 2026 15:00:53 +0200 Subject: [PATCH 011/100] Add field to JASChannel to keep sample data backing wave replacements alive How to ensure stuff doesn't get freed when mod reloading gets involved? More shared_ptr --- .../include/JSystem/JAudio2/JASChannel.h | 20 +++++++++++++++++++ .../include/JSystem/JAudio2/JASWaveInfo.h | 10 ++++++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 7 +++++-- src/dusk/mods/svc/audio_res/audio_res.cpp | 4 ++++ src/dusk/mods/svc/audio_res/audio_res.hpp | 8 ++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 59512fbadd..2310cc590f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -8,6 +8,7 @@ #include "JSystem/JAudio2/JASWaveInfo.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include +#include struct JASDSPChannel; @@ -15,6 +16,20 @@ namespace JASDsp { struct TChannel; } +#if TARGET_PC +/** + * An object to manage the lifetime of the sample data pointed to by JASChannel. + * + * JASChannel can (optionally) accept an unique_ptr to an object to this type, + * in which case it will be destroyed when the JASChannel is done. + * + * @see JASChannel::mSampleReference + */ +struct JASSampleDataReference { + virtual ~JASSampleDataReference() = default; +}; +#endif + /** * @ingroup jsystem-jaudio * @@ -176,6 +191,11 @@ public: * If nullptr, the regular emulated ARAM is used. */ void const* mAramBaseAddress; + + /** + * @see JASSampleDataReference + */ + std::unique_ptr mSampleReference; #endif static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index f559e86c4c..45874f5aa7 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -2,6 +2,11 @@ #define JASWAVEINFO_H #include +#include + +#if TARGET_PC +struct JASSampleDataReference; +#endif struct JASWaveArc; @@ -50,6 +55,11 @@ public: * @see JASChannel::mAramBaseAddress */ [[nodiscard]] virtual void const* getAramBaseAddress() const = 0; + + /** + * Create a @ref JASSampleDataReference to keep the sample data for this wave alive. + */ + [[nodiscard]] virtual std::unique_ptr getSampleReference() const { return nullptr; } #endif }; diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 9491204cc7..1dc66b377e 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -38,6 +38,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 if (found_replacement != s_replacements.end()) { waveHandle = found_replacement->second.get(); } + + auto const aramBase = waveHandle->getAramBaseAddress(); #endif const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); @@ -45,7 +47,7 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } intptr_t wavePtr = waveHandle->getWavePtr(); - if (!wavePtr IF_DUSK(&& !waveHandle->getAramBaseAddress())) { + if (!wavePtr IF_DUSK(&& !aramBase)) { return NULL; } @@ -57,7 +59,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 channel->field_0xdc.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; #if TARGET_PC - channel->mAramBaseAddress = waveHandle->getAramBaseAddress(); + channel->mAramBaseAddress = aramBase; + channel->mSampleReference = waveHandle->getSampleReference(); #endif channel->field_0xdc.mChannelType = stack_60.field_0x1c; channel->setBankDisposeID(param_0); diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 20b3e8a95f..d95aba652a 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -201,6 +201,10 @@ intptr_t AudioWaveReplacementValue::getWavePtr() const { return 0; } +std::unique_ptr AudioWaveReplacementValue::getSampleReference() const { + return std::make_unique(data); +} + void SampleDataPcm16::be_swap() { for (auto& sample : data) { ::be_swap(sample); diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index b336dbeda8..bed4567af8 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASWaveInfo.h" #include "absl/container/flat_hash_map.h" #include "mods/svc/audio_res.h" @@ -40,6 +41,12 @@ struct SampleDataU8 : SampleDataBuf { } }; +struct SampleReference : JASSampleDataReference { + explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} + + std::shared_ptr data; +}; + struct AudioWaveKey { AudioWaveBank bank; u32 wave_id; @@ -59,6 +66,7 @@ struct AudioWaveReplacementValue : JASWaveHandle { [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; [[nodiscard]] intptr_t getWavePtr() const override; [[nodiscard]] void const* getAramBaseAddress() const override; + [[nodiscard]] std::unique_ptr getSampleReference() const override; JASWaveInfo wave_info; std::shared_ptr data; From 92906a3269793004bbc6c44b9ac0b5ddb12ad44e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 27 Jul 2026 15:41:08 +0200 Subject: [PATCH 012/100] Audio replacement priority & lifecycle handling --- libs/JSystem/src/JAudio2/JASBank.cpp | 3 +- sdk/include/mods/svc/audio_res.h | 2 + src/dusk/mods/svc/audio_res/audio_res.cpp | 85 ++++++++++++++++++----- src/dusk/mods/svc/audio_res/audio_res.hpp | 6 +- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 1dc66b377e..b85258bb8b 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -34,9 +34,10 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 } #if TARGET_PC auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + std::lock_guard lock(s_replacements_mutex); auto const found_replacement = s_replacements.find(key); if (found_replacement != s_replacements.end()) { - waveHandle = found_replacement->second.get(); + waveHandle = &found_replacement->second; } auto const aramBase = waveHandle->getAramBaseAddress(); diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index f5ec3693dc..0d126c1912 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -51,6 +51,8 @@ typedef struct AudioResService { char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + + ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); } AudioResService; #ifdef __cplusplus diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index d95aba652a..dc7b2337fd 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -6,6 +6,7 @@ #include "../registry.hpp" #include "../slot_map.hpp" #include "aurora/lib/logging.hpp" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/mods/loader/loader.hpp" namespace dusk::mods::svc { @@ -15,6 +16,8 @@ namespace { using namespace dusk::helpers::cast; +bool audio_replacements_dirty = false; + aurora::Module Log("dusk::mods::svc::audio_res"); SlotMap s_waveReplacements; @@ -163,14 +166,7 @@ ModResult insert_replace_wave( slot.loop_end_sample = slot.sample_count; } - // TODO: Handle conflicts - - audio_res::AudioWaveReplacementValue value; - value.data = slot.data; - value.wave_info = wave_info_from_slot(slot); - audio_res::s_replacements.emplace( - audio_res::AudioWaveKey{.bank = bank, .wave_id = wave_id}, - std::make_unique(std::move(value))); + audio_replacements_dirty = true; const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); if (out_handle) { @@ -180,13 +176,64 @@ ModResult insert_replace_wave( return MOD_OK; } +bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { + auto const result = s_waveReplacements.erase_owned(handle, mod); + audio_replacements_dirty |= result; + return result; +} + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + if (!wave_remove(*mod, handle)) { + Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + return MOD_OK; +} + constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), - .replace_wave = &audio_res::insert_replace_wave}; + .replace_wave = &insert_replace_wave, + .remove_wave = &remove_wave, +}; + +void sync_audio_replacements() { + audio_replacements_dirty = false; + + absl::flat_hash_map new_map; + + for (auto const& mod : ModLoader::instance().active_mods()) { + for (auto const& slot : s_waveReplacements.take_all(mod)) { + auto const wave_info = wave_info_from_slot(slot.value); + new_map.emplace( + AudioWaveKey(slot.value.bank, slot.value.wave_id), + AudioWaveReplacementValue(wave_info, slot.value.data)); + } + } + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); + + std::lock_guard lock(s_replacements_mutex); + // Note: new_map will contain the old contents, and is dropped *outside* the lock. + // As to avoid holding the lock any longer than necessary. + std::exchange(s_replacements, std::move(new_map)); + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); +} + +void replacements_remove_mod(LoadedMod& mod) { + s_waveReplacements.erase_all(mod); + + audio_replacements_dirty = true; +} } -absl::flat_hash_map> s_replacements; +absl::flat_hash_map s_replacements; +std::mutex s_replacements_mutex; AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { @@ -218,14 +265,14 @@ constinit const ServiceModule g_audioResModule{ .majorVersion = AUDIO_RES_SERVICE_MAJOR, .minorVersion = AUDIO_RES_SERVICE_MINOR, .service = &audio_res::s_audioResService, - // .modDetached = overlay_remove_mod, - // .lifecycleApplied = overlay_sync_files, - // .frameEnd = - // [] { - // if (consume_overlays_dirty()) { - // overlay_sync_files(); - // } - // }, + .modDetached = audio_res::replacements_remove_mod, + .lifecycleApplied = audio_res::sync_audio_replacements, + .frameEnd = + [] { + if (audio_res::audio_replacements_dirty) { + audio_res::sync_audio_replacements(); + } + } + }; }; -} diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index bed4567af8..4ae3e0dd1c 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -51,6 +51,8 @@ struct AudioWaveKey { AudioWaveBank bank; u32 wave_id; + AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; + template friend H AbslHashValue(H h, const AudioWaveKey& k) { return H::combine(std::move(h), k.bank, k.wave_id); @@ -62,6 +64,7 @@ struct AudioWaveKey { }; struct AudioWaveReplacementValue : JASWaveHandle { + AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) : wave_info(wave_info), data(std::move(data)) {}; ~AudioWaveReplacementValue() override; [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; [[nodiscard]] intptr_t getWavePtr() const override; @@ -72,7 +75,8 @@ struct AudioWaveReplacementValue : JASWaveHandle { std::shared_ptr data; }; -extern absl::flat_hash_map> s_replacements; +extern absl::flat_hash_map s_replacements; +extern std::mutex s_replacements_mutex; struct RuntimeWaveReplacementSlot { std::string bundle_path; From 5cf52b824091801e6f82cc9137d826c8a9ef03ff Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 27 Jul 2026 15:45:42 +0200 Subject: [PATCH 013/100] Acknowledge use of WSYS header ID in recent changes --- docs/imhex/jaudio/wsys.hexpat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/imhex/jaudio/wsys.hexpat b/docs/imhex/jaudio/wsys.hexpat index 88027ade04..aa131ea226 100644 --- a/docs/imhex/jaudio/wsys.hexpat +++ b/docs/imhex/jaudio/wsys.hexpat @@ -207,7 +207,7 @@ struct THeader { /** * ID of wave bank. * This matches the value passed to the BAA load command. - * The game does not use this value itself. + * The game originally does not use this value itself, but Dusklight does rely on it. */ u32 mId; From 9b0af0bf5c9d5d81830364f02cc01f3b270be483 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 02:17:09 +0200 Subject: [PATCH 014/100] BST hexpat --- docs/imhex/jaudio/bst.hexpat | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/imhex/jaudio/bst.hexpat diff --git a/docs/imhex/jaudio/bst.hexpat b/docs/imhex/jaudio/bst.hexpat new file mode 100644 index 0000000000..07bf28684e --- /dev/null +++ b/docs/imhex/jaudio/bst.hexpat @@ -0,0 +1,71 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct ItemSoundEffect { + u8 mPriority; + u8 mVolume; + padding[2]; + u32 mSwBit; + float mPitch; +}; + +struct ItemSequence { + u8 mPriority; + u8 mVolume; + u16 mResourceId; +}; + +struct ItemStream { + u8 mPriority; + u8 mVolume; + u16 mStreamPanParameters; + char* mStreamFilePath[] : u32; +}; + +struct SoundTableGroupEntry { + u8 mTypeId; + if (mTypeId == 0x51) { + ItemSoundEffect* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x60) { + ItemSequence* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x70 || mTypeId == 0x71) { + ItemStream* mOffset : u24 [[inline]]; + } else { + u24 mOffset; + } +}; + +struct TGroup { + u32 mNumItems; + padding[4]; + SoundTableGroupEntry mEntries[mNumItems]; +}; + +struct TSection { + u32 mNumGroups; + Offset mGroupOffsets[mNumGroups]; +}; + +struct Root { + u32 mSectionNumber; + Offset mSectionOffsets[mSectionNumber]; +}; + +struct THeader { + type::Magic<"BST "> mMagic; + + padding[8]; + Root* mRoot : u32; +}; + +THeader header @ 0x0; \ No newline at end of file From 67eea1599a0f74345f0cafeba5219d29315c7544 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 02:20:21 +0200 Subject: [PATCH 015/100] JAudio doc/naming pass --- include/Z2AudioLib/Z2Audience.h | 129 ++++++--- .../include/JSystem/JAudio2/JAISound.h | 10 +- .../include/JSystem/JAudio2/JAISoundParams.h | 12 +- .../include/JSystem/JAudio2/JAUAudibleParam.h | 22 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 166 ++++++++++- libs/JSystem/src/JAudio2/JAISe.cpp | 6 +- libs/JSystem/src/JAudio2/JAISeMgr.cpp | 22 +- libs/JSystem/src/JAudio2/JAISound.cpp | 8 +- src/Z2AudioLib/Z2Audience.cpp | 268 +++++++++--------- src/Z2AudioLib/Z2SceneMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundInfo.cpp | 150 +++++----- src/Z2AudioLib/Z2SoundObject.cpp | 2 +- 12 files changed, 495 insertions(+), 302 deletions(-) diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index e6d2f7da3a..b55b7a7424 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -9,6 +9,8 @@ #include "JSystem/JAudio2/JAUAudibleParam.h" #include "JSystem/TPosition3.h" +#define Z2_AUDIO_PLAYERS 1 + struct Z2Audible; struct Z2AudibleAbsPos { @@ -49,10 +51,14 @@ struct Z2AudioCamera { f32 getCamDist() const { return mCamDist; } - /* 0x00 */ JGeometry::TPosition3f32 field_0x0; + /* 0x00 */ JGeometry::TPosition3f32 mViewMatrix; /* 0x30 */ JGeometry::TVec3 mVel; /* 0x3C */ JGeometry::TVec3 mPos; - /* 0x48 */ JGeometry::TVec3 field_0x48; + + /** + * + */ + /* 0x48 */ JGeometry::TVec3 mLastPos; /* 0x54 */ f32 mFovySin; /* 0x58 */ f32 mVolCenterZ; /* 0x5C */ f32 mTargetVolume; @@ -83,14 +89,14 @@ struct Z2SpotMic { /* 0x04 */ f32 field_0x4; /* 0x08 */ f32 field_0x8; /* 0x0C */ f32 field_0xc; - /* 0x10 */ Z2AudioCamera* field_0x10[1]; + /* 0x10 */ Z2AudioCamera* field_0x10[Z2_AUDIO_PLAYERS]; /* 0x14 */ Vec* mPosPtr; - /* 0x18 */ f32 field_0x18[1]; + /* 0x18 */ f32 field_0x18[Z2_AUDIO_PLAYERS]; /* 0x1C */ f32 field_0x1c; - /* 0x20 */ f32 field_0x20[1]; + /* 0x20 */ f32 field_0x20[Z2_AUDIO_PLAYERS]; /* 0x24 */ bool mIgnoreIfOut; /* 0x25 */ bool mMicOn; - /* 0x26 */ bool field_0x26[1]; + /* 0x26 */ bool field_0x26[Z2_AUDIO_PLAYERS]; }; // Size: 0x28 struct Z2Audience3DSetting { @@ -102,62 +108,99 @@ struct Z2Audience3DSetting { void updateDolbyDist(f32, f32); void calcVolumeFactorAll() { - field_0x0[1] = 1.25f * field_0x0[0]; - field_0x0[2] = 1.5f * field_0x0[0]; - field_0x0[3] = 2.0f * field_0x0[0]; - field_0x0[4] = 3.0f * field_0x0[0]; - field_0x0[5] = 4.0f * field_0x0[0]; - field_0x0[6] = 6.0f * field_0x0[0]; - field_0x0[7] = 8.0f * field_0x0[0]; - field_0x0[8] = 0.9f * field_0x0[0]; - field_0x0[9] = 0.8f * field_0x0[0]; - field_0x0[10] = 0.7f * field_0x0[0]; - field_0x0[11] = 0.6f * field_0x0[0]; - field_0x0[12] = 0.5f * field_0x0[0]; - field_0x0[13] = 0.4f * field_0x0[0]; - field_0x0[14] = 0.3f * field_0x0[0]; + mDistanceMaxes[1] = 1.25f * mDistanceMaxes[0]; + mDistanceMaxes[2] = 1.5f * mDistanceMaxes[0]; + mDistanceMaxes[3] = 2.0f * mDistanceMaxes[0]; + mDistanceMaxes[4] = 3.0f * mDistanceMaxes[0]; + mDistanceMaxes[5] = 4.0f * mDistanceMaxes[0]; + mDistanceMaxes[6] = 6.0f * mDistanceMaxes[0]; + mDistanceMaxes[7] = 8.0f * mDistanceMaxes[0]; + mDistanceMaxes[8] = 0.9f * mDistanceMaxes[0]; + mDistanceMaxes[9] = 0.8f * mDistanceMaxes[0]; + mDistanceMaxes[10] = 0.7f * mDistanceMaxes[0]; + mDistanceMaxes[11] = 0.6f * mDistanceMaxes[0]; + mDistanceMaxes[12] = 0.5f * mDistanceMaxes[0]; + mDistanceMaxes[13] = 0.4f * mDistanceMaxes[0]; + mDistanceMaxes[14] = 0.3f * mDistanceMaxes[0]; for (int i = 0; i < 15; i++) { - field_0x70[i] = (field_0x40 - 1.0f) / (field_0x0[i] - field_0x3c); + mVolumeFactor[i] = (mMinDistanceVolume - 1.0f) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcPriorityFactorAll() { for (int i = 0; i < 15; i++) { - field_0xac[i] = field_0x64 / (field_0x0[i] - field_0x3c); + mPriorityFactor[i] = mMaxDistancePriority / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcFxMixFactorAll() { for (int i = 0; i < 15; i++) { - field_0xe8[i] = (field_0x54 - field_0x50) / (field_0x0[i] - field_0x3c); + mFxMixFactor[i] = (mMaxDistanceFxMix - mMinDistanceFxMix) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } - /* 0x000 */ f32 field_0x0[15]; - /* 0x03C */ f32 field_0x3c; - /* 0x040 */ f32 field_0x40; - /* 0x044 */ f32 field_0x44; - /* 0x048 */ f32 field_0x48; - /* 0x04C */ f32 field_0x4c; - /* 0x050 */ f32 field_0x50; - /* 0x054 */ f32 field_0x54; - /* 0x058 */ f32 field_0x58; - /* 0x05C */ f32 field_0x5c; + /** + * Maximum distance a sound can reach before being "far away" + * Being far away affects stuff like culling, lowering its priority, forcibly stopping it, etc. + * Sounds select which entry they use based on their VolBits. + */ + /* 0x000 */ f32 mDistanceMaxes[15]; + + /** + * Distance at which the max volume of a sound is reached. + * i.e. sounds do *not* get louder if they get closer than this. + */ + /* 0x03C */ f32 mMaxVolumeDistance; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x040 */ f32 mMinDistanceVolume; + /* 0x044 */ f32 mDolbyFrontDistanceMax; + /* 0x048 */ f32 mDolbyBehindDistanceMax; + /* 0x04C */ f32 mDolbyCenterValue; + + /** + * FX Mix value at minimum distance (@ref mMaxVolumeDistance) + */ + /* 0x050 */ f32 mMinDistanceFxMix; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x054 */ f32 mMaxDistanceFxMix; + /* 0x058 */ f32 mPanFactor; + /* 0x05C */ f32 mSonicSpeed; // Used for doppler effect calculations. /* 0x060 */ f32 field_0x60; - /* 0x064 */ u32 field_0x64; + + /** + * Priority that sounds receive when "far away". + * @see mDistanceMaxes + */ + /* 0x064 */ u32 mMaxDistancePriority; /* 0x068 */ f32 field_0x68; /* 0x06C */ f32 field_0x6c; - /* 0x070 */ f32 field_0x70[15]; - /* 0x0AC */ f32 field_0xac[15]; - /* 0x0E8 */ f32 field_0xe8[15]; + /* 0x070 */ f32 mVolumeFactor[15]; + /* 0x0AC */ f32 mPriorityFactor[15]; + /* 0x0E8 */ f32 mFxMixFactor[15]; /* 0x124 */ bool mVolumeDistInit; /* 0x125 */ bool mDolbyDistInit; }; // Size: 0x128 struct Z2AudibleRelPos { - /* 0x00 */ JGeometry::TVec3 field_0x00; - /* 0x0C */ f32 field_0xC; - /* 0x10 */ f32 field_0x10; + /* 0x00 */ JGeometry::TVec3 mCameraRelative; + + /** + * Distance from mCameraRelative. This is from the object root and not the + * exact distance used for volume/priority calculations. + */ + /* 0x0C */ f32 mTrueDistance; + + /** + * Distance from mCameraRelative but offset by mVolCenterZ. + * This presumably means the distance is more centered on the object than mTrueDistance. + */ + /* 0x10 */ f32 mCenterDistance; }; struct Z2AudibleChannel { @@ -171,7 +214,7 @@ struct Z2AudibleChannel { } /* 0x00 */ JASSoundParams mParams; - /* 0x14 */ Z2AudibleRelPos field_0x14; + /* 0x14 */ Z2AudibleRelPos mRelPos; /* 0x28 */ f32 field_0x28; /* 0x2c */ f32 mPan; /* 0x30 */ f32 mDolby; @@ -200,8 +243,8 @@ struct Z2Audible : public JAIAudible, public JASPoolAllocObject { /* 0x10 */ JAUAudibleParam mParam; /* 0x14 */ Z2AudibleAbsPos mAbsPos; - /* 0x2C */ Z2AudibleChannel mChannel[1]; - /* 0x64 */ f32 field_0x64[1]; + /* 0x2C */ Z2AudibleChannel mChannel[Z2_AUDIO_PLAYERS]; + /* 0x64 */ f32 mMicDistances[Z2_AUDIO_PLAYERS]; }; struct Z2Audience : public JAIAudience, public JASGlobalInstance { diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index fa262a7426..4de69b8416 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -101,7 +101,7 @@ struct JAISoundStatus_ { bool isMute() const { return field_0x0.flags.mute; } bool isPaused() const { return field_0x0.flags.paused; } void pauseWhenOut() { - field_0x1.flags.flag3 = 1; + field_0x1.flags.mPauseWhenOut = 1; } /* 0x0 */ union { @@ -120,9 +120,9 @@ struct JAISoundStatus_ { /* 0x1 */ union { u8 value; struct { - u8 flag1 : 1; + u8 mComesBack : 1; u8 flag2 : 1; - u8 flag3 : 1; + u8 mPauseWhenOut : 1; u8 flag4 : 1; u8 flag5 : 1; u8 flag6 : 1; @@ -309,7 +309,7 @@ public: bool hasLifeTime() const { return status_.field_0x1.flags.flag2; } void removeLifeTime_() { - status_.field_0x1.flags.flag1 = false; + status_.field_0x1.flags.mComesBack = false; status_.field_0x1.flags.flag2 = 0; } @@ -346,7 +346,7 @@ public: void setComesBack(bool param_0) { JUT_ASSERT(354, status_.state.flags.calcedOnce == 0); - status_.field_0x1.flags.flag1 = 1; + status_.field_0x1.flags.mComesBack = 1; if (param_0) { status_.pauseWhenOut(); } diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h index 22d175154a..eccdc8deac 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h @@ -9,14 +9,14 @@ */ struct JAISoundParamsProperty { void init() { - field_0x0 = 1.0f; - field_0x4 = 0.0f; - field_0x8 = 1.0f; + mVolume = 1.0f; + mFxMix = 0.0f; + mPitch = 1.0f; } - /* 0x00 */ f32 field_0x0; - /* 0x04 */ f32 field_0x4; - /* 0x08 */ f32 field_0x8; + /* 0x00 */ f32 mVolume; + /* 0x04 */ f32 mFxMix; + /* 0x08 */ f32 mPitch; }; // Size: 0xC /** diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h index cc5653fe25..4c0ade1282 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h @@ -10,7 +10,7 @@ */ struct JAUAudibleParam { f32 getDopplerPower() const { - return field_0x0.bytes.b0_0 * (1.0f / 15.0f); + return field_0x0.bytes.mDopplerPower * (1.0f / 15.0f); } union { @@ -19,14 +19,18 @@ struct JAUAudibleParam { BE(u16) f1; } half; struct { - u8 b0_0 : 4; - u8 b0_4 : 1; - u8 b0_5 : 1; - u8 b0_6 : 1; - u8 b0_7 : 1; - u8 b1_0 : 1; - u8 b1_1 : 1; - u8 b1_2_7 : 6; + u8 mDopplerPower : 4; + u8 mCalculatePriority : 1; + u8 mCalcDistanceVolume : 1; + u8 mCalcFxMix : 1; + u8 mCullAtMaxDistance : 1; + u8 mCalcPan : 1; + u8 mCalcDolby : 1; + /** + * Most bits in this field are unused, 8 indicates clamping of volume + * to ensure it doesn't go below 0.2 in mixChannelOut() + */ + u8 mClampMinVolume : 6; u8 b2; u8 b3; } bytes; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 3571e386b6..40ac65bf45 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -5,16 +5,162 @@ #include "JSystem/JAudio2/JASGadget.h" #include "helpers/endian.h" +// Constants for TypeIDs of JAUSoundTable entries. + +/** + * Used as a test to see if the type ID is valid. All other type IDs contain this bit. + */ +#define SOUND_TYPEID_VALID 0x40 + +/** + * Sound is a sound effect. + */ +#define SOUND_TYPEID_SOUND_EFFECT 0x51 + +/** + * Sound is a music sequence. + */ +#define SOUND_TYPEID_SEQUENCE 0x60 + +/** + * Sound is a streamed music file. + */ +#define SOUND_TYPEID_STREAM 0x70 + +/** + * Sound is a streamed music file. Unsure of difference from SOUND_TYPEID_STREAM + */ +#define SOUND_TYPEID_STREAM_ALT 0x71 + +// +// Values for mSwBit on sound effect items. +// + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 + +#define SOUND_VOL_DIST_BIT_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_MASK >> 16) +#define SOUND_VOL_DIST_BIT_2_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_2_MASK >> 16) +#define SOUND_VOL_SOMETHING_MASK_SHIFTED (SOUND_SW_VOL_SOMETHING_MASK >> 16) + /** * @ingroup jsystem-jaudio - * + * A single entry in the sound table. + * Fields are interpreted differently depending on the type ID of the entry, + * which is not stored in this struct. */ struct JAUSoundTableItem { u8 mPriority; - u8 field_0x1; - BE(u16) mResourceId; - BE(u32) field_0x4; - BE(f32) field_0x8; + u8 mVolume; + union { + /** + * For music sequences: the resource ID of the sequence. + */ + BE(u16) mResourceId; + + /** + * For streamed music: bitpacked channel configuration. + */ + BE(u16) mStreamPanParameters; + }; + union { + /** + * For sound effects: bitpacked field controlling a *bunch* of audio parameters. + */ + BE(u32) mSwBit; + + /** + * For streamed music: offset (relative to start of BST) + * to null-terminated string containing music file path. + */ + BE(u32) mStreamFilePath; + }; + + /** + * For sound effects: pitch multiplier. + */ + BE(f32) mPitch; }; /** @@ -33,8 +179,8 @@ struct JAUSoundTable_ { mRoot = NULL; } - void init(const void* param_0) { - mData = param_0; + void init(const void* dataStart) { + mData = dataStart; // magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?) // I also do not know how it is different between JAUSoundTable and JAUSoundNameTable // Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber(). @@ -60,16 +206,16 @@ struct JAUSoundTable_ { return (Section*)((u8*)mData + offset); } - Group* getGroup(Section* param_1, int index) const { + Group* getGroup(Section* section, int index) const { int iVar1; if (index < 0) { return NULL; } - if ((u32)index >= param_1->mNumGroups) { + if ((u32)index >= section->mNumGroups) { return NULL; } - u32 offset = param_1->getGroupOffset(index); + u32 offset = section->getGroupOffset(index); if (offset == 0) { return NULL; } diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index 504c875bad..eb8ea7b90e 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -70,7 +70,7 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, if (inner_.field_0x26c) { switch (inner_.track.getStatus()) { case JASTrack::STATUS_STOPPED: - if (status_.field_0x1.flags.flag1) { + if (status_.field_0x1.flags.mComesBack) { startTrack_(params); } else { stop_JAISound_(); @@ -84,8 +84,8 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, startTrack_(params); } } - } else if (status_.field_0x1.flags.flag1) { - if (status_.field_0x1.flags.flag3) { + } else if (status_.field_0x1.flags.mComesBack) { + if (status_.field_0x1.flags.mPauseWhenOut) { inner_.track.pause(true); } else { stopTrack_(); diff --git a/libs/JSystem/src/JAudio2/JAISeMgr.cpp b/libs/JSystem/src/JAudio2/JAISeMgr.cpp index 0809ede5c6..25ed54ca39 100644 --- a/libs/JSystem/src/JAudio2/JAISeMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeMgr.cpp @@ -167,7 +167,7 @@ JAISeMgr::JAISeMgr(bool setInstance) : JASGlobalInstance(setInstance) } bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { if (mCategoryMgrs[i].JAISeCategoryMgr::isUsingSeqData(seqDataRegion)) { return true; } @@ -177,7 +177,7 @@ bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { bool r30 = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { switch (mCategoryMgrs[i].JAISeCategoryMgr::releaseSeqData(seqDataRegion)) { case 0: return 0; @@ -191,14 +191,14 @@ int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { } void JAISeMgr::setCategoryArrangement(const JAISeCategoryArrangement& arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].setMaxActiveSe(arrangement.mItems[i].mMaxActiveSe); mCategoryMgrs[i].setMaxInactiveSe(arrangement.mItems[i].mMaxInactiveSe); } } void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { int active = mCategoryMgrs[i].getMaxActiveSe(); JUT_ASSERT(299, active <= 255); arrangement->mItems[i].mMaxActiveSe = active; @@ -209,19 +209,19 @@ void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { } void JAISeMgr::stop() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stop(); } } void JAISeMgr::stopSoundID(JAISoundID id) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stopSoundID(id); } } void JAISeMgr::initParams() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].getParams()->init(); } } @@ -270,16 +270,16 @@ JAISe* JAISeMgr::newSe_(int category, u32 priority) { void JAISeMgr::calc() { mParams.calc(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_calc_(); } - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_freeDeadSe_(); } } void JAISeMgr::mixOut() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_mixOut_(mParams, mSoundActivity); } } @@ -324,7 +324,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry int JAISeMgr::getNumActiveSe() const { int num = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { num += mCategoryMgrs[i].getNumSe(); } return num; diff --git a/libs/JSystem/src/JAudio2/JAISound.cpp b/libs/JSystem/src/JAudio2/JAISound.cpp index fe6b45bb8b..6dec60c39d 100644 --- a/libs/JSystem/src/JAudio2/JAISound.cpp +++ b/libs/JSystem/src/JAudio2/JAISound.cpp @@ -54,9 +54,9 @@ s32 JAISoundStatus_::unlockIfLocked() { } void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* outParams, f32 param_2) { - outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.field_0x0) * param_2; - outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.field_0x4); - outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.field_0x8); + outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.mVolume) * param_2; + outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.mFxMix); + outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.mPitch); outParams->mPan = (inParams.mPan + move_.params_.mPan) - 0.5f; outParams->mDolby = inParams.mDolby + move_.params_.mDolby; } @@ -181,7 +181,7 @@ bool JAISound::calc_JAISound_() { } if (audience_ != NULL && audible_ != NULL) { - if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.flag1 == 0) { + if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.mComesBack == 0) { stop_JAISound_(); } } else { diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index dda1a4b2d0..5dfe3baed9 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -36,14 +36,14 @@ Z2Audible::Z2Audible(const JGeometry::TVec3& pos, const JGeometry::TVec3= 0); - JUT_ASSERT(81, index < 1); + JUT_ASSERT(81, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index].mParams; } void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundParams& inParams, int index) { JUT_ASSERT(89, index >= 0); - JUT_ASSERT(90, index < 1); + JUT_ASSERT(90, index < 1); // Z2_AUDIO_PLAYERS Z2AudibleChannel* channel = &mChannel[index]; - u8 iStack_94 = (mParam.field_0x0.half.f1 & 0xf00) >> 8; - if (iStack_94 == 0) { + u8 volSomething = (mParam.field_0x0.half.f1 & SOUND_VOL_SOMETHING_MASK_SHIFTED) >> 8; + if (volSomething == 0) { channel->mParams.combine(outParams, inParams); return; } @@ -91,7 +91,7 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa f32 dVar9 = inParams.mVolume * local_b8.length(); f32 dVar10 = 1.0f; - f32 dVar12 = Z2Calc::linearTransform(iStack_94, 0.0f, 15.0f, 1.0f, 0.3f, true); + f32 dVar12 = Z2Calc::linearTransform(volSomething, 0.0f, 15.0f, 1.0f, 0.3f, true); if (inParams.mVolume > dVar12 && dVar9 > 0.001f) { dVar10 = Z2Calc::getParamByExp(dVar9, 0.3f, 0.001f, 0.1f, dVar12, 1.0f, Z2Calc::CURVE_POSITIVE); @@ -141,18 +141,18 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa Z2AudibleChannel* Z2Audible::getChannel(int index) { JUT_ASSERT(220, index >= 0); - JUT_ASSERT(221, index < 1); + JUT_ASSERT(221, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index]; } u32 Z2Audible::getDistVolBit() { u16 uVar1 = getAudibleParam()->field_0x0.half.f1; if (uVar1 != 0) { - if ((uVar1 & 7) != 0) { - return uVar1 & 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED) != 0) { + return uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED; } - if ((uVar1 & 0x70) != 0) { - return ((int)(uVar1 & 0x70) >> 4) + 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) != 0) { + return ((int)(uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) >> 4) + 7; } } @@ -166,15 +166,15 @@ Z2Audience3DSetting::Z2Audience3DSetting() { } void Z2Audience3DSetting::init() { - field_0x40 = Z2Param::MIN_DISTANCE_VOLUME; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x54 = Z2Param::DISTANCE_FX_PARAM; - field_0x50 = 0.0f; - field_0x4c = Z2Param::DOLBY_CENTER_VALUE; - field_0x58 = 0.5f; - field_0x64 = 0xff; - field_0x5c = Z2Param::SONIC_SPEED; + mMinDistanceVolume = Z2Param::MIN_DISTANCE_VOLUME; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mMaxDistanceFxMix = Z2Param::DISTANCE_FX_PARAM; + mMinDistanceFxMix = 0.0f; + mDolbyCenterValue = Z2Param::DOLBY_CENTER_VALUE; + mPanFactor = 0.5f; + mMaxDistancePriority = 0xff; + mSonicSpeed = Z2Param::SONIC_SPEED; field_0x60 = 1.5f; initVolumeDist(); @@ -184,8 +184,8 @@ void Z2Audience3DSetting::init() { void Z2Audience3DSetting::initVolumeDist() { if (mVolumeDistInit) return; - field_0x0[0] = Z2Param::DISTANCE_MAX; - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mDistanceMaxes[0] = Z2Param::DISTANCE_MAX; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; calcVolumeFactorAll(); calcPriorityFactorAll(); @@ -197,12 +197,12 @@ void Z2Audience3DSetting::initVolumeDist() { static f32 cNearFarRatio = Z2Param::MAX_VOLUME_DISTANCE / Z2Param::DISTANCE_MAX; void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { - field_0x0[0] = param_0; + mDistanceMaxes[0] = param_0; if (param_0 > Z2Param::DISTANCE_MAX) { - field_0x3c = cNearFarRatio * param_0; + mMaxVolumeDistance = cNearFarRatio * param_0; } else { - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; } calcVolumeFactorAll(); @@ -214,29 +214,29 @@ void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { void Z2Audience3DSetting::initDolbyDist() { if (!mDolbyDistInit) { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x68 = -field_0x4c / field_0x44; - field_0x6c = (1.0f - field_0x4c) / field_0x48; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + field_0x68 = -mDolbyCenterValue / mDolbyFrontDistanceMax; + field_0x6c = (1.0f - mDolbyCenterValue) / mDolbyBehindDistanceMax; mDolbyDistInit = true; } } void Z2Audience3DSetting::updateDolbyDist(f32 param_0, f32 param_1) { if (param_0 > -1.0f * Z2Param::DOLBY_FLONT_DISTANCE_MAX) { - field_0x44 = -1.0f * param_0; + mDolbyFrontDistanceMax = -1.0f * param_0; } else { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; } - if (param_1 < 2.0f * field_0x44) { - param_1 = 2.0f * field_0x44; + if (param_1 < 2.0f * mDolbyFrontDistanceMax) { + param_1 = 2.0f * mDolbyFrontDistanceMax; } if (param_1 > Z2Param::DOLBY_BEHIND_DISTANCE_MAX) { - field_0x48 = param_1; + mDolbyBehindDistanceMax = param_1; } else { - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; } mDolbyDistInit = false; @@ -254,28 +254,28 @@ Z2AudioCamera::Z2AudioCamera() { } void Z2AudioCamera::init() { - JGeometry::TPosition3f32 aTStack_38; - aTStack_38.identity(); + JGeometry::TPosition3f32 viewMatrix; + viewMatrix.identity(); JGeometry::TVec3 VStack_44; VStack_44.set(100000.0f, 100000.0f, 100000.0f); - setCameraState(aTStack_38, *(Vec*)&VStack_44, true); + setCameraState(viewMatrix, *(Vec*)&VStack_44, true); } -void Z2AudioCamera::setCameraState(f32 const (*param_0)[4], Vec& pos, bool param_2) { - field_0x0.set(param_0); - if (param_2) { +void Z2AudioCamera::setCameraState(f32 const (*viewMatrix)[4], Vec& pos, bool initial) { + mViewMatrix.set(viewMatrix); + if (initial) { mPos.set(pos); - field_0x48.set(mPos); + mLastPos.set(mPos); mVel.zero(); } else { - field_0x48.set(mPos); + mLastPos.set(mPos); mPos.set(pos); - mVel.sub(mPos, field_0x48); + mVel.sub(mPos, mLastPos); } } -void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, bool param_6) { +void Z2AudioCamera::setCameraState(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, bool initial) { JGeometry::TVec3 aTStack_c0; VECSubtract(¶m_2, &pos, (Vec*)&aTStack_c0); mCamDist = aTStack_c0.length(); @@ -312,7 +312,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 Z2GetAudience()->getSetting()->updateDolbyDist(mCamDist, mCamDist); } } else { - Vec aTStack_cc = {param_0[0][0], param_0[0][1], param_0[0][2]}; + Vec aTStack_cc = {viewMatrix[0][0], viewMatrix[0][1], viewMatrix[0][2]}; Mtx rotMtx; MTXRotAxisRad(rotMtx, &aTStack_cc, (M_PI / 180.0f) * (-1.0f * dVar10)); JGeometry::TVec3 aTStack_d8; @@ -324,7 +324,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 } } - setCameraState(param_0, pos, param_6); + setCameraState(viewMatrix, pos, initial); } void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { @@ -337,17 +337,17 @@ void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { return; } - Z2AudibleRelPos* relPos = &channel->field_0x14; - MTXMultVec(field_0x0, (Vec*)&audible->getPos(), (Vec*)&relPos->field_0x00); - relPos->field_0xC = relPos->field_0x00.length(); + Z2AudibleRelPos* relPos = &channel->mRelPos; + MTXMultVec(mViewMatrix, (Vec*)&audible->getPos(), (Vec*)&relPos->mCameraRelative); + relPos->mTrueDistance = relPos->mCameraRelative.length(); - JGeometry::TVec3 aTStack_38(relPos->field_0x00); + JGeometry::TVec3 aTStack_38(relPos->mCameraRelative); aTStack_38.z += mVolCenterZ; - relPos->field_0x10 = aTStack_38.length(); + relPos->mCenterDistance = aTStack_38.length(); } bool Z2AudioCamera::convertAbsToRel(Vec& src, Vec* dst) const { - MTXMultVec(field_0x0, &src, dst); + MTXMultVec(mViewMatrix, &src, dst); return isInSight(*dst); } @@ -395,7 +395,7 @@ void Z2SpotMic::calcVolumeFactor(int camID) { void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { JUT_ASSERT(622, camID >= 0); - JUT_ASSERT(623, camID < 1); + JUT_ASSERT(623, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn && mPosPtr != 0 && camera != NULL) { clearMicState(camID); @@ -406,8 +406,8 @@ void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { aVStack_58.z += camera->getVolCenterZ(); f32 dVar10 = aVStack_58.length(); - f32 dVar11 = Z2GetAudience()->getSetting()->field_0x0[0]; - f32 dVar12 = Z2GetAudience()->getSetting()->field_0x3c; + f32 dVar11 = Z2GetAudience()->getSetting()->mDistanceMaxes[0]; + f32 dVar12 = Z2GetAudience()->getSetting()->mMaxVolumeDistance; if (dVar10 > dVar11) { field_0x18[camID] = field_0xc; } else { @@ -443,21 +443,21 @@ f32 Z2SpotMic::calcMicDist(Z2Audible* audible) { return aTStack_1c.length(); } -u32 Z2SpotMic::calcMicPriority(f32 param_0) { - if (param_0 > field_0x4) { - return Z2GetAudience()->getSetting()->field_0x64; +u32 Z2SpotMic::calcMicPriority(f32 micDistance) { + if (micDistance > field_0x4) { + return Z2GetAudience()->getSetting()->mMaxDistancePriority; } - if (param_0 < field_0x0) { + if (micDistance < field_0x0) { return 0; } - return field_0x1c * (param_0 - field_0x0); + return field_0x1c * (micDistance - field_0x0); } f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { JUT_ASSERT(687, camID >= 0); - JUT_ASSERT(688, camID < 1); + JUT_ASSERT(688, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn == false) { return param_2; @@ -497,7 +497,7 @@ f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { } Z2Audience::Z2Audience() : JASGlobalInstance(true), field_0x4(1.0f), field_0x8(0x7f) { - mNumPlayers = 1; + mNumPlayers = Z2_AUDIO_PLAYERS; mUsingOffMicVol = false; mAudioCamera[0].init(); mAudioCamera[0].setMainCamera(true); @@ -512,11 +512,11 @@ bool Z2Audience::isActive() const { return Z2Audible::getTotalMemCount() != Z2Audible::getFreeMemCount(); } -void Z2Audience::setAudioCamera(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, int camID, bool param_7) { +void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, int camID, bool initial) { JUT_ASSERT(687, camID >= 0); JUT_ASSERT(688, camID < mNumPlayers); - mAudioCamera[camID].setCameraState(param_0, pos, param_2, param_3, param_4, param_5, param_7); + mAudioCamera[camID].setCameraState(viewMatrix, pos, param_2, param_3, param_4, param_5, initial); mLinkMic->setMicState(&mAudioCamera[camID], camID); } @@ -529,7 +529,7 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID } JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID); - bool x = params.field_0x0.bytes.b0_0 != 0; + bool x = params.field_0x0.bytes.mDopplerPower != 0; Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x); if (audible == NULL) { @@ -543,13 +543,13 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID if (channel != NULL) { u32 distVolBit = audible->getDistVolBit(); mAudioCamera[i].convertAbsToRel(audible, i); - calcDeltaPriority_(channel->field_0x14.field_0x10, distVolBit, false); + calcDeltaPriority_(channel->mRelPos.mCenterDistance, distVolBit, false); } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - audible->field_0x64[i] = mSpotMic[i].calcMicDist(audible); + audible->mMicDistances[i] = mSpotMic[i].calcMicDist(audible); } } @@ -569,7 +569,7 @@ Z2Audible::~Z2Audible() {} u32 Z2Audience::calcPriority(JAIAudible* audible) { Z2Audible* Z2audible = (Z2Audible*)audible; - if (!Z2audible->getAudibleParam()->field_0x0.bytes.b0_4) { + if (!Z2audible->getAudibleParam()->field_0x0.bytes.mCalculatePriority) { for (int i = 0; i < mNumPlayers; i++) { mAudioCamera[i].convertAbsToRel(Z2audible, i); } @@ -584,17 +584,17 @@ u32 Z2Audience::calcPriority(JAIAudible* audible) { mAudioCamera[i].convertAbsToRel(Z2audible, i); u32 distvolBit = Z2audible->getDistVolBit(); const JAUAudibleParam* puVar1 = Z2audible->getAudibleParam(); - deltaPriority[i] = calcDeltaPriority_(channel->field_0x14.field_0x10, distvolBit, puVar1->field_0x0.bytes.b0_7); + deltaPriority[i] = calcDeltaPriority_(channel->mRelPos.mCenterDistance, distvolBit, puVar1->field_0x0.bytes.mCullAtMaxDistance); if (deltaPriority[i] < rv) { rv = deltaPriority[i]; } } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - Z2audible->field_0x64[i] = mSpotMic[i].calcMicDist(Z2audible); - u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->field_0x64[i]); + Z2audible->mMicDistances[i] = mSpotMic[i].calcMicDist(Z2audible); + u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->mMicDistances[i]); if (micPriority < rv) { rv = micPriority; } @@ -619,31 +619,31 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi return; } - JASSoundParams local_60; + JASSoundParams calcParams; u32 distVolBit = Z2audible->getDistVolBit(); - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_5) { - local_60.mVolume = calcVolume_(channel->field_0x14.field_0x10, distVolBit); - f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->field_0x64[0], channelNum, local_60.mVolume); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDistanceVolume) { + calcParams.mVolume = calcVolume_(channel->mRelPos.mCenterDistance, distVolBit); + f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->mMicDistances[0], channelNum, calcParams.mVolume); if (dVar9 < 0.0f) { - dVar9 = calcOffMicSound(local_60.mVolume); + dVar9 = calcOffMicSound(calcParams.mVolume); } - local_60.mVolume = dVar9; + calcParams.mVolume = dVar9; JAUAudibleParam* params = Z2audible->getAudibleParam(); - if ((params->field_0x0.bytes.b1_2_7 & 8) && (local_60.mVolume <= 0.2f)) { - local_60.mVolume = 0.2f; + if ((params->field_0x0.bytes.mClampMinVolume & 8) && (calcParams.mVolume <= 0.2f)) { + calcParams.mVolume = 0.2f; } } else { - local_60.mVolume = 1.0f; + calcParams.mVolume = 1.0f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_1 && mNumPlayers == 1) { - local_60.mDolby = calcRelPosDolby(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDolby && mNumPlayers == 1) { + calcParams.mDolby = calcRelPosDolby(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } else { - local_60.mDolby = 0.5f; + calcParams.mDolby = 0.5f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_0) { + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcPan) { if (mNumPlayers > 2) { f32 fVar1; if (channelNum & 1) { @@ -651,24 +651,24 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi } else { fVar1 = 0.0f; } - local_60.mPan = fVar1; + calcParams.mPan = fVar1; } else { - local_60.mPan = calcRelPosPan(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + calcParams.mPan = calcRelPosPan(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } } else { - local_60.mPan = 0.5f; + calcParams.mPan = 0.5f; } - local_60.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); - f32 dVar9; - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_6) { - dVar9 = calcFxMix_(channel->field_0x14.field_0xC, distVolBit); + calcParams.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); + f32 fxMix; + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcFxMix) { + fxMix = calcFxMix_(channel->mRelPos.mTrueDistance, distVolBit); } else { - dVar9 = 0.0f; + fxMix = 0.0f; } - local_60.mFxMix = dVar9; - local_60.clamp(); - Z2audible->setOuterParams(outParams, local_60, channelNum); + calcParams.mFxMix = fxMix; + calcParams.clamp(); + Z2audible->setOuterParams(outParams, calcParams, channelNum); } @@ -688,18 +688,18 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) { aTStack_3c.y *= 1.5f; f32 len = aTStack_3c.length(); - if (len > mSetting.field_0x0[0] * param_1) { - return mSetting.field_0x40; + if (len > mSetting.mDistanceMaxes[0] * param_1) { + return mSetting.mMinDistanceVolume; } - if (len < mSetting.field_0x3c) { + if (len < mSetting.mMaxVolumeDistance) { return 1.0f; } return JGeometry::TUtil::clamp( - 1.0f + ((mSetting.field_0x40 - 1.0f) / - ((mSetting.field_0x0[0] * param_1) - mSetting.field_0x3c)) * - (len - mSetting.field_0x3c), + 1.0f + ((mSetting.mMinDistanceVolume - 1.0f) / + ((mSetting.mDistanceMaxes[0] * param_1) - mSetting.mMaxVolumeDistance)) * + (len - mSetting.mMaxVolumeDistance), 0.0f, 1.0f); } @@ -717,7 +717,7 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { return 0.5f; } - dVar6 = (0.5f + mSetting.field_0x58 * (local_54.x / dVar6)); + dVar6 = (0.5f + mSetting.mPanFactor * (local_54.x / dVar6)); if (local_54.z <= 0.0f) { f32 fovySin = mAudioCamera[camID].getFovySin(); if (dVar6 < 0.5f) { @@ -757,52 +757,52 @@ f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) { return 0.5f - 0.5f * cosf(t * static_cast(M_PI)); } #endif - if (fVar1 > mSetting.field_0x48) { + if (fVar1 > mSetting.mDolbyBehindDistanceMax) { return 1.0f; } - if (fVar1 < mSetting.field_0x44) { + if (fVar1 < mSetting.mDolbyFrontDistanceMax) { return 0.0f; } if (fVar1 < 0.0f) { return Z2Calc::getParamByExp( - fVar1, mSetting.field_0x44, 0.0f, + fVar1, mSetting.mDolbyFrontDistanceMax, 0.0f, 0.3f, 0.0f, - mSetting.field_0x4c, Z2Calc::CURVE_POSITIVE); + mSetting.mDolbyCenterValue, Z2Calc::CURVE_POSITIVE); } return Z2Calc::getParamByExp( - fVar1, 0.0f, mSetting.field_0x48, - 0.3f, mSetting.field_0x4c, + fVar1, 0.0f, mSetting.mDolbyBehindDistanceMax, + 0.3f, mSetting.mDolbyCenterValue, 1.0f, Z2Calc::CURVE_NEGATIVE); } f32 Z2Audience::calcVolume_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x40; + if (param_0 > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMinDistanceVolume; } - if (param_0 < mSetting.field_0x3c) { + if (param_0 < mSetting.mMaxVolumeDistance) { return 1.0f; } - return JGeometry::TUtil::clamp((mSetting.field_0x70[distVolBit] * (param_0 - mSetting.field_0x3c)) + 1.0f, 0.0f, 1.0f); + return JGeometry::TUtil::clamp((mSetting.mVolumeFactor[distVolBit] * (param_0 - mSetting.mMaxVolumeDistance)) + 1.0f, 0.0f, 1.0f); } -u32 Z2Audience::calcDeltaPriority_(f32 param_0, int distVolBit, bool param_2) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - if (param_2) { +u32 Z2Audience::calcDeltaPriority_(f32 distance, int distVolBit, bool cullMaxDistance) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + if (cullMaxDistance) { return -1; } - return mSetting.field_0x64; + return mSetting.mMaxDistancePriority; } - if (param_0 < mSetting.field_0x3c) { + if (distance < mSetting.mMaxVolumeDistance) { return 0; } - return mSetting.field_0xac[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mPriorityFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, @@ -810,24 +810,24 @@ f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, const JGeometry::TVec3& param_2, f32 param_3) const { f32 dVar8 = param_0.dot(param_1); f32 dVar7 = param_0.dot(param_2); - return (mSetting.field_0x5c + param_3 * dVar8) / (mSetting.field_0x5c + param_3 * dVar7); + return (mSetting.mSonicSpeed + param_3 * dVar8) / (mSetting.mSonicSpeed + param_3 * dVar7); } -f32 Z2Audience::calcFxMix_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x54; +f32 Z2Audience::calcFxMix_(f32 distance, int distVolBit) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMaxDistanceFxMix; } - if (param_0 < mSetting.field_0x3c) { - return mSetting.field_0x50; + if (distance < mSetting.mMaxVolumeDistance) { + return mSetting.mMinDistanceFxMix; } - return mSetting.field_0x50 + mSetting.field_0xe8[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mMinDistanceFxMix + mSetting.mFxMixFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitch_(Z2AudibleChannel* channel, const Z2Audible* audible, const Z2AudioCamera* camera) const { JAUAudibleParam audParam = *audible->getAudibleParam(); - if (audParam.field_0x0.bytes.b0_0) { + if (audParam.field_0x0.bytes.mDopplerPower) { JGeometry::TVec3 aTStack_4c; - aTStack_4c.normalize(channel->field_0x14.field_0x00); + aTStack_4c.normalize(channel->mRelPos.mCameraRelative); JAUAudibleParam audParam = *audible->getAudibleParam(); f32 doppler = audParam.getDopplerPower(); diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index 1b2152fcb7..f51fc10d92 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) { JSUList* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList(); JSULink* stream; for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) { - if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != 0x71) { + if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != SOUND_TYPEID_STREAM) { stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME); } } diff --git a/src/Z2AudioLib/Z2SoundInfo.cpp b/src/Z2AudioLib/Z2SoundInfo.cpp index cb67b4c805..9befa79b26 100644 --- a/src/Z2AudioLib/Z2SoundInfo.cpp +++ b/src/Z2AudioLib/Z2SoundInfo.cpp @@ -23,7 +23,7 @@ u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const { if (data != NULL) { switch ((typeID & 0xf0)) { - case 0x60: + case SOUND_TYPEID_SEQUENCE: return (u16)data->mResourceId; } } @@ -53,7 +53,7 @@ u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { return data->mPriority; } @@ -67,64 +67,64 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_FX_MIX) != 0) { + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: - audibleParam.field_0x0.bytes.b0_0 = 0; - audibleParam.field_0x0.bytes.b0_4 = 1; - audibleParam.field_0x0.bytes.b0_5 = 1; - audibleParam.field_0x0.bytes.b0_6 = 1; - audibleParam.field_0x0.bytes.b0_7 = 0; - audibleParam.field_0x0.bytes.b1_0 = 1; - audibleParam.field_0x0.bytes.b1_1 = 1; - audibleParam.field_0x0.bytes.b1_2_7 = 0; + audibleParam.field_0x0.bytes.mDopplerPower = 0; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; + audibleParam.field_0x0.bytes.mCalcPan = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; + audibleParam.field_0x0.bytes.mClampMinVolume = 0; audibleParam.field_0x0.half.f1 = 0; break; } @@ -139,53 +139,53 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: @@ -208,19 +208,19 @@ void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { } switch(typeID) { - case 81: - sePtr->getProperty().field_0x8 *= data->field_0x8; - u32 uStack_6c = (getSwBit(soundID) & 0xf0) >> 4; - if (uStack_6c > 8) { - sePtr->getProperty().field_0x8 += Z2Calc::linearTransform(uStack_6c, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); + case SOUND_TYPEID_SOUND_EFFECT: + sePtr->getProperty().mPitch *= data->mPitch; + u32 pitchParam = (getSwBit(soundID) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; + if (pitchParam > 8) { + sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); } else { - sePtr->getProperty().field_0x8 += (uStack_6c / 48.0f) * Z2Calc::getRandom_0_1(); + sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1(); } - u32 uVar1 = (u32)getSwBit(soundID) >> 0x1c; + u32 uVar1 = (u32)getSwBit(soundID) >> SOUND_SW_RANDOM_VOLUME_OFFSET; if (uVar1 != 0) { f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1(); - sePtr->getProperty().field_0x0 -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); + sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); } break; } @@ -240,13 +240,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID & 0xf0) { - case 0x70: + case SOUND_TYPEID_STREAM: u16 uVar1; s32 iVar4; data = JASGlobalInstance::getInstance()->getData(soundID); JUT_ASSERT(356, data); - uVar1 = data->mResourceId; + uVar1 = data->mStreamPanParameters; numChild = streamPtr->getNumChild(); iVar4 = 0; for (; iVar4 < numChild && uVar1 != 0; uVar1 >>= JAUStdSoundTableType::STRM_CH_SHIFT, iVar4++) { @@ -277,12 +277,12 @@ const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { const void* resource; switch (JASGlobalInstance::getInstance()->getTypeID(soundID) & 0xf0) { - case 0x70: + case SOUND_TYPEID_STREAM: data = JASGlobalInstance::getInstance()->getData(soundID); JUT_ASSERT(394, data); resource = JASGlobalInstance::getInstance()->getResource(); JUT_ASSERT(398, resource); - return JAUStdSoundTableType::StringOffset::getString(resource, data->field_0x4); + return JAUStdSoundTableType::StringOffset::getString(resource, data->mStreamFilePath); default: return NULL; } @@ -300,8 +300,8 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); if (data != NULL) { switch(typeID) { - case 81: - return data->field_0x4; + case SOUND_TYPEID_SOUND_EFFECT: + return data->mSwBit; } } @@ -313,7 +313,7 @@ void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const { JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { - soundPtr->getProperty().field_0x0 = (1.0f / 127.0f) * data->field_0x1; + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { + soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume; } } diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index 23e8171736..f38fa14e6a 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -222,7 +222,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startCollisionSE(u32 hitID, u32 mapinfo, Z2So if (30 <= mapinfo && mapinfo <= 52) { Z2Audible* audible = (Z2Audible*)(*handle)->getAudible(); if (audible != NULL) { - audible->getAudibleParam()->field_0x0.bytes.b1_2_7 = 8; + audible->getAudibleParam()->field_0x0.bytes.mClampMinVolume = 8; } } } From 4bcecb3926bda9b752cfc69d1d2b38c0138ca7c0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 06:46:41 +0200 Subject: [PATCH 016/100] Linux/Apple compile warnings consistency This re-organizes the CMakeLists.txt a lil so that some of the warning compile flags on Linux also get applied to Apple platforms. --- CMakeLists.txt | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 696ec1af80..1933ca70f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,24 +194,21 @@ if (DUSK_MOVIE_SUPPORT) endif () endif () -if (CMAKE_SYSTEM_NAME STREQUAL Linux) +if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers # (CW, GCC, Clang, MSVC) encode them identically in big-endian order. # For >4-char literals (which GCC/Clang truncate to int), use the MULTI_CHAR() macro. # -Wdeprecated-declarations: JSystem uses std::iterator, deprecated in C++17 - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-multichar") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations") - set(CMAKE_INSTALL_RPATH "$ORIGIN") - set(CMAKE_BUILD_RPATH "$ORIGIN") -elseif (APPLE) - add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs) - set(CMAKE_INSTALL_RPATH "@loader_path") - set(CMAKE_BUILD_RPATH "@loader_path") -elseif (MSVC) add_compile_options( - $<$:/bigobj> - $<$:/MP> - $<$:/FS> + $<$:-Wno-multichar> + $<$:-Wno-trigraphs> + $<$:-Wno-deprecated-declarations> + ) +elseif (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + add_compile_options( + $<$:/bigobj> + $<$:/MP> + $<$:/FS> ) if (NOT DUSK_BUILD_WARNINGS) @@ -226,6 +223,15 @@ elseif (MSVC) add_compile_options($<$:/utf-8>) endif () +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + set(CMAKE_INSTALL_RPATH "$ORIGIN") + set(CMAKE_BUILD_RPATH "$ORIGIN") +elseif (APPLE) + add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs) + set(CMAKE_INSTALL_RPATH "@loader_path") + set(CMAKE_BUILD_RPATH "@loader_path") +endif () + include(FetchContent) # Declare all dependencies first so CMake can download them in parallel From 6d7269b654ab2d7c46900d566777d748433c1994 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 15:03:46 +0200 Subject: [PATCH 017/100] Fix missing sizeof in JASBasicBank.cpp Likely doesn't matter but good to clean up ig --- libs/JSystem/src/JAudio2/JASBasicBank.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASBasicBank.cpp b/libs/JSystem/src/JAudio2/JASBasicBank.cpp index 9b3c032bbb..868e7189dd 100644 --- a/libs/JSystem/src/JAudio2/JASBasicBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicBank.cpp @@ -14,9 +14,9 @@ void JASBasicBank::newInstTable(u8 num, JKRHeap* heap) { mInstNumMax = num; mInstTable = JKR_NEW_ARRAY_ARGS(JASInst*, mInstNumMax, heap, 0); #if TARGET_ANDROID - JASCalc::_bzero(mInstTable, mInstNumMax * 4); + JASCalc::_bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #else - JASCalc::bzero(mInstTable, mInstNumMax * 4); + JASCalc::bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #endif } } From c43ed69b6d8b3ef7c7da24e9b893057d22232e68 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 15:19:21 +0200 Subject: [PATCH 018/100] More JAudio naming, focused on instruments this time --- .../include/JSystem/JAudio2/JASBasicInst.h | 32 ++++----- .../include/JSystem/JAudio2/JASChannel.h | 2 +- .../include/JSystem/JAudio2/JASDrumSet.h | 8 +-- libs/JSystem/src/JAudio2/JASAramStream.cpp | 4 +- libs/JSystem/src/JAudio2/JASBNKParser.cpp | 28 ++++---- libs/JSystem/src/JAudio2/JASBank.cpp | 72 +++++++++---------- libs/JSystem/src/JAudio2/JASBasicBank.cpp | 4 +- libs/JSystem/src/JAudio2/JASBasicInst.cpp | 30 ++++---- libs/JSystem/src/JAudio2/JASChannel.cpp | 10 +-- libs/JSystem/src/JAudio2/JASDrumSet.cpp | 30 ++++---- libs/JSystem/src/JAudio2/JASTrack.cpp | 12 ++-- libs/JSystem/src/JAudio2/JASVoiceBank.cpp | 6 +- 12 files changed, 119 insertions(+), 119 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h index 5fe6b84db4..fdd141a5c5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h @@ -14,20 +14,20 @@ const int OSC_MAX = 2; */ struct JASInstParam : public JASSoundParams { JASInstParam() { - field_0x14 = NULL; - field_0x18 = 0; - field_0x1a = 0; - field_0x1c = 0; - field_0x1d = 0; - field_0x1e = 0; + mOscillators = NULL; + mDirectRelease = 0; + mWaveId = 0; + mChannelType = 0; + mOscillatorCount = 0; + mDontSetKey = 0; } - JASOscillator::Data** field_0x14; - u16 field_0x18; - u16 field_0x1a; - u8 field_0x1c; - u8 field_0x1d; - u8 field_0x1e; + JASOscillator::Data** mOscillators; + u16 mDirectRelease; + u16 mWaveId; + u8 mChannelType; // Always CHANNEL_WAVE in practice + u8 mOscillatorCount; + u8 mDontSetKey; }; /** @@ -52,10 +52,10 @@ struct JASBasicInst : public JASInst { void setHighKey(int key) { mHighKey = key; } /* 0x0 */ s32 mHighKey; - /* 0x4 */ u16 field_0x4; + /* 0x4 */ u16 mWaveId; /* 0x6 */ u16 field_0x6; - /* 0x8 */ f32 field_0x8; - /* 0xC */ f32 field_0xc; + /* 0x8 */ f32 mVolumeMult; + /* 0xC */ f32 mPitchMult; }; JASBasicInst(); @@ -74,7 +74,7 @@ struct JASBasicInst : public JASInst { /* 0x04 */ f32 mVolume; /* 0x08 */ f32 mPitch; - /* 0x0C */ JASOscillator::Data const* field_0xc[OSC_MAX]; + /* 0x0C */ JASOscillator::Data const* mOscillators[OSC_MAX]; /* 0x10 */ u32 mKeymapCount; /* 0x14 */ TKeymap* mKeymap; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 2310cc590f..f8579c4be3 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -174,7 +174,7 @@ public: struct { u32 mChannelType; // CHANNEL_WAVE or CHANNEL_OSCILLATOR JASWaveInfo mWaveInfo; - } field_0xdc; + } mAnon; union { u32 mWaveAramAddress; u32 mOscillatorSomething; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h index 8e5c8df244..f791b21865 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h @@ -21,10 +21,10 @@ struct JASDrumSet : public JASInst { /* 0x00 */ f32 mVolume; /* 0x04 */ f32 mPitch; /* 0x08 */ f32 mPan; - /* 0x0C */ u16 field_0xc; - /* 0x0E */ u16 field_0xe; - /* 0x10 */ f32 field_0x10; - /* 0x14 */ f32 field_0x14; + /* 0x0C */ u16 mDirectRelease; + /* 0x0E */ u16 mWaveId; + /* 0x10 */ f32 mVolumeMult; + /* 0x14 */ f32 mPitchMult; }; JASDrumSet(); diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 2ff00b6a14..74026d94ff 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -722,9 +722,9 @@ void JASAramStream::channelStart() { } jc->setInitPitch(mSampleRate / JASDriver::getDacRate()); jc->setOscInit(0, &OSC_ENV); - jc->field_0xdc.mWaveInfo = wave_info; + jc->mAnon.mWaveInfo = wave_info; jc->mWaveAramAddress = mAramAddress + sBlockSize * mAramBlocksPerChannel * i; - jc->field_0xdc.mChannelType = 0; + jc->mAnon.mChannelType = 0; int ret = jc->playForce(); JUT_ASSERT(977, ret); JUT_ASSERT_MSG(979, mChannels[i] == NULL, "channelStart for already playing channel"); diff --git a/libs/JSystem/src/JAudio2/JASBNKParser.cpp b/libs/JSystem/src/JAudio2/JASBNKParser.cpp index ad8aa1ee46..6fbd3ad5cf 100644 --- a/libs/JSystem/src/JAudio2/JASBNKParser.cpp +++ b/libs/JSystem/src/JAudio2/JASBNKParser.cpp @@ -115,9 +115,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h JASBasicInst::TKeymap* keymap = instp->getKeyRegion(j); keymap->setHighKey(*data >> 0x18); u32 fVar4 = data[1]; - keymap->field_0x4 = JSULoHalf(data[3]); - keymap->field_0x8 = *(BE(f32)*)&data[4]; - keymap->field_0xc = *(BE(f32)*)&data[5]; + keymap->mWaveId = JSULoHalf(data[3]); + keymap->mVolumeMult = *(BE(f32)*)&data[4]; + keymap->mPitchMult = *(BE(f32)*)&data[5]; data += 2; for (int k = 0; k < fVar4; k++) { data += 4; @@ -159,9 +159,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h ptr++; } u32 pVar6 = ptr[0]; - percp->field_0xe = JSULoHalf(ptr[2]); - percp->field_0x10 = *(BE(f32)*)&ptr[3]; - percp->field_0x14 = *(BE(f32)*)&ptr[4]; + percp->mWaveId = JSULoHalf(ptr[2]); + percp->mVolumeMult = *(BE(f32)*)&ptr[3]; + percp->mPitchMult = *(BE(f32)*)&ptr[4]; for (int k = 0; k < pVar6; k++) {} drump->setPerc(j, percp); } @@ -260,9 +260,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h TKeymap* tkeymap = tinst->mKeymapOffset[j].ptr(header); keymap->setHighKey(tkeymap->mHighKey); TVmap* tvmap = tkeymap->mVmapOffset.ptr(header); - keymap->field_0x4 = JSULoHalf(tvmap->field_0x4); - keymap->field_0x8 = tvmap->field_0x8; - keymap->field_0xc = tvmap->field_0xc; + keymap->mWaveId = JSULoHalf(tvmap->field_0x4); + keymap->mVolumeMult = tvmap->field_0x8; + keymap->mPitchMult = tvmap->field_0xc; } bank->setInst(i, instp); @@ -288,9 +288,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h percp->setRelease(tperc->mRelease[j]); } TVmap* vmap = tpmap->mVmapOffset.ptr(header); - percp->field_0xe = JSULoHalf(vmap->field_0x4); - percp->field_0x10 = vmap->field_0x8; - percp->field_0x14 = vmap->field_0xc; + percp->mWaveId = JSULoHalf(vmap->field_0x4); + percp->mVolumeMult = vmap->field_0x8; + percp->mPitchMult = vmap->field_0xc; setp->setPerc(j, percp); } } @@ -314,8 +314,8 @@ JASOscillator::Data* JASBNKParser::Ver0::findOscPtr(JASBasicBank* bank, THeader if (inst != NULL) { JASInstParam param; inst->getParam(0x3c, 0x7f, ¶m); - if (j < param.field_0x1d) { - return param.field_0x14[j]; + if (j < param.mOscillatorCount) { + return param.mOscillators[j]; } } } diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index b85258bb8b..b36b076dc8 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -11,31 +11,31 @@ using namespace dusk::mods::svc::audio_res; // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, - void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_6) { - if (param_1 >= 0xf0) { - return noteOnOsc(param_1 - 0xf0, param_2, param_3, param_4, param_5, param_6); +JASChannel* JASBank::noteOn(JASBank const* bank, int program, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { + if (program >= 0xf0) { + return noteOnOsc(program - 0xf0, key, velocity, priority, callback, callback_data); } - if (!param_0) { + if (!bank) { return NULL; } - JASInstParam stack_60; - if (!param_0->getInstParam(param_1, param_2, param_3, &stack_60)) { + JASInstParam instParam; + if (!bank->getInstParam(program, key, velocity, &instParam)) { return NULL; } - JASWaveBank* waveBank = param_0->getWaveBank(); + JASWaveBank* waveBank = bank->getWaveBank(); if (!waveBank) { return NULL; } - JASWaveHandle* waveHandle = waveBank->getWaveHandle(stack_60.field_0x1a); + JASWaveHandle* waveHandle = waveBank->getWaveHandle(instParam.mWaveId); if (!waveHandle) { return NULL; } #if TARGET_PC - auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + auto const wave_key = AudioWaveKey(static_cast(waveBank->bankId), instParam.mWaveId); std::lock_guard lock(s_replacements_mutex); - auto const found_replacement = s_replacements.find(key); + auto const found_replacement = s_replacements.find(wave_key); if (found_replacement != s_replacements.end()) { waveHandle = &found_replacement->second; } @@ -52,32 +52,32 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } - JASChannel* channel = JKR_NEW JASChannel(param_5, param_6); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_4); - channel->field_0xdc.mWaveInfo = *waveInfo; + channel->setPriority(priority); + channel->mAnon.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; #if TARGET_PC channel->mAramBaseAddress = aramBase; channel->mSampleReference = waveHandle->getSampleReference(); #endif - channel->field_0xdc.mChannelType = stack_60.field_0x1c; - channel->setBankDisposeID(param_0); - channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); - if (stack_60.field_0x1e == 0) { - channel->setKey(param_2 - waveInfo->mBaseKey); + channel->mAnon.mChannelType = instParam.mChannelType; + channel->setBankDisposeID(bank); + channel->setInitPitch(instParam.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); + if (instParam.mDontSetKey == 0) { + channel->setKey(key - waveInfo->mBaseKey); } - channel->setInitVolume(stack_60.mVolume); - channel->setVelocity(param_3); - channel->setInitPan(stack_60.mPan); - channel->setInitFxmix(stack_60.mFxMix); - channel->setInitDolby(stack_60.mDolby); - for (u32 i = 0; i < stack_60.field_0x1d; i++) { - channel->setOscInit(i, stack_60.field_0x14[i]); + channel->setInitVolume(instParam.mVolume); + channel->setVelocity(velocity); + channel->setInitPan(instParam.mPan); + channel->setInitFxmix(instParam.mFxMix); + channel->setInitDolby(instParam.mDolby); + for (u32 i = 0; i < instParam.mOscillatorCount; i++) { + channel->setOscInit(i, instParam.mOscillators[i]); } - channel->setDirectRelease(stack_60.field_0x18); + channel->setDirectRelease(instParam.mDirectRelease); if (!channel->play()) { return NULL; } @@ -85,24 +85,24 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 } // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOnOsc(int param_0, u8 param_1, u8 param_2, u16 param_3, - void (*param_4)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_5) { +JASChannel* JASBank::noteOnOsc(int param_0, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { static JASOscillator::Point const OSC_RELEASE_TABLE[2] = { {0x0001, 0x000A, 0x0000}, {0x000F, 0x0000, 0x0000}, }; static const JASOscillator::Data OSC_ENV = {0, 1.0f, NULL, OSC_RELEASE_TABLE, 1.0f, 0.0f}; - JASChannel* channel = JKR_NEW JASChannel(param_4, param_5); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_3); + channel->setPriority(priority); channel->mOscillatorSomething = param_0; - channel->field_0xdc.mChannelType = 2; + channel->mAnon.mChannelType = CHANNEL_OSCILLATOR; channel->setInitPitch(16736.016f / JASDriver::getDacRate()); - channel->setKey(param_1 - channel->field_0xdc.mWaveInfo.mBaseKey); - channel->setVelocity(param_2); + channel->setKey(key - channel->mAnon.mWaveInfo.mBaseKey); + channel->setVelocity(velocity); channel->setOscInit(0, &OSC_ENV); if (!channel->play()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JASBasicBank.cpp b/libs/JSystem/src/JAudio2/JASBasicBank.cpp index 868e7189dd..16fef1d746 100644 --- a/libs/JSystem/src/JAudio2/JASBasicBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicBank.cpp @@ -21,13 +21,13 @@ void JASBasicBank::newInstTable(u8 num, JKRHeap* heap) { } } -bool JASBasicBank::getInstParam(int prg_no, int param_1, int param_2, +bool JASBasicBank::getInstParam(int prg_no, int key, int velocity, JASInstParam* o_param) const { JASInst* inst = getInst(prg_no); if (inst == NULL) { return false; } - return inst->getParam(param_1, param_2, o_param); + return inst->getParam(key, velocity, o_param); } void JASBasicBank::setInst(int prg_no, JASInst* inst) { diff --git a/libs/JSystem/src/JAudio2/JASBasicInst.cpp b/libs/JSystem/src/JAudio2/JASBasicInst.cpp index fba53d010f..50e764eeac 100644 --- a/libs/JSystem/src/JAudio2/JASBasicInst.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicInst.cpp @@ -14,9 +14,9 @@ JASBasicInst::JASBasicInst() { mKeymapCount = 0; mKeymap = NULL; #if TARGET_ANDROID - JASCalc::_bzero(field_0xc, sizeof(field_0xc)); + JASCalc::_bzero(mOscillators, sizeof(mOscillators)); #else - JASCalc::bzero(field_0xc, sizeof(field_0xc)); + JASCalc::bzero(mOscillators, sizeof(mOscillators)); #endif } @@ -24,18 +24,18 @@ JASBasicInst::~JASBasicInst() { JKR_DELETE_ARRAY(mKeymap); } -bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); - param_2->field_0x1c = 0; - param_2->field_0x1e = 0; - param_2->field_0x14 = (JASOscillator::Data**)&field_0xc; - param_2->field_0x1d = 2; - param_2->mVolume = mVolume; - param_2->mPitch = mPitch; +bool JASBasicInst::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); + o_param->mChannelType = 0; + o_param->mDontSetKey = 0; + o_param->mOscillators = (JASOscillator::Data**)&mOscillators; + o_param->mOscillatorCount = 2; + o_param->mVolume = mVolume; + o_param->mPitch = mPitch; TKeymap* keyMap = NULL; for (int i = 0; i < mKeymapCount; i++) { - if (param_0 <= mKeymap[i].mHighKey) { + if (key <= mKeymap[i].mHighKey) { keyMap = &mKeymap[i]; break; } @@ -45,9 +45,9 @@ bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) con return false; } - param_2->mVolume *= keyMap->field_0x8; - param_2->mPitch *= keyMap->field_0xc; - param_2->field_0x1a = u16(keyMap->field_0x4); + o_param->mVolume *= keyMap->mVolumeMult; + o_param->mPitch *= keyMap->mPitchMult; + o_param->mWaveId = u16(keyMap->mWaveId); return true; } @@ -61,7 +61,7 @@ void JASBasicInst::setKeyRegionCount(u32 count, JKRHeap* param_1) { void JASBasicInst::setOsc(int index, JASOscillator::Data const* param_1) { JUT_ASSERT(128, index < OSC_MAX); JUT_ASSERT(129, index >= 0); - field_0xc[index] = param_1; + mOscillators[index] = param_1; } JASBasicInst::TKeymap* JASBasicInst::getKeyRegion(int index) { diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index d227ad3fc1..3799832119 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -37,7 +37,7 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mKeySweepCount(0), mSkipSamples(0) { - field_0xdc.mChannelType = 0; + mAnon.mChannelType = 0; field_0x104 = 0; mMixConfig[0].whole = 0x150; mMixConfig[1].whole = 0x210; @@ -240,7 +240,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -256,9 +256,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { return -1; } - switch (field_0xdc.mChannelType) { + switch (mAnon.mChannelType) { case 0: - i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); + i_channel->setWaveInfo(mAnon.mWaveInfo, mWaveAramAddress, mSkipSamples); #if TARGET_PC i_channel->mAramBaseAddress = mAramBaseAddress; #endif @@ -321,7 +321,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; diff --git a/libs/JSystem/src/JAudio2/JASDrumSet.cpp b/libs/JSystem/src/JAudio2/JASDrumSet.cpp index 399cc117ec..4b6a95b289 100644 --- a/libs/JSystem/src/JAudio2/JASDrumSet.cpp +++ b/libs/JSystem/src/JAudio2/JASDrumSet.cpp @@ -26,8 +26,8 @@ void JASDrumSet::newPercArray(u8 num, JKRHeap* heap) { } } -bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); +bool JASDrumSet::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); JUT_ASSERT(48, key >= 0); if (mPercArray == NULL) { return false; @@ -43,12 +43,12 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { return false; } - param_2->field_0x1c = 0; - param_2->field_0x1e = 1; - param_2->mVolume = perc->mVolume; - param_2->mPitch = perc->mPitch; - param_2->mPan = perc->mPan; - param_2->field_0x18 = u16(perc->field_0xc); + o_param->mChannelType = 0; + o_param->mDontSetKey = 1; + o_param->mVolume = perc->mVolume; + o_param->mPitch = perc->mPitch; + o_param->mPan = perc->mPan; + o_param->mDirectRelease = u16(perc->mDirectRelease); static JASOscillator::Data osc; @@ -61,11 +61,11 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { static JASOscillator::Data* oscp = &osc; - param_2->field_0x14 = &oscp; - param_2->field_0x1d = 1; - param_2->mVolume *= perc->field_0x10; - param_2->mPitch *= perc->field_0x14; - param_2->field_0x1a = u16(perc->field_0xe); + o_param->mOscillators = &oscp; + o_param->mOscillatorCount = 1; + o_param->mVolume *= perc->mVolumeMult; + o_param->mPitch *= perc->mPitchMult; + o_param->mWaveId = u16(perc->mWaveId); return true; } @@ -107,12 +107,12 @@ JASDrumSet::TPerc::TPerc() { mVolume = 1.0f; mPitch = 1.0f; mPan = 0.5f; - field_0xc = 1000; + mDirectRelease = 1000; } void JASDrumSet::TPerc::setRelease(u32 release) { JUT_ASSERT(224, release < 0x10000); - field_0xc = release; + mDirectRelease = release; } u32 JASDrumSet::getType() const { return 'PERC'; } diff --git a/libs/JSystem/src/JAudio2/JASTrack.cpp b/libs/JSystem/src/JAudio2/JASTrack.cpp index 4b1a7ba7aa..32e66e140c 100644 --- a/libs/JSystem/src/JAudio2/JASTrack.cpp +++ b/libs/JSystem/src/JAudio2/JASTrack.cpp @@ -288,13 +288,13 @@ void JASTrack::setLatestKey(u8 param_0) { field_0x22b += getTransposeTotal(); } -JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 param_1, u32 param_2, +JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 key, u32 velocity, u32 i_updateTimer) { JASBank* bank = NULL; if (mBankTable != NULL) { bank = mBankTable->getBank(mBankNumber); } - JASChannel* channel = JASBank::noteOn(bank, mProgNumber, param_1, param_2, + JASChannel* channel = JASBank::noteOn(bank, mProgNumber, key, velocity, mNoteOnPrio | mReleasePrio << 8, channelUpdateCallback, i_channelMgr); if (channel == NULL) { @@ -315,17 +315,17 @@ JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 para return channel; } -int JASTrack::noteOn(u32 noteid, u32 param_1, u32 param_2) { +int JASTrack::noteOn(u32 noteid, u32 key, u32 velocity) { JUT_ASSERT(486, noteid != 0 && noteid < TChannelMgr::CHANNEL_MAX); if (isMute()) { return 0; } int ret = 1; - param_1 += getTransposeTotal(); + key += getTransposeTotal(); for (u32 i = 0; i < mChannelMgrCount; i++) { if (mChannelMgrs[i] != NULL) { mChannelMgrs[i]->noteOff(noteid, 0); - JASChannel* channel = channelStart(mChannelMgrs[i], param_1, param_2, 0); + JASChannel* channel = channelStart(mChannelMgrs[i], key, velocity, 0); if (channel == NULL) { ret = 0; } @@ -356,7 +356,7 @@ int JASTrack::gateOn(u32 param_0, u32 i_velocity, f32 i_time, u32 i_flags) { } else { JASChannel* channel = channel_mgr->mChannels[0]; if (channel != NULL) { - channel->setKey(uvar7 - channel->field_0xdc.mWaveInfo.mBaseKey); + channel->setKey(uvar7 - channel->mAnon.mWaveInfo.mBaseKey); channel->setVelocity(i_velocity); channel->setUpdateTimer(update_timer); } diff --git a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp index 0d3cd3adb2..3ba1dff173 100644 --- a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp +++ b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp @@ -15,9 +15,9 @@ bool JASVoiceBank::getInstParam(int param_0, int param_1, int param_2, return false; } sOscTable = (JASOscillator::Data*)&sOscData; - param_3->field_0x1a = param_0; - param_3->field_0x1d = 1; - param_3->field_0x14 = &sOscTable; + param_3->mWaveId = param_0; + param_3->mOscillatorCount = 1; + param_3->mOscillators = &sOscTable; return true; } From cb09860e1f1743f1456b5b708ad22cb9084eba91 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 16:56:03 +0200 Subject: [PATCH 019/100] BSC ImHex pattern --- docs/imhex/jaudio/bsc.hexpat | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/imhex/jaudio/bsc.hexpat diff --git a/docs/imhex/jaudio/bsc.hexpat b/docs/imhex/jaudio/bsc.hexpat new file mode 100644 index 0000000000..a9f8e3ca6e --- /dev/null +++ b/docs/imhex/jaudio/bsc.hexpat @@ -0,0 +1,26 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct JAUSeqCollectionTable { + u32 mEntryCount; + u32 mBmsOffset[mEntryCount]; +}; + +struct JAUSeqCollectionData { + type::Magic<"SC"> mMagic; + u16 mNumSoundCategories; + u32 mSectionSize; + Offset mTableOffsets[mNumSoundCategories]; +}; + +JAUSeqCollectionData data @ 0x0; From d4ca7b696bee74d3771c5a4191d9dca1a91d829b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:05:19 +0200 Subject: [PATCH 020/100] Fix include shenanigans --- .../include/JSystem/JAudio2/JASChannel.h | 12 +----------- .../JSystem/JAudio2/JASSampleDataReference.h | 18 ++++++++++++++++++ .../include/JSystem/JAudio2/JASWaveInfo.h | 2 +- src/dusk/mods/svc/audio_res/audio_res.hpp | 1 + 4 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index f8579c4be3..77bfb45966 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -17,17 +17,7 @@ namespace JASDsp { } #if TARGET_PC -/** - * An object to manage the lifetime of the sample data pointed to by JASChannel. - * - * JASChannel can (optionally) accept an unique_ptr to an object to this type, - * in which case it will be destroyed when the JASChannel is done. - * - * @see JASChannel::mSampleReference - */ -struct JASSampleDataReference { - virtual ~JASSampleDataReference() = default; -}; +#include "JSystem/JAudio2/JASSampleDataReference.h" #endif /** diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h new file mode 100644 index 0000000000..d196b4f34c --- /dev/null +++ b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h @@ -0,0 +1,18 @@ +#ifndef DUSKLIGHT_JASSAMPLEDATAREFERENCE_H +#define DUSKLIGHT_JASSAMPLEDATAREFERENCE_H + +#if TARGET_PC +/** + * An object to manage the lifetime of the sample data pointed to by JASChannel. + * + * JASChannel can (optionally) accept an unique_ptr to an object to this type, + * in which case it will be destroyed when the JASChannel is done. + * + * @see JASChannel::mSampleReference + */ +struct JASSampleDataReference { + virtual ~JASSampleDataReference() = default; +}; +#endif + +#endif // DUSKLIGHT_JASSAMPLEDATAREFERENCE_H diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 45874f5aa7..a092f7450e 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -5,7 +5,7 @@ #include #if TARGET_PC -struct JASSampleDataReference; +#include "JSystem/JAudio2/JASSampleDataReference.h" #endif struct JASWaveArc; diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index 4ae3e0dd1c..ce46db0721 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASWaveInfo.h" #include "absl/container/flat_hash_map.h" From 756f8d5fb9fff0baaa970563f437d1e562f687f4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:21:13 +0200 Subject: [PATCH 021/100] Move wsys replacements into their own .cpp file This is gonna be a big PR --- files.cmake | 2 + libs/JSystem/src/JAudio2/JASBank.cpp | 4 +- src/dusk/mods/svc/audio_res/audio_res.cpp | 258 ++------------------- src/dusk/mods/svc/audio_res/audio_res.hpp | 97 -------- src/dusk/mods/svc/audio_res/opus.cpp | 6 +- src/dusk/mods/svc/audio_res/wave.cpp | 6 +- src/dusk/mods/svc/audio_res/wsys.cpp | 260 ++++++++++++++++++++++ src/dusk/mods/svc/audio_res/wsys.hpp | 120 ++++++++++ 8 files changed, 404 insertions(+), 349 deletions(-) create mode 100644 src/dusk/mods/svc/audio_res/wsys.cpp create mode 100644 src/dusk/mods/svc/audio_res/wsys.hpp diff --git a/files.cmake b/files.cmake index af29738867..90a0dcd4f8 100644 --- a/files.cmake +++ b/files.cmake @@ -1487,6 +1487,8 @@ set(DUSK_FILES src/dusk/mods/manifest.hpp src/dusk/mods/svc/audio_res/audio_res.hpp src/dusk/mods/svc/audio_res/audio_res.cpp + src/dusk/mods/svc/audio_res/wsys.cpp + src/dusk/mods/svc/audio_res/wsys.hpp src/dusk/mods/svc/audio_res/wave.cpp src/dusk/mods/svc/audio_res/opus.cpp src/dusk/mods/svc/camera.cpp diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index b36b076dc8..0acc5b9ed4 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -2,13 +2,13 @@ #include "JSystem/JAudio2/JASBank.h" -#include "dusk/mods/svc/audio_res/audio_res.hpp" +#include "dusk/mods/svc/audio_res/wsys.hpp" #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASBasicInst.h" #include "JSystem/JAudio2/JASBasicWaveBank.h" #include "JSystem/JAudio2/JASChannel.h" -using namespace dusk::mods::svc::audio_res; +using namespace dusk::mods::svc::audio_res::wsys; // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations JASChannel* JASBank::noteOn(JASBank const* bank, int program, u8 key, u8 velocity, u16 priority, diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index dc7b2337fd..28a18453ca 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -4,10 +4,7 @@ #include "mods/svc/audio_res.h" #include "../registry.hpp" -#include "../slot_map.hpp" -#include "aurora/lib/logging.hpp" -#include "dusk/audio/DuskAudioSystem.h" -#include "dusk/mods/loader/loader.hpp" +#include "wsys.hpp" namespace dusk::mods::svc { @@ -16,246 +13,24 @@ namespace { using namespace dusk::helpers::cast; -bool audio_replacements_dirty = false; - -aurora::Module Log("dusk::mods::svc::audio_res"); - -SlotMap s_waveReplacements; - -constexpr ContainerLoadFunction s_containerLoadFunctions[] = { - load_wav, -#if DUSK_OPUS - load_opus, -#endif -}; - -bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { - u32 samples_per_block; - u32 bytes_per_block; - switch (raw.format) { - case Adpcm4: - samples_per_block = 16; - bytes_per_block = 9; - break; - case Pcm16: - samples_per_block = 1; - bytes_per_block = 2; - break; - default: - Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path); - return false; - } - - if (actual_size % bytes_per_block != 0) { - Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); - return false; - } - - sample_count = (actual_size / bytes_per_block) * samples_per_block; - return true; -} - -ModResult load_raw( - LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) { - auto file_contents = mod.bundle->readFile(slot.bundle_path); - u32 sample_count = 0; - - if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) { - Log.error( - "[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified", - mod.metadata.id, slot.bundle_path); - - return MOD_INVALID_ARGUMENT; - } - - if (slot.format == Pcm16) { - SampleDataPcm16 pcm16; - pcm16.data.resize(sample_count); - memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); - slot.data = std::make_shared(std::move(pcm16)); - } else { - slot.data = std::make_shared(std::move(file_contents)); - } - - slot.sample_count = sample_count; - slot.sample_rate = raw.sample_rate; - slot.format = raw.format; - slot.sample_value_penult = raw.sample_value_penult; - slot.sample_value_last = raw.sample_value_last; - return MOD_OK; -} - -/** - * Load an audio file stored in some sort of container format. - * File type is determined by looking at contents, not extension. - */ -ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) { - auto file_contents = mod.bundle->readFile(slot.bundle_path); - - for (auto const loader : s_containerLoadFunctions) { - auto const result = loader(mod, slot, file_contents); - if (result == MOD_OK) { - return MOD_OK; - } - - if (result != MOD_UNSUPPORTED) { - return result; - } - } - - return MOD_UNSUPPORTED; -} - -JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { - JASWaveInfo info; - info.mWaveFormat = static_cast(slot.format); - info.mBaseKey = slot.base_key; - info.mLoopFlag = slot.loop ? 0xFF : 0; - info.mSampleRate = slot.sample_rate; - info.mOffsetStart = 0; // Unused but just init it ig. - info.mOffsetLength = bounded_cast(slot.data->size()); - info.mLoopStartSample = slot.loop_start_sample; - info.mLoopEndSample = bounded_cast(slot.loop_end_sample); - info.mSampleCount = bounded_cast(slot.sample_count); - info.mpLast = slot.sample_value_last; - info.mpPenult = slot.sample_value_penult; - // mpLoaded is initialized to point to a 1, so we're good there. - return info; -} - -ModResult insert_replace_wave( - ModContext* ctx, - AudioWaveBank bank, - u16 wave_id, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle) { - if (out_handle != nullptr) { - *out_handle = 0; - } - - auto mod = mod_from_context(ctx); - if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) { - return MOD_INVALID_ARGUMENT; - } - - RuntimeWaveReplacementSlot slot{}; - slot.bundle_path = file_name; - slot.bank = bank; - slot.wave_id = wave_id; - - ModResult result; - if (wave_info && wave_info->raw_wave) { - result = load_raw(*mod, slot, *wave_info->raw_wave); - } else { - result = load_container(*mod, slot); - } - if (result != MOD_OK) { - return result; - } - - if (wave_info != nullptr) { - slot.base_key = wave_info->base_key; - slot.loop = wave_info->loop; - slot.loop_start_sample = wave_info->loop_start_sample; - slot.loop_end_sample = wave_info->loop_end_sample; - } else { - slot.base_key = AUDIO_RES_DEFAULT_KEY; - slot.loop = false; - slot.loop_start_sample = 0; - slot.loop_end_sample = slot.sample_count; - } - - audio_replacements_dirty = true; - - const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); - if (out_handle) { - *out_handle = handle; - } - - return MOD_OK; -} - -bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { - auto const result = s_waveReplacements.erase_owned(handle, mod); - audio_replacements_dirty |= result; - return result; -} - -ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { - auto* mod = mod_from_context(ctx); - if (mod == nullptr || handle == 0) { - return MOD_INVALID_ARGUMENT; - } - if (!wave_remove(*mod, handle)) { - Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); - return MOD_INVALID_ARGUMENT; - } - return MOD_OK; -} - constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), - .replace_wave = &insert_replace_wave, - .remove_wave = &remove_wave, + .replace_wave = &wsys::insert_replace_wave, + .remove_wave = &wsys::remove_wave, }; +void frame_end() { + wsys::frame_end(); +} + +void mod_detached(LoadedMod& mod) { + wsys::remove_mod(mod); +} + void sync_audio_replacements() { - audio_replacements_dirty = false; - - absl::flat_hash_map new_map; - - for (auto const& mod : ModLoader::instance().active_mods()) { - for (auto const& slot : s_waveReplacements.take_all(mod)) { - auto const wave_info = wave_info_from_slot(slot.value); - new_map.emplace( - AudioWaveKey(slot.value.bank, slot.value.wave_id), - AudioWaveReplacementValue(wave_info, slot.value.data)); - } - } - - // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); - - std::lock_guard lock(s_replacements_mutex); - // Note: new_map will contain the old contents, and is dropped *outside* the lock. - // As to avoid holding the lock any longer than necessary. - std::exchange(s_replacements, std::move(new_map)); - - // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); + wsys::sync_audio_replacements(); } -void replacements_remove_mod(LoadedMod& mod) { - s_waveReplacements.erase_all(mod); - - audio_replacements_dirty = true; -} - -} - -absl::flat_hash_map s_replacements; -std::mutex s_replacements_mutex; - -AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; -const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { - return &wave_info; -} - -void const* AudioWaveReplacementValue::getAramBaseAddress() const { - return data->get_data().data(); -} - -intptr_t AudioWaveReplacementValue::getWavePtr() const { - return 0; -} - -std::unique_ptr AudioWaveReplacementValue::getSampleReference() const { - return std::make_unique(data); -} - -void SampleDataPcm16::be_swap() { - for (auto& sample : data) { - ::be_swap(sample); - } } } // namespace audio_res @@ -265,14 +40,9 @@ constinit const ServiceModule g_audioResModule{ .majorVersion = AUDIO_RES_SERVICE_MAJOR, .minorVersion = AUDIO_RES_SERVICE_MINOR, .service = &audio_res::s_audioResService, - .modDetached = audio_res::replacements_remove_mod, + .modDetached = audio_res::mod_detached, .lifecycleApplied = audio_res::sync_audio_replacements, - .frameEnd = - [] { - if (audio_res::audio_replacements_dirty) { - audio_res::sync_audio_replacements(); - } - } + .frameEnd = audio_res::frame_end }; }; diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index ce46db0721..5b2ae0a87a 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,106 +1,9 @@ #pragma once -#include -#include -#include "JSystem/JAudio2/JASChannel.h" -#include "JSystem/JAudio2/JASWaveInfo.h" -#include "absl/container/flat_hash_map.h" -#include "mods/svc/audio_res.h" namespace dusk::mods { struct LoadedMod; } namespace dusk::mods::svc::audio_res { -struct SampleDataBuf { - virtual ~SampleDataBuf() = default; - [[nodiscard]] virtual std::span get_data() const = 0; - [[nodiscard]] size_t size() const { - return get_data().size(); - } -}; - -// Making a separate type for this rather than just using a vector -// because I don't want aliasing or alignment to eat my face. -struct SampleDataPcm16 : SampleDataBuf { - std::vector data; - - [[nodiscard]] std::span get_data() const override { - return as_bytes(std::span(data)); - } - - void be_swap(); -}; - -struct SampleDataU8 : SampleDataBuf { - std::vector data; - - explicit SampleDataU8(std::vector data) : data(std::move(data)) {} - - [[nodiscard]] std::span get_data() const override { - return as_bytes(std::span(data)); - } -}; - -struct SampleReference : JASSampleDataReference { - explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} - - std::shared_ptr data; -}; - -struct AudioWaveKey { - AudioWaveBank bank; - u32 wave_id; - - AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; - - template - friend H AbslHashValue(H h, const AudioWaveKey& k) { - return H::combine(std::move(h), k.bank, k.wave_id); - } - - [[nodiscard]] bool operator==(const AudioWaveKey& other) const { - return other.bank == bank && other.wave_id == wave_id; - } -}; - -struct AudioWaveReplacementValue : JASWaveHandle { - AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) : wave_info(wave_info), data(std::move(data)) {}; - ~AudioWaveReplacementValue() override; - [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; - [[nodiscard]] intptr_t getWavePtr() const override; - [[nodiscard]] void const* getAramBaseAddress() const override; - [[nodiscard]] std::unique_ptr getSampleReference() const override; - - JASWaveInfo wave_info; - std::shared_ptr data; -}; - -extern absl::flat_hash_map s_replacements; -extern std::mutex s_replacements_mutex; - -struct RuntimeWaveReplacementSlot { - std::string bundle_path; - AudioWaveBank bank; - u16 wave_id; - - u8 base_key; - bool loop; - u32 loop_start_sample; - u32 loop_end_sample; - - AudioWaveFormat format; - f32 sample_rate; - u32 sample_count; - s16 sample_value_last; - s16 sample_value_penult; - - std::shared_ptr data; -}; - -using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); - -ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); - } diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp index 3541f6be81..aa4e6bea99 100644 --- a/src/dusk/mods/svc/audio_res/opus.cpp +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -1,6 +1,6 @@ #if DUSK_OPUS -#include "audio_res.hpp" +#include "wsys.hpp" #include "aurora/lib/logging.hpp" #include "dusk/mod_loader.hpp" #include "helpers/cast.hpp" @@ -8,7 +8,7 @@ namespace { -aurora::Module Log("dusk::mods::svc::audio_res::opus"); +aurora::Module Log("dusk::mods::svc::audio_res::wsys::opus"); struct OpusHandle { OggOpusFile* file; @@ -24,7 +24,7 @@ struct OpusHandle { } -namespace dusk::mods::svc::audio_res { +namespace dusk::mods::svc::audio_res::wsys { ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { OpusHead head; const int result = op_test(&head, fileData.data(), fileData.size()); diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index 69e1e2e10f..ae97a8f81b 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -1,11 +1,11 @@ -#include "audio_res.hpp" +#include "wsys.hpp" #include "aurora/lib/logging.hpp" #include "dusk/mod_loader.hpp" #include "helpers/alignment.hpp" namespace { -aurora::Module Log("dusk::mods::svc::audio_res::wav"); +aurora::Module Log("dusk::mods::svc::audio_res::wsys::wav"); struct ChunkHeader { char magic[4]; @@ -37,7 +37,7 @@ bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { } -namespace dusk::mods::svc::audio_res { +namespace dusk::mods::svc::audio_res::wsys { ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { using namespace helpers::alignment; diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp new file mode 100644 index 0000000000..5abe9cea71 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -0,0 +1,260 @@ +#include "wsys.hpp" +#include "../registry.hpp" +#include "../slot_map.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/audio/DuskAudioSystem.h" +#include "dusk/mods/loader/loader.hpp" +#include "helpers/alignment.hpp" +#include "helpers/cast.hpp" + +namespace dusk::mods::svc::audio_res::wsys { + +namespace { + +using namespace dusk::helpers::cast; + +bool wave_replacements_dirty = false; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap s_waveReplacements; + +constexpr ContainerLoadFunction s_containerLoadFunctions[] = { + load_wav, +#if DUSK_OPUS + load_opus, +#endif +}; + + +bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { + u32 samples_per_block; + u32 bytes_per_block; + switch (raw.format) { + case Adpcm4: + samples_per_block = 16; + bytes_per_block = 9; + break; + case Pcm16: + samples_per_block = 1; + bytes_per_block = 2; + break; + default: + Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path); + return false; + } + + if (actual_size % bytes_per_block != 0) { + Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); + return false; + } + + sample_count = (actual_size / bytes_per_block) * samples_per_block; + return true; +} + +ModResult load_raw( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + u32 sample_count = 0; + + if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) { + Log.error( + "[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified", + mod.metadata.id, slot.bundle_path); + + return MOD_INVALID_ARGUMENT; + } + + if (slot.format == Pcm16) { + SampleDataPcm16 pcm16; + pcm16.data.resize(sample_count); + memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); + slot.data = std::make_shared(std::move(pcm16)); + } else { + slot.data = std::make_shared(std::move(file_contents)); + } + + slot.sample_count = sample_count; + slot.sample_rate = raw.sample_rate; + slot.format = raw.format; + slot.sample_value_penult = raw.sample_value_penult; + slot.sample_value_last = raw.sample_value_last; + return MOD_OK; +} + +/** + * Load an audio file stored in some sort of container format. + * File type is determined by looking at contents, not extension. + */ +ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) { + auto file_contents = mod.bundle->readFile(slot.bundle_path); + + for (auto const loader : s_containerLoadFunctions) { + auto const result = loader(mod, slot, file_contents); + if (result == MOD_OK) { + return MOD_OK; + } + + if (result != MOD_UNSUPPORTED) { + return result; + } + } + + return MOD_UNSUPPORTED; +} + +JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { + JASWaveInfo info; + info.mWaveFormat = static_cast(slot.format); + info.mBaseKey = slot.base_key; + info.mLoopFlag = slot.loop ? 0xFF : 0; + info.mSampleRate = slot.sample_rate; + info.mOffsetStart = 0; // Unused but just init it ig. + info.mOffsetLength = bounded_cast(slot.data->size()); + info.mLoopStartSample = slot.loop_start_sample; + info.mLoopEndSample = bounded_cast(slot.loop_end_sample); + info.mSampleCount = bounded_cast(slot.sample_count); + info.mpLast = slot.sample_value_last; + info.mpPenult = slot.sample_value_penult; + // mpLoaded is initialized to point to a 1, so we're good there. + return info; +} + +bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { + auto const result = s_waveReplacements.erase_owned(handle, mod); + wave_replacements_dirty |= result; + return result; +} + +} + +absl::flat_hash_map s_replacements; +std::mutex s_replacements_mutex; + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + if (!wave_remove(*mod, handle)) { + Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + return MOD_OK; +} + +ModResult insert_replace_wave( + ModContext* ctx, + AudioWaveBank bank, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) { + return MOD_INVALID_ARGUMENT; + } + + RuntimeWaveReplacementSlot slot{}; + slot.bundle_path = file_name; + slot.bank = bank; + slot.wave_id = wave_id; + + ModResult result; + if (wave_info && wave_info->raw_wave) { + result = load_raw(*mod, slot, *wave_info->raw_wave); + } else { + result = load_container(*mod, slot); + } + if (result != MOD_OK) { + return result; + } + + if (wave_info != nullptr) { + slot.base_key = wave_info->base_key; + slot.loop = wave_info->loop; + slot.loop_start_sample = wave_info->loop_start_sample; + slot.loop_end_sample = wave_info->loop_end_sample; + } else { + slot.base_key = AUDIO_RES_DEFAULT_KEY; + slot.loop = false; + slot.loop_start_sample = 0; + slot.loop_end_sample = slot.sample_count; + } + + wave_replacements_dirty = true; + + const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +void remove_mod(LoadedMod& mod) { + s_waveReplacements.erase_all(mod); + + wave_replacements_dirty = true; +} + +void sync_audio_replacements() { + wave_replacements_dirty = false; + + absl::flat_hash_map new_map; + + for (auto const& mod : ModLoader::instance().active_mods()) { + for (auto const& slot : s_waveReplacements.take_all(mod)) { + auto const wave_info = wave_info_from_slot(slot.value); + new_map.emplace( + AudioWaveKey(slot.value.bank, slot.value.wave_id), + AudioWaveReplacementValue(wave_info, slot.value.data)); + } + } + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); + + std::lock_guard lock(s_replacements_mutex); + // Note: new_map will contain the old contents, and is dropped *outside* the lock. + // As to avoid holding the lock any longer than necessary. + std::exchange(s_replacements, std::move(new_map)); + + // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); +} + +void frame_end() { + if (wave_replacements_dirty) { + sync_audio_replacements(); + } +} + +AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; +const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { + return &wave_info; +} + +void const* AudioWaveReplacementValue::getAramBaseAddress() const { + return data->get_data().data(); +} + +intptr_t AudioWaveReplacementValue::getWavePtr() const { + return 0; +} + +std::unique_ptr AudioWaveReplacementValue::getSampleReference() const { + return std::make_unique(data); +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + + +} \ No newline at end of file diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp new file mode 100644 index 0000000000..76b966f65a --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include "JSystem/JAudio2/JASWaveInfo.h" +#include "absl/container/flat_hash_map.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} + +namespace dusk::mods::svc::audio_res::wsys { + +struct SampleDataBuf { + virtual ~SampleDataBuf() = default; + [[nodiscard]] virtual std::span get_data() const = 0; + [[nodiscard]] size_t size() const { + return get_data().size(); + } +}; + +// Making a separate type for this rather than just using a vector +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct SampleReference : JASSampleDataReference { + explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} + + std::shared_ptr data; +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; + + template + friend H AbslHashValue(H h, const AudioWaveKey& k) { + return H::combine(std::move(h), k.bank, k.wave_id); + } + + [[nodiscard]] bool operator==(const AudioWaveKey& other) const { + return other.bank == bank && other.wave_id == wave_id; + } +}; + +struct AudioWaveReplacementValue : JASWaveHandle { + AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) : wave_info(wave_info), data(std::move(data)) {}; + ~AudioWaveReplacementValue() override; + [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; + [[nodiscard]] intptr_t getWavePtr() const override; + [[nodiscard]] void const* getAramBaseAddress() const override; + [[nodiscard]] std::unique_ptr getSampleReference() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map s_replacements; +extern std::mutex s_replacements_mutex; + +struct RuntimeWaveReplacementSlot { + std::string bundle_path; + AudioWaveBank bank; + u16 wave_id; + + u8 base_key; + bool loop; + u32 loop_start_sample; + u32 loop_end_sample; + + AudioWaveFormat format; + f32 sample_rate; + u32 sample_count; + s16 sample_value_last; + s16 sample_value_penult; + + std::shared_ptr data; +}; + +using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +void frame_end(); +void remove_mod(LoadedMod& mod); +void sync_audio_replacements(); + +ModResult insert_replace_wave( + ModContext* ctx, + AudioWaveBank bank, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); + +} \ No newline at end of file From 2fca4631397d7834b787eb6bb4de8bf51492c775 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:55:16 +0200 Subject: [PATCH 022/100] Allow allocating new audio waves ...probably. I have no means to test this code atm. --- files.cmake | 2 + sdk/include/mods/svc/audio_res.h | 8 ++ src/dusk/mods/svc/audio_res/audio_res.cpp | 1 + src/dusk/mods/svc/audio_res/wsys.cpp | 100 +++++++++++++++++----- src/dusk/mods/svc/audio_res/wsys.hpp | 9 ++ src/dusk/mods/svc/id_allocator.cpp | 9 ++ src/dusk/mods/svc/id_allocator.hpp | 43 ++++++++++ 7 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 src/dusk/mods/svc/id_allocator.cpp create mode 100644 src/dusk/mods/svc/id_allocator.hpp diff --git a/files.cmake b/files.cmake index 90a0dcd4f8..0f915e5abe 100644 --- a/files.cmake +++ b/files.cmake @@ -1498,6 +1498,8 @@ set(DUSK_FILES src/dusk/mods/svc/gfx.cpp src/dusk/mods/svc/hook.cpp src/dusk/mods/svc/host.cpp + src/dusk/mods/svc/id_allocator.cpp + src/dusk/mods/svc/id_allocator.hpp src/dusk/mods/svc/log.cpp src/dusk/mods/svc/overlay.cpp src/dusk/mods/svc/registry.cpp diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index 0d126c1912..b5af109959 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -52,6 +52,14 @@ typedef struct AudioResService { AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + ModResult (*add_wave)( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + uint16_t* out_wave_id); + ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); } AudioResService; diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 28a18453ca..a63eb4e3ce 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -16,6 +16,7 @@ using namespace dusk::helpers::cast; constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), .replace_wave = &wsys::insert_replace_wave, + .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, }; diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 5abe9cea71..266c0ca55e 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -1,10 +1,9 @@ #include "wsys.hpp" -#include "../registry.hpp" #include "../slot_map.hpp" +#include "../id_allocator.hpp" #include "aurora/lib/logging.hpp" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/mods/loader/loader.hpp" -#include "helpers/alignment.hpp" #include "helpers/cast.hpp" namespace dusk::mods::svc::audio_res::wsys { @@ -26,6 +25,12 @@ constexpr ContainerLoadFunction s_containerLoadFunctions[] = { #endif }; +PlainIdAllocator sound_effect_id_allocator(5'000); +PlainIdAllocator music_sample_id_allocator(1'000); + +PlainIdAllocator& id_allocator_for_bank(AudioWaveBank const bank) { + return bank == SoundEffects ? sound_effect_id_allocator : music_sample_id_allocator; +} bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { u32 samples_per_block; @@ -122,32 +127,25 @@ JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { } bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { - auto const result = s_waveReplacements.erase_owned(handle, mod); - wave_replacements_dirty |= result; - return result; -} - -} - -absl::flat_hash_map s_replacements; -std::mutex s_replacements_mutex; - -ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { - auto* mod = mod_from_context(ctx); - if (mod == nullptr || handle == 0) { - return MOD_INVALID_ARGUMENT; + auto const found = s_waveReplacements.find_owned(handle, mod); + if (found == nullptr) { + return false; } - if (!wave_remove(*mod, handle)) { - Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); - return MOD_INVALID_ARGUMENT; + + if (found->value.mod_defined) { + auto& allocator = id_allocator_for_bank(found->value.bank); + allocator.free(found->value.wave_id); } - return MOD_OK; + + s_waveReplacements.erase_owned(handle, mod); + return true; } -ModResult insert_replace_wave( +ModResult insert_replace_wave_core( ModContext* ctx, AudioWaveBank bank, u16 wave_id, + bool mod_defined, char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle) { @@ -164,6 +162,7 @@ ModResult insert_replace_wave( slot.bundle_path = file_name; slot.bank = bank; slot.wave_id = wave_id; + slot.mod_defined = mod_defined; ModResult result; if (wave_info && wave_info->raw_wave) { @@ -197,6 +196,65 @@ ModResult insert_replace_wave( return MOD_OK; } + +} + +absl::flat_hash_map s_replacements; +std::mutex s_replacements_mutex; + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + if (!wave_remove(*mod, handle)) { + Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + return MOD_OK; +} + +ModResult insert_replace_wave( + ModContext* ctx, + AudioWaveBank bank, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle) { + + return insert_replace_wave_core(ctx, bank, wave_id, false, file_name, wave_info, out_handle); +} + +ModResult insert_add_wave( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + u16* out_wave_id) { + + if (out_wave_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + *out_wave_id = 0; + + if (bank != SoundEffects && bank != MusicSamples) { + return MOD_INVALID_ARGUMENT; + } + + auto& allocator = id_allocator_for_bank(bank); + auto const new_wave_id = allocator.alloc(); + + auto const result = insert_replace_wave_core(ctx, bank, new_wave_id, true, file_name, wave_info, out_handle); + if (result != MOD_OK) { + allocator.free(new_wave_id); + } + + *out_wave_id = new_wave_id; + return result; +} + void remove_mod(LoadedMod& mod) { s_waveReplacements.erase_all(mod); diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index 76b966f65a..16d144e4b3 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -82,6 +82,7 @@ extern std::mutex s_replacements_mutex; struct RuntimeWaveReplacementSlot { std::string bundle_path; AudioWaveBank bank; + bool mod_defined; u16 wave_id; u8 base_key; @@ -115,6 +116,14 @@ ModResult insert_replace_wave( AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); +ModResult insert_add_wave( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + u16* out_wave_id); + ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); } \ No newline at end of file diff --git a/src/dusk/mods/svc/id_allocator.cpp b/src/dusk/mods/svc/id_allocator.cpp new file mode 100644 index 0000000000..3778bc7b81 --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.cpp @@ -0,0 +1,9 @@ +#include "id_allocator.hpp" + +namespace dusk::mods::svc { + +void id_allocator_exhausted() { + CRASH("ID allocator exhausted!"); +} + +} // namespace dusk::mods::svc \ No newline at end of file diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp new file mode 100644 index 0000000000..b336e0fbfa --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +namespace dusk::mods::svc { + +[[noreturn]] void id_allocator_exhausted(); + +template requires std::is_integral_v +class PlainIdAllocator { + std::vector reusable; + T alloc_next; + T alloc_max; + +public: + explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) + : alloc_next(first), alloc_max(max) { + + } + + T alloc() { + if (reusable.empty()) { + if (alloc_next == alloc_max) { + id_allocator_exhausted(); + } + + return alloc_next++; + } + + auto val = reusable.back(); + reusable.pop_back(); + return val; + } + + void free(T value) { + assert(value < alloc_max && value < alloc_next); + + reusable.push_back(value); + } +}; + +} \ No newline at end of file From 89480a520f526864d859b49b92e9c7283b2f346e Mon Sep 17 00:00:00 2001 From: Irastris Date: Thu, 13 Aug 2026 14:21:00 -0400 Subject: [PATCH 023/100] Delta time fades and wipes --- libs/JSystem/src/JFramework/JFWDisplay.cpp | 12 ++++---- libs/JSystem/src/JUtility/JUTFader.cpp | 34 ++++++++++++---------- src/d/d_drawlist.cpp | 2 +- src/dusk/game_clock.cpp | 19 ++++++++++++ src/dusk/game_clock.h | 3 ++ src/f_pc/f_pc_manager.cpp | 19 ++++++++---- src/m_Do/m_Do_graphic.cpp | 15 ++-------- 7 files changed, 63 insertions(+), 41 deletions(-) diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 0524cb86f7..939659205e 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -14,6 +14,7 @@ #if TARGET_PC #include "dusk/dusk.h" +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" #include "dusk/logging.h" #include "dusk/settings.h" @@ -219,11 +220,12 @@ void JFWDisplay::endGX() { if (mFader != NULL) { ortho.setPort(); #if TARGET_PC - if (dusk::interp::get_ui_tick_pending()) { - mFader->advance(); - } - if (mFader->getStatus() != JUTFader::Wait) { - mFader->draw(); + if (dusk::game_clock::g_frameTiming.separatePresentation) { + if (mFader->getStatus() != JUTFader::Wait) { + mFader->draw(); + } + } else { + mFader->control(); } #else mFader->control(); diff --git a/libs/JSystem/src/JUtility/JUTFader.cpp b/libs/JSystem/src/JUtility/JUTFader.cpp index 2e937b176e..cf9a9e2364 100644 --- a/libs/JSystem/src/JUtility/JUTFader.cpp +++ b/libs/JSystem/src/JUtility/JUTFader.cpp @@ -8,8 +8,8 @@ #include "JSystem/JUtility/JUTFader.h" #include "JSystem/J2DGraph/J2DOrthoGraph.h" -#ifdef TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#if TARGET_PC +#include "dusk/game_clock.h" #include #endif @@ -39,6 +39,7 @@ void JUTFader::advance() { case FadeIn: #if AVOID_UB if (mDuration == 0) { + IF_DUSK(mColor.a = 0); mStatus = Wait; break; } @@ -53,6 +54,7 @@ void JUTFader::advance() { case FadeOut: #if AVOID_UB if (mDuration == 0) { + IF_DUSK(mColor.a = 0xFF); mStatus = None; break; } @@ -69,26 +71,26 @@ void JUTFader::advance() { void JUTFader::control() { advance(); + IF_DUSK_BLOCK(getStatus() != Wait) draw(); + IF_DUSK_BLOCK_END } void JUTFader::draw() { - if (mColor.a != 0) { -#ifdef TARGET_PC - if (dusk::interp::is_enabled() && mDuration != 0) { - const auto step = dusk::interp::get_interpolation_step(); - const auto progress = static_cast(mTimer) / static_cast(mDuration); - const auto timer = mTimer - 1 + step + progress; - auto alpha = timer / mDuration; - if (mStatus == FadeIn) { - alpha = 1.0f - alpha; - } - alpha = std::clamp(alpha, 0.0f, 1.0f); - mColor.a = static_cast(alpha * 255.0f); - } +#if TARGET_PC + JUtility::TColor color = mColor; + if (dusk::game_clock::g_frameTiming.separatePresentation && mDuration != 0 && + (mStatus == FadeIn || mStatus == FadeOut)) + { + const f32 timer = std::min(mTimer + dusk::game_clock::sample_interpolation_step(), + static_cast(mDuration)); + const u8 alpha = static_cast((timer * 0xFF) / mDuration); + color.a = mStatus == FadeIn ? 0xFF - alpha : alpha; + } #endif + if (DUSK_IF_ELSE(color, mColor).a != 0) { J2DOrthoGraph orthograph; - orthograph.setColor(mColor); + orthograph.setColor(DUSK_IF_ELSE(color, mColor)); orthograph.fillBox(mBox); } } diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index b5ed876126..957d17be4f 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -2067,7 +2067,7 @@ void dDlst_list_c::wipeIn(f32 i_wipeSpeed) { void dDlst_list_c::calcWipe() { if (mWipe) { - mWipeRate += mWipeSpeed; + mWipeRate += mWipeSpeed IF_DUSK(* dusk::game_clock::original_frames()); if (mWipeRate < 0.0f) { mWipeRate = 0.0f; } else if (mWipeRate > 1.0f) { diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp index 3a680fcca8..52472f42a9 100644 --- a/src/dusk/game_clock.cpp +++ b/src/dusk/game_clock.cpp @@ -26,11 +26,21 @@ bool s_timingModeInitialized = false; bool s_previousSeparatePresentation = false; bool s_previousInterpolating = false; bool s_previousTimeStopped = false; +float s_presentationDtSeconds = kUiInitialDt; constexpr game_clock::duration kSimPeriodDuration = std::chrono::duration_cast(std::chrono::duration(kSimPeriod)); constexpr native_clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); constexpr int kMaxSimTicksPerFrame = static_cast(aurora::time::kMaximumTimeScale) * 4; + +float ui_dt() { + if (s_simTickActive) { + return kSimPeriod; + } + + const float maximumDt = kUiMaximumDt * aurora::time::scale(); + return std::clamp(s_presentationDtSeconds, 0.0f, maximumDt); +} } // namespace void initialize() { @@ -76,6 +86,7 @@ const FrameTiming& advance() { .dt = std::chrono::duration(gameFrameGap).count(), .presentationEpoch = s_presentationEpoch, }; + s_presentationDtSeconds = out.dt; const float timeScale = aurora::time::scale(); const bool interpolating = @@ -147,6 +158,10 @@ bool is_sim_frame() { return !g_frameTiming.separatePresentation || s_simTickActive; } +bool is_presentation_frame() { + return !g_frameTiming.separatePresentation || !s_simTickActive; +} + float sample_interpolation_step() { const float step = std::chrono::duration(game_clock::now() - s_currentSnapshotTime).count() / @@ -154,6 +169,10 @@ float sample_interpolation_step() { return std::clamp(step, 0.0f, 1.0f); } +float original_frames() { + return ui_dt() / kSimPeriod; +} + float consume_interval(const void* consumer) { const auto key = reinterpret_cast(consumer); const auto now = s_simTickActive ? s_pendingSimTime : game_clock::now(); diff --git a/src/dusk/game_clock.h b/src/dusk/game_clock.h index 3492456927..df04d7d420 100644 --- a/src/dusk/game_clock.h +++ b/src/dusk/game_clock.h @@ -7,6 +7,8 @@ constexpr float kSimPeriod = 1.0f / 30.0f; constexpr float kUiMaximumDt = 0.05f; constexpr float kUiInitialDt = 1.0f / 60.0f; +float original_frames(); + struct FrameTiming { // Amount of time elapsed in seconds since the last advance float dt; @@ -29,6 +31,7 @@ void commit_sim_tick(); float sample_interpolation_step(); bool is_sim_frame(); +bool is_presentation_frame(); float consume_interval(const void* consumer); diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index d2276bf8cb..b9952a27a4 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -24,6 +24,8 @@ #if TARGET_PC #include "dusk/game_clock.h" +#include "m_Do/m_Do_graphic.h" + #include #endif @@ -67,13 +69,18 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p l_dvdError = false; } -#ifdef TARGET_PC - // The main loop manages painting when simulation and presentation are separated. - if (!dusk::game_clock::g_frameTiming.separatePresentation) -#endif - { - cAPIGph_Painter(); +#if TARGET_PC + if (dusk::game_clock::g_frameTiming.separatePresentation) { + if (JUTFader* fader = mDoGph_gInf_c::getFader()) { + fader->advance(); + } } +#endif + + // The main loop manages painting when simulation and presentation are separated. + IF_DUSK_BLOCK(!dusk::game_clock::g_frameTiming.separatePresentation) + cAPIGph_Painter(); + IF_DUSK_BLOCK_END if (!dPa_control_c::isStatus(1)) { fpcDt_Handler(); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index f83f151724..cb8721aca9 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -47,6 +47,7 @@ #if TARGET_PC #include "dusk/dusk.h" +#include "dusk/game_clock.h" #include "dusk/gfx.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/interp/frame_interpolation.h" @@ -474,9 +475,8 @@ void darwFilter(GXColor matColor) { } void mDoGph_gInf_c::calcFade() { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) if (mFade != 0) { - mFadeRate += mFadeSpeed; + mFadeRate += mFadeSpeed IF_DUSK(* dusk::game_clock::original_frames()); if (mFadeRate < 0.0f) { mFadeRate = 0.0f; @@ -497,17 +497,8 @@ void mDoGph_gInf_c::calcFade() { mFadeColor.a = 0; } } - IF_DUSK_BLOCK_END if (mFadeColor.a != 0) { -#if TARGET_PC - if (dusk::interp::is_enabled() && mFade != 0) { - const auto step = dusk::interp::get_interpolation_step(); - const auto progress = mFadeSpeed < 0.0f ? 1.0f - mFadeRate : mFadeRate; - const auto fade_amt = mFadeRate + mFadeSpeed * (step - 1.0f + progress); - mFadeColor.a = 255.0f * std::clamp(fade_amt, 0.0f, 1.0f); - } -#endif darwFilter(mFadeColor); } } @@ -2762,9 +2753,7 @@ int mDoGph_Painter() { #endif GXSetClipMode(GX_CLIP_ENABLE); - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) dDlst_list_c::calcWipe(); - IF_DUSK_BLOCK_END j3dSys.reinitGX(); ortho.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), From f8ef48d74e2039938e98ba24ffb11bebe1e17cfd Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 16 Aug 2026 13:32:00 -0400 Subject: [PATCH 024/100] Delta time pane scale/color/alpha --- include/d/d_pane_class.h | 25 +++++++++++--- include/d/d_pane_class_alpha.h | 27 ++++++++++++--- src/d/actor/d_a_title.cpp | 7 ++++ src/d/d_pane_class.cpp | 56 +++++++++++++++++++++++++++++++- src/d/d_pane_class_alpha.cpp | 50 ++++++++++++++++++++++++++-- src/dusk/interp/lerp.h | 9 +++++ src/dusk/interp/user_interface.h | 54 ++++++++++++++++++++++++++++++ 7 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 src/dusk/interp/user_interface.h diff --git a/include/d/d_pane_class.h b/include/d/d_pane_class.h index ffc067e411..29cee0c2ea 100644 --- a/include/d/d_pane_class.h +++ b/include/d/d_pane_class.h @@ -4,6 +4,10 @@ #include "JSystem/JUtility/TColor.h" #include "d/d_pane_class_alpha.h" +#if TARGET_PC +#include +#endif + class JKRHeap; void dPaneClass_showNullPane(J2DScreen*); @@ -26,6 +30,9 @@ public: void setBlackWhite(JUtility::TColor, JUtility::TColor); void paneTrans(f32, f32); void paneScale(f32, f32); +#if TARGET_PC + void presentAnime(); +#endif bool scaleAnime(s16, f32, f32, u8); bool colorAnime(s16, JUtility::TColor, JUtility::TColor, JUtility::TColor, JUtility::TColor, u8); @@ -54,8 +61,14 @@ public: void resize(f32 x, f32 y) { getPanePtr()->resize(x, y); } void move(f32 x, f32 y) { getPanePtr()->move(x, y); } - void scaleAnimeStart(s16 v) { mScaleAnime = v; } - void colorAnimeStart(s16 start) { mColorAnime = start; } + void scaleAnimeStart(DUSK_IF_ELSE(f32, s16) v) { + mScaleAnime = v; + IF_DUSK(mScaleAnimation.active = false;) + } + void colorAnimeStart(DUSK_IF_ELSE(f32, s16) start) { + mColorAnime = start; + IF_DUSK(mColorAnimation.active = false;) + } f32 getPosX() { return getPanePtr()->getBounds().i.x; } f32 getPosY() { return getPanePtr()->getBounds().i.y; } @@ -106,10 +119,14 @@ public: /* 0x5C */ JUtility::TColor mInitBlack; /* 0x60 */ s16 field_0x60; /* 0x62 */ s16 field_0x62; - /* 0x64 */ s16 mScaleAnime; + /* 0x64 */ DUSK_IF_ELSE(f32, s16) mScaleAnime; /* 0x66 */ s16 field_0x66; /* 0x68 */ s16 field_0x68; - /* 0x6A */ s16 mColorAnime; + /* 0x6A */ DUSK_IF_ELSE(f32, s16) mColorAnime; +#if TARGET_PC + Animation mScaleAnimation; + Animation> mColorAnimation; +#endif }; #endif /* D_PANE_D_PANE_CLASS_H */ diff --git a/include/d/d_pane_class_alpha.h b/include/d/d_pane_class_alpha.h index cf38229b97..f55227a3ee 100644 --- a/include/d/d_pane_class_alpha.h +++ b/include/d/d_pane_class_alpha.h @@ -9,6 +9,16 @@ class JKRExpHeap; class CPaneMgrAlpha { public: +#if TARGET_PC + template + struct Animation { + bool active = false; + s16 duration; + u8 curve; + T start, end; + }; +#endif + virtual ~CPaneMgrAlpha(); virtual void setAlpha(u8); @@ -18,9 +28,12 @@ public: void show(); void hide(); bool isVisible(); - f32 rateCalc(s16, s16, u8); + f32 rateCalc(DUSK_IF_ELSE(f32, s16), DUSK_IF_ELSE(f32, s16), u8); void setAlphaRate(f32); f32 getAlphaRate(); +#if TARGET_PC + void presentAlphaAnime(); +#endif bool alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcType); bool alphaAnimeLoop(s16, u8, u8, u8); void childPaneCount(J2DPane*); @@ -29,8 +42,11 @@ public: J2DPane* getPanePtr() { return mPane; } u8 getAlpha() { return getPanePtr()->getAlpha(); } - s16 getAlphaTimer() { return mAlphaTimer; } - void alphaAnimeStart(s16 start) { mAlphaTimer = start; } + DUSK_IF_ELSE(f32, s16) getAlphaTimer() { return mAlphaTimer; } + void alphaAnimeStart(DUSK_IF_ELSE(f32, s16) start) { + mAlphaTimer = start; + IF_DUSK(mAlphaAnimation.active = false;) + } u8 getInitAlpha() { return mInitAlpha; } /* 0x04 */ J2DPane* mPane; @@ -38,9 +54,12 @@ public: /* 0x0C */ void* mpFirstStackAlpha; /* 0x10 */ u8* field_0x10; /* 0x14 */ s16 mChildPaneCount; - /* 0x16 */ s16 mAlphaTimer; + /* 0x16 */ DUSK_IF_ELSE(f32, s16) mAlphaTimer; /* 0x18 */ u8 mInitAlpha; /* 0x19 */ u8 mFlags; +#if TARGET_PC + Animation mAlphaAnimation; +#endif }; class CPaneMgrAlphaMorf : public CPaneMgrAlpha { diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index 0d011f9213..ccf03f4a5d 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -19,6 +19,7 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" #include "dusk/version.hpp" #endif @@ -380,6 +381,12 @@ int daTitle_c::getDemoPrm() { } int daTitle_c::Draw() { +#if TARGET_PC + if (field_0x600 != NULL && dusk::game_clock::is_presentation_frame()) { + field_0x600->presentAlphaAnime(); + } +#endif + J3DModelData* modelData = mpModel->getModelData(); cMtx_trans(mpModel->getBaseTRMtx(), IREG_F(7), IREG_F(8), IREG_F(9) + -430.0f); mpModel->getBaseScale()->x = -1.0f; diff --git a/src/d/d_pane_class.cpp b/src/d/d_pane_class.cpp index 4d5104ffcf..f833857873 100644 --- a/src/d/d_pane_class.cpp +++ b/src/d/d_pane_class.cpp @@ -13,7 +13,16 @@ #include "m_Do/m_Do_ext.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include "dusk/interp/lerp.h" +#include "dusk/interp/user_interface.h" +#endif + CPaneMgr::CPaneMgr() { +#if TARGET_PC + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif mpFirstStackAlpha = NULL; mpFirstStackSize = NULL; } @@ -45,6 +54,12 @@ void CPaneMgr::setAlpha(u8 alpha) { } void CPaneMgr::reinit() { +#if TARGET_PC + mAlphaAnimation.active = false; + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif + mInitPos.x = getPanePtr()->getBounds().i.x; mInitPos.y = getPanePtr()->getBounds().i.y; @@ -66,6 +81,12 @@ void CPaneMgr::reinit() { } void CPaneMgr::initiate(J2DPane* p_pane, JKRExpHeap* p_heap) { +#if TARGET_PC + mAlphaAnimation.active = false; + mScaleAnimation.active = false; + mColorAnimation.active = false; +#endif + mPane = p_pane; if (p_heap) { @@ -269,7 +290,29 @@ void CPaneMgr::paneScale(f32 x, f32 y) { } } +#if TARGET_PC +void CPaneMgr::presentAnime() { + presentAlphaAnime(); + dusk::vdt::present_animation(mScaleAnimation, mScaleAnime, *this, [&](f32 rate) { + const f32 scale = dusk::interp::lerp(mScaleAnimation.start, mScaleAnimation.end, rate); + getPanePtr()->scale(getInitScaleX() * scale, getInitScaleY() * scale); + }); + dusk::vdt::present_animation(mColorAnimation, mColorAnime, *this, [&](f32 rate) { + setBlackWhite( + dusk::interp::lerp(mColorAnimation.start.first, mColorAnimation.end.first, rate), + dusk::interp::lerp(mColorAnimation.start.second, mColorAnimation.end.second, rate)); + }); +} +#endif + bool CPaneMgr::scaleAnime(s16 param_0, f32 param_1, f32 param_2, u8 param_3) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mScaleAnimation, mScaleAnime, param_0, param_1, param_2, param_3)) { + return false; + } + getPanePtr()->scale(mInitScale.x * param_2, mInitScale.y * param_2); + return true; +#else if (mScaleAnime < param_0 - 1) { mScaleAnime++; f32 rate = rateCalc(param_0, mScaleAnime, param_3); @@ -281,10 +324,20 @@ bool CPaneMgr::scaleAnime(s16 param_0, f32 param_1, f32 param_2, u8 param_3) { return true; } return false; +#endif } bool CPaneMgr::colorAnime(s16 anmTimer, JUtility::TColor startBlack, JUtility::TColor endBlack, - JUtility::TColor startWhite, JUtility::TColor endWhite, u8 calcType) { + JUtility::TColor startWhite, JUtility::TColor endWhite, u8 calcType) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mColorAnimation, mColorAnime, anmTimer, + {startBlack, startWhite}, {endBlack, endWhite}, calcType)) + { + return false; + } + setBlackWhite(endBlack, endWhite); + return true; +#else if (mColorAnime < anmTimer - 1) { mColorAnime++; f32 rate = rateCalc(anmTimer, mColorAnime, calcType); @@ -349,6 +402,7 @@ bool CPaneMgr::colorAnime(s16 anmTimer, JUtility::TColor startBlack, JUtility::T } return false; +#endif } Vec CPaneMgr::getGlobalVtx(J2DPane* p_pane, Mtx* param_1, u8 param_2, bool param_3, diff --git a/src/d/d_pane_class_alpha.cpp b/src/d/d_pane_class_alpha.cpp index 31bc8bc9b4..d82b77d220 100644 --- a/src/d/d_pane_class_alpha.cpp +++ b/src/d/d_pane_class_alpha.cpp @@ -5,7 +5,13 @@ #include "JSystem/JKernel/JKRExpHeap.h" #include "m_Do/m_Do_ext.h" -CPaneMgrAlpha::CPaneMgrAlpha() {} +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + +CPaneMgrAlpha::CPaneMgrAlpha() { + IF_DUSK(mAlphaAnimation.active = false); +} CPaneMgrAlpha::CPaneMgrAlpha(J2DScreen* p_screen, u64 tag, u8 flags, JKRExpHeap* p_heap) { J2DPane* pane = p_screen->search(tag); @@ -21,6 +27,8 @@ CPaneMgrAlpha::~CPaneMgrAlpha() { } void CPaneMgrAlpha::initiateAlpha(J2DPane* p_pane, JKRExpHeap* p_heap) { + IF_DUSK(mAlphaAnimation.active = false); + mPane = p_pane; if (p_heap != NULL) { @@ -68,7 +76,7 @@ bool CPaneMgrAlpha::isVisible() { return mPane->isVisible(); } -f32 CPaneMgrAlpha::rateCalc(s16 maxTimer, s16 curTimer, u8 calcType) { +f32 CPaneMgrAlpha::rateCalc(DUSK_IF_ELSE(f32, s16) maxTimer, DUSK_IF_ELSE(f32, s16) curTimer, u8 calcType) { if (maxTimer <= curTimer) { return 1.0f; } @@ -105,7 +113,22 @@ f32 CPaneMgrAlpha::getAlphaRate() { return rate; } +#if TARGET_PC +void CPaneMgrAlpha::presentAlphaAnime() { + dusk::vdt::present_animation(mAlphaAnimation, mAlphaTimer, *this, [&](f32 rate) { + setAlpha(mAlphaAnimation.start + rate * (f32)(mAlphaAnimation.end - mAlphaAnimation.start)); + }); +} +#endif + bool CPaneMgrAlpha::alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcType) { +#if TARGET_PC + if (!dusk::vdt::request_animation(mAlphaAnimation, mAlphaTimer, timer, startAlpha, endAlpha, calcType)) { + return false; + } + setAlpha(endAlpha); + return true; +#else if (mAlphaTimer < timer - 1) { mAlphaTimer++; f32 rate = rateCalc(timer, mAlphaTimer, calcType); @@ -117,20 +140,41 @@ bool CPaneMgrAlpha::alphaAnime(s16 timer, u8 startAlpha, u8 endAlpha, u8 calcTyp } return false; +#endif } bool CPaneMgrAlpha::alphaAnimeLoop(s16 param_0, u8 param_1, u8 param_2, u8 param_3) { bool ret = false; s16 temp_r4 = param_0 / 2; +#if TARGET_PC + mAlphaAnimation.active = false; + if (param_0 <= 0) { + mAlphaTimer = 0.0f; + setAlpha(param_1); + return true; + } + const f32 frames = dusk::game_clock::original_frames(); + if (frames <= 0.0f) { + return false; + } + if (mAlphaTimer >= param_0) { + mAlphaTimer = 0.0f; + ret = true; + } else { + ret = mAlphaTimer + frames >= param_0; + dusk::vdt::advance_looping_frame(mAlphaTimer, 1.0f, param_0); + } +#else mAlphaTimer++; if (mAlphaTimer >= param_0) { mAlphaTimer = 0; ret = true; } +#endif - s16 tmp; + DUSK_IF_ELSE(f32, s16) tmp; if (mAlphaTimer < temp_r4) { tmp = mAlphaTimer; } else { diff --git a/src/dusk/interp/lerp.h b/src/dusk/interp/lerp.h index 1f05f070fa..c0820f2439 100644 --- a/src/dusk/interp/lerp.h +++ b/src/dusk/interp/lerp.h @@ -1,5 +1,6 @@ #pragma once +#include "JSystem/JUtility/TColor.h" #include "SSystem/SComponent/c_angle.h" #include "SSystem/SComponent/c_sxyz.h" #include "SSystem/SComponent/c_xyz.h" @@ -49,5 +50,13 @@ inline void lerp(Mtx& out, const Mtx& lhs, const Mtx& rhs, float step) { } } +inline JUtility::TColor lerp(const JUtility::TColor& lhs, const JUtility::TColor& rhs, float step) { + return JUtility::TColor( + lerp(lhs.r, rhs.r, step), + lerp(lhs.g, rhs.g, step), + lerp(lhs.b, rhs.b, step), + lerp(lhs.a, rhs.a, step)); +} + } // namespace dusk::interp #endif diff --git a/src/dusk/interp/user_interface.h b/src/dusk/interp/user_interface.h new file mode 100644 index 0000000000..4d86c2c3f4 --- /dev/null +++ b/src/dusk/interp/user_interface.h @@ -0,0 +1,54 @@ +#pragma once + +#include "dusk/game_clock.h" + +#include + +#include +#include + +namespace dusk::vdt { + +inline void advance_looping_frame(f32& frame, f32 speed, f32 max) { + if (max <= 0.0f) { + return; + } + frame += speed * game_clock::original_frames(); + if (frame >= max) { + frame = fmodf(frame, max); + } +} + +inline void advance_toward_frame(f32& frame, f32 target, f32 speed) { + if (frame == target) { + return; + } + const f32 step = speed * game_clock::original_frames(); + frame = frame < target ? std::min(frame + step, target) : std::max(frame - step, target); +} + +template +bool request_animation(Animation& animation, f32& frame, s16 time, + decltype(animation.start) from, decltype(animation.end) to, u8 curve) { + if (frame < time) { + animation = {true, time, curve, from, to}; + return false; + } + animation.active = false; + frame = time; + return true; +} + +template +void present_animation(Animation& animation, f32& frame, Pane& pane, Apply apply) { + if (!animation.active) { + return; + } + advance_toward_frame(frame, animation.duration, 1.0f); + apply(pane.rateCalc(animation.duration, frame, animation.curve)); + if (frame >= animation.duration) { + animation.active = false; + } +} + +} // namespace dusk::vdt From 115bb6d2ea7ee3445fb311e4dfe2e50401242579 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 16 Aug 2026 16:48:00 -0400 Subject: [PATCH 025/100] Delta time meters --- include/d/actor/d_a_balloon_2D.h | 2 +- include/d/d_meter_button.h | 8 +- include/d/d_meter_map.h | 9 ++- include/d/d_meter_string.h | 7 +- include/d/d_timer.h | 20 ++++- src/d/actor/d_a_balloon_2D.cpp | 45 +++++++---- src/d/d_meter_button.cpp | 127 +++++++++++++++++++++---------- src/d/d_meter_haihai.cpp | 51 ++++++++++--- src/d/d_meter_hakusha.cpp | 20 +++++ src/d/d_meter_map.cpp | 28 +++++-- src/d/d_meter_string.cpp | 20 ++--- src/d/d_timer.cpp | 98 ++++++++++++++++++++---- src/dusk/interp/user_interface.h | 40 ++++++++++ 13 files changed, 364 insertions(+), 111 deletions(-) diff --git a/include/d/actor/d_a_balloon_2D.h b/include/d/actor/d_a_balloon_2D.h index 80aa3e3b02..04d450665c 100644 --- a/include/d/actor/d_a_balloon_2D.h +++ b/include/d/actor/d_a_balloon_2D.h @@ -72,7 +72,7 @@ public: struct ScoreCount { cXyz field_0x0; u16 field_0xc; - u8 field_0xe; + DUSK_IF_ELSE(f32, u8) field_0xe; u8 field_0xf; }; diff --git a/include/d/d_meter_button.h b/include/d/d_meter_button.h index 2d9a4bb5d8..04a2f7f488 100644 --- a/include/d/d_meter_button.h +++ b/include/d/d_meter_button.h @@ -255,7 +255,7 @@ public: /* 0x4B2 */ u16 mMsgID; /* 0x4B4 */ s16 field_0x4b4; /* 0x4B6 */ s16 field_0x4b6; - /* 0x4B8 */ s16 field_0x4b8[2]; + /* 0x4B8 */ DUSK_IF_ELSE(f32, s16) field_0x4b8[2]; /* 0x4BC */ u8 field_0x4bc[2]; /* 0x4BE */ u8 field_0x4be[2]; /* 0x4C0 */ bool mPlayedButtonSound[BUTTON_NUM]; @@ -344,10 +344,8 @@ public: /* 0x624 */ f32 mMidonaPosX; /* 0x628 */ f32 mMidonaPosY; /* 0x62C */ f32 mMidonaScale; - -#ifdef TARGET_PC - bool mWasListen[2]; - bool mWasRepeat[2]; +#if TARGET_PC + u16 mBlinkButton = 0; #endif }; diff --git a/include/d/d_meter_map.h b/include/d/d_meter_map.h index 648fb5aef5..630d50b71d 100644 --- a/include/d/d_meter_map.h +++ b/include/d/d_meter_map.h @@ -4,6 +4,10 @@ #include "d/d_map_path_dmap.h" #include "JSystem/JHostIO/JORReflexible.h" +#if TARGET_PC +#include "global.h" +#endif + class J2DPicture; class dMap_c; class dMap_HIO_c; @@ -111,13 +115,16 @@ public: /* 0x1C */ f32 mDrawPosY; /* 0x20 */ f32 mSizeW; /* 0x24 */ f32 mSizeH; - /* 0x28 */ s16 mSlidePositionOffset; + /* 0x28 */ DUSK_IF_ELSE(f32, s16) mSlidePositionOffset; /* 0x2A */ u8 field_0x2a; /* 0x2B */ u8 field_0x2b; /* 0x2C */ u8 mMapAlpha; /* 0x2D */ u8 mMapIsInside; /* 0x2E */ u8 field_0x2e; /* 0x30 */ int field_0x30; +#if TARGET_PC + f32 mSlidePositionOffsetTarget; +#endif }; #endif /* D_METER_D_METER_MAP_H */ diff --git a/include/d/d_meter_string.h b/include/d/d_meter_string.h index 382fcbf99b..a02daea8d7 100644 --- a/include/d/d_meter_string.h +++ b/include/d/d_meter_string.h @@ -17,7 +17,12 @@ public: virtual int _execute(u32); virtual int _delete(); - f32 acc(s16 param_0, s16 param_1, s16 param_2) { + f32 acc(DUSK_IF_ELSE(f32, s16) param_0, DUSK_IF_ELSE(f32, s16) param_1, DUSK_IF_ELSE(f32, s16) param_2) { +#if TARGET_PC + if (param_0 == param_2) { + return 1.0f; + } +#endif return ((f32)(param_1 - param_2) * (f32)(param_1 - param_2)) / ((f32)(param_0 - param_2) * (f32)(param_0 - param_2)); } diff --git a/include/d/d_timer.h b/include/d/d_timer.h index de145650ac..235b22a981 100644 --- a/include/d/d_timer.h +++ b/include/d/d_timer.h @@ -57,6 +57,9 @@ public: #endif void drawPikari(int); +#if TARGET_PC + void presentAnims(); +#endif virtual void draw(); virtual ~dDlst_TimerScrnDraw_c(); @@ -68,10 +71,15 @@ public: void resetCowID() { mCowID = 0; } bool isVisible() { return mTimerVisible; } - f32 acc(s16 param_0, s16 param_1, s16 param_2) { + f32 acc(DUSK_IF_ELSE(f32, s16) param_0, DUSK_IF_ELSE(f32, s16) param_1, DUSK_IF_ELSE(f32, s16) param_2) { +#if TARGET_PC + if (param_0 == param_2) { + return 1.0f; + } +#endif return ((f32)(param_1 - param_2) * (f32)(param_1 - param_2)) / ((f32)(param_0 - param_2) * (f32)(param_0 - param_2)); } - + void setTimerTrans(f32 x, f32 y) { mTimerTransX = x; mTimerTransY = y; @@ -103,7 +111,7 @@ public: /* 0x3CC */ int field_0x3CC; /* 0x3D0 */ int field_0x3D0; /* 0x3D4 */ int field_0x3D4; - /* 0x3D8 */ int field_0x3D8; + /* 0x3D8 */ DUSK_IF_ELSE(f32, int) field_0x3D8; /* 0x3DC */ u8 mCowID; /* 0x3DD */ u8 mHIOType; /* 0x3DE */ u8 field_0x3DE; @@ -111,6 +119,12 @@ public: /* 0x3E0 */ u8 field_0x3E0; /* 0x3E1 */ u8 mTimerVisible; /* 0x3E2 */ u8 field_0x3e2; +#if TARGET_PC + bool mPresentStep = false; + bool mPresentOpening = false; + bool mPresentClosing = false; + bool mPresentTimerSlide = false; +#endif }; class dTimer_c : public msg_class { diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 5d5923abc3..aa9fb8a163 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -21,7 +21,10 @@ #include #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" + +#include #endif class daBalloon2D_HIO_c : public mDoHIO_entry_c { @@ -270,12 +273,12 @@ int daBalloon2D_c::draw() { int daBalloon2D_c::execute() { +#if TARGET_PC + updateOnWide(); +#else setAllAlpha(); setComboAlpha(); - - #if TARGET_PC - updateOnWide(); - #endif +#endif setHIO(false); return 1; @@ -283,6 +286,10 @@ int daBalloon2D_c::execute() { void daBalloon2D_c::drawMeter() { update(); +#if TARGET_PC + setAllAlpha(); + setComboAlpha(); +#endif mScreen->draw(0.0f, 0.0f, dComIfGp_getCurrentGrafPort()); drawAddScore(); } @@ -344,7 +351,7 @@ void daBalloon2D_c::setComboNum(u8 comboNum) { field_0x5a8[5]->changeTexture(resTimg, 0); resTimg = (ResTIMG*)dComIfGp_getMain2DArchive()->getResource('TIMG', dMeter2Info_getNumberTextureName(comboNum % 10)); field_0x5a8[6]->changeTexture(resTimg, 0); - setComboAlpha(); + IF_NOT_DUSK(setComboAlpha()); } void daBalloon2D_c::setBalloonSize(u8 balloonSize) { @@ -397,10 +404,14 @@ void daBalloon2D_c::setAllAlpha() { mAllAlpha = field_0x578->getAlphaRate(); if (isVisible()) { if (mAllAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mAllAlpha, 1.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mAllAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mAllAlpha - 1.0f) < 0.1f) { mAllAlpha = 1.0f; } +#endif field_0x578->setAlphaRate(mAllAlpha); field_0x580->setAlphaRate(l_HOSTIO.m.m5DNumberAlpha * mAllAlpha); field_0x594->setAlphaRate(l_HOSTIO.m.m2DNumberComboAlpha * mAllAlpha); @@ -409,10 +420,14 @@ void daBalloon2D_c::setAllAlpha() { field_0x5a0->setAlphaRate(l_HOSTIO.m.mBalloonSmallAlpha * mAllAlpha); } } else if (mAllAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mAllAlpha, 0.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mAllAlpha, 0.0f, 0.4f, 0.5f); if (fabsf(mAllAlpha) < 0.1f) { mAllAlpha = 0.0f; } +#endif field_0x578->setAlphaRate(mAllAlpha); field_0x580->setAlphaRate(l_HOSTIO.m.m5DNumberAlpha * mAllAlpha); field_0x594->setAlphaRate(l_HOSTIO.m.m2DNumberComboAlpha * mAllAlpha); @@ -427,10 +442,14 @@ void daBalloon2D_c::setComboAlpha() { mComboAlpha = field_0x5a4->getAlphaRate() * field_0x578->getAlphaRate(); if (mComboNum != 0) { if (mComboAlpha != 1.0f) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mComboAlpha, 1.0f, 0.4f, 0.5f, 0.1f); +#else cLib_addCalc2(&mComboAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mComboAlpha - 1.0f) < 0.1f) { mComboAlpha = 1.0f; } +#endif field_0x5a4->setAlphaRate(mComboAlpha); } } else if (mComboAlpha != 0.0f) { @@ -442,20 +461,20 @@ void daBalloon2D_c::setComboAlpha() { void daBalloon2D_c::drawAddScore() { for (s32 i = 19; i >= 0; i--) { if (field_0x5f8[i].field_0xe != 0) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) - field_0x5f8[i].field_0xe--; - IF_DUSK_BLOCK_END + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x5f8[i].field_0xe, 0.0f, 1.0f), field_0x5f8[i].field_0xe--); s32 score3; s32 score2; s32 score = field_0x5f8[i].field_0xc; +#if TARGET_PC + s16 temp0 = (s16)(fmodf(field_0x5f8[i].field_0xe, 60.0f) * 1024.0f); +#else s16 temp0 = (field_0x5f8[i].field_0xe % 60) * 1024; +#endif u8 local_88 = 0xff; f32 dVar11 = 30.0f; f32 dVar9 = 30.0f; - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) - field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; - field_0x5f8[i].field_0x0.y -= 1.0f; - IF_DUSK_BLOCK_END + field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f IF_DUSK(* dusk::game_clock::original_frames()); + field_0x5f8[i].field_0x0.y -= 1.0f IF_DUSK(* dusk::game_clock::original_frames()); if (field_0x5f8[i].field_0xe < 10) { f32 fVar5 = field_0x5f8[i].field_0xe / 10.0f; local_88 = fVar5 * 255.0f; diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp index 5c4207b2c8..068fcf3b53 100644 --- a/src/d/d_meter_button.cpp +++ b/src/d/d_meter_button.cpp @@ -19,9 +19,29 @@ #include #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #include "helpers/string.hpp" + +namespace { +bool advance_repeat_hit(f32& frame, u8& cycle, f32 period, f32& elapsedFrames) { + const f32 frames = dusk::game_clock::original_frames(); + elapsedFrames = frames; + if (period <= 0.0f || frames <= 0.0f) { + return false; + } + f32 phase = frame + cycle * period; + const bool pulse = phase == 0.0f || phase + frames > 2.0f * period; + dusk::vdt::advance_looping_frame(phase, 1.0f, 2.0f * period); + if (pulse) { + elapsedFrames = phase == 0.0f ? 2.0f * period : phase; + } + cycle = phase >= period; + frame = phase - cycle * period; + return pulse; +} +} // namespace #endif #if TARGET_PC || VERSION == VERSION_GCN_JPN @@ -51,6 +71,10 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra bool i_draw3DB, bool i_drawNURE, bool i_drawReel, bool i_drawReel2, bool i_drawAB, bool i_drawTate, bool i_drawNunZ, bool i_drawNunC, bool i_drawBin) { +#if TARGET_PC + mBlinkButton = g_meter2_info.mBlinkButton; + dMeter2Info_resetBlinkButton(); +#endif updateButton(); updateText(i_flags); field_0x4b0 = 0; @@ -228,6 +252,7 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra } } +#if !TARGET_PC for (int i = 0; i < 2; i++) { if (field_0x2f4[i] != 0.0f) { field_0x2fc[i] = field_0x2f4[i]; @@ -241,6 +266,7 @@ int dMeterButton_c::_execute(u32 i_flags, bool i_drawA, bool i_drawB, bool i_dra trans_button(i, field_0x2fc[i]); } +#endif return 1; } @@ -248,6 +274,23 @@ void dMeterButton_c::draw() { J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); +#if TARGET_PC + if (isButtonShowBit(BUTTON_Z_e) && mpButtonZ->getAlphaTimer() < 5.0f) { + mpButtonZ->setAlphaRate(1.0f); + dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonZ, 5); + } + + for (int i = 0; i < 2; i++) { + if (field_0x2f4[i] != 0.0f) { + field_0x2fc[i] = field_0x2f4[i]; + } else { + dusk::vdt::present_chase(&field_0x2fc[i], field_0x2f4[i], 1.0f, 10.0f, 0.1f); + } + + trans_button(i, field_0x2fc[i]); + } +#endif + mpButtonScreen->draw(0.0f, 0.0f, graf_ctx); if (field_0x00c != NULL) { field_0x00c->draw(0.0f, 0.0f, graf_ctx); @@ -281,20 +324,28 @@ void dMeterButton_c::draw() { SAFE_STRCPY(static_cast(mpTm_c[0]->getPanePtr())->getStringPtr(), tmp_buf); } +#if TARGET_PC + const bool blinkA = (mBlinkButton & 1) != 0; + const bool blinkB = (mBlinkButton & 2) != 0; +#endif + for (int i = 0; i < 2; i++) { bool var_r22 = 0; bool var_r23 = 0; + IF_DUSK(f32 repeatFrames = 0.0f); if (field_0x4be[i] == BUTTON_A_e && dComIfGp_isDoSetFlag(4)) { var_r23 = 1; - if (field_0x4b8[i] == 0 && field_0x4bc[i] == 0) { + if (DUSK_IF_ELSE(advance_repeat_hit(field_0x4b8[i], field_0x4bc[i], g_drawHIO.mEmpButton.mRepeatHitFrameNum, repeatFrames), + field_0x4b8[i] == 0 && field_0x4bc[i] == 0)) + { field_0x2e8[i] = 18.0f - g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed; } s16 temp_r6 = g_drawHIO.mEmpButton.mRepeatHitFrameNum; s16 temp_r6_2 = g_drawHIO.mEmpButton.mRepeatHitFrameNum / 2; - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if !TARGET_PC field_0x4b8[i]++; if (field_0x4b8[i] >= temp_r6) { @@ -306,7 +357,7 @@ void dMeterButton_c::draw() { field_0x4bc[i] = 0; } } - IF_DUSK_BLOCK_END +#endif f32 var_f2; if (temp_r6_2 < field_0x4b8[i]) { @@ -320,9 +371,9 @@ void dMeterButton_c::draw() { } else if ((field_0x4be[i] == BUTTON_A_e && (dComIfGp_getDoStatus() == 0x3B || dComIfGp_getDoStatus() == 0x3F || dComIfGp_getDoStatus() == 0x40) && - dMeter2Info_isBlinkButton(1)) || + DUSK_IF_ELSE(blinkA, dMeter2Info_isBlinkButton(1))) || (field_0x4be[i] == BUTTON_B_e && dComIfGp_getAStatus() == 0x3A && - dMeter2Info_isBlinkButton(2))) + DUSK_IF_ELSE(blinkB, dMeter2Info_isBlinkButton(2)))) { var_r22 = 1; if (field_0x2e8[i] <= 0.0f) { @@ -378,18 +429,8 @@ void dMeterButton_c::draw() { } if (var_r3) { -#if TARGET_PC - if (dusk::interp::get_ui_tick_pending()) { - mWasListen[i] = var_r22; - mWasRepeat[i] = var_r23; - } else { - var_r22 = mWasListen[i]; - var_r23 = mWasRepeat[i]; - } -#endif if (var_r22) { - if (field_0x2e8[i] == 18.0f IF_DUSK(&& dusk::interp::get_ui_tick_pending())) - { + if (field_0x2e8[i] == 18.0f) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); } @@ -419,7 +460,7 @@ void dMeterButton_c::draw() { g_drawHIO.mEmpButton.mPikariRepeatHitFrontInner, g_drawHIO.mEmpButton.mPikariRepeatHitBackOuter, g_drawHIO.mEmpButton.mPikariRepeatHitBackInner, - g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed, 0); + g_drawHIO.mEmpButton.mPikariRepeatHitAnimSpeed, 0 IF_DUSK_ARG(repeatFrames)); } else if (isFastSet(i)) { dMeter2Info_getMeterClass()->getMeterDrawPtr()->drawPikari( vtx.x, vtx.y, &field_0x2e8[i], g_drawHIO.mEmpButton.mPikariFastScale, @@ -441,7 +482,7 @@ void dMeterButton_c::draw() { } } - dMeter2Info_resetBlinkButton(); + IF_NOT_DUSK(dMeter2Info_resetBlinkButton()); } int dMeterButton_c::_delete() { @@ -2075,7 +2116,7 @@ void dMeterButton_c::setAlphaButtonAAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonA, 5); } - if (mpButtonA->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_A_e]) { + if (DUSK_IF_ELSE(true, mpButtonA->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_A_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_A_e] = true; } @@ -2111,7 +2152,7 @@ void dMeterButton_c::setAlphaButtonBAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonB, 5); } - if (mpButtonB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_B_e]) { + if (DUSK_IF_ELSE(true, mpButtonB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_B_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_B_e] = true; } @@ -2147,7 +2188,7 @@ void dMeterButton_c::setAlphaButtonRAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonR, 5); } - if (mpButtonR->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_R_e]) { + if (DUSK_IF_ELSE(true, mpButtonR->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_R_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_R_e] = true; } @@ -2166,7 +2207,7 @@ void dMeterButton_c::setAlphaButtonZAnimeMin() { mpButtonZ->setAlphaRate(0.0f); mpButtonZ->alphaAnimeStart(0); - if (mpButtonZ->getAlphaRate() == 0.0f) { + if (DUSK_IF_ELSE(true, mpButtonZ->getAlphaRate() == 0.0f)) { hide_button(BUTTON_Z_e); mPlayedButtonSound[BUTTON_Z_e] = false; } @@ -2178,12 +2219,14 @@ void dMeterButton_c::setAlphaButtonZAnimeMax() { if (dComIfGp_isZSetFlag(1)) { mpButtonZ->setAlphaRate(1.0f); mpButtonZ->alphaAnimeStart(5); +#if !TARGET_PC } else { mpButtonZ->setAlphaRate(1.0f); dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonZ, 5); +#endif } - if (mpButtonZ->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_Z_e]) { + if (DUSK_IF_ELSE(true, mpButtonZ->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_Z_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_Z_e] = true; } @@ -2220,7 +2263,7 @@ void dMeterButton_c::setAlphaButton3DAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButton3D, 5); } - if (mpButton3D->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_3D_e]) { + if (DUSK_IF_ELSE(true, mpButton3D->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_3D_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_3D_e] = true; } @@ -2264,7 +2307,7 @@ void dMeterButton_c::setAlphaButtonCAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonC, 5); } - if (mpButtonC->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_C_e]) { + if (DUSK_IF_ELSE(true, mpButtonC->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_C_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_C_e] = true; } @@ -2329,7 +2372,7 @@ void dMeterButton_c::setAlphaButtonSAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonS, 5); } - if (mpButtonS->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_S_e]) { + if (DUSK_IF_ELSE(true, mpButtonS->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_S_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_S_e] = true; } @@ -2365,7 +2408,7 @@ void dMeterButton_c::setAlphaButtonXAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonX, 5); } - if (mpButtonX->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_X_e]) { + if (DUSK_IF_ELSE(true, mpButtonX->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_X_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_X_e] = true; } @@ -2401,7 +2444,7 @@ void dMeterButton_c::setAlphaButtonYAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonY, 5); } - if (mpButtonY->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_Y_e]) { + if (DUSK_IF_ELSE(true, mpButtonY->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_Y_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_Y_e] = true; } @@ -2440,7 +2483,7 @@ void dMeterButton_c::setAlphaButtonNunAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNun, 5); } - if (mpButtonNun->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUN_e]) { + if (DUSK_IF_ELSE(true, mpButtonNun->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUN_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUN_e] = true; } @@ -2480,7 +2523,7 @@ void dMeterButton_c::setAlphaButtonRemoAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonRemo, 5); } - if (mpButtonRemo->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REMO_e]) { + if (DUSK_IF_ELSE(true, mpButtonRemo->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REMO_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REMO_e] = true; } @@ -2520,7 +2563,7 @@ void dMeterButton_c::setAlphaButtonRemo2AnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonRemo2, 5); } - if (mpButtonRemo2->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REMO2_e]) { + if (DUSK_IF_ELSE(true, mpButtonRemo2->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REMO2_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REMO2_e] = true; } @@ -2562,7 +2605,7 @@ void dMeterButton_c::setAlphaButtonARAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonAR, 5); } - if (mpButtonAR->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_AR_e]) { + if (DUSK_IF_ELSE(true, mpButtonAR->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_AR_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_AR_e] = true; } @@ -2604,7 +2647,7 @@ void dMeterButton_c::setAlphaButton3DBAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButton3DB, 5); } - if (mpButton3DB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_3DB_e]) { + if (DUSK_IF_ELSE(true, mpButton3DB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_3DB_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_3DB_e] = true; } @@ -2646,7 +2689,7 @@ void dMeterButton_c::setAlphaButtonNUREAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNURE, 5); } - if (mpButtonNURE->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NURE_e]) { + if (DUSK_IF_ELSE(true, mpButtonNURE->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NURE_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NURE_e] = true; } @@ -2688,7 +2731,7 @@ void dMeterButton_c::setAlphaButtonReelAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonReel, 5); } - if (mpButtonReel->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REEL_e]) { + if (DUSK_IF_ELSE(true, mpButtonReel->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REEL_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REEL_e] = true; } @@ -2720,7 +2763,7 @@ void dMeterButton_c::setAlphaButtonReel2AnimeMax() { mpButtonReel2->setAlphaRate(1.0f); mpButtonReel2->alphaAnimeStart(5); - if (mpButtonReel2->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_REEL2_e]) { + if (DUSK_IF_ELSE(true, mpButtonReel2->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_REEL2_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_REEL2_e] = true; } @@ -2752,7 +2795,7 @@ void dMeterButton_c::setAlphaButtonABAnimeMax() { mpButtonAB->setAlphaRate(1.0f); mpButtonAB->alphaAnimeStart(5); - if (mpButtonAB->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_AB_e]) { + if (DUSK_IF_ELSE(true, mpButtonAB->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_AB_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_AB_e] = true; } @@ -2784,7 +2827,7 @@ void dMeterButton_c::setAlphaButtonTateAnimeMax() { mpButtonTate->setAlphaRate(1.0f); mpButtonTate->alphaAnimeStart(5); - if (mpButtonTate->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_TATE_e]) { + if (DUSK_IF_ELSE(true, mpButtonTate->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_TATE_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_TATE_e] = true; } @@ -2826,7 +2869,7 @@ void dMeterButton_c::setAlphaButtonNunZAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNunZ, 5); } - if (mpButtonNunZ->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUNZ_e]) { + if (DUSK_IF_ELSE(true, mpButtonNunZ->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUNZ_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUNZ_e] = true; } @@ -2868,7 +2911,7 @@ void dMeterButton_c::setAlphaButtonNunCAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonNunC, 5); } - if (mpButtonNunC->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_NUNC_e]) { + if (DUSK_IF_ELSE(true, mpButtonNunC->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_NUNC_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_NUNC_e] = true; } @@ -2910,7 +2953,7 @@ void dMeterButton_c::setAlphaButtonBinAnimeMax() { dMeter2Info_getMeterClass()->getMeterDrawPtr()->setAlphaAnimeMax(mpButtonBin, 5); } - if (mpButtonBin->getAlphaRate() == 1.0f && !mPlayedButtonSound[BUTTON_BIN_e]) { + if (DUSK_IF_ELSE(true, mpButtonBin->getAlphaRate() == 1.0f) && !mPlayedButtonSound[BUTTON_BIN_e]) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON, NULL, 0, 0); mPlayedButtonSound[BUTTON_BIN_e] = true; } diff --git a/src/d/d_meter_haihai.cpp b/src/d/d_meter_haihai.cpp index 59a69ed61e..6eadfca9d9 100644 --- a/src/d/d_meter_haihai.cpp +++ b/src/d/d_meter_haihai.cpp @@ -13,7 +13,7 @@ #include "d/d_pane_class.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #endif dMeterHaihai_c::dMeterHaihai_c(u8 i_type) { @@ -59,12 +59,13 @@ int dMeterHaihai_c::_create() { int dMeterHaihai_c::_execute(u32 i_flags) { mFlags = i_flags; - updateHaihai(); + DUSK_IF_ELSE(setScale(1.0f), updateHaihai()); alphaAnimeHaihai(i_flags); return 1; } void dMeterHaihai_c::draw() { + IF_DUSK(updateHaihai()); if ((mFlags & 8) || (mFlags & 0x10) || (mFlags & 0x1000000) || (mFlags & 0x800000) || (mFlags & 0x20) || (mFlags & 0x4000000)) { @@ -132,6 +133,7 @@ void dMeterHaihai_c::draw() { } void dMeterHaihai_c::drawHaihai(u8 i_direction) { + IF_DUSK(updateHaihai()); f32 center_x = FB_WIDTH_BASE / 2; f32 center_y = FB_HEIGHT_BASE / 2; @@ -187,6 +189,7 @@ void dMeterHaihai_c::drawHaihai(u8 i_direction) { } void dMeterHaihai_c::drawHaihai(u8 i_direction, f32 i_posX, f32 i_posY, f32 param_3, f32 param_4) { + IF_DUSK(updateHaihai()); if (mType == 1) { i_posX += (3.0f + g_drawHIO.mScrollArrowCenterPosX); i_posY += (g_drawHIO.mScrollArrowCenterPosY - 5.0f); @@ -284,13 +287,21 @@ void dMeterHaihai_c::updateHaihai() { playBtkAnime(mpCursorBtk); playBpkAnime(mpCursorBpk); mpHaihaiScreen->animation(); - setScale(1.0f); + IF_NOT_DUSK(setScale(1.0f)); } void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { if (checkPlayAnime(1)) { if (i_bck != NULL) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBCKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBCKAnimSpeed; + } + dusk::vdt::present_looping(mBckFrame, i_bck, speed); +#else if (mType == 4) { mBckFrame += g_drawHIO.mWiiLockArrowBCKAnimSpeed; } else { @@ -300,12 +311,12 @@ void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { if (mBckFrame >= i_bck->getFrameMax()) { mBckFrame -= i_bck->getFrameMax(); } - IF_DUSK_BLOCK_END +#endif } else { mBtkFrame = 1.0f; } - i_bck->setFrame(mBckFrame); + IF_NOT_DUSK(i_bck->setFrame(mBckFrame)); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->setAnimation(i_bck); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->animationTransform(); mpHaihaiScreen->search(MULTI_CHAR('n_anim'))->setAnimation((J2DAnmTransform*)NULL); @@ -315,7 +326,15 @@ void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { if (checkPlayAnime(2)) { if (i_btk != NULL) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBTKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBTKAnimSpeed; + } + dusk::vdt::present_looping(mBtkFrame, i_btk, speed); +#else if (mType == 4) { mBtkFrame += g_drawHIO.mWiiLockArrowBTKAnimSpeed; } else { @@ -325,12 +344,12 @@ void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { if (mBtkFrame >= i_btk->getFrameMax()) { mBtkFrame -= i_btk->getFrameMax(); } - IF_DUSK_BLOCK_END +#endif } else { mBtkFrame = 1.0f; } - i_btk->setFrame(mBtkFrame); + IF_NOT_DUSK(i_btk->setFrame(mBtkFrame)); mpHaihaiScreen->search(MULTI_CHAR('yaji00'))->setAnimation(i_btk); mpHaihaiScreen->search(MULTI_CHAR('yaji01'))->setAnimation(i_btk); } @@ -339,7 +358,15 @@ void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { void dMeterHaihai_c::playBpkAnime(J2DAnmColor* i_bpk) { if (checkPlayAnime(0)) { if (i_bpk != NULL) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + f32 speed; + if (mType == 4) { + speed = g_drawHIO.mWiiLockArrowBPKAnimSpeed; + } else { + speed = g_drawHIO.mScrollArrowBPKAnimSpeed; + } + dusk::vdt::present_looping(mBpkFrame, i_bpk, speed); +#else if (mType == 4) { mBpkFrame += g_drawHIO.mWiiLockArrowBPKAnimSpeed; } else { @@ -349,12 +376,12 @@ void dMeterHaihai_c::playBpkAnime(J2DAnmColor* i_bpk) { if (mBpkFrame >= i_bpk->getFrameMax()) { mBpkFrame -= i_bpk->getFrameMax(); } - IF_DUSK_BLOCK_END +#endif } else { mBpkFrame = 1.0f; } - i_bpk->setFrame(mBpkFrame); + IF_NOT_DUSK(i_bpk->setFrame(mBpkFrame)); mpHaihaiScreen->search(MULTI_CHAR('npc_l1'))->setAnimation(i_bpk); mpHaihaiScreen->search(MULTI_CHAR('yaji_l'))->setAnimation(i_bpk); } diff --git a/src/d/d_meter_hakusha.cpp b/src/d/d_meter_hakusha.cpp index aa03e1755a..a9fc1421ad 100644 --- a/src/d/d_meter_hakusha.cpp +++ b/src/d/d_meter_hakusha.cpp @@ -15,6 +15,10 @@ #include "d/d_pane_class.h" #include +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + dMeterHakusha_c::dMeterHakusha_c(void* i_screen) { field_0x004 = (J2DScreen*)i_screen; _create(); @@ -115,6 +119,20 @@ void dMeterHakusha_c::draw() { mpButtonScreen->draw(0.0f, 0.0f, graf_ctx); for (int i = 0; i < getHakushaNum(); i++) { +#if TARGET_PC + if (mHakushaAnimFrame[i] > 0.0f) { + if (mHakushaStatus[i] == 0) { + dusk::vdt::advance_toward_frame(mHakushaAnimFrame[i], 28.0f, g_drawHIO.mSpurIconPikariAnimSpeed); + } else { + dusk::vdt::advance_toward_frame(mHakushaAnimFrame[i], 28.0f, g_drawHIO.mSpurIconRevivePikariAnimSpeed); + } + + if (mHakushaAnimFrame[i] >= 28.0f) { + mHakushaAnimFrame[i] = 0.0f; + } + } +#endif + if (mHakushaData[i].flags & 1) { mpHakushaOn->show(); } else { @@ -226,6 +244,7 @@ void dMeterHakusha_c::updateHakusha() { } for (int i = 0; i < getHakushaNum(); i++) { +#if !TARGET_PC if (mHakushaAnimFrame[i] > 0.0f) { if (mHakushaStatus[i] == 0) { mHakushaAnimFrame[i] += g_drawHIO.mSpurIconPikariAnimSpeed; @@ -237,6 +256,7 @@ void dMeterHakusha_c::updateHakusha() { mHakushaAnimFrame[i] = 0.0f; } } +#endif mHakushaData[i].pos_x = abtn_x_offset; mHakushaData[i].pos_y = abtn_y_offset; diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 6349648862..110d35b3cb 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -16,15 +16,16 @@ #include "f_op/f_op_overlap_mng.h" #include "m_Do/m_Do_controller_pad.h" #include "d/d_camera.h" -#if TARGET_PC -#include "JSystem/JUtility/JUTPalette.h" -#include "dusk/settings.h" -#include -#endif #include #if TARGET_PC #include "dusk/action_bindings.h" +#include "dusk/interp/user_interface.h" +#include "dusk/settings.h" + +#include "JSystem/JUtility/JUTPalette.h" + +#include namespace { @@ -490,15 +491,18 @@ void dMeterMap_c::_create(J2DScreen* unused) { if (mMapIsInside != 0) { mMapIsInside = 1; mSlidePositionOffset = getDispPosInside_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); dMeter2Info_setMapStatus(1); } else { mMapIsInside = 0; mSlidePositionOffset = getDispPosOutSide_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); dMeter2Info_setMapStatus(0); } field_0x2e = 0; mSlidePositionOffset = 0; + IF_DUSK(mSlidePositionOffsetTarget = 0); field_0x30 = 0; /* dSv_event_flag_c::M_085 - Twilight Hyrule Field - Midna dialogue right before Boss Bug's Tear of Light appears */ field_0x2b = dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[118]); @@ -507,11 +511,13 @@ void dMeterMap_c::_create(J2DScreen* unused) { void dMeterMap_c::setDispPosOutSide() { mMapIsInside = 0; mSlidePositionOffset = getDispPosOutSide_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); } void dMeterMap_c::setDispPosInSide() { mMapIsInside = 1; mSlidePositionOffset = getDispPosInside_OffsetX(); + IF_DUSK(mSlidePositionOffsetTarget = mSlidePositionOffset); } void dMeterMap_c::_delete() { @@ -573,6 +579,8 @@ void dMeterMap_c::_move(u32 param_0) { } if (mMapIsInside != 0) { + IF_DUSK(mSlidePositionOffsetTarget = getDispPosInside_OffsetX()); +#if !TARGET_PC if (mSlidePositionOffset != getDispPosInside_OffsetX()) { if (!cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosInside_OffsetX(), 2, 60, 10)) { #if DEBUG @@ -580,8 +588,10 @@ void dMeterMap_c::_move(u32 param_0) { #endif } } +#endif } else { - cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosOutSide_OffsetX(), 2, 60, 10); + IF_DUSK(mSlidePositionOffsetTarget = getDispPosOutSide_OffsetX()); + IF_NOT_DUSK(cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosOutSide_OffsetX(), 2, 60, 10)); } Vec map_pos = dMapInfo_n::getMapPlayerPos(); @@ -636,6 +646,12 @@ void dMeterMap_c::draw() { f32 drawPosX = mDrawPosX; f32 drawPosY = mDrawPosY; +#if TARGET_PC + dusk::vdt::present_addCalc(&mSlidePositionOffset, mSlidePositionOffsetTarget, 0.5f, 60.0f, 10.0f); + mDrawPosX = mSlidePositionOffset + getMapDispEdgeLeftX_Layout(); + drawPosX = mDrawPosX; +#endif + u8 alpha = mMapAlpha; #if DEBUG if (g_meter_mapHIO.mMapAlphaTest) { diff --git a/src/d/d_meter_string.cpp b/src/d/d_meter_string.cpp index 3bc5795dc0..0e19fa1c04 100644 --- a/src/d/d_meter_string.cpp +++ b/src/d/d_meter_string.cpp @@ -19,7 +19,7 @@ #include #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #endif dMeterString_c::dMeterString_c(int i_stringID) { @@ -109,22 +109,20 @@ void dMeterString_c::draw() { f32 var_f30 = 1.0f; if (mAnimFrame < 60.0f) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_toward_frame(mAnimFrame, 60.0f, g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed); +#else mAnimFrame += g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed; if (mAnimFrame > 60.0f) { mAnimFrame = 60.0f; } - IF_DUSK_BLOCK_END +#endif playBckAnimation(mAnimFrame); } else if (mAnimFrame < (f32)g_drawHIO.mMiniGame.mReadyFightTextWaitFrames + 60.0f) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) - mAnimFrame += var_f30; - IF_DUSK_BLOCK_END + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(mAnimFrame, (f32)g_drawHIO.mMiniGame.mReadyFightTextWaitFrames + 60.0f, 1.0f), mAnimFrame += var_f30); } else if (mAnimFrame < var_f31) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) - mAnimFrame += var_f30; - IF_DUSK_BLOCK_END + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(mAnimFrame, var_f31, 1.0f), mAnimFrame += var_f30); var_f30 = acc(g_drawHIO.mMiniGame.field_0x172, var_f31 - mAnimFrame, 0); } @@ -137,17 +135,15 @@ void dMeterString_c::draw() { if (mPikariAnimFrame > 0.0f) { drawPikari(); - } else if (mPikariAnimFrame == -1.0f IF_DUSK(&& dusk::interp::get_ui_tick_pending()) && + } else if (mPikariAnimFrame == -1.0f && mAnimFrame > g_drawHIO.mMiniGame.mReadyFightPikariAppearFrames) { mPikariAnimFrame = 18.0f - g_drawHIO.mMiniGame.mReadyFightPikariAnimSpeed; } - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) if (mAnimFrame >= var_f31) { dMeter2Info_resetMeterString(); } - IF_DUSK_BLOCK_END } } } diff --git a/src/d/d_timer.cpp b/src/d/d_timer.cpp index df1a3ef104..8b138d507c 100644 --- a/src/d/d_timer.cpp +++ b/src/d/d_timer.cpp @@ -24,7 +24,7 @@ #include #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #endif @@ -215,6 +215,9 @@ int dTimer_c::_execute() { } if (m_timer_mode == 3 || m_timer_mode == 4 || m_timer_mode == 6 || m_timer_mode == 5) { +#if TARGET_PC + mp_tm_scrn->mPresentTimerSlide = true; +#else f32 pos_y = mp_tm_scrn->getTimerTransY(); if (pos_y < 145.0f) { pos_y += 25.0f; @@ -224,6 +227,7 @@ int dTimer_c::_execute() { mp_tm_scrn->setTimerTrans(0.0f, pos_y); } +#endif } break; default: @@ -883,6 +887,7 @@ void dDlst_TimerScrnDraw_c::setTimer(int i_time) { } } +#if !TARGET_PC if (mpTimeParent != NULL) { if ((dComIfGp_event_getMode() == 1 && field_0x3DF != 0) || !isVisible()) { s16 alpha_time = mpTimeParent->getAlphaTimer(); @@ -902,6 +907,7 @@ void dDlst_TimerScrnDraw_c::setTimer(int i_time) { } } } +#endif } void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { @@ -1006,6 +1012,7 @@ void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { } } +#if !TARGET_PC if ((dComIfGp_event_getMode() == 1 && field_0x3E0 != 0) || !isVisible()) { if (mpCowParent != NULL) { s16 alpha_time = mpCowParent->getAlphaTimer(); @@ -1055,6 +1062,7 @@ void dDlst_TimerScrnDraw_c::setCounter(u8 i_count, u8 i_max) { } } } +#endif } void dDlst_TimerScrnDraw_c::setParentPos(f32 i_posX, f32 i_posY) { @@ -1112,17 +1120,24 @@ void dDlst_TimerScrnDraw_c::setShowType(u8 i_type) { } void dDlst_TimerScrnDraw_c::anime() { +#if TARGET_PC + if (!mPresentStep) { + mPresentOpening = true; + return; + } + const f32 previousFrame = field_0x3D8; +#endif static const s16 animeFrame[] = {7, 15, 22}; if (field_0x3DE == 0) { if (field_0x3D8 <= animeFrame[2]) { - field_0x3D8++; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x3D8, 23.0f, 1.0f), field_0x3D8++); } else { field_0x3DE = 1; } - if (field_0x3D8 <= animeFrame[1]) { - f32 temp_f31 = acc(animeFrame[1], field_0x3D8, 0); + if (DUSK_IF_ELSE(previousFrame, field_0x3D8) <= animeFrame[1]) { + f32 temp_f31 = acc(animeFrame[1], DUSK_IF_ELSE(std::min(field_0x3D8, (f32)animeFrame[1]), field_0x3D8), 0); f32 temp_f1 = (1.0f - temp_f31) * -50.0f; if (mpTimeParent != NULL) { @@ -1146,13 +1161,13 @@ void dDlst_TimerScrnDraw_c::anime() { } } - if (field_0x3D8 == animeFrame[1]) { + if (field_0x3D8 DUSK_IF_ELSE(>=, ==) animeFrame[1]) { field_0x3DF = 1; } } - if (field_0x3D8 > animeFrame[0] && field_0x3D8 <= animeFrame[2]) { - f32 temp_f31 = acc(animeFrame[1], field_0x3D8 - animeFrame[0], 0); + if (field_0x3D8 > animeFrame[0] && DUSK_IF_ELSE(previousFrame, field_0x3D8) <= animeFrame[2]) { + f32 temp_f31 = acc(animeFrame[1], DUSK_IF_ELSE(std::min(field_0x3D8, (f32)animeFrame[2]), field_0x3D8) - animeFrame[0], 0); f32 temp_f1 = (1.0f - temp_f31) * -50.0f; if (mpCowParent != NULL) { @@ -1173,7 +1188,7 @@ void dDlst_TimerScrnDraw_c::anime() { g_drawHIO.mMiniGame.mIconSizeY[mHIOType]); } - if (field_0x3D8 == animeFrame[2]) { + if (field_0x3D8 DUSK_IF_ELSE(>=, ==) animeFrame[2]) { field_0x3E0 = 1; } } @@ -1181,8 +1196,14 @@ void dDlst_TimerScrnDraw_c::anime() { } BOOL dDlst_TimerScrnDraw_c::closeAnime() { +#if TARGET_PC + if (!mPresentStep) { + mPresentClosing = true; + return field_0x3D8 >= 7.0f; + } +#endif BOOL var_r31 = false; - field_0x3D8++; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x3D8, 7.0f, 1.0f), field_0x3D8++); if (field_0x3D8 <= 7) { f32 temp_f31 = acc(7, field_0x3D8, 0); @@ -1332,7 +1353,54 @@ s32 dDlst_TimerScrnDraw_c::createStart(u16 i_messageID) { return 0; } +#if TARGET_PC +void dDlst_TimerScrnDraw_c::presentAnims() { + mPresentStep = true; + if (mPresentTimerSlide) { + dusk::vdt::advance_toward_frame(mTimerTransY, 145.0f, 25.0f); + if (field_0x3e2 != 0 && mHIOType == 0) { + setTimerPos(g_drawHIO.mMiniGame.mTimerPosX_4x3, + g_drawHIO.mMiniGame.mTimerPosY_4x3); + } else { + setTimerPos(g_drawHIO.mMiniGame.mTimerPosX[mHIOType], + g_drawHIO.mMiniGame.mTimerPosY[mHIOType]); + } + } + if (mPresentClosing) { + closeAnime(); + } else { + if (mPresentOpening) { + anime(); + } + const bool event = dComIfGp_event_getMode() == 1; + auto fade = [&](CPaneMgr* pane, bool hidden, bool ready, f32 alpha, bool timer) { + if (pane == NULL) { + return; + } + f32 frame = pane->getAlphaTimer(); + const f32 previous = frame; + dusk::vdt::advance_toward_frame(frame, hidden ? 5.0f : 0.0f, 1.0f); + if (ready || frame != previous) { + const f32 rate = timer ? acc(5.0f, 5.0f - frame, 0.0f) : + 1.0f - acc(5.0f, frame, 0.0f); + pane->setAlphaRate(mParentAlpha * alpha * rate); + } + pane->alphaAnimeStart(frame); + }; + fade(mpTimeParent, (event && field_0x3DF != 0) || !isVisible(), field_0x3DF != 0, + g_drawHIO.mMiniGame.mTimerAlpha[mHIOType], true); + const bool hideCounter = (event && field_0x3E0 != 0) || !isVisible(); + fade(mpCowParent, hideCounter, field_0x3E0 != 0, + g_drawHIO.mMiniGame.mCounterAlpha[mHIOType], false); + fade(mpImageParent, hideCounter, field_0x3E0 != 0, + g_drawHIO.mMiniGame.mIconAlpha[mHIOType], false); + } + mPresentStep = false; +} +#endif + void dDlst_TimerScrnDraw_c::draw() { + IF_DUSK(presentAnims()); J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); @@ -1342,20 +1410,22 @@ void dDlst_TimerScrnDraw_c::draw() { ((f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f); for (int i = 0; i < 51; i++) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { if (m_getin_info[i].bck_frame < 60.0f) { +#if TARGET_PC + dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, 60.0f, g_drawHIO.mMiniGame.mGetInTextAnimSpeed); +#else m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; if (m_getin_info[i].bck_frame > 60.0f) { m_getin_info[i].bck_frame = 60.0f; } +#endif } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { - m_getin_info[i].bck_frame++; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, (f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f, 1.0f), m_getin_info[i].bck_frame++); } else if (m_getin_info[i].bck_frame < temp) { - m_getin_info[i].bck_frame++; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(m_getin_info[i].bck_frame, temp, 1.0f), m_getin_info[i].bck_frame++); } } - IF_DUSK_BLOCK_END if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { f32 var_f29 = 1.0f; @@ -1395,7 +1465,6 @@ void dDlst_TimerScrnDraw_c::draw() { if (m_getin_info[i].pikari_frame > 0.0f) { drawPikari(i); } else if (m_getin_info[i].pikari_frame == -1.0f) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) if (m_getin_info[i].field_0xc == 0) { if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mGetInPikariAppearFrames) { m_getin_info[i].pikari_frame = @@ -1405,7 +1474,6 @@ void dDlst_TimerScrnDraw_c::draw() { m_getin_info[i].pikari_frame = 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; } - IF_DUSK_BLOCK_END } } } diff --git a/src/dusk/interp/user_interface.h b/src/dusk/interp/user_interface.h index 4d86c2c3f4..8e86fda556 100644 --- a/src/dusk/interp/user_interface.h +++ b/src/dusk/interp/user_interface.h @@ -2,6 +2,9 @@ #include "dusk/game_clock.h" +#include "JSystem/J2DGraph/J2DAnimation.h" +#include "SSystem/SComponent/c_lib.h" + #include #include @@ -51,4 +54,41 @@ void present_animation(Animation& animation, f32& frame, Pane& pane, Apply apply } } +inline void present_looping(f32& frame, J2DAnmBase* anm, f32 speed) { + if (anm == nullptr) { + return; + } + advance_looping_frame(frame, speed, anm->getFrameMax()); + anm->setFrame(frame); +} + +inline void present_addCalc(f32* value, f32 target, f32 scale, f32 maxStep, f32 minStep) { + const f32 frames = game_clock::original_frames(); + if (*value == target || frames <= 0.0f) { + return; + } + const f32 timedScale = frames == 1.0f ? scale : 1.0f - std::pow(1.0f - scale, frames); + cLib_addCalc(value, target, timedScale, maxStep * frames, minStep * frames); +} + +inline void present_addCalc2(f32* value, f32 target, f32 scale, f32 maxStep, f32 snap) { + const f32 frames = game_clock::original_frames(); + if (*value == target || frames <= 0.0f) { + return; + } + const f32 timedScale = frames == 1.0f ? scale : 1.0f - std::pow(1.0f - scale, frames); + cLib_addCalc2(value, target, timedScale, maxStep * frames); + if (fabsf(*value - target) < snap) { + *value = target; + } +} + +inline bool present_chase(f32* value, f32 target, f32 scale, f32 maxStep, f32 snap) { + if (*value == target) { + return false; + } + present_addCalc2(value, target, scale, maxStep, snap); + return true; +} + } // namespace dusk::vdt From 8e844cf93b0c29e0741e8374bf272ac2507eec60 Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 17 Aug 2026 21:36:00 -0400 Subject: [PATCH 026/100] Delta time menus --- include/d/d_file_sel_warning.h | 5 +- include/d/d_file_select.h | 66 ++++---- include/d/d_menu_collect.h | 3 + include/d/d_menu_dmap.h | 16 +- include/d/d_menu_dmap_map.h | 8 +- include/d/d_menu_fmap.h | 9 +- include/d/d_menu_fmap2D.h | 7 +- include/d/d_menu_fmap_map.h | 12 +- include/d/d_menu_letter.h | 3 +- include/d/d_menu_map_common.h | 4 +- include/d/d_menu_option.h | 10 +- include/d/d_menu_ring.h | 15 +- include/d/d_menu_save.h | 28 ++-- include/d/d_menu_skill.h | 3 +- include/d/d_name.h | 8 +- src/d/d_file_sel_warning.cpp | 20 +++ src/d/d_file_select.cpp | 278 ++++++++++++++++++++++++++++--- src/d/d_gameover.cpp | 6 + src/d/d_menu_collect.cpp | 23 ++- src/d/d_menu_dmap.cpp | 195 ++++++++++++++++++++-- src/d/d_menu_dmap_map.cpp | 40 +++-- src/d/d_menu_fmap.cpp | 125 ++++++++++++-- src/d/d_menu_fmap2D.cpp | 65 +++++--- src/d/d_menu_fmap_map.cpp | 61 ++++++- src/d/d_menu_insect.cpp | 7 + src/d/d_menu_item_explain.cpp | 4 +- src/d/d_menu_letter.cpp | 53 +++++- src/d/d_menu_map_common.cpp | 20 +++ src/d/d_menu_option.cpp | 143 ++++++++++++++-- src/d/d_menu_ring.cpp | 142 +++++----------- src/d/d_menu_save.cpp | 117 +++++++++++-- src/d/d_menu_skill.cpp | 26 ++- src/d/d_menu_window.cpp | 8 + src/d/d_name.cpp | 26 ++- src/d/d_s_name.cpp | 5 + src/d/d_select_icon.cpp | 11 +- src/dusk/interp/lerp.h | 4 + src/dusk/interp/menus.h | 53 ++++++ src/dusk/interp/user_interface.h | 64 +++++++ 39 files changed, 1359 insertions(+), 334 deletions(-) create mode 100644 src/dusk/interp/menus.h diff --git a/include/d/d_file_sel_warning.h b/include/d/d_file_sel_warning.h index c8740b5f74..b4a3790688 100644 --- a/include/d/d_file_sel_warning.h +++ b/include/d/d_file_sel_warning.h @@ -35,6 +35,9 @@ public: void drawSelf(); void setText(u32); void setFontColor(JUtility::TColor, JUtility::TColor); +#if TARGET_PC + void presentAnims(); +#endif virtual ~dFile_warning_c(); @@ -47,7 +50,7 @@ public: /* 0x1C */ CPaneMgr* mpRootPane; /* 0x20 */ J2DTextBox* field_0x20; /* 0x24 */ J2DAnmTransform* field_0x24; - /* 0x28 */ int field_0x28; + /* 0x28 */ DUSK_IF_ELSE(f32, int) field_0x28; /* 0x2C */ int field_0x2c; /* 0x30 */ u8 field_0x30[4]; /* 0x34 */ f32 field_0x34; diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index dd4d5a3aae..c68f68db5d 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -385,7 +385,7 @@ public: void menuCursorShow(); void yesnoWakuAlpahAnmInit(u8, u8, u8, u8); bool yesnoWakuAlpahAnm(u8); - #if TARGET_PC +#if TARGET_PC void fileSelectWide(); bool pointerDataSelect(); bool pointerMenuSelect(); @@ -398,7 +398,10 @@ public: modoruTxtDispAnmInit(0); mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE; } - #endif + void presentLayoutAnims(); + void presentAnims(); + void presentNameInputFade(); +#endif void _draw(); void errorMoveAnmInitSet(int, int); bool errorMoveAnm(); @@ -523,7 +526,7 @@ public: /* 0x00A0 */ s32 field_0x00a0; /* 0x00A4 */ CPaneMgr* mBaseMovePane; /* 0x00A8 */ u8 field_0x00a8[0xb0-0xa8]; - /* 0x00B0 */ s32 mBaseMoveAnmFrame; + /* 0x00B0 */ DUSK_IF_ELSE(f32, s32) mBaseMoveAnmFrame; /* 0x00B4 */ s32 mBaseMoveAnmFrameMax; /* 0x00B8 */ u8 field_0x00b8; /* 0x00B9 */ u8 field_0x00b9; @@ -532,26 +535,26 @@ public: /* 0x00BC */ CPaneMgr* mSelFilePanes[3]; /* 0x00C8 */ f32 field_0x00c8[3]; /* 0x00D4 */ f32 field_0x00d4[3]; - /* 0x00E0 */ s32 field_0x00e0[3]; + /* 0x00E0 */ DUSK_IF_ELSE(f32, s32) field_0x00e0[3]; /* 0x00EC */ s32 field_0x00ec; /* 0x00F0 */ CPaneMgr* mYnSelPane[2]; - /* 0x00F8 */ s32 field_0x00f8[2]; - /* 0x0100 */ s32 field_0x0100; + /* 0x00F8 */ DUSK_IF_ELSE(f32, s32) field_0x00f8[2]; + /* 0x0100 */ DUSK_IF_ELSE(f32, s32) field_0x0100; /* 0x0104 */ s32 field_0x0104; /* 0x0108 */ bool field_0x0108; /* 0x0109 */ bool field_0x0109; /* 0x010A */ u8 field_0x10a[2]; /* 0x010C */ J2DPane* mBaseSubPane; - /* 0x0110 */ int field_0x0110; + /* 0x0110 */ DUSK_IF_ELSE(f32, int) field_0x0110; /* 0x0114 */ int field_0x0114; /* 0x0118 */ J2DPane* m3mMenuPane; /* 0x011C */ J2DPane* mNameBasePane; - /* 0x0120 */ int field_0x0120; + /* 0x0120 */ DUSK_IF_ELSE(f32, int) field_0x0120; /* 0x0124 */ int field_0x0124; /* 0x0128 */ bool field_0x0128; /* 0x0129 */ u8 field_0x0129[0x012C - 0x0129]; /* 0x012C */ J2DPane* mErrorMsgPane; - /* 0x0130 */ int field_0x0130; + /* 0x0130 */ DUSK_IF_ELSE(f32, int) field_0x0130; /* 0x0134 */ int field_0x0134; /* 0x0138 */ CPaneMgrAlpha* mErrorMsgTxtPane[2]; /* 0x0140 */ TEXT_SPAN mErrorMsgStringPtr[2]; @@ -578,24 +581,24 @@ public: /* 0x01BE */ u8 field_0x01be[2]; /* 0x01C0 */ CPaneMgr* mYnSelTxtPane[2]; /* 0x01C8 */ J2DAnmColor* mFileSelBpk; - /* 0x01CC */ s32 mSelFileBpkFrame; + /* 0x01CC */ DUSK_IF_ELSE(f32, s32) mSelFileBpkFrame; /* 0x01D0 */ J2DAnmTextureSRTKey* mFileSel05Btk; - /* 0x01D4 */ s32 mSelFileBtk05Frame; + /* 0x01D4 */ DUSK_IF_ELSE(f32, s32) mSelFileBtk05Frame; /* 0x01D8 */ J2DAnmColor* mSelFileBookBpk; - /* 0x01DC */ s32 mSelFileBookBpkFrame; + /* 0x01DC */ DUSK_IF_ELSE(f32, s32) mSelFileBookBpkFrame; /* 0x01E0 */ J2DAnmTextureSRTKey* mSelFileBookBtk; - /* 0x01E4 */ s32 mSelFileBookBtkFrame; + /* 0x01E4 */ DUSK_IF_ELSE(f32, s32) mSelFileBookBtkFrame; /* 0x01E8 */ J2DAnmTevRegKey* mSelFileBookBrk; - /* 0x01EC */ s32 mSelFileBookBrkFrame; + /* 0x01EC */ DUSK_IF_ELSE(f32, s32) mSelFileBookBrkFrame; /* 0x01F0 */ J2DAnmTextureSRTKey* mDtEffBtk; - /* 0x01F4 */ s32 mDtEffBtkFrame; + /* 0x01F4 */ DUSK_IF_ELSE(f32, s32) mDtEffBtkFrame; /* 0x01F8 */ J2DAnmTextureSRTKey* mCpEffBtk; - /* 0x01FC */ s32 mCpEffBtkFrame; + /* 0x01FC */ DUSK_IF_ELSE(f32, s32) mCpEffBtkFrame; /* 0x0200 */ J2DAnmTevRegKey* mCpDtEffBrk; - /* 0x0204 */ s32 mCpDtEffBrkFrame; + /* 0x0204 */ DUSK_IF_ELSE(f32, s32) mCpDtEffBrkFrame; /* 0x0208 */ u8 field_0x0208; /* 0x0209 */ u8 field_0x0209; - /* 0x020A */ u8 mFadeTimer; + /* 0x020A */ DUSK_IF_ELSE(f32, u8) mFadeTimer; /* 0x020B */ u8 field_0x020b; /* 0x020C */ CPaneMgrAlpha* mHeaderTxtPane[2]; /* 0x0214 */ TEXT_SPAN mHeaderStringPtr[2]; @@ -648,7 +651,7 @@ public: /* 0x02A0 */ J2DAnmTransform* mCpSelBck2; /* 0x02A4 */ CPaneMgr* mCpSelPane[3]; /* 0x02B0 */ u8 field_0x02b0[0x2b4-0x2b0]; - /* 0x02B4 */ int field_0x02b4[3]; + /* 0x02B4 */ DUSK_IF_ELSE(f32, int) field_0x02b4[3]; /* 0x02C0 */ CPaneMgr* mCpSelPane_moyo[2]; /* 0x02C8 */ CPaneMgr* mCpSelPane_gold[2]; /* 0x02D0 */ CPaneMgr* mCpSelPane_gold2[2]; @@ -658,30 +661,30 @@ public: /* 0x02E4 */ u8 field_0x02e4[2]; /* 0x02E4 */ u8 field_0x02e6[0x2e8-0x2e6]; /* 0x02E8 */ J2DAnmColor* mCpSelBpk; - /* 0x02EC */ s32 mCpSelBpkFrame; + /* 0x02EC */ DUSK_IF_ELSE(f32, s32) mCpSelBpkFrame; /* 0x02F0 */ J2DAnmTextureSRTKey* mCpSel03Btk; - /* 0x02F4 */ s32 mCpSel03BtkFrame; + /* 0x02F4 */ DUSK_IF_ELSE(f32, s32) mCpSel03BtkFrame; /* 0x02F8 */ J2DAnmColor* mCpSelBookBpk; - /* 0x02FC */ s32 mCpSelBookBpkFrame; + /* 0x02FC */ DUSK_IF_ELSE(f32, s32) mCpSelBookBpkFrame; /* 0x0300 */ J2DAnmTextureSRTKey* mCpSelBookBtk; - /* 0x0304 */ s32 mCpSelBookBtkFrame; + /* 0x0304 */ DUSK_IF_ELSE(f32, s32) mCpSelBookBtkFrame; /* 0x0308 */ J2DAnmTevRegKey* mCpSelBookBrk; - /* 0x030C */ s32 mCpSelBookBrkFrame; + /* 0x030C */ DUSK_IF_ELSE(f32, s32) mCpSelBookBrkFrame; /* 0x0310 */ J2DAnmTransform* mYnSelBck; /* 0x0314 */ J2DAnmTransform* mYnSelBck2; /* 0x0318 */ J2DAnmColor* mYnSelBpk; - /* 0x031C */ s32 mYnSelBpkFrame; + /* 0x031C */ DUSK_IF_ELSE(f32, s32) mYnSelBpkFrame; /* 0x0320 */ J2DAnmTextureSRTKey* mYnSelBtk; - /* 0x0324 */ s32 mYnSelBtkFrame; + /* 0x0324 */ DUSK_IF_ELSE(f32, s32) mYnSelBtkFrame; /* 0x0328 */ J2DAnmTransform* m3mBck; /* 0x032C */ J2DAnmTransform* m3mBck2; /* 0x0330 */ J2DAnmColor* m3mBpk; - /* 0x0334 */ s32 m3mBpkFrame; + /* 0x0334 */ DUSK_IF_ELSE(f32, s32) m3mBpkFrame; /* 0x0338 */ J2DAnmTextureSRTKey* m3mBtk; - /* 0x033C */ s32 m3mBtkFrame; + /* 0x033C */ DUSK_IF_ELSE(f32, s32) m3mBtkFrame; /* 0x0340 */ CPaneMgr* m3mSelPane[3]; - /* 0x034C */ s32 field_0x034c[3]; - /* 0x0358 */ int field_0x0358; + /* 0x034C */ DUSK_IF_ELSE(f32, s32) field_0x034c[3]; + /* 0x0358 */ DUSK_IF_ELSE(f32, int) field_0x0358; /* 0x035C */ int field_0x035c; /* 0x0360 */ bool field_0x0360; /* 0x0361 */ u8 field_0x0361[3]; @@ -694,7 +697,7 @@ public: /* 0x0390 */ u8 field_0x0391[3]; /* 0x0394 */ CPaneMgr* m3mSelTextPane[3]; /* 0x03A0 */ J2DAnmTextureSRTKey* mSelDtBtk; - /* 0x03A4 */ s32 mSelDtBtkFrame; + /* 0x03A4 */ DUSK_IF_ELSE(f32, s32) mSelDtBtkFrame; /* 0x03A8 */ CPaneMgr* mSelDtPane_mset; /* 0x03AC */ s16 mWaitTimer; /* 0x03AE */ u8 field_0x03AE[0x03B0 - 0x03AE]; @@ -720,6 +723,7 @@ public: dDlst_FileSelFade_c mFadeDlst; bool mGameModeSaveStartBuildUi = true; GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING; + bool mNameMoveRequested = false; #endif }; diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index d2a4028b2e..0a7dcbf0d3 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -194,6 +194,9 @@ public: /* 0x25F */ u8 field_0x25f; /* 0x260 */ u8 field_0x260; /* 0x261 */ u8 field_0x261; +#if TARGET_PC + f32 mBackAlpha = 0.0f; +#endif }; class dMenu_Collect3D_c { diff --git a/include/d/d_menu_dmap.h b/include/d/d_menu_dmap.h index 039b42aab2..7af8c750bd 100644 --- a/include/d/d_menu_dmap.h +++ b/include/d/d_menu_dmap.h @@ -91,10 +91,6 @@ public: void calcCursor(); void drawCursor(); - #if TARGET_PC - void dMapBgWide(); - #endif - void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; } f32 getMapWidth() { return mMapWidth; } @@ -108,7 +104,7 @@ public: } #if TARGET_PC - void resetScrollArrowMask() { field_0xdda = 0; } + void dMapBgWide(); #endif /* 0xC98 */ JKRExpHeap* mpHeap; @@ -174,16 +170,15 @@ public: /* 0xDCC */ f32 field_0xdcc; /* 0xDD0 */ u8 field_0xdd0; /* 0xDD1 */ u8 field_0xdd1; - /* 0xDD2 */ u8 field_0xdd2; + /* 0xDD2 */ DUSK_IF_ELSE(f32, u8) field_0xdd2; /* 0xDD3 */ u8 field_0xdd3; /* 0xDD4 */ s8 field_0xdd4; /* 0xDD5 */ u8 field_0xdd5; /* 0xDD6 */ s8 field_0xdd6; - /* 0xDD7 */ u8 field_0xdd7; + /* 0xDD7 */ DUSK_IF_ELSE(f32, u8) field_0xdd7; /* 0xDD8 */ u8 field_0xdd8; /* 0xDD9 */ u8 field_0xdd9; /* 0xDDA */ u8 field_0xdda; - #if TARGET_PC J2DTextBox* mpPoeCountPane; J2DPicture* mpPoeCountIcon; @@ -210,6 +205,11 @@ public: void mapControl(); bool isOpen(); bool isClose(); +#if TARGET_PC + void presentAnims(); + void presentLayoutAnims(); + void presentMapView(); +#endif void _draw(); void itemInfo_init_proc(); void itemInfo_proc(); diff --git a/include/d/d_menu_dmap_map.h b/include/d/d_menu_dmap_map.h index 3f3eb37a0f..6b16730360 100644 --- a/include/d/d_menu_dmap_map.h +++ b/include/d/d_menu_dmap_map.h @@ -191,10 +191,10 @@ public: /* 0xEE */ s8 field_0xee; /* 0xEF */ u8 field_0xef; /* 0xF0 */ u8 field_0xf0; - /* 0xF1 */ u8 field_0xf1; - /* 0xF2 */ u8 field_0xf2; - /* 0xF3 */ u8 field_0xf3; - /* 0xF4 */ s8 field_0xf4; + /* 0xF1 */ DUSK_IF_ELSE(f32, u8) field_0xf1; + /* 0xF2 */ DUSK_IF_ELSE(f32, u8) field_0xf2; + /* 0xF3 */ DUSK_IF_ELSE(f32, u8) field_0xf3; + /* 0xF4 */ DUSK_IF_ELSE(f32, s8) field_0xf4; /* 0xF5 */ u8 field_0xf5; /* 0xF6 */ u8 field_0xf6; /* 0xF7 */ s8 field_0xf7; diff --git a/include/d/d_menu_fmap.h b/include/d/d_menu_fmap.h index 3104d7e76b..81a735da33 100644 --- a/include/d/d_menu_fmap.h +++ b/include/d/d_menu_fmap.h @@ -112,12 +112,17 @@ public: class dMenu_Fmap_c { public: typedef void (dMenu_Fmap_c::*process)(); - + dMenu_Fmap_c(JKRExpHeap*, STControl*, CSTControl*, u8, u8, u8, f32, f32, u8); void _create(); void _delete(); void _move(); void _draw(); +#if TARGET_PC + void presentAnims(); + void presentZoomView(); + f32 getRenderZoomLevel() const; +#endif u8 getNextStatus(u8*); bool isSync(); void all_map_init(); @@ -266,7 +271,7 @@ public: /* 0x1B */ PROC_HOWL_DEMO1, /* 0x1C */ PROC_HOWL_DEMO2, /* 0x1D */ PROC_HOWL_DEMO3, - }; + }; static DUSK_GAME_DATA dMenu_Fmap_c* MyClass; diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h index 3c37c3da74..be638899bd 100644 --- a/include/d/d_menu_fmap2D.h +++ b/include/d/d_menu_fmap2D.h @@ -156,9 +156,6 @@ public: void setRegionCursor(u8 i_value) { mRegionCursor = i_value; } void setMapDrawFlag(bool i_flag) { mMapDrawFlag = i_flag; } void resetDrug() { field_0x1238 = 0; } -#if TARGET_PC - void resetScrollArrowMask() { field_0x122d = 0; } -#endif void offArrowDrawFlag() { mArrowDrawFlag = false; } void onArrowDrawFlag() { mArrowDrawFlag = true; } @@ -288,8 +285,8 @@ public: /* 0x120C */ JUtility::TColor field_0x120c; /* 0x1210 */ float field_0x1210; /* 0x1214 */ float field_0x1214; - /* 0x1218 */ s16 field_0x1218; - /* 0x121A */ s16 field_0x121a; + /* 0x1218 */ DUSK_IF_ELSE(f32, s16) field_0x1218; + /* 0x121A */ DUSK_IF_ELSE(f32, s16) field_0x121a; /* 0x121C */ u8 mRegionTextureReadNum[8]; /* 0x1224 */ u8 mSpotNum; /* 0x1225 */ u8 mStageDataNum; diff --git a/include/d/d_menu_fmap_map.h b/include/d/d_menu_fmap_map.h index b0d1063d18..b92cceec76 100644 --- a/include/d/d_menu_fmap_map.h +++ b/include/d/d_menu_fmap_map.h @@ -172,6 +172,10 @@ public: ~dMenu_FmapMap_c(); void _create(u16, u16, u16, u16, void*); void _delete(); +#if TARGET_PC + void presentAnims(); + void presentRendering(dMenu_Fmap_world_data_c*, int, f32, f32, f32, f32); +#endif void draw(); void rendering(line_class const*); int getLineWidth(int); @@ -224,9 +228,15 @@ public: /* 0xDC */ int mStageCursor; /* 0xE0 */ int mLastStageCursor; /* 0xE4 */ u8 mRegionCursor; - /* 0xE5 */ u8 mFlashTimer; + /* 0xE5 */ DUSK_IF_ELSE(f32, u8) mFlashTimer; /* 0xE8 */ u8* mp_roomList; /* 0xEC */ int m_roomListNumber; +#if TARGET_PC + f32 mFlashFrame = 0.0f; + f32 mPaletteFrame[2] = {}; + bool mResetFlashFrame = true; + bool mPaletteFrameInitialized = false; +#endif static dMenu_FmapMap_c* mMySelfPointer; }; diff --git a/include/d/d_menu_letter.h b/include/d/d_menu_letter.h index 8e112f04d2..f714205853 100644 --- a/include/d/d_menu_letter.h +++ b/include/d/d_menu_letter.h @@ -57,6 +57,7 @@ public: #if TARGET_PC bool pointerWait(); + void presentAnims(); #endif virtual void draw() { _draw(); } @@ -109,7 +110,7 @@ public: /* 0x360 */ f32 field_0x360; /* 0x364 */ f32 field_0x364; /* 0x368 */ s16 field_0x368; - /* 0x36A */ s16 field_0x36a; + /* 0x36A */ DUSK_IF_ELSE(f32, s16) field_0x36a; /* 0x36C */ u8 mStatus; /* 0x36D */ u8 mProcess; /* 0x36E */ u8 mIndex; diff --git a/include/d/d_menu_map_common.h b/include/d/d_menu_map_common.h index 55de3d0f6c..87ffa25136 100644 --- a/include/d/d_menu_map_common.h +++ b/include/d/d_menu_map_common.h @@ -123,8 +123,8 @@ public: /* 0x06C */ u32 _6c; /* 0x070 */ IconInfo_s mIconInfo[128]; /* 0xC70 */ u16 mIconNum; - /* 0xC72 */ s16 mBlinkTimer; - /* 0xC74 */ s16 mLightDropFlashTimer; + /* 0xC72 */ DUSK_IF_ELSE(f32, s16) mBlinkTimer; + /* 0xC74 */ DUSK_IF_ELSE(f32, s16) mLightDropFlashTimer; /* 0xC76 */ u16 _c76; /* 0xC78 */ f32 mBlinkAlpha; /* 0xC7C */ f32 _c7c; diff --git a/include/d/d_menu_option.h b/include/d/d_menu_option.h index ff4b84f5e3..8ccacf6490 100644 --- a/include/d/d_menu_option.h +++ b/include/d/d_menu_option.h @@ -96,6 +96,8 @@ public: bool dpdMenuMove(); #if TARGET_PC bool pointerConfirmSelect(); + void presentLayoutAnims(); + void presentAnims(); #endif void paneResize(u64); void initialize(); @@ -190,11 +192,11 @@ public: /* 0x3B4 */ f32 field_0x3b4; /* 0x3B8 */ JUtility::TColor field_0x3b8; /* 0x3BC */ JUtility::TColor field_0x3bc; - /* 0x3C0 */ int field_0x3c0; - /* 0x3C4 */ int field_0x3c4; - /* 0x3C8 */ int field_0x3c8[4]; + /* 0x3C0 */ DUSK_IF_ELSE(f32, int) field_0x3c0; + /* 0x3C4 */ DUSK_IF_ELSE(f32, int) field_0x3c4; + /* 0x3C8 */ DUSK_IF_ELSE(f32, int) field_0x3c8[4]; /* 0x3D8 */ s16 mFrame; - /* 0x3DA */ s16 field_0x3da; + /* 0x3DA */ DUSK_IF_ELSE(f32, s16) field_0x3da; /* 0x3DC */ u16 field_0x3dc; /* 0x3DE */ u16 field_0x3de; /* 0x3E0 */ u8 field_0x3e0; diff --git a/include/d/d_menu_ring.h b/include/d/d_menu_ring.h index fa4f97496f..735121df96 100644 --- a/include/d/d_menu_ring.h +++ b/include/d/d_menu_ring.h @@ -27,6 +27,9 @@ public: void _delete(); void _move(); void _draw(); +#if TARGET_PC + void advanceSelectItem(); +#endif void setKanteraPos(f32, f32); bool isOpen(); bool isMoveEnd(); @@ -166,7 +169,7 @@ public: /* 0x67E */ s16 field_0x67e; /* 0x680 */ s16 mCursorSpeed; /* 0x682 */ s16 field_0x682; - /* 0x684 */ s16 field_0x684; + /* 0x684 */ DUSK_IF_ELSE(f32, s16) field_0x684; /* 0x686 */ s16 field_0x686[4]; /* 0x68E */ s16 field_0x68e; /* 0x690 */ u8 mItemSlots[MAX_ITEM_SLOTS]; @@ -207,16 +210,6 @@ public: /* 0x6D2 */ u8 field_0x6d2; /* 0x6D3 */ u8 field_0x6d3; #if TARGET_PC - f32 mSelectItemSlideElapsed[4]; - f32 mCursorInterpPrevX; - f32 mCursorInterpPrevY; - f32 mCursorInterpCurrX; - f32 mCursorInterpCurrY; - s16 mCursorInterpPrevAngle; - s16 mCursorInterpCurrAngle; - bool mCursorInterpPrevAngular; - bool mCursorInterpCurrAngular; - bool mCursorInterpInit; bool mPointerTouchPressHoveredCurrent; #endif }; diff --git a/include/d/d_menu_save.h b/include/d/d_menu_save.h index a237bbb849..b12322ba8c 100644 --- a/include/d/d_menu_save.h +++ b/include/d/d_menu_save.h @@ -263,11 +263,12 @@ public: void setInitSaveData(); void _draw(); - #if TARGET_PC +#if TARGET_PC void menuSaveWide(); bool pointerSaveSelect(); bool pointerYesNoSelect(bool errorSelect, u8 errParam = 0, u8 soundParam = 0); - #endif + void presentLayoutAnims(); +#endif void _draw2(); @@ -293,16 +294,16 @@ public: /* 0x0054 */ u8 mSelectedFile; /* 0x0055 */ u8 mLastSelFile; /* 0x0058 */ CPaneMgr* mpSelectMoveBase; - /* 0x005C */ int mDataBaseMoveAnmFrame; + /* 0x005C */ DUSK_IF_ELSE(f32, int) mDataBaseMoveAnmFrame; /* 0x0060 */ int mDataBaseMoveFrameMax; /* 0x0064 */ u8 field_0x64; /* 0x0065 */ u8 field_0x65; /* 0x0068 */ CPaneMgr* mpSelData[3]; - /* 0x0074 */ int field_0x74[3]; + /* 0x0074 */ DUSK_IF_ELSE(f32, int) field_0x74[3]; /* 0x0080 */ int field_0x80; /* 0x0084 */ CPaneMgr* mpNoYes[2]; - /* 0x008C */ int field_0x8c[2]; - /* 0x0094 */ int mYesNoMoveAnmFrame; + /* 0x008C */ DUSK_IF_ELSE(f32, int) field_0x8c[2]; + /* 0x0094 */ DUSK_IF_ELSE(f32, int) mYesNoMoveAnmFrame; /* 0x0098 */ int mYesNoMoveAnmMax; /* 0x009C */ u8 field_0x9c; /* 0x009D */ u8 field_0x9d; @@ -310,7 +311,7 @@ public: /* 0x00A0 */ J2DPane* field_0xa0; /* 0x00A4 */ u8 field_0xa4[0x10]; /* 0x00B4 */ J2DPane* field_0xb4; - /* 0x00B8 */ int field_0xb8; + /* 0x00B8 */ DUSK_IF_ELSE(f32, int) field_0xb8; /* 0x00BC */ int field_0xbc; /* 0x00C0 */ CPaneMgrAlpha* mpErrTxtPane[2]; /* 0x00C8 */ TEXT_SPAN mpErrTxt[2]; @@ -334,15 +335,15 @@ public: /* 0x0134 */ u8 mNoYesWakuAnmTimer[2]; /* 0x0138 */ CPaneMgr* mpNoYesTxt[2]; /* 0x0140 */ J2DAnmColor* mpFileWakuAnm; - /* 0x0144 */ int mFileWakuAnmFrame; + /* 0x0144 */ DUSK_IF_ELSE(f32, int) mFileWakuAnmFrame; /* 0x0148 */ J2DAnmTextureSRTKey* mpFileWakuRotAnm; - /* 0x014C */ int mFileWakuRotAnmFrame; + /* 0x014C */ DUSK_IF_ELSE(f32, int) mFileWakuRotAnmFrame; /* 0x0150 */ J2DAnmColor* field_0x150; - /* 0x0154 */ int field_0x154; + /* 0x0154 */ DUSK_IF_ELSE(f32, int) field_0x154; /* 0x0158 */ J2DAnmTextureSRTKey* field_0x158; - /* 0x015C */ int field_0x15c; + /* 0x015C */ DUSK_IF_ELSE(f32, int) field_0x15c; /* 0x0160 */ J2DAnmTevRegKey* field_0x160; - /* 0x0164 */ int field_0x164; + /* 0x0164 */ DUSK_IF_ELSE(f32, int) field_0x164; /* 0x0168 */ CPaneMgrAlpha* mpHeaderTxtPane[2]; /* 0x0170 */ TEXT_SPAN mpHeaderTxt[2]; /* 0x0178 */ u8 mHeaderTxtType; // 0: Select Menu 1: YesNo Menu @@ -394,6 +395,9 @@ public: /* 0x21A1 */ u8 field_0x21a1; /* 0x21A2 */ u8 field_0x21a2; /* 0x21A3 */ u8 field_0x21a3; +#if TARGET_PC + u8 mYesNoSelAnmCol; +#endif }; #endif /* D_MENU_D_MENU_SAVE_H */ diff --git a/include/d/d_menu_skill.h b/include/d/d_menu_skill.h index 2c2dc445d1..bd5e16e879 100644 --- a/include/d/d_menu_skill.h +++ b/include/d/d_menu_skill.h @@ -51,6 +51,7 @@ public: #if TARGET_PC bool pointerWait(); + void presentAnims(); #endif virtual void draw() { _draw(); } @@ -90,7 +91,7 @@ public: /* 0x1F4 */ f32 mBarScale[2]; /* 0x1FC */ u32 mStringID; /* 0x200 */ s16 mFrame; - /* 0x202 */ s16 mProcFrame; + /* 0x202 */ DUSK_IF_ELSE(f32, s16) mProcFrame; /* 0x204 */ u8 mStatus; /* 0x205 */ u8 mProcess; /* 0x206 */ u8 mIndex; diff --git a/include/d/d_name.h b/include/d/d_name.h index de084defb3..95779b576a 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -111,9 +111,9 @@ public: void menuCursorMove2(); void selectCursorPosSet(int); - #if TARGET_PC +#if TARGET_PC void nameWide(); - #endif +#endif void _draw(); void screenSet(); @@ -135,9 +135,9 @@ public: /* 0x00C */ dDlst_NameIN_c nameIn; /* 0x020 */ dSelect_cursor_c* mSelIcon; /* 0x024 */ J2DAnmColorKey* mCursorColorKey; - /* 0x028 */ int mCurColAnmF; + /* 0x028 */ DUSK_IF_ELSE(f32, int) mCurColAnmF; /* 0x02C */ J2DAnmTextureSRTKey* mCursorTexKey; - /* 0x030 */ int mCurTexAnmF; + /* 0x030 */ DUSK_IF_ELSE(f32, int) mCurTexAnmF; /* 0x034 */ CPaneMgrAlpha* mNameCursor[8]; /* 0x054 */ TEXT_SPAN mNameText[8]; /* 0x074 */ CPaneMgr* mMojiIcon[65]; diff --git a/src/d/d_file_sel_warning.cpp b/src/d/d_file_sel_warning.cpp index 97b667bb45..b76998c038 100644 --- a/src/d/d_file_sel_warning.cpp +++ b/src/d/d_file_sel_warning.cpp @@ -13,6 +13,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #endif @@ -61,6 +62,10 @@ void dFile_warning_c::screenSet() { mFileWarn.Scr->setAnimation(field_0x24); field_0x24->setFrame(2849.0f); mFileWarn.Scr->animation(); +#if TARGET_PC + field_0x28 = 2849; + field_0x2c = 2849; +#endif mFileWarn.mFont = mDoExt_getMesgFont(); mpRootPane = JKR_NEW CPaneMgr(mFileWarn.Scr, MULTI_CHAR('Nm_02'), 0, NULL); @@ -116,6 +121,7 @@ bool dFile_warning_c::baseMoveAnm() { bool rt; if (field_0x28 != field_0x2c) { +#if !TARGET_PC if (field_0x28 < field_0x2c) { field_0x28 += 2; @@ -132,6 +138,7 @@ bool dFile_warning_c::baseMoveAnm() { field_0x24->setFrame(field_0x28); mFileWarn.Scr->animation(); +#endif rt = false; } @@ -149,6 +156,19 @@ bool dFile_warning_c::baseMoveAnm() { return rt; } +#if TARGET_PC +void dFile_warning_c::presentAnims() { + if (field_0x28 == field_0x2c) { + return; + } + dusk::vdt::present_toward(field_0x28, (f32)field_0x2c, field_0x24); + mFileWarn.Scr->animation(); + if (mPosY != 0.0f) { + mpRootPane->translate(mpRootPane->getTranslateX(), mPosY - field_0x34); + } +} +#endif + void dFile_warning_c::openInit() { field_0x28 = 2849; field_0x2c = 2859; diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index ed73d5f9cf..9a2a7d194b 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -24,7 +24,9 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/game_mode.hpp" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/mods/svc/save.hpp" #include "dusk/utilities.hpp" @@ -357,6 +359,8 @@ static DataSelProcFunc DataSelProc[] = { }; void dFile_select_c::_move() { + IF_DUSK(mNameMoveRequested = false); + #if DEBUG if (g_fsHIO.title_mesg_check) { titleMsgCheck(); @@ -393,6 +397,7 @@ void dFile_select_c::_move() { (this->*DataSelProc[mDataSelProc])(); +#if !TARGET_PC selFileWakuAnm(); bookIconAnm(); dataDelEffAnm(); @@ -408,6 +413,7 @@ void dFile_select_c::_move() { copyBookIconAnm(); mCpSel.Scr->animation(); } +#endif mpFileWarning->_move(); mpFileSelect3d->_move(); @@ -477,6 +483,15 @@ void dFile_select_c::errorMsgCheck() { #endif void dFile_select_c::selFileWakuAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mSelFileBpkFrame, mFileSelBpk, 2.0f); + dusk::vdt::present_looping(mSelFileBtk05Frame, mFileSel05Btk, 2.0f); + dusk::vdt::present_looping(mYnSelBpkFrame, mYnSelBpk, 2.0f); + dusk::vdt::present_looping(mYnSelBtkFrame, mYnSelBtk, 2.0f); + dusk::vdt::present_looping(m3mBpkFrame, m3mBpk, 2.0f); + dusk::vdt::present_looping(m3mBtkFrame, m3mBtk, 2.0f); + dusk::vdt::present_looping(mSelDtBtkFrame, mSelDtBtk, 2.0f); +#else mSelFileBpkFrame += 2; if (mSelFileBpkFrame >= mFileSelBpk->getFrameMax()) mSelFileBpkFrame -= mFileSelBpk->getFrameMax(); @@ -511,9 +526,15 @@ void dFile_select_c::selFileWakuAnm() { if (mSelDtBtkFrame >= mSelDtBtk->getFrameMax()) mSelDtBtkFrame -= mSelDtBtk->getFrameMax(); mSelDtBtk->setFrame(mSelDtBtkFrame); +#endif } void dFile_select_c::bookIconAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mSelFileBookBpkFrame, mSelFileBookBpk, 2.0f); + dusk::vdt::present_looping(mSelFileBookBtkFrame, mSelFileBookBtk, 2.0f); + dusk::vdt::present_looping(mSelFileBookBrkFrame, mSelFileBookBrk, 2.0f); +#else mSelFileBookBpkFrame += 2; if (mSelFileBookBpkFrame >= mSelFileBookBpk->getFrameMax()) mSelFileBookBpkFrame -= mSelFileBookBpk->getFrameMax(); @@ -528,9 +549,14 @@ void dFile_select_c::bookIconAnm() { if (mSelFileBookBrkFrame >= mSelFileBookBrk->getFrameMax()) mSelFileBookBrkFrame -= mSelFileBookBrk->getFrameMax(); mSelFileBookBrk->setFrame(mSelFileBookBrkFrame); +#endif } void dFile_select_c::selCopyFileWakuAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCpSelBpkFrame, mCpSelBpk, 2.0f); + dusk::vdt::present_looping(mCpSel03BtkFrame, mCpSel03Btk, 2.0f); +#else mCpSelBpkFrame += 2; if (mCpSelBpkFrame >= mCpSelBpk->getFrameMax()) mCpSelBpkFrame -= mCpSelBpk->getFrameMax(); @@ -540,9 +566,15 @@ void dFile_select_c::selCopyFileWakuAnm() { if (mCpSel03BtkFrame >= mCpSel03Btk->getFrameMax()) mCpSel03BtkFrame -= mCpSel03Btk->getFrameMax(); mCpSel03Btk->setFrame(mCpSel03BtkFrame); +#endif } void dFile_select_c::copyBookIconAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCpSelBookBpkFrame, mCpSelBookBpk, 2.0f); + dusk::vdt::present_looping(mCpSelBookBtkFrame, mCpSelBookBtk, 2.0f); + dusk::vdt::present_looping(mCpSelBookBrkFrame, mCpSelBookBrk, 2.0f); +#else mCpSelBookBpkFrame += 2; if (mCpSelBookBpkFrame >= mCpSelBookBpk->getFrameMax()) mCpSelBookBpkFrame -= mCpSelBookBpk->getFrameMax(); @@ -557,10 +589,15 @@ void dFile_select_c::copyBookIconAnm() { if (mCpSelBookBrkFrame >= mCpSelBookBrk->getFrameMax()) mCpSelBookBrkFrame -= mCpSelBookBrk->getFrameMax(); mCpSelBookBrk->setFrame(mCpSelBookBrkFrame); +#endif } void dFile_select_c::dataDelEffAnm() { if (field_0x0208 != 0) { +#if TARGET_PC + dusk::vdt::present_looping(mCpDtEffBrkFrame, mCpDtEffBrk, 2.0f); + dusk::vdt::present_looping(mDtEffBtkFrame, mDtEffBtk, 2.0f); +#else mCpDtEffBrkFrame += 2; if (mCpDtEffBrkFrame >= mCpDtEffBrk->getFrameMax()) mCpDtEffBrkFrame -= mCpDtEffBrk->getFrameMax(); @@ -570,11 +607,16 @@ void dFile_select_c::dataDelEffAnm() { if (mDtEffBtkFrame >= mDtEffBtk->getFrameMax()) mDtEffBtkFrame -= mDtEffBtk->getFrameMax(); mDtEffBtk->setFrame(mDtEffBtkFrame); +#endif } } void dFile_select_c::dataCopyEffAnm() { if (field_0x0209 != 0) { +#if TARGET_PC + dusk::vdt::present_looping(mCpDtEffBrkFrame, mCpDtEffBrk, 2.0f); + dusk::vdt::present_looping(mCpEffBtkFrame, mCpEffBtk, 2.0f); +#else mCpDtEffBrkFrame += 2; if (mCpDtEffBrkFrame >= mCpDtEffBrk->getFrameMax()) mCpDtEffBrkFrame -= mCpDtEffBrk->getFrameMax(); @@ -584,6 +626,7 @@ void dFile_select_c::dataCopyEffAnm() { if (mCpEffBtkFrame >= mCpEffBtk->getFrameMax()) mCpEffBtkFrame -= mCpEffBtk->getFrameMax(); mCpEffBtk->setFrame(mCpEffBtkFrame); +#endif } } @@ -602,6 +645,7 @@ void dFile_select_c::selectDataBaseMoveAnmInitSet(int i_frame, int i_frameMax) { bool dFile_select_c::selectDataBaseMoveAnm() { bool ret; if (mBaseMoveAnmFrame != mBaseMoveAnmFrameMax) { +#if !TARGET_PC if (mBaseMoveAnmFrame < mBaseMoveAnmFrameMax) { mBaseMoveAnmFrame += 2; if (mBaseMoveAnmFrame > mBaseMoveAnmFrameMax) @@ -614,6 +658,7 @@ bool dFile_select_c::selectDataBaseMoveAnm() { mBaseMoveAnm->setFrame(mBaseMoveAnmFrame); mBaseMovePane->getPanePtr()->animationTransform(); +#endif ret = false; } else { if (mBaseMoveAnmFrame == 33) { @@ -753,6 +798,7 @@ void dFile_select_c::dataSelectInit() { isKetteiTxtDisp = ketteiTxtDispAnm(); if (field_0x00e0[mSelectNum] != SelEndFrameTbl[mSelectNum]) { +#if !TARGET_PC field_0x00e0[mSelectNum] += 2; if (field_0x00e0[mSelectNum] > SelEndFrameTbl[mSelectNum]) @@ -760,6 +806,7 @@ void dFile_select_c::dataSelectInit() { mBaseMoveAnm->setFrame(field_0x00e0[mSelectNum]); mSelFilePanes[mSelectNum]->getPanePtr()->animationTransform(); +#endif check = false; } } @@ -1058,6 +1105,7 @@ bool dFile_select_c::selectDataMoveAnm() { bool ret; if (field_0x00e0[mSelectNum] != field_0x00ec) { +#if !TARGET_PC if (field_0x00e0[mSelectNum] < field_0x00ec) { field_0x00e0[mSelectNum] += 2; @@ -1077,6 +1125,7 @@ bool dFile_select_c::selectDataMoveAnm() { mSelFilePanes[i]->getPanePtr()->animationTransform(); } mBaseSubPane->animationTransform(); +#endif } if (field_0x00e0[mSelectNum] == field_0x00ec) { @@ -1133,6 +1182,7 @@ void dFile_select_c::dataSelectMoveAnime() { iVar6 = selectWakuAlpahAnm(mLastSelectNum); if (field_0x00e0[mLastSelectNum] != SelStartFrameTbl[mLastSelectNum]) { +#if !TARGET_PC field_0x00e0[mLastSelectNum] -= 2; if (field_0x00e0[mLastSelectNum] < SelStartFrameTbl[mLastSelectNum]) @@ -1140,6 +1190,7 @@ void dFile_select_c::dataSelectMoveAnime() { field_0x0088->setFrame(field_0x00e0[mLastSelectNum]); mSelFilePanes[mLastSelectNum]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } } @@ -1151,6 +1202,7 @@ void dFile_select_c::dataSelectMoveAnime() { iVar5 = mSelFilePane_Book_l[mSelectNum]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0, 0xff, 1); if (field_0x00e0[mSelectNum] != SelEndFrameTbl[mSelectNum]) { +#if !TARGET_PC field_0x00e0[mSelectNum] += 2; if (field_0x00e0[mSelectNum] > SelEndFrameTbl[mSelectNum]) @@ -1158,6 +1210,7 @@ void dFile_select_c::dataSelectMoveAnime() { mBaseMoveAnm->setFrame(field_0x00e0[mSelectNum]); mSelFilePanes[mSelectNum]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } } @@ -1488,6 +1541,7 @@ bool dFile_select_c::menuMoveAnm() { bool ret; if (field_0x0358 != field_0x035c) { +#if !TARGET_PC if (field_0x0358 < field_0x035c) { field_0x0358 += 2; @@ -1502,6 +1556,7 @@ bool dFile_select_c::menuMoveAnm() { m3mBck->setFrame(field_0x0358); m3mMenuPane->animationTransform(); +#endif ret = false; } else { m3mMenuPane->setAnimation((J2DAnmTransform*)NULL); @@ -1561,6 +1616,7 @@ void dFile_select_c::menuSelectMoveAnm() { if (mSelectMenuNum != 0xFF && field_0x034c[mSelectMenuNum] != MenuSelStartFrameTbl[mSelectMenuNum]) { +#if !TARGET_PC if (field_0x034c[mSelectMenuNum] > MenuSelStartFrameTbl[mSelectMenuNum]) { field_0x034c[mSelectMenuNum] -= 2; @@ -1575,6 +1631,7 @@ void dFile_select_c::menuSelectMoveAnm() { m3mBck->setFrame(field_0x034c[mSelectMenuNum]); m3mSelPane[mSelectMenuNum]->getPanePtr()->animationTransform(); +#endif tmp1 = false; } @@ -1583,6 +1640,7 @@ void dFile_select_c::menuSelectMoveAnm() { if (mLastSelectMenuNum != 0xFF) { if (field_0x034c[mLastSelectMenuNum] != MenuSelEndFrameTbl[mLastSelectMenuNum]) { +#if !TARGET_PC if (field_0x034c[mLastSelectMenuNum] < MenuSelEndFrameTbl[mLastSelectMenuNum]) { field_0x034c[mLastSelectMenuNum] += 2; @@ -1597,6 +1655,7 @@ void dFile_select_c::menuSelectMoveAnm() { } m3mBck2->setFrame(field_0x034c[mLastSelectMenuNum]); m3mSelPane[mLastSelectMenuNum]->getPanePtr()->animationTransform(); +#endif tmp2 = false; } @@ -1718,14 +1777,14 @@ void dFile_select_c::nameToDataSelectMove() { } void dFile_select_c::nameInputFade() { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (1.0f - (mFadeTimer / 15.0f)) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { char name[32]; headerTxtSet(900, 1, 1); dMeter2Info_getString(899, name, NULL); @@ -1746,14 +1805,14 @@ void dFile_select_c::nameInputFade() { void dFile_select_c::nameInput2Move() { if (!mDoRst::isReset()) { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (mFadeTimer / 15.0f) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { mpName->showIcon(); mDataSelProc = DATASELPROC_NAME_INPUT2; } @@ -1805,14 +1864,14 @@ void dFile_select_c::nameInput2() { } void dFile_select_c::backNameInputMove0() { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (1.0f - (mFadeTimer / 15.0f)) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { headerTxtSet(901, 1, 1); mpName->setNextNameStr(dComIfGs_getPlayerName()); mpName->initial(); @@ -1831,14 +1890,14 @@ void dFile_select_c::backNameInputMove0() { void dFile_select_c::backNameInputMove() { if (!mDoRst::isReset()) { - mFadeTimer--; + IF_NOT_DUSK(mFadeTimer--); - #if PLATFORM_GCN +#if !TARGET_PC && PLATFORM_GCN u8 alpha = (mFadeTimer / 15.0f) * 255.0f; mpFadePict->setAlpha(alpha); - #endif +#endif - if (mFadeTimer == 0) { + if (mFadeTimer DUSK_IF_ELSE(<=, ==) 0) { modoruTxtChange(1); mDataSelProc = DATASELPROC_NAME_INPUT_WAIT; } @@ -2084,12 +2143,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() { iVar7 = mCpSelPane_book[field_0x026c]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0xff, 0, 1); iVar6 = copySelectWakuAlpahAnm(field_0x026c); if (field_0x02b4[field_0x026c] != 109) { +#if !TARGET_PC field_0x02b4[field_0x026c] += 2; if (field_0x02b4[field_0x026c] > 109) { field_0x02b4[field_0x026c] = 109; } mCpSelBck2->setFrame(field_0x02b4[field_0x026c]); mCpSelPane[field_0x026c]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } } @@ -2099,12 +2160,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() { if (field_0x026b != 0xff) { iVar5 = mCpSelPane_book[field_0x026b]->alphaAnime(g_fsHIO.base_effect_appear_frames, 0, 0xff, 1); if (field_0x02b4[field_0x026b] != 99) { +#if !TARGET_PC field_0x02b4[field_0x026b] -= 2; if (field_0x02b4[field_0x026b] < 99) { field_0x02b4[field_0x026b] = 99; } mCpSelBck->setFrame(field_0x02b4[field_0x026b]); mCpSelPane[field_0x026b]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } } @@ -2259,6 +2322,7 @@ void dFile_select_c::yesnoMenuMoveAnmInitSet(int param_1, int param_2) { bool dFile_select_c::yesnoMenuMoveAnm() { bool rv; if (field_0x0100 != field_0x0104) { +#if !TARGET_PC if (field_0x0100 < field_0x0104) { field_0x0100 += 2; if (field_0x0100 > field_0x0104) { @@ -2274,6 +2338,7 @@ bool dFile_select_c::yesnoMenuMoveAnm() { mYnSelBck3->setFrame(field_0x0100); mYnSelPane[0]->getPanePtr()->animationTransform(); mYnSelPane[1]->getPanePtr()->animationTransform(); +#endif rv = false; } else { mYnSelPane[0]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL); @@ -2337,6 +2402,7 @@ bool dFile_select_c::yesnoSelectMoveAnm() { bool bVar1 = true; if (field_0x0269 != 0xff && field_0x00f8[field_0x0269] != YnSelStartFrameTbl[field_0x0269]) { +#if !TARGET_PC if (field_0x00f8[field_0x0269] < YnSelStartFrameTbl[field_0x0269]) { field_0x00f8[field_0x0269] += 2; if (field_0x00f8[field_0x0269] > YnSelStartFrameTbl[field_0x0269]) { @@ -2351,11 +2417,13 @@ bool dFile_select_c::yesnoSelectMoveAnm() { mYnSelBck->setFrame(field_0x00f8[field_0x0269]); mYnSelPane[field_0x0269]->getPanePtr()->animationTransform(); +#endif bVar1 = false; } bool bVar2 = true; if (field_0x0268 != 0xff && field_0x00f8[field_0x0268] != YnSelEndFrameTbl[field_0x0268]) { +#if !TARGET_PC if (field_0x00f8[field_0x0268] < YnSelEndFrameTbl[field_0x0268]) { field_0x00f8[field_0x0268] += 2; if (field_0x00f8[field_0x0268] > YnSelEndFrameTbl[field_0x0268]) { @@ -2370,6 +2438,7 @@ bool dFile_select_c::yesnoSelectMoveAnm() { mYnSelBck2->setFrame(field_0x00f8[field_0x0268]); mYnSelPane[field_0x0268]->getPanePtr()->animationTransform(); +#endif bVar2 = false; } @@ -3577,9 +3646,7 @@ void dFile_select_c::screenSet() { timg, NULL); mpFadePict->setBlackWhite(black, white); mpFadePict->setAlpha(0); -#ifdef TARGET_PC - mFadeDlst.mpPict = mpFadePict; -#endif + IF_DUSK(mFadeDlst.mpPict = mpFadePict); #endif } @@ -3782,6 +3849,10 @@ void dFile_select_c::screenSet3Menu() { m3mMenuPane = m3mSel.Scr3m->search(MULTI_CHAR('wmenu_n')); m3mMenuPane->setAnimation(m3mBck); +#if TARGET_PC + field_0x0358 = 799; + field_0x035c = 799; +#endif m3mBck->setFrame(799.0f); m3mMenuPane->animationTransform(); @@ -4396,14 +4467,157 @@ void dFile_select_c::fileSelectWide() { break; } } + +void dFile_select_c::presentLayoutAnims() { + dusk::vdt::present_toward(mBaseMoveAnmFrame, (f32)mBaseMoveAnmFrameMax, mBaseMoveAnm, + mBaseMovePane != NULL ? mBaseMovePane->getPanePtr() : NULL); + + if (mBaseSubPane != NULL && mBaseSubPane->mTransform == mBaseMoveAnm && mSelectNum != 0xFF) { + dusk::vdt::present_toward(field_0x00e0[mSelectNum], (f32)field_0x00ec, mBaseMoveAnm); + for (int i = 0; i < 3; i++) { + J2DPane* pane = mSelFilePanes[i]->getPanePtr(); + if (pane != NULL && pane->mTransform != NULL) { + pane->animationTransform(); + } + } + mBaseSubPane->animationTransform(); + } else { + for (int i = 0; i < 3; i++) { + J2DPane* pane = mSelFilePanes[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x00e0[i], pane, mBaseMoveAnm, (f32)SelEndFrameTbl[i], + field_0x0088, (f32)SelStartFrameTbl[i]); + } + } + + dusk::vdt::present_toward(field_0x0358, (f32)field_0x035c, m3mBck, m3mMenuPane); + + for (int i = 0; i < 3; i++) { + J2DPane* pane = m3mSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x034c[i], pane, m3mBck, (f32)MenuSelStartFrameTbl[i], + m3mBck2, (f32)MenuSelEndFrameTbl[i]); + } + + if (field_0x0100 != field_0x0104) { + dusk::vdt::present_shared(field_0x0100, (f32)field_0x0104, mYnSelBck3, + {mYnSelPane[0]->getPanePtr(), mYnSelPane[1]->getPanePtr()}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mYnSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x00f8[i], pane, mYnSelBck, (f32)YnSelStartFrameTbl[i], + mYnSelBck2, (f32)YnSelEndFrameTbl[i]); + } + + for (int i = 0; i < 2; i++) { + if (mCpSelPane[i] == NULL) { + continue; + } + J2DPane* pane = mCpSelPane[i]->getPanePtr(); + dusk::vdt::present_selected(field_0x02b4[i], pane, mCpSelBck, 99.0f, mCpSelBck2, 109.0f); + } + + dusk::vdt::present_toward(field_0x0130, (f32)field_0x0134, field_0x0090, mErrorMsgPane); + + if (mBaseSubPane != NULL && mBaseSubPane->mTransform == field_0x009c) { + dusk::vdt::present_toward(field_0x0110, (f32)field_0x0114, field_0x009c, mBaseSubPane); + } + + if (mNameMoveRequested) { + dusk::vdt::present_toward(field_0x0120, (f32)field_0x0124, field_0x0094, mNameBasePane); + } +} + +void dFile_select_c::presentNameInputFade() { + bool fadeOut; + switch (mDataSelProc) { + case DATASELPROC_NAME_INPUT_FADE: + case DATASELPROC_BACK_NAME_INPUT_MOVE0: + fadeOut = true; + break; + case DATASELPROC_NAME_INPUT2_MOVE: + case DATASELPROC_BACK_NAME_INPUT_MOVE: + if (mDoRst::isReset()) { + return; + } + fadeOut = false; + break; + default: + return; + } + + mFadeTimer -= dusk::game_clock::original_frames(); + if (mFadeTimer < 0.0f) { + mFadeTimer = 0.0f; + } + + f32 t = mFadeTimer / 15.0f; + u8 alpha = fadeOut ? (1.0f - t) * 255.0f : t * 255.0f; + mpFadePict->setAlpha(alpha); +} + +void dFile_select_c::presentAnims() { + if (mpFileWarning != NULL) { + mpFileWarning->presentAnims(); + } + + presentNameInputFade(); + selFileWakuAnm(); + bookIconAnm(); + dataDelEffAnm(); + dataCopyEffAnm(); + fileSel.Scr->animation(); + mYnSel.ScrYn->animation(); + m3mSel.Scr3m->animation(); + mSelDt.ScrDt->animation(); + if (mCpSel.isShow) { + selCopyFileWakuAnm(); + copyBookIconAnm(); + mCpSel.Scr->animation(); + } + presentLayoutAnims(); + for (int i = 0; i < 3; ++i) { + mSelFilePanes[i]->presentAnime(); + mDeleteEfPane[i]->presentAlphaAnime(); + mCopyEfPane[i]->presentAlphaAnime(); + mSelFileMoyoPane[i]->presentAnime(); + mSelFileGoldPane[i]->presentAnime(); + mSelFileGold2Pane[i]->presentAnime(); + mSelFilePane_Book_l[i]->presentAnime(); + mFileInfoDatBasePane[i]->presentAlphaAnime(); + mFileInfoNoDatBasePane[i]->presentAlphaAnime(); + m3mSelPane_mo[i]->presentAnime(); + m3mSelPane_g[i]->presentAnime(); + m3mSelPane_gr[i]->presentAnime(); + m3mSelTextPane[i]->presentAnime(); + } + for (int i = 0; i < 2; ++i) { + mErrorMsgTxtPane[i]->presentAlphaAnime(); + mYnSelPane_m[i]->presentAlphaAnime(); + mYnSelPane_g[i]->presentAlphaAnime(); + mYnSelPane_gr[i]->presentAlphaAnime(); + mYnSelTxtPane[i]->presentAnime(); + mHeaderTxtPane[i]->presentAlphaAnime(); + mCpSelPane_moyo[i]->presentAnime(); + mCpSelPane_gold[i]->presentAnime(); + mCpSelPane_gold2[i]->presentAnime(); + mCpSelPane_book[i]->presentAnime(); + } + mBbtnPane->presentAlphaAnime(); + mAbtnPane->presentAlphaAnime(); + mModoruTxtPane->presentAlphaAnime(); + mKetteiTxtPane->presentAlphaAnime(); + fileSelectWide(); +} #endif void dFile_select_c::_draw() { - #if TARGET_PC - fileSelectWide(); - #endif - if (!mHasDrawn) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + } +#endif + dComIfGd_set2DOpa(&fileSel); for (int i = 0; i < 3; i++) { @@ -4505,6 +4719,7 @@ void dFile_select_c::errorMoveAnmInitSet(int param_1, int param_2) { bool dFile_select_c::errorMoveAnm() { bool ret; if (field_0x0130 != field_0x0134) { +#if !TARGET_PC if (field_0x0130 < field_0x0134) { field_0x0130 += 2; @@ -4519,6 +4734,7 @@ bool dFile_select_c::errorMoveAnm() { field_0x0090->setFrame(field_0x0130); mErrorMsgPane->animationTransform(); +#endif ret = false; } else { mErrorMsgPane->setAnimation((J2DAnmTransform*)NULL); @@ -5797,6 +6013,7 @@ bool dFile_select_c::fileInfoScaleAnm() { bool ret; if (field_0x0110 != field_0x0114) { +#if !TARGET_PC if (field_0x0110 < field_0x0114) { field_0x0110 += 2; @@ -5811,6 +6028,7 @@ bool dFile_select_c::fileInfoScaleAnm() { field_0x009c->setFrame(field_0x0110); mBaseSubPane->animationTransform(); +#endif ret = false; } @@ -5823,6 +6041,13 @@ bool dFile_select_c::fileInfoScaleAnm() { } void dFile_select_c::nameMoveAnmInitSet(int param_1, int param_2) { +#if TARGET_PC + f32 startFrame = param_1; + if (mNameBasePane->mTransform == field_0x0094 && field_0x0124 == param_1) { + startFrame = field_0x0120; + } + mNameMoveRequested = false; +#endif if (param_1 == 3359) { field_0x0128 = true; } @@ -5830,15 +6055,17 @@ void dFile_select_c::nameMoveAnmInitSet(int param_1, int param_2) { mpName->hideIcon(); } mNameBasePane->setAnimation(field_0x0094); - field_0x0120 = param_1; + field_0x0120 = DUSK_IF_ELSE(startFrame, param_1); field_0x0124 = param_2; field_0x0094->setFrame(field_0x0120); mNameBasePane->animationTransform(); } bool dFile_select_c::nameMoveAnm() { + IF_DUSK(mNameMoveRequested = true); bool ret; if (field_0x0120 != field_0x0124) { +#if !TARGET_PC if (field_0x0120 < field_0x0124) { field_0x0120 += 2; @@ -5854,6 +6081,7 @@ bool dFile_select_c::nameMoveAnm() { } field_0x0094->setFrame(field_0x0120); mNameBasePane->animationTransform(); +#endif ret = false; } else { mNameBasePane->setAnimation((J2DAnmTransform*)0); @@ -5987,6 +6215,7 @@ void dFile_select3D_c::_move() { } void dFile_select3D_c::draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) if (mpModel) { dComIfGd_setListItem3D(); g_env_light.settingTevStruct(13, &field_0x03a4, &mTevstr); @@ -5995,6 +6224,7 @@ void dFile_select3D_c::draw() { mDoExt_modelUpdateDL(mpModel); dComIfGd_setList(); } + IF_DUSK_BLOCK_END } void dFile_select3D_c::setJ3D(char const* param_0, char const* param_1, char const* param_2) { diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index ac85b5d2b4..2dfd843f6f 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -15,6 +15,7 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "helpers/gx_helper.h" #endif @@ -192,6 +193,9 @@ int dGameover_c::_create() { } (this->*init_process[mProc])(); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; return phase; @@ -335,9 +339,11 @@ void dGameover_c::deleteWait_init() {} void dGameover_c::deleteWait_proc() {} int dGameover_c::_draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame() && (mIsDemoSave && mProc >= PROC_DISP_WAIT)) if (dgo_capture_c != NULL && dComIfGp_isPauseFlag()) { dComIfGd_set2DOpa(dgo_capture_c); } + IF_DUSK_BLOCK_END if (mIsDemoSave && mProc >= PROC_DISP_WAIT) { if (dgo_screen_c != NULL) { diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index 32c0a360c2..fefb1d5703 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -38,6 +38,8 @@ #include "m_Do/m_Do_mtx.h" #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/utilities.hpp" #endif @@ -999,10 +1001,14 @@ void dMenu_Collect2D_c::animationSet() { void dMenu_Collect2D_c::btkAnimeLoop0(J2DAnmTextureSRTKey* i_SRTKey) { if (i_SRTKey != NULL) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mFrame, 1.0f, i_SRTKey->getFrameMax()); +#else mFrame++; if (mFrame >= i_SRTKey->getFrameMax()) { mFrame -= i_SRTKey->getFrameMax(); } +#endif i_SRTKey->setFrame(mFrame); } else { mFrame = 0.0f; @@ -1016,6 +1022,11 @@ void dMenu_Collect2D_c::btkAnimeLoop0(J2DAnmTextureSRTKey* i_SRTKey) { } void dMenu_Collect2D_c::setBackAlpha() { +#if TARGET_PC + const f32 target = mProcess >= 1 && mProcess <= 18 ? 1.0f : 0.0f; + dusk::vdt::advance_toward_frame(mBackAlpha, target, 0.2f); + mpBlackTex->setAlpha(mBackAlpha * 150.0f); +#else f32 alpha = mpBlackTex->getAlpha() / 150.0f; switch (mProcess) { @@ -1056,6 +1067,7 @@ void dMenu_Collect2D_c::setBackAlpha() { } mpBlackTex->setAlpha(alpha * 150.0f); +#endif } // Not sure if this works without gotos @@ -2435,17 +2447,22 @@ void dMenu_Collect2D_c::_move() { if (mProcess != last_process) { (this->*init[mProcess])(); } +#if !TARGET_PC btkAnimeLoop0(mpAnmKey); mpScreen->animation(); setBackAlpha(); +#endif setHIO(false); } void dMenu_Collect2D_c::_draw() { - #if TARGET_PC +#if TARGET_PC + btkAnimeLoop0(mpAnmKey); + mpScreen->animation(); + setBackAlpha(); menuCollectWide(); - #endif +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); grafPort->setup2D(); @@ -3157,6 +3174,8 @@ void dMenu_Collect_c::_move() { void dMenu_Collect_c::draw() { dComIfGd_set2DOpa(mpCollect2D); + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) mpCollect3D->draw(); + IF_DUSK_BLOCK_END mpCollect2D->drawTop(); } diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index f8514e3817..119352d3d7 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -26,7 +26,10 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/version.hpp" #include "helpers/string.hpp" @@ -240,12 +243,14 @@ void dMenu_DmapBg_c::mapScreenInit() { void dMenu_DmapBg_c::mapScreenAnime() { if (0.0f == field_0xd94 && 0.0f == field_0xd98) { for (int i = 0; i < 1; i++) { +#if !TARGET_PC field_0xdc8[i] += 0.4f; if (field_0xdc8[i] >= field_0xd28[i]->getFrameMax()) { field_0xdc8[i] -= field_0xd28[i]->getFrameMax(); } field_0xd28[i]->setFrame(field_0xdc8[i]); mMapScreen[i]->animation(); +#endif } } } @@ -277,7 +282,9 @@ bool dMenu_DmapBg_c::iconScaleAnm() { return true; } - f32 dVar7 = fopMsgM_valueIncrease(field_0xdd1, field_0xdd2, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xdd2, field_0xdd1, 1.0f)); + f32 dVar7 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xdd1, field_0xdd2), + fopMsgM_valueIncrease(field_0xdd1, field_0xdd2, 4)); f32 dVar8; f32 dVar1; if (field_0xdac > field_0xdb0) { @@ -291,7 +298,7 @@ bool dMenu_DmapBg_c::iconScaleAnm() { iconScale(1, dVar7, dVar8, dVar1); - field_0xdd2++; + IF_NOT_DUSK(field_0xdd2++); if (field_0xdd2 >= field_0xdd1) { rv = true; } @@ -923,8 +930,8 @@ void dMenu_DmapBg_c::addGoldFrameAlphaRate() { rate = 1.0f; setGoldAnimation(true); } else { - field_0xdd7++; - if (field_0xdd7 == g_fmapHIO.mDisplayFrameNum) { + DUSK_IF_ELSE(field_0xdd7 += dusk::game_clock::original_frames(), field_0xdd7++); + if (field_0xdd7 DUSK_IF_ELSE(>=, ==) g_fmapHIO.mDisplayFrameNum) { setGoldAnimation(true); } rate = (f32)(field_0xdd7 * field_0xdd7) / (f32)(g_fmapHIO.mDisplayFrameNum * g_fmapHIO.mDisplayFrameNum); @@ -1217,7 +1224,7 @@ void dMenu_DmapBg_c::update() { } if (mpBackTexture != NULL && field_0xdbc < 255.0f) { - field_0xdbc += 25.5f; + field_0xdbc += 25.5f IF_DUSK(* dusk::game_clock::original_frames()); if (field_0xdbc > 255.0f) { field_0xdbc = 255.0f; } @@ -1647,6 +1654,7 @@ void dMenu_Dmap_c::getIconPos(s8 i_floorNo, f32 param_2) { } dMenu_Dmap_c::~dMenu_Dmap_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); s32 iVar2 = field_0xe8->getTotalFreeSize(); s32 iVar4 = field_0xe8->getTotalFreeSize(); @@ -2008,7 +2016,7 @@ void dMenu_Dmap_c::_move() { var_f31 = 0.70588237f; } - cLib_addCalc2(&field_0x110, var_f31, 0.4f, 0.5f); + DUSK_IF_ELSE(dusk::vdt::present_addCalc2, cLib_addCalc2)(&field_0x110, var_f31, 0.4f, 0.5f IF_DUSK_ARG(0.1f)); mpDrawBg->mpBlack->setAlphaRate(field_0x110); } @@ -2024,9 +2032,10 @@ void dMenu_Dmap_c::_move() { setMapTexture(); mapBgAnime(); mpDrawBg->calcCursor(); - mpDrawBg->addGoldFrameAlphaRate(); + IF_NOT_DUSK(mpDrawBg->addGoldFrameAlphaRate()); mDoExt_setCurrentHeap(prev_heap); + IF_DUSK(dusk::interp::capture_menu_values(this, field_0x104, field_0x108, field_0x10c)); } void dMenu_Dmap_c::setMapTexture() { @@ -2035,13 +2044,13 @@ void dMenu_Dmap_c::setMapTexture() { } void dMenu_Dmap_c::mapBgAnime() { - mpDrawBg->mapScreenAnime(); + IF_NOT_DUSK(mpDrawBg->mapScreenAnime()); } void dMenu_Dmap_c::mapControl() { u8 temp_r27 = field_0x17e; if (m_process == 0 || field_0x183 == 0) { - mMapCtrl->move(); + IF_NOT_DUSK(mMapCtrl->move()); getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); @@ -2067,6 +2076,7 @@ void dMenu_Dmap_c::mapControl() { if (stick_value >= sp28 && field_0x181 != 2) { var_r28 = true; +#if !TARGET_PC f32 var_f31 = mMapCtrl->getStageMapSizeX(); if (var_f31 < mMapCtrl->getStageMapSizeZ()) { var_f31 = mMapCtrl->getStageMapSizeZ(); @@ -2084,9 +2094,10 @@ void dMenu_Dmap_c::mapControl() { f32 sp14 = temp_f28 * cM_scos(stick_angle); mMapCtrl->setPlusZoomCenterX(IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp18 :) sp18); mMapCtrl->setPlusZoomCenterZ(sp14); +#endif } - mMapCtrl->move(); + IF_NOT_DUSK(mMapCtrl->move()); getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); @@ -2186,6 +2197,7 @@ bool dMenu_Dmap_c::isOpen() { mpDrawBg->setAllAlphaRate(field_0x10c, var_r27); mpDrawBg->setGoldFrameAlphaRate(0.0f); + IF_DUSK(dusk::interp::capture_menu_values(this, field_0x104, field_0x108, field_0x10c)); return var_r28; } @@ -2225,14 +2237,159 @@ bool dMenu_Dmap_c::isClose() { field_0x10c = temp_f31; mpDrawBg->setAllAlphaRate(field_0x10c, var_r29); mpDrawBg->decGoldFrameAlphaRate(); + IF_DUSK(dusk::interp::capture_menu_values(this, field_0x104, field_0x108, field_0x10c)); return var_r30; } -void dMenu_Dmap_c::_draw() { - if (mMapCtrl != NULL) { - mMapCtrl->draw(); +#if TARGET_PC +void dMenu_Dmap_c::presentAnims() { + if (mMapCtrl == NULL || mpDrawBg == NULL) { + return; + } + for (int i = 0; i < 8; ++i) { + mSelFloor[i]->presentAnime(); + } + for (int i = 0; i < 3; ++i) { + field_0x88[i]->presentAnime(); + } + + if (field_0x10c == 1.0f) { + mpDrawBg->addGoldFrameAlphaRate(); + + if (0.0f == mpDrawBg->field_0xd94 && 0.0f == mpDrawBg->field_0xd98) { + for (int i = 0; i < 1; i++) { + dusk::vdt::present_looping(mpDrawBg->field_0xdc8[i], mpDrawBg->field_0xd28[i], + 0.4f); + mpDrawBg->mMapScreen[i]->animation(); + } + } + mpDrawBg->clearIconInfo(); + presentMapView(); + if (field_0x17e == 1 || field_0x17e == 2) { + mpDrawBg->iconScaleAnm(); + } + } + + presentLayoutAnims(); +} + +void dMenu_Dmap_c::presentLayoutAnims() { + mpDrawBg->setAllTrans(field_0x104, field_0x108); + + if (mpDrawBg->mpBackTexture != NULL) { + f32 var_f31 = 100.0f * mpDrawBg->field_0xd88; + f32 sp24 = mpDrawBg->field_0xd80 / var_f31; + f32 sp20 = mpDrawBg->field_0xd84 / var_f31; + + const ResTIMG* back_timg = mpDrawBg->mpBackTexture->getTexture(0)->getTexInfo(); + field_0x13c = mMapCtrl->getPixelStageSizeX(); + field_0x140 = mMapCtrl->getPixelStageSizeZ(); + field_0x134 = mMapCtrl->getPixelCenterX(); + field_0x138 = mMapCtrl->getPixelCenterZ(); + + f32 temp_f30; + f32 var_f29; + f32 var_f27; + if (field_0x13c > field_0x140) { + temp_f30 = back_timg->width * var_f31; + var_f29 = mMapCtrl->getStageMapSizeX(); + var_f27 = field_0x13c * (temp_f30 / var_f29); + } else { + temp_f30 = back_timg->height * var_f31; + var_f29 = mMapCtrl->getStageMapSizeZ(); + var_f27 = field_0x140 * (temp_f30 / var_f29); + } + + f32 sp1C[2]; + sp1C[0] = var_f27; + sp1C[1] = var_f27; + + f32 sp14[2]; + sp14[0] = -(field_0x134 + (100.0f * (sp24 * mMapCtrl->getPixelPerCm()))); + sp14[1] = -(field_0x138 + 100.0f * (sp20 * mMapCtrl->getPixelPerCm())); + + CPaneMgr sp70; + //!@bug It's unclear what this is supposed to be, but a stack pointer being converted to a bool is probably not intended. + u8 sp40[0x30]; + Vec sp34 = sp70.getGlobalVtxCenter(mpDrawBg->getMapPane(), (bool)sp40, 0); + mpDrawBg->mpBackTexture->move(sp14[0] + (sp34.x - (sp1C[0] / 2)), + sp14[1] + (sp34.y - (sp1C[1] / 2))); + mpDrawBg->mpBackTexture->resize(sp1C[0], sp1C[1]); + } +} + +void dMenu_Dmap_c::presentMapView() { + const bool can_stick_scroll = m_process != 0 && field_0x183 != 0 && mMapCtrl->isEndZoomIn(); + f32 disp_center_x = mMapCtrl->getDispCenterX(); + f32 disp_center_z = mMapCtrl->getDispCenterZ(); + bool scrolled = false; + if (can_stick_scroll) { + f32 sp28 = g_fmapHIO.mScrollSpeedSlowBound < g_fmapHIO.mScrollSpeedFastBound ? + g_fmapHIO.mScrollSpeedSlowBound : + g_fmapHIO.mScrollSpeedFastBound; + f32 sp24 = g_fmapHIO.mScrollSpeedSlowBound > g_fmapHIO.mScrollSpeedFastBound ? + g_fmapHIO.mScrollSpeedSlowBound : + g_fmapHIO.mScrollSpeedFastBound; + f32 stick_value = mpCStick->getValueStick(); + s16 stick_angle = mpCStick->getAngleStick(); + if (stick_value >= sp28 && field_0x181 != 2) { + scrolled = true; + f32 var_f31 = mMapCtrl->getStageMapSizeX(); + if (var_f31 < mMapCtrl->getStageMapSizeZ()) { + var_f31 = mMapCtrl->getStageMapSizeZ(); + } + f32 var_f29 = stick_value < sp24 ? g_fmapHIO.mScrollSpeedDungeonMapSlow : + g_fmapHIO.mScrollSpeedDungeonMapFast; + f32 temp_f28 = (var_f29 / 100.0f) * var_f31 * dusk::game_clock::original_frames(); + f32 sp18 = temp_f28 * cM_ssin(stick_angle); + f32 sp14 = temp_f28 * cM_scos(stick_angle); + mMapCtrl->setPlusZoomCenterX(dusk::getSettings().game.enableMirrorMode ? -sp18 : sp18); + mMapCtrl->setPlusZoomCenterZ(sp14); + } + } + mMapCtrl->move(); + if (can_stick_scroll) { + if (scrolled && (disp_center_x != mMapCtrl->getDispCenterX() || + disp_center_z != mMapCtrl->getDispCenterZ())) + { + Z2GetAudioMgr()->seStartLevel(Z2SE_SY_MAP_SCROLL, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + } + if (mpDrawBg->mpMeterHaihai != NULL && isMapMoveState() && + (getCMessageNum() == 0x37B || getCMessageNum() == 0x569)) + { + mpDrawBg->field_0xdda = 0; + if (mMapCtrl->getDispCenterX() > mMapCtrl->getZoomCenterMinX()) { + mpDrawBg->field_0xdda |= 1; + } + if (mMapCtrl->getDispCenterX() < mMapCtrl->getZoomCenterMaxX()) { + mpDrawBg->field_0xdda |= 4; + } + if (mMapCtrl->getDispCenterZ() > mMapCtrl->getZoomCenterMinZ()) { + mpDrawBg->field_0xdda |= 8; + } + if (mMapCtrl->getDispCenterZ() < mMapCtrl->getZoomCenterMaxZ()) { + mpDrawBg->field_0xdda |= 2; + } + mpDrawBg->mpMeterHaihai->_execute(0); + } + } + getIconPos(mMapCtrl->getDispFloorNo(), mMapCtrl->getMapBlendPer()); + getIconPos(mMapCtrl->getDispFloor2No(), 1.0f - mMapCtrl->getMapBlendPer()); +} +#endif + +void dMenu_Dmap_c::_draw() { + IF_DUSK(dusk::interp::ScopedMenuValues pose(this, field_0x104, field_0x108, field_0x10c)); + if (mMapCtrl != NULL) { + IF_NOT_DUSK(mMapCtrl->draw()); if (mpDrawBg != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + mpDrawBg->setAllAlphaRate(field_0x10c, false); + } +#else mpDrawBg->setAllTrans(field_0x104, field_0x108); if (mpDrawBg->mpBackTexture != NULL) { @@ -2284,7 +2441,13 @@ void dMenu_Dmap_c::_draw() { } dComIfGd_set2DOpa(mpDrawBg); +#endif } + +#if TARGET_PC + mMapCtrl->draw(); + dComIfGd_set2DOpa(mpDrawBg); +#endif } } @@ -2759,7 +2922,7 @@ void dMenu_Dmap_c::zoomIn_init_proc() { void dMenu_Dmap_c::zoomIn_proc() { bool temp_r30 = mMapCtrl->isEndZoomIn(); - bool temp_r29 = mpDrawBg->iconScaleAnm(); + bool temp_r29 = DUSK_IF_ELSE(mpDrawBg->field_0xdd2 >= mpDrawBg->field_0xdd1, mpDrawBg->iconScaleAnm()); bool temp_r28 = true; if (temp_r30 == true && temp_r29 == true && temp_r28 == true) { @@ -2775,7 +2938,7 @@ void dMenu_Dmap_c::zoomIn_proc() { void dMenu_Dmap_c::zoomOut_init_proc() { #if TARGET_PC if (dusk::interp::is_enabled()) { - mpDrawBg->resetScrollArrowMask(); + mpDrawBg->field_0xdda = 0; } #endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -2793,7 +2956,7 @@ void dMenu_Dmap_c::zoomOut_init_proc() { void dMenu_Dmap_c::zoomOut_proc() { bool temp_r30 = mMapCtrl->isEndZoomOut(); - bool temp_r29 = mpDrawBg->iconScaleAnm(); + bool temp_r29 = DUSK_IF_ELSE(mpDrawBg->field_0xdd2 >= mpDrawBg->field_0xdd1, mpDrawBg->iconScaleAnm()); bool temp_r28 = true; if (temp_r30 == true && temp_r29 == true && temp_r28 == true) { diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index b0be1dbf4b..3abb6c13c1 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -13,7 +13,7 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC -#include +#include "dusk/interp/user_interface.h" #endif struct dMdm_HIO_prm_res_dst_s { @@ -462,7 +462,7 @@ void dMenu_StageMapCtrl_c::setPlusNowStayFloorNo(s8 param_0, u8 param_1) { } void dMenu_StageMapCtrl_c::moveFloor() { - if ((field_0xf3 == 0) && (field_0xf1 == 0)) { + if ((field_0xf3 DUSK_IF_ELSE(<= , ==) 0) && (field_0xf1 DUSK_IF_ELSE(<= , ==) 0)) { if (field_0xea != 0) { field_0xf3 = field_0xf0; field_0x90 = 0.0f; @@ -489,7 +489,7 @@ void dMenu_StageMapCtrl_c::moveFloor() { field_0xea = 0; } - } else if (field_0xf1 != 0 && field_0xea != 0) { + } else if (field_0xf1 DUSK_IF_ELSE(>, !=) 0 && field_0xea != 0) { if (field_0xea > 0) { if (field_0xe7 < field_0xed) { if ((field_0xe7 == std::floor(field_0xc4) && field_0xe8 == field_0xe7 + field_0xea) || field_0x90 > 0.5f) { @@ -521,22 +521,22 @@ void dMenu_StageMapCtrl_c::moveFloor() { field_0xeb = field_0xe7; } - if (field_0xf1 != 0) { + if (field_0xf1 DUSK_IF_ELSE(>, !=) 0) { f32 temp_f31 = (f32)(field_0xf0 - field_0xf3) / (f32)field_0xf0; field_0xeb = getFloorNo(field_0xc4); field_0xec = getFloorNo(field_0xc0); field_0x90 = getBlendPer(temp_f31 - std::floor(temp_f31)); - field_0xf1--; - if (field_0xf1 == 0) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0xf1, 0.0f, 1.0f), field_0xf1--); + if (field_0xf1 DUSK_IF_ELSE(<=, ==) 0) { field_0xe8 = field_0xe7; } } - if (field_0xf3 != 0) { - field_0xf3--; - if (field_0xf3 != 0 && field_0xf1 != 0) { + if (field_0xf3 DUSK_IF_ELSE(>, !=) 0) { + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0xf3, 0.0f, 1.0f), field_0xf3--); + if (field_0xf3 DUSK_IF_ELSE(>, !=) 0 && field_0xf1 DUSK_IF_ELSE(>, !=) 0) { f32 temp_f30 = (f32)(field_0xf0 - field_0xf3) / (f32)field_0xf0; field_0xeb = getFloorNo(field_0xc4); field_0xec = getFloorNo(field_0xc0); @@ -805,12 +805,14 @@ void dMenu_StageMapCtrl_c::zoomCalcSet(f32 param_0) { } void dMenu_StageMapCtrl_c::zoomIn_proc() { - f32 temp_f1 = fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xf4, 0.0f, 1.0f)); + f32 temp_f1 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xf2, field_0xf2 - field_0xf4), + fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4)); field_0xd8 = temp_f1; zoomCalcSet(temp_f1); - field_0xf4--; - if (field_0xf4 < 0) { + IF_NOT_DUSK(field_0xf4--); + if (field_0xf4 DUSK_IF_ELSE(<=, <) 0) { field_0xf5 = 2; } } @@ -844,7 +846,7 @@ void dMenu_StageMapCtrl_c::zoomWait_init_proc() { } bool dMenu_DmapMapCtrl_c::isEnableZoomMove() const { - return field_0xf3 == 0 && (!getDisableZoomMoveFlgX() || !getDisableZoomMoveFlgZ()); + return field_0xf3 DUSK_IF_ELSE(<=, ==) 0 && (!getDisableZoomMoveFlgX() || !getDisableZoomMoveFlgZ()); } void dMenu_StageMapCtrl_c::zoomWait_proc() { @@ -888,12 +890,14 @@ void dMenu_StageMapCtrl_c::zoomOut_init_proc() { } void dMenu_StageMapCtrl_c::zoomOut_proc() { - f32 temp_f1 = fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4); + IF_DUSK(dusk::vdt::advance_toward_frame(field_0xf4, 0.0f, 1.0f)); + f32 temp_f1 = DUSK_IF_ELSE(dusk::vdt::present_sine_ease(field_0xf2, field_0xf2 - field_0xf4), + fopMsgM_valueIncrease(field_0xf2, field_0xf2 - field_0xf4, 4)); zoomCalcSet(1.0f - temp_f1); field_0xd8 = 1.0f - temp_f1; - field_0xf4--; - if (field_0xf4 < 0) { + IF_NOT_DUSK(field_0xf4--); + if (field_0xf4 DUSK_IF_ELSE(<=, <) 0) { field_0xf5 = 0; } } @@ -991,7 +995,7 @@ void dMenu_StageMapCtrl_c::_delete() { bool dMenu_StageMapCtrl_c::isEnableZoomIn() { bool var_r30 = 0; - if (field_0xf3 == 0 && dStage_roomControl_c::getFileList2(field_0xe6) != NULL) { + if (field_0xf3 DUSK_IF_ELSE(<=, ==) 0 && dStage_roomControl_c::getFileList2(field_0xe6) != NULL) { var_r30 = 1; } @@ -999,7 +1003,7 @@ bool dMenu_StageMapCtrl_c::isEnableZoomIn() { } bool dMenu_StageMapCtrl_c::isEnableZoomOut() { - return field_0xf3 == 0; + return field_0xf3 DUSK_IF_ELSE(<=, ==) 0; } void dMenu_StageMapCtrl_c::setPlusZoomCenterX(f32 param_0) { diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index 57c329a61f..2dd1ba72f7 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -24,7 +24,10 @@ #include "d/actor/d_a_midna.h" #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" #include "dusk/memory.h" #include "dusk/version.hpp" #include "helpers/string.hpp" @@ -276,6 +279,7 @@ dMenu_Fmap_c::dMenu_Fmap_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i } dMenu_Fmap_c::~dMenu_Fmap_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); mpHeap->getTotalFreeSize(); if (mpFieldDat != NULL) { mpHeap->free(mpFieldDat); @@ -500,22 +504,25 @@ void dMenu_Fmap_c::_move() { mpDraw2DBack->clearIconInfo(); (this->*move_process[mProcess])(); - - mpDraw2DTop->_execute(); + + IF_NOT_DUSK(mpDraw2DTop->_execute()); mpDraw2DBack->setBaseBackAlpha(g_fmHIO.mBaseBackAlpha); - mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep); + IF_NOT_DUSK(mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep)); mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); - mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep); + IF_NOT_DUSK(mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep)); if (mProcess != process) { (this->*init_process[mProcess])(); + IF_DUSK(dusk::interp::erase_owned_samples(this)); } +#if !TARGET_PC if (mProcess == PROC_PORTAL_WARP_FORBID) { mpDraw2DBack->calcBackAlpha(true); } else { mpDraw2DBack->calcBackAlpha(false); } +#endif mpDraw2DBack->setAllTrans(mTransX, mTransY); mpDraw2DTop->setAllTrans(mTransX, mTransY); @@ -524,6 +531,7 @@ void dMenu_Fmap_c::_move() { drawDebugStage(); } +#if !TARGET_PC if (mSpotTextureFadeAlpha != 1.0f) { cLib_addCalc2(&mSpotTextureFadeAlpha, 1.0f, 0.4f, 0.5f); if (fabsf(mSpotTextureFadeAlpha - 1.0f) < 0.1f) { @@ -531,6 +539,7 @@ void dMenu_Fmap_c::_move() { } mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); } +#endif #if TARGET_PC u8 region = mpDraw2DBack->getSelectRegion(); @@ -540,18 +549,41 @@ void dMenu_Fmap_c::_move() { mpDraw2DTop->mSelectRegionNo = 0xFF; } + dusk::interp::capture_menu_values(this, mTransX, mTransY, mAlphaRatio, field_0x1ec, mZoomLevel); #endif } void dMenu_Fmap_c::_draw() { + IF_DUSK(dusk::interp::ScopedMenuValues pose(this, mTransX, mTransY, mAlphaRatio, field_0x1ec)); if (mpDraw2DBack != NULL && mpDraw2DTop != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + presentZoomView(); + mpDraw2DBack->setAllTrans(mTransX, mTransY); + mpDraw2DTop->setAllTrans(mTransX, mTransY); + mpDraw2DBack->setAllAlphaRate(mAlphaRatio, false); + mpDraw2DTop->setAllAlphaRate(mAlphaRatio, false); + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + if (field_0x305) { + mpMenuFmapMap->presentRendering(mpWorldData, mStartStageNo, + mpDraw2DBack->getRenderingPosX(), + mpDraw2DBack->getRenderingPosZ(), + mpDraw2DBack->getRenderingScale(), + mpDraw2DBack->getMapZoomRate()); + } + mpDraw2DBack->clearIconInfo(); + } +#endif if (field_0x305) { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) mpMenuFmapMap->setRendering(mpWorldData, mStartStageNo, mpDraw2DBack->getRenderingPosX(), mpDraw2DBack->getRenderingPosZ(), mpDraw2DBack->getRenderingScale(), mpDraw2DBack->getMapZoomRate()); mpDraw2DBack->setStageInfo(mSpotNum, mpMenuFmapMap); + IF_DUSK_BLOCK_END drawIcon(field_0x1ec, false); if (mProcess == PROC_ZOOM_REGION_TO_SPOT || mProcess == PROC_ZOOM_SPOT_TO_REGION || mProcess == PROC_YAMIBOSS_DEMO4 || mProcess == PROC_LIGHT_DEMO1 @@ -563,7 +595,7 @@ void dMenu_Fmap_c::_draw() { } else { drawPortalIcon(); if (mProcess == PROC_ZOOM_ALL_TO_REGION || mProcess == PROC_ZOOM_REGION_TO_ALL) { - f32 scale = 1.0f - (f32)mZoomLevel / 10.0f; + f32 scale = 1.0f - DUSK_IF_ELSE(getRenderZoomLevel(), (f32)mZoomLevel) / 10.0f; mpDraw2DBack->iconScale(0, scale, scale, 1.0f - scale); } } @@ -574,6 +606,66 @@ void dMenu_Fmap_c::_draw() { } } +#if TARGET_PC +void dMenu_Fmap_c::presentAnims() { + if (mAlphaRatio == 1.0f) { + if (mProcess == PROC_ALL_MAP) { + mpDraw2DBack->allmap_move2(mpStick); + } else if (mProcess == PROC_REGION_MAP) { + mpDraw2DBack->regionMapMove(mpStick); + } else if (mProcess == PROC_SPOT_MAP) { + mpDraw2DBack->stageMapMove(mpStick, 1, true); + } else if (mProcess == PROC_PORTAL_WARP_MAP || mProcess == PROC_PORTAL_DEMO5) { + mpDraw2DBack->regionMapMove(mpStick); + } + + mpDraw2DTop->_execute(); + mpDraw2DBack->calcBackAlpha(mProcess == PROC_PORTAL_WARP_FORBID); + if (mSpotTextureFadeAlpha != 1.0f) { + dusk::vdt::present_addCalc2(&mSpotTextureFadeAlpha, 1.0f, 0.4f, 0.5f, 0.1f); + mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); + } + } + if (mpDraw2DBack != NULL && mpDraw2DTop != NULL) { + mpDraw2DBack->btkAnimeLoop(g_fmHIO.mBackAnimeStep); + mpDraw2DTop->btkAnimeLoop(g_fmHIO.mTopAnimeStep); + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + } +} + +void dMenu_Fmap_c::presentZoomView() { + const f32 zoomLevel = getRenderZoomLevel(); + switch (mProcess) { + case PROC_ZOOM_ALL_TO_REGION: + case PROC_ZOOM_REGION_TO_ALL: + case PROC_PORTAL_DEMO1: + case PROC_PORTAL_DEMO3: + mpDraw2DBack->zoomMapCalc(std::clamp(zoomLevel / 10.0f, 0.0f, 1.0f)); + break; + case PROC_TABLE_DEMO1: + if (zoomLevel > 0) { + mpDraw2DBack->zoomMapCalc(zoomLevel / 10.0f); + } + break; + case PROC_ZOOM_REGION_TO_SPOT: + case PROC_ZOOM_SPOT_TO_REGION: + case PROC_YAMIBOSS_DEMO4: + mpDraw2DBack->zoomMapCalc2(field_0x1ec); + break; + case PROC_TABLE_DEMO2: + case PROC_HOWL_DEMO1: + if (zoomLevel > 0) { + mpDraw2DBack->zoomMapCalc2(field_0x1ec); + } + break; + } +} + +f32 dMenu_Fmap_c::getRenderZoomLevel() const { + return dusk::interp::get(this).values.read(4, (f32)mZoomLevel); +} +#endif + u8 dMenu_Fmap_c::getNextStatus(u8* param_0) { u8 ret = 8; *param_0 = 0; @@ -774,9 +866,9 @@ void dMenu_Fmap_c::all_map_proc() { } } else { u8 region1 = mpDraw2DBack->getSelectRegion(); - mpDraw2DBack->allmap_move2(mpStick); + IF_NOT_DUSK(mpDraw2DBack->allmap_move2(mpStick)); u8 region2 = mpDraw2DBack->getSelectRegion(); - if (region1 != region2 || mResetAreaName) { + if (DUSK_IF_ELSE(region_change, region1 != region2) || mResetAreaName) { mResetAreaName = false; if (mpDraw2DBack->getSelectRegion() != 0xff && mpDraw2DBack->isShowRegion(mpDraw2DBack->getSelectRegion())) @@ -932,7 +1024,7 @@ void dMenu_Fmap_c::region_map_proc() { if (g_fmapHIO.mDisplayReferenceArea) { mpDraw2DBack->zoomMapCalc(1.0f); } - mpDraw2DBack->regionMapMove(mpStick); + IF_NOT_DUSK(mpDraw2DBack->regionMapMove(mpStick)); int stage_no, room_no; f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF() @@ -1151,9 +1243,8 @@ void dMenu_Fmap_c::zoom_spot_to_region_init() { mZoomLevel = 10; field_0x1ec = 1.0f; #if TARGET_PC - // Frame interp note: field_0x122d used to be set every draw, causing flickering. Do it here instead. if (dusk::interp::is_enabled()) { - mpDraw2DBack->resetScrollArrowMask(); + mpDraw2DBack->field_0x122d = 0; } #endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -1215,7 +1306,7 @@ void dMenu_Fmap_c::spot_map_proc() { } else if (dMw_A_TRIGGER() && !dMeter2Info_isTouchKeyCheck(0xc) && dMeter2Info_getMeterClass()->getMeterDrawPtr()->getInsideObjCheck() != 1) { - mpDraw2DBack->stageMapMove(mpStick, 1, true); + IF_NOT_DUSK(mpDraw2DBack->stageMapMove(mpStick, 1, true)); } else if (dMw_Z_TRIGGER() && mpDraw2DTop->isWarpAccept()) { #if TARGET_PC || VERSION >= VERSION_GCN_JPN IF_DUSK_BLOCK(dusk::version::isRegionJpn()) @@ -1264,7 +1355,7 @@ void dMenu_Fmap_c::spot_map_proc() { mpDraw2DBack->zoomMapCalc2(field_0x1ec); } - mpDraw2DBack->stageMapMove(mpStick, 1, true); + IF_NOT_DUSK(mpDraw2DBack->stageMapMove(mpStick, 1, true)); int stage_no, room_no; f32 pos_x = mpDraw2DBack->getMapAreaGlobalCenterPosX() - mDoGph_gInf_c::getMinXF() @@ -1758,6 +1849,10 @@ bool dMenu_Fmap_c::isOpen() { mpDraw2DTop->setAllAlphaRate(mAlphaRatio, init); mpDraw2DBack->setSpotTextureFadeAlpha(mSpotTextureFadeAlpha); +#if TARGET_PC + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + dusk::interp::capture_menu_values(this, mTransX, mTransY, mAlphaRatio, field_0x1ec, mZoomLevel); +#endif return ret; } @@ -1799,6 +1894,10 @@ bool dMenu_Fmap_c::isClose() { mpDraw2DTop->setAllTrans(mTransX, mTransY); mpDraw2DTop->setAllAlphaRate(mAlphaRatio, bVar2); +#if TARGET_PC + mpDraw2DTop->setMoyaAlpha(g_fmHIO.mMoyaAlpha); + dusk::interp::capture_menu_values(this, mTransX, mTransY, mAlphaRatio, field_0x1ec, mZoomLevel); +#endif return ret; } @@ -2475,7 +2574,7 @@ void dMenu_Fmap_c::setAreaNameZero() { } void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) { - mpDraw2DBack->regionMapMove(i_stick); + IF_NOT_DUSK(mpDraw2DBack->regionMapMove(i_stick)); dMenu_Fmap_portal_data_c* portal_dat = mpPortalDat; dMenu_Fmap_portal_data_c::data* portals = portal_dat->mData; f32 arrow_x = mpDraw2DBack->getArrowPos2DX(); diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index 58db87ee68..2c87c13cf3 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -20,7 +20,9 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #include "dusk/ui/touch_controls.hpp" #include "dusk/version.hpp" @@ -358,10 +360,8 @@ void dMenu_Fmap2DBack_c::draw() { scrollAreaDraw(); } - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) blinkMove(30); moveLightDropAnime(); - IF_DUSK_BLOCK_END setCenterPosX(field_0x11dc, 1); drawIcon(mTransX, mTransZ, mAlphaRate, field_0xfa8 * mSpotTextureFadeAlpha); @@ -396,13 +396,11 @@ void dMenu_Fmap2DBack_c::draw() { (mArrowPos3DZ + control_ypos + fVar3) - fVar5, &mArrowPos2DX, &mArrowPos2DY); - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) - field_0x11e0 -= g_fmapHIO.mCursorSpeed; + field_0x11e0 -= g_fmapHIO.mCursorSpeed IF_DUSK(* dusk::game_clock::original_frames()); if (field_0x11e0 < 0.0f) { field_0x11e0 += 360.0f; } - IF_DUSK_BLOCK_END mpPointParent->getPanePtr()->rotate(mpPointParent->getSizeX() / 2.0f, mpPointParent->getSizeY() / 2.0f, ROTATE_Z, @@ -1017,8 +1015,8 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) { } f32 speed = (sp24 / 100.0f) * zoomRate; - f32 delta_y = speed * cM_ssin(angle); - f32 delta_x = speed * cM_scos(angle); + f32 delta_y = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 delta_x = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); #ifdef TARGET_PC if (dusk::getSettings().game.enableMirrorMode) { @@ -1862,7 +1860,10 @@ void dMenu_Fmap2DBack_c::calcBlink() { t * (g_fmapHIO.mMapBlink[i + 1].mUnselectedRegion.mBlinkSpeed - g_fmapHIO.mMapBlink[i].mUnselectedRegion.mBlinkSpeed); - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1218, 1.0f, selected_blink_speed); + dusk::vdt::advance_looping_frame(field_0x121a, 1.0f, unselected_blink_speed); +#else field_0x1218++; if (field_0x1218 >= selected_blink_speed) { field_0x1218 = 0; @@ -1872,7 +1873,7 @@ void dMenu_Fmap2DBack_c::calcBlink() { if (field_0x121a >= unselected_blink_speed) { field_0x121a = 0; } - IF_DUSK_BLOCK_END +#endif f32 t_selected = 0.0f; f32 t_unselected = 0.0f; @@ -1898,6 +1899,9 @@ void dMenu_Fmap2DBack_c::calcBlink() { } void dMenu_Fmap2DBack_c::calcBackAlpha(bool param_0) { +#if TARGET_PC + dusk::vdt::present_addCalc2(&mBackAlpha, param_0 ? 1.0f : 0.0f, 0.4f, 0.5f, 0.1f); +#else if (param_0) { if (mBackAlpha != 1.0f) { cLib_addCalc2(&mBackAlpha, 1.0f, 0.4f, 0.5f); @@ -1913,14 +1917,19 @@ void dMenu_Fmap2DBack_c::calcBackAlpha(bool param_0) { } } } +#endif } void dMenu_Fmap2DBack_c::btkAnimeLoop(f32 i_step) { if (mpBaseAnm) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mAnmFrame, i_step, mpBaseAnm->getFrameMax()); +#else mAnmFrame += i_step; if (mAnmFrame >= mpBaseAnm->getFrameMax()) { mAnmFrame -= mpBaseAnm->getFrameMax(); } +#endif mpBaseAnm->setFrame(mAnmFrame); } else { mAnmFrame = 0.0f; @@ -1964,8 +1973,8 @@ void dMenu_Fmap2DBack_c::regionMapMove(STControl* i_stick) { base_speed = g_fmapHIO.mScrollSpeedRegionFast; } f32 speed = base_speed / 100.0f * local_78; - f32 speed_y = speed * cM_ssin(angle); - f32 speed_x = speed * cM_scos(angle); + f32 speed_y = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 speed_x = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); control_xpos += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_y :) speed_y; control_ypos += speed_x; } @@ -2034,8 +2043,8 @@ void dMenu_Fmap2DBack_c::stageMapMove(STControl* i_stick, u8 param_1, bool param base_speed = g_fmapHIO.mScrollSpeedRegionZoomFast; } f32 speed = base_speed / 100.0f * local_78; - f32 speed_x = speed * cM_ssin(angle); - f32 speed_z = speed * cM_scos(angle); + f32 speed_x = speed * cM_ssin(angle) IF_DUSK(* dusk::game_clock::original_frames()); + f32 speed_z = speed * cM_scos(angle) IF_DUSK(* dusk::game_clock::original_frames()); mStageTransX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -speed_x :) speed_x; mStageTransZ += speed_z; } else if (!param_2) { @@ -2841,10 +2850,14 @@ void dMenu_Fmap2DTop_c::draw() { void dMenu_Fmap2DTop_c::btkAnimeLoop(J2DAnmTextureSRTKey* i_anm, f32 i_delta) { if (i_anm) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(mAnmFrame, i_delta, i_anm->getFrameMax()); +#else mAnmFrame += i_delta; if (mAnmFrame >= i_anm->getFrameMax()) { mAnmFrame -= i_anm->getFrameMax(); } +#endif i_anm->setFrame(mAnmFrame); } else { mAnmFrame = 0.0f; @@ -3103,9 +3116,9 @@ void dMenu_Fmap2DTop_c::setArrowAlphaRatio(u8 i_mask, f32 i_rate) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer > 0 || i_pane->getAlphaRate() != 0.0f) { - timer--; + DUSK_IF_ELSE(timer -= dusk::game_clock::original_frames(), timer--); if (timer < 0) { timer = 0; } @@ -3115,12 +3128,24 @@ void dMenu_Fmap2DTop_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMid(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer != 3 || i_pane->getAlphaRate() != 0.25f) { if (timer > 3) { - timer--; +#if TARGET_PC + timer -= dusk::game_clock::original_frames(); + if (timer < 3) { + timer = 3; + } +#endif + IF_NOT_DUSK(timer--); } else if (timer < 3) { - timer++; +#if TARGET_PC + timer += dusk::game_clock::original_frames(); + if (timer > 3) { + timer = 3; + } +#endif + IF_NOT_DUSK(timer++); } i_pane->alphaAnimeStart(timer); i_pane->setAlphaRate(timer / 6.0f * 0.5f); @@ -3128,9 +3153,9 @@ void dMenu_Fmap2DTop_c::setAlphaAnimeMid(CPaneMgrAlpha* i_pane) { } void dMenu_Fmap2DTop_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane) { - s16 timer = i_pane->getAlphaTimer(); + DUSK_IF_ELSE(f32, s16) timer = i_pane->getAlphaTimer(); if (timer < 5 || i_pane->getAlphaRate() != 1.0f) { - timer++; + DUSK_IF_ELSE(timer += dusk::game_clock::original_frames(), timer++); if (timer > 5) { timer = 5; } diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index 8a3c34fbde..c1e96bc56a 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -11,7 +11,7 @@ #include #if TARGET_PC -#include +#include "dusk/interp/user_interface.h" #endif static u8 twoValueLineInterpolation(u8 i_value1, u8 i_value2, f32 i_param) { @@ -481,6 +481,10 @@ dMenu_FmapMap_c::~dMenu_FmapMap_c() { } void dMenu_FmapMap_c::_create(u16 i_texWidth, u16 i_texHeight, u16 param_2, u16 param_3, void* i_res) { +#if TARGET_PC + mResetFlashFrame = true; + mPaletteFrameInitialized = false; +#endif m_res = (dMfm_prm_res_s*)i_res; m_palette = m_res->palette_data; field_0xcc = 0x6c; @@ -508,10 +512,55 @@ void dMenu_FmapMap_c::_delete() { DUSK_GAME_DATA const dMfm_HIO_prm_res_src_s dMfm_HIO_prm_res_src_s::m_other = {30}; +#if TARGET_PC +void dMenu_FmapMap_c::presentAnims() { + const f32 duration = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; + if (mResetFlashFrame) { + mFlashFrame = 0.0f; + mResetFlashFrame = false; + } else { + dusk::vdt::advance_looping_frame(mFlashFrame, 1.0f, duration + 1.0f); + } + mFlashTimer = std::max(0.0f, duration - mFlashFrame); + + const int periods[] = {m_res->field_0x168, m_res->field_0x16a}; + for (int i = 0; i < 2; ++i) { + if (periods[i] <= 0) { + mPaletteFrame[i] = 0.0f; + } else if (!mPaletteFrameInitialized) { + mPaletteFrame[i] = g_Counter.mCounter0 % periods[i]; + } else { + dusk::vdt::advance_looping_frame(mPaletteFrame[i], 1.0f, periods[i]); + } + } + mPaletteFrameInitialized = true; +} + +void dMenu_FmapMap_c::presentRendering(dMenu_Fmap_world_data_c* i_worldData, int i_startStageNo, + f32 i_posX, f32 i_posY, f32 i_scale, f32 i_zoomRate) +{ + mZoomRate = i_zoomRate; + mpWorldData = i_worldData; + mStartStageNo = i_startStageNo; + mPosX = i_posX; + mPosZ = i_posY; + mCmPerTexel = i_scale; + field_0x8 = mCmPerTexel * field_0x20 * mDoGph_gInf_c::getScale(); + field_0xc = mCmPerTexel * field_0x22; +} +#endif + void dMenu_FmapMap_c::draw() { { int unused; } f32 f30 = 0.0f; +#if TARGET_PC + presentAnims(); + const f32 half = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2; + if (half > 0.0f) { + f30 = std::clamp(std::fabs(mFlashTimer - half) / half, 0.0f, 1.0f); + } +#else if (mFlashTimer < dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2) { f30 = (int)(dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2 - mFlashTimer) / @@ -521,16 +570,19 @@ void dMenu_FmapMap_c::draw() { (int)(mFlashTimer - dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2) / (f32)(int)(dMfm_HIO_prm_res_src_s::m_other.mFlashDuration / 2); } +#endif setPointColor(1.0f - f30); GXColor color; f32 f31; - f31 = getRateWithFrameCount(m_res->field_0x168); + f31 = DUSK_IF_ELSE(dusk::vdt::clamped_fraction(mPaletteFrame[0], m_res->field_0x168), + getRateWithFrameCount(m_res->field_0x168)); f31 = cM_ssin(f31 * 0x10000 - 0x8000) * 0.5f + 0.5f; twoColorLineInterporation(m_res->field_0xd8, m_res->field_0xdc, f31, color); setFmapPaletteColor(PALETTE_19, color); - f31 = getRateWithFrameCount(m_res->field_0x16a); + f31 = DUSK_IF_ELSE(dusk::vdt::clamped_fraction(mPaletteFrame[1], m_res->field_0x16a), + getRateWithFrameCount(m_res->field_0x16a)); f31 = cM_ssin(f31 * 0x10000 - 0x8000) * 0.5f + 0.5f; twoColorLineInterporation(m_res->field_0xe0, m_res->field_0xe4, f31, color); setFmapPaletteColor(PALETTE_1A, color); @@ -681,15 +733,18 @@ void dMenu_FmapMap_c::setRendering(dMenu_Fmap_world_data_c* i_worldData, int i_s f32 i_posX, f32 i_posY, f32 i_scale, f32 i_zoomRate) { mZoomRate = i_zoomRate; if (mFlash != mLastFlash || mStageCursor != mLastStageCursor) { + IF_DUSK(mResetFlashFrame = true); mFlashTimer = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; mLastFlash = mFlash; mLastStageCursor = mStageCursor; } else { +#if !TARGET_PC if (mFlashTimer != 0) { mFlashTimer--; } else { mFlashTimer = dMfm_HIO_prm_res_src_s::m_other.mFlashDuration; } +#endif } entry(i_worldData, i_startStageNo, i_posX, i_posY, i_scale); } diff --git a/src/d/d_menu_insect.cpp b/src/d/d_menu_insect.cpp index 684dd1235c..828e39384d 100644 --- a/src/d/d_menu_insect.cpp +++ b/src/d/d_menu_insect.cpp @@ -23,6 +23,7 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/menu_pointer.h" #include "dusk/version.hpp" #endif @@ -164,6 +165,11 @@ void dMenu_Insect_c::_move() { void dMenu_Insect_c::_draw() { if (mpArchive != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + mpExpParent->presentAnime(); + } +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); mpBlackTex->setAlpha(0xff); @@ -193,6 +199,7 @@ void dMenu_Insect_c::_draw() { // operations applied which cannot easily be reverse engineered mpSelect_c->translate(g_drawHIO.mInsectListScreen.mConfirmOptionPosX_4x3 + 486.0f, g_drawHIO.mInsectListScreen.mConfirmOptionPosY_4x3 + 209.0f); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); mpIconScreen->draw(0.0f, 0.0f, grafPort); } diff --git a/src/d/d_menu_item_explain.cpp b/src/d/d_menu_item_explain.cpp index e6134e6c8b..0ca70af9e7 100644 --- a/src/d/d_menu_item_explain.cpp +++ b/src/d/d_menu_item_explain.cpp @@ -26,6 +26,7 @@ #include "d/d_msg_scrn_arrow.h" #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/version.hpp" #endif @@ -355,6 +356,7 @@ void dMenu_ItemExplain_c::draw(J2DOrthoGraph* i_graph) { // were likely either chosen by hand or had multiple arithmetic // operations applied which cannot easily be reverse engineered mpSelect_c->translate(486.0f, 209.0f); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); } if (mpArrow != NULL) { @@ -443,7 +445,7 @@ void dMenu_ItemExplain_c::open_init() { } void dMenu_ItemExplain_c::open_proc() { - mAlphaRatio += 2.0f; + mAlphaRatio += 2.0f IF_DUSK(* dusk::game_clock::original_frames()); if (mAlphaRatio >= 201.0f) { mAlphaRatio = 201.0f; mStatus = 2; diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index a1349e9fe5..55a240abac 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -17,8 +17,9 @@ #include "d/d_msg_scrn_arrow.h" #include "d/d_lib.h" -#ifdef TARGET_PC +#if TARGET_PC #include "dusk/achievements.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/ui/touch_controls.hpp" #include "dusk/version.hpp" @@ -29,9 +30,7 @@ static void enable_turn_page_controls(bool enabled) { dusk::ui::set_control_override(dusk::ui::Control::L, controlOverride); dusk::ui::set_control_override(dusk::ui::Control::R, controlOverride); } -#endif -#if TARGET_PC #define D_MENU_LETTER_LINE_MAX (dusk::version::isRegionJpn() ? 9 : 12) #elif VERSION == VERSION_GCN_JPN #define D_MENU_LETTER_LINE_MAX 9 @@ -226,6 +225,7 @@ void dMenu_Letter_c::_draw() { if (mpArchive == NULL) { return; } + IF_DUSK(presentAnims()); J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort(); u8 prevAlpha = mpBlackTex->getAlpha(); mpBlackTex->setAlpha(0xff); @@ -531,7 +531,7 @@ void dMenu_Letter_c::slide_right_init() { } void dMenu_Letter_c::slide_right_move() { - cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f); + IF_NOT_DUSK(cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f)); if (fabsf(field_0x358) < 0.1f) { field_0x358 = 0.0f; mProcess = 0; @@ -552,7 +552,7 @@ void dMenu_Letter_c::slide_left_init() { } void dMenu_Letter_c::slide_left_move() { - cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f); + IF_NOT_DUSK(cLib_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f)); if (fabsf(field_0x358) < 0.1f) { field_0x358 = 0.0f; mProcess = 0; @@ -607,7 +607,7 @@ void dMenu_Letter_c::read_open_init() { void dMenu_Letter_c::read_open_move() { s16 openLetterFrame = g_drawHIO.mLetterSelectScreen.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a++; + IF_NOT_DUSK(field_0x36a++); if (field_0x36a >= openLetterFrame) { mProcess = 4; for (int i = 0; i < 2; i++) { @@ -695,7 +695,7 @@ void dMenu_Letter_c::read_next_fadeout_init() { void dMenu_Letter_c::read_next_fadeout_move() { s16 closeWindowFrame = g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::WINDOW_FRAME]; - field_0x36a--; + IF_NOT_DUSK(field_0x36a--); if (field_0x36a <= 0) { mProcess = 6; for (int i = 0; i < 2; i++) { @@ -744,7 +744,7 @@ void dMenu_Letter_c::read_next_fadein_init() { void dMenu_Letter_c::read_next_fadein_move() { s16 openLetterFrame = g_drawHIO.mLetterSelectScreen.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a++; + IF_NOT_DUSK(field_0x36a++); if (field_0x36a >= openLetterFrame) { mProcess = 4; for (int i = 0; i < 2; i++) { @@ -773,7 +773,7 @@ void dMenu_Letter_c::read_close_init() { void dMenu_Letter_c::read_close_move() { s16 closeLetterFrame = g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::LETTER_FRAME]; - field_0x36a--; + IF_NOT_DUSK(field_0x36a--); if (field_0x36a <= 0) { mProcess = 0; for (int i = 0; i < 2; i++) { @@ -792,6 +792,41 @@ void dMenu_Letter_c::read_close_move() { } } +#if TARGET_PC +void dMenu_Letter_c::presentAnims() { + if (mProcess == 1 || mProcess == 2) { + dusk::vdt::present_addCalc2(&field_0x358, 0.0f, 0.5f, 50.0f, 0.1f); + return; + } + + const auto& hio = g_drawHIO.mLetterSelectScreen; + const bool opening = mProcess == 3 || mProcess == 6; + const bool page = mProcess == 5 || mProcess == 6; + if (!opening && mProcess != 5 && mProcess != 7) { + return; + } + const f32 duration = opening ? hio.mOpenFrame[dMeter_drawLetterHIO_c::LETTER_FRAME] : + hio.mCloseFrame[page ? dMeter_drawLetterHIO_c::WINDOW_FRAME : dMeter_drawLetterHIO_c::LETTER_FRAME]; + dusk::vdt::advance_toward_frame(field_0x36a, opening ? duration : 0.0f, 1.0f); + const f32 alpha = duration <= 0.0f ? (opening ? 1.0f : 0.0f) : + dusk::vdt::clamped_fraction(field_0x36a, duration); + for (int i = 0; i < 2; i++) { + if (page) { + field_0x2ec[i]->setAlphaRate(alpha); + if (field_0x2f4[i] != NULL) { + field_0x2f4[i]->setAlphaRate(alpha); + } + } else { + mpTextParent[i]->scale(hio.mLetterWindowScale * alpha, hio.mLetterWindowScale * alpha); + mpTextParent[i]->setAlphaRate(alpha); + } + } + if (!page) { + mpBlackTex->setAlpha(hio.mWindowBGAlpha * alpha); + } +} +#endif + void dMenu_Letter_c::screenSetMenu() { static const u64 tag_sub0[6] = { MULTI_CHAR('menu_t0s'), MULTI_CHAR('menu_t1s'), MULTI_CHAR('menu_t2s'), MULTI_CHAR('menu_t3s'), MULTI_CHAR('menu_t4s'), MULTI_CHAR('menu_t5s'), diff --git a/src/d/d_menu_map_common.cpp b/src/d/d_menu_map_common.cpp index c18f5825ff..c4c1485826 100644 --- a/src/d/d_menu_map_common.cpp +++ b/src/d/d_menu_map_common.cpp @@ -6,6 +6,10 @@ #include "d/d_select_cursor.h" #include "global.h" +#if TARGET_PC +#include "dusk/interp/user_interface.h" +#endif + struct map_icon_size_t { f32 size_x; f32 size_y; @@ -584,10 +588,16 @@ void dMenuMapCommon_c::setBlendRatio(u8 i_iconNo, f32 param_2, f32 param_3) { } void dMenuMapCommon_c::blinkMove(s16 param_1) { +#if TARGET_PC + f32 phase = mBlinkTimer + 15.0f; + dusk::vdt::advance_looping_frame(phase, 1.0f, std::max(0, (int)param_1) + 15.0f); + mBlinkTimer = phase - 15.0f; +#else mBlinkTimer++; if (mBlinkTimer >= param_1) { S16_SUB(mBlinkTimer, param_1 + 15); } +#endif if (mBlinkTimer < 0) { mBlinkAlpha = 1.0f; @@ -610,10 +620,20 @@ void dMenuMapCommon_c::moveLightDropAnime() { u8 flash_start_alpha = g_fmapHIO.mMapIconHIO.mLightDropFlashStartAlphaOut[bVar6]; u8 flash_end_alpha = g_fmapHIO.mMapIconHIO.mLightDropFlashEndAlphaOut[bVar6]; +#if TARGET_PC + if (flash_frame_num <= 0) { + mLightDropFlashTimer = 0.0f; + _c80 = flash_start_alpha; + _c7c = flash_start_scale; + return; + } + dusk::vdt::advance_looping_frame(mLightDropFlashTimer, 1.0f, flash_frame_num); +#else mLightDropFlashTimer++; if (mLightDropFlashTimer >= flash_frame_num) { mLightDropFlashTimer -= flash_frame_num; } +#endif f32 fVar7; if (mLightDropFlashTimer <= flash_frame_num / 2.0f) { diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index 6790684e7b..fa10149e08 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -27,6 +27,8 @@ #include "JSystem/JAudio2/JASDriverIF.h" #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/ui/touch_controls.hpp" #include "dusk/version.hpp" @@ -568,7 +570,7 @@ skip: } (this->*process[OPTION_PROC(field_0x3ef)])(); - mpSelectScreen->animation(); + IF_NOT_DUSK(mpSelectScreen->animation()); if (oldValue != field_0x3ef) { (this->*init[OPTION_PROC(field_0x3ef)])(); } @@ -578,6 +580,11 @@ skip: void dMenu_Option_c::_draw() { if (mpArchive != NULL) { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + presentAnims(); + } +#endif J2DGrafContext* ctx = dComIfGp_getCurrentGrafPort(); #if (PLATFORM_WII || PLATFORM_SHIELD) if (mpCalibration != NULL && field_0x3f4 != 5 && field_0x3f4 != 0 && field_0x3f4 != 4) { @@ -667,6 +674,10 @@ bool dMenu_Option_c::checkRightTrigger() { } void dMenu_Option_c::setAnimation() { +#if TARGET_PC + dusk::vdt::present_looping(field_0x3c0, field_0x2c, 2.0f); + dusk::vdt::present_looping(field_0x3c4, field_0x30, 2.0f); +#else s16 frameMax; field_0x3c0 += 2; @@ -682,6 +693,7 @@ void dMenu_Option_c::setAnimation() { field_0x3c4 = field_0x3c4 - frameMax; } field_0x30->setFrame(field_0x3c4); +#endif } bool dMenu_Option_c::_open() { @@ -1014,6 +1026,7 @@ void dMenu_Option_c::change_init() { } void dMenu_Option_c::change_move() { +#if !TARGET_PC f32 x = 0.0f; if (field_0x3da > 0) { @@ -1022,15 +1035,16 @@ void dMenu_Option_c::change_move() { field_0x3da++; } u8 index; +#endif switch (OPTION_PROC(field_0x3f5)) { case PROC_ATTEN_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setAttenString(); } break; case PROC_RUBY_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { IF_DUSK_BLOCK(dusk::version::isRegionJpn()) setRubyString(); @@ -1038,18 +1052,19 @@ void dMenu_Option_c::change_move() { } break; case PROC_VIB_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setVibString(); } break; case PROC_SOUND_e: - index = field_0x3f5; + IF_NOT_DUSK(index = field_0x3f5); if (field_0x3da == 0) { setSoundString(); } break; } +#if !TARGET_PC if (field_0x3da > 0) { f32 initPosX = (5 - field_0x3da) / 5.0f; if (mpMenuText[index][3] != NULL) { @@ -1069,6 +1084,7 @@ void dMenu_Option_c::change_move() { mpMenuText[index][i]->paneTrans(x + field_0x3b4, 0.0f); } } +#endif if (field_0x3da == 0) { for (int i = 0; i < 6; i++) { for (int j = 2; j < 6; j++) { @@ -1102,18 +1118,20 @@ void dMenu_Option_c::confirm_open_move() { u32 status = mpWarning->getStatus(); bool yesNoMenuMove = yesnoMenuMoveAnm(); +#if !TARGET_PC if (field_0x374 != 1.0f) { cLib_addCalc2(&field_0x374, 1.0f, 0.4f, 0.5f); if (fabsf(field_0x374 - 1.0f) < 0.1f) { field_0x374 = 1.0f; } } +#endif if (status == 1 && yesNoMenuMove == 1 && field_0x374 == 1.0f) { yesnoCursorShow(); field_0x3ef = OPTION_SELECT(PROC_CONFIRM_MOVE_MOVE_e); } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_move_init() { @@ -1143,13 +1161,11 @@ void dMenu_Option_c::confirm_move_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } yesnoSelectAnmSet(); field_0x3ef = OPTION_SELECT(PROC_CONFIRM_SELECT_MOVE_e); mpWarning->_move(); - setAnimation(); return; } if (clicked) { @@ -1157,7 +1173,6 @@ void dMenu_Option_c::confirm_move_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } } @@ -1192,7 +1207,7 @@ void dMenu_Option_c::confirm_move_move() { } } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_select_init() { @@ -1225,14 +1240,13 @@ void dMenu_Option_c::confirm_select_move() { field_0x3ef = OPTION_SELECT(PROC_CONFIRM_CLOSE_MOVE_e); dMeter2Info_set2DVibrationM(); mpWarning->_move(); - setAnimation(); return; } #endif field_0x3ef = OPTION_SELECT(PROC_CONFIRM_MOVE_MOVE_e); } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::confirm_close_init() { @@ -1247,12 +1261,14 @@ void dMenu_Option_c::confirm_close_init() { void dMenu_Option_c::confirm_close_move() { u32 status = mpWarning->getStatus(); yesnoMenuMoveAnm(); +#if !TARGET_PC if (field_0x374 != 0.0f) { cLib_addCalc2(&field_0x374, 0.0f, 0.4f, 0.5); if (fabsf(field_0x374) < 0.1f) { field_0x374 = 0.0f; } } +#endif if (status == 1 && status == 1 && field_0x374 == 0.0f) { if (field_0x3f7 == 1) { if (field_0x3f9 == 1) { @@ -1290,7 +1306,7 @@ void dMenu_Option_c::confirm_close_move() { } } mpWarning->_move(); - setAnimation(); + IF_NOT_DUSK(setAnimation()); } void dMenu_Option_c::tv_open1_move() { @@ -2660,6 +2676,7 @@ void dMenu_Option_c::yesnoMenuMoveAnmInitSet(int param_0, int param_1) { bool dMenu_Option_c::yesnoMenuMoveAnm() { bool ret; if (field_0x3c8[2] != field_0x3c8[3]) { +#if !TARGET_PC if (field_0x3c8[2] < field_0x3c8[3]) { field_0x3c8[2] += 2; if (field_0x3c8[2] > field_0x3c8[3]) { @@ -2674,6 +2691,7 @@ bool dMenu_Option_c::yesnoMenuMoveAnm() { field_0x20->setFrame(field_0x3c8[2]); mpYesNoSelBase_c[0]->getPanePtr()->animationTransform(); mpYesNoSelBase_c[1]->getPanePtr()->animationTransform(); +#endif ret = 0; } if (field_0x3c8[2] == field_0x3c8[3]) { @@ -2698,6 +2716,101 @@ static s32 OptYnSelStartFrameTbl[2] = {1251, 1236}; static s32 OptYnSelEndFrameTbl[2] = {1236, 1251}; +#if TARGET_PC +void dMenu_Option_c::presentAnims() { + if (field_0x3da != 0.0f) { + dusk::vdt::advance_toward_frame(field_0x3da, 0.0f, 1.0f); + const u8 index = field_0x3f5; + if (field_0x3da == 0.0f) { + switch (OPTION_PROC(field_0x3f5)) { + case PROC_ATTEN_e: + setAttenString(); + break; + case PROC_RUBY_e: + IF_DUSK_BLOCK(dusk::version::isRegionJpn()) + setRubyString(); + IF_DUSK_BLOCK_END + break; + case PROC_VIB_e: + setVibString(); + break; + case PROC_SOUND_e: + setSoundString(); + break; + } + for (int i = 0; i < 6; i++) { + for (int j = 2; j < 6; j++) { + if (mpMenuText[i][j] != NULL) { + mpMenuText[i][j]->hide(); + } + } + } + for (int i = 0; i < 2; i++) { + if (mpMenuText[index][i] != NULL) { + mpMenuText[index][i]->show(); + mpMenuText[index][i]->paneTrans(field_0x3b4, 0.0f); + } + } + } else { + f32 x = 0.0f; + if (field_0x3da > 0) { + f32 initPosX = (5 - field_0x3da) / 5.0f; + if (mpMenuText[index][3] != NULL) { + x = mpMenuText[index][3]->getInitPosX() - mpMenuText[index][0]->getInitPosX(); + } + x *= initPosX; + } else if (field_0x3da < 0) { + f32 initPosX = (field_0x3da + 5) / 5.0f; + if (mpMenuText[index][5] != NULL) { + x = mpMenuText[index][5]->getInitPosX() - mpMenuText[index][0]->getInitPosX(); + } + x *= initPosX; + } + for (int i = 0; i < 6; i++) { + if (mpMenuText[index][i] != NULL) { + mpMenuText[index][i]->show(); + mpMenuText[index][i]->paneTrans(x + field_0x3b4, 0.0f); + } + } + } + } + setAnimation(); + if (OPTION_PROC(field_0x3ef) == PROC_CONFIRM_OPEN_MOVE_e) { + dusk::vdt::present_addCalc2(&field_0x374, 1.0f, 0.4f, 0.5f, 0.1f); + } else if (OPTION_PROC(field_0x3ef) == PROC_CONFIRM_CLOSE_MOVE_e) { + dusk::vdt::present_addCalc2(&field_0x374, 0.0f, 0.4f, 0.5f, 0.1f); + } + if (mpSelectScreen != NULL) { + mpSelectScreen->animation(); + } + presentLayoutAnims(); + for (int i = 0; i < 2; ++i) { + mpYesNoTxt_c[i]->presentAnime(); + mpYesNoCurWaku_c[i]->presentAnime(); + mpYesNoCurWakuG0_c[i]->presentAnime(); + mpYesNoCurWakuG1_c[i]->presentAnime(); + } +} + +void dMenu_Option_c::presentLayoutAnims() { + if (mpWarning != NULL) { + mpWarning->presentAnims(); + } + + if (field_0x3c8[2] != field_0x3c8[3]) { + dusk::vdt::present_shared(field_0x3c8[2], field_0x3c8[3], field_0x20, + {mpYesNoSelBase_c[0] != NULL ? mpYesNoSelBase_c[0]->getPanePtr() : NULL, + mpYesNoSelBase_c[1] != NULL ? mpYesNoSelBase_c[1]->getPanePtr() : NULL}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mpYesNoSelBase_c[i] != NULL ? mpYesNoSelBase_c[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x3c8[i], pane, field_0x24, (f32)OptYnSelStartFrameTbl[i], + field_0x28, (f32)OptYnSelEndFrameTbl[i]); + } +} +#endif + u8 dMenu_Option_c::yesnoSelectMoveAnm() { u8 ret = 0; bool bVar1; @@ -2705,6 +2818,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { if (field_0x3fa != 0xff) { if (field_0x3c8[field_0x3fa] != OptYnSelStartFrameTbl[field_0x3fa]) { +#if !TARGET_PC if (field_0x3c8[field_0x3fa] < OptYnSelStartFrameTbl[field_0x3fa]) { field_0x3c8[field_0x3fa] += 2; if (field_0x3c8[field_0x3fa] > OptYnSelStartFrameTbl[field_0x3fa]) { @@ -2718,6 +2832,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } field_0x24->setFrame(field_0x3c8[field_0x3fa]); mpYesNoSelBase_c[field_0x3fa]->getPanePtr()->animationTransform(); +#endif } if (field_0x3c8[field_0x3fa] == OptYnSelStartFrameTbl[field_0x3fa]) { bVar1 = true; @@ -2729,6 +2844,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } if (field_0x3f9 != 0xff) { if (field_0x3c8[field_0x3f9] != OptYnSelEndFrameTbl[field_0x3f9]) { +#if !TARGET_PC if (field_0x3c8[field_0x3f9] < OptYnSelEndFrameTbl[field_0x3f9]) { field_0x3c8[field_0x3f9] += 2; if (field_0x3c8[field_0x3f9] > OptYnSelEndFrameTbl[field_0x3f9]) { @@ -2742,6 +2858,7 @@ u8 dMenu_Option_c::yesnoSelectMoveAnm() { } field_0x28->setFrame(field_0x3c8[field_0x3f9]); mpYesNoSelBase_c[field_0x3f9]->getPanePtr()->animationTransform(); +#endif } if (field_0x3c8[field_0x3f9] == OptYnSelEndFrameTbl[field_0x3f9]) { bVar2 = true; diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index 73832f8918..f1161af804 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -30,8 +30,8 @@ #include #if TARGET_PC -#include "dusk/game_clock.h" -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/menus.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/settings.h" #include "dusk/ui/touch_controls.hpp" @@ -190,23 +190,9 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i field_0x682 = 0xc000; break; } -#if TARGET_PC - mCursorInterpPrevX = 0.0f; - mCursorInterpPrevY = 0.0f; - mCursorInterpCurrX = 0.0f; - mCursorInterpCurrY = 0.0f; - mCursorInterpPrevAngle = 0; - mCursorInterpCurrAngle = 0; - mCursorInterpPrevAngular = false; - mCursorInterpCurrAngular = false; - mCursorInterpInit = false; - mPointerTouchPressHoveredCurrent = false; -#endif + IF_DUSK(mPointerTouchPressHoveredCurrent = false); for (int i = 0; i < 4; i++) { field_0x674[i] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i] = 0.0f; -#endif field_0x518[i] = 0.0f; field_0x528[i] = 0.0f; field_0x538[i] = 0.0f; @@ -567,6 +553,7 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i } dMenu_Ring_c::~dMenu_Ring_c() { + IF_DUSK(dusk::interp::erase_owned_samples(this)); mpHeap->getTotalFreeSize(); dMeter2Info_setItemExplainWindowStatus(0); for (int i = 0; i < 4; i++) { @@ -701,9 +688,17 @@ void dMenu_Ring_c::_move() { mRingCursorScale = g_ringHIO.mCursorScale; mpDrawCursor->setScale(g_ringHIO.mCursorScale); } + IF_DUSK(dusk::interp::capture_menu_values(this, mCenterPosX, mCenterPosY, mAlphaRate, + field_0x674[0], field_0x674[1], field_0x674[2], field_0x674[3])); } void dMenu_Ring_c::_draw() { +#if TARGET_PC + dusk::interp::ScopedMenuValues pose(this, mCenterPosX, mCenterPosY, mAlphaRate); + if (mDrawFlag == 0) { + dusk::vdt::advance_looping_frame(field_0x684, 1.0f, g_ringHIO.mItemAlphaFlashDuration); + } +#endif J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); grafPort->setup2D(); if (mDrawFlag == 0) { @@ -756,71 +751,7 @@ void dMenu_Ring_c::_draw() { } else { drawSelectItem(); drawItem2(); -#if TARGET_PC - f32 simX = 0.0f; - f32 simY = 0.0f; - bool restoreSimPos = false; - if (dusk::interp::is_enabled() && mAlphaRate >= 1.0f) { - simX = mpDrawCursor->getPositionX(); - simY = mpDrawCursor->getPositionY(); - - const bool isAngular = (mStatus == STATUS_MOVE) && !mDirectSelectActive; - - if (dusk::interp::get_ui_tick_pending()) { - mCursorInterpPrevX = mCursorInterpCurrX; - mCursorInterpPrevY = mCursorInterpCurrY; - mCursorInterpPrevAngle = mCursorInterpCurrAngle; - mCursorInterpPrevAngular = mCursorInterpCurrAngular; - - mCursorInterpCurrX = simX; - mCursorInterpCurrY = simY; - mCursorInterpCurrAngle = field_0x66e; - mCursorInterpCurrAngular = isAngular; - - // reset prev = curr for first render pass or - // when angle modes prev/curr differ - // to prevent arrival jitter - if (!mCursorInterpInit || - mCursorInterpPrevAngular != mCursorInterpCurrAngular) { - mCursorInterpPrevX = mCursorInterpCurrX; - mCursorInterpPrevY = mCursorInterpCurrY; - mCursorInterpPrevAngle = mCursorInterpCurrAngle; - mCursorInterpPrevAngular = mCursorInterpCurrAngular; - mCursorInterpInit = true; - } - } - if (mCursorInterpInit) { - const f32 step = dusk::interp::get_interpolation_step(); - if (mCursorInterpPrevAngular && mCursorInterpCurrAngular) { - const s16 delta = mCursorInterpCurrAngle - mCursorInterpPrevAngle; - const s16 lerpedAngle = mCursorInterpPrevAngle + (s16)(delta * step); - - // yoinked from stick_move_proc() - const f32 x = g_ringHIO.mItemRingPosX + FB_WIDTH_BASE / 2 + - mRingRadiusH * cM_ssin(lerpedAngle); - const f32 y = g_ringHIO.mItemRingPosY + FB_HEIGHT_BASE / 2 + - mRingRadiusV * cM_scos(lerpedAngle); - mpDrawCursor->setPos(x, y); - } else { - mpDrawCursor->setPos( - mCursorInterpPrevX + (mCursorInterpCurrX - mCursorInterpPrevX) * step, - mCursorInterpPrevY + (mCursorInterpCurrY - mCursorInterpPrevY) * step - ); - } - restoreSimPos = true; - } - } else { - mCursorInterpInit = false; - } -#endif mpDrawCursor->draw(); -#if TARGET_PC - // prevents offsetting at destination on the next frame - // since stick_wait_proc doesn't call setPos and we clobbered mPositionX/Y - if (restoreSimPos) { - mpDrawCursor->setPos(simX, simY); - } -#endif mpItemExplain->trans(mCenterPosX, mCenterPosY); mpItemExplain->draw((J2DOrthoGraph*)grafPort); drawFlag0(); @@ -851,7 +782,7 @@ bool dMenu_Ring_c::isOpen() { mCenterPosX = (1.0f - mAlphaRate) * -FB_WIDTH_BASE; mCenterPosY = 0.0f; } - if (mOpenCloseFrames >= g_ringHIO.mOpenFrames) { + if (mOpenCloseFrames >= g_ringHIO.mOpenFrames) { // Opening is finished, set to g_ringHIO.mCloseFrames as a // preparation for when the player closes the item wheel mOpenCloseFrames = g_ringHIO.mCloseFrames; @@ -868,6 +799,8 @@ bool dMenu_Ring_c::isOpen() { mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.6f, 0.5f); } + IF_DUSK(dusk::interp::capture_menu_values(this, mCenterPosX, mCenterPosY, mAlphaRate, + field_0x674[0], field_0x674[1], field_0x674[2], field_0x674[3])); return opened; } @@ -931,6 +864,8 @@ bool dMenu_Ring_c::isClose() { mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.6f, 0.5f); } + IF_DUSK(dusk::interp::capture_menu_values(this, mCenterPosX, mCenterPosY, mAlphaRate, + field_0x674[0], field_0x674[1], field_0x674[2], field_0x674[3])); return closed; } @@ -1223,9 +1158,6 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[0] != dComIfGs_getMixItemIndex(0)) { field_0x674[0] = 1; -#if TARGET_PC - mSelectItemSlideElapsed[0] = 0.0f; -#endif } } else if (field_0x6b3 == 1) { field_0x538[0] = g_ringHIO.mUnselectItemScale; @@ -1234,9 +1166,6 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[1] != dComIfGs_getMixItemIndex(1)) { field_0x674[1] = 1; -#if TARGET_PC - mSelectItemSlideElapsed[1] = 0.0f; -#endif } } if (field_0x674[0] == 1) { @@ -1460,10 +1389,12 @@ void dMenu_Ring_c::setMixItem() { } void dMenu_Ring_c::drawItem() { +#if !TARGET_PC field_0x684++; if (field_0x684 >= g_ringHIO.mItemAlphaFlashDuration) { field_0x684 = 0; } +#endif s32 halfFlashDuration = g_ringHIO.mItemAlphaFlashDuration / 2; f32 fVar16; if (field_0x684 < halfFlashDuration) { @@ -1804,18 +1735,25 @@ void dMenu_Ring_c::setSelectItem(int i_idx, u8 i_itemNo) { mpSelectItemTexBuf[i_idx][field_0x6be[i_idx]][0]->height / 48.0f * texScale; } +#if TARGET_PC +void dMenu_Ring_c::advanceSelectItem() { + mpDrawCursor->setPos(mpDrawCursor->getPositionX(), mpDrawCursor->getPositionY()); + for (int i = 0; i < 4; ++i) { + if (field_0x674[i] >= 10) { + setSelectItemForce(i); + } else if (field_0x674[i] != 0) { + ++field_0x674[i]; + } + } + dusk::interp::capture_menu_values(this, mCenterPosX, mCenterPosY, mAlphaRate, field_0x674[0], + field_0x674[1], field_0x674[2], field_0x674[3]); +} +#endif + void dMenu_Ring_c::drawSelectItem() { for (int i = 0; i < 4; i++) { if (field_0x674[i] != 0) { -#if TARGET_PC - mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this); - const f32 u = std::min(mSelectItemSlideElapsed[i] / (dusk::game_clock::kSimPeriod * 10.0f), 1.0f); - if (u >= 1.0f) { - setSelectItemForce(i); - } else { -#else - if (field_0x674[i] < 10) { -#endif + if (DUSK_IF_ELSE(true, field_0x674[i] < 10)) { #if TARGET_PC f32 initSizeX; f32 initSizeY; @@ -1852,7 +1790,9 @@ void dMenu_Ring_c::drawSelectItem() { #endif #if TARGET_PC - f32 fVar14 = 0.1f + 0.8f * u; + const auto& samples = dusk::interp::get(this).values; + const f32 frame = field_0x674[i] == 1 ? 1.0f : samples.read(3 + i, (f32)field_0x674[i]); + f32 fVar14 = std::clamp(frame, 1.0f, 9.0f) / 10.0f; #else f32 fVar14 = field_0x674[i] / 10.0f; #endif @@ -1891,9 +1831,6 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { if (field_0x674[i_idx] != 0) { dComIfGs_setSelectItemIndex(i_idx, field_0x6b4[i_idx]); field_0x674[i_idx] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i_idx] = 0.0f; -#endif } } else if (field_0x674[i_idx] != 0) { for (int i = 0; i < 2; i++) { @@ -1901,9 +1838,6 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { dComIfGs_setSelectItemIndex(i, field_0x6b4[i]); } field_0x674[i_idx] = 0; -#if TARGET_PC - mSelectItemSlideElapsed[i_idx] = 0.0f; -#endif } } diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index a2b1a5dd2c..1fec2ec552 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -22,7 +22,8 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/mods/svc/save.hpp" #include "dusk/settings.h" @@ -102,6 +103,8 @@ dMenu_save_c::dMenu_save_c() { } mpSelectMoveBase = NULL; + IF_DUSK(mYesNoSelAnmCol = 0); + for (int i = 0; i < 2; i++) { mpNoYes[i] = NULL; mNoYesBase[i] = NULL; @@ -809,9 +812,7 @@ void dMenu_save_c::_move() { } (this->*MenuSaveProc[mMenuProc])(); -#if !TARGET_PC - saveSelAnm(); -#endif + IF_NOT_DUSK(saveSelAnm()); if (mWarning != NULL) { mWarning->_move(); @@ -828,7 +829,10 @@ void dMenu_save_c::saveSelAnm() { } void dMenu_save_c::selFileWakuAnm() { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::present_looping(mFileWakuAnmFrame, mpFileWakuAnm, 2.0f); + dusk::vdt::present_looping(mFileWakuRotAnmFrame, mpFileWakuRotAnm, 2.0f); +#else mFileWakuAnmFrame += 2; if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) { mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax(); @@ -838,13 +842,17 @@ void dMenu_save_c::selFileWakuAnm() { if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) { mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax(); } - IF_DUSK_BLOCK_END mpFileWakuAnm->setFrame(mFileWakuAnmFrame); mpFileWakuRotAnm->setFrame(mFileWakuRotAnmFrame); +#endif } void dMenu_save_c::bookIconAnm() { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::present_looping(field_0x154, field_0x150, 2.0f); + dusk::vdt::present_looping(field_0x15c, field_0x158, 2.0f); + dusk::vdt::present_looping(field_0x164, field_0x160, 2.0f); +#else field_0x154 += 2; if (field_0x154 >= field_0x150->getFrameMax()) { field_0x154 -= field_0x150->getFrameMax(); @@ -859,10 +867,10 @@ void dMenu_save_c::bookIconAnm() { if (field_0x164 >= field_0x160->getFrameMax()) { field_0x164 -= field_0x160->getFrameMax(); } - IF_DUSK_BLOCK_END field_0x150->setFrame(field_0x154); field_0x158->setFrame(field_0x15c); field_0x160->setFrame(field_0x164); +#endif } void dMenu_save_c::memCardWatch() { @@ -1836,6 +1844,7 @@ void dMenu_save_c::openSaveSelect3() { var_r29 = ketteiTxtDispAnm(); if (field_0x74[mSelectedFile] != SelEndFrameTbl[mSelectedFile]) { +#if !TARGET_PC field_0x74[mSelectedFile] += 2; if (field_0x74[mSelectedFile] > SelEndFrameTbl[mSelectedFile]) { @@ -1844,6 +1853,7 @@ void dMenu_save_c::openSaveSelect3() { field_0x40->setFrame(field_0x74[mSelectedFile]); mpSelData[mSelectedFile]->getPanePtr()->animationTransform(); +#endif var_r28 = false; } } @@ -2051,6 +2061,7 @@ void dMenu_save_c::saveSelectMoveAnime() { selWakuAnmComplete = selectWakuAlpahAnm(mLastSelFile); if (field_0x74[mLastSelFile] != SelStartFrameTbl[mLastSelFile]) { +#if !TARGET_PC field_0x74[mLastSelFile] -= 2; if (field_0x74[mLastSelFile] < SelStartFrameTbl[mLastSelFile]) { @@ -2059,6 +2070,7 @@ void dMenu_save_c::saveSelectMoveAnime() { field_0x44->setFrame(field_0x74[mLastSelFile]); mpSelData[mLastSelFile]->getPanePtr()->animationTransform(); +#endif var_r29 = false; } } @@ -2069,6 +2081,7 @@ void dMenu_save_c::saveSelectMoveAnime() { var_r28 = mpBookWaku[mSelectedFile]->alphaAnime(g_msHIO.mEffectDispFrames, 0, 0xFF, 1); if (field_0x74[mSelectedFile] != SelEndFrameTbl[mSelectedFile]) { +#if !TARGET_PC field_0x74[mSelectedFile] += 2; if (field_0x74[mSelectedFile] > SelEndFrameTbl[mSelectedFile]) { @@ -2077,6 +2090,7 @@ void dMenu_save_c::saveSelectMoveAnime() { field_0x40->setFrame(field_0x74[mSelectedFile]); mpSelData[mSelectedFile]->getPanePtr()->animationTransform(); +#endif var_r27 = false; } } @@ -2191,6 +2205,8 @@ void dMenu_save_c::saveMoveDisp2() { } void dMenu_save_c::yesnoSelectAnmSet(u8 param_0) { + IF_DUSK(mYesNoSelAnmCol = param_0); + if (mYesNoPrevCursor != 0xFF) { yesnoWakuAlpahAnmInit(mYesNoPrevCursor, 255, 0, g_msHIO.mSelectFrames); mpNoYes[mYesNoPrevCursor]->getPanePtr()->setAnimation(field_0x40); @@ -2528,6 +2544,7 @@ void dMenu_save_c::selectDataBaseMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::selectDataBaseMoveAnm() { if (mDataBaseMoveAnmFrame != mDataBaseMoveFrameMax) { +#if !TARGET_PC if (mDataBaseMoveAnmFrame < mDataBaseMoveFrameMax) { mDataBaseMoveAnmFrame += 2; @@ -2544,6 +2561,7 @@ bool dMenu_save_c::selectDataBaseMoveAnm() { field_0x40->setFrame(mDataBaseMoveAnmFrame); mpSelectMoveBase->getPanePtr()->animationTransform(); +#endif return false; } else { #if TARGET_PC @@ -2607,6 +2625,7 @@ void dMenu_save_c::selectDataMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::selectDataMoveAnm() { if (field_0x74[mSelectedFile] != field_0x80) { +#if !TARGET_PC if (field_0x74[mSelectedFile] < field_0x80) { field_0x74[mSelectedFile] += 2; @@ -2625,6 +2644,7 @@ bool dMenu_save_c::selectDataMoveAnm() { for (int i = 0; i < 3; i++) { mpSelData[i]->getPanePtr()->animationTransform(); } +#endif return false; } else { @@ -2662,6 +2682,7 @@ void dMenu_save_c::yesnoMenuMoveAnmInitSet(int anmFrame, int frameMax, u8 param_ bool dMenu_save_c::yesnoMenuMoveAnm() { if (mYesNoMoveAnmFrame != mYesNoMoveAnmMax) { +#if !TARGET_PC if (mYesNoMoveAnmFrame < mYesNoMoveAnmMax) { mYesNoMoveAnmFrame += 2; @@ -2679,6 +2700,7 @@ bool dMenu_save_c::yesnoMenuMoveAnm() { field_0x48->setFrame(mYesNoMoveAnmFrame); mpNoYes[0]->getPanePtr()->animationTransform(); mpNoYes[1]->getPanePtr()->animationTransform(); +#endif return false; } else { mpNoYes[0]->getPanePtr()->setAnimation((J2DAnmTransformKey*)NULL); @@ -2713,6 +2735,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { bool var_r30 = true; if (mYesNoPrevCursor != 0xFF) { if (field_0x8c[mYesNoPrevCursor] != YnSelStartFrameTbl[mYesNoPrevCursor][param_0]) { +#if !TARGET_PC if (field_0x8c[mYesNoPrevCursor] < YnSelStartFrameTbl[mYesNoPrevCursor][param_0]) { field_0x8c[mYesNoPrevCursor] += 2; @@ -2729,6 +2752,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { field_0x40->setFrame(field_0x8c[mYesNoPrevCursor]); mpNoYes[mYesNoPrevCursor]->getPanePtr()->animationTransform(); +#endif var_r30 = false; } } @@ -2736,6 +2760,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { bool var_r7 = true; if (mYesNoCursor != 0xFF) { if (field_0x8c[mYesNoCursor] != YnSelEndFrameTbl[mYesNoCursor][param_0]) { +#if !TARGET_PC if (field_0x8c[mYesNoCursor] < YnSelEndFrameTbl[mYesNoCursor][param_0]) { field_0x8c[mYesNoCursor] += 2; @@ -2752,6 +2777,7 @@ bool dMenu_save_c::yesnoSelectMoveAnm(u8 param_0) { field_0x44->setFrame(field_0x8c[mYesNoCursor]); mpNoYes[mYesNoCursor]->getPanePtr()->animationTransform(); +#endif var_r7 = false; } } @@ -2798,6 +2824,7 @@ void dMenu_save_c::errorMoveAnmInitSet(int param_0, int param_1) { bool dMenu_save_c::errorMoveAnm() { if (field_0xb8 != field_0xbc) { +#if !TARGET_PC if (field_0xb8 < field_0xbc) { field_0xb8 += 2; @@ -2814,6 +2841,7 @@ bool dMenu_save_c::errorMoveAnm() { field_0x4c->setFrame(field_0xb8); field_0xb4->animationTransform(); +#endif return 0; } else { field_0x4c->setFrame(field_0xbc); @@ -3072,22 +3100,85 @@ void dMenu_save_c::menuSaveWide() { } #endif } + +void dMenu_save_c::presentLayoutAnims() { + if (mWarning != NULL) { + mWarning->presentAnims(); + } + + dusk::vdt::present_toward(mDataBaseMoveAnmFrame, (f32)mDataBaseMoveFrameMax, field_0x40, mpSelectMoveBase != NULL ? mpSelectMoveBase->getPanePtr() : NULL); + + bool sharedSel = true; + for (int i = 0; i < 3; i++) { + J2DPane* pane = mpSelData[i] != NULL ? mpSelData[i]->getPanePtr() : NULL; + if (pane == NULL || pane->mTransform != field_0x40) { + sharedSel = false; + break; + } + } + if (sharedSel && mSelectedFile != 0xFF) { + dusk::vdt::present_toward(field_0x74[mSelectedFile], (f32)field_0x80, field_0x40); + for (int i = 0; i < 3; i++) { + mpSelData[i]->getPanePtr()->animationTransform(); + } + } else { + for (int i = 0; i < 3; i++) { + J2DPane* pane = mpSelData[i] != NULL ? mpSelData[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x74[i], pane, field_0x40, (f32)SelEndFrameTbl[i], + field_0x44, (f32)SelStartFrameTbl[i]); + } + } + + if (mYesNoMoveAnmFrame != mYesNoMoveAnmMax) { + dusk::vdt::present_shared(mYesNoMoveAnmFrame, (f32)mYesNoMoveAnmMax, field_0x48, + {mpNoYes[0] != NULL ? mpNoYes[0]->getPanePtr() : NULL, + mpNoYes[1] != NULL ? mpNoYes[1]->getPanePtr() : NULL}); + } + + for (int i = 0; i < 2; i++) { + J2DPane* pane = mpNoYes[i] != NULL ? mpNoYes[i]->getPanePtr() : NULL; + dusk::vdt::present_selected(field_0x8c[i], pane, field_0x40, (f32)YnSelStartFrameTbl[i][mYesNoSelAnmCol], + field_0x44, (f32)YnSelEndFrameTbl[i][mYesNoSelAnmCol]); + } + + dusk::vdt::present_toward(field_0xb8, (f32)field_0xbc, field_0x4c, field_0xb4); + + for (int i = 0; i < 3; ++i) { + mpBookWaku[i]->presentAnime(); + mpSelWakuMoyo[i]->presentAnime(); + mpSelWakuGold[i]->presentAnime(); + mpSelWakuGold2[i]->presentAnime(); + } + for (int i = 0; i < 2; ++i) { + mNoYesBase[i]->presentAnime(); + mNoYesGold[i]->presentAnime(); + mNoYesGold2[i]->presentAnime(); + mpNoYesTxt[i]->presentAnime(); + mpErrTxtPane[i]->presentAlphaAnime(); + mpHeaderTxtPane[i]->presentAlphaAnime(); + } + mpBBtnIcon->presentAlphaAnime(); + mpABtnIcon->presentAlphaAnime(); + mpBackTxt->presentAlphaAnime(); + mpConfirmTxt->presentAlphaAnime(); +} #endif void dMenu_save_c::_draw2() { - if (field_0x21a1 == 0) { #if TARGET_PC + if (mDisplayMenu && dusk::game_clock::is_presentation_frame()) { saveSelAnm(); + presentLayoutAnims(); + menuSaveWide(); + } #endif + + if (field_0x21a1 == 0) { if (mpScrnExplain != NULL) { dComIfGd_set2DOpa(&mMenuSaveExplain); } if (mDisplayMenu) { - #if TARGET_PC - menuSaveWide(); - #endif - dComIfGd_set2DOpa(&mSaveSel); for (int i = 0; i < 3; i++) { diff --git a/src/d/d_menu_skill.cpp b/src/d/d_menu_skill.cpp index 90d4a3f92b..fddfa61b51 100644 --- a/src/d/d_menu_skill.cpp +++ b/src/d/d_menu_skill.cpp @@ -21,6 +21,7 @@ #include "dusk/version.hpp" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #endif @@ -146,6 +147,7 @@ void dMenu_Skill_c::_move() { void dMenu_Skill_c::_draw() { if (mpArchive != NULL) { + IF_DUSK(presentAnims()); J2DGrafContext* context = dComIfGp_getCurrentGrafPort(); u8 alpha = mpBlackTex->mAlpha; mpBlackTex->setAlpha(0xff); @@ -362,7 +364,7 @@ void dMenu_Skill_c::read_open_init() { void dMenu_Skill_c::read_open_move() { s16 openSkillDescFrame = g_drawHIO.mSkillListScreen.mOpenFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; - mProcFrame++; + IF_NOT_DUSK(mProcFrame++); if (mProcFrame >= openSkillDescFrame) { mProcess = PROC_OPEN_MOVE; mpTextParent->setAlphaRate(1.0f); @@ -402,7 +404,7 @@ void dMenu_Skill_c::read_close_init() { void dMenu_Skill_c::read_close_move() { s16 closeSkillDescFrame = g_drawHIO.mSkillListScreen.mCloseFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; - mProcFrame--; + IF_NOT_DUSK(mProcFrame--); if (mProcFrame <= 0) { mProcess = PROC_CLOSE_MOVE; mpTextParent->setAlphaRate(0.0f); @@ -414,6 +416,26 @@ void dMenu_Skill_c::read_close_move() { } } +#if TARGET_PC +void dMenu_Skill_c::presentAnims() { + const auto& hio = g_drawHIO.mSkillListScreen; + f32 duration; + if (mProcess == PROC_WAIT_MOVE) { + duration = hio.mOpenFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; + dusk::vdt::advance_toward_frame(mProcFrame, duration, 1.0f); + } else if (mProcess == PROC_MOVE_MOVE) { + duration = hio.mCloseFrame[dMeter_drawSkillHIO_c::SKILL_DESC]; + dusk::vdt::advance_toward_frame(mProcFrame, 0.0f, 1.0f); + } else { + return; + } + const f32 alpha = duration <= 0.0f ? (mProcess == PROC_WAIT_MOVE ? 1.0f : 0.0f) : + dusk::vdt::clamped_fraction(mProcFrame, duration); + mpTextParent->setAlphaRate(alpha); + mpBlackTex->setAlpha(hio.mWindowBGalpha * alpha); +} +#endif + void dMenu_Skill_c::screenSetMenu() { static const u64 tag_sub0[7] = { MULTI_CHAR('menu_t0s'), MULTI_CHAR('menu_t1s'), MULTI_CHAR('menu_t2s'), MULTI_CHAR('menu_t3s'), MULTI_CHAR('menu_t4s'), MULTI_CHAR('menu_t5s'), MULTI_CHAR('menu_t51'), diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 80f374cf46..aee078fb1c 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -1589,10 +1589,18 @@ int dMw_c::_create() { field_0x144 = 3; dMeter2Info_setWindowStatus(0); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; } int dMw_c::_execute() { +#if TARGET_PC + if (mpMenuRing != NULL) { + mpMenuRing->advanceSelectItem(); + } +#endif if (field_0x151 != 0) { field_0x151--; } diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index 89506e8078..c41d90ed37 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -12,6 +12,8 @@ #include "f_op/f_op_msg_mng.h" #if TARGET_PC +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #include "dusk/utilities.hpp" #include "dusk/version.hpp" @@ -431,7 +433,7 @@ void dName_c::_move() { } #endif - cursorAnm(); + IF_NOT_DUSK(cursorAnm()); } int dName_c::nameCheck() { @@ -476,6 +478,10 @@ void dName_c::playNameSet(int nameLength) { } void dName_c::cursorAnm() { +#if TARGET_PC + dusk::vdt::present_looping(mCurColAnmF, mCursorColorKey, 2.0f); + dusk::vdt::present_looping(mCurTexAnmF, mCursorTexKey, 2.0f); +#else mCurColAnmF += 2; if (mCurColAnmF >= mCursorColorKey->getFrameMax()) { mCurColAnmF -= mCursorColorKey->getFrameMax(); @@ -487,6 +493,7 @@ void dName_c::cursorAnm() { mCurTexAnmF -= mCursorTexKey->getFrameMax(); } mCursorTexKey->setFrame(mCurTexAnmF); +#endif nameIn.NameInScr->animation(); } @@ -1615,9 +1622,20 @@ void dName_c::nameWide() { #endif void dName_c::_draw() { - #if TARGET_PC - nameWide(); - #endif +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + for (int i = 0; i < 65; ++i) { + mMojiIcon[i]->presentAnime(); + } + for (int i = 0; i < 4; ++i) { + if (mMenuIcon[i] != NULL) { + mMenuIcon[i]->presentAnime(); + } + } + cursorAnm(); + nameWide(); + } +#endif dComIfGd_set2DOpa(&nameIn); dComIfGd_set2DOpa(mSelIcon); diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 7faabde76b..12caa544c1 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -19,6 +19,7 @@ #if TARGET_PC #include "dusk/autosave.h" +#include "dusk/game_clock.h" #include "dusk/game_mode.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/memory.h" @@ -147,6 +148,8 @@ s32 dScnName_c::create() { #endif dComIfGp_getVibration().Init(); + + IF_DUSK(base.draw_interp_frame = true); } return phase_state; } @@ -243,7 +246,9 @@ s32 dScnName_c::execute() { } s32 dScnName_c::draw() { + IF_DUSK_BLOCK(dusk::game_clock::is_sim_frame()) dComIfGp_getVibration().Run(); + IF_DUSK_BLOCK_END switch (mDrawProc) { case 0: diff --git a/src/d/d_select_icon.cpp b/src/d/d_select_icon.cpp index e55beaf54c..895146c986 100644 --- a/src/d/d_select_icon.cpp +++ b/src/d/d_select_icon.cpp @@ -4,14 +4,17 @@ #include "JSystem/J2DGraph/J2DAnimation.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #endif dSi_HIO_c::dSi_HIO_c() {} void dSelect_icon_c::animation() { if (field_0x10->getAlpha() != 0) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::present_looping(field_0x20, field_0x1c, field_0x2c); + dusk::vdt::present_looping(field_0x28, field_0x24, field_0x2c); +#else field_0x20 += field_0x2c; if (field_0x20 >= field_0x1c->getFrameMax()) { field_0x20 = 0.0f; @@ -22,10 +25,8 @@ void dSelect_icon_c::animation() { if (field_0x28 >= field_0x24->getFrameMax()) { field_0x28 = 0.0f; } - IF_DUSK_BLOCK_END - IF_DUSK(field_0x1c->setFrame(field_0x20)); // FRAME INTERP NOTE: Set even if not advancing - field_0x24->setFrame(field_0x28); +#endif field_0x8->animation(); field_0x10->paneScale(field_0x14, field_0x14); } diff --git a/src/dusk/interp/lerp.h b/src/dusk/interp/lerp.h index c0820f2439..8581458ec5 100644 --- a/src/dusk/interp/lerp.h +++ b/src/dusk/interp/lerp.h @@ -25,6 +25,10 @@ inline f32 lerp(f32 lhs, f32 rhs, float step) { return lhs + (rhs - lhs) * step; } +inline void lerp(f32& out, const f32& lhs, const f32& rhs, float step) { + out = lerp(lhs, rhs, step); +} + inline u8 lerp(u8 lhs, u8 rhs, float step) { return static_cast(std::lround(lerp(static_cast(lhs), static_cast(rhs), step))); } diff --git a/src/dusk/interp/menus.h b/src/dusk/interp/menus.h new file mode 100644 index 0000000000..108d9937ee --- /dev/null +++ b/src/dusk/interp/menus.h @@ -0,0 +1,53 @@ +#pragma once + +#include "samples.h" + +#include + +namespace dusk::interp { + +struct MenuValues { + Samples values; +}; + +template +void capture_menu_values(const void* owner, Values... values) { + if (!should_capture()) { + return; + } + + const f32 pose[] = {static_cast(values)...}; + get(owner).values.capture(pose, sizeof...(Values)); +} + +template +class ScopedMenuValues { +public: + template + ScopedMenuValues(const void* owner, Values&... values) : mValues{&values...}, mSaved{values...} { + if (is_enabled() && is_presentation_active()) { + const auto& samples = get(owner).values; + for (size_t i = 0; i < Count; ++i) { + *mValues[i] = samples.read(i, mSaved[i]); + } + } + } + + ~ScopedMenuValues() { + for (size_t i = 0; i < Count; ++i) { + *mValues[i] = mSaved[i]; + } + } + + ScopedMenuValues(const ScopedMenuValues&) = delete; + ScopedMenuValues& operator=(const ScopedMenuValues&) = delete; + +private: + std::array mValues; + std::array mSaved; +}; + +template +ScopedMenuValues(const void*, Values&...) -> ScopedMenuValues; + +} // namespace dusk::interp diff --git a/src/dusk/interp/user_interface.h b/src/dusk/interp/user_interface.h index 8e86fda556..30ddbf2034 100644 --- a/src/dusk/interp/user_interface.h +++ b/src/dusk/interp/user_interface.h @@ -3,15 +3,25 @@ #include "dusk/game_clock.h" #include "JSystem/J2DGraph/J2DAnimation.h" +#include "JSystem/J2DGraph/J2DPane.h" #include "SSystem/SComponent/c_lib.h" +#include "SSystem/SComponent/c_math.h" #include #include #include +#include namespace dusk::vdt { +inline f32 clamped_fraction(f32 timer, f32 duration) { + if (duration <= 0.0f) { + return 1.0f; + } + return std::clamp(timer / duration, 0.0f, 1.0f); +} + inline void advance_looping_frame(f32& frame, f32 speed, f32 max) { if (max <= 0.0f) { return; @@ -62,6 +72,50 @@ inline void present_looping(f32& frame, J2DAnmBase* anm, f32 speed) { anm->setFrame(frame); } +inline void present_toward(f32& frame, f32 target, J2DAnmTransform* anm, J2DPane* pane = nullptr) { + if (anm == nullptr) { + return; + } + if (pane != nullptr && pane->mTransform != anm) { + return; + } + advance_toward_frame(frame, target, 2.0f); + anm->setFrame(frame); + if (pane != nullptr) { + pane->animationTransform(); + } +} + +inline void present_selected(f32& frame, J2DPane* pane, J2DAnmTransform* first, f32 firstTarget, + J2DAnmTransform* second, f32 secondTarget) +{ + if (pane != nullptr) { + if (pane->mTransform == first) { + present_toward(frame, firstTarget, first, pane); + } else if (pane->mTransform == second) { + present_toward(frame, secondTarget, second, pane); + } + } +} + +inline void present_shared(f32& frame, f32 target, J2DAnmTransform* anm, + std::initializer_list panes) +{ + if (frame == target) { + return; + } + bool advanced = false; + for (J2DPane* pane : panes) { + if (pane != nullptr && pane->mTransform == anm) { + if (!advanced) { + present_toward(frame, target, anm); + advanced = true; + } + pane->animationTransform(); + } + } +} + inline void present_addCalc(f32* value, f32 target, f32 scale, f32 maxStep, f32 minStep) { const f32 frames = game_clock::original_frames(); if (*value == target || frames <= 0.0f) { @@ -83,6 +137,16 @@ inline void present_addCalc2(f32* value, f32 target, f32 scale, f32 maxStep, f32 } } +inline f32 present_sine_ease(f32 max, f32 value) { + if (max <= 0.0f) { + return 1.0f; + } + value = std::clamp(value, 0.0f, max); + f32 rate = value / max; + rate = cM_ssin((int)(0.5f * (32768.0f * rate))); + return rate * rate; +} + inline bool present_chase(f32* value, f32 target, f32 scale, f32 maxStep, f32 snap) { if (*value == target) { return false; From 81a33e27ea3a4c00013b7683a843474ee31f60ff Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 18 Aug 2026 19:14:00 -0400 Subject: [PATCH 027/100] Delta time msg --- include/d/d_msg_class.h | 8 +++ include/d/d_msg_object.h | 5 +- include/d/d_msg_out_font.h | 4 +- include/d/d_msg_scrn_3select.h | 9 +++ include/d/d_msg_scrn_base.h | 4 ++ include/d/d_msg_scrn_explain.h | 5 +- include/d/d_msg_scrn_howl.h | 7 +- include/d/d_msg_scrn_item.h | 6 ++ include/d/d_msg_scrn_kanban.h | 3 + include/d/d_msg_scrn_talk.h | 6 +- include/d/d_msg_scrn_tree.h | 3 + src/d/d_msg_class.cpp | 11 ++++ src/d/d_msg_object.cpp | 117 +++++++++++++++++++++++++++++---- src/d/d_msg_out_font.cpp | 89 +++++++++++++++++-------- src/d/d_msg_scrn_3select.cpp | 85 ++++++++++++++++++++++++ src/d/d_msg_scrn_arrow.cpp | 14 ++++ src/d/d_msg_scrn_base.cpp | 11 ++++ src/d/d_msg_scrn_explain.cpp | 30 ++++++++- src/d/d_msg_scrn_howl.cpp | 76 +++++++++++++++++++-- src/d/d_msg_scrn_item.cpp | 30 ++++++++- src/d/d_msg_scrn_kanban.cpp | 12 ++++ src/d/d_msg_scrn_light.cpp | 15 +++-- src/d/d_msg_scrn_talk.cpp | 18 ++++- src/d/d_msg_scrn_tree.cpp | 13 ++++ 24 files changed, 518 insertions(+), 63 deletions(-) diff --git a/include/d/d_msg_class.h b/include/d/d_msg_class.h index 49a99a6ef1..bd26cae084 100644 --- a/include/d/d_msg_class.h +++ b/include/d/d_msg_class.h @@ -197,12 +197,16 @@ struct jmessage_tReference : public JMessage::TReference { void addDrawLightCount() { mDrawLightCount++; } void setCharAlpha(f32 alpha) { mCharAlpha = alpha; } +#if TARGET_PC + void addCharAlpha(); +#else void addCharAlpha() { mCharAlpha += mAddCharAlpha; if (mCharAlpha > 255.0f) { mCharAlpha = 255.0f; } } +#endif void setLineLength(int i_no, f32 i_strLen, f32 i_spaceLen) { mStrLength[i_no] = i_strLen; @@ -215,12 +219,16 @@ struct jmessage_tReference : public JMessage::TReference { } } +#if TARGET_PC + void addCharAllAlphaRate(); +#else void addCharAllAlphaRate() { mCharAllAlphaRate += mAddCharAllAlphaRate; if (mCharAllAlphaRate > 1.0f) { mCharAllAlphaRate = 1.0f; } } +#endif void addLineLength(int param_0, f32 param_1, f32 param_2) { mStrLength[param_0] += param_1; diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index 3e8c535800..84dbcc9f8d 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -21,6 +21,9 @@ public: int _create(msg_class*); int _execute(); int _draw(); +#if TARGET_PC + void presentAnims(); +#endif int _delete(); void setMessageIndex(u32, u32, bool); void setMessageIndexDemo(u32, bool); @@ -285,7 +288,7 @@ public: /* 0x164 */ u16 mNodeIdx; /* 0x166 */ u16 field_0x166; /* 0x168 */ u16 field_0x168; - /* 0x16A */ s16 field_0x16a; + /* 0x16A */ DUSK_IF_ELSE(f32, s16) field_0x16a; /* 0x16C */ s16 mCurrentGroupID; // group whose BMG is parsed; -1 none, 0 common, 1-8 stage /* 0x16E */ s16 field_0x16e; /* 0x170 */ s16 mNowTalkFlowNo; diff --git a/include/d/d_msg_out_font.h b/include/d/d_msg_out_font.h index 3bd41ca825..df9b9e4368 100644 --- a/include/d/d_msg_out_font.h +++ b/include/d/d_msg_out_font.h @@ -39,7 +39,7 @@ class COutFont_c { public: COutFont_c(u8); void initialize(); - void setBlendAnime(J2DPicture*, s16); + void setBlendAnime(J2DPicture*, DUSK_IF_ELSE(f32, s16)); const char* getBtiName(int); virtual ~COutFont_c(); @@ -57,7 +57,7 @@ public: /* 0x1A8 */ f32 mAlphaRatio; /* 0x1AC */ f32 field_0x1ac; /* 0x1B0 */ f32 field_0x1b0; - /* 0x1B4 */ s16 field_0x1b4[70]; + /* 0x1B4 */ DUSK_IF_ELSE(f32, s16) field_0x1b4[70]; /* 0x240 */ bool field_0x240; /* 0x241 */ u8 mRupeeColor; /* 0x242 */ u8 field_0x242; diff --git a/include/d/d_msg_scrn_3select.h b/include/d/d_msg_scrn_3select.h index 35c831d3c5..de74919d4f 100644 --- a/include/d/d_msg_scrn_3select.h +++ b/include/d/d_msg_scrn_3select.h @@ -26,10 +26,16 @@ public: dMsgScrn3Select_c(); virtual ~dMsgScrn3Select_c(); bool isSelect(); +#if TARGET_PC + bool isAnimeActive() { return mProcess < PROC_MAX_e; } +#endif void setString(DUSK_CONST char*, DUSK_CONST char*, DUSK_CONST char*); void setRubyString(DUSK_CONST char*, DUSK_CONST char*, DUSK_CONST char*); void translate(f32, f32); void draw(f32, f32); +#if TARGET_PC + void presentAnims(); +#endif void selAnimeInit(u8, u8, u8, f32, u8); bool selAnimeMove(u8, u8, bool); bool selAnimeEnd(); @@ -101,6 +107,9 @@ public: /* 0x115 */ u8 mDPDPoint; /* 0x116 */ u8 field_0x116; /* 0x117 */ u8 field_0x117; +#if TARGET_PC + bool mPresenting; +#endif }; // Size: 0x118 #endif /* MSG_SCRN_D_MSG_SCRN_3SELECT_H */ diff --git a/include/d/d_msg_scrn_base.h b/include/d/d_msg_scrn_base.h index 17fd7380d3..fe91363970 100644 --- a/include/d/d_msg_scrn_base.h +++ b/include/d/d_msg_scrn_base.h @@ -37,6 +37,10 @@ public: virtual void fukiAlpha(f32); virtual void fontAlpha(f32); virtual void fukiPosCalc(u8) {} +#if TARGET_PC + virtual void presentAnims(); + virtual bool isSelectAnimeActive() { return false; } +#endif f32 getSelTextBoxPosX(int idx) { return mSelTextBoxPosX[idx]; } f32 getSelTextBoxPosY(int idx) { return mSelTextBoxPosY[idx]; } diff --git a/include/d/d_msg_scrn_explain.h b/include/d/d_msg_scrn_explain.h index 1b638f9b83..2868d173f9 100644 --- a/include/d/d_msg_scrn_explain.h +++ b/include/d/d_msg_scrn_explain.h @@ -23,6 +23,9 @@ public: dMsgScrnExplain_c(STControl*, u8, bool, u8); void move(); void draw(J2DOrthoGraph*); +#if TARGET_PC + void presentAnims(); +#endif void wait_init(); void wait_proc(); void open_request_init(); @@ -67,7 +70,7 @@ public: /* 0x50 */ f32 field_0x50; /* 0x54 */ u32 mOpenMsgId; /* 0x58 */ bool field_0x58; - /* 0x5A */ s16 field_0x5a; + /* 0x5A */ DUSK_IF_ELSE(f32, s16) field_0x5a; /* 0x5C */ s16 field_0x5c; /* 0x5E */ s16 mKeyWaitTimer; /* 0x60 */ u8 mStatus; diff --git a/include/d/d_msg_scrn_howl.h b/include/d/d_msg_scrn_howl.h index 5db5db0232..c168b1f4f3 100644 --- a/include/d/d_msg_scrn_howl.h +++ b/include/d/d_msg_scrn_howl.h @@ -8,6 +8,9 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { ~dMsgScrnHowl_c(); void exec(); void drawSelf(); +#if TARGET_PC + void presentAnims(); +#endif void guide_on_init(); void guide_on_proc(); void guide_off_init(); @@ -77,7 +80,7 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { /* 0x1A18 */ f32 field_0x1a18[30]; /* 0x1A90 */ f32 field_0x1a90[30]; /* 0x1B08 */ f32 field_0x1b08[3]; - /* 0x1B14 */ s16 field_0x1b14[0x300]; + /* 0x1B14 */ DUSK_IF_ELSE(f32, s16) field_0x1b14[0x300]; /* 0x2114 */ u32 field_0x2114; /* 0x2118 */ u32 field_0x2118; /* 0x211C */ u32 field_0x211c; @@ -113,6 +116,8 @@ struct dMsgScrnHowl_c : public dMsgScrnBase_c { #if TARGET_PC u8 showCursor; + f32 mSampleAcc; + f32 mPulseFrame; #endif }; diff --git a/include/d/d_msg_scrn_item.h b/include/d/d_msg_scrn_item.h index 865071a2af..a0387b8127 100644 --- a/include/d/d_msg_scrn_item.h +++ b/include/d/d_msg_scrn_item.h @@ -13,6 +13,9 @@ struct dMsgScrnItem_c : public dMsgScrnBase_c { ~dMsgScrnItem_c(); void exec(); void drawSelf(); +#if TARGET_PC + void presentAnims(); +#endif void arwAnimeInit(); void arwAnimeMove(); void dotAnimeInit(); @@ -23,6 +26,9 @@ struct dMsgScrnItem_c : public dMsgScrnBase_c { void selectAnimeInit(u8, u8, f32, u8); bool selectAnimeMove(u8, u8, bool); bool selectAnimeEnd(); +#if TARGET_PC + bool isSelectAnimeActive(); +#endif void fukiScale(f32); void fukiTrans(f32, f32); void fukiAlpha(f32); diff --git a/include/d/d_msg_scrn_kanban.h b/include/d/d_msg_scrn_kanban.h index bba4ebd734..7e9fcb1caf 100644 --- a/include/d/d_msg_scrn_kanban.h +++ b/include/d/d_msg_scrn_kanban.h @@ -13,6 +13,9 @@ struct dMsgScrnKanban_c : public dMsgScrnBase_c { ~dMsgScrnKanban_c(); void exec(); void draw(); +#if TARGET_PC + void presentAnims(); +#endif void fukiAlpha(f32); void fukiScale(f32); void fukiTrans(f32, f32); diff --git a/include/d/d_msg_scrn_talk.h b/include/d/d_msg_scrn_talk.h index 1c8283ad44..c33b67a76c 100644 --- a/include/d/d_msg_scrn_talk.h +++ b/include/d/d_msg_scrn_talk.h @@ -23,6 +23,10 @@ struct dMsgScrnTalk_c : public dMsgScrnBase_c { void selectAnimeInit(u8, u8, f32, u8); bool selectAnimeMove(u8, u8, bool); bool selectAnimeEnd(); +#if TARGET_PC + void presentAnims(); + bool isSelectAnimeActive(); +#endif void fukiScale(f32); void fukiTrans(f32, f32); void fukiAlpha(f32); @@ -42,7 +46,7 @@ struct dMsgScrnTalk_c : public dMsgScrnBase_c { /* 0x0F4 */ f32 field_0xf4; /* 0x0F8 */ f32 field_0xf8[150]; /* 0x350 */ f32 field_0x350[3]; - /* 0x35C */ s16 field_0x35c[150]; + /* 0x35C */ DUSK_IF_ELSE(f32, s16) field_0x35c[150]; /* 0x488 */ u8 field_0x488; }; diff --git a/include/d/d_msg_scrn_tree.h b/include/d/d_msg_scrn_tree.h index f30f8a6193..844d378efd 100644 --- a/include/d/d_msg_scrn_tree.h +++ b/include/d/d_msg_scrn_tree.h @@ -11,6 +11,9 @@ struct dMsgScrnTree_c : public dMsgScrnBase_c { virtual ~dMsgScrnTree_c(); void exec(); void draw(); +#if TARGET_PC + void presentAnims(); +#endif void fukiAlpha(f32); void fukiScale(f32); void fukiTrans(f32, f32); diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 0c2c3cf358..e0f9584a9c 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -13,6 +13,7 @@ #include "JSystem/JUtility/JUTFont.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/scope_guard.hpp" #include "dusk/version.hpp" @@ -907,6 +908,16 @@ bool jmessage_tReference::isLightEnd() { return false; } +#if TARGET_PC +void jmessage_tReference::addCharAlpha() { + dusk::vdt::advance_toward_frame(mCharAlpha, 255.0f, mAddCharAlpha); +} + +void jmessage_tReference::addCharAllAlphaRate() { + dusk::vdt::advance_toward_frame(mCharAllAlphaRate, 1.0f, mAddCharAllAlphaRate); +} +#endif + void jmessage_tReference::decideOutFontRupeeColor(int i_inputVal) { int price_diff = getObjectPtr()->getNowTotalPrice() - getObjectPtr()->getNowTotalPayment(); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 072bd5039b..0157d2521f 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -30,9 +30,7 @@ #include "m_Do/m_Do_lib.h" #if TARGET_PC -#include -#include -#include +#include "dusk/interp/user_interface.h" #include "dusk/language.hpp" #include "dusk/logging.h" #include "dusk/menu_pointer.h" @@ -40,6 +38,10 @@ #include "dusk/mods/svc/flow.hpp" #include "dusk/settings.h" #include "dusk/version.hpp" + +#include +#include +#include #endif static void dMsgObject_addFundRaising(s16 param_0); @@ -569,6 +571,97 @@ int dMsgObject_c::_draw() { return 1; } +#if TARGET_PC +void dMsgObject_c::presentAnims() { + if (mpScrnDraw == NULL || mpOutFont == NULL) { + return; + } + + const u16 status = getStatusLocal(); + f32 target = 0.0f; + if (status == 2) { + if (isPlaceMessage() || isStaffMessage()) { + target = g_MsgObject_HIO_c.mStageTitleFadeIn; + } else if (isBossMessage()) { + target = g_MsgObject_HIO_c.mBossNameFadeIn; + } else if (isBookMessage()) { + target = g_MsgObject_HIO_c.mBoxAppearFrame + g_MsgObject_HIO_c.mWaitFrame + + g_MsgObject_HIO_c.mLightAppearFrame; + } else { + target = g_MsgObject_HIO_c.mBoxAppearFrame; + } + dusk::vdt::advance_toward_frame(field_0x16a, target, 1.0f); + if (isKanbanMessage() || isPlaceMessage() || isStaffMessage() || isBossMessage()) { + f32 ratio = dusk::vdt::clamped_fraction(field_0x16a, target); + mpScrnDraw->fukiScale(ratio); + mpScrnDraw->fukiAlpha(ratio); + mpOutFont->setAlphaRatio(ratio); + } else if (isBookMessage()) { + if (field_0x16a <= g_MsgObject_HIO_c.mBoxAppearFrame) { + f32 ratio = dusk::vdt::clamped_fraction(field_0x16a, g_MsgObject_HIO_c.mBoxAppearFrame); + mpScrnDraw->fukiAlpha(ratio); + mpOutFont->setAlphaRatio(ratio); + } else { + mpScrnDraw->fukiAlpha(1.0f); + mpOutFont->setAlphaRatio(1.0f); + } + s16 waitEnd = g_MsgObject_HIO_c.mBoxAppearFrame + g_MsgObject_HIO_c.mWaitFrame; + if (field_0x16a >= waitEnd && + field_0x16a <= waitEnd + g_MsgObject_HIO_c.mLightAppearFrame) + { + f32 scale = dusk::vdt::clamped_fraction(field_0x16a - waitEnd, g_MsgObject_HIO_c.mLightAppearFrame); + mpScrnDraw->fukiScale(scale); + } + } else { + f32 ratio = dusk::vdt::clamped_fraction(field_0x16a, target); + mpScrnDraw->fukiScale(1.0f); + mpScrnDraw->fukiAlpha(ratio); + mpOutFont->setAlphaRatio(ratio); + } + } else if (status == 17) { + if (isKanbanMessage() || isBookMessage()) { + target = g_MsgObject_HIO_c.field_0x304; + } else if (isPlaceMessage() || isStaffMessage()) { + target = g_MsgObject_HIO_c.mStageTitleFadeOut; + } else if (isBossMessage()) { + target = g_MsgObject_HIO_c.mBossNameFadeOut; + } else { + target = 5.0f; + } + dusk::vdt::advance_toward_frame(field_0x16a, target, 1.0f); + f32 ratio = dusk::vdt::clamped_fraction(field_0x16a, target); + mpScrnDraw->fukiAlpha(1.0f - ratio); + if (isBookMessage()) { + mpScrnDraw->fukiScale(1.0f - ratio); + } + mpOutFont->setAlphaRatio(1.0f - ratio); + } else if (status == 6) { + if (isBookMessage() && field_0x16a > 0) { + dusk::vdt::advance_toward_frame(field_0x16a, 0.0f, 1.0f); + f32 alpha = (10 - field_0x16a) / 10.0f; + mpScrnDraw->fontAlpha(alpha); + mpOutFont->setAlphaRatio(alpha); + } + jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); + if (pRef->getCharAllAlphaRate() < 1.0f) { + pRef->addCharAllAlphaRate(); + f32 alpha = pRef->getCharAllAlphaRate(); + mpScrnDraw->setCharAlphaRate(alpha); + mpOutFont->setAlphaRatio(alpha); + } + } else if (status == 5 && isBookMessage() && + (isSend() || field_0x16a > 0)) + { + dusk::vdt::advance_toward_frame(field_0x16a, 10.0f, 1.0f); + if (field_0x16a <= 10) { + f32 alpha = (10 - field_0x16a) / 10.0f; + mpScrnDraw->fontAlpha(alpha); + mpOutFont->setAlphaRatio(alpha); + } + } +} +#endif + int dMsgObject_c::_delete() { mpResCont->destroyResource_all(); if (mpScrnDraw != NULL) { @@ -961,7 +1054,7 @@ void dMsgObject_c::waitProc() { void dMsgObject_c::openProc() { if (isMidonaMessage()) { bool uVar12 = 0; - if (field_0x16a == 0) { + if (DUSK_IF_ELSE(!mpScrnDraw->isSelectAnimeActive(), field_0x16a == 0)) { jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference(); field_0x1a3 = 0; if (mpRefer->getMsgID() == 0x7fa) { @@ -995,7 +1088,7 @@ void dMsgObject_c::openProc() { } } } - field_0x16a++; + IF_NOT_DUSK(field_0x16a++); s16 sVar7; if (isKanbanMessage()) { sVar7 = g_MsgObject_HIO_c.mBoxAppearFrame; @@ -1062,12 +1155,12 @@ void dMsgObject_c::openProc() { void dMsgObject_c::outnowProc() { mpRefer->shiftCharCountBuffer(); - if (isBookMessage() && field_0x16a > 0) { - field_0x16a--; + if (isBookMessage() && field_0x16a DUSK_IF_ELSE(>, !=) 0) { + IF_NOT_DUSK(field_0x16a--); f32 alpha = (10 - field_0x16a) / 10.0f; mpScrnDraw->fontAlpha(alpha); mpOutFont->setAlphaRatio(alpha); - if (field_0x16a > 0) { + if (field_0x16a DUSK_IF_ELSE(>, !=) 0) { return; } } @@ -1077,7 +1170,7 @@ void dMsgObject_c::outnowProc() { if (mDoCPd_c::getTrigA(0)) { pRef->setCharAllAlphaRate(1.0f); } else { - pRef->addCharAllAlphaRate(); + IF_NOT_DUSK(pRef->addCharAllAlphaRate()); } f32 alpha = pRef->getCharAllAlphaRate(); mpScrnDraw->setCharAlphaRate(alpha); @@ -1131,8 +1224,8 @@ void dMsgObject_c::outwaitProc() { (jmessage_tReference*)mpRenProc->getReference(); mpScrnDraw->arwAnimeMove(); if (isBookMessage()) { - if (isSend() || field_0x16a > 0) { - field_0x16a++; + if (isSend() || field_0x16a DUSK_IF_ELSE(>, !=) 0) { + IF_NOT_DUSK(field_0x16a++); if (field_0x16a <= 10) { f32 alpha = (10 - field_0x16a) / 10.0f; mpScrnDraw->fontAlpha(alpha); @@ -1413,7 +1506,7 @@ void dMsgObject_c::finishProc() { } void dMsgObject_c::endProc() { - field_0x16a++; + IF_NOT_DUSK(field_0x16a++); s16 sVar4 = 5; if (isKanbanMessage() || isBookMessage()) { sVar4 = g_MsgObject_HIO_c.field_0x304; diff --git a/src/d/d_msg_out_font.cpp b/src/d/d_msg_out_font.cpp index ce5e97c2d0..9cab28a328 100644 --- a/src/d/d_msg_out_font.cpp +++ b/src/d/d_msg_out_font.cpp @@ -8,7 +8,7 @@ #include "f_op/f_op_msg_mng.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #endif @@ -312,20 +312,12 @@ void COutFont_c::setAlphaRatio(f32 i_ratio) { } void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param_3) { - s16 sp256[70]; + DUSK_IF_ELSE(f32, s16) sp256[70]; for (int i = 0; i < 70; i++) { sp256[i] = field_0x1b4[i]; } -#if TARGET_PC - if (dusk::interp::get_ui_tick_pending()) { - for (int i = 0; i < 70; i++) { - sp256[i] = -1; - } - } -#endif - for (int i = 0; i < 35; i++) { u8 type = mpOfs[i]->getType(); J2DTextBox* tbox = mpOfs[i]->getTextBoxPtr(); @@ -413,11 +405,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param } case 9: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 80.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 80) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -433,55 +428,70 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param break; case 14: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, true); break; case 15: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, true, true, true); break; case 16: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 17: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 20.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 20) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, true, true, false); break; case 18: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -493,11 +503,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param break; case 19: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -528,24 +541,28 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 20: case 21: case 22: - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 28.0f); +#else field_0x1b4[type]++; if (field_0x1b4[type] >= 28) { field_0x1b4[type] = 0; } - IF_DUSK_BLOCK_END - +#endif mpPane[type]->rotate(0.5f * sizeX, 0.5f * sizeY, ROTATE_Z, (360.0f * (f32)field_0x1b4[type]) / 28.0f); mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 25: // some issues in here, 2020 if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 18.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 18) { field_0x1b4[type] -= 18; } +#endif } f32 alpha; @@ -622,11 +639,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 67: case 68: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } @@ -642,23 +662,28 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 59: case 60: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 40.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 40) { field_0x1b4[type] = 0; } +#endif setBlendAnime(mpPane[type], field_0x1b4[type]); } mpPane[type]->draw(posX, posY, sizeX, sizeY, false, false, false); break; case 62: if (sp256[type] == field_0x1b4[type]) { +#if TARGET_PC + dusk::vdt::advance_looping_frame(field_0x1b4[type], 1.0f, 10.0f); +#else field_0x1b4[type]++; - if (field_0x1b4[type] >= 10) { field_0x1b4[type] = 0; } - +#endif if (field_0x1b4[type] < 5) { mpPane[type]->setBlendRatio(1.0f, 0.0f); } else { @@ -697,14 +722,22 @@ void COutFont_c::reset(J2DTextBox* i_textbox) { } } -void COutFont_c::setBlendAnime(J2DPicture* i_pic, s16 param_1) { +void COutFont_c::setBlendAnime(J2DPicture* i_pic, DUSK_IF_ELSE(f32, s16) param_1) { +#if TARGET_PC + f32 i = fmodf(param_1, 20.0f); + if (i < 0.0f) { + i += 20.0f; + } +#else int i = param_1 % 20; +#endif if (i < 10) { - f32 dVar6 = fopMsgM_valueIncrease(10, i, 0); + f32 dVar6 = DUSK_IF_ELSE((i / 10.0f) * (i / 10.0f), fopMsgM_valueIncrease(10, i, 0)); i_pic->setBlendRatio(1.0f - dVar6, dVar6); } else { - f32 dVar6 = fopMsgM_valueIncrease(10, i - 10, 0); + f32 dVar6 = DUSK_IF_ELSE(((i - 10.0f) / 10.0f) * ((i - 10.0f) / 10.0f), + fopMsgM_valueIncrease(10, i - 10, 0)); i_pic->setBlendRatio(dVar6, 1.0f - dVar6); } } diff --git a/src/d/d_msg_scrn_3select.cpp b/src/d/d_msg_scrn_3select.cpp index b7e0e8a8db..1545f51584 100644 --- a/src/d/d_msg_scrn_3select.cpp +++ b/src/d/d_msg_scrn_3select.cpp @@ -17,6 +17,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/menu_pointer.h" #include "dusk/version.hpp" @@ -315,6 +316,7 @@ dMsgScrn3Select_c::dMsgScrn3Select_c() { mDPDPoint = 0xFF; field_0x116 = 0xFF; field_0x117 = 0; + IF_DUSK(mPresenting = false); mSelMsgInactiveCol.set(0xFF, 0xFF, 0xFF, 0xFF); } @@ -441,6 +443,34 @@ void dMsgScrn3Select_c::translate(f32 i_x, f32 i_y) { mpParent->translate(i_x, i_y); } +#if TARGET_PC +void dMsgScrn3Select_c::presentAnims() { + if (mProcess >= PROC_MAX_e) { + return; + } + mPresenting = true; + if (mProcess == PROC_OPEN2_e || mProcess == PROC_CHANGE_e || mProcess == PROC_CLOSE_e) { + (this->*process[mProcess])(); + } + dusk::vdt::present_looping(mBpkFrame, mpAnmBpk, 1.0f); + dusk::vdt::present_looping(mBtkFrame, mpAnmBtk, 1.0f); + mpScreen->animation(); + + for (int i = 0; i < 3; i++) { + if (i == mSelNo) { + mpSelCld_c[i]->getPanePtr()->scale(1.0f, 1.0f); + mpSelCldr_c[i]->getPanePtr()->scale(1.0f, 1.0f); + } else { + mpSelCld_c[i]->getPanePtr()->scale(0.0f, 0.0f); + mpSelCldr_c[i]->getPanePtr()->scale(0.0f, 0.0f); + } + } + selectTrans(); + + mPresenting = false; +} +#endif + void dMsgScrn3Select_c::draw(f32 i_xPos, f32 i_yPos) { J2DGrafContext* port = dComIfGp_getCurrentGrafPort(); port->setup2D(); @@ -574,10 +604,14 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) { field_0x108 = param_2; #if TARGET_PC pointerMove(); + if (mProcess >= PROC_MAX_e) { + return false; + } #endif (this->*process[mProcess])(); +#if !TARGET_PC mBpkFrame++; if (mBpkFrame >= mpAnmBpk->getFrameMax()) { mBpkFrame -= mpAnmBpk->getFrameMax(); @@ -603,6 +637,7 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) { } selectTrans(); +#endif for (int i = 0; i < 3; i++) { if (i == mSelNo) { @@ -689,6 +724,7 @@ bool dMsgScrn3Select_c::selAnimeEnd() { (this->*process[mProcess])(); +#if !TARGET_PC mBpkFrame++; if (mBpkFrame >= mpAnmBpk->getFrameMax()) { mBpkFrame -= mpAnmBpk->getFrameMax(); @@ -702,6 +738,7 @@ bool dMsgScrn3Select_c::selAnimeEnd() { mpAnmBtk->setFrame(mBtkFrame); mpScreen->animation(); +#endif for (int i = 0; i < 3; i++) { if (i == mSelNo) { @@ -778,7 +815,13 @@ void dMsgScrn3Select_c::open1Proc() { void dMsgScrn3Select_c::open2Proc() { f32 sel_anm_frame[3] = {204.0f, 304.0f, 404.0f}; +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 199.0f, 1.0f); + } +#else mBckFrame++; +#endif mpSelectCursor->setAlphaRate(1.0f); if (mBckFrame >= 199.0f) { @@ -903,7 +946,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 303.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 303.0f, 1.0f); + } +#else mBckFrame++; +#endif } selectAnimeTransform(0); @@ -915,7 +964,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 403.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 403.0f, 1.0f); + } +#else mBckFrame++; +#endif } selectAnimeTransform(0); @@ -930,7 +985,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 300.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 300.0f, 1.0f); + } +#else mBckFrame--; +#endif } selectAnimeTransform(1); @@ -942,7 +1003,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 403.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 403.0f, 1.0f); + } +#else mBckFrame++; +#endif } selectAnimeTransform(1); @@ -957,7 +1024,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 400.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 400.0f, 1.0f); + } +#else mBckFrame--; +#endif } selectAnimeTransform(2); @@ -969,7 +1042,13 @@ void dMsgScrn3Select_c::changeProc() { if (field_0x108 != 0) { mBckFrame = 300.0f; } else { +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 300.0f, 1.0f); + } +#else mBckFrame--; +#endif } selectAnimeTransform(2); @@ -985,7 +1064,13 @@ void dMsgScrn3Select_c::changeProc() { void dMsgScrn3Select_c::closeProc() { mpSelectCursor->setAlphaRate(0.0f); +#if TARGET_PC + if (mPresenting) { + dusk::vdt::advance_toward_frame(mBckFrame, 190.0f, 1.0f); + } +#else mBckFrame--; +#endif for (int i = 0; i < 3; i++) { f32 scale_x = mpSel_c[i]->getPanePtr()->getScaleX(); diff --git a/src/d/d_msg_scrn_arrow.cpp b/src/d/d_msg_scrn_arrow.cpp index cea0dae145..587eb6a21a 100644 --- a/src/d/d_msg_scrn_arrow.cpp +++ b/src/d/d_msg_scrn_arrow.cpp @@ -7,6 +7,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #endif @@ -73,6 +74,14 @@ void dMsgScrnArrow_c::draw() { if (dusk::getSettings().game.recordingMode) { return; } + if (mpArw_c->isVisible()) { + dusk::vdt::present_looping(mBckFrame, mpBck, 1.0f); + dusk::vdt::present_looping(mBpkFrame, mpBpk, 1.0f); + mpScreen->animation(); + } else if (mpDot_c->isVisible()) { + dusk::vdt::present_looping(mBpkFrame, mpBpk, 1.0f); + mpScreen->animation(); + } #endif J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); mpScreen->draw(0.0f, 0.0f, graf_ctx); @@ -103,6 +112,7 @@ void dMsgScrnArrow_c::arwAnimeMove() { mpDot_c->hide(); } +#if !TARGET_PC mBckFrame += 1.0f; if (mBckFrame >= (f32)mpBck->getFrameMax()) { mBckFrame -= (f32)mpBck->getFrameMax(); @@ -114,6 +124,7 @@ void dMsgScrnArrow_c::arwAnimeMove() { mBpkFrame -= (f32)mpBpk->getFrameMax(); } mpBpk->setFrame(mBpkFrame); +#endif mpScreen->animation(); } @@ -136,11 +147,14 @@ void dMsgScrnArrow_c::dotAnimeMove() { mpDot_c->show(); } +#if !TARGET_PC mBpkFrame += 1.0f; if (mBpkFrame >= (f32)mpBpk->getFrameMax()) { mBpkFrame -= (f32)mpBpk->getFrameMax(); } mpBpk->setFrame(mBpkFrame); +#endif + mpScreen->animation(); } diff --git a/src/d/d_msg_scrn_base.cpp b/src/d/d_msg_scrn_base.cpp index 5488b76648..638bf1fc8b 100644 --- a/src/d/d_msg_scrn_base.cpp +++ b/src/d/d_msg_scrn_base.cpp @@ -71,8 +71,19 @@ void dMsgScrnBase_c::multiDraw() { } } +#if TARGET_PC +void dMsgScrnBase_c::presentAnims() { + if (dMsgObject_c* msg = dMsgObject_getMsgObjectClass()) { + if (msg->getScrnDrawPtrLocal() == this) { + msg->presentAnims(); + } + } +} +#endif + void dMsgScrnBase_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } diff --git a/src/d/d_msg_scrn_explain.cpp b/src/d/d_msg_scrn_explain.cpp index 4771257ccd..1d90898017 100644 --- a/src/d/d_msg_scrn_explain.cpp +++ b/src/d/d_msg_scrn_explain.cpp @@ -15,6 +15,7 @@ #include #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #endif @@ -326,6 +327,7 @@ dMsgScrnExplain_c::~dMsgScrnExplain_c() { void dMsgScrnExplain_c::move() { u8 currentStatus = mStatus; +#if !TARGET_PC f32 y_offset = 0.0f; if (field_0x66 == 2) { y_offset = -35.0f; @@ -335,6 +337,7 @@ void dMsgScrnExplain_c::move() { mpRoot_c[i]->paneTrans(field_0x48 + g_MsgObject_HIO_c.mChoicePos[0][6], field_0x4c + g_MsgObject_HIO_c.mBoxPos[0][6] + y_offset); } +#endif (this->*move_process[mStatus])(); @@ -346,7 +349,29 @@ void dMsgScrnExplain_c::move() { g_MsgObject_HIO_c.mBoxTalkScaleY); } +#if TARGET_PC +void dMsgScrnExplain_c::presentAnims() { + if (mStatus == STATUS_OPEN_e || mStatus == STATUS_CLOSE_e) { + dusk::vdt::advance_toward_frame(field_0x5a, mStatus == STATUS_OPEN_e ? 5.0f : 0.0f, 1.0f); + field_0x48 = FB_WIDTH_BASE * getAlphaRatio(); + for (int i = 0; i < 2; i++) { + mpRoot_c[i]->setAlphaRate(1.0f - getAlphaRatio()); + } + if (mpBackTex != NULL) { + mpBackTex->setAlpha((1.0f - getAlphaRatio()) * 150.0f); + } + } + + const f32 y_offset = field_0x66 == 2 ? -35.0f : 0.0f; + for (int i = 0; i < 2; i++) { + mpRoot_c[i]->paneTrans(field_0x48 + g_MsgObject_HIO_c.mChoicePos[0][6], + field_0x4c + g_MsgObject_HIO_c.mBoxPos[0][6] + y_offset); + } +} +#endif + void dMsgScrnExplain_c::draw(J2DOrthoGraph* i_graf) { + IF_DUSK(presentAnims()); if (mStatus == STATUS_WAIT_e || mStatus == STATUS_OPEN_REQ_e) { return; } @@ -389,6 +414,7 @@ void dMsgScrnExplain_c::draw(J2DOrthoGraph* i_graf) { mpSelect_c->translate(486.0f + g_MsgObject_HIO_c.mChoicePos[0][7], 209.0f + g_MsgObject_HIO_c.mBoxPos[0][7] + y_offset); + IF_DUSK(mpSelect_c->presentAnims()); mpSelect_c->draw(0.0f, 0.0f); } @@ -445,7 +471,7 @@ void dMsgScrnExplain_c::open_init() { } void dMsgScrnExplain_c::open_proc() { - field_0x5a++; + IF_NOT_DUSK(field_0x5a++); if (field_0x5a >= 5) { field_0x5a = 5; if (field_0x64 == 1 || field_0x64 == 2) { @@ -627,7 +653,7 @@ void dMsgScrnExplain_c::close_proc() { iVar1 = true; } - field_0x5a--; + IF_NOT_DUSK(field_0x5a--); if (field_0x5a <= 0) { field_0x5a = 0; if (iVar1) { diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index c87c4bbb0f..a661526b9c 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -21,7 +21,8 @@ #include "m_Do/m_Do_graphic.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/game_clock.h" +#include "dusk/interp/user_interface.h" #endif // POSIX already defines a macro with this name, but we know that this specific name is @@ -147,6 +148,10 @@ dMsgScrnHowl_c::dMsgScrnHowl_c() { field_0x2124 = 0; field_0x2128 = 0; mPlotTime = 0; +#if TARGET_PC + mSampleAcc = 0.0f; + mPulseFrame = 0.0f; +#endif field_0x212c = 0; field_0x212e = 0; field_0x2126 = 0; @@ -288,18 +293,52 @@ dMsgScrnHowl_c::~dMsgScrnHowl_c() { dComIfGp_getMsgArchive(5)->removeResourceAll();; } +#if TARGET_PC +void dMsgScrnHowl_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + for (int i = 0; i < 0x300; i++) { + if (field_0x1b14[i] > 0) { + dusk::vdt::advance_toward_frame(field_0x1b14[i], 0.0f, 1.0f); + } + } + f32 alphaRate = mpPmP_c->getAlphaRate(); + f32 fVar1; + f32 fVar2; + if (field_0x2798 == 3) { + fVar1 = 0.5f; + fVar2 = 0.0f; + } else { + fVar1 = 1.0f; + fVar2 = 1.0f; + } + if (field_0x1994 != fVar1) { + dusk::vdt::present_addCalc2(&field_0x1994, fVar1, 0.2f, 1.0f, 0.1f); + } + if (field_0x1998 != fVar2) { + dusk::vdt::present_addCalc2(&field_0x1998, fVar2, 0.2f, 1.0f, 0.1f); + } + mpButtonIcon[0]->setAlphaRate(field_0x1994 * alphaRate); + mpButtonIcon[1]->setAlphaRate(field_0x1994 * alphaRate); + mpButtonText[0]->setAlphaRate(field_0x1998 * alphaRate); + mpButtonText[1]->setAlphaRate(field_0x1998 * alphaRate); +} +#endif + void dMsgScrnHowl_c::exec() { field_0x2799 = field_0x2798; (this->*process[field_0x2798])(); +#if !TARGET_PC for (int i = 0; i < 0x300; i++) { if (field_0x1b14[i] > 0) { field_0x1b14[i]--; } } +#endif if (field_0x2799 != field_0x2798) { (this->*init_proc[field_0x2798])(); } - + +#if !TARGET_PC f32 alphaRate = mpPmP_c->getAlphaRate(); f32 fVar1; f32 fVar2; @@ -326,6 +365,7 @@ void dMsgScrnHowl_c::exec() { mpButtonIcon[1]->setAlphaRate(field_0x1994 * alphaRate); mpButtonText[0]->setAlphaRate(field_0x1998 * alphaRate); mpButtonText[1]->setAlphaRate(field_0x1998 * alphaRate); +#endif #if TARGET_PC showCursor = true; @@ -466,6 +506,7 @@ void dMsgScrnHowl_c::resetLine() { field_0x2195 = 0; field_0x2128 = 0; mPlotTime = 0; + IF_DUSK(mSampleAcc = 0.0f); field_0x212c = 0; field_0x212e = 0; field_0x2124 = 0; @@ -502,7 +543,7 @@ void dMsgScrnHowl_c::drawWave() { if (local_80 < 0) { local_80 = 0; } - s32 local_94 = 0; + DUSK_IF_ELSE(f32, s32) local_94 = 0; Vec fVar12 = field_0x128; Vec this_02 = field_0x140; #if TARGET_PC // TODO: make this actually use the scissor @@ -540,7 +581,7 @@ void dMsgScrnHowl_c::drawWave() { for (int iVar10 = 0; iVar10 < field_0x2128 - 1; iVar10++) { f32 local_54 = local_e0; f32 local_c8 = field_0x180[sVar14]; - s32 sVar3 = field_0x1b14[sVar14]; + DUSK_IF_ELSE(f32, s32) sVar3 = field_0x1b14[sVar14]; local_e0 += field_0x1980; local_68 += field_0x1984; if (local_68 > 255.0f) { @@ -548,7 +589,7 @@ void dMsgScrnHowl_c::drawWave() { } sVar14 = addCount(sVar14); f32 fVar2 = field_0x180[sVar14]; - s16 temp_r4 = field_0x1b14[sVar14]; + DUSK_IF_ELSE(f32, s16) temp_r4 = field_0x1b14[sVar14]; if (iVar10 == field_0x2128 - 2) { local_60 = local_e0; local_64 = fVar2; @@ -595,17 +636,24 @@ void dMsgScrnHowl_c::drawWave() { f17 = local_60; f18 = local_64; } else { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_looping_frame(mPulseFrame, 1.0f, 31.0f); + if (mPulseFrame < 15.0f) { + local_dc = mPulseFrame / 15.0f; + } else { + local_dc = (30.0f - mPulseFrame) / 15.0f; + } +#else field_0x2134++; if (field_0x2134 > 30) { field_0x2134 = 0; } - IF_DUSK_BLOCK_END if (field_0x2134 < 15) { local_dc = field_0x2134 / 15.0f; } else { local_dc = (30.0f - field_0x2134) / 15.0f; } +#endif f17 = f26; f18 = local_e4; } @@ -878,6 +926,19 @@ void dMsgScrnHowl_c::drawEffect() { } void dMsgScrnHowl_c::calcMain() { +#if TARGET_PC + mSampleAcc += dusk::game_clock::original_frames(); + while (mSampleAcc >= 1.0f) { + mSampleAcc -= 1.0f; + if (mPlotTime < field_0x2138 + 380) { + mPlotTime++; + } else { + field_0x212c--; + } + calcWave(); + calcGuide(); + } +#else if (mPlotTime < field_0x2138 + 380) { mPlotTime++; } else { @@ -885,6 +946,7 @@ void dMsgScrnHowl_c::calcMain() { } calcWave(); calcGuide(); +#endif } void dMsgScrnHowl_c::calcWave() { diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index 17007403dc..33a975b6e9 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -25,6 +25,7 @@ #include #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/version.hpp" #endif @@ -396,6 +397,25 @@ dMsgScrnItem_c::~dMsgScrnItem_c() { dComIfGp_getMsgCommonArchive()->removeResourceAll();; } +#if TARGET_PC +void dMsgScrnItem_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + mpSelect_c->presentAnims(); + for (int i = 0; i < 1; i++) { + dusk::vdt::present_looping(field_0x140[i], field_0x118[i], g_MsgObject_HIO_c.mBoxItemAnmSpeed); + } + for (int i = 0; i < 2; i++) { + dusk::vdt::present_looping(field_0x154[i], field_0x12c[i], g_MsgObject_HIO_c.mBoxItemAnmSpeed); + } + mpScreen->animation(); + if (isTalkNow()) { + fukiAlpha(1.0f); + } else { + fukiAlpha(field_0x13c); + } +} +#endif + void dMsgScrnItem_c::exec() { f32 dVar12 = field_0x13c; if (!field_0x19d) { @@ -404,6 +424,7 @@ void dMsgScrnItem_c::exec() { setBpk1Animation(field_0x12c[1]); field_0x19d = true; } +#if !TARGET_PC for (int i = 0; i < 1; i++) { field_0x140[i] += g_MsgObject_HIO_c.mBoxItemAnmSpeed; if (field_0x140[i] >= field_0x118[i]->getFrameMax()) { @@ -425,7 +446,8 @@ void dMsgScrnItem_c::exec() { } else { fukiAlpha(dVar12); } - +#endif + f32 yOffset; switch(field_0x19c) { case 1: @@ -578,6 +600,12 @@ bool dMsgScrnItem_c::selectAnimeEnd() { return mpSelect_c->selAnimeEnd(); } +#if TARGET_PC +bool dMsgScrnItem_c::isSelectAnimeActive() { + return mpSelect_c != NULL && mpSelect_c->isAnimeActive(); +} +#endif + void dMsgScrnItem_c::fukiScale(f32 param_0) { } diff --git a/src/d/d_msg_scrn_kanban.cpp b/src/d/d_msg_scrn_kanban.cpp index 7f254bbafb..41efff8824 100644 --- a/src/d/d_msg_scrn_kanban.cpp +++ b/src/d/d_msg_scrn_kanban.cpp @@ -14,6 +14,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/version.hpp" #endif @@ -204,7 +205,16 @@ dMsgScrnKanban_c::~dMsgScrnKanban_c() { dComIfGp_getMsgArchive(2)->removeResourceAll(); } +#if TARGET_PC +void dMsgScrnKanban_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + dusk::vdt::present_looping(field_0xd8, field_0xd0, 1.0f); + mpScreen->animation(); +} +#endif + void dMsgScrnKanban_c::exec() { +#if !TARGET_PC field_0xd8 += 1.0f; if (field_0xd8 >= field_0xd0->getFrameMax()) { field_0xd8 -= field_0xd0->getFrameMax(); @@ -212,6 +222,7 @@ void dMsgScrnKanban_c::exec() { field_0xd0->setFrame(field_0xd8); mpScreen->animation(); +#endif if (isTalkNow()) { fukiAlpha(1.0f); @@ -222,6 +233,7 @@ void dMsgScrnKanban_c::exec() { void dMsgScrnKanban_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } diff --git a/src/d/d_msg_scrn_light.cpp b/src/d/d_msg_scrn_light.cpp index 0d9f409978..0434da93b1 100644 --- a/src/d/d_msg_scrn_light.cpp +++ b/src/d/d_msg_scrn_light.cpp @@ -8,7 +8,7 @@ #include "d/d_pane_class.h" #if TARGET_PC -#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #endif class dMsgScrnLight_HIO_c { @@ -206,12 +206,14 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX } if (mPlayAnim) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_looping_frame(*i_anmFrame, 1.0f, mpBck->getFrameMax()); +#else *i_anmFrame += 1.0f; if (*i_anmFrame >= mpBck->getFrameMax()) { *i_anmFrame = 0.0f; } - IF_DUSK_BLOCK_END +#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; @@ -226,13 +228,14 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX mpParent_c->setBlackWhite(i_black, i_white); if (mPlayAnim) { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + dusk::vdt::advance_looping_frame(*i_anmFrame, i_anmRate, mpBck->getFrameMax()); +#else *i_anmFrame += i_anmRate; - if (*i_anmFrame >= mpBck->getFrameMax()) { *i_anmFrame = 0.0f; } - IF_DUSK_BLOCK_END +#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp index 1e63d67a9b..d4b5d4e24f 100644 --- a/src/d/d_msg_scrn_talk.cpp +++ b/src/d/d_msg_scrn_talk.cpp @@ -21,6 +21,7 @@ #include #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/version.hpp" #endif @@ -373,6 +374,15 @@ void dMsgScrnTalk_c::exec() { } } +#if TARGET_PC +void dMsgScrnTalk_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + if (mpSelect_c != NULL) { + mpSelect_c->presentAnims(); + } +} +#endif + void dMsgScrnTalk_c::drawSelf() { #if TARGET_PC if (dusk::getSettings().game.recordingMode) { @@ -404,7 +414,7 @@ void dMsgScrnTalk_c::drawSelf() { } } if (field_0x35c[i] > 0) { - field_0x35c[i]--; + DUSK_IF_ELSE(dusk::vdt::advance_toward_frame(field_0x35c[i], 0.0f, 1.0f), field_0x35c[i]--); } else { mpLight_c->draw( &mCharInfoPtr[i].field_0x14, mCharInfoPtr[i].field_0x00, mCharInfoPtr[i].field_0x04, @@ -489,6 +499,12 @@ bool dMsgScrnTalk_c::selectAnimeEnd() { return false; } +#if TARGET_PC +bool dMsgScrnTalk_c::isSelectAnimeActive() { + return mpSelect_c != NULL && mpSelect_c->isAnimeActive(); +} +#endif + void dMsgScrnTalk_c::fukiScale(f32 param_0) { } diff --git a/src/d/d_msg_scrn_tree.cpp b/src/d/d_msg_scrn_tree.cpp index bbed47cb05..36d4b9494f 100644 --- a/src/d/d_msg_scrn_tree.cpp +++ b/src/d/d_msg_scrn_tree.cpp @@ -10,6 +10,7 @@ #include "d/d_pane_class.h" #if TARGET_PC +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/version.hpp" #endif @@ -216,7 +217,17 @@ dMsgScrnTree_c::~dMsgScrnTree_c() { dComIfGp_getMsgArchive(2)->removeResourceAll(); } +#if TARGET_PC +void dMsgScrnTree_c::presentAnims() { + dMsgScrnBase_c::presentAnims(); + dusk::vdt::present_looping(field_0xdc, field_0xd0, 1.0f); + dusk::vdt::present_looping(field_0xe0, field_0xd4, 1.0f); + mpScreen->animation(); +} +#endif + void dMsgScrnTree_c::exec() { +#if !TARGET_PC field_0xdc += 1.0f; if (field_0xdc >= field_0xd0->getFrameMax()) { field_0xdc -= field_0xd0->getFrameMax(); @@ -229,6 +240,7 @@ void dMsgScrnTree_c::exec() { } field_0xd4->setFrame(field_0xe0); mpScreen->animation(); +#endif if (isTalkNow()) { fukiAlpha(1.0f); @@ -246,6 +258,7 @@ void dMsgScrnTree_c::exec() { void dMsgScrnTree_c::draw() { #if TARGET_PC + presentAnims(); if (dusk::getSettings().game.recordingMode) { return; } From 2bc53dddcca315ed1fd4cbe976ab6bdf15218896 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 24 Aug 2026 21:04:16 +0200 Subject: [PATCH 028/100] BST SE/STREAM replacement support No SEQ yet since idk how I wanna handle it. --- files.cmake | 2 + include/Z2AudioLib/Z2Audience.h | 3 +- include/Z2AudioLib/Z2SoundInfo.h | 22 +- include/Z2AudioLib/Z2SoundMgr.h | 2 +- .../include/JSystem/JAudio2/JAIAudience.h | 7 +- libs/JSystem/include/JSystem/JAudio2/JAISe.h | 2 +- .../include/JSystem/JAudio2/JAISeMgr.h | 2 +- .../include/JSystem/JAudio2/JAISound.h | 27 +- .../include/JSystem/JAudio2/JAISoundInfo.h | 10 +- .../include/JSystem/JAudio2/JAIStream.h | 2 +- .../JSystem/JAudio2/JAIStreamDataMgr.h | 4 +- .../include/JSystem/JAudio2/JAIStreamMgr.h | 2 +- .../include/JSystem/JAudio2/JAUSoundInfo.h | 4 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 25 +- .../JSystem/JAudio2/JAUStreamFileTable.h | 2 +- libs/JSystem/src/JAudio2/JAISe.cpp | 5 +- libs/JSystem/src/JAudio2/JAISeMgr.cpp | 9 +- libs/JSystem/src/JAudio2/JAISeq.cpp | 2 +- libs/JSystem/src/JAudio2/JAISound.cpp | 7 +- libs/JSystem/src/JAudio2/JAIStream.cpp | 5 +- libs/JSystem/src/JAudio2/JAIStreamMgr.cpp | 8 +- libs/JSystem/src/JAudio2/JAUSectionHeap.cpp | 7 +- libs/JSystem/src/JAudio2/JAUSoundTable.cpp | 16 +- .../src/JAudio2/JAUStreamFileTable.cpp | 7 +- mods/audio_mod/CMakeLists.txt | 7 +- mods/audio_mod/src/mod.cpp | 10 + sdk/include/mods/svc/audio_res.h | 96 +++++ src/Z2AudioLib/Z2Audience.cpp | 5 +- src/Z2AudioLib/Z2SceneMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundHandles.cpp | 4 +- src/Z2AudioLib/Z2SoundInfo.cpp | 144 ++++--- src/Z2AudioLib/Z2SoundMgr.cpp | 44 ++- src/Z2AudioLib/Z2SoundObject.cpp | 6 +- src/dusk/mods/svc/audio_res/audio_res.cpp | 9 + src/dusk/mods/svc/audio_res/bst.cpp | 350 ++++++++++++++++++ src/dusk/mods/svc/audio_res/bst.hpp | 99 +++++ src/dusk/mods/svc/id_allocator.hpp | 2 +- 37 files changed, 832 insertions(+), 128 deletions(-) create mode 100644 src/dusk/mods/svc/audio_res/bst.cpp create mode 100644 src/dusk/mods/svc/audio_res/bst.hpp diff --git a/files.cmake b/files.cmake index 0f915e5abe..443b060eeb 100644 --- a/files.cmake +++ b/files.cmake @@ -1487,6 +1487,8 @@ set(DUSK_FILES src/dusk/mods/manifest.hpp src/dusk/mods/svc/audio_res/audio_res.hpp src/dusk/mods/svc/audio_res/audio_res.cpp + src/dusk/mods/svc/audio_res/bst.cpp + src/dusk/mods/svc/audio_res/bst.hpp src/dusk/mods/svc/audio_res/wsys.cpp src/dusk/mods/svc/audio_res/wsys.hpp src/dusk/mods/svc/audio_res/wave.cpp diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index b55b7a7424..2746dfa889 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -266,7 +266,8 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance { virtual ~Z2Audience(); virtual JAIAudible* newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3*, u32); + const JGeometry::TVec3*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)); virtual int getMaxChannels(); virtual void deleteAudible(JAIAudible* audible); virtual u32 calcPriority(JAIAudible* audible); diff --git a/include/Z2AudioLib/Z2SoundInfo.h b/include/Z2AudioLib/Z2SoundInfo.h index e2fae177a3..404e0ad5df 100644 --- a/include/Z2AudioLib/Z2SoundInfo.h +++ b/include/Z2AudioLib/Z2SoundInfo.h @@ -6,23 +6,29 @@ #include "JSystem/JAudio2/JAUSoundInfo.h" #include "JSystem/JAudio2/JAUSoundTable.h" +#define STRM_CH_SHIFT_ 2 + +#define STRM_CH_CENTER 1 +#define STRM_CH_LEFT 2 +#define STRM_CH_RIGHT 3 + class Z2SoundInfo : public JAISoundInfo, public JAUSoundInfo, public JAIStreamDataMgr, public JASGlobalInstance { public: Z2SoundInfo() : JAISoundInfo(true), JAUSoundInfo(true), JASGlobalInstance(true) {} - virtual u16 getAudibleSw(JAISoundID soundID) const; + virtual u16 getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; virtual u16 getBgmSeqResourceID(JAISoundID soundID) const; - virtual s32 getStreamFileEntry(JAISoundID soundID); + virtual s32 getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); virtual int getSoundType(JAISoundID soundID) const; virtual int getCategory(JAISoundID soundID) const; - virtual u32 getPriority(JAISoundID soundID) const; - virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr) const; + virtual u32 getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; + virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const; virtual void getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const; - virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const; + virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const; virtual ~Z2SoundInfo() {} - JAUAudibleParam getAudibleSwFull(JAISoundID soundID); - const char* getStreamFilePath(JAISoundID soundID); - int getSwBit(JAISoundID soundID) const; + JAUAudibleParam getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)); + const char* getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); + int getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; void getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const; BOOL isValid() const { diff --git a/include/Z2AudioLib/Z2SoundMgr.h b/include/Z2AudioLib/Z2SoundMgr.h index cd6341b2cb..82486ed5bc 100644 --- a/include/Z2AudioLib/Z2SoundMgr.h +++ b/include/Z2AudioLib/Z2SoundMgr.h @@ -42,7 +42,7 @@ private: /* 0x004 */ JAISeMgr seMgr_; /* 0x728 */ JAISeqMgr seqMgr_; /* 0x79C */ JAIStreamMgr streamMgr_; - /* 0x80C */ JAISoundID soundID_; + /* 0x80C */ JAISoundID bgmMuter; }; // Size: 0x810 #if VERSION != VERSION_SHIELD_DEBUG diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h index 1b565a069e..20d700097c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h @@ -3,6 +3,10 @@ #include "JSystem/JGeometry.h" +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +} + class JAIAudible; class JAISoundID; struct JASSoundParams; @@ -14,7 +18,8 @@ struct JASSoundParams; struct JAIAudience { virtual ~JAIAudience(); virtual JAIAudible* newAudible(JGeometry::TVec3 const&, JAISoundID, - JGeometry::TVec3 const*, u32) = 0; + JGeometry::TVec3 const*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)) = 0; virtual int getMaxChannels() = 0; virtual void deleteAudible(JAIAudible*) = 0; virtual u32 calcPriority(JAIAudible*) = 0; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISe.h b/libs/JSystem/include/JSystem/JAudio2/JAISe.h index 408a84703a..2ff990bec0 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISe.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISe.h @@ -43,7 +43,7 @@ public: void startTrack_(const JASSoundParams& params); void JAISeCategoryMgr_mixOut_(bool, const JASSoundParams& params, JAISoundActivity activity); void JAISeCategoryMgr_calc_(); - void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_getSeqData_(); void prepare_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h index 928d85525b..57fdbd114a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h @@ -104,7 +104,7 @@ public: JAISe* newSe_(int category, u32 priority); void calc(); void mixOut(); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); int getNumActiveSe() const; /* 0x004 */ JAISoundActivity mSoundActivity; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index 4de69b8416..9dbd84d35a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -6,7 +6,16 @@ #include "JSystem/JUtility/JUTAssert.h" #include "global.h" #include "helpers/endian.h" -#include + +#if TARGET_PC +#include + +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +struct SoundEffectReplacementSlot; +struct StreamReplacementSlot; +} +#endif class JAISound; @@ -271,10 +280,16 @@ class JAITempoMgr; */ class JAISound { public: +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; + using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot; + using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot; +#endif + void releaseHandle(); void attachHandle(JAISoundHandle* handle); JAISound(); - void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool acceptsNewAudible() const; void newAudible(const JGeometry::TVec3&, JGeometry::TVec3 const*, u32, JAIAudience*); @@ -297,6 +312,11 @@ public: virtual bool JAISound_tryDie_() = 0; JAISoundID getID() const { return soundID_; } +#if TARGET_PC + SoundTableReplacementSlot* getReplacement() const { + return replacement.get(); + } +#endif u32 getAnimationState() const { return status_.state.flags.animationState; } bool isAnimated() const { return getAnimationState() != 0; } void setAnimationState(u32 state) { @@ -380,6 +400,9 @@ public: /* 0x34 */ u32 priority_; /* 0x38 */ s32 count_; /* 0x3C */ JAISoundParams params_; +#if TARGET_PC + std::shared_ptr replacement; +#endif }; // Size: 0x98 STATIC_ASSERT(sizeof(JAISound) == 0x98); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h index 4f536a0f63..23713d96a9 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h @@ -9,13 +9,17 @@ * */ struct JAISoundInfo : public JASGlobalInstance { + IF_DUSK(using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + IF_DUSK(using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot); + IF_DUSK(using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + JAISoundInfo(bool); virtual int getSoundType(JAISoundID) const = 0; virtual int getCategory(JAISoundID) const = 0; - virtual u32 getPriority(JAISoundID) const = 0; - virtual void getSeInfo(JAISoundID, JAISe*) const = 0; + virtual u32 getPriority(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const = 0; + virtual void getSeInfo(JAISoundID, JAISe* IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const = 0; virtual void getSeqInfo(JAISoundID, JAISeq*) const = 0; - virtual void getStreamInfo(JAISoundID, JAIStream*) const = 0; + virtual void getStreamInfo(JAISoundID, JAIStream* IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const = 0; virtual ~JAISoundInfo(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h index d6d6694be0..31ef4da4f5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h @@ -20,7 +20,7 @@ public: JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* soundStrategyMgr); void JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category); + int category IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_prepareStream_(); void prepare_(); void prepare_startStream_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h index eee03cc168..d528a25f85 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h @@ -8,7 +8,9 @@ * */ struct JAIStreamDataMgr { - virtual s32 getStreamFileEntry(JAISoundID) = 0; + IF_DUSK(using StreamReplacementSlot2 = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)) = 0; virtual ~JAIStreamDataMgr(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h index 7c8e68a852..27d69d57df 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h @@ -15,7 +15,7 @@ struct JAIStreamDataMgr; class JAIStreamMgr : public JASGlobalInstance { public: JAIStreamMgr(bool setInstance); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); void freeDeadStream_(); void calc(); void stop(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h index 86fa88f913..4083b4e774 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h @@ -10,8 +10,10 @@ */ class JAUSoundInfo : public JASGlobalInstance { public: + IF_DUSK(using SoundTableReplacementSlot2 = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + JAUSoundInfo(bool param_0) : JASGlobalInstance(param_0) {} - virtual u16 getAudibleSw(JAISoundID) const = 0; + virtual u16 getAudibleSw(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot2 const*)) const = 0; virtual u16 getBgmSeqResourceID(JAISoundID) const = 0; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 40ac65bf45..4a4ca33214 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -51,6 +51,11 @@ */ #define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + /** * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK */ @@ -82,10 +87,21 @@ */ #define SOUND_SW_IGNORE_DOLBY 0x0000'2000 +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + /** * 3-bit mask used to select a volume distance/falloff class for this sound. */ #define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 /** * Limit minimum volume of this sound (after distance falloff) to 0.2. @@ -97,6 +113,7 @@ * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. */ #define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 /** * Mark sound as "far away" or "culled" when at max distance (selected by distance class). @@ -290,13 +307,17 @@ struct JAUSoundTableGroup { * */ struct JAUSoundTable : public JASGlobalInstance { +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; +#endif + JAUSoundTable(bool setInstance) : JASGlobalInstance(setInstance) { } ~JAUSoundTable() {} void init(void const*); - u8 getTypeID(JAISoundID) const; - JAUSoundTableItem* getData(JAISoundID) const; + u8 getTypeID(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; + JAUSoundTableItem DUSK_CONST* getData(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; int getNumGroups_inSection(u8) const; int getNumItems_inGroup(u8, u8) const; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h index 65eef3f8b9..1f29d54f9d 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h @@ -29,7 +29,7 @@ struct JAUStreamFileTable { * */ struct JAUStreamDataMgr_StreamFileTable : public JAIStreamDataMgr, JAUStreamFileTable { - virtual s32 getStreamFileEntry(JAISoundID); + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)); virtual ~JAUStreamDataMgr_StreamFileTable() {} }; diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index eb8ea7b90e..16f3c284da 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -3,6 +3,7 @@ #include "JSystem/JAudio2/JAISe.h" #include "JSystem/JAudio2/JAIAudience.h" #include "JSystem/JAudio2/JAISeMgr.h" +#include "dusk/mods/svc/audio_res/bst.hpp" JAISe::JAISe(JAISeMgr* seMgr, JAISoundStrategyMgr* soundStrategyMgr, u32 priority) : JSULink(this) { inner_.mSoundStrategyMgr = soundStrategyMgr; @@ -129,10 +130,10 @@ bool JAISe::JAISound_tryDie_() { return false; } -void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(221, inner_.track.getStatus() == JASTrack::STATUS_FREE); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); inner_.field_0x26c = 0; if (inner_.mSoundStrategyMgr) { inner_.field_0x278 = inner_.mSoundStrategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISeMgr.cpp b/libs/JSystem/src/JAudio2/JAISeMgr.cpp index 25ed54ca39..825ac8d78e 100644 --- a/libs/JSystem/src/JAudio2/JAISeMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeMgr.cpp @@ -4,6 +4,7 @@ #include "JSystem/JAudio2/JAISoundHandles.h" #include "JSystem/JAudio2/JAISoundInfo.h" #include "JSystem/JAudio2/JASReport.h" +#include "dusk/mods/svc/audio_res/bst.hpp" bool JAISeCategoryMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { { @@ -284,7 +285,7 @@ void JAISeMgr::mixOut() { } } -bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { if (handle && *handle) { (*handle)->stop(); } @@ -294,7 +295,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry u32 category; if (soundInfoPtr != NULL) { category = soundInfoPtr->getCategory(id); - priority = soundInfoPtr->getPriority(id); + priority = soundInfoPtr->getPriority(id IF_DUSK_ARG(replacement.get())); } else { category = id.id_.info.type.parts.groupID; priority = 0; @@ -309,10 +310,10 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry } JAIAudience* audiencePtr = getAudience(category); - se->JAISeMgr_startID_(id, posPtr, audiencePtr); + se->JAISeMgr_startID_(id, posPtr, audiencePtr IF_DUSK_ARG(replacement)); if (soundInfoPtr != NULL) { - soundInfoPtr->getSeInfo(id, se); + soundInfoPtr->getSeInfo(id, se IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAISeq.cpp b/libs/JSystem/src/JAudio2/JAISeq.cpp index c05494b82e..36895d2d63 100644 --- a/libs/JSystem/src/JAudio2/JAISeq.cpp +++ b/libs/JSystem/src/JAudio2/JAISeq.cpp @@ -28,7 +28,7 @@ void JAISeq::JAISeqMgr_startID_(JAISoundID id, const JGeometry::TVec3* posP JAIAudience* audience, int category, int param_4) { inner_.field_0x39c = category; reserveChildTracks_(param_4); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(nullptr)); if (inner_.strategyMgr) { field_0x3a8 = inner_.strategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISound.cpp b/libs/JSystem/src/JAudio2/JAISound.cpp index 6dec60c39d..78141503ed 100644 --- a/libs/JSystem/src/JAudio2/JAISound.cpp +++ b/libs/JSystem/src/JAudio2/JAISound.cpp @@ -63,7 +63,7 @@ void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* o JAISound::JAISound() : params_() {} -void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { handle_ = NULL; soundID_ = id; status_.init(); @@ -72,9 +72,10 @@ void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPt audience_ = audience; prepareCount_ = 0; count_ = 0; + IF_DUSK(this->replacement = std::move(replacement)); if (posPtr != NULL && audience_ != NULL) { - audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0); + audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0 IF_DUSK_ARG(getReplacement())); } else { audible_ = NULL; } @@ -98,7 +99,7 @@ void JAISound::newAudible(const JGeometry::TVec3& pos, } JUT_ASSERT(157, audience_); - audible_ = audience_->newAudible(pos, soundID_, param_1, param_2); + audible_ = audience_->newAudible(pos, soundID_, param_1, param_2 IF_DUSK_ARG(getReplacement())); } void JAISound::stop(u32 fadeTime) { diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 5799080b12..e75292a34b 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -5,6 +5,7 @@ #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStreamDataMgr.h" #include "JSystem/JAudio2/JAIAudience.h" +#include "dusk/mods/svc/audio_res/bst.hpp" static void JAIStream_JASAramStreamCallback_(u32 type, JASAramStream* aramStream, void* userData) { JAIStream* stream = (JAIStream*)userData; @@ -34,10 +35,10 @@ JAIStream::JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* so void JAIStream::JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category) { + int category IF_DUSK_ARG(std::shared_ptr replacement)) { field_0x298 = category; field_0x294 = streamFileEntry; - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); field_0x290 = 0; if (field_0x2c0 != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp index 2baafdf24a..ca982bafc7 100644 --- a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp @@ -14,13 +14,13 @@ JAIStreamMgr::JAIStreamMgr(bool setInstance) : JASGlobalInstance(s mActivity.init(); } -bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(37, streamDataMgr_); if (handle != NULL && *handle) { (*handle)->stop(); } - s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id); + s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id IF_DUSK_ARG(replacement.get())); if (streamFileEntry < 0) { JUT_WARN(46, "Cannot find the stream file entry for ID:%08x\n", id.id_.composite_) return false; @@ -38,9 +38,9 @@ bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeom return false; } - stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category); + stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category IF_DUSK_ARG(replacement)); if (soundInfo != NULL) { - soundInfo->getStreamInfo(id, stream); + soundInfo->getStreamInfo(id, stream IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp index cc94097712..fe12281a01 100644 --- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp +++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp @@ -47,7 +47,12 @@ namespace { mStreamFileDVDEntryNums[i] = DVDConvertPathToEntrynum(stack_14.getFilePath(i)); } } - virtual s32 getStreamFileEntry(JAISoundID id) { + virtual s32 getStreamFileEntry(JAISoundID id IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("TStreamDataMgr::getStreamFileEntry not implemented for replacements"); + } +#endif u32 short_id = id.id_.info.waveID; if (short_id >= mNumStreamFiles) { return -1; diff --git a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp index cab2e45a3c..802b1c3cb9 100644 --- a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp @@ -6,6 +6,8 @@ #include "JSystem/JAudio2/JAUSoundTable.h" +#include "dusk/mods/svc/audio_res/bst.hpp" + void JAUSoundTable::init(void const* param_0) { if (param_0 != NULL) { field_0x0.init(param_0); @@ -14,11 +16,16 @@ void JAUSoundTable::init(void const* param_0) { } } -u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { +u8 JAUSoundTable::getTypeID(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return 0xff; } +#endif +#if TARGET_PC + if (replacement) { + return replacement->get_type_id(); + } #endif if (param_0.isAnonymous()) { return 0xff; @@ -34,11 +41,16 @@ u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { return group->getTypeID(param_0.id_.info.waveID); } -JAUSoundTableItem* JAUSoundTable::getData(JAISoundID param_0) const { +JAUSoundTableItem DUSK_CONST* JAUSoundTable::getData(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return NULL; } +#endif +#if TARGET_PC + if (replacement) { + return &replacement->item; + } #endif if (param_0.isAnonymous()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp index f30c5c4a01..7f37f21f3e 100644 --- a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp @@ -31,7 +31,12 @@ const char* JAUStreamFileTable::getFilePath(int index) const { return (char*)(data + *(int*)(data + 8 + (index * sizeof(s32)))); } -s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId) { +s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("JAUStreamDataMgr_StreamFileTable::getStreamFileEntry not implemented for replacements"); + } +#endif const char* filePath = getFilePath(soundId.id_.info.waveID); if (filePath == NULL) { return -1; diff --git a/mods/audio_mod/CMakeLists.txt b/mods/audio_mod/CMakeLists.txt index 083a348ed2..cc882572b8 100644 --- a/mods/audio_mod/CMakeLists.txt +++ b/mods/audio_mod/CMakeLists.txt @@ -13,7 +13,8 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) endif () add_mod(audio_mod - SOURCES src/mod.cpp - MOD_JSON mod.json - RES_DIR res + SOURCES src/mod.cpp + MOD_JSON mod.json + RES_DIR res + OVERLAY_DIR overlay ) diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp index a68e6bb98e..aa3da367ec 100644 --- a/mods/audio_mod/src/mod.cpp +++ b/mods/audio_mod/src/mod.cpp @@ -25,6 +25,16 @@ MOD_EXPORT ModResult mod_initialize(ModError*) { "res/go.opus", nullptr, &handle); + + constexpr AudioSoundTableEffectInfo effect(128, 1, 1.5); + + svc_audio_res->replace_sound_table_effect( + mod_ctx, + SE_CATEGORY_CHARA_SE, + 0, // MIDNA_APPEAR + &effect, + nullptr); + return MOD_OK; } diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index b5af109959..a5a0b7b71e 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -6,6 +6,10 @@ #define AUDIO_RES_SERVICE_MAJOR 1u #define AUDIO_RES_SERVICE_MINOR 0u +/* + * WSYS structs + */ + #define AUDIO_RES_DEFAULT_KEY 0x3C; typedef enum AudioWaveBank : uint8_t { @@ -21,6 +25,7 @@ typedef enum AudioWaveFormat : uint8_t { } AudioWaveFormat; typedef uint64_t AudioWaveHandle; +typedef uint64_t AudioSoundTableHandle; /** * @@ -41,9 +46,68 @@ typedef struct AudioWaveInfo { AudioRawWave const* raw_wave; } AudioWaveInfo; +/* + * BST structs + */ + +typedef enum SoundEffectCategory : uint8_t { + SE_CATEGORY_SYSTEM_SE, + SE_CATEGORY_PLAYER_VOICE, + SE_CATEGORY_PLAYER_SE, + SE_CATEGORY_FOOTNOTE_SE, + SE_CATEGORY_COLLISION_SE, + SE_CATEGORY_CHARA_VOICE, + SE_CATEGORY_CHARA_SE, + SE_CATEGORY_ENEMY_SE, + SE_CATEGORY_OBJECT_SE, + SE_CATEGORY_ENV_SE +} SoundEffectCategory; + +typedef struct AudioSoundTableEffectInfo { + uint8_t priority; + float volume; + float pitch; + + bool always_max_priority; + + bool ignore_distance_volume; + bool ignore_distance_fx_mix; + bool ignore_pan; + bool ignore_dolby; + + uint8_t random_volume; + uint8_t random_pitch; + + uint8_t doppler_power; + + uint8_t volume_dist_class; + bool clamp_min_volume; + bool cull_at_max_distance; +} AudioSoundTableEffectInfo; + +typedef enum StreamPan : uint8_t { + STREAM_PAN_CENTER, + STREAM_PAN_LEFT, + STREAM_PAN_RIGHT, +} StreamPan; + +#define STREAM_MAX_CHILDREN 6 + +typedef struct AudioSoundTableStreamInfo { + uint8_t priority; + float volume; + StreamPan pan_parameters[STREAM_MAX_CHILDREN]; + char const* file_path; + bool stop_on_scene_change; +} AudioSoundTableStreamInfo; + typedef struct AudioResService { ServiceHeader header; + /* + * WSYS API + */ + ModResult (*replace_wave)( ModContext* ctx, AudioWaveBank bank, @@ -61,6 +125,38 @@ typedef struct AudioResService { uint16_t* out_wave_id); ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); + + /* + * BST API + */ + + ModResult (*replace_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + + ModResult (*add_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + + ModResult (*replace_sound_table_stream)( + ModContext* ctx, + uint16_t stream_id, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle); + + ModResult (*add_sound_table_stream)( + ModContext* ctx, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + + ModResult (*remove_sound_table)(ModContext* ctx, AudioSoundTableHandle handle); } AudioResService; #ifdef __cplusplus diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index 5dfe3baed9..0a2d52540c 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -521,14 +521,15 @@ void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f3 } JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3* param_2, u32 param_3) { + const JGeometry::TVec3* param_2, u32 param_3 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const* replacement)) { u32 channelNum = param_3 | ~((1 << mNumPlayers) + -1); if (channelNum == 0xFFFFFFFF) { JUT_WARN(826, "%s", "You masked all audible channels !"); return NULL; } - JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID); + JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID IF_DUSK_ARG(replacement)); bool x = params.field_0x0.bytes.mDopplerPower != 0; Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x); diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index f51fc10d92..6c727ee803 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) { JSUList* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList(); JSULink* stream; for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) { - if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != SOUND_TYPEID_STREAM) { + if (bVar2 || sound_table->getTypeID(stream->getObject()->getID() IF_DUSK_ARG(stream->getObject()->getReplacement())) != SOUND_TYPEID_STREAM) { stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME); } } diff --git a/src/Z2AudioLib/Z2SoundHandles.cpp b/src/Z2AudioLib/Z2SoundHandles.cpp index de7e265ae7..99e11e0784 100644 --- a/src/Z2AudioLib/Z2SoundHandles.cpp +++ b/src/Z2AudioLib/Z2SoundHandles.cpp @@ -89,7 +89,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { return handle; } - u32 prio = soundInfo->getPriority((*handle)->getID()); + u32 prio = soundInfo->getPriority((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())); if (prio < low_prio) { low_prio = prio; rethandle = handle; @@ -102,7 +102,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { } #endif - if (soundInfo->getPriority(soundID) >= low_prio) { + if (soundInfo->getPriority(soundID IF_DUSK_ARG(nullptr)) >= low_prio) { return rethandle; } diff --git a/src/Z2AudioLib/Z2SoundInfo.cpp b/src/Z2AudioLib/Z2SoundInfo.cpp index 9befa79b26..394034e322 100644 --- a/src/Z2AudioLib/Z2SoundInfo.cpp +++ b/src/Z2AudioLib/Z2SoundInfo.cpp @@ -1,11 +1,13 @@ #include "Z2AudioLib/Z2SoundInfo.h" -#include "JSystem/JAudio2/JAISeq.h" + #include "JSystem/JAudio2/JAISe.h" +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStream.h" #include "JSystem/JAudio2/JAUSoundTable.h" #include "JSystem/JUtility/JUTAssert.h" #include "Z2AudioLib/Z2Calc.h" +#include "dusk/mods/svc/audio_res/bst.hpp" struct JAUStdSoundTableType { static DUSK_GAME_DATA const u32 STRM_CH_SHIFT; @@ -18,8 +20,8 @@ struct JAUStdSoundTableType { u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const { JUT_ASSERT(20, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(nullptr)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(nullptr)); if (data != NULL) { switch ((typeID & 0xf0)) { @@ -48,10 +50,22 @@ int Z2SoundInfo::getCategory(JAISoundID soundID) const { return soundID.id_.info.type.parts.groupID; } -u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { +u32 Z2SoundInfo::getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(63, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + +#if TARGET_PC + // This function gets called at least once in a context where we can't easily pass in the replacement. + // So check it here too. + // It's only a tiny priority difference, highly unlikely to cause any issues. + std::shared_ptr replacement_shared; + if (!replacement) { + replacement_shared = dusk::mods::svc::audio_res::bst::get_override_for(soundID); + replacement = replacement_shared.get(); + } +#endif + + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { return data->mPriority; @@ -60,59 +74,65 @@ u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { return 0; } -JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { +#define SW_BIT DUSK_IF_ELSE(sw_bit, getSwBit(soundID)) + +JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) { JAUAudibleParam audibleParam; JUT_ASSERT(82, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit;) + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { case SOUND_TYPEID_SOUND_EFFECT: - audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; - if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_FX_MIX) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { audibleParam.field_0x0.bytes.mCalcPan = 0; } else { audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; @@ -132,59 +152,63 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { return audibleParam; } -u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { +u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JAUAudibleParam audibleParam; JUT_ASSERT(184, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit); + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { case SOUND_TYPEID_SOUND_EFFECT: - audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; - if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { audibleParam.field_0x0.bytes.mCalcPan = 0; } else { audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; @@ -197,12 +221,14 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { return audibleParam.field_0x0.half.f0; } -void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { +#undef SW_BIT + +void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const { getSoundInfo_(soundID, sePtr); JUT_ASSERT(292, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data == NULL) { return; } @@ -210,14 +236,14 @@ void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { switch(typeID) { case SOUND_TYPEID_SOUND_EFFECT: sePtr->getProperty().mPitch *= data->mPitch; - u32 pitchParam = (getSwBit(soundID) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; + u32 pitchParam = (getSwBit(soundID IF_DUSK_ARG(replacement)) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; if (pitchParam > 8) { sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); } else { sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1(); } - u32 uVar1 = (u32)getSwBit(soundID) >> SOUND_SW_RANDOM_VOLUME_OFFSET; + u32 uVar1 = (u32)getSwBit(soundID IF_DUSK_ARG(replacement)) >> SOUND_SW_RANDOM_VOLUME_OFFSET; if (uVar1 != 0) { f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1(); sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); @@ -230,20 +256,20 @@ void Z2SoundInfo::getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const { getSoundInfo_(soundID, seqPtr); } -DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = 2; +DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = STRM_CH_SHIFT_; -void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const { +void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const { int numChild; - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; getSoundInfo_(soundID, streamPtr); JUT_ASSERT(349, isValid()); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID & 0xf0) { case SOUND_TYPEID_STREAM: u16 uVar1; s32 iVar4; - data = JASGlobalInstance::getInstance()->getData(soundID); + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(356, data); uVar1 = data->mStreamPanParameters; @@ -255,13 +281,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const JAISoundChild* child = streamPtr->getChild(iVar4); if (child != NULL) { switch (uVar2) { - case 1: + case STRM_CH_CENTER: child->mMove.params_.mPan = 0.5f; break; - case 2: + case STRM_CH_LEFT: child->mMove.params_.mPan = 0.0f; break; - case 3: + case STRM_CH_RIGHT: child->mMove.params_.mPan = 1.0f; break; } @@ -271,14 +297,20 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const } } -const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { +const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { JUT_ASSERT(387, isValid()); - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; const void* resource; - switch (JASGlobalInstance::getInstance()->getTypeID(soundID) & 0xf0) { + switch (JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)) & 0xf0) { case SOUND_TYPEID_STREAM: - data = JASGlobalInstance::getInstance()->getData(soundID); +#if TARGET_PC + if (replacement) { + return replacement->file_path.c_str(); + } +#endif + + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(394, data); resource = JASGlobalInstance::getInstance()->getResource(); JUT_ASSERT(398, resource); @@ -288,16 +320,16 @@ const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { } } -s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID) { - const char* path = getStreamFilePath(soundID); +s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { + const char* path = getStreamFilePath(soundID IF_DUSK_ARG(replacement)); return !path ? -1 : DVDConvertPathToEntrynum(path); } -int Z2SoundInfo::getSwBit(JAISoundID soundID) const { +int Z2SoundInfo::getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(418, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data != NULL) { switch(typeID) { case SOUND_TYPEID_SOUND_EFFECT: @@ -310,9 +342,9 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const { void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const { JUT_ASSERT(440, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(soundPtr->getReplacement())); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(soundPtr->getReplacement())); if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume; } diff --git a/src/Z2AudioLib/Z2SoundMgr.cpp b/src/Z2AudioLib/Z2SoundMgr.cpp index 6978a61585..43bd6c7d5c 100644 --- a/src/Z2AudioLib/Z2SoundMgr.cpp +++ b/src/Z2AudioLib/Z2SoundMgr.cpp @@ -1,14 +1,15 @@ #include "Z2AudioLib/Z2SoundMgr.h" -#include "Z2AudioLib/Z2Param.h" -#include "Z2AudioLib/Z2SceneMgr.h" -#include "Z2AudioLib/Z2SpeechMgr2.h" -#include "Z2AudioLib/Z2SeqMgr.h" -#include "Z2AudioLib/Z2SeMgr.h" -#include "Z2AudioLib/Z2SoundInfo.h" #include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JAUSectionHeap.h" +#include "Z2AudioLib/Z2Param.h" +#include "Z2AudioLib/Z2SceneMgr.h" +#include "Z2AudioLib/Z2SeMgr.h" +#include "Z2AudioLib/Z2SeqMgr.h" +#include "Z2AudioLib/Z2SoundInfo.h" +#include "Z2AudioLib/Z2SpeechMgr2.h" #include "d/d_com_inf_game.h" +#include "dusk/mods/svc/audio_res/bst.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include "Z2AudioCS/Z2AudioCS.h" @@ -113,19 +114,25 @@ Z2SoundMgr::Z2SoundMgr() : seqMgr_(true), streamMgr_(true) { - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { DUSK_AUDIO_SKIP(true); + IF_DUSK(using namespace dusk::mods::svc::audio_res::bst); int soundType = Z2GetSoundInfo()->getSoundType(soundID); switch (soundType) { case 0: { - if (Z2GetSoundInfo()->getSwBit(soundID) & 8) { + #if TARGET_PC + { + auto replace_se = get_override_for_se(soundID); + #endif + + if (Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG(replace_se.get())) & SOUND_SW_MUTE_BGM) { OS_REPORT("[Z2SoundMgr::startSound] se seq Mute! id = %08x\n", *(u32*)&soundID); Z2GetSeqMgr()->bgmAllMute(3, 0.3f); - soundID_ = soundID; + bgmMuter = soundID; } #if PLATFORM_WII || PLATFORM_SHIELD @@ -136,15 +143,16 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG } #endif - bool result = seMgr_.startSound(soundID, handle, posPtr); + bool result = seMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_se))); return result; + IF_DUSK_BLOCK_END } case 1: if (soundID == Z2BGM_LUTERA_DEMO) { Z2GetSeqMgr()->bgmStart(Z2BGM_LUTERA2, 0, 0); Z2GetSeqMgr()->unMuteSceneBgm(0); Z2GetSeqMgr()->changeBgmStatus(0); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } else { bool loaded = false; JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); @@ -172,15 +180,21 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG return seqMgr_.startSound(soundID, handle, posPtr); } case 2: +#if TARGET_PC + { + auto replace_stream = get_override_for_stream(soundID); +#endif + if (soundID == 0x2000001 || soundID == 0x200004d) { streamMgr_.stop(180); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } if (soundID == 0x2000005) { seMgr_.getCategory(9)->pause(false); seMgr_.getCategory(9)->getParams()->moveVolume(1.0f, 45); } - return streamMgr_.startSound(soundID, handle, posPtr); + return streamMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_stream))); + IF_DUSK_BLOCK_END default: char error[64]; SAFE_SPRINTF(error, "Unknown Sound-Type id :%08x\n", (u32)soundID); @@ -192,11 +206,11 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG void Z2SoundMgr::calc() { seMgr_.calc(); - if (!soundID_.isAnonymous() && !isPlayingSoundID(soundID_)) { + if (!bgmMuter.isAnonymous() && !isPlayingSoundID(bgmMuter)) { if (Z2GetSceneMgr()->isSceneExist()) { Z2GetSeqMgr()->bgmAllUnMute(3); } - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } seqMgr_.calc(); streamMgr_.calc(); diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index f38fa14e6a..c45a638b3c 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -70,7 +70,7 @@ void Z2SoundObjBase::dispose() { for (i = getFirst(); i != NULL; i = i->getNext()) { handle = i->getObject(); if (handle != NULL && *handle) { - if ((Z2GetSoundInfo()->getSwBit((*handle)->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { handle->releaseSound(); } else { (*handle)->stop(); @@ -82,7 +82,7 @@ void Z2SoundObjBase::dispose() { } bool Z2SoundObjBase::stopOK(Z2SoundHandlePool& handlePool) { - if ((Z2GetSoundInfo()->getSwBit(handlePool->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(handlePool->getID() IF_DUSK_ARG(handlePool->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { return false; } else { return true; @@ -112,7 +112,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startSound(JAISoundID soundID, u32 mapinfo, s Z2SoundHandlePool* handle = getHandleSoundID(soundID); if (handle != NULL) { - if ((Z2GetSoundInfo()->getSwBit(soundID) & 0x4000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_1) != 0) { handle = NULL; } else { return NULL; diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index a63eb4e3ce..7550b360cc 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -5,6 +5,7 @@ #include "../registry.hpp" #include "wsys.hpp" +#include "bst.hpp" namespace dusk::mods::svc { @@ -18,18 +19,26 @@ constexpr AudioResService s_audioResService{ .replace_wave = &wsys::insert_replace_wave, .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, + .replace_sound_table_effect = &bst::replace_sound_table_effect, + .add_sound_table_effect = &bst::add_sound_table_effect, + .replace_sound_table_stream = &bst::replace_sound_table_stream, + .add_sound_table_stream = &bst::add_sound_table_stream, + .remove_sound_table = &bst::remove_sound_table, }; void frame_end() { wsys::frame_end(); + bst::frame_end(); } void mod_detached(LoadedMod& mod) { wsys::remove_mod(mod); + bst::remove_mod(mod); } void sync_audio_replacements() { wsys::sync_audio_replacements(); + bst::sync_audio_replacements(); } } diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp new file mode 100644 index 0000000000..e47ad2d216 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -0,0 +1,350 @@ +#include "bst.hpp" + +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" +#include "dusk/mods/svc/id_allocator.hpp" +#include "dusk/mods/svc/slot_map.hpp" + +namespace dusk::mods::svc::audio_res::bst { + +namespace { + +bool sound_replacements_dirty = false; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap> sound_replacements; + +std::array sound_effect_id_allocator = { + PlainIdAllocator(0x1000), // SYSTEM_SE + PlainIdAllocator(0x1000), // PLAYER_VOICE + PlainIdAllocator(0x1000), // PLAYER_SE + PlainIdAllocator(0x1000), // FOOTNOTE_SE + PlainIdAllocator(0x1000), // COLLISION_SE + PlainIdAllocator(0x1000), // CHARA_VOICE + PlainIdAllocator(0x1000), // CHARA_SE + PlainIdAllocator(0x1000), // ENEMY_SE + PlainIdAllocator(0x1000), // OBJECT_SE + PlainIdAllocator(0x1000), // ENV_SE +}; + +PlainIdAllocator stream_id_allocator(0x1000); + +bool validate_category(SoundEffectCategory const category) { + return category <= SE_CATEGORY_ENV_SE; +} + +uint8_t volume_to_item(float volume) { + return static_cast(volume * 127); +} + +absl::flat_hash_map> active_se_replacements; +absl::flat_hash_map> active_stream_replacements; +std::mutex active_replacements_mutex; + +} // namespace + +SoundTableReplacementSlot::SoundTableReplacementSlot(bool const mod_defined, u16 const id) + : mod_defined(mod_defined), id(id), item() {} + +SoundTableReplacementSlot::~SoundTableReplacementSlot() = default; + +SoundEffectReplacementSlot::SoundEffectReplacementSlot( + bool mod_defined, u16 id, SoundEffectCategory category, const AudioSoundTableEffectInfo& info) + : SoundTableReplacementSlot(mod_defined, id), category(category) { + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + item.mPitch = info.pitch; + + u32 sw_bit = 0; + if (info.always_max_priority) + sw_bit |= SOUND_SW_ALWAYS_MAX_PRIORITY; + if (info.ignore_distance_volume) + sw_bit |= SOUND_SW_IGNORE_DISTANCE_VOL; + if (info.ignore_distance_fx_mix) + sw_bit |= SOUND_SW_IGNORE_FX_MIX; + if (info.ignore_pan) + sw_bit |= SOUND_SW_IGNORE_PAN; + if (info.ignore_dolby) + sw_bit |= SOUND_SW_IGNORE_DOLBY; + sw_bit |= (info.random_volume << SOUND_SW_RANDOM_VOLUME_OFFSET) & SOUND_SW_RANDOM_VOLUME_MASK; + sw_bit |= (info.random_pitch << SOUND_SW_RANDOM_PITCH_OFFSET) & SOUND_SW_RANDOM_PITCH_MASK; + sw_bit |= (info.doppler_power << SOUND_SW_DOPPLER_POWER_OFFSET) & SOUND_SW_DOPPLER_POWER_MASK; + + if (info.volume_dist_class < 8) { + sw_bit |= (info.volume_dist_class << SOUND_SW_VOL_DIST_BIT_OFFSET) & SOUND_SW_VOL_DIST_BIT_MASK; + } else { + sw_bit |= ((info.volume_dist_class - 8) << SOUND_SW_VOL_DIST_BIT_2_OFFSET) & SOUND_SW_VOL_DIST_BIT_2_MASK; + } + + if (info.clamp_min_volume) + sw_bit |= SOUND_SW_CLAMP_MIN_VOLUME; + if (info.cull_at_max_distance) + sw_bit |= SOUND_SW_CULL_AT_MAX_DISTANCE; + + item.mSwBit = sw_bit; +} + +u8 SoundEffectReplacementSlot::get_type_id() const { + return SOUND_TYPEID_SOUND_EFFECT; +} + +StreamReplacementSlot::StreamReplacementSlot( + bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info) + : SoundTableReplacementSlot(mod_defined, id), file_path(info.file_path) { + stop_on_scene_change = info.stop_on_scene_change; + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + + u16 panParam = 0; + for (int i = STREAM_MAX_CHILDREN - 1; i >= 0; i--) { + panParam <<= STRM_CH_SHIFT_; + + switch (info.pan_parameters[i]) { + case STREAM_PAN_CENTER: + panParam |= STRM_CH_CENTER; + break; + case STREAM_PAN_LEFT: + panParam |= STRM_CH_LEFT; + break; + case STREAM_PAN_RIGHT: + panParam |= STRM_CH_RIGHT; + break; + } + } + item.mStreamPanParameters = panParam; +} + +u8 StreamReplacementSlot::get_type_id() const { + return stop_on_scene_change ? SOUND_TYPEID_STREAM_ALT : SOUND_TYPEID_STREAM; +} + +std::shared_ptr get_override_for(JAISoundID id) { + switch (id.id_.info.type.parts.sectionID) { + case 0: + return get_override_for_se(id); + case 2: + return get_override_for_stream(id); + default: + return nullptr; + } +} + +std::shared_ptr get_override_for_se(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 0) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + SoundEffectKey const key( + static_cast(id.id_.info.type.parts.groupID), + id.id_.info.waveID); + + auto const entry = active_se_replacements.find(key); + if (entry == active_se_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +std::shared_ptr get_override_for_stream(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 2) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + auto const entry = active_stream_replacements.find(id.id_.info.waveID); + if (entry == active_stream_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +void remove_mod(LoadedMod const& mod) { + sound_replacements.erase_all(mod); + + sound_replacements_dirty = true; +} + +void frame_end() { + if (sound_replacements_dirty) { + sync_audio_replacements(); + } +} + +void sync_audio_replacements() { + sound_replacements_dirty = false; + + absl::flat_hash_map> new_se_replacements; + absl::flat_hash_map> new_stream_replacements; + + for (auto const& mod : ModLoader::instance().active_mods()) { + sound_replacements.for_each([&](auto, auto const& entry) { + if (entry.owner != &mod) { + return; + } + + std::shared_ptr const& value = entry.value; + auto se = std::dynamic_pointer_cast(value); + if (se) { + new_se_replacements.emplace(SoundEffectKey(se->category, se->id), std::move(se)); + return; + } + + auto stream = std::dynamic_pointer_cast(value); + if (stream) { + new_stream_replacements.emplace(stream->id, std::move(stream)); + return; + } + }); + } + + std::lock_guard lock(active_replacements_mutex); + + std::exchange(active_se_replacements, std::move(new_se_replacements)); + std::exchange(active_stream_replacements, std::move(new_stream_replacements)); +} + +static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, bool mod_defined, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || info == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + auto slot = std::make_shared(mod_defined, effect_id, category_id, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_effect_core(ctx, category_id, effect_id, false, info, out_handle); +} + +ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id) { + + if (out_effect_id == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + assert(category_id < sound_effect_id_allocator.size()); + + auto& allocator = sound_effect_id_allocator[category_id]; + auto const effect_id = allocator.alloc(); + + auto const result = insert_sound_table_effect_core(ctx, category_id, effect_id, true, info, out_handle); + if (result != MOD_OK) { + allocator.free(effect_id); + } + + *out_effect_id = effect_id; + + return result; +} + +static ModResult insert_sound_table_stream_core( + ModContext* ctx, + uint16_t stream_id, + bool mod_defined, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || info == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + auto slot = std::make_shared(mod_defined, stream_id, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_stream_core(ctx, stream_id, false, info, out_handle); +} + +ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, uint16_t* out_stream_id) { + if (out_stream_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + auto const stream_id = stream_id_allocator.alloc(); + + auto const result = insert_sound_table_stream_core(ctx, stream_id, true, info, out_handle); + if (result != MOD_OK) { + stream_id_allocator.free(stream_id); + } + + *out_stream_id = stream_id; + + return result; +} + +static PlainIdAllocator& id_allocator_for(SoundTableReplacementSlot const* slot) { + if (dynamic_cast(slot)) + return stream_id_allocator; + + if (auto const se = dynamic_cast(slot)) + return sound_effect_id_allocator[se->id]; + + CRASH("Unknown type???"); +} + +static bool sound_table_remove(LoadedMod const& mod, AudioSoundTableHandle const handle) { + auto const found = sound_replacements.find_owned(handle, mod); + if (found == nullptr) { + return false; + } + + if (found->value->mod_defined) { + auto& allocator = id_allocator_for(found->value.get()); + allocator.free(found->value->id); + } + + sound_replacements.erase_owned(handle, mod); + return true; +} + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + + if (!sound_table_remove(*mod, handle)) { + Log.error("[{}] remove sound table failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + + return MOD_OK; +} + +} // namespace dusk::mods::svc::audio_res::bst diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp new file mode 100644 index 0000000000..de13534f20 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include "JSystem/JAudio2/JAUSoundTable.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} + +namespace dusk::mods::svc::audio_res::bst { + +struct SoundTableReplacementSlot { + bool mod_defined; + u16 id; + JAUSoundTableItem item; + + SoundTableReplacementSlot(bool mod_defined, u16 id); + + virtual ~SoundTableReplacementSlot() = 0; + + [[nodiscard]] virtual u8 get_type_id() const = 0; +}; + +struct SoundEffectReplacementSlot final : SoundTableReplacementSlot { + SoundEffectCategory category; + + SoundEffectReplacementSlot(bool mod_defined, u16 id, SoundEffectCategory category, + const AudioSoundTableEffectInfo& info); + + ~SoundEffectReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct StreamReplacementSlot final : SoundTableReplacementSlot { + std::string file_path; + bool stop_on_scene_change; + + StreamReplacementSlot(bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info); + + ~StreamReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct SoundEffectKey { + SoundEffectCategory category; + u16 id; + + template + friend H AbslHashValue(H h, const SoundEffectKey& k) { + return H::combine(std::move(h), k.category, k.id); + } + + [[nodiscard]] bool operator==(const SoundEffectKey& other) const { + return other.category == category && other.id == id; + } +}; + +std::shared_ptr get_override_for(JAISoundID id); +std::shared_ptr get_override_for_se(JAISoundID id); +std::shared_ptr get_override_for_stream(JAISoundID id); + +void frame_end(); +void remove_mod(LoadedMod const& mod); +void sync_audio_replacements(); + +ModResult replace_sound_table_effect( + ModContext* ctx, + SoundEffectCategory category_id, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_effect( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + +ModResult replace_sound_table_stream( + ModContext* ctx, + uint16_t stream_id, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_stream( + ModContext* ctx, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); + +} diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp index b336e0fbfa..49eecf6f1c 100644 --- a/src/dusk/mods/svc/id_allocator.hpp +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -14,7 +14,7 @@ class PlainIdAllocator { T alloc_max; public: - explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) + constexpr explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) : alloc_next(first), alloc_max(max) { } From 4b4529d4b6b3f8bd4a8399d1b57ce92ca0d50796 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 24 Aug 2026 21:10:12 +0200 Subject: [PATCH 029/100] Fix WSYS using take_all whoops --- src/dusk/mods/svc/audio_res/wsys.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 266c0ca55e..d27c759520 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -267,12 +267,16 @@ void sync_audio_replacements() { absl::flat_hash_map new_map; for (auto const& mod : ModLoader::instance().active_mods()) { - for (auto const& slot : s_waveReplacements.take_all(mod)) { - auto const wave_info = wave_info_from_slot(slot.value); + s_waveReplacements.for_each([&](auto, auto entry) { + if (entry.owner != &mod) { + return; + } + + auto const wave_info = wave_info_from_slot(entry.value); new_map.emplace( - AudioWaveKey(slot.value.bank, slot.value.wave_id), - AudioWaveReplacementValue(wave_info, slot.value.data)); - } + AudioWaveKey(entry.value.bank, entry.value.wave_id), + AudioWaveReplacementValue(wave_info, entry.value.data)); + }); } // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); From 2d3fe8fd8888554874de77826ca47cc476166cb3 Mon Sep 17 00:00:00 2001 From: Irastris Date: Thu, 27 Aug 2026 20:41:00 -0400 Subject: [PATCH 030/100] Delta time meter2 --- include/d/d_meter2.h | 15 + include/d/d_meter2_draw.h | 38 ++- src/d/d_meter2.cpp | 81 ++++- src/d/d_meter2_draw.cpp | 583 ++++++++++++++++++++++++------------ src/d/d_meter2_draw_vdt.inc | 581 +++++++++++++++++++++++++++++++++++ src/d/d_meter2_vdt.inc | 40 +++ src/d/d_meter_map.cpp | 10 + 7 files changed, 1139 insertions(+), 209 deletions(-) create mode 100644 src/d/d_meter2_draw_vdt.inc create mode 100644 src/d/d_meter2_vdt.inc diff --git a/include/d/d_meter2.h b/include/d/d_meter2.h index 4246d9249d..b3bde323ae 100644 --- a/include/d/d_meter2.h +++ b/include/d/d_meter2.h @@ -100,6 +100,11 @@ public: bool isShowFlag(int i_no) { return field_0x1e6 & (1 << i_no); } void onShowFlag(int i_no) { field_0x1e6 |= (1 << i_no); } +#if TARGET_PC + void presentAnims(); + void presentMap(); +#endif + /* 0x0FC */ int field_0xfc; /* 0x100 */ JKRExpHeap* mpHeap; /* 0x104 */ JKRExpHeap* mpSubHeap; @@ -299,6 +304,16 @@ public: /* 0x460 */ u8 field_0x460[0x4bc - 0x460]; /* 0x4BC */ u8 field_0x4bc; /* 0x4BC */ u8 field_0x4bd; + +#if TARGET_PC + struct { + f32 vesselX, vesselY, vesselScale, vesselAlpha; + f32 buttonAX[2], buttonAY[2]; + f32 buttonBX[2], buttonBY[2]; + f32 crossX, crossY; + bool ready; + } mPresentationTargets; +#endif }; #endif /* D_METER_D_METER2_H */ diff --git a/include/d/d_meter2_draw.h b/include/d/d_meter2_draw.h index c9dc8217e9..927d71d63c 100644 --- a/include/d/d_meter2_draw.h +++ b/include/d/d_meter2_draw.h @@ -11,6 +11,7 @@ class JKRExpHeap; class JKRHeap; class dKantera_icon_c; +IF_DUSK(class dMeterMap_c); class dMeter2Draw_c : public dDlst_base_c { public: @@ -46,7 +47,8 @@ public: void playPikariBpkAnimation(f32); void playOxygenBpkAnimation(J2DAnmColor*); void drawPikari(f32, f32, f32*, f32, JUtility::TColor, JUtility::TColor, - JUtility::TColor, JUtility::TColor, f32, u8); + JUtility::TColor, JUtility::TColor, f32, u8 + IF_DUSK_ARG(f32 elapsedFrames = -1.0f)); void drawPikari(CPaneMgr*, f32*, f32, JUtility::TColor, JUtility::TColor, JUtility::TColor, JUtility::TColor, f32, u8); void drawPikariHakusha(f32, f32, f32, f32, JUtility::TColor, JUtility::TColor, @@ -59,11 +61,11 @@ public: void drawKanteraScreen(u8); void drawMagic(s16, s16, f32, f32); void setAlphaMagicChange(bool); - void drawKantera(s32, s32, f32, f32); + void drawKantera(s32, DUSK_IF_ELSE(f32, s32), f32, f32); void setAlphaKanteraChange(bool); void setAlphaKanteraAnimeMin(); void setAlphaKanteraAnimeMax(); - void drawOxygen(s32, s32, f32, f32); + void drawOxygen(s32, DUSK_IF_ELSE(f32, s32), f32, f32); void setAlphaOxygenChange(bool); void setAlphaOxygenAnimeMin(); void setAlphaOxygenAnimeMax(); @@ -134,7 +136,11 @@ public: J2DScreen* getMainScreenPtr() { return mpScreen; } bool isEmphasisC() { return field_0x766 == 7 ? true : false; } +#if TARGET_PC + f32 getMeterGaugeAlphaRate(u8 i_no); +#else f32 getMeterGaugeAlphaRate(u8 i_no) { return mMeterAlphaRate[i_no]; } +#endif bool isEmphasisA() { return field_0x761 == 7 ? true : false; } bool isEmphasisB() { return field_0x762 == 7 ? true : false; } bool isEmphasisBin() { return field_0x763 == 7 ? true : false; } @@ -152,6 +158,27 @@ public: constexpr f32 getButtonZAlpha() const { return mButtonZAlpha; } + + void beginExecution(); + void endExecution(); + bool isExecuting() const { return mExecuting; } + void setExecuting(bool executing) { mExecuting = executing; } + + void publishKantera(s32 now, s32 max); + void publishOxygen(s32 now, s32 max); + + void present(); + void presentVessel(f32 i_posX, f32 i_posY, f32 i_scale); + void presentButtonA(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale); + void presentButtonB(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale); + void presentMapAlpha(dMeterMap_c* map); + + void showLightDrop(bool show, bool get_path = false); + void showCross(bool show); + void showMap(bool show); + + bool lightDropRowUsed() const; + void resetRows(); #endif /* 0x004 */ item_params mItemParams[4]; @@ -284,7 +311,7 @@ public: /* 0x742 */ s16 field_0x742[3]; /* 0x748 */ u8 field_0x748[0xC]; /* 0x756 */ u16 field_0x754; - /* 0x756 */ s16 field_0x756; + /* 0x756 */ DUSK_IF_ELSE(f32, s16) field_0x756; /* 0x758 */ u8 field_0x758; /* 0x759 */ u8 field_0x759; /* 0x75A */ u8 field_0x75a; @@ -363,6 +390,9 @@ public: /* 0x858 */ GXColor mButtonZTextColor; /* 0x85C */ GXColor mButtonXYTextColor; /* 0x860 */ u8 field_0x860[2]; +#if TARGET_PC + bool mExecuting; +#endif }; #endif /* D_METER_D_METER2_DRAW_H */ diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp index a90124ebb6..80dd19c7b1 100644 --- a/src/d/d_meter2.cpp +++ b/src/d/d_meter2.cpp @@ -36,9 +36,12 @@ f32 dGetUserHudScale() { } } // namespace + +#include "d_meter2_vdt.inc" #endif int dMeter2_c::_create() { + IF_DUSK(mPresentationTargets = {}); stage_stag_info_class* stag_info = dComIfGp_getStageStagInfo(); if (dStage_stagInfo_GetUpButton(stag_info) == 1) { mpHeap = fopMsgM_createExpHeap(HEAP_SIZE(0x5A400, 0xA0000), NULL); @@ -256,10 +259,19 @@ int dMeter2_c::_create() { mpHeap->getTotalFreeSize(); field_0x11c = NULL; mDoExt_setCurrentHeap(heap); + + IF_DUSK(base.draw_interp_frame = true); + return cPhs_COMPLEATE_e; } int dMeter2_c::_execute() { +#if TARGET_PC + if (mpMeterDraw != NULL) { + mpMeterDraw->beginExecution(); + } +#endif + JKRHeap* heap = mDoExt_setCurrentHeap(mpHeap); if (!dComIfGs_isCollectMirror(0) @@ -321,6 +333,13 @@ int dMeter2_c::_execute() { dComIfGp_setBottleStatus(0, 0); dComIfGp_setCStickStatus(0, 0, 0); +#if TARGET_PC + if (mpMeterDraw != NULL) { + mPresentationTargets.ready = true; + mpMeterDraw->endExecution(); + } +#endif + mDoExt_setCurrentHeap(heap); return 1; } @@ -820,11 +839,12 @@ void dMeter2_c::moveKantera() { } if (draw_kantera == true) { - mpMeterDraw->drawKantera(mMaxOil, mNowOil, x_pos, y_pos); + IF_NOT_DUSK(mpMeterDraw->drawKantera(mMaxOil, mNowOil, x_pos, y_pos)); } alphaAnimeKantera(); dComIfGp_setItemNowOil(mNowOil); + IF_DUSK(mpMeterDraw->publishKantera(mNowOil, mMaxOil)); } void dMeter2_c::moveOxygen() { @@ -941,11 +961,12 @@ void dMeter2_c::moveOxygen() { } if (draw_oxygen == true) { - mpMeterDraw->drawOxygen(mMaxOxygen, mNowOxygen, x_pos, y_pos); + IF_NOT_DUSK(mpMeterDraw->drawOxygen(mMaxOxygen, mNowOxygen, x_pos, y_pos)); } alphaAnimeOxygen(); dComIfGp_setNowOxygen(mNowOxygen); + IF_DUSK(mpMeterDraw->publishOxygen(mNowOxygen, mMaxOxygen)); } void dMeter2_c::moveLightDrop() { @@ -1002,6 +1023,12 @@ void dMeter2_c::moveLightDrop() { alpha = g_drawHIO.mLightDrop.mVesselAlpha[0]; } +#if TARGET_PC + mPresentationTargets.vesselX = pos_x; + mPresentationTargets.vesselY = pos_y; + mPresentationTargets.vesselScale = scale; + mPresentationTargets.vesselAlpha = alpha; +#else if (mVesselPosX != pos_x) { cLib_addCalc2(&mVesselPosX, pos_x, 1.0f, 10.0f); draw_lightdrop = true; @@ -1033,6 +1060,7 @@ void dMeter2_c::moveLightDrop() { mVesselAlpha = alpha; } } +#endif if (draw_lightdrop == true) { mpMeterDraw->drawLightDrop(mLightDropNum, mNeedLightDropNum, mVesselPosX, mVesselPosY, @@ -1384,6 +1412,12 @@ void dMeter2_c::moveButtonA() { var_f31 = 1.0f; } +#if TARGET_PC + for (int i = 0; i < 2; i++) { + mPresentationTargets.buttonAX[i] = pos_x[i]; + mPresentationTargets.buttonAY[i] = pos_y[i]; + } +#else for (int i = 0; i < 2; i++) { if (mButtonATalkPosX[i] != pos_x[i]) { cLib_addCalc2(&mButtonATalkPosX[i], pos_x[i], 1.0f, 10.0f); @@ -1409,6 +1443,7 @@ void dMeter2_c::moveButtonA() { field_0x144 = var_f31; } } +#endif if (field_0x200 != dMsgObject_isTalkNowCheck()) { field_0x200 = dMsgObject_isTalkNowCheck(); @@ -1589,6 +1624,12 @@ void dMeter2_c::moveButtonB() { draw_buttonB = true; } +#if TARGET_PC + for (int i = 0; i < 2; i++) { + mPresentationTargets.buttonBX[i] = pos_x[i]; + mPresentationTargets.buttonBY[i] = pos_y[i]; + } +#else for (int i = 0; i < 2; i++) { if (field_0x148[i] != pos_x[i]) { cLib_addCalc2(&field_0x148[i], pos_x[i], 1.0f, 10.0f); @@ -1614,6 +1655,7 @@ void dMeter2_c::moveButtonB() { field_0x158 = var_f31; } } +#endif if (g_drawHIO.mItemScaleAdjustON && field_0x4bc != g_drawHIO.mItemScalePercent) { field_0x4bc = g_drawHIO.mItemScalePercent; @@ -2199,6 +2241,10 @@ void dMeter2_c::moveButtonCross() { temp_f30 = mButtonCrossOFFPosX + (((f32)field_0x1b4 / (f32)g_drawHIO.mButtonCrossMoveFrame) * (mButtonCrossONPosX - mButtonCrossOFFPosX)); +#if TARGET_PC + mPresentationTargets.crossX = temp_f30; + mPresentationTargets.crossY = var_f31; +#else if (field_0x15c != var_f31) { cLib_addCalc2(&field_0x15c, var_f31, 0.5f, 50.0f); if ((f32)fabs(field_0x15c - var_f31) < 0.5f) { @@ -2206,6 +2252,7 @@ void dMeter2_c::moveButtonCross() { } draw_cross = true; } +#endif if (draw_cross == true) { mpMeterDraw->drawButtonCross(temp_f30, field_0x15c); @@ -2850,7 +2897,15 @@ void dMeter2_c::alphaAnimeLightDrop() { if (!isShowLightDrop()) { mpMeterDraw->setAlphaLightDropAnimeMin(); } else { +#if TARGET_PC + const bool dropGetPath = + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF; + + mpMeterDraw->showLightDrop(true, dropGetPath); +#else mpMeterDraw->setAlphaLightDropAnimeMax(); +#endif } mpMeterDraw->setAlphaLightDropChange(false); @@ -2994,36 +3049,34 @@ void dMeter2_c::alphaAnimeButtonCross() { { mpMeterDraw->setAlphaButtonCrossAnimeMin(); - if ((!dComIfGp_event_chkEventFlag(0x40) || dMeter2Info_isGameStatus(2) || - (mStatus & 0x100)) && - field_0x190 > 0) - { - field_0x190--; + if ((!dComIfGp_event_chkEventFlag(0x40) || dMeter2Info_isGameStatus(2) || (mStatus & 0x100)) IF_NOT_DUSK(&& field_0x190 > 0)) { + DUSK_IF_ELSE(mpMeterDraw->showMap(false), field_0x190--); } } else if (dMeter2Info_isSub2DStatus(1) || dMeter2Info_isFloatingMessageVisible()) { mpMeterDraw->setAlphaButtonCrossAnimeMin(); +#if TARGET_PC + mpMeterDraw->showMap(true); +#else if (field_0x190 < 5) { field_0x190++; } +#endif } else { #if TARGET_PC - if (dusk::getSettings().game.enableTouchControls) { - mpMeterDraw->setAlphaButtonCrossAnimeMin(); - } else { - mpMeterDraw->setAlphaButtonCrossAnimeMax(); - } + mpMeterDraw->showCross(!dusk::getSettings().game.enableTouchControls); + mpMeterDraw->showMap(true); #else mpMeterDraw->setAlphaButtonCrossAnimeMax(); -#endif if (field_0x190 < 5) { field_0x190++; } +#endif } if (mpMap != NULL) { - mpMap->setMapAlpha((field_0x190 * 255.0f) / 5.0f); + IF_NOT_DUSK(mpMap->setMapAlpha((field_0x190 * 255.0f) / 5.0f)); } } diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index 742fe6762d..9771e030c4 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -23,14 +23,23 @@ #include #if TARGET_PC +#include "dusk/game_clock.h" #include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" #include "dusk/settings.h" #include "dusk/ui/icon_provider.hpp" #include "dusk/version.hpp" +#include "d/d_meter2.h" + #include namespace { +f32 s_button_a_scale = 1.0f; +f32 s_button_a_text_scale = 1.0f; +int s_button_b_kind; +f32 s_button_b_scale = 1.0f; +f32 s_button_b_text_scale = 1.0f; // Reads the user HUD scale setting, clamped to a safe range. f32 dGetUserHudScale() { @@ -56,12 +65,14 @@ void dAnchorHudScale(CPaneMgr* i_pane, HudCorner i_corner, f32* io_x, f32* io_y, *io_x += dirX * i_pane->getInitSizeX() * half * i_pull; *io_y += dirY * i_pane->getInitSizeY() * half; } - } // namespace + +#include "d_meter2_draw_vdt.inc" #endif dMeter2Draw_c::dMeter2Draw_c(JKRExpHeap* mp_heap) { #if TARGET_PC + setExecuting(false); // correct hio data here because we can't do runtime disc checks in sinit data constructors if (dusk::version::isRegionJpn()) { g_drawHIO.mButtonATextSpacing = -2.0f; @@ -607,6 +618,7 @@ void dMeter2Draw_c::init() { initButton(); initButtonCross(); field_0x772 = 0; + IF_DUSK(resetRows()); } void dMeter2Draw_c::exec(u32 i_status) { @@ -691,6 +703,15 @@ void dMeter2Draw_c::exec(u32 i_status) { } void dMeter2Draw_c::draw() { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + advance_fade_rows(); + if (dMeter2_c* meter = dMeter2Info_getMeterClass()) { + meter->presentAnims(); + } + } +#endif + J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort(); graf_ctx->setup2D(); @@ -707,16 +728,12 @@ void dMeter2Draw_c::draw() { drawKanteraScreen(1); drawKanteraScreen(2); -#if TARGET_PC - if (!touchControlsEnabled) { -#endif + IF_DUSK_BLOCK(!touchControlsEnabled) for (int i = 0; i < 2; i++) { if (mpItemXY[i] != NULL) { for (int j = 0; j < 3; j++) { f32 temp_f30 = mItemParams[i].num_scale * 16.0f; -#if TARGET_PC - temp_f30 *= dGetUserHudScale(); -#endif + IF_DUSK(temp_f30 *= dGetUserHudScale()); Vec vtx0 = mpItemXY[i]->getPanePtr()->getGlbVtx(0); Vec vtx3 = mpItemXY[i]->getPanePtr()->getGlbVtx(3); @@ -758,9 +775,7 @@ void dMeter2Draw_c::draw() { } } } -#if TARGET_PC - } -#endif + IF_DUSK_BLOCK_END if (mpLightDropParent->getAlphaRate() != 0.0f) { f32 var_f28 = g_drawHIO.mLightDrop.mPikariScaleNormal; @@ -769,24 +784,22 @@ void dMeter2Draw_c::draw() { if (field_0x756 >= 0) { var_f29 = g_drawHIO.mLightDrop.mDropPikariAnimSpeed_Completed; int temp_r5_2 = g_drawHIO.mLightDrop.mPikariInterval * 15; - IF_DUSK(var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete); // FRAME INTERP NOTE: Set even if not advancing - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) if (field_0x756 <= temp_r5_2) { - int temp_r4 = (field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); - int temp_r3_5 = field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; + int temp_r4 = (IF_DUSK((int))field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); + int temp_r3_5 = IF_DUSK((int))field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; if (temp_r4 == 0 && field_0x62c[temp_r3_5] == 0.0f) { field_0x62c[temp_r3_5] = 18.0f; } var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - field_0x756++; + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); } else { int temp_r5_3 = temp_r5_2 + 1; - if (field_0x756 == temp_r5_3) { + if (field_0x756 DUSK_IF_ELSE(< temp_r5_3 + 1, == temp_r5_3)) { if (field_0x62c[15] == 0.0f) { - field_0x756++; + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); } var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; } else if (field_0x756 >= g_drawHIO.mLightDrop.field_0x54 + temp_r5_3) { @@ -797,10 +810,9 @@ void dMeter2Draw_c::draw() { field_0x756 = -1; } else { - field_0x756++; + DUSK_IF_ELSE(field_0x756 += dusk::game_clock::original_frames(), field_0x756++); } } - IF_DUSK_BLOCK_END } for (int i = 0; i < 16; i++) { @@ -984,8 +996,7 @@ void dMeter2Draw_c::initMagic() { g_drawHIO.mMagicMeterPosY + offsetY); setAlphaMagicChange(true); - drawKantera(dComIfGs_getMaxOil(), dComIfGs_getOil(), g_drawHIO.mLanternMeterPosX + offsetX, - g_drawHIO.mNoMagicPosY + (g_drawHIO.mLanternMeterPosY + offsetY)); + IF_NOT_DUSK(drawKantera(dComIfGs_getMaxOil(), dComIfGs_getOil(), g_drawHIO.mLanternMeterPosX + offsetX, g_drawHIO.mNoMagicPosY + (g_drawHIO.mLanternMeterPosY + offsetY))); setAlphaKanteraChange(true); } @@ -1465,7 +1476,7 @@ void dMeter2Draw_c::drawPikari(CPaneMgr* i_pane, f32* i_framep, f32 i_scale, void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scale, JUtility::TColor i_moyaBlack, JUtility::TColor i_moyaWhite, JUtility::TColor i_moyabsBlack, JUtility::TColor i_moyabsWhite, - f32 param_8, u8 param_9) { + f32 param_8, u8 param_9 IF_DUSK_ARG(f32 elapsedFrames)) { f32 var_f31 = 28.0f; if (param_9 == 4) { @@ -1490,7 +1501,30 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal if (param_9 != 3 && param_9 != 4 && param_9 != 5 && dMsgObject_isTalkNowCheck()) { *i_framep = 0.0f; } else { - IF_DUSK_BLOCK(dusk::interp::get_ui_tick_pending()) +#if TARGET_PC + const f32 previous = *i_framep; + const f32 frames = elapsedFrames >= 0.0f ? elapsedFrames : dusk::game_clock::original_frames(); + const f32 next = previous + param_8 * frames; + const bool looping = param_9 == 1 || param_9 == 2 || param_9 == 3; + *i_framep = next > var_f31 ? (looping ? 18.0f : 0.0f) : next; + + if (previous <= 18.0f && next > 18.0f) { + switch (param_9) { + case 1: + mDoAud_seStart(Z2SE_NAVI_BLINK, NULL, 0, 0); + break; + case 2: + mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); + break; + case 3: + if (i_framep == &field_0x738) { + mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); + dMeter2Info_set2DVibration(); + } + break; + } + } +#else *i_framep += param_8; if (*i_framep > var_f31) { if (param_9 == 1 || param_9 == 2 || param_9 == 3) { @@ -1505,7 +1539,7 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal } else if (*i_framep == 18.0f && param_9 == 2) { mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); } - IF_DUSK_BLOCK_END +#endif playPikariBckAnimation(*i_framep); playPikariBpkAnimation(*i_framep); @@ -1651,6 +1685,7 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) } void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool var_r31 = false; bool var_r30 = false; bool var_r29 = false; @@ -1696,20 +1731,29 @@ void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { if (var_r31 || var_r28 || param_0) { mpBigHeart->setAlphaRate(mBigHeartAlpha * mLifeParentAlpha); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaLifeAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowLife, false); +#else if (mpLifeParent->getAlphaRate() != 0.0f) { mpLifeParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMin(mpLifeParent, 5); } +#endif } void dMeter2Draw_c::setAlphaLifeAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowLife, true); +#else if (mpLifeParent->getAlphaRate() != g_drawHIO.mParentAlpha) { mpLifeParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMax(mpLifeParent, 5); } +#endif } void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) { @@ -1835,7 +1879,7 @@ static f32 dummyLiteralOrder() { return 5.0f; } -void dMeter2Draw_c::drawKantera(s32 i_max, s32 i_oil, f32 i_posX, f32 i_posY) { +void dMeter2Draw_c::drawKantera(s32 i_max, DUSK_IF_ELSE(f32, s32) i_oil, f32 i_posX, f32 i_posY) { f32 var_f6 = mpMagicFrameR->getInitPosX() - mpMagicFrameL->getInitPosX(); f32 var_f7 = 0.0f; f32 var_f4 = 0.0f; @@ -1858,6 +1902,7 @@ void dMeter2Draw_c::drawKantera(s32 i_max, s32 i_oil, f32 i_posX, f32 i_posY) { } void dMeter2Draw_c::setAlphaKanteraChange(bool i_forceSet) { + IF_DUSK_BLOCK(!isExecuting()) bool meter_parent_alpha_set = false; bool meter_alpha_set = false; bool meter_frame_alpha_set = false; @@ -1885,9 +1930,13 @@ void dMeter2Draw_c::setAlphaKanteraChange(bool i_forceSet) { mpMagicFrameL->setAlphaRate(mLanternMeterFrameAlpha * field_0x7b0); mpMagicFrameR->setAlphaRate(mLanternMeterFrameAlpha * field_0x7b0); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaKanteraAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowKantera, false); +#else if (field_0x742[1] <= 0) { mMeterAlphaRate[1] = 0.0f; } else { @@ -1898,9 +1947,13 @@ void dMeter2Draw_c::setAlphaKanteraAnimeMin() { mMeterAlphaRate[1] = (field_0x742[1] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::setAlphaKanteraAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowKantera, true); +#else if (field_0x742[1] >= 5) { mMeterAlphaRate[1] = g_drawHIO.mParentAlpha; } else { @@ -1911,9 +1964,10 @@ void dMeter2Draw_c::setAlphaKanteraAnimeMax() { mMeterAlphaRate[1] = (field_0x742[1] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } -void dMeter2Draw_c::drawOxygen(s32 i_max, s32 i_oxygen, f32 i_posX, f32 i_posY) { +void dMeter2Draw_c::drawOxygen(s32 i_max, DUSK_IF_ELSE(f32, s32) i_oxygen, f32 i_posX, f32 i_posY) { f32 var_f6 = mpMagicFrameR->getInitPosX() - mpMagicFrameL->getInitPosX(); f32 var_f7 = 0.0f; f32 var_f4 = 0.0f; @@ -1936,6 +1990,7 @@ void dMeter2Draw_c::drawOxygen(s32 i_max, s32 i_oxygen, f32 i_posX, f32 i_posY) } void dMeter2Draw_c::setAlphaOxygenChange(bool i_forceSet) { + IF_DUSK_BLOCK(!isExecuting()) bool meter_parent_alpha_set = false; bool meter_alpha_set = false; bool meter_frame_alpha_set = false; @@ -1963,9 +2018,13 @@ void dMeter2Draw_c::setAlphaOxygenChange(bool i_forceSet) { mpMagicFrameL->setAlphaRate(mOxygenMeterFrameAlpha * field_0x7bc); mpMagicFrameR->setAlphaRate(mOxygenMeterFrameAlpha * field_0x7bc); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaOxygenAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowOxygen, false); +#else if (field_0x742[2] <= 0) { mMeterAlphaRate[2] = 0.0f; } else { @@ -1976,9 +2035,13 @@ void dMeter2Draw_c::setAlphaOxygenAnimeMin() { mMeterAlphaRate[2] = (field_0x742[2] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::setAlphaOxygenAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowOxygen, true); +#else if (field_0x742[2] >= 5) { mMeterAlphaRate[2] = g_drawHIO.mParentAlpha; } else { @@ -1989,6 +2052,7 @@ void dMeter2Draw_c::setAlphaOxygenAnimeMax() { mMeterAlphaRate[2] = (field_0x742[2] / 5.0f) * g_drawHIO.mParentAlpha; } +#endif } void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY, f32 i_vesselScale, @@ -2037,23 +2101,14 @@ void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY } } + IF_DUSK_BLOCK(!isExecuting()) field_0x6fc = param_5; mLightDropVesselScale = i_vesselScale; -#if TARGET_PC - const f32 lightDropUserScale = dGetUserHudScale(); - const f32 lightDropScale = mLightDropVesselScale * field_0x6f8 * lightDropUserScale; - mpLightDropParent->scale(lightDropScale, lightDropScale); - - f32 lightDropPosX = i_posX; - f32 lightDropPosY = i_posY; - dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &lightDropPosX, &lightDropPosY); - mpLightDropParent->paneTrans(lightDropPosX, lightDropPosY); -#else - mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, - mLightDropVesselScale * field_0x6f8); - + mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8 IF_DUSK(* dGetUserHudScale()), + mLightDropVesselScale * field_0x6f8 IF_DUSK(* dGetUserHudScale())); + IF_DUSK(dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &i_posX, &i_posY)); mpLightDropParent->paneTrans(i_posX, i_posY); -#endif + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaLightDropChange(bool unused) {} @@ -2075,6 +2130,9 @@ f32 dMeter2Draw_c::getNowLightDropRateCalc() { } void dMeter2Draw_c::setAlphaLightDropAnimeMin() { +#if TARGET_PC + showLightDrop(false); +#else if (mpLightDropParent->getAlphaRate() != 0.0f) { mpLightDropParent->setAlphaRate(g_drawHIO.mParentAlpha * field_0x6fc); setAlphaAnimeMin(mpLightDropParent, 5); @@ -2094,14 +2152,20 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMin() { } } } +#endif } void dMeter2Draw_c::setAlphaLightDropAnimeMax() { +#if TARGET_PC + const bool dropGetPath = dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF; + + showLightDrop(true, dropGetPath); +#else f32 temp_f31 = g_drawHIO.mParentAlpha * field_0x6fc; if (dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) > 1 && - dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF) - { + dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) != 0xFF) { if (mpLightDropParent->getAlphaTimer() == 0) { Z2GetAudioMgr()->seStart(Z2SE_SY_LIGHT_POT_EQUIP, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -2126,13 +2190,8 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMax() { field_0x6f8 = 1.0f; } -#if TARGET_PC - const f32 dropAnimScale = mLightDropVesselScale * field_0x6f8 * dGetUserHudScale(); - mpLightDropParent->scale(dropAnimScale, dropAnimScale); -#else mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, - mLightDropVesselScale * field_0x6f8); -#endif + mLightDropVesselScale * field_0x6f8); if (g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum == mpLightDropParent->getAlphaTimer()) { dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 0xFF); @@ -2164,6 +2223,7 @@ void dMeter2Draw_c::setAlphaLightDropAnimeMax() { mpSIParts[i][2]->setAlphaRate(mpSIParent[1]->getAlphaRate() * 0.3f); } } +#endif } void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { @@ -2238,6 +2298,7 @@ void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { } void dMeter2Draw_c::setAlphaRupeeChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_rupeekey = false; bool set_rupee = false; @@ -2290,9 +2351,13 @@ void dMeter2Draw_c::setAlphaRupeeChange(bool param_0) { } } } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaRupeeAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowRupee, false); +#else f32 alphas[3]; alphas[0] = g_drawHIO.mRupeeAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); alphas[1] = g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); @@ -2304,9 +2369,13 @@ void dMeter2Draw_c::setAlphaRupeeAnimeMin() { setAlphaAnimeMin(mpRupeeParent[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaRupeeAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowRupee, true); +#else f32 alphas[3]; alphas[0] = g_drawHIO.mRupeeAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); alphas[1] = g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); @@ -2318,6 +2387,7 @@ void dMeter2Draw_c::setAlphaRupeeAnimeMax() { setAlphaAnimeMax(mpRupeeParent[i], 5); } } +#endif } void dMeter2Draw_c::drawKey(s16 i_keyNum) { @@ -2352,6 +2422,7 @@ void dMeter2Draw_c::drawKey(s16 i_keyNum) { } void dMeter2Draw_c::setAlphaKeyChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_rupeekey = false; bool set_key = false; @@ -2386,24 +2457,33 @@ void dMeter2Draw_c::setAlphaKeyChange(bool param_0) { mpKeyTexture[i]->setAlphaRate(field_0x7e0 * (field_0x7e4 * (mKeyNumAlpha * mKeyAlpha))); } } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaKeyAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowKey, false); +#else f32 alpha = g_drawHIO.mKeyAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); if (mpKeyParent->getAlphaRate() != 0.0f) { mpKeyParent->setAlphaRate(alpha); setAlphaAnimeMin(mpKeyParent, 5); } +#endif } void dMeter2Draw_c::setAlphaKeyAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowKey, true); +#else f32 alpha = g_drawHIO.mKeyAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha); if (mpKeyParent->getAlphaRate() != alpha) { mpKeyParent->setAlphaRate(alpha); setAlphaAnimeMax(mpKeyParent, 5); } +#endif } void dMeter2Draw_c::drawButtonA(u8 i_action, f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, @@ -2463,11 +2543,16 @@ void dMeter2Draw_c::drawButtonA(u8 i_action, f32 i_posX, f32 i_posY, f32 i_textP } } +#if TARGET_PC + s_button_a_scale = var_f31; + s_button_a_text_scale = var_f30; +#else mpButtonA->scale(var_f31 * i_scale, var_f31 * i_scale); mpButtonA->paneTrans(i_posX, i_posY); mpTextA->scale(var_f30 * i_scale, var_f30 * i_scale); mpTextA->paneTrans(g_drawHIO.mButtonATextPosX + i_textPosX, g_drawHIO.mButtonATextPosY + i_textPosY IF_DUSK(+ (dusk::version::isLessThanWiiJpn() ? 6.0f : 0.0f))); +#endif } void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_posY, f32 i_textPosX, @@ -2554,9 +2639,15 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos mpScreen->search(MULTI_CHAR('item_b_n'))->hide(); } +#if TARGET_PC + s_button_b_kind = var_r31; + s_button_b_scale = var_f31; + s_button_b_text_scale = var_f30; +#endif mpItemB->getPanePtr()->rotate(mpItemB->getSizeX() * 0.5f, mpItemB->getSizeY() * 0.5f, ROTATE_Z, g_drawHIO.mButtonBItemRotation[var_r31]); +#if !TARGET_PC field_0x730 = var_f31 * i_scale; mpButtonB->scale(field_0x730 * field_0x734, field_0x730 * field_0x734); mpButtonB->paneTrans(g_drawHIO.mButtonBPosX + i_posX, g_drawHIO.mButtonBPosY + i_posY); @@ -2573,6 +2664,7 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos mpTextB->scale(var_f30 * i_scale, var_f30 * i_scale); mpTextB->paneTrans(g_drawHIO.mButtonBFontPosX + i_textPosX, g_drawHIO.mButtonBFontPosY + i_textPosY); +#endif } void dMeter2Draw_c::drawButtonR(u8 unused0, u8 i_action, bool unused1, bool unused2) { @@ -2824,27 +2916,20 @@ f32 dMeter2Draw_c::getButtonCrossParentInitTransY() { } void dMeter2Draw_c::drawButtonCross(f32 i_posX, f32 i_posY) { -#if TARGET_PC - const f32 buttonCrossUserScale = dGetUserHudScale(); - const f32 buttonCrossScale = g_drawHIO.mButtonCrossScale * buttonCrossUserScale; - mpButtonCrossParent->scale(buttonCrossScale, buttonCrossScale); -#else - mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale, g_drawHIO.mButtonCrossScale); -#endif + IF_DUSK_BLOCK(!isExecuting()) + mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale IF_DUSK(* dGetUserHudScale()), + g_drawHIO.mButtonCrossScale IF_DUSK(* dGetUserHudScale())); mpTextI->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); mpTextM->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); - -#if TARGET_PC - f32 buttonCrossPosX = i_posX; - f32 buttonCrossPosY = i_posY; - dAnchorHudScale(mpButtonCrossParent, HudCorner::BottomLeft, &buttonCrossPosX, &buttonCrossPosY); - mpButtonCrossParent->paneTrans(buttonCrossPosX, buttonCrossPosY); -#else + IF_DUSK(dAnchorHudScale(mpButtonCrossParent, HudCorner::BottomLeft, &i_posX, &i_posY)); mpButtonCrossParent->paneTrans(i_posX, i_posY); -#endif + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { +#if TARGET_PC + showCross(false); +#else if (mpButtonCrossParent->getAlphaRate() != 0.0f) { mpButtonCrossParent->setAlphaRate(g_drawHIO.mParentAlpha); setAlphaAnimeMin(mpButtonCrossParent, 5); @@ -2852,6 +2937,7 @@ void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { setAlphaButtonCrossItemAnimeMin(); setAlphaButtonCrossMapAnimeMin(); +#endif } void dMeter2Draw_c::setAlphaButtonCrossAnimeMax() { @@ -2930,6 +3016,7 @@ void dMeter2Draw_c::setAlphaButtonCrossMapAnimeMax() { } void dMeter2Draw_c::setAlphaButtonChange(bool param_0) { + IF_DUSK_BLOCK(!isExecuting()) bool set_parent = false; bool set_buttonA = false; bool set_buttonB = false; @@ -3125,9 +3212,13 @@ void dMeter2Draw_c::setAlphaButtonChange(bool param_0) { static_cast(mpXYText[4][1]->getPanePtr()) ->setFontColor(g_drawHIO.mButtonXYTextColor, g_drawHIO.mButtonXYTextColor); } + IF_DUSK_BLOCK_END } void dMeter2Draw_c::setAlphaButtonAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonParent, false); +#else if (mpButtonParent->getAlphaRate() != 0.0f) { mpButtonParent->setAlphaRate(g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); setAlphaAnimeMin(mpButtonParent, 5); @@ -3136,9 +3227,13 @@ void dMeter2Draw_c::setAlphaButtonAnimeMin() { mpUzu->setAlphaRate(mButtonBaseAlpha * mpButtonParent->getAlphaRate()); } } +#endif } void dMeter2Draw_c::setAlphaButtonAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonParent, true); +#else if (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha != mpButtonParent->getAlphaRate()) { mpButtonParent->setAlphaRate(g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); setAlphaAnimeMax(mpButtonParent, 5); @@ -3153,9 +3248,13 @@ void dMeter2Draw_c::setAlphaButtonAnimeMax() { dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 2); } } +#endif } void dMeter2Draw_c::setAlphaButtonAAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonA, false); +#else if (mpButtonA->getAlphaRate() != 0.0f) { mpButtonA->setAlphaRate(g_drawHIO.mButtonAAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); @@ -3168,9 +3267,13 @@ void dMeter2Draw_c::setAlphaButtonAAnimeMin() { setAlphaAnimeMin(mpAText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonAAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonA, true); +#else if (g_drawHIO.mButtonAAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) != mpButtonA->getAlphaRate()) { @@ -3185,9 +3288,13 @@ void dMeter2Draw_c::setAlphaButtonAAnimeMax() { setAlphaAnimeMax(mpAText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonBAnimeMin() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonB, false); +#else if (mpButtonB->getAlphaRate() != 0.0f) { mpButtonB->setAlphaRate(g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); @@ -3209,9 +3316,13 @@ void dMeter2Draw_c::setAlphaButtonBAnimeMin() { setAlphaAnimeMin(mpBText[i], 5); } } +#endif } void dMeter2Draw_c::setAlphaButtonBAnimeMax() { +#if TARGET_PC + write_fade_row(this, kFadeRowButtonB, true); +#else if (mpButtonB->getAlphaRate() != g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)) { @@ -3235,83 +3346,116 @@ void dMeter2Draw_c::setAlphaButtonBAnimeMax() { setAlphaAnimeMax(mpBText[i], 5); } } +#endif } void dMeter2Draw_c::setButtonIconAAlpha(u8 unused0, u32 unused1, bool unused2) { - if (mpButtonA->isVisible()) { - u8 alpha = g_drawHIO.mButtonAAlpha * - (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * - (f32)mpButtonA->getInitAlpha(); - f32 alpha_rate = mpButtonParent->getAlphaRate(); + if (DUSK_IF_ELSE(isExecuting() ? mpButtonA->isVisible() : s_follow_a.live, mpButtonA->isVisible())) { +#if TARGET_PC + if (isExecuting()) { + follow_button_a(true, !dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4), !dMeter2Info_isUseButton(1)); + } else +#endif + { + u8 alpha = g_drawHIO.mButtonAAlpha * + (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * + (f32)mpButtonA->getInitAlpha(); + f32 alpha_rate = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); - if (!dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4)) { - alpha = g_drawHIO.mButtonXYBaseDimAlpha; - } - - mpButtonA->setAlpha(alpha * alpha_rate); - - for (int i = 0; i < 5; i++) { - u8 alpha = mpAText[i]->getInitAlpha(); - if (!dMeter2Info_isUseButton(1)) { - alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + if (DUSK_IF_ELSE(s_follow_a.dim, !dMeter2Info_isUseButton(1) && !dComIfGp_isDoSetFlag(4))) { + alpha = g_drawHIO.mButtonXYBaseDimAlpha; } - mpAText[i]->setAlpha(alpha * alpha_rate); + mpButtonA->setAlpha(alpha * alpha_rate); + + for (int i = 0; i < 5; i++) { + u8 alpha = mpAText[i]->getInitAlpha(); + if (DUSK_IF_ELSE(s_follow_a.dim_txt, !dMeter2Info_isUseButton(1))) { + alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + } + + mpAText[i]->setAlpha(alpha * alpha_rate); + } } +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_a(false, false, false); + } +#endif } } void dMeter2Draw_c::setButtonIconBAlpha(u8 unused0, u32 unused1, bool param_2) { - if (mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible()) { - f32 temp_f30 = - g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + if (DUSK_IF_ELSE(isExecuting() ? (mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible()) + : s_follow_b.live, mpItemB->isVisible() || mpLightB->isVisible() || mpButtonB->isVisible())) + { +#if TARGET_PC + if (isExecuting()) { + follow_button_b(true, !(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1), !dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1), param_2, mpItemB->isVisible()); + } else +#endif + { + f32 temp_f30 = + g_drawHIO.mButtonBAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - f32 var_f2; - if (mpItemB->isVisible()) { - var_f2 = mItemBBaseAlpha[0]; - } else { - var_f2 = mItemBBaseAlpha[1]; - } - - u8 var_r25 = mpItemB->getInitAlpha(); - u8 var_r26 = var_f2 * (temp_f30 * (f32)mpLightB->getInitAlpha()); - u8 var_r27 = temp_f30 * (f32)mpButtonB->getInitAlpha(); - f32 temp_f31 = mpButtonParent->getAlphaRate(); - - if (!(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1)) { - var_r25 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26 = g_drawHIO.mButtonXYItemDimAlpha; - var_r27 = g_drawHIO.mButtonXYBaseDimAlpha; - } - - if (!param_2) { - var_r26 = 0; - } - - mpItemB->setAlpha(var_r25 * temp_f31); - mpLightB->setAlpha(var_r26 * temp_f31); - mpButtonB->setAlpha(var_r27 * temp_f31); - - for (int i = 0; i < 5; i++) { - u8 alpha = mpBText[i]->getInitAlpha(); - if (!dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1)) { - alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + f32 var_f2; + if (DUSK_IF_ELSE(s_follow_b.item_visible, mpItemB->isVisible())) { + var_f2 = mItemBBaseAlpha[0]; + } else { + var_f2 = mItemBBaseAlpha[1]; } - mpBText[i]->setAlpha(alpha * temp_f31); + u8 var_r25 = mpItemB->getInitAlpha(); + u8 var_r26 = var_f2 * (temp_f30 * (f32)mpLightB->getInitAlpha()); + u8 var_r27 = temp_f30 * (f32)mpButtonB->getInitAlpha(); + f32 temp_f31 = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); + + if (DUSK_IF_ELSE(s_follow_b.dim, !(dMeter2Info_isUseButton(2) & 1) && !dMeter2Info_isSub2DStatus(1))) { + var_r25 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26 = g_drawHIO.mButtonXYItemDimAlpha; + var_r27 = g_drawHIO.mButtonXYBaseDimAlpha; + } + + if (!DUSK_IF_ELSE(s_follow_b.light_on, param_2)) { + var_r26 = 0; + } + + mpItemB->setAlpha(var_r25 * temp_f31); + mpLightB->setAlpha(var_r26 * temp_f31); + mpButtonB->setAlpha(var_r27 * temp_f31); + + for (int i = 0; i < 5; i++) { + u8 alpha = mpBText[i]->getInitAlpha(); + if (DUSK_IF_ELSE(s_follow_b.dim_txt, !dMeter2Info_isUseButton(2) && !dMeter2Info_isSub2DStatus(1))) { + alpha = (f32)alpha * ((f32)g_drawHIO.field_0x42c / 255.0f); + } + + mpBText[i]->setAlpha(alpha * temp_f31); + } } +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_b(false, false, false, false, false); + } +#endif } } void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { +#if TARGET_PC + bool midona_live = false; + bool midona_hold = false; + f32 midona_z_target = 0.0f; +#endif mpButtonMidona->scale(g_drawHIO.mMidnaIconScale, g_drawHIO.mMidnaIconScale); mpButtonMidona->paneTrans(g_drawHIO.mMidnaIconPosX, g_drawHIO.mMidnaIconPosY); if (mpButtonMidona->isVisible()) { - f32 temp_f30 = - g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + IF_NOT_DUSK(f32 temp_f30 = g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha)); f32 var_f29 = (f32)mpButtonMidona->getInitAlpha() / 255.0f; - f32 temp_f31 = mpButtonParent->getAlphaRate(); + f32 temp_f31 = DUSK_IF_ELSE(fade_row_factor(s_fade_rows[kFadeRowButtonParent]), mpButtonParent->getAlphaRate()); bool var_r31 = 1; if (getCanoeFishing() || @@ -3333,12 +3477,14 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { field_0x738 = 18.0f; } +#if !TARGET_PC if (field_0x738 == 18.0f) { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); dMeter2Info_set2DVibration(); } +#endif - mButtonZAlpha = var_f29; + IF_NOT_DUSK(mButtonZAlpha = var_f29); var_r31 = 0; } @@ -3347,6 +3493,7 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { field_0x738 = 0.0f; } +#if !TARGET_PC if (mButtonZAlpha != var_f29) { cLib_addCalc2(&mButtonZAlpha, var_f29, 0.4f, 0.5f); @@ -3354,24 +3501,34 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { mButtonZAlpha = var_f29; } } +#endif } +#if TARGET_PC + midona_live = true; + midona_hold = var_r31 == 0; + midona_z_target = var_f29; +#else mpButtonMidona->setAlpha(temp_f30 * (255.0f * mButtonZAlpha * temp_f31)); +#endif } else { - mButtonZAlpha = 0.0f; + IF_NOT_DUSK(mButtonZAlpha = 0.0f); } f32 var_f29_2 = (g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * (f32)mpButtonXY[2]->getInitAlpha()) / 255.0f; - f32 temp_f30_2 = mpButtonParent->getAlphaRate(); + IF_NOT_DUSK(f32 temp_f30_2 = mpButtonParent->getAlphaRate()); if (param_0 & 0x1000000) { var_f29_2 = 0.0f; } else if (!dMeter2Info_isUseButton(0x800)) { var_f29_2 = (f32)g_drawHIO.mButtonXYBaseDimAlpha / 255.0f; } +#if TARGET_PC + chase_midona(midona_live, midona_hold, midona_z_target, var_f29_2); +#else if (field_0x724 != var_f29_2) { cLib_addCalc2(&field_0x724, var_f29_2, 0.4f, 0.5f); @@ -3381,91 +3538,102 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) { } mpButtonXY[2]->setAlpha(255.0f * field_0x724 * temp_f30_2); - -#if TARGET_PC - dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL); #endif + + IF_DUSK(dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL)); } void dMeter2Draw_c::setButtonIconAlpha(int i_no, u8 unused0, u32 unused1, bool unused2) { JUT_ASSERT(0, i_no < SELECT_MAX_e); - if (mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || - mpButtonXY[i_no]->isVisible()) - { - f32 var_f30 = 1.0f; + if (DUSK_IF_ELSE(isExecuting() ? (mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || mpButtonXY[i_no]->isVisible()) + : s_follow_xy[i_no].live, mpItemXY[i_no]->isVisible() || mpLightXY[i_no]->isVisible() || mpButtonXY[i_no]->isVisible())) { +#if TARGET_PC + if (isExecuting()) { + follow_button_xy(i_no, true, !dMeter2Info_isUseButton(i_no == 0 ? 4 : 8), getFishingType() != 0, mpItemXY[i_no]->isVisible()); + } else +#endif + { + f32 var_f30 = 1.0f; - int var_r26 = 1; - if (mpItemXY[i_no]->isVisible()) { - var_r26 = 0; - } + int var_r26 = 1; + if (DUSK_IF_ELSE(s_follow_xy[i_no].item_visible, mpItemXY[i_no]->isVisible())) { + var_r26 = 0; + } - f32 var_f2; - if (i_no == 0) { - var_f2 = - g_drawHIO.mButtonXAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - var_f30 = g_drawHIO.mButtonXItemBaseAlpha[var_r26]; - } else if (i_no == 1) { - var_f2 = - g_drawHIO.mButtonYAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - var_f30 = g_drawHIO.mButtonYItemBaseAlpha[var_r26]; - } else { - var_f2 = - g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); - } + f32 var_f2; + if (i_no == 0) { + var_f2 = + g_drawHIO.mButtonXAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + var_f30 = g_drawHIO.mButtonXItemBaseAlpha[var_r26]; + } else if (i_no == 1) { + var_f2 = + g_drawHIO.mButtonYAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + var_f30 = g_drawHIO.mButtonYItemBaseAlpha[var_r26]; + } else { + var_f2 = + g_drawHIO.mButtonZAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + } - u8 var_r28 = mpItemXY[i_no]->getInitAlpha(); - u8 var_r27 = var_f30 * (var_f2 * (f32)mpLightXY[i_no]->getInitAlpha()); - u8 var_r26_2 = var_f2 * (f32)mpButtonXY[i_no]->getInitAlpha(); - f32 temp_f31 = mpButtonParent->getAlphaRate(); + u8 var_r28 = mpItemXY[i_no]->getInitAlpha(); + u8 var_r27 = var_f30 * (var_f2 * (f32)mpLightXY[i_no]->getInitAlpha()); + u8 var_r26_2 = var_f2 * (f32)mpButtonXY[i_no]->getInitAlpha(); + f32 temp_f31 = DUSK_IF_ELSE(button_parent_rate(this), mpButtonParent->getAlphaRate()); - if (i_no == 0) { - if (!dMeter2Info_isUseButton(4)) { - if (getFishingType()) { - var_r28 = 0; - } else { - var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + if (i_no == 0) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(4))) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].fishing, getFishingType())) { + var_r28 = 0; + } else { + var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + } + + var_r27 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + } + } else if (i_no == 1) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(8))) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].fishing, getFishingType())) { + var_r28 = 0; + } else { + var_r28 = g_drawHIO.mButtonXYItemDimAlpha; + } + + var_r27 = g_drawHIO.mButtonXYItemDimAlpha; + var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + } + } + + mpItemXY[i_no]->setAlpha((f32)var_r28 * temp_f31); + mpLightXY[i_no]->setAlpha((f32)var_r27 * temp_f31); + mpButtonXY[i_no]->setAlpha((f32)var_r26_2 * temp_f31); + + int var_r26_3 = 1; + if (i_no == 0) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(4))) { + var_r26_3 = 0; + } + } else if (i_no == 1) { + if (DUSK_IF_ELSE(s_follow_xy[i_no].dim, !dMeter2Info_isUseButton(8))) { + var_r26_3 = 0; + } + } + + for (int i = 0; i < 5; i++) { + u8 var_r0 = mpXYText[i][i_no]->getInitAlpha(); + if (!var_r26_3) { + var_r0 = (f32)var_r0 * (g_drawHIO.field_0x42c / 255.0f); } - var_r27 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; - } - } else if (i_no == 1) { - if (!dMeter2Info_isUseButton(8)) { - if (getFishingType()) { - var_r28 = 0; - } else { - var_r28 = g_drawHIO.mButtonXYItemDimAlpha; - } - - var_r27 = g_drawHIO.mButtonXYItemDimAlpha; - var_r26_2 = g_drawHIO.mButtonXYBaseDimAlpha; + mpXYText[i][i_no]->setAlpha((f32)var_r0 * temp_f31); } } - - mpItemXY[i_no]->setAlpha((f32)var_r28 * temp_f31); - mpLightXY[i_no]->setAlpha((f32)var_r27 * temp_f31); - mpButtonXY[i_no]->setAlpha((f32)var_r26_2 * temp_f31); - - int var_r26_3 = 1; - if (i_no == 0) { - if (!dMeter2Info_isUseButton(4)) { - var_r26_3 = 0; - } - } else if (i_no == 1) { - if (!dMeter2Info_isUseButton(8)) { - var_r26_3 = 0; - } - } - - for (int i = 0; i < 5; i++) { - u8 var_r0 = mpXYText[i][i_no]->getInitAlpha(); - if (!var_r26_3) { - var_r0 = (f32)var_r0 * (g_drawHIO.field_0x42c / 255.0f); - } - - mpXYText[i][i_no]->setAlpha((f32)var_r0 * temp_f31); +#if TARGET_PC + } else { + if (isExecuting()) { + follow_button_xy(i_no, false, false, false, false); } +#endif } } @@ -3630,6 +3798,24 @@ void dMeter2Draw_c::changeTextureItemXY(int i_no, u8 i_itemNo) { } void dMeter2Draw_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane, s16 i_min) { +#if TARGET_PC + if (i_pane == NULL) { + return; + } + if (i_min <= 0) { + i_pane->alphaAnimeStart(0.0f); + i_pane->setAlphaRate(0.0f); + return; + } + const f32 alphaRate = i_pane->getAlphaRate(); + f32 alphaTimer = i_pane->getAlphaTimer(); + if (alphaTimer > i_min) { + alphaTimer = i_min; + } + dusk::vdt::advance_toward_frame(alphaTimer, 0.0f, 1.0f); + i_pane->alphaAnimeStart(alphaTimer); + i_pane->setAlphaRate(alphaRate * dusk::vdt::clamped_fraction(alphaTimer, i_min)); +#else f32 alpha_rate = i_pane->getAlphaRate(); s16 alpha_timer = i_pane->getAlphaTimer(); if (alpha_timer > i_min) { @@ -3649,9 +3835,23 @@ void dMeter2Draw_c::setAlphaAnimeMin(CPaneMgrAlpha* i_pane, s16 i_min) { i_pane->alphaAnimeStart(alpha_timer); i_pane->setAlphaRate(alpha_rate * ((f32)alpha_timer / (f32)i_min)); } +#endif } void dMeter2Draw_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane, s16 i_max) { +#if TARGET_PC + if (i_pane == NULL) { + return; + } + if (i_max <= 0) { + return; + } + const f32 alphaRate = i_pane->getAlphaRate(); + f32 alphaTimer = i_pane->getAlphaTimer(); + dusk::vdt::advance_toward_frame(alphaTimer, i_max, 1.0f); + i_pane->alphaAnimeStart(alphaTimer); + i_pane->setAlphaRate(alphaRate * dusk::vdt::clamped_fraction(alphaTimer, i_max)); +#else f32 alpha_rate = i_pane->getAlphaRate(); s16 alpha_timer = i_pane->getAlphaTimer(); @@ -3668,6 +3868,7 @@ void dMeter2Draw_c::setAlphaAnimeMax(CPaneMgrAlpha* i_pane, s16 i_max) { i_pane->alphaAnimeStart(alpha_timer); i_pane->setAlphaRate(alpha_rate * ((f32)alpha_timer / (f32)i_max)); } +#endif } void dMeter2Draw_c::setItemNum(u8 i_button, u8 i_num, u8 i_max) { @@ -3765,7 +3966,7 @@ void dMeter2Draw_c::drawKanteraMeter(u8 i_button, f32 i_alphaRate) { } u8 dMeter2Draw_c::isButtonVisible() { - return (mpButtonParent->getAlphaRate() == 0.0f) ^ 1; + return (DUSK_IF_ELSE(fade_row_factor(s_fade_rows[kFadeRowButtonParent]), mpButtonParent->getAlphaRate()) == 0.0f) ^ 1; } void dMeter2Draw_c::setItemParamX(u8 i_itemNo) { @@ -4191,7 +4392,7 @@ bool dMeter2Draw_c::isBButtonShow(bool param_0) { } s16 dMeter2Draw_c::getButtonTimer() { - return mpButtonParent->getAlphaTimer(); + return DUSK_IF_ELSE((s16)s_fade_rows[kFadeRowButtonParent].timer, mpButtonParent->getAlphaTimer()); } // unused diff --git a/src/d/d_meter2_draw_vdt.inc b/src/d/d_meter2_draw_vdt.inc new file mode 100644 index 0000000000..2e8bb1e8d1 --- /dev/null +++ b/src/d/d_meter2_draw_vdt.inc @@ -0,0 +1,581 @@ +#include "dusk/game_clock.h" +#include "dusk/interp/frame_interpolation.h" +#include "dusk/interp/user_interface.h" + +#include "d/d_com_inf_game.h" +#include "d/d_meter2_info.h" +#include "d/d_meter_HIO.h" +#include "d/d_meter_map.h" +#include "d/d_pane_class.h" + +namespace { + +struct FadeRow { + bool used; + bool show; + bool follow; + bool got_free; + bool got_follow; + f32 timer; + f32 duration; + f32 full; +}; + +struct FollowA { + bool live; + bool dim; + bool dim_txt; +}; + +struct FollowB { + bool live; + bool dim; + bool dim_txt; + bool light_on; + bool item_visible; +}; + +struct FollowXY { + bool live; + bool dim; + bool fishing; + bool item_visible; +}; + +struct MidonaChase { + bool used; + bool live; + bool hold; + f32 z_target; + f32 xy_target; +}; + +struct GaugeFill { + bool used; + s32 prev; + s32 now; + s32 max; +}; + +enum FadeRowId { + kFadeRowLife, + kFadeRowKantera, + kFadeRowOxygen, + kFadeRowLightDrop, + kFadeRowRupee, + kFadeRowKey, + kFadeRowButtonParent, + kFadeRowButtonA, + kFadeRowButtonB, + kFadeRowCross, + kFadeRowCrossItem, + kFadeRowCrossMap, + kFadeRowMap, + kFadeRowCount +}; + +FadeRow s_fade_rows[kFadeRowCount]; +FollowA s_follow_a; +FollowB s_follow_b; +FollowXY s_follow_xy[2]; +MidonaChase s_midona; +GaugeFill s_kantera_fill; +GaugeFill s_oxygen_fill; +bool s_light_drop_get; + +f32 fade_row_factor(FadeRow const& r) { + if (r.duration <= 0.0f) { + return r.show ? 1.0f : 0.0f; + } + return dusk::vdt::clamped_fraction(r.timer, r.duration); +} + +void publish_fade_row(FadeRow& c, bool show, f32 duration, f32 full) { + c.used = true; + c.duration = duration > 0.0f ? duration : 1.0f; + if (c.timer > c.duration) { + c.timer = c.duration; + } + c.full = full; + c.show = show; + c.got_free = true; +} + +void publish_follow_row(FadeRow& c) { + c.used = true; + c.got_follow = true; +} + +f32 button_parent_rate(dMeter2Draw_c* d) { + if (s_fade_rows[kFadeRowButtonParent].used) { + return s_fade_rows[kFadeRowButtonParent].full * fade_row_factor(s_fade_rows[kFadeRowButtonParent]); + } + if (d->mpButtonParent != NULL) { + return d->mpButtonParent->getAlphaRate(); + } + return 0.0f; +} + +void reset_follow_intent(FadeRow& r) { + r.got_free = false; + r.got_follow = false; +} + +void resolve_follow_row(FadeRow& r, FadeRow const& parent) { + if (r.got_follow) { + r.follow = true; + } else if (r.follow && r.got_free) { + r.follow = false; + r.timer = fade_row_factor(parent) * r.duration; + } +} + +void step_fade_row(FadeRow& c) { + if (c.used && !c.follow && (c.show ? c.timer < c.duration : c.timer > 0.0f)) { + dusk::vdt::advance_toward_frame(c.timer, c.show ? c.duration : 0.0f, 1.0f); + } +} + +f32 const kFadeDuration = 5.0f; + +f32 fade_full(dMeter2Draw_c* d, FadeRowId id) { + f32 const parent = g_drawHIO.mParentAlpha; + f32 const buttons = parent * g_drawHIO.mMainHUDButtonsAlpha; + f32 const rupee_key = parent * g_drawHIO.mRupeeKeyAlpha; + switch (id) { + case kFadeRowLife: + case kFadeRowKantera: + case kFadeRowOxygen: + case kFadeRowCross: + return parent; + case kFadeRowLightDrop: + return parent * d->field_0x6fc; + case kFadeRowRupee: + return g_drawHIO.mRupeeAlpha * rupee_key; + case kFadeRowKey: + return g_drawHIO.mKeyAlpha * rupee_key; + case kFadeRowButtonParent: + return buttons; + case kFadeRowButtonA: + return g_drawHIO.mButtonAAlpha * buttons; + case kFadeRowButtonB: + return g_drawHIO.mButtonBAlpha * buttons; + case kFadeRowCrossItem: + return parent * g_drawHIO.mButtonCrossITEMAlpha; + case kFadeRowCrossMap: + return parent * g_drawHIO.mButtonCrossMAPAlpha; + case kFadeRowMap: + return 1.0f; + default: + return 0.0f; + } +} + +void apply_parent_rate(CPaneMgrAlpha* pane, FadeRow const& row) { + if (!row.used || pane == NULL || pane->getPanePtr() == NULL) { + return; + } + const f32 rate = row.full * fade_row_factor(row); + if (pane->getAlphaRate() != rate) { + pane->setAlphaRate(rate); + } +} + +void apply_meter_rate(dMeter2Draw_c* d, int index, FadeRow const& row) { + if (!row.used) { + return; + } + d->mMeterAlphaRate[index] = row.full * fade_row_factor(row); +} + +void apply_cross_text(CPaneMgr* text, CPaneMgr** juji, FadeRow const& row) { + if (!row.used) { + return; + } + const f32 factor = fade_row_factor(row); + if (text != NULL) { + text->setAlphaRate(row.full * factor); + } + const f32 juji_rate = g_drawHIO.mParentAlpha * factor; + for (int i = 0; i < 5; i++) { + if (juji[i] != NULL) { + juji[i]->setAlphaRate(juji_rate); + } + } +} + +void apply_light_drop(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowLightDrop].used || d->mpLightDropParent == NULL) { + return; + } + + const f32 parent_rate = s_fade_rows[kFadeRowLightDrop].full * + fade_row_factor(s_fade_rows[kFadeRowLightDrop]); + if (d->mpLightDropParent->getAlphaRate() != parent_rate) { + d->mpLightDropParent->setAlphaRate(parent_rate); + } + + f32 si_parent_rate[2]; + for (int i = 0; i < 2; i++) { + si_parent_rate[i] = g_drawHIO.mLightDrop.mVesselAlpha[i + 2] * parent_rate; + if (d->mpSIParent[i] != NULL) { + d->mpSIParent[i]->setAlphaRate(si_parent_rate[i]); + } + } + + if (s_light_drop_get && s_fade_rows[kFadeRowLightDrop].show) { + if (g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum > 1) { + const f32 duration = g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum; + const f32 remainder = duration - s_fade_rows[kFadeRowLightDrop].timer; + const f32 scale = g_drawHIO.mLightDrop.mDropGetScale - 1.0f; + d->field_0x6f8 = 1.0f + (scale * remainder) / (duration - 1.0f); + } else { + d->field_0x6f8 = 1.0f; + } + } + + const int drop_num = dComIfGs_getLightDropNum(dComIfGp_getStartStageDarkArea()); + const f32 si1 = si_parent_rate[1]; + for (int i = 0; i < 16; i++) { + const f32 rate = i < drop_num ? si1 : si1 * 0.3f; + if (d->mpSIParts[i][1] != NULL) { + d->mpSIParts[i][1]->setAlphaRate(rate); + } + if (d->mpSIParts[i][2] != NULL) { + d->mpSIParts[i][2]->setAlphaRate(rate); + } + } +} + +void apply_rupee(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowRupee].used) { + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowRupee]); + const f32 parent_rate[3] = { + s_fade_rows[kFadeRowRupee].full * factor, + g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha) * factor, + g_drawHIO.mRupeeFrameAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mRupeeKeyAlpha) * factor, + }; + + for (int i = 0; i < 3; i++) { + if (d->mpRupeeParent[i] != NULL && d->mpRupeeParent[i]->getAlphaRate() != parent_rate[i]) { + d->mpRupeeParent[i]->setAlphaRate(parent_rate[i]); + } + } +} + +void apply_button_parent(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonParent].used || d->mpButtonParent == NULL) { + return; + } + + apply_parent_rate(d->mpButtonParent, s_fade_rows[kFadeRowButtonParent]); + const f32 parent_rate = s_fade_rows[kFadeRowButtonParent].full * + fade_row_factor(s_fade_rows[kFadeRowButtonParent]); + if (d->mpUzu != NULL) { + d->mpUzu->setAlphaRate(d->mButtonBaseAlpha * parent_rate); + } +} + +void apply_button_a(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonA].used || d->mpButtonA == NULL) { + return; + } + + if (s_fade_rows[kFadeRowButtonA].follow) { + d->setButtonIconAAlpha(0, 0, false); + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowButtonA]); + d->mpButtonA->setAlphaRate(s_fade_rows[kFadeRowButtonA].full * factor); + const f32 text_rate = (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * factor; + for (int i = 0; i < 5; i++) { + if (d->mpAText[i] != NULL) { + d->mpAText[i]->setAlphaRate(text_rate); + } + } +} + +void apply_button_b(dMeter2Draw_c* d) { + if (!s_fade_rows[kFadeRowButtonB].used || d->mpButtonB == NULL) { + return; + } + + if (s_fade_rows[kFadeRowButtonB].follow) { + d->setButtonIconBAlpha(0, 0, false); + return; + } + + const f32 factor = fade_row_factor(s_fade_rows[kFadeRowButtonB]); + const f32 button_rate = s_fade_rows[kFadeRowButtonB].full * factor; + const f32 text_rate = (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha) * factor; + d->mpButtonB->setAlphaRate(button_rate); + if (d->mpLightB != NULL) { + d->mpLightB->setAlphaRate(button_rate); + } + if (d->mpItemB != NULL) { + d->mpItemB->setAlphaRate(text_rate); + } + for (int i = 0; i < 5; i++) { + if (d->mpBText[i] != NULL) { + d->mpBText[i]->setAlphaRate(text_rate); + } + } +} + +void advance_fade_rows() { + for (int i = 0; i < kFadeRowCount; i++) { + step_fade_row(s_fade_rows[i]); + } +} + +void write_fade_row(dMeter2Draw_c* d, FadeRowId id, bool show) { + if (id >= kFadeRowCount) { + return; + } + publish_fade_row(s_fade_rows[id], show, kFadeDuration, fade_full(d, id)); + if (id == kFadeRowButtonParent && show) { + if (fade_row_factor(s_fade_rows[kFadeRowButtonParent]) >= 1.0f) { + if (dMeter2Info_getLightDropGetFlag(dComIfGp_getStartStageDarkArea()) == 1) { + dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 2); + } + } + } +} + +void follow_button_a(bool live, bool dim, bool dim_txt) { + s_follow_a = {live, dim, dim_txt}; + if (live) { + publish_follow_row(s_fade_rows[kFadeRowButtonA]); + } +} + +void follow_button_b(bool live, bool dim, bool dim_txt, bool light_on, bool item_visible) { + s_follow_b = {live, dim, dim_txt, light_on, item_visible}; + if (live) { + publish_follow_row(s_fade_rows[kFadeRowButtonB]); + } +} + +void follow_button_xy(int i_no, bool live, bool dim, bool fishing, bool item_visible) { + if (i_no < 0 || i_no > 1) { + return; + } + s_follow_xy[i_no] = {live, dim, fishing, item_visible}; +} + +void chase_midona(bool live, bool hold, f32 z_target, f32 xy_target) { + s_midona = {true, live, hold, z_target, xy_target}; +} + +void seed_gauge(GaugeFill& g, s32 now) { + g.prev = now; +} + +void publish_gauge(GaugeFill& g, s32 now, s32 max) { + g.used = true; + g.now = now; + g.max = max; +} + +f32 gauge_draw(GaugeFill const& g) { + const f32 step = dusk::interp::get_interpolation_step(); + return (f32)g.prev + ((f32)g.now - (f32)g.prev) * step; +} +} // namespace + +f32 dMeter2Draw_c::getMeterGaugeAlphaRate(u8 i_no) { + if (i_no == 1) { + return fade_row_factor(s_fade_rows[kFadeRowKantera]) * g_drawHIO.mParentAlpha; + } + if (i_no == 2) { + return fade_row_factor(s_fade_rows[kFadeRowOxygen]) * g_drawHIO.mParentAlpha; + } + return mMeterAlphaRate[i_no]; +} + +void dMeter2Draw_c::beginExecution() { + mExecuting = true; + reset_follow_intent(s_fade_rows[kFadeRowButtonA]); + reset_follow_intent(s_fade_rows[kFadeRowButtonB]); + seed_gauge(s_kantera_fill, dComIfGp_getItemNowOil()); + seed_gauge(s_oxygen_fill, dComIfGp_getNowOxygen()); +} + +void dMeter2Draw_c::endExecution() { + resolve_follow_row(s_fade_rows[kFadeRowButtonA], s_fade_rows[kFadeRowButtonParent]); + resolve_follow_row(s_fade_rows[kFadeRowButtonB], s_fade_rows[kFadeRowButtonParent]); + mExecuting = false; +} + +void dMeter2Draw_c::publishKantera(s32 now, s32 max) { + publish_gauge(s_kantera_fill, now, max); +} + +void dMeter2Draw_c::publishOxygen(s32 now, s32 max) { + publish_gauge(s_oxygen_fill, now, max); +} + +void dMeter2Draw_c::present() { + apply_parent_rate(this->mpLifeParent, s_fade_rows[kFadeRowLife]); + if (s_fade_rows[kFadeRowLife].used) { + this->setAlphaLifeChange(false); + } + + apply_meter_rate(this, 1, s_fade_rows[kFadeRowKantera]); + if (s_fade_rows[kFadeRowKantera].used) { + this->setAlphaKanteraChange(true); + } + if (s_kantera_fill.used) { + drawKantera(s_kantera_fill.max, gauge_draw(s_kantera_fill), + g_drawHIO.mLanternMeterPosX, g_drawHIO.mLanternMeterPosY); + } + + apply_meter_rate(this, 2, s_fade_rows[kFadeRowOxygen]); + if (s_fade_rows[kFadeRowOxygen].used) { + this->setAlphaOxygenChange(false); + } + if (s_oxygen_fill.used) { + drawOxygen(s_oxygen_fill.max, gauge_draw(s_oxygen_fill), + g_drawHIO.mOxygenMeterPosX, g_drawHIO.mOxygenMeterPosY); + } + + apply_light_drop(this); + if (s_fade_rows[kFadeRowLightDrop].used) { + this->setAlphaLightDropChange(false); + } + + apply_rupee(this); + if (s_fade_rows[kFadeRowRupee].used) { + this->setAlphaRupeeChange(false); + } + + apply_parent_rate(this->mpKeyParent, s_fade_rows[kFadeRowKey]); + if (s_fade_rows[kFadeRowKey].used) { + this->setAlphaKeyChange(false); + } + + apply_button_parent(this); + apply_button_a(this); + apply_button_b(this); + setButtonIconAlpha(0, 0, 0, false); + setButtonIconAlpha(1, 0, 0, false); + if (s_fade_rows[kFadeRowButtonParent].used) { + this->setAlphaButtonChange(false); + } + + apply_parent_rate(this->mpButtonCrossParent, s_fade_rows[kFadeRowCross]); + apply_cross_text(this->mpTextI, this->mpJujiI, s_fade_rows[kFadeRowCrossItem]); + apply_cross_text(this->mpTextM, this->mpJujiM, s_fade_rows[kFadeRowCrossMap]); + + if (!s_midona.used || this->mpButtonMidona == NULL) { + return; + } + + const f32 parent_rate = button_parent_rate(this); + if (s_midona.live) { + if (s_midona.hold) { + this->mButtonZAlpha = s_midona.z_target; + } else { + dusk::vdt::present_addCalc2(&this->mButtonZAlpha, s_midona.z_target, 0.4f, 0.5f, 0.1f); + } + const f32 full = g_drawHIO.mMidnaIconAlpha * (g_drawHIO.mParentAlpha * g_drawHIO.mMainHUDButtonsAlpha); + this->mpButtonMidona->setAlpha((u8)(full * (255.0f * this->mButtonZAlpha * parent_rate))); + } else { + this->mButtonZAlpha = 0.0f; + } + dusk::vdt::present_addCalc2(&this->field_0x724, s_midona.xy_target, 0.4f, 0.5f, 0.1f); + this->mpButtonXY[2]->setAlpha((u8)(255.0f * this->field_0x724 * parent_rate)); +} + +void dMeter2Draw_c::presentVessel(f32 i_posX, f32 i_posY, f32 i_scale) { + mLightDropVesselScale = i_scale; + const f32 lightDropScale = mLightDropVesselScale * field_0x6f8 * dGetUserHudScale(); + mpLightDropParent->scale(lightDropScale, lightDropScale); + dAnchorHudScale(mpLightDropParent, HudCorner::TopRight, &i_posX, &i_posY); + mpLightDropParent->paneTrans(i_posX, i_posY); +} + +void dMeter2Draw_c::presentButtonA(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale) { + mpButtonA->scale(s_button_a_scale * i_scale, s_button_a_scale * i_scale); + mpButtonA->paneTrans(i_posX, i_posY); + mpTextA->scale(s_button_a_text_scale * i_scale, s_button_a_text_scale * i_scale); + mpTextA->paneTrans(g_drawHIO.mButtonATextPosX + i_textPosX, + g_drawHIO.mButtonATextPosY + i_textPosY + (dusk::version::isLessThanWiiJpn() ? 6.0f : 0.0f)); +} + +void dMeter2Draw_c::presentButtonB(f32 i_posX, f32 i_posY, f32 i_textPosX, f32 i_textPosY, f32 i_scale) { + const int kind = s_button_b_kind; + field_0x730 = s_button_b_scale * i_scale; + mpButtonB->scale(field_0x730 * field_0x734, field_0x730 * field_0x734); + mpButtonB->paneTrans(g_drawHIO.mButtonBPosX + i_posX, g_drawHIO.mButtonBPosY + i_posY); + + field_0x728 = g_drawHIO.mButtonBItemScale[kind] * i_scale; + mpItemB->scale(field_0x728 * field_0x734, field_0x728 * field_0x734); + mpItemB->paneTrans(field_0x6dc + (g_drawHIO.mButtonBItemPosX[kind] + i_posX), field_0x6e0 + (g_drawHIO.mButtonBItemPosY[kind] + i_posY)); + + field_0x72c = g_drawHIO.mItemBBaseScale[0] * i_scale; + mpLightB->scale(field_0x72c * field_0x734, field_0x72c * field_0x734); + mpLightB->paneTrans(g_drawHIO.mItemBBasePosX[0] + i_posX, g_drawHIO.mItemBBasePosY[0] + i_posY); + + mpTextB->scale(s_button_b_text_scale * i_scale, s_button_b_text_scale * i_scale); + mpTextB->paneTrans(g_drawHIO.mButtonBFontPosX + i_textPosX, + g_drawHIO.mButtonBFontPosY + i_textPosY + (dusk::version::isLessThanWiiJpn() ? 4.0f : 0.0f)); +} + +void dMeter2Draw_c::presentMapAlpha(dMeterMap_c* map) { + if (map == NULL) { + return; + } + if (!s_fade_rows[kFadeRowMap].used) { + map->setMapAlpha(0); + return; + } + map->setMapAlpha((u8)(fade_row_factor(s_fade_rows[kFadeRowMap]) * 255.0f)); +} + +void dMeter2Draw_c::showLightDrop(bool show, bool get_path) { + if (show && get_path) { + if (s_fade_rows[kFadeRowLightDrop].timer <= 0.0f) { + Z2GetAudioMgr()->seStart(Z2SE_SY_LIGHT_POT_EQUIP, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + } + if (fade_row_factor(s_fade_rows[kFadeRowLightDrop]) >= 1.0f) { + dMeter2Info_setLightDropGetFlag(dComIfGp_getStartStageDarkArea(), 0xFF); + } + } + const f32 duration = get_path ? (f32)g_drawHIO.mLightDrop.mDropGetScaleAnimFrameNum : kFadeDuration; + publish_fade_row(s_fade_rows[kFadeRowLightDrop], show, duration, fade_full(this, kFadeRowLightDrop)); + s_light_drop_get = get_path; +} + +void dMeter2Draw_c::showCross(bool show) { + write_fade_row(this, kFadeRowCross, show); + write_fade_row(this, kFadeRowCrossItem, show && dMeter2Info_isItemOpenCheck()); + write_fade_row(this, kFadeRowCrossMap, show && dMeter2Info_isMapOpenCheck()); +} + +void dMeter2Draw_c::showMap(bool show) { + write_fade_row(this, kFadeRowMap, show); +} + +bool dMeter2Draw_c::lightDropRowUsed() const { + return s_fade_rows[kFadeRowLightDrop].used; +} + +void dMeter2Draw_c::resetRows() { + mExecuting = false; + for (int i = 0; i < kFadeRowCount; i++) { + s_fade_rows[i] = FadeRow{}; + } + s_follow_a = FollowA{}; + s_follow_b = FollowB{}; + s_follow_xy[0] = FollowXY{}; + s_follow_xy[1] = FollowXY{}; + s_midona = MidonaChase{}; + s_kantera_fill = GaugeFill{}; + s_oxygen_fill = GaugeFill{}; + s_light_drop_get = false; +} diff --git a/src/d/d_meter2_vdt.inc b/src/d/d_meter2_vdt.inc new file mode 100644 index 0000000000..17f6452335 --- /dev/null +++ b/src/d/d_meter2_vdt.inc @@ -0,0 +1,40 @@ +#include "dusk/interp/user_interface.h" + +#include "d/d_meter_map.h" + +void dMeter2_c::presentAnims() { + if (mpMeterDraw != NULL) { + mpMeterDraw->present(); + + if (!mPresentationTargets.ready) { + return; + } + + if (mpMeterDraw->lightDropRowUsed()) { + dusk::vdt::present_addCalc2(&mVesselPosX, mPresentationTargets.vesselX, 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselPosY, mPresentationTargets.vesselY, 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselScale, mPresentationTargets.vesselScale, 0.2f, 1.0f, 0.1f); + dusk::vdt::present_addCalc2(&mVesselAlpha, mPresentationTargets.vesselAlpha, 0.2f, 1.0f, 0.1f); + mpMeterDraw->field_0x6fc = mVesselAlpha; + mpMeterDraw->presentVessel(mVesselPosX, mVesselPosY, mVesselScale); + } + + for (int i = 0; i < 2; i++) { + dusk::vdt::present_addCalc2(&mButtonATalkPosX[i], mPresentationTargets.buttonAX[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&mButtonATalkPosY[i], mPresentationTargets.buttonAY[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&field_0x148[i], mPresentationTargets.buttonBX[i], 1.0f, 10.0f, 0.1f); + dusk::vdt::present_addCalc2(&field_0x150[i], mPresentationTargets.buttonBY[i], 1.0f, 10.0f, 0.1f); + } + dusk::vdt::present_addCalc2(&field_0x15c, mPresentationTargets.crossY, 0.5f, 50.0f, 0.5f); + mpMeterDraw->presentButtonA(mButtonATalkPosX[0], mButtonATalkPosY[0], mButtonATalkPosX[1], mButtonATalkPosY[1], 1.0f); + mpMeterDraw->presentButtonB(field_0x148[0], field_0x150[0], field_0x148[1], field_0x150[1], 1.0f); + mpMeterDraw->drawButtonCross(mPresentationTargets.crossX, field_0x15c); + } +} + +void dMeter2_c::presentMap() { + if (mpMeterDraw == NULL || mpMap == NULL) { + return; + } + mpMeterDraw->presentMapAlpha(mpMap); +} diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 110d35b3cb..e5fef0783e 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -20,9 +20,12 @@ #if TARGET_PC #include "dusk/action_bindings.h" +#include "dusk/game_clock.h" #include "dusk/interp/user_interface.h" #include "dusk/settings.h" +#include "d/d_meter2.h" + #include "JSystem/JUtility/JUTPalette.h" #include @@ -633,6 +636,13 @@ void dMeterMap_c::_draw() { } void dMeterMap_c::draw() { +#if TARGET_PC + if (dusk::game_clock::is_presentation_frame()) { + if (dMeter2_c* meter = dMeter2Info_getMeterClass()) { + meter->presentMap(); + } + } +#endif if ( #if DEBUG !g_meter_mapHIO.mMapDisplayProhibited && From bd7e5cb77d540c3106708139dabff965579f2f47 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 02:00:21 +0200 Subject: [PATCH 031/100] JAudio documentation updates --- docs/jaudio.md | 159 +++++++++++++++++- .../include/JSystem/JAudio2/JAUSoundTable.h | 2 +- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/docs/jaudio.md b/docs/jaudio.md index a42df217b2..70c726a548 100644 --- a/docs/jaudio.md +++ b/docs/jaudio.md @@ -30,7 +30,7 @@ Contains the BMS instructions for all BMS-based music and sound effects. Not all ### `/Audiores/Stream/*.ast` -Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. +Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. ### `/Audiores/Waves/*.aw` @@ -79,12 +79,163 @@ Following is data descriptions for the remaining data in the `.BAA`, as pointed Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk. -For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) +*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) -*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* +### `BST ` / Sound Table + +The `BST` / Sound table defines various parameters for music and sound effect playback. + +The layout is pretty simple: it's hierarchical with sections (sound effects, music sequences, streamed music)[^sectionids], +which have groups (only sound effects use this) and each group just has a flat list of items. +These indices match directly to the fields of the `JAISoundID`. + +*Relevant classes: `JAUSoundTable`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/bst.hexpat) + +Each item has a Type ID and a set of data. Types used by TP are as follows: + +#### `0x51` / sound effect + +Defines that this is a sound effect. The actual BMS code to execute (and unlike the other types, including its location) is looked up in the BSC. + +Layout: + +``` +u8 mPriority; // Priority relative to other sound effects. +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +padding[2]; +u32 mSwBit; // See below. +float mPitch; // Pitch multiplier +``` + +```cpp +// Values for mSwBit on sound effect items. (taken from JAUSoundTable.h) + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 +``` + +#### `0x60` / music sequence + +Defines background music that plays through the BMS system. + +``` +u8 mPriority; +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +u16 mResourceId; // ID to look up in Z2SoundSeqs.arc +``` + +#### `0x70` / `0x71` / music sequence + +Defines a streamed music track. Difference between `0x70` and `0x71` seems to only be that `0x71` stops automatically +on scene changes in TP's game code. + +``` +u8 mPriority; +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (00), left (01), or right (10). +char* mStreamFilePath[] : u32; // File path to the .ast on disc. +``` ## Further reading & credits * https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP) * XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/) -* The decompiled source code, duh. \ No newline at end of file +* The decompiled source code, duh. diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 4a4ca33214..3b38ff2eb6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -28,7 +28,7 @@ #define SOUND_TYPEID_STREAM 0x70 /** - * Sound is a streamed music file. Unsure of difference from SOUND_TYPEID_STREAM + * Sound is a streamed music file. Stopped automatically on scene change by Z2SceneMgr.cpp */ #define SOUND_TYPEID_STREAM_ALT 0x71 From cd1d12eec6ad42e8968375b3a07743ad1d0564f8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 20:50:59 +0200 Subject: [PATCH 032/100] Audio res documentation, API cleanup, bug fixes --- docs/images/jaudio.mermaid | 58 +++++ docs/images/jaudio_se_flow.svg | 1 + docs/images/jaudio_seq_flow.svg | 1 + docs/images/jaudio_stream_flow.svg | 1 + docs/jaudio.md | 4 +- mods/audio_mod/src/mod.cpp | 4 +- sdk/include/mods/svc/audio_res.h | 302 ++++++++++++++++++++-- src/dusk/mods/svc/audio_res/audio_res.cpp | 3 + src/dusk/mods/svc/audio_res/bst.cpp | 38 ++- src/dusk/mods/svc/audio_res/bst.hpp | 8 +- src/dusk/mods/svc/audio_res/opus.cpp | 2 +- src/dusk/mods/svc/audio_res/wave.cpp | 2 +- src/dusk/mods/svc/audio_res/wsys.cpp | 21 +- src/dusk/mods/svc/audio_res/wsys.hpp | 2 + 14 files changed, 410 insertions(+), 37 deletions(-) create mode 100644 docs/images/jaudio.mermaid create mode 100644 docs/images/jaudio_se_flow.svg create mode 100644 docs/images/jaudio_seq_flow.svg create mode 100644 docs/images/jaudio_stream_flow.svg diff --git a/docs/images/jaudio.mermaid b/docs/images/jaudio.mermaid new file mode 100644 index 0000000000..e5e3ee221e --- /dev/null +++ b/docs/images/jaudio.mermaid @@ -0,0 +1,58 @@ +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + subgraph BSC + bms_start[Look up BMS start] + end + Start-->|Sound ID|BSC + + BSC-->|BMS data| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|File path| ast[Stream /AudioRes/Stream/xxx.ast from disc] + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|Resource ID| arc[Z2SoundSeqs.arc] + arc-->|BMS file| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS diff --git a/docs/images/jaudio_se_flow.svg b/docs/images/jaudio_se_flow.svg new file mode 100644 index 0000000000..03d4bbb29b --- /dev/null +++ b/docs/images/jaudio_se_flow.svg @@ -0,0 +1 @@ +

Sound ID

Sound ID

BMS data

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BSC

Look up BMS start

BST

Look up sound parameters

Start Sound Effect

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_seq_flow.svg b/docs/images/jaudio_seq_flow.svg new file mode 100644 index 0000000000..f39ee7ca5b --- /dev/null +++ b/docs/images/jaudio_seq_flow.svg @@ -0,0 +1 @@ +

BMS file

Sound ID

Resource ID

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BST

Look up sound parameters

Start Sound Effect

Z2SoundSeqs.arc

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_stream_flow.svg b/docs/images/jaudio_stream_flow.svg new file mode 100644 index 0000000000..47bee926eb --- /dev/null +++ b/docs/images/jaudio_stream_flow.svg @@ -0,0 +1 @@ +

Sound ID

File path

BST

Look up sound parameters

Start Sound Effect

Stream /AudioRes/Stream/xxx.ast from disc

\ No newline at end of file diff --git a/docs/jaudio.md b/docs/jaudio.md index 70c726a548..84a0d9dcfa 100644 --- a/docs/jaudio.md +++ b/docs/jaudio.md @@ -26,7 +26,7 @@ All audio data is stored in `/Audiores` on the disc. Files are as follows: ### `/Audiores/Seqs/Z2SoundSeqs.arc` -Contains the BMS instructions for all BMS-based music and sound effects. Not all data is kept in memory at once. +Contains the BMS instructions for all BMS-based music. Not all data is kept in memory at once. ### `/Audiores/Stream/*.ast` @@ -230,7 +230,7 @@ on scene changes in TP's game code. ``` u8 mPriority; u8 mVolume; // Converted to float: mVolume * (1.0/127.0) -u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (00), left (01), or right (10). +u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (01), left (10), or right (11). char* mStreamFilePath[] : u32; // File path to the .ast on disc. ``` diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp index aa3da367ec..6319310eea 100644 --- a/mods/audio_mod/src/mod.cpp +++ b/mods/audio_mod/src/mod.cpp @@ -13,14 +13,14 @@ MOD_EXPORT ModResult mod_initialize(ModError*) { AudioWaveHandle handle; svc_audio_res->replace_wave( mod_ctx, - SoundEffects, + AUDIO_WAVE_BANK_SOUND_EFFECTS, 4238, "res/go.opus", nullptr, &handle); svc_audio_res->replace_wave( mod_ctx, - SoundEffects, + AUDIO_WAVE_BANK_SOUND_EFFECTS, 4237, "res/go.opus", nullptr, diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index a5a0b7b71e..ae57e3a75c 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -6,43 +6,117 @@ #define AUDIO_RES_SERVICE_MAJOR 1u #define AUDIO_RES_SERVICE_MINOR 0u +/* + * Defines APIs for replacing and adding audio resources. + * + * TP's audio engine (JAudioV2) is extremely complex. For more of an overview through its + * functionality, please see docs/jaudio.md in the repo. + */ + /* * WSYS structs */ -#define AUDIO_RES_DEFAULT_KEY 0x3C; - -typedef enum AudioWaveBank : uint8_t { - SoundEffects = 0, - MusicSamples = 1, -} AudioWaveBank; - -typedef enum AudioWaveFormat : uint8_t { - Adpcm4 = 0, - Adpcm2 = 1, - Pcm8 = 2, - Pcm16 = 3, -} AudioWaveFormat; - -typedef uint64_t AudioWaveHandle; -typedef uint64_t AudioSoundTableHandle; +/** + * "Default" key for wave definitions. Key like musical key, not map keys or whatever. + */ +#define AUDIO_RES_DEFAULT_KEY 0x3C /** + * Defines TP's raw wave banks, which can be modified. + */ +typedef enum AudioWaveBank : uint8_t { + AUDIO_WAVE_BANK_SOUND_EFFECTS = 0, + AUDIO_WAVE_BANK_MUSIC_SAMPLES = 1, +} AudioWaveBank; + +/** + * Defines formats for raw sample data the DSP can play back. + */ +typedef enum AudioWaveFormat : uint8_t { + /** + * 16 samples per 9 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM4 = 0, + + /** + * 16 samples per 5 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM2 = 1, + + /** + * 1-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM8 = 2, + + /** + * 2-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM16 = 3, +} AudioWaveFormat; + +/** + * Handle to reference wave replacements/additions by mods. + */ +typedef uint64_t AudioWaveHandle; + +/** + * Data needed to load a raw audio data file. * + * @see AudioWaveInfo */ typedef struct AudioRawWave { + /** + * Format of the raw sample data. + */ AudioWaveFormat format; + + /** + * Sample rate, in Hertz. + */ float sample_rate; + + /** + * Last audio sample before ADPCM loop start point. (unused if not looping) + */ int16_t sample_value_last; + + /** + * Second-to-last audio sample before ADPCM loop start point. (unused if not looping) + */ int16_t sample_value_penult; } AudioRawWave; +/** + * Metadata needed to play a wave. + */ typedef struct AudioWaveInfo { + /** + * Base key (musical key). Set to AUDIO_RES_DEFAULT_KEY if you don't care. + */ uint8_t base_key; + + /** + * If true, the sample loops. + */ bool loop; + + /** + * Sample number where loop resumes when end is reached. + * Does nothing if not looping. + */ uint32_t loop_start_sample; + + /** + * End of the wave. Stops playback if not looping. + * Automatically clamped to the sample count from the file, if greater. + */ uint32_t loop_end_sample; + /** + * If provided, specifies that the passed file is "raw" and only contains sample data. + * This must be used if you want to ship ADPCM samples, as other containers do not support that. + */ AudioRawWave const* raw_wave; } AudioWaveInfo; @@ -50,6 +124,9 @@ typedef struct AudioWaveInfo { * BST structs */ +/** + * Game defined categories (groups) for sound effects. + */ typedef enum SoundEffectCategory : uint8_t { SE_CATEGORY_SYSTEM_SE, SE_CATEGORY_PLAYER_VOICE, @@ -63,44 +140,131 @@ typedef enum SoundEffectCategory : uint8_t { SE_CATEGORY_ENV_SE } SoundEffectCategory; +/** + * Data that can be defined for a sound effect in the sound table. + */ typedef struct AudioSoundTableEffectInfo { + /** + * Priority relative to other sound effects. Can affect culling and such if + * many effects are playing. + */ uint8_t priority; + + /** + * Volume multiplier for this sound effect. Clamped to range 0-2. + */ float volume; + + /** + * Pitch multiplier for this sound effect. + */ float pitch; + /** + * If true, the effect is always treated as max priority, regardless of factors like distance. + */ bool always_max_priority; + /** + * Don't calculate volume changes by distance. + */ bool ignore_distance_volume; + + /** + * Don't FX Mix (reverb) changes by distance. + */ bool ignore_distance_fx_mix; + + /** + * Don't calculate panning (left/right balance). + */ bool ignore_pan; + + /** + * Don't calculate dolby (front/back balance). + */ bool ignore_dolby; + /** + * 0-15 value controlling volume randomization strength. + */ uint8_t random_volume; + + /** + * 0-15 value controlling pitch randomization strength. + */ uint8_t random_pitch; + /** + * 0-15 value controlling Doppler effect strength. + */ uint8_t doppler_power; + /** + * Value from 0-15 selecting "volume distance" class. This effectively selects a fixed curve for + * parameters like volume by distance. + */ uint8_t volume_dist_class; + + /** + * Limit minimum volume of sound (after distance drop-off) to 0.2. + */ bool clamp_min_volume; + + /** + * Treat this sound as "far away" or "culled" at "max distance." + * Affects things like automatic stopping. + */ bool cull_at_max_distance; } AudioSoundTableEffectInfo; +/** + * Stereo panning parameters for stream channels. + */ typedef enum StreamPan : uint8_t { STREAM_PAN_CENTER, STREAM_PAN_LEFT, STREAM_PAN_RIGHT, } StreamPan; +/** + * Maximum amount of channels a streamed track can have. + */ #define STREAM_MAX_CHILDREN 6 +/** + * Data that can be defined for a stream in the sound table. + */ typedef struct AudioSoundTableStreamInfo { + /** + * Unsure if used. + */ uint8_t priority; + + /** + * Volume multiplier for this stream. Clamped to range 0-2. + */ float volume; + + /** + * For each channel in the loaded .ast file, specifies the panning position for said channel. + */ StreamPan pan_parameters[STREAM_MAX_CHILDREN]; - char const* file_path; + + /** + * Whether this stream automatically stops on scene change. + */ bool stop_on_scene_change; } AudioSoundTableStreamInfo; +/** + * Handle to reference sound table replacements/additions by mods. + */ +typedef uint64_t AudioSoundTableHandle; + +/** + * Defines APIs for replacing and adding audio resources. + */ typedef struct AudioResService { ServiceHeader header; @@ -108,6 +272,32 @@ typedef struct AudioResService { * WSYS API */ + /** + * Default wave info if none is provided: default key, no loop, not raw audio. + */ + AudioWaveInfo const* default_wave_info; + + /** + * Replace an existing audio wave in the game. This replacement will follow mod order + * prioritization. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param wave_id ID of the wave to replace. This does *not* directly correlate to sound table + * entries or JAISound values in any way! + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This + * does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable + * defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used + * with @ref remove_wave. + */ ModResult (*replace_wave)( ModContext* ctx, AudioWaveBank bank, @@ -116,6 +306,22 @@ typedef struct AudioResService { AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + /** + * Add a new audio wave to the game. The service allocates the placed ID for you. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the addition. This can be used with @ref remove_wave. + * @param out_wave_id Receives the allocated ID in the wave bank. + */ ModResult (*add_wave)( ModContext* ctx, AudioWaveBank bank, @@ -124,12 +330,33 @@ typedef struct AudioResService { AudioWaveHandle* out_handle, uint16_t* out_wave_id); + /** + * Remove a wave addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); /* * BST API */ + /** + * Default sound effect info if none is provided: default volume, pitch, medium priority. + */ + AudioSoundTableEffectInfo const* default_effect_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param effect_id ID of the effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ ModResult (*replace_sound_table_effect)( ModContext* ctx, SoundEffectCategory category_id, @@ -137,6 +364,15 @@ typedef struct AudioResService { AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle); + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_effect_id Receives the allocated ID of the sound effect. + */ ModResult (*add_sound_table_effect)( ModContext* ctx, SoundEffectCategory category_id, @@ -144,18 +380,50 @@ typedef struct AudioResService { AudioSoundTableHandle* out_handle, uint16_t* out_effect_id); + /** + * Default sound effect info if none is provided: default volume, medium priority, stereo channels, *no* stop on scene change. + */ + AudioSoundTableStreamInfo const* default_stream_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param stream_id ID of the stream. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ ModResult (*replace_sound_table_stream)( ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle); + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_stream_id Receives the allocated ID of the stream. + */ ModResult (*add_sound_table_stream)( ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id); + /** + * Remove a sound table addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ ModResult (*remove_sound_table)(ModContext* ctx, AudioSoundTableHandle handle); } AudioResService; diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 7550b360cc..1c8f359c75 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -16,11 +16,14 @@ using namespace dusk::helpers::cast; constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .default_wave_info = &wsys::default_wave_info, .replace_wave = &wsys::insert_replace_wave, .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, + .default_effect_info = &bst::default_effect_info, .replace_sound_table_effect = &bst::replace_sound_table_effect, .add_sound_table_effect = &bst::add_sound_table_effect, + .default_stream_info = &bst::default_stream_info, .replace_sound_table_stream = &bst::replace_sound_table_stream, .add_sound_table_stream = &bst::add_sound_table_stream, .remove_sound_table = &bst::remove_sound_table, diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp index e47ad2d216..9dcf635c2a 100644 --- a/src/dusk/mods/svc/audio_res/bst.cpp +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -35,6 +35,7 @@ bool validate_category(SoundEffectCategory const category) { } uint8_t volume_to_item(float volume) { + volume = std::clamp(volume, 0.0f, 2.0f); return static_cast(volume * 127); } @@ -90,8 +91,8 @@ u8 SoundEffectReplacementSlot::get_type_id() const { } StreamReplacementSlot::StreamReplacementSlot( - bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info) - : SoundTableReplacementSlot(mod_defined, id), file_path(info.file_path) { + bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info) + : SoundTableReplacementSlot(mod_defined, id), file_path(file_path) { stop_on_scene_change = info.stop_on_scene_change; item.mPriority = info.priority; item.mVolume = volume_to_item(info.volume); @@ -216,10 +217,14 @@ static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCate } auto mod = mod_from_context(ctx); - if (mod == nullptr || info == nullptr || !validate_category(category_id)) { + if (mod == nullptr || !validate_category(category_id)) { return MOD_INVALID_ARGUMENT; } + if (info == nullptr) { + info = &default_effect_info; + } + auto slot = std::make_shared(mod_defined, effect_id, category_id, *info); sound_replacements_dirty = true; @@ -236,6 +241,8 @@ ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory catego return insert_sound_table_effect_core(ctx, category_id, effect_id, false, info, out_handle); } +AudioSoundTableEffectInfo const default_effect_info(128, 1, 1); + ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_effect_id) { @@ -263,6 +270,7 @@ static ModResult insert_sound_table_stream_core( ModContext* ctx, uint16_t stream_id, bool mod_defined, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { if (out_handle != nullptr) { @@ -270,11 +278,15 @@ static ModResult insert_sound_table_stream_core( } auto mod = mod_from_context(ctx); - if (mod == nullptr || info == nullptr) { + if (mod == nullptr || file_path == nullptr) { return MOD_INVALID_ARGUMENT; } - auto slot = std::make_shared(mod_defined, stream_id, *info); + if (info == nullptr) { + info = &default_stream_info; + } + + auto slot = std::make_shared(mod_defined, stream_id, file_path, *info); sound_replacements_dirty = true; auto const handle = sound_replacements.emplace(*mod, std::move(slot)); @@ -285,12 +297,22 @@ static ModResult insert_sound_table_stream_core( return MOD_OK; } +AudioSoundTableStreamInfo const default_stream_info( + 128, + 1, + { + STREAM_PAN_LEFT, + STREAM_PAN_RIGHT + }); + ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { - return insert_sound_table_stream_core(ctx, stream_id, false, info, out_handle); + return insert_sound_table_stream_core(ctx, stream_id, false, file_path, info, out_handle); } -ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo const* info, +ModResult add_sound_table_stream(ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id) { if (out_stream_id == nullptr) { return MOD_INVALID_ARGUMENT; @@ -298,7 +320,7 @@ ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo cons auto const stream_id = stream_id_allocator.alloc(); - auto const result = insert_sound_table_stream_core(ctx, stream_id, true, info, out_handle); + auto const result = insert_sound_table_stream_core(ctx, stream_id, true, file_path, info, out_handle); if (result != MOD_OK) { stream_id_allocator.free(stream_id); } diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp index de13534f20..67b6c1aa18 100644 --- a/src/dusk/mods/svc/audio_res/bst.hpp +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -39,7 +39,7 @@ struct StreamReplacementSlot final : SoundTableReplacementSlot { std::string file_path; bool stop_on_scene_change; - StreamReplacementSlot(bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info); + StreamReplacementSlot(bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info); ~StreamReplacementSlot() override = default; @@ -68,6 +68,8 @@ void frame_end(); void remove_mod(LoadedMod const& mod); void sync_audio_replacements(); +extern AudioSoundTableEffectInfo const default_effect_info; + ModResult replace_sound_table_effect( ModContext* ctx, SoundEffectCategory category_id, @@ -82,14 +84,18 @@ ModResult add_sound_table_effect( AudioSoundTableHandle* out_handle, uint16_t* out_effect_id); +extern AudioSoundTableStreamInfo const default_stream_info; + ModResult replace_sound_table_stream( ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle); ModResult add_sound_table_stream( ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id); diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp index aa4e6bea99..f885a6e5c6 100644 --- a/src/dusk/mods/svc/audio_res/opus.cpp +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -84,7 +84,7 @@ ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std: slot.data = std::make_shared(std::move(pcm_buffer)); slot.sample_rate = 48000; // Opus always decodes at 48 kHz. slot.sample_count = length; - slot.format = Pcm16; + slot.format = AUDIO_WAVE_FORMAT_PCM16; return MOD_OK; } diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index ae97a8f81b..5835fbcc6f 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -135,7 +135,7 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: pcmData.be_swap(); - slot.format = Pcm16; + slot.format = AUDIO_WAVE_FORMAT_PCM16; slot.sample_count = pcmData.size() / sizeof(u16); slot.data = std::make_unique(std::move(pcmData)); diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index d27c759520..7899bb117c 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -29,18 +29,18 @@ PlainIdAllocator sound_effect_id_allocator(5'000); PlainIdAllocator music_sample_id_allocator(1'000); PlainIdAllocator& id_allocator_for_bank(AudioWaveBank const bank) { - return bank == SoundEffects ? sound_effect_id_allocator : music_sample_id_allocator; + return bank == AUDIO_WAVE_BANK_SOUND_EFFECTS ? sound_effect_id_allocator : music_sample_id_allocator; } bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) { u32 samples_per_block; u32 bytes_per_block; switch (raw.format) { - case Adpcm4: + case AUDIO_WAVE_FORMAT_ADPCM4: samples_per_block = 16; bytes_per_block = 9; break; - case Pcm16: + case AUDIO_WAVE_FORMAT_PCM16: samples_per_block = 1; bytes_per_block = 2; break; @@ -71,7 +71,7 @@ ModResult load_raw( return MOD_INVALID_ARGUMENT; } - if (slot.format == Pcm16) { + if (slot.format == AUDIO_WAVE_FORMAT_PCM16) { SampleDataPcm16 pcm16; pcm16.data.resize(sample_count); memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); @@ -179,6 +179,15 @@ ModResult insert_replace_wave_core( slot.loop = wave_info->loop; slot.loop_start_sample = wave_info->loop_start_sample; slot.loop_end_sample = wave_info->loop_end_sample; + + if (slot.loop_end_sample > slot.sample_count) { + slot.loop_end_sample = slot.sample_count; + } + + if (slot.loop_start_sample >= slot.loop_end_sample) { + Log.error("[{}] wave has start >= end", mod->metadata.id); + return MOD_INVALID_ARGUMENT; + } } else { slot.base_key = AUDIO_RES_DEFAULT_KEY; slot.loop = false; @@ -202,6 +211,8 @@ ModResult insert_replace_wave_core( absl::flat_hash_map s_replacements; std::mutex s_replacements_mutex; +AudioWaveInfo const default_wave_info(AUDIO_RES_DEFAULT_KEY, false, 0, std::numeric_limits::max(), nullptr); + ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { auto* mod = mod_from_context(ctx); if (mod == nullptr || handle == 0) { @@ -239,7 +250,7 @@ ModResult insert_add_wave( *out_wave_id = 0; - if (bank != SoundEffects && bank != MusicSamples) { + if (bank != AUDIO_WAVE_BANK_SOUND_EFFECTS && bank != AUDIO_WAVE_BANK_MUSIC_SAMPLES) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index 16d144e4b3..79b66b70e1 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -108,6 +108,8 @@ void frame_end(); void remove_mod(LoadedMod& mod); void sync_audio_replacements(); +extern AudioWaveInfo const default_wave_info; + ModResult insert_replace_wave( ModContext* ctx, AudioWaveBank bank, From adb2d7c50649fc8b99e092c7120ca00202756421 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 20:51:18 +0200 Subject: [PATCH 033/100] Add .gitkeep to audio mod overlay for now --- mods/audio_mod/overlay/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mods/audio_mod/overlay/.gitkeep diff --git a/mods/audio_mod/overlay/.gitkeep b/mods/audio_mod/overlay/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From b25d729d6414d99427fddbac9d9e0ab3e8df5132 Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Tue, 16 Jun 2026 04:54:50 +0000 Subject: [PATCH 034/100] Hide sound setting in in-game options --- src/d/d_menu_option.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index 9d6f0816e2..e70382bab0 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -876,7 +876,7 @@ void dMenu_Option_c::vib_init() { void dMenu_Option_c::vib_move() { bool upTrigger = mpStick->checkUpTrigger(); - bool downTrigger = mpStick->checkDownTrigger(); + IF_NOT_DUSK(bool downTrigger =) mpStick->checkDownTrigger(); bool leftTrigger = checkLeftTrigger(); bool rightTrigger = checkRightTrigger(); @@ -891,10 +891,14 @@ void dMenu_Option_c::vib_move() { field_0x3ef = PROC_ATTEN_e; #endif Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); - } else if (downTrigger) { + } +#ifndef TARGET_PC + else if (downTrigger) { field_0x3ef = OPTION_SELECT(PROC_SOUND_e); Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); - } else if (leftTrigger) { + } +#endif + else if (leftTrigger) { if (isRumbleSupported()) { if (field_0x3ea == 0) { field_0x3ea = 1; @@ -1369,8 +1373,7 @@ void dMenu_Option_c::calibration_close2_move() { void dMenu_Option_c::menuVisible() { for (int i = 0; i < 6; i++) { - if (i < OPTION_SELECT(PROC_CHANGE_MOVE_e)) - { + if (i < OPTION_SELECT(DUSK_IF_ELSE(PROC_SOUND_e, PROC_CHANGE_MOVE_e))) { menuShow(i); } else { menuHide(i); @@ -2478,7 +2481,7 @@ bool dMenu_Option_c::isRumbleSupported() { #if TARGET_PC bool dMenu_Option_c::pointerConfirmSelect() { dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options); - for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 4 : 3); ++i) { + for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 3 : 2); ++i) { if (dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) { dusk::menu_pointer::set_hover_target(i); return false; @@ -2502,7 +2505,7 @@ bool dMenu_Option_c::pointerConfirmSelect() { bool dMenu_Option_c::dpdMenuMove() { #if TARGET_PC dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options); - for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 4 : 3); ++i) { + for (u8 i = 0; i < (dusk::version::isRegionJpn() ? 3 : 2); ++i) { if (!dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) { continue; } @@ -2582,20 +2585,6 @@ bool dMenu_Option_c::dpdMenuMove() { -1.0f, 0); } return true; - case PROC_SOUND_e: - if (field_0x3e9 == 0) { - field_0x3e9 = 2; - } else { - field_0x3e9--; - } - field_0x3da = 5; - mDoAud_setOutputMode(dMo_soundMode[field_0x3e9]); - setSoundMode(dMo_soundMode[field_0x3e9]); - field_0x3ef = OPTION_SELECT(PROC_CHANGE_MOVE_e); - field_0x3f5 = OPTION_SELECT(PROC_SOUND_e); - Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f, - -1.0f, 0); - return true; } } #endif From c03c3cb9629902cf49347e9a2db2e5ce54b76781 Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Wed, 2 Sep 2026 00:50:59 +0000 Subject: [PATCH 035/100] Refactor stereo panning --- src/dusk/audio/DuskDsp.cpp | 511 ++++++++++++++++--------------------- src/dusk/audio/DuskDsp.hpp | 9 - 2 files changed, 217 insertions(+), 303 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 4009b37e39..0122adbc43 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -101,21 +101,6 @@ static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 sampl return (samples / channel.mSamplesPerBlock) * BlockBytes(channel); } -/** - * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. - */ -static void RenderChannel( - JASDsp::TChannel& channel, - ChannelAuxData& channelAux, - OutputSubframe& subframe); - -static void RenderOutputChannel( - const JASDsp::TChannel& sourceChannel, - ChannelAuxData& aux, - OutputChannel outputChannel, - const std::span inputSamples, - OutputSubframe& fullOutputSubframe); - /** * Converts a pitch value on a DSP channel to a sample rate. */ @@ -203,16 +188,14 @@ static void GenerateEvolvingHarmonic() { } } - static void RenderOscChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - OutputSubframe& subframe) { + DspSubframe& buf) { if (channel.mResetFlag) ResetChannel(channel, channelAux); const u32 pitch = channel.mPitch; - DspSubframe buf = {}; const auto oscType = static_cast(channel.mBytesPerBlock); switch (oscType) { @@ -270,142 +253,6 @@ static void RenderOscChannel( DuskLog.error("RenderOscChannel: unimplemented oscillator type {}", channel.mBytesPerBlock); break; } - - auto samples = std::span(buf).subspan(0, DSP_SUBFRAME_SIZE); - RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, samples, subframe); - RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, samples, subframe); -} - - -void dusk::audio::DspRender(OutputSubframe& subframe) { - ZoneScoped; - if (DumpAudio != sDumpWasActive) { - sDumpWasActive = DumpAudio; - if (DumpAudio) { - OpenChannelDumpFiles(); - } else { - CloseChannelDumpFiles(); - } - } - - GenerateEvolvingHarmonic(); - - std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); - - DspSubframe reverbInputL = {}; - DspSubframe reverbInputR = {}; - bool anyReverbInput = false; - - DspSubframe surroundBus = {}; - bool anySurroundInput = false; - - for (int i = 0; i < channels.size(); i++) { - auto& channel = channels[i]; - auto& channelAux = ChannelAux[i]; - - if (!channel.mIsActive) { - continue; - } - else if (channel.mPauseFlag) { - // Not really sure what the practical difference between pause and - // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? - continue; - } - else if (channel.mForcedStop) { - channel.mIsFinished = true; - continue; - } - - OutputSubframe channelSubframe = {}; - if (channel.mWaveAramAddress == 0) { - RenderOscChannel(channel, channelAux, channelSubframe); - } else { - ValidateChannel(channel); - RenderChannel(channel, channelAux, channelSubframe); - } - - if (EnableReverb) { - // scale the input to the reverb rather than using wet/dry on the output. - // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, - // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes - // prevents transients when the next sound starts playing with a different reverb level - // 600.0f was pulled out of my ass and just sounds good enough for console - f32 inputGain = (channel.mAutoMixerFxMix >> 8) / 600.0f; - if (inputGain > 0) { - anyReverbInput = true; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - reverbInputL[j] += channelSubframe.channels[0][j] * inputGain; - reverbInputR[j] += channelSubframe.channels[1][j] * inputGain; - } - } - } - - 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]) { - f32 interleaved[DSP_SUBFRAME_SIZE * 2]; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - interleaved[j * 2 + 0] = channelSubframe.channels[0][j]; - interleaved[j * 2 + 1] = channelSubframe.channels[1][j]; - } - fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); - } - - for (int o = 0; o < subframe.channels.size(); o++) { - MixSubframe(subframe.channels[o], channelSubframe.channels[o]); - } - } - - if (EnableReverb && (anyReverbInput || ReverbHasTail)) { - // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 - constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; - f32 wetEnergy = SharedReverb.processmix( - reverbInputL.data(), reverbInputR.data(), - subframe.channels[0].data(), subframe.channels[1].data(), - DSP_SUBFRAME_SIZE, 1, 1.0f - ); - 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) { - ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); - } - PrevMasterVolume = MasterVolume; } /** @@ -539,128 +386,12 @@ static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int ne } /** - * Get the expected BusConnect value needed to define the given output channel in a DSP channel. - */ -constexpr u16 GetBusConnect(const OutputChannel channel) { - switch (channel) { - // TODO: This is a guess for now. - case OutputChannel::LEFT: - return 0x0D00; - case OutputChannel::RIGHT: - return 0x0D60; - default: - CRASH("Invalid output channel!"); - } -} - -/** - * For a DSP channel the JASDsp::OutputChannelConfig value targeting the given output channel. - * Returns null if the DSP channel does not output to this output channel. - */ -static const JASDsp::OutputChannelConfig* GetOutputConfig( - const JASDsp::TChannel& sourceChannel, - OutputChannel channel) { - - auto busConnect = GetBusConnect(channel); - for (const auto& mOutputChannel : sourceChannel.mOutputChannels) { - auto config = &mOutputChannel; - if (config->mBusConnect == busConnect) { - return config; - } - } - - return nullptr; -} - -struct VolumeValue { - f32 Target; - f32 Init; -}; - -/** - * Get the volume that the given DSP channel should render to the given output channel at. - */ -static VolumeValue GetVolumeForOutputChannel( - const JASDsp::TChannel& sourceChannel, - OutputChannel outputChannel) { - - u16 volume; - u16 initVolume; - f32 panValue = 1; - if (sourceChannel.mAutoMixerBeenSet) { - volume = sourceChannel.mAutoMixerVolume; - initVolume = sourceChannel.mAutoMixerInitVolume; - - auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; - - switch (outputChannel) { - case OutputChannel::LEFT: - panValue = 1 - autoMixerPan; - break; - case OutputChannel::RIGHT: - panValue = autoMixerPan; - break; - default: - CRASH("Unhandled output channel: OutputChannel"); - } - - } else { - auto config = GetOutputConfig(sourceChannel, outputChannel); - if (config == nullptr) { - return {0, 0}; - } - - volume = config->mTargetVolume; - initVolume = config->mCurrentVolume; - } - - // TODO: interpolate to avoid popping. - f32 targetRatio = VolumeFromU16(volume); - targetRatio *= panValue; - - f32 initRatio = VolumeFromU16(initVolume); - initRatio *= panValue; - - return {targetRatio, initRatio}; -} - -/** - * Given decoded & resampled input samples, render a DSP channel to a given output channel. - */ -static void RenderOutputChannel( - const JASDsp::TChannel& sourceChannel, - ChannelAuxData& aux, - OutputChannel outputChannel, - const std::span inputSamples, - OutputSubframe& fullOutputSubframe) { - - auto& outputSubframe = fullOutputSubframe[outputChannel]; - assert(inputSamples.size() <= outputSubframe.size()); - - auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); - - f32 targetVolume = volume.Target; - auto& prevVolume = aux.PrevVolume(outputChannel); - if (std::isnan(prevVolume)) { - // Initialize previous volume to new volume on first render. - prevVolume = volume.Init; - } - - if (prevVolume == 0 && targetVolume == 0) { - return; - } - - ApplyVolume(outputSubframe, inputSamples, prevVolume, targetVolume); - prevVolume = targetVolume; -} - -/** - * Fetch, decode, resample, output + * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. */ static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - OutputSubframe& subframe) { + DspSubframe& buf) { if (channel.mResetFlag) { ResetChannel(channel, channelAux); @@ -679,7 +410,6 @@ static void RenderChannel( channel.mIsFinished = true; } - DspSubframe audioLoadBuffer = {}; f32 pos = channelAux.resamplePos; s16 prev = channelAux.resamplePrev; s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev; @@ -687,7 +417,7 @@ static void RenderChannel( // linear resampling and f32 conversion for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { - audioLoadBuffer[i] = (prev + pos * (next - prev)) / 32768.0f; + buf[i] = (prev + pos * (next - prev)) / 32768.0f; pos += step; while (pos >= 1.0f) { pos -= 1.0f; @@ -705,7 +435,7 @@ static void RenderChannel( // IIR part 1, low-pass: out[n] = (in[n] - in[n-1]) * (coeff/128) + out[n-1] if (s16 coeff = channel.iir_filter_params[4]; coeff != 0) { - for (f32& sample : audioLoadBuffer) { + for (f32& sample : buf) { f32 out = std::clamp( (sample - channelAux.prev_lp_in) * ((f32)coeff / 128.0f) + channelAux.prev_lp_out, -1.0f, 1.0f ); @@ -717,7 +447,7 @@ static void RenderChannel( // IIR part 2, biquad: out[n] = (b1*in[n-1] + b2*in[n-2] + a1*out[n-1] + a2*out[n-2]) / 32768 if ((channel.mFilterMode & 0x20) != 0) { - for (f32& sample : audioLoadBuffer) { + for (f32& sample : buf) { f32 out = std::clamp(( channel.iir_filter_params[0] * channelAux.biq_in1 + // b1 channel.iir_filter_params[1] * channelAux.biq_in2 + // b2 @@ -740,28 +470,16 @@ static void RenderChannel( } channelAux.decodeBufCount = std::max(0, remainingDecodeBuf); - - auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, DSP_SUBFRAME_SIZE); - - static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); - - RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe); - RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, hasReadSamples, subframe); } -void dusk::audio::DspInit() { - SharedReverb.setwet(1.0f); - SharedReverb.setdry(0.0f); - SharedReverb.setroomsize(0.5f); - SharedReverb.setdamp(0.7f); - SharedReverb.setwidth(1.0f); - SharedReverb.setmode(0.0f); - SharedReverb.mute(); -} +struct VolumeValue { + f32 Target; + f32 Init; +}; -void dusk::audio::ApplyVolume( +static void ApplyVolume( std::span dst, - const std::span src, + std::span src, const f32 startVolume, const f32 endVolume) { assert(dst.size() >= src.size()); @@ -777,3 +495,208 @@ void dusk::audio::ApplyVolume( } } } + +static void ApplyPanning( + const JASDsp::TChannel& voice, + ChannelAuxData& aux, + const DspSubframe& input, + OutputSubframe& output) +{ + std::array volumes = {}; + + if (voice.mAutoMixerBeenSet) { + const auto volume = VolumeFromU16(voice.mAutoMixerVolume); + const auto initVolume = VolumeFromU16(voice.mAutoMixerInitVolume); + + const auto right = static_cast(voice.mAutoMixerPanDolby >> 8) / 127.0f; + const auto left = 1.0f - right; + + volumes[0] = {left * volume, left * initVolume}; + volumes[1] = {right * volume, right * initVolume}; + } else { + for (const auto& outChannel : voice.mOutputChannels) { + std::optional ch; + + switch (outChannel.mBusConnect) { + case 0x0D00: + ch = OutputChannel::LEFT; + break; + case 0x0D60: + ch = OutputChannel::RIGHT; + break; + default: + break; + } + + if (ch) { + auto& v = volumes[static_cast(*ch)]; + v.Target = VolumeFromU16(outChannel.mTargetVolume); + v.Init = VolumeFromU16(outChannel.mCurrentVolume); + } + } + } + + for (size_t i = 0; i < OutputSubframe::NUM_CHANNELS; i++) { + const auto ch = static_cast(i); + const auto& volume = volumes[i]; + + const f32 targetVolume = volume.Target; + auto& prevVolume = aux.PrevVolume(ch); + + if (std::isnan(prevVolume)) { + // Initialize previous volume to new volume on first render. + prevVolume = volume.Init; + } + + if (prevVolume == 0 && targetVolume == 0) { + continue; + } + + ApplyVolume(output[ch], input, prevVolume, targetVolume); + prevVolume = targetVolume; + } +} + +void dusk::audio::DspInit() { + SharedReverb.setwet(1.0f); + SharedReverb.setdry(0.0f); + SharedReverb.setroomsize(0.5f); + SharedReverb.setdamp(0.7f); + SharedReverb.setwidth(1.0f); + SharedReverb.setmode(0.0f); + SharedReverb.mute(); +} + +void dusk::audio::DspRender(OutputSubframe& subframe) { + ZoneScoped; + if (DumpAudio != sDumpWasActive) { + sDumpWasActive = DumpAudio; + if (DumpAudio) { + OpenChannelDumpFiles(); + } else { + CloseChannelDumpFiles(); + } + } + + GenerateEvolvingHarmonic(); + + std::span voices(JASDsp::CH_BUF, DSP_CHANNELS); + + DspSubframe reverbInputL = {}; + DspSubframe reverbInputR = {}; + bool anyReverbInput = false; + + DspSubframe surroundBus = {}; + bool anySurroundInput = false; + + for (int i = 0; i < voices.size(); i++) { + auto& voice = voices[i]; + auto& aux = ChannelAux[i]; + + if (!voice.mIsActive) { + continue; + } + else if (voice.mPauseFlag) { + // Not really sure what the practical difference between pause and + // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? + continue; + } + else if (voice.mForcedStop) { + voice.mIsFinished = true; + continue; + } + + DspSubframe monoBuf = {}; + if (voice.mWaveAramAddress == 0) { + RenderOscChannel(voice, aux, monoBuf); + } else { + ValidateChannel(voice); + RenderChannel(voice, aux, monoBuf); + } + + OutputSubframe buf = {}; + ApplyPanning(voice, aux, monoBuf, buf); + + if (EnableReverb) { + // scale the input to the reverb rather than using wet/dry on the output. + // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, + // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes + // prevents transients when the next sound starts playing with a different reverb level + // 600.0f was pulled out of my ass and just sounds good enough for console + f32 inputGain = (voice.mAutoMixerFxMix >> 8) / 600.0f; + if (inputGain > 0) { + anyReverbInput = true; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + reverbInputL[j] += buf.channels[0][j] * inputGain; + reverbInputR[j] += buf.channels[1][j] * inputGain; + } + } + } + + if (EnableHrtf && voice.mAutoMixerBeenSet) { + f32 dolby = (voice.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 = (buf.channels[0][j] + buf.channels[1][j]) * 0.5f; + surroundBus[j] += mono * extract; + buf.channels[0][j] *= frontScale; + buf.channels[1][j] *= frontScale; + } + } + } + + if (DumpAudio && sChannelDumpFiles[i]) { + f32 interleaved[DSP_SUBFRAME_SIZE * 2]; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + interleaved[j * 2 + 0] = buf.channels[0][j]; + interleaved[j * 2 + 1] = buf.channels[1][j]; + } + fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); + } + + for (int o = 0; o < subframe.channels.size(); o++) { + MixSubframe(subframe.channels[o], buf.channels[o]); + } + } + + if (EnableReverb && (anyReverbInput || ReverbHasTail)) { + // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 + constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; + f32 wetEnergy = SharedReverb.processmix( + reverbInputL.data(), reverbInputR.data(), + subframe.channels[0].data(), subframe.channels[1].data(), + DSP_SUBFRAME_SIZE, 1, 1.0f + ); + 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) { + ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); + } + PrevMasterVolume = MasterVolume; +} diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index cfcbfe3f46..a2504ab586 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -5,9 +5,6 @@ #include #include -#include "SDL3/SDL_audio.h" -#include - // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -123,12 +120,6 @@ namespace dusk::audio { return channel.mBytesPerBlock; } - /** - * Apply a volume level to audio data. - * Interpolates across the two provided volume levels to avoid clicking. - */ - void ApplyVolume(std::span dst, std::span src, f32 startVolume, f32 endVolume); - extern f32 MasterVolume; extern f32 PrevMasterVolume; extern bool EnableReverb; From 9e05a0a69250520a22a6f0ef12cb07371dd9a667 Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Wed, 2 Sep 2026 03:55:28 +0000 Subject: [PATCH 036/100] Simplify resampling ratio calculation --- src/dusk/audio/DuskDsp.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 0122adbc43..3ea0c51623 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -101,13 +101,6 @@ static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 sampl return (samples / channel.mSamplesPerBlock) * BlockBytes(channel); } -/** - * Converts a pitch value on a DSP channel to a sample rate. - */ -constexpr static int PitchToSampleRate(u16 value) { - return static_cast(static_cast(SampleRate) * value / 4096); -} - /** * Reset state for a DSP channel between independent playbacks. */ @@ -398,7 +391,7 @@ static void RenderChannel( } // how many input samples we step per output sample, aka the resampling ratio - f32 step = (f32)PitchToSampleRate(channel.mPitch) / SampleRate; + auto step = static_cast(channel.mPitch) / 4096.0f; // how many input samples to resample to DSP_SUBFRAME_SIZE output samples int needed = static_cast(channelAux.resamplePos + DSP_SUBFRAME_SIZE * step) + 2; From 548b98072d65f83ec7a41b8828866a6cb09946c1 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 31 Aug 2026 20:40:30 -0600 Subject: [PATCH 037/100] Initial mod browser + RmlUi CSS variables --- CMakeLists.txt | 2 +- files.cmake | 8 + res/rml/logs.rcss | 74 +- res/rml/mod_browser.rcss | 801 +++++++++++++++++++++ res/rml/mods.rcss | 140 ++-- res/rml/overlay.rcss | 84 ++- res/rml/popover.rcss | 66 +- res/rml/popup.rcss | 17 +- res/rml/prelaunch.rcss | 175 +++-- res/rml/tabbing.rcss | 31 +- res/rml/theme.rcss | 72 ++ res/rml/touch_controls.rcss | 86 +-- res/rml/touch_controls_editor.rcss | 42 +- res/rml/tuner.rcss | 65 +- res/rml/window.rcss | 294 ++++---- src/dusk/mods/catalog.cpp | 456 ++++++++++++ src/dusk/mods/catalog.hpp | 131 ++++ src/dusk/mods/svc/ui.cpp | 23 +- src/dusk/ui/achievements.cpp | 73 +- src/dusk/ui/button.cpp | 2 +- src/dusk/ui/color_input.cpp | 4 +- src/dusk/ui/graphics_tuner.cpp | 43 +- src/dusk/ui/logs_window.cpp | 38 +- src/dusk/ui/menu_bar.cpp | 19 +- src/dusk/ui/mod_browser.cpp | 906 ++++++++++++++++++++++++ src/dusk/ui/mod_browser.hpp | 49 ++ src/dusk/ui/mod_texture_provider.cpp | 75 +- src/dusk/ui/modal.cpp | 37 +- src/dusk/ui/modal.hpp | 4 + src/dusk/ui/mods_window.cpp | 142 ++-- src/dusk/ui/mods_window.hpp | 3 + src/dusk/ui/overlay.cpp | 56 +- src/dusk/ui/pane.cpp | 3 +- src/dusk/ui/popover.cpp | 1 + src/dusk/ui/prelaunch.cpp | 100 ++- src/dusk/ui/preset.cpp | 48 +- src/dusk/ui/remote_texture_provider.cpp | 243 +++++++ src/dusk/ui/remote_texture_provider.hpp | 14 + src/dusk/ui/reporting.cpp | 54 +- src/dusk/ui/runtime_image.cpp | 73 ++ src/dusk/ui/runtime_image.hpp | 19 + src/dusk/ui/select_button.cpp | 19 +- src/dusk/ui/settings.cpp | 80 ++- src/dusk/ui/touch_controls.cpp | 3 +- src/dusk/ui/touch_controls_editor.cpp | 4 +- src/dusk/ui/ui.cpp | 29 +- src/dusk/ui/ui.hpp | 4 + src/dusk/ui/window.cpp | 7 +- src/dusk/ui/window.hpp | 2 +- 49 files changed, 3765 insertions(+), 956 deletions(-) create mode 100644 res/rml/mod_browser.rcss create mode 100644 res/rml/theme.rcss create mode 100644 src/dusk/mods/catalog.cpp create mode 100644 src/dusk/mods/catalog.hpp create mode 100644 src/dusk/ui/mod_browser.cpp create mode 100644 src/dusk/ui/mod_browser.hpp create mode 100644 src/dusk/ui/remote_texture_provider.cpp create mode 100644 src/dusk/ui/remote_texture_provider.hpp create mode 100644 src/dusk/ui/runtime_image.cpp create mode 100644 src/dusk/ui/runtime_image.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dcd53086fe..e81a7ce434 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,7 +304,7 @@ include(cmake/GameABIConfig.cmake) find_package(Threads REQUIRED) set(GAME_COMPILE_DEFS DUSK_BUILDING_GAME=1) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select borealis::io borealis::log borealis::presentation borealis::sentry borealis::update freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt + aurora::card borealis::cli borealis::crash borealis::data borealis::disc borealis::discord borealis::file_select borealis::http borealis::io borealis::log borealis::presentation borealis::sentry borealis::update freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt Threads::Threads zstd::libzstd dusklight_game_headers) if (DUSK_HAS_FUNCHOOK) list(APPEND GAME_LIBS funchook-static) diff --git a/files.cmake b/files.cmake index 7cde5bfb2f..c8f1470e5f 100644 --- a/files.cmake +++ b/files.cmake @@ -1482,6 +1482,8 @@ set(DUSK_FILES src/dusk/mods/loader/native_module.hpp src/dusk/mods/loader/prepatch.cpp src/dusk/mods/loader/prepatch.hpp + src/dusk/mods/catalog.cpp + src/dusk/mods/catalog.hpp src/dusk/mods/item.hpp src/dusk/mods/item_checks.cpp src/dusk/mods/item_gives.cpp @@ -1563,8 +1565,14 @@ set(DUSK_FILES src/dusk/ui/list.hpp src/dusk/ui/menu_bar.cpp src/dusk/ui/menu_bar.hpp + src/dusk/ui/mod_browser.cpp + src/dusk/ui/mod_browser.hpp src/dusk/ui/mod_texture_provider.cpp src/dusk/ui/mod_texture_provider.hpp + src/dusk/ui/remote_texture_provider.cpp + src/dusk/ui/remote_texture_provider.hpp + src/dusk/ui/runtime_image.cpp + src/dusk/ui/runtime_image.hpp src/dusk/ui/mod_window.cpp src/dusk/ui/mod_window.hpp src/dusk/ui/modal.cpp diff --git a/res/rml/logs.rcss b/res/rml/logs.rcss index 126684f9d2..fd1933ed3a 100644 --- a/res/rml/logs.rcss +++ b/res/rml/logs.rcss @@ -2,19 +2,20 @@ window.logs content { flex-flow: column; } -window.logs .log-toolbar { +window.logs log-toolbar { display: flex; flex-flow: row; - flex: 0 0 64dp; - height: 64dp; + flex: 0 0 var(--toolbar-height); + height: var(--toolbar-height); align-items: center; - gap: 8dp; + gap: var(--space-sm); padding-right: 72dp; - background-color: rgba(217, 217, 217, 10%); - border-bottom: 2dp #92875B; - font-family: "Fira Sans Condensed"; + background-color: rgba(var(--color-neutral-rgb), 10%); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; + font-size: var(--font-size-xl); } window.logs > close { @@ -22,76 +23,77 @@ window.logs > close { right: 8dp; } -window.logs .log-title { +window.logs log-title { align-self: stretch; flex: 0 0 auto; - padding: 0 24dp; - line-height: 64dp; + padding: 0 var(--space-xl); + line-height: var(--toolbar-height); text-transform: uppercase; - border-bottom: 4dp #C2A42D; + border-bottom-width: 4dp; + border-bottom-color: var(--color-accent); font-effect: glow(0dp 4dp 0dp 4dp black); } -window.logs .log-title-mod { +window.logs log-title-mod { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 15dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-base); + color: rgba(var(--color-text-rgb), 55%); } -.log-toolbar-spacer { +log-toolbar-spacer { flex: 1 1 0; } -.log-toolbar button { +log-toolbar button { flex: 0 0 auto; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 15dp; - padding: 5dp 12dp; + font-size: var(--font-size-base); + padding: 5dp var(--space-md); } window.logs content pane.log-view { flex: 1 1 0; - padding: 12dp 16dp; + padding: var(--space-md) var(--space-lg); padding-bottom: 0dp; gap: 0dp; } -.log-lines { +log-lines { display: block; } -.log-line { +log-line { display: block; - font-family: "Noto Mono"; - font-size: 13dp; + font-family: var(--font-family-monospace); + font-size: var(--font-size-xs); line-height: 1.5; word-break: break-word; white-space: pre-wrap; } -.log-line .log-time { - color: rgba(224, 219, 200, 45%); +log-line log-time { + color: rgba(var(--color-text-rgb), 45%); } -.log-line .log-mod { - color: rgba(194, 164, 45, 80%); +log-line log-mod { + color: rgba(var(--color-accent-rgb), 80%); } -.log-line.lvl-trace, -.log-line.lvl-debug { +log-line.lvl-trace, +log-line.lvl-debug { opacity: 0.55; } -.log-line.lvl-warn .log-msg { - color: #ffa826; +log-line.lvl-warn log-msg { + color: var(--color-warning); } -.log-line.lvl-error .log-msg { - color: #cc4444; +log-line.lvl-error log-msg { + color: var(--color-error); } diff --git a/res/rml/mod_browser.rcss b/res/rml/mod_browser.rcss new file mode 100644 index 0000000000..22186313e3 --- /dev/null +++ b/res/rml/mod_browser.rcss @@ -0,0 +1,801 @@ +window.mod-browser, +window.mod-browser-detail, +window.screenshot-viewer { + --color-official-badge: #46edd5; + --color-catalog-warning-background-rgb: 180, 107, 42; + --color-catalog-warning-text: #e8cba7; + --color-catalog-missing: #e8b177; + background-color: rgba(var(--color-surface-rgb), 96%); +} + +window.mod-browser > content { + flex-flow: row; +} + +catalog-filters { + display: flex; + flex-flow: column; + flex: 0 0 264dp; + min-width: 0; + padding: var(--space-xl) 18dp; + gap: var(--space-sm); + border-right-width: 1dp; + border-right-color: var(--color-border); + background-color: rgba(var(--color-control-rgb), 45%); +} + +catalog-filters h1, +catalog-results header h1 { + margin: 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-5xl); + font-weight: bold; +} + +catalog-filters h1 { + padding: 0 var(--space-sm) var(--space-sm) var(--space-sm); +} + +catalog-filters h2 { + margin: var(--space-md) var(--space-sm) 0 var(--space-sm); + font-family: var(--font-family-heading); + font-size: var(--font-size-xs); + font-weight: bold; + text-transform: uppercase; + opacity: 0.42; +} + +catalog-filters select-button { + padding: var(--space-sm) 10dp; + border-radius: var(--radius-panel); +} + +catalog-filters select-button key { + font-size: var(--font-size-xs); +} + +catalog-filters select-button value, +catalog-filters select-button input { + font-size: var(--font-size-sm); +} + +catalog-filters button.catalog-library-link { + padding: var(--space-sm) 10dp; + border-radius: var(--radius-panel); + text-align: left; + font-size: var(--font-size-sm); +} + +catalog-results { + display: flex; + flex-flow: column; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + padding: var(--space-xl); + gap: var(--space-lg); +} + +catalog-results header { + display: block; + flex: 0 0 auto; + padding-right: var(--space-2xl); +} + +catalog-results header catalog-results-summary { + display: block; + margin-top: var(--space-2xs); + font-size: var(--font-size-xs); + opacity: 0.5; +} + +catalog-viewport { + display: block; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + overflow: hidden auto; +} + +catalog-grid { + display: flex; + flex-flow: row wrap; + min-width: 0; + gap: 14dp; + padding: var(--space-2xs); +} + +button.catalog-card { + display: flex; + flex-flow: column; + flex: 0 0 48%; + min-width: 260dp; + height: 310dp; + padding: 0; + overflow: hidden; + text-align: left; + border-radius: 12dp; +} + +catalog-card-art { + display: block; + position: relative; + flex: 0 0 118dp; + min-height: 118dp; + background-color: rgba(var(--color-border-rgb), 12%); +} + +catalog-card-art-shadow { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + decorator: linear-gradient(180deg, rgba(var(--color-surface-rgb), 0%) 45%, rgba(var(--color-surface-rgb), 85%)); +} + +catalog-card-body { + display: flex; + flex-flow: column; + flex: 1 1 auto; + min-height: 0; + padding: var(--space-xl) var(--space-lg) var(--space-md) var(--space-lg); + gap: 7dp; +} + +catalog-card-kicker { + display: flex; + align-items: center; + gap: 6dp; + font-size: var(--font-size-sm); +} + +catalog-official-badge { + padding: var(--space-2xs) 5dp; + border-radius: var(--radius-small); + background-color: rgba(var(--color-accent-rgb), 28%); + color: var(--color-official-badge); + font-style: normal; + font-size: 9dp; +} + +catalog-card-kicker > catalog-card-category { + font-family: var(--font-family-heading); + font-size: var(--font-size-sm); + font-weight: bold; + text-transform: uppercase; + color: var(--color-accent); +} + +catalog-card-updated { + margin-left: auto; + font-weight: normal; + text-transform: none; + opacity: 0.45; +} + +catalog-card-identity { + display: block; +} + +catalog-card-icon { + display: block; + position: absolute; + left: 14dp; + bottom: -18dp; + z-index: 1; + width: 56dp; + height: 56dp; + border-radius: var(--radius-panel); + overflow: hidden; + background-color: rgba(var(--color-border-rgb), 18%); + box-shadow: rgba(0, 0, 0, 60%) 0 6dp 16dp; +} + +catalog-card-icon-image { + display: block; + width: 100%; + height: 100%; +} + +catalog-card-title { + display: block; + font-family: var(--font-family-body); + font-size: var(--font-size-3xl); + font-weight: bold; + color: var(--color-neutral); + line-height: 1.5; +} + +catalog-card-author { + display: block; + font-size: var(--font-size-3xs); + opacity: 0.55; +} + +catalog-card-summary { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + font-size: var(--font-size-base); + line-height: 1.35; + color: rgba(var(--color-text-rgb), 68%); +} + +catalog-card-meta { + display: flex; + align-items: center; + gap: 10dp; + font-size: var(--font-size-3xs); + opacity: 0.52; +} + +catalog-card-stat { + display: flex; + align-items: center; + gap: 3dp; +} + +catalog-card-meta icon { + flex: 0 0 13dp; + font-size: var(--font-size-sm); + line-height: 1; +} + +catalog-card-size { + margin-left: auto; + font-size: var(--font-size-sm); +} + +catalog-card-size.installed { + color: var(--color-success); +} + +catalog-pagination { + display: flex; + flex-flow: row; + align-items: center; + flex: 0 0 auto; + gap: var(--space-sm); +} + +catalog-pagination-label { + flex: 1 1 auto; + text-align: center; + font-size: var(--font-size-xs); + opacity: 0.5; +} + +catalog-pagination button { + font-size: var(--font-size-sm); + padding: 6dp var(--space-md); +} + +catalog-results-status, +catalog-detail-status { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: 100%; + gap: var(--space-sm); + text-align: center; +} + +catalog-status-title { + display: block; + font-family: var(--font-family-heading); + font-size: 25dp; + font-weight: bold; +} + +catalog-status-message { + display: block; + opacity: 0.58; +} + +catalog-results-status button, +catalog-detail-status button { + margin-top: var(--space-sm); + font-size: var(--font-size-base); +} + +window.mod-browser-detail > content { + display: block; +} + +detail-scroll { + display: flex; + flex-flow: column; + width: 100%; + height: 100%; + min-width: 0; + overflow: hidden auto; + padding-bottom: var(--space-2xl); +} + +catalog-detail-hero { + display: flex; + position: relative; + flex-flow: column; + justify-content: space-between; + flex: 0 0 220dp; + min-height: 220dp; + padding: 18dp var(--space-xl) 20dp var(--space-xl); + background-color: rgba(var(--color-control-rgb), 75%); +} + +catalog-detail-hero-shadow { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + decorator: linear-gradient(180deg, rgba(var(--color-surface-rgb), 25%) 40%, rgba(var(--color-surface-rgb), 92%)); +} + +catalog-detail-actions { + display: flex; + position: relative; + z-index: 1; + flex-flow: row; + gap: var(--space-sm); +} + +catalog-detail-actions button, +catalog-source-actions button { + font-size: var(--font-size-sm); + padding: 7dp var(--space-md); + --button-background: rgba(var(--color-control-rgb), 75%); + --button-background-hover: rgba(var(--color-control-rgb), 90%); + --button-background-selected: rgba(var(--color-control-rgb), 90%); + --button-background-active: rgba(var(--color-surface-rgb), 90%); +} + +.catalog-icon-action { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.catalog-icon-action icon { + flex: 0 0 18dp; + font-size: var(--font-size-xl); + line-height: 1; +} + +catalog-detail-identity { + display: flex; + position: relative; + z-index: 1; + align-items: center; + gap: 14dp; +} + +catalog-detail-icon { + display: block; + flex: 0 0 70dp; + width: 70dp; + height: 70dp; + border-radius: var(--radius-panel); + overflow: hidden; + background-color: rgba(var(--color-border-rgb), 25%); + box-shadow: rgba(0, 0, 0, 65%) 0 8dp 20dp; +} + +catalog-detail-icon-image { + display: block; + width: 100%; + height: 100%; +} + +catalog-detail-heading { + display: block; + min-width: 0; +} + +catalog-detail-category { + display: block; + font-family: var(--font-family-heading); + font-size: var(--font-size-sm); + font-weight: bold; + text-transform: uppercase; + color: var(--color-accent); +} + +catalog-detail-identity h1 { + display: block; + margin: 1dp 0; + font-family: var(--font-family-body); + font-size: var(--font-size-5xl); + font-weight: bold; + color: var(--color-neutral); +} + +catalog-detail-identity h1 small { + margin-left: 9dp; + font-family: var(--font-family-body); + font-size: var(--font-size-xs); + font-weight: normal; + opacity: 0.55; +} + +catalog-detail-identity p { + display: block; + margin: 0; + font-size: var(--font-size-xs); + opacity: 0.72; +} + +catalog-detail-stats { + display: flex; + flex-flow: row; + align-items: center; + gap: 28dp; + padding: 13dp 28dp; + border-top-width: 1dp; + border-top-color: rgba(var(--color-border-rgb), 30%); + border-bottom-width: 1dp; + border-bottom-color: rgba(var(--color-border-rgb), 30%); + font-size: var(--font-size-2xs); + color: rgba(var(--color-text-rgb), 58%); +} + +catalog-detail-stat { + display: flex; + align-items: center; + gap: 5dp; +} + +catalog-detail-stats icon { + flex: 0 0 17dp; + font-size: var(--font-size-lg); + line-height: 1; +} + +catalog-stat-value { + color: var(--color-text); + font-weight: bold; +} + +/*catalog-native-warning { + display: flex; + align-items: center; + gap: 10dp; + margin: 18dp var(--space-xl) 0 var(--space-xl); + padding: 11dp 14dp; + border-radius: var(--radius-panel); + background-color: rgba(var(--color-catalog-warning-background-rgb), 18%); + box-shadow: rgba(206, 137, 67, 45%) 0 0 0 1dp; + color: var(--color-catalog-warning-text); +} + +catalog-native-warning icon { + font-size: var(--font-size-4xl); +} + +catalog-warning-copy { + display: flex; + flex-flow: column; + font-size: var(--font-size-2xs); +} + +catalog-warning-title { + display: block; + font-size: var(--font-size-xs); + font-weight: bold; +} + +catalog-warning-message { + display: block; + font-size: var(--font-size-3xs); +}*/ + +catalog-detail-body { + display: flex; + flex-flow: row; + align-items: flex-start; + gap: 28dp; + padding: var(--space-xl); +} + +catalog-detail-body main { + display: flex; + flex-flow: column; + flex: 1 1 auto; + min-width: 0; + gap: 28dp; +} + +catalog-detail-body section { + display: block; +} + +catalog-detail-body section.catalog-scroll-anchor { + focus: auto; +} + +catalog-detail-body section.catalog-scroll-anchor:focus-visible { + border-radius: var(--radius-control); + box-shadow: rgba(194, 164, 45, 45%) 0 0 0 1dp; +} + +catalog-detail-body h2, +catalog-detail-body h3 { + display: block; + margin: 0 0 10dp 0; + font-family: var(--font-family-heading); + font-size: 21dp; + font-weight: bold; +} + +catalog-detail-body h2 small { + margin-left: 7dp; + font-family: var(--font-family-body); + font-size: var(--font-size-3xs); + font-weight: normal; + opacity: 0.5; +} + +catalog-fragment { + display: block; + font-size: var(--font-size-md); + line-height: 1.55; + color: var(--color-neutral); +} + +catalog-fragment p, +catalog-fragment ul, +catalog-fragment ol { + display: block; + margin: 0 0 10dp 0; +} + +catalog-fragment ul, +catalog-fragment ol { + padding-left: var(--space-xl); +} + +catalog-fragment li { + display: block; + position: relative; + margin: 3dp 0; +} + +catalog-list-marker { + display: block; + position: absolute; + right: 100%; + width: var(--space-xl); + padding-right: var(--space-sm); + text-align: right; +} + +catalog-fragment h1, +catalog-fragment h2, +catalog-fragment h3, +catalog-fragment h4, +catalog-fragment h5, +catalog-fragment h6 { + display: block; + margin: 15dp 0 5dp 0; + font-family: var(--font-family-heading); + font-weight: bold; + font-size: var(--font-size-xl); + color: var(--color-neutral); +} + +catalog-fragment h1 { + font-size: var(--font-size-5xl); +} + +catalog-fragment h2 { + font-size: var(--font-size-4xl); +} + +catalog-fragment h3 { + font-size: var(--font-size-2xl); +} + +window.screenshot-viewer > close { + display: none; +} + +catalog-gallery { + display: flex; + flex-flow: row; + height: 210dp; + gap: var(--space-sm); +} + +button.catalog-screenshot { + flex: 1 1 0; + height: 100%; + min-width: 0; + padding: 0; + border-radius: var(--radius-panel); + --button-background: rgba(var(--color-border-rgb), 14%); + font-size: var(--font-size-5xl); +} + +button.catalog-screenshot.primary { + flex: 2 1 0; +} + +catalog-dependencies { + display: flex; + flex-flow: column; + gap: 7dp; + font-size: var(--font-size-xs); +} + +catalog-dependency { + display: flex; + flex-flow: column; + padding: 9dp 11dp; + border-radius: var(--radius-control); + background-color: rgba(var(--color-border-rgb), 9%); +} + +catalog-dependency-status { + display: block; + font-size: var(--font-size-3xs); + opacity: 0.56; +} + +catalog-dependency.missing { + color: var(--color-catalog-missing); +} + +catalog-detail-body aside { + display: flex; + flex-flow: column; + flex: 0 0 264dp; + min-width: 0; + padding: var(--space-lg); + gap: var(--space-md); + border-radius: 9dp; + background-color: rgba(var(--color-control-rgb), 42%); + box-shadow: rgba(146, 135, 91, 24%) 0 0 0 1dp; +} + +catalog-detail-body dl { + display: flex; + flex-flow: row wrap; + margin: 0; + font-size: var(--font-size-2xs); +} + +catalog-detail-body dt { + flex: 0 0 42%; + padding: 5dp 0; + opacity: 0.5; +} + +catalog-detail-body dd { + flex: 1 1 52%; + margin: 0; + padding: 5dp 0; + text-align: right; +} + +catalog-source-actions { + display: flex; + flex-flow: column; +} + +window.screenshot-viewer > content { + flex-flow: column; + padding: 18dp; + gap: var(--space-md); +} + +catalog-screenshot-full { + display: block; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + background-color: rgba(var(--color-black-rgb), 45%); +} + +catalog-screenshot-actions { + display: flex; + flex-flow: row; + justify-content: center; + flex: 0 0 auto; + gap: var(--space-sm); +} + +catalog-screenshot-actions button { + font-size: var(--font-size-sm); + padding: 7dp 13dp; +} + +@media (max-height: 640dp) { + catalog-filters { + flex-basis: 232dp; + padding: 15dp var(--space-md); + gap: 5dp; + } + + catalog-filters h1, + catalog-results header h1 { + font-size: 23dp; + } + + catalog-filters h2 { + margin-top: 6dp; + } + + catalog-results { + padding: 15dp; + gap: 10dp; + } + + button.catalog-card { + min-width: 220dp; + height: 254dp; + } + + catalog-card-art { + flex-basis: 78dp; + min-height: 78dp; + } + + catalog-card-body { + padding: 18dp 10dp var(--space-sm) 10dp; + } + + catalog-card-icon { + left: 10dp; + bottom: -14dp; + width: 40dp; + height: 40dp; + } + + catalog-card-kicker, + catalog-card-kicker > catalog-card-category { + font-size: var(--font-size-3xs); + } + + catalog-card-title { + font-size: var(--font-size-md); + } + + catalog-card-summary { + font-size: var(--font-size-3xs); + } + + catalog-detail-hero { + flex-basis: 160dp; + min-height: 160dp; + padding: var(--space-md) 18dp; + } + + catalog-detail-icon { + flex-basis: 56dp; + width: 56dp; + height: 56dp; + } + + catalog-detail-category { + font-size: var(--font-size-2xs); + } + + catalog-detail-identity h1 { + font-size: var(--font-size-3xl); + } + + catalog-detail-body { + padding: 18dp; + gap: 18dp; + } + + catalog-gallery { + height: 160dp; + } +} diff --git a/res/rml/mods.rcss b/res/rml/mods.rcss index 34e86f485b..81ef5800de 100644 --- a/res/rml/mods.rcss +++ b/res/rml/mods.rcss @@ -1,8 +1,8 @@ window.mods content pane.mod-list { flex: 0 0 360dp; - padding: 16dp; + padding: var(--space-lg); padding-bottom: 0dp; - gap: 4dp; + gap: var(--space-xs); } @media (max-height: 640dp) { @@ -12,29 +12,36 @@ window.mods content pane.mod-list { } window.mods content pane.mod-detail { - gap: 12dp; + gap: var(--space-md); } -.mod-info-row { +window.mods content pane.mod-list button.mod-browser-entry { + margin-bottom: var(--space-sm); + text-align: left; + font-family: var(--font-family-heading); + font-weight: bold; +} + +mod-info-row { display: flex; align-items: center; - gap: 12dp; - padding: 4dp 0; + gap: var(--space-md); + padding: var(--space-xs) 0; } -.mod-info-label { - font-family: "Fira Sans Condensed"; +mod-info-label { + font-family: var(--font-family-heading); font-weight: bold; opacity: 0.55; flex: 0 0 auto; } -.mod-info-value { +mod-info-value { flex: 1 1 0; } .mod-path { - font-size: 14dp; + font-size: var(--font-size-sm); word-break: break-all; opacity: 0.7; } @@ -42,10 +49,10 @@ window.mods content pane.mod-detail { mod-entry { display: flex; flex-flow: row; - gap: 12dp; + gap: var(--space-md); padding: 10dp; border-radius: 10dp; - decorator: vertical-gradient(#c2a42d00 #c2a42d00); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0%) rgba(var(--color-accent-rgb), 0%)); transition: decorator 0.1s linear-in-out; cursor: pointer; focus: auto; @@ -57,43 +64,43 @@ mod-entry.current { mod-entry:hover, mod-entry:focus-visible { - decorator: vertical-gradient(#c2a42d00 #c2a42d26); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0) rgba(var(--color-accent-rgb), 38)); } mod-entry:selected { - decorator: vertical-gradient(#c2a42d10 #c2a42d40); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 16) rgba(var(--color-accent-rgb), 64)); } mod-entry .mod-icon { flex: 0 0 auto; width: 56dp; height: 56dp; - border-radius: 8dp; + border-radius: var(--radius-panel); } mod-entry icon.mod-icon { font-size: 36dp; - background-color: rgba(17, 16, 10, 20%); - color: rgba(224, 219, 200, 45%); + background-color: rgba(var(--color-control-rgb), 20%); + color: rgba(var(--color-text-rgb), 45%); decorator: text("" center center); } -mod-entry .mod-entry-info { +mod-entry mod-entry-info { display: flex; flex-flow: column; flex: 1 1 0; min-width: 0; - gap: 2dp; + gap: var(--space-2xs); } -mod-entry .mod-entry-name { +mod-entry mod-entry-name { display: flex; flex-flow: row; align-items: baseline; gap: 6dp; } -mod-entry .mod-entry-name-text { +mod-entry mod-entry-name-text { flex: 0 1 auto; min-width: 0; font-weight: bold; @@ -101,47 +108,47 @@ mod-entry .mod-entry-name-text { overflow: hidden; } -mod-entry .mod-entry-version { +mod-entry mod-entry-version { flex: 0 0 auto; - font-size: 13dp; - color: rgba(224, 219, 200, 50%); + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); } -mod-entry .mod-entry-status.active { - color: #44cc55; +mod-entry mod-entry-status.active { + color: var(--color-success); } -mod-entry .mod-entry-status.failed { - color: #cc4444; +mod-entry mod-entry-status.failed { + color: var(--color-error); } -mod-entry .mod-entry-network { +mod-entry mod-entry-network { margin-left: 6dp; padding: 1dp 5dp; border-radius: 5dp; - background-color: rgba(67, 151, 219, 20%); - color: #6fb7ef; + background-color: rgba(var(--color-info-rgb), 20%); + color: var(--color-info); } -mod-entry .mod-entry-desc { - font-size: 14dp; +mod-entry mod-entry-desc { + font-size: var(--font-size-sm); line-height: 1.3; - color: rgba(224, 219, 200, 65%); + color: rgba(var(--color-text-rgb), 65%); max-height: 2.6em; overflow: hidden; white-space: pre-wrap; } -mod-entry .mod-entry-sub { - font-size: 13dp; - color: rgba(224, 219, 200, 50%); +mod-entry mod-entry-sub { + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 50%); } mod-entry.inactive .mod-icon { filter: grayscale(1); } -mod-entry.inactive .mod-entry-info { +mod-entry.inactive mod-entry-info { opacity: 0.5; } @@ -155,19 +162,19 @@ mod-header.has-banner { margin: -24dp -24dp 0dp -24dp; } -mod-header .mod-actions { +mod-header mod-actions { position: absolute; top: 24dp; left: 24dp; display: flex; flex-flow: row; - gap: 8dp; + gap: var(--space-sm); } -mod-header .mod-actions button { - font-size: 16dp; +mod-header mod-actions button { + font-size: var(--font-size-md); padding: 6dp 14dp; - background-color: rgba(21, 22, 16, 80%); + background-color: rgba(var(--color-surface-rgb), 80%); box-shadow: rgba(146, 135, 91, 60%) 0 0 0 1dp; } @@ -177,56 +184,55 @@ mod-header.no-banner { align-items: center; } -mod-header.no-banner .mod-actions { +mod-header.no-banner mod-actions { position: static; } -window.mods .mod-title { +window.mods mod-title { display: block; - font-size: 28dp; + font-size: var(--font-size-5xl); font-weight: bold; } -window.mods .mod-title .mod-title-version { +window.mods mod-title mod-title-version { font-weight: normal; - font-size: 16dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-md); + color: rgba(var(--color-text-rgb), 55%); } -window.mods .mod-author { +window.mods mod-author { display: block; - font-size: 15dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-base); + color: rgba(var(--color-text-rgb), 55%); } -window.mods .mod-restart-note { - font-size: 15dp; - color: #ffa826; +window.mods mod-restart-note { + font-size: var(--font-size-base); + color: var(--color-warning); opacity: 0.85; } -window.mods .mod-description { +window.mods mod-description { line-height: 1.5; } -.status-badge { - font-size: 14dp; +status-badge { + font-size: var(--font-size-sm); opacity: 0.7; } -.status-badge.active, -.mod-info-label.active { - color: #44cc55; +status-badge.active, +mod-info-label.active { + color: var(--color-success); opacity: 1; } -.status-badge.failed, -.mod-info-label.failed { - color: #cc4444; +status-badge.failed, +mod-info-label.failed { + color: var(--color-error); opacity: 1; } - -.status-badge.network { - color: #6fb7ef; +status-badge.network { + color: var(--color-info); opacity: 1; } diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss index 98f8bfa27a..fed6597c70 100644 --- a/res/rml/overlay.rcss +++ b/res/rml/overlay.rcss @@ -8,10 +8,10 @@ body { height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 20dp; - color: #E0DBC8; + font-size: var(--font-size-2xl); + color: var(--color-text); display: flex; flex-direction: column; justify-content: flex-end; @@ -24,16 +24,22 @@ fps, pipeline-progress, toast { position: absolute; - border: 1dp #92875B; - background-color: rgba(21, 22, 16, 80%); + border-width: 1dp; + background-color: rgba(var(--color-surface-rgb), 80%); +} + +fps, +pipeline-progress { + border-color: var(--color-border); } toast { + border-color: var(--toast-border-color); top: 40dp; right: 40dp; display: flex; flex-flow: column; - border-radius: 14dp; + border-radius: var(--radius-window); overflow: hidden; backdrop-filter: blur(5dp); box-shadow: 0 0 15dp 3dp; @@ -41,8 +47,8 @@ toast { transform: scale(0.9); transform-origin: center; transition: filter transform 0.2s cubic-in-out; - padding: 18dp 24dp; - gap: 8dp; + padding: 18dp var(--space-xl); + gap: var(--space-sm); } toast[open] { @@ -67,14 +73,14 @@ toast heading { display: flex; gap: 18dp; align-items: center; - font-family: "Fira Sans Condensed"; - font-size: 18dp; + font-family: var(--font-family-heading); + font-size: var(--font-size-xl); font-weight: bold; text-transform: uppercase; - color: #92875B; + color: var(--toast-heading-color); } -toast heading > span { +toast heading > toast-title { flex: 1 0 auto; } @@ -82,13 +88,13 @@ toast heading > row { flex: 1 0 auto; display: flex; align-items: center; - gap: 4dp; + gap: var(--space-xs); } toast message { display: flex; flex-flow: column; - gap: 8dp; + gap: var(--space-sm); } toast message row { @@ -108,7 +114,7 @@ progress { } progress fill { - background-color: rgba(194, 164, 45, 80%); + background-color: rgba(var(--color-accent-rgb), 80%); } pipeline-progress { @@ -119,8 +125,8 @@ pipeline-progress { z-index: 100; min-width: 260dp; max-width: 90%; - padding: 10dp 16dp 12dp; - border-radius: 7dp; + padding: 10dp var(--space-lg) var(--space-md); + border-radius: var(--radius-control); overflow: hidden; filter: opacity(0); transition: filter 0.2s linear-in-out; @@ -134,8 +140,8 @@ pipeline-progress[open] { pipeline-status { display: flex; align-items: center; - gap: 8dp; - font-size: 18dp; + gap: var(--space-sm); + font-size: var(--font-size-xl); font-weight: normal; white-space: nowrap; } @@ -145,26 +151,16 @@ icon.pipeline-spinner { height: 1.2em; line-height: 1.2em; font-size: 1.2em; - color: #C2A42D; + color: var(--color-accent); text-align: center; transform-origin: center; animation: 1s linear infinite pipeline-spinner-spin; } -toast.achievement { - border: 1dp #C2A42D; -} - -toast.achievement heading { - color: #C2A42D; -} - +toast.achievement, toast.warning { - border: 1dp #C2A42D; -} - -toast.warning heading { - color: #C2A42D; + --toast-border-color: var(--color-accent); + --toast-heading-color: var(--color-accent); } toast.controller-warning { @@ -181,8 +177,8 @@ toast.controller-warning[open] { transform: translateX(-50%) scale(1); } -toast.controller-warning heading { - color: #C2A42D; +toast.controller-warning { + --toast-heading-color: var(--color-accent); } toast.menu-notification { @@ -209,7 +205,7 @@ toast.menu-notification message row { } icon { - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; display: inline-block; vertical-align: middle; @@ -246,10 +242,10 @@ icon.warning { fps { display: none; z-index: 99; - font-size: 18dp; + font-size: var(--font-size-xl); font-weight: bold; - padding: 9dp 12dp; - border-radius: 7dp; + padding: 9dp var(--space-md); + border-radius: var(--radius-control); pointer-events: none; white-space: nowrap; } @@ -260,12 +256,12 @@ speedrun-timer { bottom: 0; right: 0; z-index: 99; - background-color: rgba(0, 0, 0, 65%); - padding: 2dp 4dp; + background-color: rgba(var(--color-black-rgb), 65%); + padding: var(--space-2xs) var(--space-xs); pointer-events: none; - font-family: "Noto Mono"; - font-size: 16dp; - color: #ffffff; + font-family: var(--font-family-monospace); + font-size: var(--font-size-md); + color: var(--color-white); white-space: nowrap; } diff --git a/res/rml/popover.rcss b/res/rml/popover.rcss index 1db4f64a05..d7074b7ac3 100644 --- a/res/rml/popover.rcss +++ b/res/rml/popover.rcss @@ -3,6 +3,8 @@ } body { + --button-background: rgba(var(--color-control-rgb), 35%); + width: 100%; height: 100%; z-index: 10; @@ -12,12 +14,13 @@ popover { position: absolute; display: flex; flex-flow: column; - font-family: "Fira Sans"; - font-size: 14dp; - color: #E0DBC8; - border-radius: 14dp; - border: 2dp #92875B; - background-color: rgba(21, 22, 16, 96%); + font-family: var(--font-family-body); + font-size: var(--font-size-sm); + color: var(--color-text); + border-radius: var(--radius-window); + border-width: 2dp; + border-color: var(--color-border); + background-color: rgba(var(--color-surface-rgb), 96%); backdrop-filter: blur(5dp); box-shadow: 0 6dp 24dp 2dp rgba(0, 0, 0, 55%); filter: opacity(0); @@ -43,7 +46,7 @@ color-sv { position: relative; width: 240dp; height: 150dp; - border-radius: 8dp; + border-radius: var(--radius-panel); box-shadow: rgba(146, 135, 91, 50%) 0 0 0 1dp; drag: drag; focus: auto; @@ -55,7 +58,7 @@ color-alpha { position: relative; width: 240dp; height: 14dp; - border-radius: 7dp; + border-radius: var(--radius-control); box-shadow: rgba(146, 135, 91, 50%) 0 0 0 1dp; drag: drag; focus: auto; @@ -82,20 +85,21 @@ color-cursor { position: absolute; width: 14dp; height: 14dp; - border-radius: 7dp; - border: 2dp #ffffff; + border-radius: var(--radius-control); + border-width: 2dp; + border-color: var(--color-white); box-shadow: 0 0 4dp 1dp rgba(0, 0, 0, 70%); pointer-events: none; } color-heading { display: block; - margin-top: 2dp; - font-family: "Fira Sans Condensed"; + margin-top: var(--space-2xs); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 13dp; + font-size: var(--font-size-xs); text-transform: uppercase; - color: rgba(224, 219, 200, 55%); + color: rgba(var(--color-text-rgb), 55%); } color-presets { @@ -114,7 +118,7 @@ button.color-swatch-button { width: 25dp; height: 25dp; padding: 0; - border-radius: 7dp; + border-radius: var(--radius-control); } color-chip { @@ -129,56 +133,56 @@ color-chip { button.color-swatch-button color-chip { width: 25dp; height: 25dp; - border-radius: 7dp; + border-radius: var(--radius-control); } color-chip.empty, color-swatch.empty { - background-color: rgba(224, 219, 200, 12%); - decorator: linear-gradient(135deg, rgba(224, 219, 200, 0) 45%, - rgba(194, 164, 45, 70%) 48%, rgba(194, 164, 45, 70%) 52%, - rgba(224, 219, 200, 0) 55%); + background-color: rgba(var(--color-text-rgb), 12%); + decorator: linear-gradient(135deg, rgba(var(--color-text-rgb), 0) 45%, + rgba(var(--color-accent-rgb), 70%) 48%, rgba(var(--color-accent-rgb), 70%) 52%, + rgba(var(--color-text-rgb), 0) 55%); } color-footer { display: flex; align-items: center; gap: 6dp; - padding-top: 2dp; + padding-top: var(--space-2xs); } button { - background-color: rgba(17, 16, 10, 35%); - padding: 4dp 8dp; - border-radius: 8dp; + background-color: var(--button-background); + padding: var(--space-xs) var(--space-sm); + border-radius: var(--radius-panel); box-shadow: rgba(146, 135, 91, 30%) 0 0 0 1dp; - color: #E0DBC8; + color: var(--button-color); cursor: pointer; focus: auto; } button:hover, button:focus-visible { - background-color: rgba(204, 184, 119, 20%); + background-color: var(--button-background-hover); box-shadow: #C2A42D 0 0 0 2dp; } button:active { - background-color: rgba(204, 184, 119, 40%); + background-color: var(--button-background-active); } color-value { display: block; flex: 1 1 auto; text-align: right; - font-family: "Noto Mono"; - font-size: 12dp; - color: #FFFFFF; + font-family: var(--font-family-monospace); + font-size: var(--font-size-2xs); + color: var(--color-white); cursor: pointer; focus: auto; } color-value:hover, color-value:focus-visible { - color: #C2A42D; + color: var(--color-accent); } diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss index effc80344d..1ed1652ecd 100644 --- a/res/rml/popup.rcss +++ b/res/rml/popup.rcss @@ -8,10 +8,10 @@ body { height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; - color: #E0DBC8; + font-size: var(--font-size-xl); + color: var(--color-text); } button { @@ -23,11 +23,12 @@ popup { width: 100%; display: flex; align-items: stretch; - height: 64dp; - background-color: rgba(21, 22, 16, 80%); - border-bottom: 2dp #92875B; + height: var(--toolbar-height); + background-color: rgba(var(--color-surface-rgb), 80%); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); backdrop-filter: blur(5dp); - transform: translateY(-64dp); + transform: translateY(var(--toolbar-hidden-offset)); transition: transform 0.2s cubic-in-out; } @@ -41,5 +42,5 @@ popup tab-bar { popup tab-bar tab { opacity: 0.35; - color: #E0DBC8; + color: var(--color-text); } diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss index 6cc5da65ae..bebba20a50 100644 --- a/res/rml/prelaunch.rcss +++ b/res/rml/prelaunch.rcss @@ -1,32 +1,38 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --color-prelaunch-accent: #FEE685; + --color-prelaunch-muted: #A6A09B; + --color-ready: #D8F999; + --menu-button-decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 0%)); + --menu-button-decorator-hover: horizontal-gradient(#FEE685FF #FEE68500); + width: 100%; height: 100%; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; - font-size: 20dp; - color: #FFFFFF; + font-size: var(--font-size-2xl); + color: var(--color-white); filter: opacity(0); transition: filter 1s 0.2s linear-in-out; z-index: -1; } -.gradient { +body.mirrored { + --menu-button-decorator-hover: horizontal-gradient(#FEE68500 #FEE685FF); +} + +prelaunch-gradient { position: absolute; width: 100%; height: 100%; /* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */ - decorator: horizontal-gradient(#000000FF #00000000); + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 100%) rgba(var(--color-black-rgb), 0%)); } -body.mirrored .gradient { - decorator: horizontal-gradient(#00000000 #000000FF); +body.mirrored prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 100%)); } -.background { +prelaunch-background { position: absolute; width: 100%; height: 100%; @@ -39,11 +45,11 @@ body[open] { filter: opacity(1); } -body[open] .background { +body[open] prelaunch-background { opacity: 1; } -body.disc-ready .background { +body.disc-ready prelaunch-background { opacity: 0; } @@ -84,7 +90,7 @@ hero { display: flex; flex-direction: column; align-items: flex-start; - gap: 4dp; + gap: var(--space-xs); } body.mirrored hero { @@ -96,8 +102,8 @@ hero img { } eyebrow { - font-family: "Alegreya SC"; - font-size: 32dp; + font-family: var(--font-family-display); + font-size: var(--font-size-6xl); } @media (min-width: 1216dp) { @@ -107,36 +113,36 @@ eyebrow { } } -eyebrow span { +eyebrow studio-name { font-weight: bold; } -#menu-list { +menu-list { display: flex; flex-direction: column; - gap: 12dp; + gap: var(--space-md); align-items: flex-start; } #menu-list button { width: 428dp; height: 54dp; - padding: 8dp 16dp; - border-radius: 8dp; + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-panel); text-align: left; text-transform: uppercase; - font-family: "Fira Sans Condensed"; - font-size: 32dp; + font-family: var(--font-family-heading); + font-size: var(--font-size-6xl); font-weight: normal; cursor: pointer; /* Define a fully transparent gradient as the default state, otherwise a white flash occurs */ - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } #menu-list button:disabled { opacity: 0.75; cursor: default; - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } #menu-list button.anim-done { @@ -157,7 +163,7 @@ eyebrow span { width: 100%; height: 100%; overflow: hidden; - border-radius: 8dp; + border-radius: var(--radius-panel); pointer-events: none; z-index: 0; } @@ -169,7 +175,7 @@ eyebrow span { left: 0; width: 100%; height: 100%; - padding: 8dp 16dp; + padding: var(--space-sm) var(--space-lg); opacity: 0; text-overflow: ellipsis; white-space: nowrap; @@ -189,8 +195,8 @@ eyebrow span { height: 54dp; align-items: center; justify-content: center; - color: #FFFFFF; - font-family: "Material Symbols Rounded"; + color: var(--color-white); + font-family: var(--font-family-icons); font-weight: normal; font-size: 30dp; z-index: 1; @@ -213,8 +219,8 @@ eyebrow span { #menu-list button:hover, #menu-list button:focus-visible { - color: black; - decorator: horizontal-gradient(#FEE685FF #FEE68500); + color: var(--color-black); + decorator: var(--menu-button-decorator-hover); } body.mirrored #menu-list { @@ -225,11 +231,6 @@ body.mirrored #menu-list button { text-align: right; } -body.mirrored #menu-list button:hover, -body.mirrored #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE68500 #FEE685FF); -} - disc-info { position: absolute; left: 96dp; @@ -237,8 +238,8 @@ disc-info { bottom: 72dp; display: flex; flex-direction: column; - gap: 12dp; - font-size: 24dp; + gap: var(--space-md); + font-size: var(--font-size-4xl); font-effect: glow(0dp 4dp 0dp 4dp black); text-align: left; } @@ -256,9 +257,9 @@ version-info { bottom: 72dp; display: flex; flex-direction: column; - gap: 12dp; + gap: var(--space-md); text-align: right; - font-size: 24dp; + font-size: var(--font-size-4xl); font-effect: glow(0dp 4dp 0dp 4dp black); text-align: right; } @@ -272,11 +273,11 @@ body.mirrored version-info { #disc-status { display: flex; align-items: center; - gap: 8dp; + gap: var(--space-sm); } #disc-status[status=good] { - color: #D8F999; + color: var(--color-ready); } #disc-status[status=bad] { @@ -284,7 +285,7 @@ body.mirrored version-info { } #disc-status[status=verifying] { - color: #FFFFFF; + color: var(--color-white); } #disc-status[status=mismatch] { @@ -292,20 +293,20 @@ body.mirrored version-info { } #disc-status[status=unknown] { - color: rgba(224, 219, 200, 65%); + color: rgba(var(--color-text-rgb), 65%); } #disc-status[status=pending] { - color: #FEE685; + color: var(--color-prelaunch-accent); } #disc-status icon { display: none; width: 24dp; height: 24dp; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; - font-size: 24dp; + font-size: var(--font-size-4xl); } #disc-status[status] icon { @@ -337,24 +338,24 @@ body.mirrored version-info { } #disc-version { - font-size: 20dp; + font-size: var(--font-size-2xl); } -.update { +update-status { display: none; - color: #A6A09B; + color: var(--color-prelaunch-muted); align-items: center; justify-content: flex-end; - gap: 8dp; - font-size: 20dp; + gap: var(--space-sm); + font-size: var(--font-size-2xl); } -.update[state=checking], -.update[state=failed] { +update-status[state=checking], +update-status[state=failed] { display: block; } -.update[state=available] { +update-status[state=available] { display: flex; } @@ -364,33 +365,33 @@ body.mirrored version-info { padding: 0dp; border-width: 0dp; background-color: transparent; - color: #D8F999; + color: var(--color-ready); cursor: pointer; text-transform: uppercase; font-weight: bold; - decorator: horizontal-gradient(#00000000 #00000000); + decorator: var(--menu-button-decorator); } -.update[state=available] #update-download { +update-status[state=available] #update-download { display: flex; align-items: center; - gap: 2dp; + gap: var(--space-2xs); } #update-download icon { display: block; width: 18dp; height: 18dp; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; decorator: text("" center center); } -.detail { - color: #A6A09B; +disc-version { + color: var(--color-prelaunch-muted); } -body.mirrored .update { +body.mirrored update-status { justify-content: flex-start; } @@ -436,12 +437,20 @@ body.animate-in .intro-item { /* Mobile layout */ @media (max-height: 640dp) { - .gradient { - decorator: horizontal-gradient(#00000000 #000000FF); + body { + --menu-button-decorator-hover: horizontal-gradient(#FEE68500 #FEE685FF); } - body.mirrored .gradient { - decorator: horizontal-gradient(#000000FF #00000000); + body.mirrored { + --menu-button-decorator-hover: horizontal-gradient(#FEE685FF #FEE68500); + } + + prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-black-rgb), 100%)); + } + + body.mirrored prelaunch-gradient { + decorator: horizontal-gradient(rgba(var(--color-black-rgb), 100%) rgba(var(--color-black-rgb), 0%)); } menu { @@ -453,7 +462,7 @@ body.animate-in .intro-item { flex-direction: row; align-items: center; justify-content: space-between; - gap: 16dp; + gap: var(--space-lg); } body.mirrored menu { @@ -466,7 +475,7 @@ body.animate-in .intro-item { flex: 1 1 0; min-width: 0; max-width: 48%; - margin-left: 32dp; + margin-left: var(--space-2xl); } body.mirrored hero { @@ -490,11 +499,6 @@ body.animate-in .intro-item { text-align: right; } - #menu-list button:hover, - #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE68500 #FEE685FF); - } - body.mirrored #menu-list { align-items: flex-start; } @@ -503,11 +507,6 @@ body.animate-in .intro-item { text-align: left; } - body.mirrored #menu-list button:hover, - body.mirrored #menu-list button:focus-visible { - decorator: horizontal-gradient(#FEE685FF #FEE68500); - } - eyebrow { display: none; } @@ -518,8 +517,8 @@ body.animate-in .intro-item { bottom: 32dp; top: auto; text-align: right; - font-size: 16dp; - gap: 8dp; + font-size: var(--font-size-md); + gap: var(--space-sm); } #disc-status { @@ -527,11 +526,11 @@ body.animate-in .intro-item { } #disc-status icon { - font-size: 20dp; + font-size: var(--font-size-2xl); } #disc-version { - font-size: 16dp; + font-size: var(--font-size-md); } version-info { @@ -540,12 +539,12 @@ body.animate-in .intro-item { bottom: auto; top: 32dp; text-align: right; - font-size: 16dp; - gap: 8dp; + font-size: var(--font-size-md); + gap: var(--space-sm); } - .update { - font-size: 16dp; + update-status { + font-size: var(--font-size-md); } body.mirrored disc-info { diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss index 8f42dd84d5..c508a354d5 100644 --- a/res/rml/tabbing.rcss +++ b/res/rml/tabbing.rcss @@ -18,17 +18,18 @@ tab-bar scrollbarhorizontal sliderbar { tab-bar tab { flex: 0 0 auto; - padding: 0 24dp; - line-height: 64dp; + padding: 0 var(--space-xl); + line-height: var(--toolbar-height); white-space: nowrap; - decorator: vertical-gradient(#c2a42d00 #c2a42d00); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0%) rgba(var(--color-accent-rgb), 0%)); 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; + border-bottom-width: 4dp; + border-bottom-color: var(--color-accent); font-effect: glow(0dp 4dp 0dp 4dp black); } @@ -36,17 +37,17 @@ tab-bar tab:focus-visible, tab-bar tab:hover { opacity: 1; font-effect: glow(0dp 4dp 0dp 4dp black); - decorator: vertical-gradient(#c2a42d00 #c2a42d26); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 0) rgba(var(--color-accent-rgb), 38)); } tab-bar tab:active { - decorator: vertical-gradient(#c2a42d10 #c2a42d40); + decorator: vertical-gradient(rgba(var(--color-accent-rgb), 16) rgba(var(--color-accent-rgb), 64)); } tab-bar[closable] tab-end-spacer { display: block; - flex: 0 0 64dp; - width: 64dp; + flex: 0 0 var(--toolbar-height); + width: var(--toolbar-height); pointer-events: none; } @@ -59,10 +60,10 @@ window > close { z-index: 1; width: 48dp; height: 48dp; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; - font-size: 24dp; - color: rgba(224, 219, 200, 70%); + font-size: var(--font-size-4xl); + color: rgba(var(--color-text-rgb), 70%); backdrop-filter: blur(2dp); border-radius: 6dp; decorator: text("" center center); @@ -74,8 +75,8 @@ tab-bar[closable] close:hover, tab-bar[closable] close:focus-visible, window > close:hover, window > close:focus-visible { - color: #fff; - background-color: rgba(194, 164, 45, 24%); + color: var(--color-white); + background-color: rgba(var(--color-accent-rgb), 24%); } window > close { @@ -85,6 +86,6 @@ window > close { tab-bar[closable] close:active, window > close:active { - color: #fff; - background-color: rgba(194, 164, 45, 40%); + color: var(--color-white); + background-color: rgba(var(--color-accent-rgb), 40%); } diff --git a/res/rml/theme.rcss b/res/rml/theme.rcss new file mode 100644 index 0000000000..29ae6a5e4d --- /dev/null +++ b/res/rml/theme.rcss @@ -0,0 +1,72 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + --font-family-body: Fira Sans; + --font-family-heading: Fira Sans Condensed; + --font-family-monospace: Noto Mono; + --font-family-icons: Material Symbols Rounded; + --font-family-display: Alegreya SC; + + --font-size-3xs: 11dp; + --font-size-2xs: 12dp; + --font-size-xs: 13dp; + --font-size-sm: 14dp; + --font-size-base: 15dp; + --font-size-md: 16dp; + --font-size-lg: 17dp; + --font-size-xl: 18dp; + --font-size-2xl: 20dp; + --font-size-3xl: 22dp; + --font-size-4xl: 24dp; + --font-size-5xl: 28dp; + --font-size-6xl: 32dp; + + --space-2xs: 2dp; + --space-xs: 4dp; + --space-sm: 8dp; + --space-md: 12dp; + --space-lg: 16dp; + --space-xl: 24dp; + --space-2xl: 32dp; + + --radius-small: 4dp; + --radius-control: 7dp; + --radius-panel: 8dp; + --radius-window: 14dp; + + --toolbar-height: 64dp; + --toolbar-hidden-offset: -64dp; + + --color-text-rgb: 224, 219, 200; + --color-text: rgb(var(--color-text-rgb)); + --color-accent-rgb: 194, 164, 45; + --color-accent: rgb(var(--color-accent-rgb)); + --color-border-rgb: 146, 135, 91; + --color-border: rgb(var(--color-border-rgb)); + --color-surface-rgb: 21, 22, 16; + --color-interactive-rgb: 204, 184, 119; + --color-control-rgb: 17, 16, 10; + --color-neutral-rgb: 217, 217, 217; + --color-neutral: rgb(var(--color-neutral-rgb)); + --color-white-rgb: 255, 255, 255; + --color-white: rgb(var(--color-white-rgb)); + --color-black-rgb: 0, 0, 0; + --color-black: rgb(var(--color-black-rgb)); + + --color-success: #44cc55; + --color-info-rgb: 111, 183, 239; + --color-info: rgb(var(--color-info-rgb)); + --color-warning: #ffa826; + --color-error: #cc4444; + + --button-color: var(--color-text); + --button-background: rgba(var(--color-control-rgb), 20%); + --button-background-hover: rgba(var(--color-interactive-rgb), 20%); + --button-background-selected: rgba(var(--color-interactive-rgb), 40%); + --button-background-active: rgba(var(--color-interactive-rgb), 40%); + + --toast-border-color: var(--color-border); + --toast-heading-color: var(--color-border); +} diff --git a/res/rml/touch_controls.rcss b/res/rml/touch_controls.rcss index 4c8057d6c0..33ad894fa1 100644 --- a/res/rml/touch_controls.rcss +++ b/res/rml/touch_controls.rcss @@ -1,16 +1,18 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { + --button-color: rgba(248, 244, 232, 90%); + --button-background: rgba(22, 24, 28, 48%); + --button-background-active: rgba(63, 78, 90, 68%); + --button-border-color: rgba(var(--color-white-rgb), 22%); + --button-border-color-active: rgba(var(--color-white-rgb), 48%); + width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - color: rgba(248, 244, 232, 90%); + color: var(--button-color); z-index: 1; filter: opacity(0); transition: filter 0.2s linear-in-out; @@ -30,9 +32,10 @@ button { justify-content: center; decorator: none; padding: 0; - border: 1dp rgba(255, 255, 255, 22%); - background-color: rgba(22, 24, 28, 48%); - color: rgba(248, 244, 232, 90%); + border-width: 1dp; + border-color: var(--button-border-color); + background-color: var(--button-background); + color: var(--button-color); text-align: center; /* backdrop-filter: blur(7dp); */ /* box-shadow: 0 6dp 18dp rgba(0, 0, 0, 28%); */ @@ -43,8 +46,8 @@ button { button.pressed, button.active { - background-color: rgba(63, 78, 90, 68%); - border-color: rgba(255, 255, 255, 48%); + background-color: var(--button-background-active); + border-color: var(--button-border-color-active); filter: brightness(1.18); } @@ -68,9 +71,9 @@ button icon { button icon glyph { display: block; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; - font-size: 24dp; + font-size: var(--font-size-4xl); line-height: 1; } @@ -92,9 +95,9 @@ button icon glyph { position: absolute; } -.trigger-l.active { - background-color: rgba(57, 116, 133, 74%); - border-color: rgba(128, 222, 234, 72%); +.trigger-l { + --button-background-active: rgba(57, 116, 133, 74%); + --button-border-color-active: rgba(128, 222, 234, 72%); } .trigger, @@ -103,12 +106,14 @@ button icon glyph { } .trigger { - font-size: 22dp; + font-size: var(--font-size-3xl); } .button-z { - background-color: rgba(118, 79, 158, 58%); - border-color: rgba(203, 170, 255, 36%); + --button-background: rgba(118, 79, 158, 58%); + --button-background-active: rgba(139, 91, 187, 82%); + --button-border-color: rgba(203, 170, 255, 36%); + --button-border-color-active: rgba(220, 194, 255, 70%); } .midna-icon { @@ -121,7 +126,7 @@ button icon glyph { .button-z.has-icon span, .face.has-item span { position: absolute; - font-size: 13dp; + font-size: var(--font-size-xs); line-height: 1; } @@ -130,18 +135,14 @@ button icon glyph { bottom: 7dp; } -.button-z.pressed { - background-color: rgba(139, 91, 187, 82%); - border-color: rgba(220, 194, 255, 70%); -} - action-bar { position: absolute; display: flex; align-items: center; - border: 1dp rgba(255, 255, 255, 22%); + border-width: 1dp; + border-color: var(--button-border-color); border-radius: 23dp; - background-color: rgba(22, 24, 28, 48%); + background-color: var(--button-background); /* backdrop-filter: blur(7dp); */ /* box-shadow: 0 -6dp 18dp rgba(0, 0, 0, 28%); */ overflow: hidden; @@ -175,7 +176,7 @@ action-bar:hidden separator { } .utility.pressed { - background-color: rgba(63, 78, 90, 68%); + background-color: var(--button-background-active); } .utility.pressed, @@ -185,7 +186,7 @@ action-bar:hidden separator { .skip { z-index: 1; - border-color: rgba(255, 255, 255, 36%); + border-color: rgba(var(--color-white-rgb), 36%); } separator { @@ -193,7 +194,7 @@ separator { flex: 0 0 1dp; width: 1dp; height: 24dp; - background-color: rgba(255, 255, 255, 18%); + background-color: rgba(var(--color-white-rgb), 18%); opacity: 1; transition: opacity 0.2s linear-in-out; } @@ -201,7 +202,7 @@ separator { .face { position: absolute; border-radius: 29dp; - font-size: 24dp; + font-size: var(--font-size-4xl); overflow: visible; } @@ -219,10 +220,10 @@ separator { min-width: 17dp; height: 15dp; padding: 1dp 3dp; - border-radius: 7dp; - background-color: rgba(0, 0, 0, 52%); - color: rgba(255, 255, 255, 92%); - font-size: 12dp; + border-radius: var(--radius-control); + background-color: rgba(var(--color-black-rgb), 52%); + color: rgba(var(--color-white-rgb), 92%); + font-size: var(--font-size-2xs); line-height: 13dp; text-align: center; } @@ -233,9 +234,9 @@ separator { bottom: -5dp; width: 34dp; height: 8dp; - padding: 2dp; + padding: var(--space-2xs); border: 1dp rgba(42, 32, 18, 82%); - border-radius: 4dp; + border-radius: var(--radius-small); background-color: rgba(18, 14, 10, 70%); /* box-shadow: 0 2dp 6dp rgba(0, 0, 0, 35%); */ } @@ -251,7 +252,7 @@ oil-fill { .face.has-item span { right: 6dp; bottom: 6dp; - color: rgba(255, 255, 255, 88%); + color: rgba(var(--color-white-rgb), 88%); } .face.a { @@ -307,7 +308,8 @@ touch-stick { height: 124dp; border-radius: 62dp; background-color: rgba(18, 20, 24, 35%); - border: 1dp rgba(255, 255, 255, 20%); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 20%); /* backdrop-filter: blur(7dp); */ /* box-shadow: 0 8dp 24dp rgba(0, 0, 0, 24%); */ opacity: 0; @@ -326,7 +328,8 @@ stick-ring { width: 88dp; height: 88dp; border-radius: 44dp; - border: 1dp rgba(255, 255, 255, 18%); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 18%); } stick-knob { @@ -335,5 +338,6 @@ stick-knob { height: 48dp; border-radius: 24dp; background-color: rgba(238, 236, 226, 55%); - border: 1dp rgba(255, 255, 255, 45%); + border-width: 1dp; + border-color: rgba(var(--color-white-rgb), 45%); } diff --git a/res/rml/touch_controls_editor.rcss b/res/rml/touch_controls_editor.rcss index 2ca99d935a..6b27118cb6 100644 --- a/res/rml/touch_controls_editor.rcss +++ b/res/rml/touch_controls_editor.rcss @@ -1,4 +1,7 @@ body.touch-editor { + --color-editor-accent-rgb: 255, 232, 128; + --color-editor-highlight-rgb: 255, 244, 190; + background-color: rgba(4, 6, 8, 34%); z-index: 8; } @@ -14,7 +17,7 @@ body.touch-editor .control:hover, body.touch-editor action-bar:hover, body.touch-editor .control.editor-selected, body.touch-editor action-bar.editor-selected { - border-color: rgba(255, 232, 128, 80%); + border-color: rgba(var(--color-editor-accent-rgb), 80%); filter: brightness(1.15); } @@ -27,8 +30,9 @@ selection-frame { display: none; position: absolute; z-index: 20; - border: 2dp rgba(255, 232, 128, 88%); - background-color: rgba(255, 232, 128, 7%); + border-width: 2dp; + border-color: rgba(var(--color-editor-accent-rgb), 88%); + background-color: rgba(var(--color-editor-accent-rgb), 7%); pointer-events: none; } @@ -41,7 +45,8 @@ resize-handle { position: absolute; width: 22dp; height: 22dp; - border: 2dp rgba(255, 244, 190, 96%); + border-width: 2dp; + border-color: rgba(var(--color-editor-highlight-rgb), 96%); border-radius: 11dp; background-color: rgba(34, 37, 42, 86%); pointer-events: auto; @@ -90,6 +95,12 @@ resize-handle.corner.bottom { } editor-toolbar { + --button-background: rgba(17, 19, 24, 88%); + --button-background-hover: rgba(78, 85, 96, 92%); + --button-border-color: rgba(var(--color-white-rgb), 26%); + --button-border-color-hover: rgba(var(--color-editor-highlight-rgb), 92%); + --button-color: rgba(255, 250, 232, 94%); + display: flex; position: absolute; left: 24dp; @@ -98,7 +109,7 @@ editor-toolbar { z-index: 30; height: 48dp; margin-top: -24dp; - gap: 8dp; + gap: var(--space-sm); justify-content: center; pointer-events: auto; } @@ -108,12 +119,13 @@ editor-toolbar button.editor-command { min-width: 96dp; height: 48dp; padding: 0 14dp; - border-radius: 8dp; - border: 1dp rgba(255, 255, 255, 26%); - background-color: rgba(17, 19, 24, 88%); - color: rgba(255, 250, 232, 94%); - font-family: "Fira Sans"; - font-size: 18dp; + border-radius: var(--radius-panel); + border-width: 1dp; + border-color: var(--button-border-color); + background-color: var(--button-background); + color: var(--button-color); + font-family: var(--font-family-body); + font-size: var(--font-size-xl); line-height: 48dp; opacity: 1; cursor: pointer; @@ -127,12 +139,12 @@ editor-toolbar button.editor-command span { } editor-toolbar button.editor-command.primary { - border-color: rgba(255, 232, 128, 70%); - background-color: rgba(96, 82, 38, 90%); + --button-border-color: rgba(var(--color-editor-accent-rgb), 70%); + --button-background: rgba(96, 82, 38, 90%); } editor-toolbar button.editor-command:hover, editor-toolbar button.editor-command:focus-visible { - border-color: rgba(255, 244, 190, 92%); - background-color: rgba(78, 85, 96, 92%); + border-color: var(--button-border-color-hover); + background-color: var(--button-background-hover); } diff --git a/res/rml/tuner.rcss b/res/rml/tuner.rcss index 86dd2043f6..5e0d96e855 100644 --- a/res/rml/tuner.rcss +++ b/res/rml/tuner.rcss @@ -1,90 +1,87 @@ -*, *: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; + font-family: var(--font-family-heading); + font-size: var(--font-size-4xl); + color: var(--color-white); display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch; } -.tuner-root { +tuner-root { width: 100%; min-height: 45%; display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch; - decorator: vertical-gradient(#00000000 #151610F2); + decorator: vertical-gradient(rgba(var(--color-black-rgb), 0%) rgba(var(--color-surface-rgb), 242)); filter: opacity(0); transition: filter 0.2s linear-in-out; } -.tuner-root[open] { +tuner-root[open] { filter: opacity(1); } -.tuner { +graphics-tuner { width: 100%; max-width: 1216dp; margin-left: auto; margin-right: auto; display: flex; flex-direction: column; - gap: 24dp; + gap: var(--space-xl); padding: 48dp 64dp; } @media (max-height: 800dp) { - .tuner-root { + tuner-root { min-height: 38%; } - .tuner { - gap: 16dp; - padding: 32dp 48dp; + graphics-tuner { + gap: var(--space-lg); + padding: var(--space-2xl) 48dp; } } -.header { +tuner-header { display: flex; justify-content: space-between; align-items: center; - gap: 24dp; + gap: var(--space-xl); } -.carousel-container { +carousel-container { flex: 1 1 auto; display: flex; justify-content: flex-end; min-width: 0; } -.description { - font-size: 18dp; +tuner-description { + font-size: var(--font-size-xl); line-height: 22dp; - color: rgba(255, 255, 255, 50%); + color: rgba(var(--color-white-rgb), 50%); } -.divider { +tuner-divider { margin: 1dp 0; - border-top: 1dp rgba(217, 217, 217, 50%); + border-top-width: 1dp; + border-top-color: rgba(var(--color-neutral-rgb), 50%); } -.footer { +tuner-footer { display: flex; justify-content: space-between; align-items: center; - gap: 24dp; + gap: var(--space-xl); } footer-button { @@ -94,12 +91,12 @@ footer-button { border: 0; padding: 0; background-color: transparent; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - font-size: 20dp; + font-size: var(--font-size-2xl); line-height: 24dp; text-transform: uppercase; - color: #FFFFFF; + color: var(--color-white); opacity: 1; cursor: pointer; } @@ -112,20 +109,20 @@ footer-button.reset { text-align: right; } -.stepped-carousel { +stepped-carousel { display: flex; align-items: center; justify-content: center; - gap: 16dp; + gap: var(--space-lg); width: auto; min-width: 246dp; padding: 0; background-color: transparent; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; } -.stepped-carousel-value { +stepped-carousel-value { line-height: 29dp; min-width: 166dp; text-align: center; @@ -142,6 +139,6 @@ footer-button.reset { background-color: transparent; opacity: 1; cursor: pointer; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; } diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 98b63dfcd9..c652396726 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -1,17 +1,13 @@ -*, *:before, *:after { - box-sizing: border-box; -} - body { display: flex; width: 100%; height: 100%; padding: 64dp; - font-family: "Fira Sans"; + font-family: var(--font-family-body); font-weight: normal; font-style: normal; - font-size: 15dp; - color: #E0DBC8; + font-size: var(--font-size-base); + color: var(--color-text); } b { @@ -27,12 +23,13 @@ window { max-width: 1088dp; max-height: 768dp; margin: auto; - border-radius: 14dp; + border-radius: var(--radius-window); overflow: hidden; - border: 2dp #92875B; + border-width: 2dp; + border-color: var(--color-border); backdrop-filter: blur(5dp); box-shadow: 0 0 25dp 5dp; - background-color: rgba(21, 22, 16, 90%); + background-color: rgba(var(--color-surface-rgb), 90%); filter: opacity(0); transform: scale(0.9); transform-origin: center; @@ -61,7 +58,7 @@ window[open] { @media (max-height: 640dp) { body { - padding: 16dp; + padding: var(--space-lg); } window { box-shadow: none; @@ -70,7 +67,7 @@ window[open] { @media (max-width: 768dp) { body { - padding: 16dp; + padding: var(--space-lg); } window.modal { width: 100%; @@ -79,13 +76,14 @@ window[open] { } window tab-bar { - flex: 0 0 64dp; - height: 64dp; - background-color: rgba(217, 217, 217, 10%); - font-family: "Fira Sans Condensed"; + flex: 0 0 var(--toolbar-height); + height: var(--toolbar-height); + background-color: rgba(var(--color-neutral-rgb), 10%); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; - border-bottom: 2dp #92875B; + font-size: var(--font-size-xl); + border-bottom-width: 2dp; + border-bottom-color: var(--color-border); } window tab-bar tab { @@ -106,14 +104,15 @@ window content pane { flex: 1 1 0; min-width: 0; min-height: 0; - padding: 24dp; - gap: 8dp; + padding: var(--space-xl); + gap: var(--space-sm); overflow: hidden auto; - font-size: 20dp; + font-size: var(--font-size-2xl); } window content pane:not(:last-of-type) { - border-right: 1dp #92875B; + border-right-width: 1dp; + border-right-color: var(--color-border); } window content pane > * { @@ -129,7 +128,7 @@ ui-list { } window content pane > ui-list, -.modal-content pane > ui-list { +modal-content pane > ui-list { flex: 1 1 0; min-width: 0; min-height: 0; @@ -146,7 +145,7 @@ ui-list-viewport { ui-list-content { display: flex; flex-flow: column; - gap: 8dp; + gap: var(--space-sm); min-width: 0; } @@ -156,7 +155,7 @@ ui-list-content > button.ui-list-row { ui-list-empty { display: block; - padding: 16dp; + padding: var(--space-lg); text-align: center; opacity: 0.45; } @@ -176,33 +175,33 @@ window content pane > ui-list:last-child { window content pane > ui-list ui-list-content, window content pane > ui-list ui-list-empty { - padding-left: 24dp; - padding-right: 24dp; + padding-left: var(--space-xl); + padding-right: var(--space-xl); } window content pane > ui-list:first-child ui-list-content, window content pane > ui-list:first-child ui-list-empty { - padding-top: 24dp; + padding-top: var(--space-xl); } window content pane > ui-list:last-child ui-list-content, window content pane > ui-list:last-child ui-list-empty { - padding-bottom: 24dp; + padding-bottom: var(--space-xl); } window content pane:last-of-type > div { line-height: 1.625; } -.data-folder-current { +data-folder-current { display: block; - font-size: 16dp; - color: rgba(224, 219, 200, 65%); + font-size: var(--font-size-md); + color: rgba(var(--color-text-rgb), 65%); } scrollbarvertical { width: 8dp; - margin: 4dp 4dp 4dp 0; + margin: var(--space-xs) var(--space-xs) var(--space-xs) 0; } scrollbarvertical sliderarrowdec, @@ -218,14 +217,14 @@ scrollbarvertical slidertrack { scrollbarvertical sliderbar { width: 8dp; min-height: 24dp; - background-color: rgba(224, 219, 200, 45%); + background-color: rgba(var(--color-text-rgb), 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%); + background-color: rgba(var(--color-accent-rgb), 80%); } scrollbarhorizontal { @@ -244,26 +243,27 @@ scrollbarhorizontal sliderbar { height: 0; } -.section-heading { - font-family: "Fira Sans Condensed"; +section-heading { + font-family: var(--font-family-heading); font-weight: bold; text-transform: uppercase; - font-size: 22dp; + font-size: var(--font-size-3xl); opacity: 0.25; } -.section-heading:not(:first-of-type) { - padding-top: 12dp; +section-heading:not(:first-of-type) { + padding-top: var(--space-md); } button { text-align: center; - background-color: rgba(17, 16, 10, 20%); + color: var(--button-color); + background-color: var(--button-background); opacity: 0.9; - padding: 8dp 16dp; - border-radius: 14dp; + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-window); box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; - font-size: 20dp; + font-size: var(--font-size-2xl); transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; cursor: pointer; focus: auto; @@ -271,18 +271,18 @@ button { button:not(:disabled):hover, button:not(:disabled):focus-visible { - background-color: rgba(204, 184, 119, 20%); + background-color: var(--button-background-hover); box-shadow: #C2A42D 0 0 0 2dp; } button:not(:disabled):selected { opacity: 1; - background-color: rgba(204, 184, 119, 40%); + background-color: var(--button-background-selected); } button:not(:disabled):active { opacity: 1; - background-color: rgba(204, 184, 119, 40%); + background-color: var(--button-background-active); box-shadow: #C2A42D 0 0 0 2dp; } @@ -299,11 +299,12 @@ button.modal-btn { select-button { display: flex; align-items: center; - gap: 8dp; - background-color: rgba(17, 16, 10, 20%); + gap: var(--space-sm); + color: var(--button-color); + background-color: var(--button-background); opacity: 0.9; - padding: 8dp 16dp; - border-radius: 14dp; + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-window); 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; @@ -312,18 +313,18 @@ select-button { select-button:not(:disabled):hover, select-button:not(:disabled):focus-visible { - background-color: rgba(204, 184, 119, 20%); + background-color: var(--button-background-hover); box-shadow: #C2A42D 0 0 0 2dp; } select-button:not(:disabled):selected { opacity: 1; - background-color: rgba(204, 184, 119, 40%); + background-color: var(--button-background-selected); } select-button:not(:disabled):active { opacity: 1; - background-color: rgba(204, 184, 119, 40%); + background-color: var(--button-background-active); box-shadow: #C2A42D 0 0 0 2dp; } @@ -333,9 +334,9 @@ select-button:disabled { } select-button key { - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; - font-size: 18dp; + font-size: var(--font-size-xl); text-transform: uppercase; flex: 0 1 auto; } @@ -343,7 +344,7 @@ select-button key { select-button value { flex: 1 1 auto; text-align: right; - font-size: 20dp; + font-size: var(--font-size-2xl); } select-button value.modified { @@ -352,7 +353,7 @@ select-button value.modified { select-button input { text-align: right; - font-size: 20dp; + font-size: var(--font-size-2xl); } select-button.group-button icon { @@ -360,7 +361,7 @@ select-button.group-button icon { margin-left: auto; width: 24dp; height: 24dp; - font-size: 24dp; + font-size: var(--font-size-4xl); color: inherit; decorator: text("" center center); } @@ -371,8 +372,8 @@ select-button.group-button value { select-button.color-input value { min-width: 0; - font-family: "Noto Mono"; - font-size: 15dp; + font-family: var(--font-family-monospace); + font-size: var(--font-size-base); } select-button.color-input color-swatch { @@ -380,19 +381,19 @@ select-button.color-input color-swatch { flex: 0 0 48dp; width: 48dp; height: 24dp; - border-radius: 7dp; + border-radius: var(--radius-control); box-shadow: rgba(255, 255, 255, 45%) 0 0 0 1dp; } select-button.color-input color-swatch.empty { - background-color: rgba(224, 219, 200, 12%); - decorator: linear-gradient(135deg, rgba(224, 219, 200, 0) 45%, rgba(194, 164, 45, 70%) 48%, rgba(194, 164, 45, 70%) 52%, rgba(224, 219, 200, 0) 55%); + background-color: rgba(var(--color-text-rgb), 12%); + decorator: linear-gradient(135deg, rgba(var(--color-text-rgb), 0) 45%, rgba(var(--color-accent-rgb), 70%) 48%, rgba(var(--color-accent-rgb), 70%) 52%, rgba(var(--color-text-rgb), 0) 55%); } icon { width: 1em; height: 1em; - font-family: "Material Symbols Rounded"; + font-family: var(--font-family-icons); font-weight: normal; display: inline-block; vertical-align: middle; @@ -418,73 +419,74 @@ icon.question-mark { decorator: text("" center center); } -.achievement-total { +achievement-total { position: absolute; top: 0; - right: 64dp; - height: 64dp; - line-height: 64dp; - font-family: "Fira Sans Condensed"; + right: var(--toolbar-height); + height: var(--toolbar-height); + line-height: var(--toolbar-height); + font-family: var(--font-family-heading); font-weight: bold; - font-size: 16dp; - color: rgba(224, 219, 200, 55%); + font-size: var(--font-size-md); + color: rgba(var(--color-text-rgb), 55%); pointer-events: none; } -.achievement-row { +achievement-row { display: flex; align-items: flex-start; gap: 10dp; - padding: 12dp 0; - border-bottom: 1dp rgba(146, 135, 91, 30%); + padding: var(--space-md) 0; + border-bottom-width: 1dp; + border-bottom-color: rgba(var(--color-border-rgb), 30%); } -.achievement-info { +achievement-info { display: block; flex: 1 1 0; min-width: 0; } -.achievement-header { +achievement-header { display: flex; align-items: center; } -.achievement-name { +achievement-name { flex: 1; font-weight: bold; } -.achievement-name.unlocked { - color: #ffa826; +achievement-name.unlocked { + color: var(--color-warning); } -.achievement-badge { - font-size: 14dp; +achievement-badge { + font-size: var(--font-size-sm); opacity: 0.7; } -.achievement-badge.unlocked { - color: #44cc55; +achievement-badge.unlocked { + color: var(--color-success); opacity: 1; } -.achievement-badge.locked { - color: #cc4444; +achievement-badge.locked { + color: var(--color-error); opacity: 1; } .achievement-desc { display: block; - color: rgba(224, 219, 200, 55%); - font-size: 16dp; - margin: 4dp 0 0 0; + color: rgba(var(--color-text-rgb), 55%); + font-size: var(--font-size-md); + margin: var(--space-xs) 0 0 0; } -.achievement-progress { +achievement-progress { display: block; - font-size: 13dp; - color: rgba(224, 219, 200, 45%); + font-size: var(--font-size-xs); + color: rgba(var(--color-text-rgb), 45%); } progress { @@ -492,12 +494,12 @@ progress { width: 100%; height: 6dp; border-radius: 3dp; - background-color: rgba(255, 255, 255, 10%); - margin: 6dp 0 2dp 0; + background-color: rgba(var(--color-white-rgb), 10%); + margin: 6dp 0 var(--space-2xs) 0; } progress fill { - background-color: rgba(194, 164, 45, 80%); + background-color: rgba(var(--color-accent-rgb), 80%); border-radius: 3dp; } @@ -512,12 +514,12 @@ progress.progress-ongoing fill { button.achievement-clear { flex: 0 0 auto; align-self: center; - font-size: 14dp; - padding: 2dp 8dp; + font-size: var(--font-size-sm); + padding: var(--space-2xs) var(--space-sm); opacity: 0.45; } -.preset-grid { +preset-grid { display: flex; flex-direction: row; gap: 20dp; @@ -526,24 +528,24 @@ button.achievement-clear { width: 100%; } -.preset-col { +preset-option { display: flex; flex-flow: column; - gap: 12dp; + gap: var(--space-md); flex: 1 1 0; } -.preset-desc { +preset-description { display: block; - font-size: 16dp; + font-size: var(--font-size-md); text-align: center; } -.modal-dialog { +modal-dialog { display: flex; flex-direction: column; align-items: flex-start; - padding: 24dp; + padding: var(--space-xl); gap: 20dp; flex: 0 1 auto; min-height: 0; @@ -556,52 +558,52 @@ window.modal.danger { border: 2dp #852221; } -.modal-header { +modal-header { display: flex; flex-direction: row; align-items: center; justify-content: space-between; width: 100%; flex: 0 0 auto; - gap: 16dp; + gap: var(--space-lg); } -.modal-header icon { - font-size: 24dp; - color: #92875B; +modal-header icon { + font-size: var(--font-size-4xl); + color: var(--color-border); } -.modal-title { +modal-title { display: block; - font-family: "Fira Sans Condensed"; + font-family: var(--font-family-heading); font-weight: bold; text-transform: uppercase; - font-size: 18dp; - color: #92875B; + font-size: var(--font-size-xl); + color: var(--color-border); flex: 1 1 auto; } -window.modal.danger .modal-title, -window.modal.danger .modal-header icon { +window.modal.danger modal-title, +window.modal.danger modal-header icon { color: #B3261E; } -.modal-body { +modal-body { display: block; width: 100%; flex: 0 0 auto; min-width: 0; - font-size: 20dp; - color: #FFFFFF; + font-size: var(--font-size-2xl); + color: var(--color-white); font-weight: normal; } -.modal-body span.tip { - font-size: 14dp; - color: #92875B; +modal-body modal-tip { + font-size: var(--font-size-sm); + color: var(--color-border); } -.modal-content { +modal-content { display: none; width: 100%; flex: 1 1 auto; @@ -609,87 +611,87 @@ window.modal.danger .modal-header icon { overflow: hidden; } -.modal-content.active { +modal-content.active { display: flex; flex-direction: column; } -.modal-content pane { +modal-content pane { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; width: 100%; - gap: 8dp; + gap: var(--space-sm); overflow: hidden auto; } -.modal-content pane > * { +modal-content pane > * { flex: 0 0 auto; } -.verification-progress { +verification-progress { display: flex; flex-direction: column; gap: 10dp; width: 100%; } -.verification-file { +verification-file { display: block; - font-size: 17dp; - color: #FFFFFF; + font-size: var(--font-size-lg); + color: var(--color-white); } progress.verification-progress-bar { height: 8dp; - margin: 2dp 0 0 0; + margin: var(--space-2xs) 0 0 0; } -.verification-detail { +verification-detail { display: block; - font-size: 14dp; - color: rgba(224, 219, 200, 65%); + font-size: var(--font-size-sm); + color: rgba(var(--color-text-rgb), 65%); } -.modal-actions { +modal-actions { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: stretch; - gap: 12dp; + gap: var(--space-md); width: 100%; flex: 0 0 auto; - padding-top: 4dp; + padding-top: var(--space-xs); } -.modal-actions-vertical { +modal-actions.vertical { flex-direction: column; align-items: stretch; } -.modal-actions-vertical button.modal-btn { +modal-actions.vertical button.modal-btn { flex: 0 0 auto; width: 100%; } @media (max-height: 640dp) { - .modal-dialog { - padding: 16dp; - gap: 12dp; + modal-dialog { + padding: var(--space-lg); + gap: var(--space-md); } - .modal-body { - font-size: 17dp; + modal-body { + font-size: var(--font-size-lg); } } @media (max-width: 640dp) { - .modal-actions { + modal-actions { flex-direction: column; } - .modal-actions button.modal-btn { + modal-actions button.modal-btn { flex: 0 0 auto; width: 100%; } diff --git a/src/dusk/mods/catalog.cpp b/src/dusk/mods/catalog.cpp new file mode 100644 index 0000000000..bda411ca37 --- /dev/null +++ b/src/dusk/mods/catalog.cpp @@ -0,0 +1,456 @@ +#include "catalog.hpp" + +#include "dusk/app_info.hpp" +#include "fmt/format.h" +#include "nlohmann/json.hpp" + +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#endif + +namespace dusk::mods::catalog { +namespace { + +using json = nlohmann::json; +using namespace std::chrono_literals; + +constexpr std::string_view catalogUrl = + "https://staging.twilitrealm.workers.dev/api/v1/games/dusklight/mods"; + +std::string_view sort_value(Sort sort) noexcept { + switch (sort) { + case Sort::Endorsements: + return "endorsements"; + case Sort::Updated: + return "updated"; + case Sort::Newest: + return "newest"; + case Sort::Name: + return "name"; + case Sort::Downloads: + default: + return "downloads"; + } +} + +std::string_view catalog_platform() noexcept { +#if defined(_WIN32) && defined(_M_ARM64) + return "windows-arm64"; +#elif defined(_WIN32) && defined(_M_X64) + return "windows-amd64"; +#elif defined(__ANDROID__) && defined(__aarch64__) + return "android-aarch64"; +#elif defined(__APPLE__) && TARGET_OS_IOS + return "ios-arm64"; +#elif defined(__APPLE__) && !TARGET_OS_TV && defined(__aarch64__) + return "macos-arm64"; +#elif defined(__APPLE__) && !TARGET_OS_TV && defined(__x86_64__) + return "macos-x86_64"; +#elif defined(__linux__) && defined(__aarch64__) + return "linux-aarch64"; +#elif defined(__linux__) && defined(__x86_64__) + return "linux-x86_64"; +#else + // The catalog rejects platforms outside its published package matrix. + return {}; +#endif +} + +std::string url_encode(std::string_view value) { + constexpr char hex[] = "0123456789ABCDEF"; + std::string encoded; + encoded.reserve(value.size()); + for (const unsigned char c : value) { + const bool unreserved = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || + c == '~'; + if (unreserved) { + encoded.push_back(static_cast(c)); + } else { + encoded.push_back('%'); + encoded.push_back(hex[c >> 4]); + encoded.push_back(hex[c & 0x0f]); + } + } + return encoded; +} + +void append_query(std::string& url, std::string_view name, std::string_view value) { + url.push_back(url.find('?') == std::string::npos ? '?' : '&'); + url.append(name); + url.push_back('='); + url.append(url_encode(value)); +} + +std::string make_url(const Query& query) { + std::string url{catalogUrl}; + if (!query.search.empty()) { + append_query(url, "q", query.search); + } + if (!query.category.empty()) { + append_query(url, "category", query.category); + } + append_query(url, "sort", sort_value(query.sort)); + append_query(url, "page", fmt::format("{}", std::max(query.page, 1))); + if (query.thisDevice) { + const auto platform = catalog_platform(); + if (!platform.empty()) { + append_query(url, "platform", platform); + } + } + return url; +} + +std::string make_detail_url(std::string_view id) { + return fmt::format("{}/{}", catalogUrl, url_encode(id)); +} + +const json& required_field(const json& object, const char* name) { + if (!object.is_object()) { + throw std::runtime_error{"expected an object"}; + } + const auto iter = object.find(name); + if (iter == object.end()) { + throw std::runtime_error{fmt::format("missing field '{}'", name)}; + } + return *iter; +} + +std::string required_string(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (!value.is_string()) { + throw std::runtime_error{fmt::format("field '{}' is not a string", name)}; + } + return value.get(); +} + +bool required_bool(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (!value.is_boolean()) { + throw std::runtime_error{fmt::format("field '{}' is not a boolean", name)}; + } + return value.get(); +} + +std::uint64_t required_count(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (value.is_number_unsigned()) { + return value.get(); + } + if (value.is_number_integer()) { + const auto count = value.get(); + if (count >= 0) { + return static_cast(count); + } + } + throw std::runtime_error{fmt::format("field '{}' is not a non-negative integer", name)}; +} + +int required_int(const json& object, const char* name) { + const std::uint64_t value = required_count(object, name); + if (value > static_cast(std::numeric_limits::max())) { + throw std::runtime_error{fmt::format("field '{}' is too large", name)}; + } + return static_cast(value); +} + +std::optional optional_string(const json& object, const char* name) { + const auto& value = required_field(object, name); + if (value.is_null()) { + return std::nullopt; + } + if (!value.is_string()) { + throw std::runtime_error{fmt::format("field '{}' is not a string or null", name)}; + } + return value.get(); +} + +std::uint16_t required_u16(const json& object, const char* name) { + const auto value = required_count(object, name); + if (value > std::numeric_limits::max()) { + throw std::runtime_error{fmt::format("field '{}' is too large", name)}; + } + return static_cast(value); +} + +Image parse_image(const json& value) { + const auto width = required_count(value, "width"); + const auto height = required_count(value, "height"); + if (width > std::numeric_limits::max() || + height > std::numeric_limits::max()) + { + throw std::runtime_error{"image dimensions are too large"}; + } + Image image{ + .width = static_cast(width), + .height = static_cast(height), + }; + const auto& sources = required_field(value, "sources"); + if (!sources.is_array()) { + throw std::runtime_error{"field 'sources' is not an array"}; + } + image.sources.reserve(sources.size()); + for (const auto& source : sources) { + const auto sourceWidth = required_count(source, "width"); + if (sourceWidth > std::numeric_limits::max()) { + throw std::runtime_error{"image source width is too large"}; + } + image.sources.push_back({ + .width = static_cast(sourceWidth), + .pngUrl = required_string(source, "png_url"), + }); + } + if (image.sources.empty()) { + throw std::runtime_error{"image has no sources"}; + } + return image; +} + +Category parse_category(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + .modCount = required_count(value, "mod_count"), + }; +} + +Category parse_mod_category(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + }; +} + +Tag parse_tag(const json& value) { + return { + .slug = required_string(value, "slug"), + .name = required_string(value, "name"), + }; +} + +Author parse_author(const json& value) { + return { + .name = required_string(value, "name"), + .handle = required_string(value, "handle"), + .official = required_bool(value, "official"), + }; +} + +Mod parse_mod(const json& value) { + Mod mod{ + .id = required_string(value, "id"), + .name = required_string(value, "name"), + .version = required_string(value, "version"), + .author = parse_author(required_field(value, "author")), + .summary = required_string(value, "summary"), + .downloads = required_count(value, "downloads"), + .endorsements = required_count(value, "endorsements"), + .publishedAt = required_string(value, "published_at"), + .updatedAt = required_string(value, "updated_at"), + .packageSize = required_count(value, "package_size"), + .containsNativeCode = required_bool(value, "contains_native_code"), + }; + + const auto& category = required_field(value, "category"); + if (!category.is_null()) { + mod.category = parse_mod_category(category); + } + + const auto& tags = required_field(value, "tags"); + if (!tags.is_array()) { + throw std::runtime_error{"field 'tags' is not an array"}; + } + mod.tags.reserve(tags.size()); + for (const auto& tag : tags) { + mod.tags.push_back(parse_tag(tag)); + } + + const auto& platforms = required_field(value, "supported_platforms"); + if (!platforms.is_array()) { + throw std::runtime_error{"field 'supported_platforms' is not an array"}; + } + mod.supportedPlatforms.reserve(platforms.size()); + for (const auto& platform : platforms) { + if (!platform.is_string()) { + throw std::runtime_error{"supported platform is not a string"}; + } + mod.supportedPlatforms.push_back(platform.get()); + } + + const auto& icon = required_field(value, "icon"); + if (!icon.is_null()) { + mod.icon = parse_image(icon); + } + const auto& banner = required_field(value, "banner"); + if (!banner.is_null()) { + mod.banner = parse_image(banner); + } + return mod; +} + +Detail parse_detail(std::string_view body) { + const json root = json::parse(body); + Detail detail{ + .mod = parse_mod(root), + .slug = required_string(root, "slug"), + .siteUrl = required_string(root, "site_url"), + .sourceUrl = optional_string(root, "source_url"), + .license = optional_string(root, "license"), + .descriptionHtml = required_string(root, "description_html"), + .changelogHtml = required_string(root, "changelog_html"), + .packageSha256 = required_string(root, "package_sha256"), + }; + + const auto& modAbi = required_field(root, "mod_abi"); + if (!modAbi.is_null()) { + const auto value = required_count(root, "mod_abi"); + if (value > std::numeric_limits::max()) { + throw std::runtime_error{"field 'mod_abi' is too large"}; + } + detail.modAbi = static_cast(value); + } + + const auto& banner = required_field(root, "banner"); + if (!banner.is_null()) { + detail.banner = parse_image(banner); + } + + const auto& screenshots = required_field(root, "screenshots"); + if (!screenshots.is_array()) { + throw std::runtime_error{"field 'screenshots' is not an array"}; + } + detail.screenshots.reserve(screenshots.size()); + for (const auto& screenshot : screenshots) { + detail.screenshots.push_back({ + .altText = required_string(screenshot, "alt_text"), + .image = parse_image(required_field(screenshot, "image")), + }); + } + + const auto& imports = required_field(root, "service_imports"); + if (!imports.is_array()) { + throw std::runtime_error{"field 'service_imports' is not an array"}; + } + detail.serviceImports.reserve(imports.size()); + for (const auto& import : imports) { + detail.serviceImports.push_back({ + .id = required_string(import, "id"), + .major = required_u16(import, "major"), + .minMinor = required_u16(import, "min_minor"), + .optional = required_bool(import, "optional"), + }); + } + return detail; +} + +Page parse_page(std::string_view body) { + const json root = json::parse(body); + const auto& game = required_field(root, "game"); + if (required_string(game, "id") != "dusklight") { + throw std::runtime_error{"catalog response is for a different game"}; + } + + Page page; + const auto& categories = required_field(root, "categories"); + if (!categories.is_array()) { + throw std::runtime_error{"field 'categories' is not an array"}; + } + page.categories.reserve(categories.size()); + for (const auto& category : categories) { + page.categories.push_back(parse_category(category)); + } + + const auto& mods = required_field(root, "mods"); + if (!mods.is_array()) { + throw std::runtime_error{"field 'mods' is not an array"}; + } + page.mods.reserve(mods.size()); + for (const auto& mod : mods) { + page.mods.push_back(parse_mod(mod)); + } + + const auto& pagination = required_field(root, "pagination"); + page.pagination = { + .page = required_int(pagination, "page"), + .pageSize = required_int(pagination, "page_size"), + .pageCount = required_int(pagination, "page_count"), + .total = required_count(pagination, "total"), + }; + return page; +} + +std::string api_error(const borealis::http::Response& response) { + try { + const auto body = json::parse(response.body); + const auto& error = required_field(body, "error"); + return required_string(error, "message"); + } catch (...) { + return fmt::format("The catalog returned HTTP {}.", response.statusCode); + } +} + +FetchResult finish_request(borealis::http::Result result) { + if (result.error != borealis::http::Error::None) { + return {.error = result.message.empty() ? "The catalog request failed." : + std::move(result.message)}; + } + if (result.response.statusCode != 200) { + return {.error = api_error(result.response)}; + } + try { + return {.page = parse_page(result.response.body)}; + } catch (const std::exception& exception) { + return {.error = fmt::format("The catalog response was invalid: {}", exception.what())}; + } catch (...) { + return {.error = "The catalog response was invalid."}; + } +} + +DetailFetchResult finish_detail_request(borealis::http::Result result) { + if (result.error != borealis::http::Error::None) { + return {.error = + result.message.empty() ? "The mod request failed." : std::move(result.message)}; + } + if (result.response.statusCode != 200) { + return {.error = api_error(result.response)}; + } + try { + return {.detail = parse_detail(result.response.body)}; + } catch (const std::exception& exception) { + return {.error = fmt::format("The mod response was invalid: {}", exception.what())}; + } catch (...) { + return {.error = "The mod response was invalid."}; + } +} + +borealis::http::Request make_request(std::string url) { + return { + .url = std::move(url), + .headers = + { + {.name = "User-Agent", .value = borealis::user_agent(dusk::AppInfo)}, + {.name = "Accept", .value = "application/json"}, + }, + .connectTimeout = 10s, + .idleTimeout = 10s, + .totalTimeout = 20s, + }; +} + +} // namespace + +borealis::Task fetch_page(Query query) { + return borealis::http::start(make_request(make_url(query))).map(finish_request); +} + +borealis::Task fetch_detail(std::string id) { + return borealis::http::start(make_request(make_detail_url(id))).map(finish_detail_request); +} + +} // namespace dusk::mods::catalog diff --git a/src/dusk/mods/catalog.hpp b/src/dusk/mods/catalog.hpp new file mode 100644 index 0000000000..b819525d2b --- /dev/null +++ b/src/dusk/mods/catalog.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace dusk::mods::catalog { + +enum class Sort { + Downloads, + Endorsements, + Updated, + Newest, + Name, +}; + +struct Query { + std::string search; + std::string category; + Sort sort = Sort::Downloads; + int page = 1; + bool thisDevice = true; +}; + +struct Category { + std::string slug; + std::string name; + std::uint64_t modCount = 0; +}; + +struct Tag { + std::string slug; + std::string name; +}; + +struct Author { + std::string name; + std::string handle; + bool official = false; +}; + +struct ImageSource { + std::uint32_t width = 0; + std::string pngUrl; +}; + +struct Image { + std::uint32_t width = 0; + std::uint32_t height = 0; + std::vector sources; +}; + +struct Mod { + std::string id; + std::string name; + std::string version; + Author author; + std::string summary; + std::optional category; + std::vector tags; + std::uint64_t downloads = 0; + std::uint64_t endorsements = 0; + std::string publishedAt; + std::string updatedAt; + std::uint64_t packageSize = 0; + bool containsNativeCode = false; + std::vector supportedPlatforms; + std::optional icon; + std::optional banner; +}; + +struct Screenshot { + std::string altText; + Image image; +}; + +struct ServiceImport { + std::string id; + std::uint16_t major = 0; + std::uint16_t minMinor = 0; + bool optional = false; +}; + +struct Detail { + Mod mod; + std::string slug; + std::string siteUrl; + std::optional sourceUrl; + std::optional license; + std::string descriptionHtml; + std::string changelogHtml; + std::string packageSha256; + std::optional modAbi; + std::optional banner; + std::vector screenshots; + std::vector serviceImports; +}; + +struct Pagination { + int page = 1; + int pageSize = 0; + int pageCount = 0; + std::uint64_t total = 0; +}; + +struct Page { + std::vector categories; + std::vector mods; + Pagination pagination; +}; + +struct FetchResult { + std::optional page; + std::string error; +}; + +struct DetailFetchResult { + std::optional detail; + std::string error; +}; + +/** Fetches one filtered page from the configured Dusklight catalog. */ +borealis::Task fetch_page(Query query); + +/** Fetches the full catalog record for one mod. */ +borealis::Task fetch_detail(std::string id); + +} // namespace dusk::mods::catalog diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index 75e1d514cf..1a429e90f2 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -114,9 +114,10 @@ struct UiSlot { std::string styleId; // Cached rendered values for element setters. These make the natural "set every update" // style cheap when the displayed value has not changed. - std::string elementRml; + std::string elementValue; float elementFloat = 0.0f; bool hasElementValue = false; + bool elementValueIsRml = false; }; SlotMap s_slots; @@ -550,7 +551,7 @@ ModResult ui_pane_add_text(LoadedMod& mod, uint64_t pane, const char* text, uint auto* elem = slot->pane->add_text(text); if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem); - elemSlot.elementRml = ui::escape(text); + elemSlot.elementValue = text; elemSlot.hasElementValue = true; track_element(*outElem, elemSlot, *elem); } @@ -565,8 +566,9 @@ ModResult ui_pane_add_rml(LoadedMod& mod, uint64_t pane, const char* rml, uint64 auto* elem = slot->pane->add_rml(rml); if (outElem != nullptr) { auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem); - elemSlot.elementRml = rml; + elemSlot.elementValue = rml; elemSlot.hasElementValue = true; + elemSlot.elementValueIsRml = true; track_element(*outElem, elemSlot, *elem); } return MOD_OK; @@ -797,13 +799,13 @@ ModResult ui_elem_set_text(LoadedMod& mod, uint64_t elem, const char* text) { if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - const std::string rml = ui::escape(text); - if (slot->hasElementValue && slot->elementRml == rml) { + if (slot->hasElementValue && !slot->elementValueIsRml && slot->elementValue == text) { return MOD_OK; } - slot->elementRml = rml; + slot->elementValue = text; slot->hasElementValue = true; - slot->element->SetInnerRML(slot->elementRml); + slot->elementValueIsRml = false; + ui::set_text_content(slot->element, slot->elementValue); return MOD_OK; } @@ -812,11 +814,12 @@ ModResult ui_elem_set_rml(LoadedMod& mod, uint64_t elem, const char* rml) { if (slot == nullptr) { return MOD_INVALID_ARGUMENT; } - if (slot->hasElementValue && slot->elementRml == rml) { + if (slot->hasElementValue && slot->elementValueIsRml && slot->elementValue == rml) { return MOD_OK; } - slot->elementRml = rml; + slot->elementValue = rml; slot->hasElementValue = true; + slot->elementValueIsRml = true; slot->element->SetInnerRML(rml); return MOD_OK; } @@ -940,7 +943,7 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out default: break; } - props.title = ui::escape(desc.title); + props.title = desc.title; props.bodyRml = desc.body_rml; props.icon = desc.icon != nullptr ? desc.icon : defaultIcon; props.onDismiss = [modPtr = &mod, handle, fn = desc.on_dismiss, userData = desc.user_data]( diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp index 905fa57f59..ec27248431 100644 --- a/src/dusk/ui/achievements.cpp +++ b/src/dusk/ui/achievements.cpp @@ -16,47 +16,39 @@ struct CategoryInfo { }; constexpr CategoryInfo kCategories[] = { - {AchievementCategory::Challenge, "Challenge"}, + {AchievementCategory::Challenge, "Challenge"}, {AchievementCategory::Collection, "Collection"}, - {AchievementCategory::Minigame, "Minigame"}, - {AchievementCategory::Misc, "Misc"}, - {AchievementCategory::Glitched, "Glitched"}, + {AchievementCategory::Minigame, "Minigame"}, + {AchievementCategory::Misc, "Misc"}, + {AchievementCategory::Glitched, "Glitched"}, }; -Rml::String build_achievement_info_rml(const Achievement& a) { - Rml::String s = fmt::format( - R"(

)" - R"({})" - R"({})" - R"(
)" - R"(

{}

)", - a.unlocked ? " unlocked" : "", - a.name, - a.unlocked ? " unlocked" : " locked", - a.unlocked ? "Unlocked" : "Locked", - a.description - ); +void append_achievement_info(Rml::Element* parent, const Achievement& a) { + auto* header = append(parent, "achievement-header"); + auto* name = append(header, "achievement-name"); + name->SetClass("unlocked", a.unlocked); + append_text(name, a.name); + auto* badge = append(header, "achievement-badge"); + badge->SetClass(a.unlocked ? "unlocked" : "locked", true); + append_text(badge, a.unlocked ? "Unlocked" : "Locked"); + auto* description = append(parent, "p"); + description->SetClass("achievement-desc", true); + append_text(description, a.description); if (a.isCounter) { - float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; - s += fmt::format( - R"()" - R"({} / {})", - fraction, - a.unlocked ? "progress-done" : "progress-ongoing", - a.progress, - a.goal - ); + const float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; + auto* progress = append(parent, "progress"); + progress->SetAttribute("value", fraction); + progress->SetClass(a.unlocked ? "progress-done" : "progress-ongoing", true); + append_text( + append(parent, "achievement-progress"), fmt::format("{} / {}", a.progress, a.goal)); } - - return s; } class AchievementRow : public FluentComponent { public: AchievementRow(Rml::Element* parent, const Achievement& a) - : FluentComponent(createRowRoot(parent)) - { + : FluentComponent(createRowRoot(parent)) { auto& btn = add_child - + @@ -329,7 +330,7 @@ void apply_disc_verification_result(const DiscVerificationResult& result) { state.pendingDiscPath = result.path; state.pendingDiscInfo = result.info; state.pendingDiscValidation = result.validation; - state.errorString = escape(get_error_msg(result.validation)); + state.errorString = get_error_msg(result.validation); return; } @@ -345,41 +346,34 @@ void apply_disc_verification_result(const DiscVerificationResult& result) { state.pendingDiscPath.clear(); state.pendingDiscInfo = {}; state.pendingDiscValidation = iso::ValidationError::Unknown; - state.errorString = escape(get_error_msg(result.validation)); + state.errorString = get_error_msg(result.validation); } class DiscVerificationModal : public WindowSmall { public: - DiscVerificationModal() : WindowSmall("modal", "modal-dialog") { - auto* header = append(mDialog, "div"); - header->SetClass("modal-header", true); + DiscVerificationModal() : WindowSmall("modal") { + auto* header = append(mDialog, "modal-header"); - auto* title = append(header, "div"); - title->SetClass("modal-title", true); - title->SetInnerRML("Verifying disc image"); + auto* title = append(header, "modal-title"); + append_text(title, "Verifying disc image"); auto* icon = append(header, "icon"); icon->SetClass("verifying", true); - auto* body = append(mDialog, "div"); - body->SetClass("modal-body", true); + auto* body = append(mDialog, "modal-body"); - auto* content = append(body, "div"); - content->SetClass("verification-progress", true); + auto* content = append(body, "verification-progress"); - mFileName = append(content, "div"); - mFileName->SetClass("verification-file", true); + mFileName = append(content, "verification-file"); mProgress = append(content, "progress"); mProgress->SetClass("progress-ongoing", true); mProgress->SetClass("verification-progress-bar", true); mProgress->SetAttribute("value", 0.f); - mDetail = append(content, "div"); - mDetail->SetClass("verification-detail", true); + mDetail = append(content, "verification-detail"); - auto* actions = append(mDialog, "div"); - actions->SetClass("modal-actions", true); + auto* actions = append(mDialog, "modal-actions"); mCancelButton = std::make_unique