From 583d0991f048b0ac5801a9740037060f84f73fdd Mon Sep 17 00:00:00 2001 From: Death Killer <884052+deathkiller@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:01:24 +0100 Subject: [PATCH] Legacy main menu, cinematics SFX, minor changes --- Content/Metadata/UI/MainMenu.res | 13 ++ .../Jazz2/Compatibility/AnimSetMapping.cpp | 43 +++-- Sources/Jazz2/Compatibility/AnimSetMapping.h | 85 +++++----- Sources/Jazz2/Compatibility/JJ2Anims.cpp | 15 +- Sources/Jazz2/Compatibility/JJ2Anims.h | 10 +- Sources/Jazz2/Compatibility/JJ2Data.cpp | 153 +++++++++++++++++- Sources/Jazz2/Compatibility/JJ2Data.h | 17 +- Sources/Jazz2/Compatibility/JJ2Episode.cpp | 6 +- Sources/Jazz2/Compatibility/JJ2Episode.h | 6 +- Sources/Jazz2/Compatibility/JJ2Level.cpp | 6 +- Sources/Jazz2/Compatibility/JJ2Level.h | 6 +- Sources/Jazz2/Compatibility/JJ2Strings.cpp | 6 +- Sources/Jazz2/Compatibility/JJ2Strings.h | 8 +- Sources/Jazz2/Compatibility/JJ2Tileset.cpp | 4 +- Sources/Jazz2/Compatibility/JJ2Tileset.h | 4 +- Sources/Jazz2/ContentResolver.h | 1 + Sources/Jazz2/LevelHandler.cpp | 2 +- Sources/Jazz2/Resources.h | 2 +- Sources/Jazz2/UI/Cinematics.cpp | 86 +++++++++- Sources/Jazz2/UI/Cinematics.h | 22 +++ Sources/Jazz2/UI/Menu/AboutSection.cpp | 2 +- .../UI/Menu/CreateServerOptionsSection.cpp | 2 +- .../UI/Menu/GameplayEnhancementsSection.cpp | 12 +- .../UI/Menu/GameplayEnhancementsSection.h | 1 - .../Jazz2/UI/Menu/InputDiagnosticsSection.cpp | 2 +- Sources/Jazz2/UI/Menu/MainMenu.cpp | 150 ++++++++++++++++- Sources/Jazz2/UI/Menu/MainMenu.h | 1 + Sources/Jazz2/UI/Menu/MenuResources.h | 3 + .../Jazz2/UI/Menu/StartGameOptionsSection.cpp | 4 +- .../UI/Menu/TouchControlsOptionsSection.cpp | 2 +- Sources/Main.cpp | 9 +- Sources/nCine/Primitives/Vector2.h | 7 + 32 files changed, 567 insertions(+), 123 deletions(-) diff --git a/Content/Metadata/UI/MainMenu.res b/Content/Metadata/UI/MainMenu.res index 81ced3db..85d02472 100644 --- a/Content/Metadata/UI/MainMenu.res +++ b/Content/Metadata/UI/MainMenu.res @@ -123,6 +123,19 @@ "States": [ 56 ] }, + "Menu16": { + "Path": "UI/menu16.aura", + "States": [ 70 ] + }, + "Menu32": { + "Path": "UI/menu32.aura", + "States": [ 71 ] + }, + "Menu128": { + "Path": "UI/menu128.aura", + "States": [ 72 ] + }, + "LoriExistsCheck": { "Path": "Lori/corpse.aura", "States": [ 80 ] diff --git a/Sources/Jazz2/Compatibility/AnimSetMapping.cpp b/Sources/Jazz2/Compatibility/AnimSetMapping.cpp index cae83403..1e3f4446 100644 --- a/Sources/Jazz2/Compatibility/AnimSetMapping.cpp +++ b/Sources/Jazz2/Compatibility/AnimSetMapping.cpp @@ -2,6 +2,11 @@ namespace Jazz2::Compatibility { + AnimSetMapping::AnimSetMapping(JJ2Version version) + : _version(version), _currentItem(0), _currentSet(0), _currentOrdinal(0) + { + } + AnimSetMapping AnimSetMapping::GetAnimMapping(JJ2Version version) { AnimSetMapping m(version); @@ -1951,27 +1956,29 @@ namespace Jazz2::Compatibility return m; } - void AnimSetMapping::DiscardItems(int advanceBy, JJ2Version appliesTo) + void AnimSetMapping::DiscardItems(std::uint32_t advanceBy, JJ2Version appliesTo) { if ((_version & appliesTo) != JJ2Version::Unknown) { - for (int i = 0; i < advanceBy; i++) { + for (std::uint32_t i = 0; i < advanceBy; i++) { Entry entry; entry.Category = Discard; - int32_t key = (_currentSet << 16) | _currentItem; + std::int32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; + _currentOrdinal++; } } } - void AnimSetMapping::SkipItems(int advanceBy) + void AnimSetMapping::SkipItems(std::uint32_t advanceBy) { _currentItem += advanceBy; + _currentOrdinal += advanceBy; } - void AnimSetMapping::NextSet(int advanceBy, JJ2Version appliesTo) + void AnimSetMapping::NextSet(std::uint32_t advanceBy, JJ2Version appliesTo) { if ((_version & appliesTo) != JJ2Version::Unknown) { _currentSet += advanceBy; @@ -1979,19 +1986,22 @@ namespace Jazz2::Compatibility } } - void AnimSetMapping::Add(JJ2Version appliesTo, const StringView& category, const StringView& name, JJ2DefaultPalette palette, bool skipNormalMap, bool allowRealtimePalette) { + void AnimSetMapping::Add(JJ2Version appliesTo, const StringView& category, const StringView& name, JJ2DefaultPalette palette, bool skipNormalMap, bool allowRealtimePalette) + { if ((_version & appliesTo) != JJ2Version::Unknown) { Entry entry; entry.Category = category; entry.Name = name; + entry.Ordinal = _currentOrdinal; entry.Palette = palette; entry.SkipNormalMap = skipNormalMap; entry.AllowRealtimePalette = allowRealtimePalette; - int32_t key = (_currentSet << 16) | _currentItem; + std::uint32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; + _currentOrdinal++; } } @@ -2000,23 +2010,25 @@ namespace Jazz2::Compatibility Entry entry; entry.Category = category; entry.Name = name; + entry.Ordinal = _currentOrdinal; entry.Palette = palette; entry.SkipNormalMap = skipNormalMap; entry.AllowRealtimePalette = allowRealtimePalette; - int32_t key = (_currentSet << 16) | _currentItem; + std::uint32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; + _currentOrdinal++; } - AnimSetMapping::Entry* AnimSetMapping::Get(int32_t set, int32_t item) + AnimSetMapping::Entry* AnimSetMapping::Get(std::uint32_t set, std::uint32_t item) { if (set > UINT16_MAX || item > UINT16_MAX) { return nullptr; } - int32_t key = (set << 16) | item; + std::uint32_t key = (set << 16) | item; auto it = _entries.find(key); if (it != _entries.end()) { return &it->second; @@ -2024,4 +2036,15 @@ namespace Jazz2::Compatibility return nullptr; } } + + AnimSetMapping::Entry* AnimSetMapping::GetByOrdinal(std::uint32_t index) + { + for (auto& [key, entry] : _entries) { + if (entry.Ordinal == index) { + return &entry; + } + } + + return nullptr; + } } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/AnimSetMapping.h b/Sources/Jazz2/Compatibility/AnimSetMapping.h index 04e3c250..9f331804 100644 --- a/Sources/Jazz2/Compatibility/AnimSetMapping.h +++ b/Sources/Jazz2/Compatibility/AnimSetMapping.h @@ -15,48 +15,45 @@ using namespace nCine; namespace Jazz2::Compatibility { - enum class JJ2DefaultPalette { - Sprite, - Menu - }; - - class AnimSetMapping - { - public: - static constexpr char Discard[] = ":discard"; - - struct Entry { - String Category; - String Name; - - JJ2DefaultPalette Palette; - bool SkipNormalMap; - bool AllowRealtimePalette; - }; - - AnimSetMapping(JJ2Version version) - : - _version(version), - _currentItem(0), - _currentSet(0) - { - } - - Entry* Get(int32_t set, int32_t item); - - static AnimSetMapping GetAnimMapping(JJ2Version version); - static AnimSetMapping GetSampleMapping(JJ2Version version); - - private: - JJ2Version _version; - HashMap _entries; - int _currentItem; - int _currentSet; - - void DiscardItems(int advanceBy, JJ2Version appliesTo = JJ2Version::All); - void SkipItems(int advanceBy = 1); - void NextSet(int advanceBy = 1, JJ2Version appliesTo = JJ2Version::All); - void Add(JJ2Version appliesTo, const StringView& category, const StringView& name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); - void Add(const StringView& category, const StringView& name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); - }; + enum class JJ2DefaultPalette { + Sprite, + Menu + }; + + class AnimSetMapping + { + public: + static constexpr char Discard[] = ":discard"; + + struct Entry { + String Category; + String Name; + std::uint32_t Ordinal; + + JJ2DefaultPalette Palette; + bool SkipNormalMap; + bool AllowRealtimePalette; + }; + + Entry* Get(std::uint32_t set, std::uint32_t item); + Entry* GetByOrdinal(std::uint32_t index); + + static AnimSetMapping GetAnimMapping(JJ2Version version); + static AnimSetMapping GetSampleMapping(JJ2Version version); + + private: + JJ2Version _version; + HashMap _entries; + std::uint32_t _currentItem; + std::uint32_t _currentSet; + std::uint32_t _currentOrdinal; + + AnimSetMapping(JJ2Version version); + + void DiscardItems(std::uint32_t advanceBy, JJ2Version appliesTo = JJ2Version::All); + void SkipItems(std::uint32_t advanceBy = 1); + void NextSet(std::uint32_t advanceBy = 1, JJ2Version appliesTo = JJ2Version::All); + void Add(JJ2Version appliesTo, const StringView& category, const StringView& name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); + void Add(const StringView& category, const StringView& name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); + }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Anims.cpp b/Sources/Jazz2/Compatibility/JJ2Anims.cpp index c69ca9d4..df59bfbd 100644 --- a/Sources/Jazz2/Compatibility/JJ2Anims.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Anims.cpp @@ -9,7 +9,7 @@ using namespace Death::IO; namespace Jazz2::Compatibility { - bool JJ2Anims::Convert(const StringView& path, const StringView& targetPath, bool isPlus) + JJ2Version JJ2Anims::Convert(const StringView path, const StringView targetPath, bool isPlus) { JJ2Version version; SmallVector anims; @@ -260,7 +260,7 @@ namespace Jazz2::Compatibility version = JJ2Version::TSF | JJ2Version::SharewareDemo; // TODO: This version is not supported (yet) LOGE("Detected Jazz Jackrabbit 2: The Secret Files Demo - This version is not supported!"); - return false; + return JJ2Version::Unknown; } else if (seemsLikeCC) { version = JJ2Version::CC; LOGI("Detected Jazz Jackrabbit 2: Christmas Chronicles"); @@ -275,7 +275,7 @@ namespace Jazz2::Compatibility version = JJ2Version::PlusExtension; if (!isPlus) { LOGE("Detected Jazz Jackrabbit 2 Plus extension - This version is not supported!"); - return false; + return JJ2Version::Unknown; } } else { version = JJ2Version::Unknown; @@ -284,10 +284,11 @@ namespace Jazz2::Compatibility ImportAnimations(targetPath, version, anims); ImportAudioSamples(targetPath, version, samples); - return true; + + return version; } - void JJ2Anims::ImportAnimations(const StringView& targetPath, JJ2Version version, SmallVectorImpl& anims) + void JJ2Anims::ImportAnimations(const StringView targetPath, JJ2Version version, SmallVectorImpl& anims) { if (anims.empty()) { return; @@ -444,7 +445,7 @@ namespace Jazz2::Compatibility } } - void JJ2Anims::ImportAudioSamples(const StringView& targetPath, JJ2Version version, SmallVectorImpl& samples) + void JJ2Anims::ImportAudioSamples(const StringView targetPath, JJ2Version version, SmallVectorImpl& samples) { if (samples.empty()) { return; @@ -515,7 +516,7 @@ namespace Jazz2::Compatibility } } - void JJ2Anims::WriteImageToFile(const StringView& targetPath, const uint8_t* data, int32_t width, int32_t height, int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry) + void JJ2Anims::WriteImageToFile(const StringView targetPath, const uint8_t* data, int32_t width, int32_t height, int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry) { auto so = fs::Open(targetPath, FileAccessMode::Write); ASSERT_MSG(so->IsValid(), "Cannot open file for writing"); diff --git a/Sources/Jazz2/Compatibility/JJ2Anims.h b/Sources/Jazz2/Compatibility/JJ2Anims.h index ca074496..9de20f65 100644 --- a/Sources/Jazz2/Compatibility/JJ2Anims.h +++ b/Sources/Jazz2/Compatibility/JJ2Anims.h @@ -19,9 +19,9 @@ namespace Jazz2::Compatibility class JJ2Anims // .j2a { public: - static constexpr uint16_t CacheVersion = 14; + static constexpr uint16_t CacheVersion = 15; - static bool Convert(const StringView& path, const StringView& targetPath, bool isPlus); + static JJ2Version Convert(const StringView path, const StringView targetPath, bool isPlus = false); static void WriteImageToFileInternal(std::unique_ptr& so, const uint8_t* data, int32_t width, int32_t height, int32_t channelCount); @@ -66,9 +66,9 @@ namespace Jazz2::Compatibility JJ2Anims(); - static void ImportAnimations(const StringView& targetPath, JJ2Version version, SmallVectorImpl& anims); - static void ImportAudioSamples(const StringView& targetPath, JJ2Version version, SmallVectorImpl& samples); + static void ImportAnimations(const StringView targetPath, JJ2Version version, SmallVectorImpl& anims); + static void ImportAudioSamples(const StringView targetPath, JJ2Version version, SmallVectorImpl& samples); - static void WriteImageToFile(const StringView& targetPath, const uint8_t* data, int32_t width, int32_t height, int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry); + static void WriteImageToFile(const StringView targetPath, const uint8_t* data, int32_t width, int32_t height, int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry); }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Data.cpp b/Sources/Jazz2/Compatibility/JJ2Data.cpp index 38ffc1d2..b5d302f7 100644 --- a/Sources/Jazz2/Compatibility/JJ2Data.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Data.cpp @@ -1,17 +1,22 @@ #include "JJ2Data.h" +#include "JJ2Anims.h" +#include "JJ2Anims.Palettes.h" #include "JJ2Block.h" +#include "../ContentResolver.h" #include "../../nCine/Base/Algorithms.h" #include +#include +using namespace Death; using namespace Death::Containers::Literals; using namespace Death::IO; using namespace nCine; namespace Jazz2::Compatibility { - bool JJ2Data::Open(const StringView& path, bool strictParser) + bool JJ2Data::Open(const StringView path, bool strictParser) { auto s = fs::Open(path, FileAccessMode::Read); RETURNF_ASSERT_MSG(s->IsValid(), "Cannot open file for reading"); @@ -60,19 +65,155 @@ namespace Jazz2::Compatibility return true; } - void JJ2Data::Extract(const String& targetPath) + void JJ2Data::Extract(const StringView targetPath) { fs::CreateDirectories(targetPath); for (auto& item : Items) { - if (item.Filename == "Shield.Plasma"_s) { - // TODO - } - auto so = fs::Open(fs::CombinePath(targetPath, item.Filename), FileAccessMode::Write); ASSERT_MSG(so->IsValid(), "Cannot open file \"%s\" for writing", item.Filename.data()); so->Write(item.Blob.get(), item.Size); } } + + void JJ2Data::Convert(const StringView targetPath, JJ2Version version) + { + AnimSetMapping animMapping = AnimSetMapping::GetSampleMapping(version); + auto animationsUiPath = fs::CombinePath({ targetPath, "Animations"_s, "UI"_s }); + auto cinematicsPath = fs::CombinePath(targetPath, "Cinematics"_s); + + fs::CreateDirectories(animationsUiPath); + fs::CreateDirectories(cinematicsPath); + + for (auto& item : Items) { + if (item.Filename == "SoundFXList.Intro"_s) { + ConvertSfxList(item, fs::CombinePath(cinematicsPath, "intro.j2sfx"), animMapping); + } else if (item.Filename == "SoundFXList.Ending"_s) { + ConvertSfxList(item, fs::CombinePath(cinematicsPath, "ending.j2sfx"), animMapping); + } else if (item.Filename == "Menu.Texture.16x16"_s) { + ConvertMenuImage(item, fs::CombinePath(animationsUiPath, "menu16.aura"), 16, 16); + } else if (item.Filename == "Menu.Texture.32x32"_s) { + ConvertMenuImage(item, fs::CombinePath(animationsUiPath, "menu32.aura"), 32, 32); + } else if (item.Filename == "Menu.Texture.128x128"_s) { + ConvertMenuImage(item, fs::CombinePath(animationsUiPath, "menu128.aura"), 128, 128); + } + } + } + + void JJ2Data::ConvertSfxList(const Item& item, const StringView targetPath, AnimSetMapping& animMapping) + { +#pragma pack(push, 1) + struct SoundFXList { + std::uint32_t Frame; + std::uint32_t Sample; + std::uint32_t Volume; + std::uint32_t Panning; + }; +#pragma pack(pop) + + auto s = fs::Open(targetPath, FileAccessMode::Write); + s->WriteValue(0x2095A59FF0BFBBEF); // Signature + s->WriteValue(ContentResolver::SfxListFile); + s->WriteValue(1); + + DeflateWriter d(*s); + + HashMap sampleToIndex; + SmallVector indexToSample; + + std::int32_t itemCount = item.Size / sizeof(SoundFXList); + std::int32_t itemRealCount = 0; + std::int32_t sampleCount = 0; + for (std::int32_t i = 0; i < itemCount; i++) { + SoundFXList sfx; + std::memcpy(&sfx, &item.Blob[i * sizeof(SoundFXList)], sizeof(SoundFXList)); + if (sfx.Frame == UINT32_MAX) { + break; + } + + auto it = sampleToIndex.find(sfx.Sample); + if (it == sampleToIndex.end()) { + sampleToIndex.emplace(sfx.Sample, sampleCount); + indexToSample.emplace_back(sfx.Sample); + sampleCount++; + } + + itemRealCount++; + } + + d.WriteValue((std::uint16_t)sampleCount); + for (std::int32_t i = 0; i < sampleCount; i++) { + auto sample = animMapping.GetByOrdinal(indexToSample[i]); + if (sample == nullptr) { + d.WriteValue(0); + } else { + String samplePath = "/"_s.join({ sample->Category, sample->Name + ".wav"_s }); + d.WriteValue((std::uint8_t)samplePath.size()); + d.Write(samplePath.data(), (std::uint32_t)samplePath.size()); + } + } + + d.WriteValue((std::uint16_t)itemRealCount); + for (std::int32_t i = 0; i < itemRealCount; i++) { + SoundFXList sfx; + std::memcpy(&sfx, &item.Blob[i * sizeof(SoundFXList)], sizeof(SoundFXList)); + + d.WriteVariableUint32(sfx.Frame); + + auto it = sampleToIndex.find(sfx.Sample); + if (it != sampleToIndex.end()) { + d.WriteValue((std::uint16_t)it->second); + } else { + d.WriteValue(0); + } + + d.WriteValue((std::uint8_t)std::max(sfx.Volume * 255 / 0x40, (std::uint32_t)UINT8_MAX)); + d.WriteValue((std::int8_t)std::clamp(((std::int32_t)sfx.Volume - 0x20) * INT8_MAX / 0x20, -(std::int32_t)INT8_MAX, (std::int32_t)INT8_MAX)); + } + } + + void JJ2Data::ConvertMenuImage(const Item& item, const StringView targetPath, std::int32_t width, std::int32_t height) + { + std::int32_t pixelCount = width * height; + RETURN_ASSERT_MSG(item.Size == pixelCount, "Image has unexpected size"); + + std::unique_ptr pixels = std::make_unique(pixelCount * 4); + for (std::int32_t i = 0; i < pixelCount; i++) { + std::uint8_t colorIdx = item.Blob[i]; + const Color& src = MenuPalette[colorIdx]; + std::uint8_t a = (colorIdx == 0 ? 0 : src.A); + + pixels[(i * 4)] = src.R; + pixels[(i * 4) + 1] = src.G; + pixels[(i * 4) + 2] = src.B; + pixels[(i * 4) + 3] = a; + } + + auto so = fs::Open(targetPath, FileAccessMode::Write); + ASSERT_MSG(so->IsValid(), "Cannot open file for writing"); + + std::uint8_t flags = 0x80 | 0x01 | 0x02; + + so->WriteValue(0xB8EF8498E2BFBBEF); + so->WriteValue(0x0002208F | (flags << 24)); // Version 2 is reserved for sprites (or bigger images) + + so->WriteValue(4); + so->WriteValue(width); + so->WriteValue(height); + + // Include Sprite extension + so->WriteValue(1); // FrameConfigurationX + so->WriteValue(1); // FrameConfigurationY + so->WriteValue(1); // FrameCount + so->WriteValue(0); // FrameRate + so->WriteValue(UINT16_MAX); // NormalizedHotspotX + so->WriteValue(UINT16_MAX); // NormalizedHotspotY + so->WriteValue(UINT16_MAX); // ColdspotX + so->WriteValue(UINT16_MAX); // ColdspotY + so->WriteValue(UINT16_MAX); // GunspotX + so->WriteValue(UINT16_MAX); // GunspotY + + JJ2Anims::WriteImageToFileInternal(so, pixels.get(), width, height, 4); + } } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Data.h b/Sources/Jazz2/Compatibility/JJ2Data.h index 5b070596..b67867b9 100644 --- a/Sources/Jazz2/Compatibility/JJ2Data.h +++ b/Sources/Jazz2/Compatibility/JJ2Data.h @@ -1,6 +1,8 @@ #pragma once #include "../../Common.h" +#include "AnimSetMapping.h" +#include "JJ2Version.h" #include #include @@ -15,17 +17,22 @@ namespace Jazz2::Compatibility public: struct Item { String Filename; - uint32_t Type; + std::uint32_t Type; std::unique_ptr Blob; - int32_t Size; + std::int32_t Size; }; + SmallVector Items; + JJ2Data() { } - bool Open(const StringView& path, bool strictParser); + bool Open(const StringView path, bool strictParser); - void Extract(const String& targetPath); + void Extract(const StringView targetPath); + void Convert(const StringView targetPath, JJ2Version version); - SmallVector Items; + private: + void ConvertSfxList(const Item& item, const StringView targetPath, AnimSetMapping& animMapping); + void ConvertMenuImage(const Item& item, const StringView targetPath, std::int32_t width, std::int32_t height); }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Episode.cpp b/Sources/Jazz2/Compatibility/JJ2Episode.cpp index 8aff20b5..6f63df93 100644 --- a/Sources/Jazz2/Compatibility/JJ2Episode.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Episode.cpp @@ -17,12 +17,12 @@ namespace Jazz2::Compatibility { } - JJ2Episode::JJ2Episode(const String& name, const String& displayName, const String& firstLevel, int32_t position) + JJ2Episode::JJ2Episode(const StringView name, const StringView displayName, const StringView firstLevel, int32_t position) : Name(name), DisplayName(displayName), FirstLevel(firstLevel), Position(position), ImageWidth(0), ImageHeight(0), TitleWidth(0), TitleHeight(0) { } - bool JJ2Episode::Open(const StringView& path) + bool JJ2Episode::Open(const StringView path) { auto s = fs::Open(path, FileAccessMode::Read); RETURNF_ASSERT_MSG(s->IsValid(), "Cannot open file for reading"); @@ -117,7 +117,7 @@ namespace Jazz2::Compatibility return true; } - void JJ2Episode::Convert(const String& targetPath, std::function levelTokenConversion, std::function episodeNameConversion, std::function(JJ2Episode*)> episodePrevNext) + void JJ2Episode::Convert(const StringView targetPath, const std::function& levelTokenConversion, const std::function& episodeNameConversion, const std::function(JJ2Episode*)>& episodePrevNext) { auto so = fs::Open(targetPath, FileAccessMode::Write); ASSERT_MSG(so->IsValid(), "Cannot open file for writing"); diff --git a/Sources/Jazz2/Compatibility/JJ2Episode.h b/Sources/Jazz2/Compatibility/JJ2Episode.h index 71afcd2f..0656eda6 100644 --- a/Sources/Jazz2/Compatibility/JJ2Episode.h +++ b/Sources/Jazz2/Compatibility/JJ2Episode.h @@ -33,10 +33,10 @@ namespace Jazz2::Compatibility std::unique_ptr TitleData; JJ2Episode(); - JJ2Episode(const String& name, const String& displayName, const String& firstLevel, int32_t position); + JJ2Episode(const StringView name, const StringView displayName, const StringView firstLevel, int32_t position); - bool Open(const StringView& path); + bool Open(const StringView path); - void Convert(const String& targetPath, std::function levelTokenConversion = nullptr, std::function episodeNameConversion = nullptr, std::function(JJ2Episode*)> episodePrevNext = nullptr); + void Convert(const StringView targetPath, const std::function& levelTokenConversion = nullptr, const std::function& episodeNameConversion = nullptr, const std::function(JJ2Episode*)>& episodePrevNext = nullptr); }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Level.cpp b/Sources/Jazz2/Compatibility/JJ2Level.cpp index 94fcb62b..deea148d 100644 --- a/Sources/Jazz2/Compatibility/JJ2Level.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Level.cpp @@ -14,7 +14,7 @@ using namespace Death::IO; namespace Jazz2::Compatibility { - bool JJ2Level::Open(const StringView& path, bool strictParser) + bool JJ2Level::Open(const StringView path, bool strictParser) { auto s = fs::Open(path, FileAccessMode::Read); RETURNF_ASSERT_MSG(s->IsValid(), "Cannot open file for reading"); @@ -510,7 +510,7 @@ namespace Jazz2::Compatibility } } - void JJ2Level::Convert(const String& targetPath, const EventConverter& eventConverter, const std::function& levelTokenConversion) + void JJ2Level::Convert(const StringView targetPath, const EventConverter& eventConverter, const std::function& levelTokenConversion) { auto so = fs::Open(targetPath, FileAccessMode::Write); ASSERT_MSG(so->IsValid(), "Cannot open file for writing"); @@ -1065,7 +1065,7 @@ namespace Jazz2::Compatibility } } - bool JJ2Level::StringHasSuffixIgnoreCase(const StringView& value, const StringView& suffix) + bool JJ2Level::StringHasSuffixIgnoreCase(const StringView value, const StringView suffix) { const std::size_t size = value.size(); const std::size_t suffixSize = suffix.size(); diff --git a/Sources/Jazz2/Compatibility/JJ2Level.h b/Sources/Jazz2/Compatibility/JJ2Level.h index 8ef4d184..700607d4 100644 --- a/Sources/Jazz2/Compatibility/JJ2Level.h +++ b/Sources/Jazz2/Compatibility/JJ2Level.h @@ -55,9 +55,9 @@ namespace Jazz2::Compatibility JJ2Level() : _version(JJ2Version::Unknown), _animCount(0), _verticalMPSplitscreen(false), _isMpLevel(false), _hasPit(false), _hasPitInstantDeath(false), _hasCTF(false), _hasLaps(false), _useLevelPalette(false) { } - bool Open(const StringView& path, bool strictParser); + bool Open(const StringView path, bool strictParser); - void Convert(const String& targetPath, const EventConverter& eventConverter, const std::function& levelTokenConversion = nullptr); + void Convert(const StringView targetPath, const EventConverter& eventConverter, const std::function& levelTokenConversion = nullptr); void AddLevelTokenTextID(uint8_t textId); JJ2Version GetVersion() const { @@ -163,6 +163,6 @@ namespace Jazz2::Compatibility void LoadMlleData(JJ2Block& block, uint32_t version, const StringView& path, bool strictParser); static void WriteLevelName(Stream& so, MutableStringView value, const std::function& levelTokenConversion = nullptr); - static bool StringHasSuffixIgnoreCase(const StringView& value, const StringView& suffix); + static bool StringHasSuffixIgnoreCase(const StringView value, const StringView suffix); }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Strings.cpp b/Sources/Jazz2/Compatibility/JJ2Strings.cpp index b8e93ce3..585c3195 100644 --- a/Sources/Jazz2/Compatibility/JJ2Strings.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Strings.cpp @@ -40,7 +40,7 @@ static const uint32_t DefaultFontColors[] = { namespace Jazz2::Compatibility { - bool JJ2Strings::Open(const StringView& path) + bool JJ2Strings::Open(const StringView path) { auto s = fs::Open(path, FileAccessMode::Read); RETURNF_ASSERT_MSG(s->IsValid(), "Cannot open file for reading"); @@ -107,7 +107,7 @@ namespace Jazz2::Compatibility return true; } - void JJ2Strings::Convert(const String& targetPath, std::function levelTokenConversion) + void JJ2Strings::Convert(const StringView targetPath, const std::function& levelTokenConversion) { auto so = fs::Open(targetPath, FileAccessMode::Write); ASSERT_MSG(so->IsValid(), "Cannot open file for writing"); @@ -173,7 +173,7 @@ namespace Jazz2::Compatibility } } - String JJ2Strings::RecodeString(const StringView& text, bool stripFormatting, bool escaped) + String JJ2Strings::RecodeString(const StringView text, bool stripFormatting, bool escaped) { if (text.empty()) { return { }; diff --git a/Sources/Jazz2/Compatibility/JJ2Strings.h b/Sources/Jazz2/Compatibility/JJ2Strings.h index f18cdfeb..1427a7c9 100644 --- a/Sources/Jazz2/Compatibility/JJ2Strings.h +++ b/Sources/Jazz2/Compatibility/JJ2Strings.h @@ -31,15 +31,15 @@ namespace Jazz2::Compatibility JJ2Strings() { } - JJ2Strings(const String& name) + JJ2Strings(const StringView name) : Name(name) { } - bool Open(const StringView& path); + bool Open(const StringView path); - void Convert(const String& targetPath, std::function levelTokenConversion); + void Convert(const StringView targetPath, const std::function& levelTokenConversion); - static String RecodeString(const StringView& text, bool stripFormatting = false, bool escaped = false); + static String RecodeString(const StringView text, bool stripFormatting = false, bool escaped = false); }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Tileset.cpp b/Sources/Jazz2/Compatibility/JJ2Tileset.cpp index a2ef8016..7deca813 100644 --- a/Sources/Jazz2/Compatibility/JJ2Tileset.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Tileset.cpp @@ -10,7 +10,7 @@ using namespace Death::IO; namespace Jazz2::Compatibility { - bool JJ2Tileset::Open(const StringView& path, bool strictParser) + bool JJ2Tileset::Open(const StringView path, bool strictParser) { auto s = fs::Open(path, FileAccessMode::Read); RETURNF_ASSERT_MSG(s->IsValid(), "Cannot open file for reading"); @@ -160,7 +160,7 @@ namespace Jazz2::Compatibility } } - void JJ2Tileset::Convert(const String& targetPath) const + void JJ2Tileset::Convert(const StringView targetPath) const { // Rearrange tiles from '10 tiles per row' to '30 tiles per row' constexpr std::int32_t TilesPerRow = 30; diff --git a/Sources/Jazz2/Compatibility/JJ2Tileset.h b/Sources/Jazz2/Compatibility/JJ2Tileset.h index 3d275990..c2ddcb9a 100644 --- a/Sources/Jazz2/Compatibility/JJ2Tileset.h +++ b/Sources/Jazz2/Compatibility/JJ2Tileset.h @@ -20,9 +20,9 @@ namespace Jazz2::Compatibility JJ2Tileset() : _version(JJ2Version::Unknown), _tileCount(0) { } - bool Open(const StringView& path, bool strictParser); + bool Open(const StringView path, bool strictParser); - void Convert(const String& targetPath) const; + void Convert(const StringView targetPath) const; int GetMaxSupportedTiles() const { return (_version == JJ2Version::BaseGame ? 1024 : 4096); diff --git a/Sources/Jazz2/ContentResolver.h b/Sources/Jazz2/ContentResolver.h index 9de51828..11b87fb3 100644 --- a/Sources/Jazz2/ContentResolver.h +++ b/Sources/Jazz2/ContentResolver.h @@ -43,6 +43,7 @@ namespace Jazz2 static constexpr std::uint8_t CacheIndexFile = 3; static constexpr std::uint8_t ConfigFile = 4; static constexpr std::uint8_t StateFile = 5; + static constexpr std::uint8_t SfxListFile = 6; static constexpr std::int32_t PaletteCount = 256; static constexpr std::int32_t ColorsPerPalette = 256; diff --git a/Sources/Jazz2/LevelHandler.cpp b/Sources/Jazz2/LevelHandler.cpp index 6acc7218..51fa10b2 100644 --- a/Sources/Jazz2/LevelHandler.cpp +++ b/Sources/Jazz2/LevelHandler.cpp @@ -1299,7 +1299,7 @@ namespace Jazz2 if (_cheatsUsed) flags |= 0x02; dest.WriteValue(flags); - dest.WriteValue((std::uint8_t)_episodeName.size()); + dest.WriteValue((std::uint8_t)_episodeName.size()); dest.Write(_episodeName.data(), (std::uint32_t)_episodeName.size()); dest.WriteValue((std::uint8_t)_levelFileName.size()); dest.Write(_levelFileName.data(), (std::uint32_t)_levelFileName.size()); diff --git a/Sources/Jazz2/Resources.h b/Sources/Jazz2/Resources.h index 168071ef..1dd2e961 100644 --- a/Sources/Jazz2/Resources.h +++ b/Sources/Jazz2/Resources.h @@ -31,7 +31,7 @@ namespace Jazz2 { GenericGraphicResourceFlags Flags; std::unique_ptr TextureDiffuse; - std::unique_ptr TextureNormal; + //std::unique_ptr TextureNormal; std::unique_ptr Mask; Vector2i FrameDimensions; Vector2i FrameConfiguration; diff --git a/Sources/Jazz2/UI/Cinematics.cpp b/Sources/Jazz2/UI/Cinematics.cpp index d561a698..0c794e6e 100644 --- a/Sources/Jazz2/UI/Cinematics.cpp +++ b/Sources/Jazz2/UI/Cinematics.cpp @@ -7,6 +7,7 @@ #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Graphics/Viewport.h" #include "../../nCine/Input/IInputManager.h" +#include "../../nCine/Audio/AudioBufferPlayer.h" #include "../../nCine/Audio/AudioReaderMpt.h" #include "../../nCine/Base/FrameTimer.h" @@ -15,14 +16,14 @@ namespace Jazz2::UI { Cinematics::Cinematics(IRootController* root, const StringView path, const std::function& callback) - : _root(root), _callback(callback), _frameDelay(0.0f), _frameProgress(0.0f), _framesLeft(0), + : _root(root), _callback(callback), _frameDelay(0.0f), _frameProgress(0.0f), _framesLeft(0), _frameIndex(0), _pressedKeys((uint32_t)KeySym::COUNT), _pressedActions(0) { Initialize(path); } Cinematics::Cinematics(IRootController* root, const StringView path, std::function&& callback) - : _root(root), _callback(std::move(callback)), _frameDelay(0.0f), _frameProgress(0.0f), _framesLeft(0), + : _root(root), _callback(std::move(callback)), _frameDelay(0.0f), _frameProgress(0.0f), _framesLeft(0), _frameIndex(0), _pressedKeys((uint32_t)KeySym::COUNT), _pressedActions(0) { Initialize(path); @@ -186,6 +187,59 @@ namespace Jazz2::UI _decompressedStreams[i].Open(_compressedStreams[i]); } + LoadSfxList(path); + + return true; + } + + bool Cinematics::LoadSfxList(const StringView path) + { + auto& resolver = ContentResolver::Get(); + String fullPath = fs::CombinePath({ resolver.GetContentPath(), "Cinematics"_s, path + ".j2sfx"_s }); + if (!fs::IsReadableFile(fullPath)) { + fullPath = fs::CombinePath({ resolver.GetCachePath(), "Cinematics"_s, path + ".j2sfx"_s }); + } + + auto s = fs::Open(fullPath, FileAccessMode::Read); + RETURNF_ASSERT_MSG(s->GetSize() > 16, "Cannot load SFX list for \"%s.j2v\"", path); + + std::uint64_t signature = s->ReadValue(); + std::uint8_t fileType = s->ReadValue(); + std::uint16_t version = s->ReadValue(); + if (signature != 0x2095A59FF0BFBBEF || fileType != ContentResolver::SfxListFile || version > SfxListVersion) { + return false; + } + + DeflateStream uc(*s); + + std::uint32_t sampleCount = uc.ReadValue(); + for (std::uint32_t i = 0; i < sampleCount; i++) { + std::uint8_t stringSize = uc.ReadValue(); + String samplePath = String(NoInit, stringSize); + uc.Read(samplePath.data(), stringSize); + + String samplePathNormalized = fs::ToNativeSeparators(samplePath); + String fullPath = fs::CombinePath({ resolver.GetContentPath(), "Animations"_s, samplePathNormalized }); + if (!fs::IsReadableFile(fullPath)) { + fullPath = fs::CombinePath({ resolver.GetCachePath(), "Animations"_s, samplePathNormalized }); + if (!fs::IsReadableFile(fullPath)) { + _sfxSamples.emplace_back(); // Sample not found + continue; + } + } + + _sfxSamples.emplace_back(fullPath); + } + + std::uint32_t itemCount = uc.ReadValue(); + for (std::uint32_t i = 0; i < itemCount; i++) { + auto& item = _sfxPlaylist.emplace_back(); + item.Frame = uc.ReadVariableUint32(); + item.Sample = uc.ReadValue(); + item.Gain = uc.ReadValue() / 255.0f; + item.Panning = uc.ReadValue() / 127.0f; + } + return true; } @@ -243,6 +297,25 @@ namespace Jazz2::UI // Create copy of the buffer std::memcpy(_lastBuffer.get(), _buffer.get(), _width * _height); + + for (std::size_t i = 0; i < _sfxPlaylist.size(); i++) { + if (_sfxPlaylist[i].Frame == _frameIndex) { + auto& item = _sfxPlaylist[i]; + auto& sample = _sfxSamples[item.Sample]; + if (sample.Buffer == nullptr) { + continue; + } + + sample.CurrentPlayer = std::make_unique(sample.Buffer.get()); + Vector2f localPos = Vector2f::FromAngleLength(item.Panning * 30.0f * DegToRad, 1.0f); + sample.CurrentPlayer->setPosition(Vector3f(localPos.X, 0, -localPos.Y)); + sample.CurrentPlayer->setGain(_sfxPlaylist[i].Gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); + sample.CurrentPlayer->setSourceRelative(true); + sample.CurrentPlayer->play(); + } + } + + _frameIndex++; } void Cinematics::Read(int streamIndex, void* buffer, std::uint32_t bytes) @@ -329,4 +402,13 @@ namespace Jazz2::UI return true; } + + Cinematics::SfxItem::SfxItem() + { + } + + Cinematics::SfxItem::SfxItem(const StringView path) + { + Buffer = std::make_unique(path); + } } \ No newline at end of file diff --git a/Sources/Jazz2/UI/Cinematics.h b/Sources/Jazz2/UI/Cinematics.h index 00f9cdef..1d713048 100644 --- a/Sources/Jazz2/UI/Cinematics.h +++ b/Sources/Jazz2/UI/Cinematics.h @@ -8,6 +8,7 @@ #include "../../nCine/Base/BitArray.h" #include "../../nCine/Graphics/Camera.h" #include "../../nCine/Graphics/Shader.h" +#include "../../nCine/Audio/AudioBufferPlayer.h" #include "../../nCine/Audio/AudioStreamPlayer.h" #include "../../nCine/Input/InputEvents.h" @@ -26,6 +27,8 @@ namespace Jazz2::UI static constexpr int DefaultWidth = 720; static constexpr int DefaultHeight = 405; + static constexpr std::uint8_t SfxListVersion = 1; + Cinematics(IRootController* root, const StringView path, const std::function& callback); Cinematics(IRootController* root, const StringView path, std::function&& callback); ~Cinematics() override; @@ -56,13 +59,31 @@ namespace Jazz2::UI RenderCommand _renderCommand; }; + struct SfxItem { + std::unique_ptr Buffer; + std::unique_ptr CurrentPlayer; + + SfxItem(); + SfxItem(const StringView path); + }; + + struct SfxPlaylistItem { + std::uint32_t Frame; + std::uint16_t Sample; + float Gain; + float Panning; + }; + IRootController* _root; UI::UpscaleRenderPass _upscalePass; std::unique_ptr _canvas; std::unique_ptr _music; + SmallVector _sfxSamples; + SmallVector _sfxPlaylist; std::function _callback; uint32_t _width, _height; float _frameDelay, _frameProgress; + int _frameIndex; int _framesLeft; std::unique_ptr _texture; std::unique_ptr _buffer; @@ -77,6 +98,7 @@ namespace Jazz2::UI void Initialize(const StringView path); bool LoadCinematicsFromFile(const StringView path); + bool LoadSfxList(const StringView path); void PrepareNextFrame(); void Read(int streamIndex, void* buffer, uint32_t bytes); void UpdatePressedActions(); diff --git a/Sources/Jazz2/UI/Menu/AboutSection.cpp b/Sources/Jazz2/UI/Menu/AboutSection.cpp index 8e173ca2..599342a7 100644 --- a/Sources/Jazz2/UI/Menu/AboutSection.cpp +++ b/Sources/Jazz2/UI/Menu/AboutSection.cpp @@ -131,7 +131,7 @@ namespace Jazz2::UI::Menu pos.Y = std::round(std::max(150.0f, pos.Y * 0.86f)); _root->DrawElement(MenuDim, pos.X, pos.Y + 24.0f - 2.0f, IMenuContainer::BackgroundLayer, - Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, 0.7f, 0.0f)); + Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, -0.7f, 0.7f)); pos.X = std::round(pos.X * 0.35f); diff --git a/Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp b/Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp index 67fa033b..468a481f 100644 --- a/Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp +++ b/Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp @@ -196,7 +196,7 @@ namespace Jazz2::UI::Menu _root->DrawElement(MenuGlow, 0, x, center.Y + 28.0f, IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.2f), (Utf8::GetLength(playerTypes[j]) + 3) * 0.4f, 2.2f, true); _root->DrawStringShadow(playerTypes[j], charOffset, x, center.Y + 28.0f, IMenuContainer::FontLayer, - Alignment::Center, playerColors[j], 1.0f, 0.4f, 0.55f, 0.55f, 0.8f, 0.9f); + Alignment::Center, playerColors[j], 1.0f, 0.4f, 0.9f, 0.9f, 0.8f, 0.9f); } else { _root->DrawStringShadow(playerTypes[j], charOffset, x, center.Y + 28.0f, IMenuContainer::FontLayer, Alignment::Center, Font::DefaultColor, 0.8f, 0.0f, 4.0f, 4.0f, 0.4f, 0.9f); diff --git a/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp b/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp index 4ed84b8d..7934f6dd 100644 --- a/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp +++ b/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp @@ -10,7 +10,7 @@ using namespace Jazz2::UI::Menu::Resources; namespace Jazz2::UI::Menu { GameplayEnhancementsSection::GameplayEnhancementsSection() - : _transition(0.0f), _isDirty(false), _isInGame(false), _wasReforgedMainMenu(false) + : _transition(0.0f), _isDirty(false), _isInGame(false) { // TRANSLATORS: Menu item in Options > Gameplay > Enhancements section _items.emplace_back(GameplayEnhancementsItem { GameplayEnhancementsItemType::ReforgedGameplay, _("Reforged Gameplay") }); @@ -29,10 +29,6 @@ namespace Jazz2::UI::Menu if (_isDirty) { _isDirty = false; PreferencesCache::Save(); - - if (_wasReforgedMainMenu != PreferencesCache::EnableReforgedMainMenu) { - _root->ApplyPreferencesChanges(ChangedPreferencesType::MainMenu); - } } } @@ -47,7 +43,6 @@ namespace Jazz2::UI::Menu ScrollableMenuSection::OnShow(root); _isInGame = (dynamic_cast(_root) != nullptr); - _wasReforgedMainMenu = PreferencesCache::EnableReforgedMainMenu; } void GameplayEnhancementsSection::OnUpdate(float timeMult) @@ -146,7 +141,10 @@ namespace Jazz2::UI::Menu break; case GameplayEnhancementsItemType::ReforgedHUD: PreferencesCache::EnableReforgedHUD = !PreferencesCache::EnableReforgedHUD; break; - case GameplayEnhancementsItemType::ReforgedMainMenu: PreferencesCache::EnableReforgedMainMenu = !PreferencesCache::EnableReforgedMainMenu; break; + case GameplayEnhancementsItemType::ReforgedMainMenu: + PreferencesCache::EnableReforgedMainMenu = !PreferencesCache::EnableReforgedMainMenu; + _root->ApplyPreferencesChanges(ChangedPreferencesType::MainMenu); + break; case GameplayEnhancementsItemType::LedgeClimb: PreferencesCache::EnableLedgeClimb = !PreferencesCache::EnableLedgeClimb; break; case GameplayEnhancementsItemType::WeaponWheel: PreferencesCache::WeaponWheel = (PreferencesCache::WeaponWheel == WeaponWheelStyle::EnabledWithAmmoCount diff --git a/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.h b/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.h index e77c7024..5cc4b8e7 100644 --- a/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.h +++ b/Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.h @@ -33,7 +33,6 @@ namespace Jazz2::UI::Menu float _transition; bool _isDirty; bool _isInGame; - bool _wasReforgedMainMenu; void OnHandleInput() override; void OnLayoutItem(Canvas* canvas, ListViewItem& item) override; diff --git a/Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp b/Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp index 4674edff..0754aae3 100644 --- a/Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp +++ b/Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp @@ -64,7 +64,7 @@ namespace Jazz2::UI::Menu constexpr float TopLine = 131.0f; _root->DrawElement(MenuDim, center.X, TopLine - 2.0f, IMenuContainer::BackgroundLayer, - Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, 0.7f, 0.0f)); + Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, -0.7f, 0.7f)); _root->DrawElement(MenuLine, 0, center.X, TopLine, IMenuContainer::MainLayer, Alignment::Center, Colorf::White, 1.6f); int32_t charOffset = 0; diff --git a/Sources/Jazz2/UI/Menu/MainMenu.cpp b/Sources/Jazz2/UI/Menu/MainMenu.cpp index d410b463..2c3609c0 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.cpp +++ b/Sources/Jazz2/UI/Menu/MainMenu.cpp @@ -189,10 +189,12 @@ namespace Jazz2::UI::Menu ViewSize = _owner->_upscalePass.GetViewSize(); _owner->_activeCanvas = ActiveCanvas::Background; - _owner->RenderTexturedBackground(renderQueue); - Vector2i center = ViewSize / 2; + if (PreferencesCache::EnableReforgedMainMenu || !_owner->RenderLegacyBackground(renderQueue)) { + _owner->RenderTexturedBackground(renderQueue); + } + Vector2i center = ViewSize / 2; int32_t charOffset = 0; int32_t charOffsetShadow = 0; @@ -652,7 +654,7 @@ namespace Jazz2::UI::Menu void MainMenu::UpdateDebris(float timeMult) { - if (_preset == Preset::Xmas) { + if (_preset == Preset::Xmas && PreferencesCache::EnableReforgedMainMenu) { int32_t weatherIntensity = Random().Fast(0, (int32_t)(3 * timeMult) + 1); for (int32_t i = 0; i < weatherIntensity; i++) { Vector2i viewSize = _canvasOverlay->ViewSize; @@ -777,7 +779,7 @@ namespace Jazz2::UI::Menu struct tm* local = localtime(&t); int32_t month = local->tm_mon; #endif - bool hasXmas = (PreferencesCache::EnableReforgedMainMenu && (month == 11 || month == 0) && TryLoadBackgroundPreset(Preset::Xmas)); + bool hasXmas = ((month == 11 || month == 0) && TryLoadBackgroundPreset(Preset::Xmas)); if (!hasXmas && !TryLoadBackgroundPreset(Preset::Default) && !TryLoadBackgroundPreset(Preset::Carrotus) && @@ -880,6 +882,146 @@ namespace Jazz2::UI::Menu renderQueue.addCommand(command); } + bool MainMenu::RenderLegacyBackground(RenderQueue& renderQueue) + { + auto* res16 = _metadata->FindAnimation(Menu16); + auto* res32 = _metadata->FindAnimation(Menu32); + auto* res128 = _metadata->FindAnimation(Menu128); + if (res16 == nullptr || res32 == nullptr || res128 == nullptr) { + return false; + } + + float animTime = _canvasBackground->AnimTime; + Vector2f center = (_canvasBackground->ViewSize / 2).As(); + + // 16 + { + GenericGraphicResource* base = res16->Base; + base->TextureDiffuse->setWrap(SamplerWrapping::Repeat); + + constexpr float repeats = 96.0f; + float scale = (0.6f + 0.04f * sinf(animTime * 0.2f)) * repeats; + Vector2f size = base->FrameDimensions.As() * scale; + + auto command = _canvasBackground->RentRenderCommand(); + if (command->material().setShaderProgramType(Material::ShaderProgramType::SPRITE)) { + command->material().reserveUniformsDataMemory(); + command->geometry().setDrawParameters(GL_TRIANGLE_STRIP, 0, 4); + // Required to reset render command properly + //command->setTransformation(command->transformation()); + + GLUniformCache* textureUniform = command->material().uniform(Material::TextureUniformName); + if (textureUniform && textureUniform->intValue(0) != 0) { + textureUniform->setIntValue(0); // GL_TEXTURE0 + } + } + + command->material().setBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + auto instanceBlock = command->material().uniformBlock(Material::InstanceBlockName); + instanceBlock->uniform(Material::TexRectUniformName)->setFloatValue(repeats, 0.0f, repeats, 0.0f); + instanceBlock->uniform(Material::SpriteSizeUniformName)->setFloatVector(size.Data()); + instanceBlock->uniform(Material::ColorUniformName)->setFloatVector(Colorf::White.Data()); + + Matrix4x4f worldMatrix = Matrix4x4f::Translation(center.X, center.Y, 0.0f); + worldMatrix.RotateZ(animTime * -0.2f); + worldMatrix.Translate(size.X * -0.5f, size.Y * -0.5f, 0.0f); + command->setTransformation(worldMatrix); + command->setLayer(100); + command->material().setTexture(*base->TextureDiffuse.get()); + + renderQueue.addCommand(command); + } + + // 32 + { + GenericGraphicResource* base = res32->Base; + base->TextureDiffuse->setWrap(SamplerWrapping::Repeat); + + constexpr float repeats = 56.0f; + float scale = (0.6f + 0.04f * sinf(animTime * 0.2f)) * repeats; + Vector2f size = base->FrameDimensions.As() * scale; + + Vector2f centerBg = center; + centerBg.X += 96.0f * sinf(animTime * 0.37f); + centerBg.Y += 96.0f * cosf(animTime * 0.31f); + + auto command = _canvasBackground->RentRenderCommand(); + if (command->material().setShaderProgramType(Material::ShaderProgramType::SPRITE)) { + command->material().reserveUniformsDataMemory(); + command->geometry().setDrawParameters(GL_TRIANGLE_STRIP, 0, 4); + // Required to reset render command properly + //command->setTransformation(command->transformation()); + + GLUniformCache* textureUniform = command->material().uniform(Material::TextureUniformName); + if (textureUniform && textureUniform->intValue(0) != 0) { + textureUniform->setIntValue(0); // GL_TEXTURE0 + } + } + + command->material().setBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + auto instanceBlock = command->material().uniformBlock(Material::InstanceBlockName); + instanceBlock->uniform(Material::TexRectUniformName)->setFloatValue(repeats, 0.0f, repeats, 0.0f); + instanceBlock->uniform(Material::SpriteSizeUniformName)->setFloatVector(size.Data()); + instanceBlock->uniform(Material::ColorUniformName)->setFloatVector(Colorf::White.Data()); + + Matrix4x4f worldMatrix = Matrix4x4f::Translation(centerBg.X, centerBg.Y, 0.0f); + worldMatrix.RotateZ(animTime * 0.4f); + worldMatrix.Translate(size.X * -0.5f, size.Y * -0.5f, 0.0f); + command->setTransformation(worldMatrix); + command->setLayer(110); + command->material().setTexture(*base->TextureDiffuse.get()); + + renderQueue.addCommand(command); + } + + // 128 + { + GenericGraphicResource* base = res128->Base; + base->TextureDiffuse->setWrap(SamplerWrapping::Repeat); + + constexpr float repeats = 20.0f; + float scale = (0.6f + 0.2f * sinf(animTime * 0.4f)) * repeats; + Vector2f size = base->FrameDimensions.As() * scale; + + Vector2f centerBg = center; + centerBg.X += 64.0f * sinf(animTime * 0.25f); + centerBg.Y += 64.0f * cosf(animTime * 0.32f); + + auto command = _canvasBackground->RentRenderCommand(); + if (command->material().setShaderProgramType(Material::ShaderProgramType::SPRITE)) { + command->material().reserveUniformsDataMemory(); + command->geometry().setDrawParameters(GL_TRIANGLE_STRIP, 0, 4); + // Required to reset render command properly + //command->setTransformation(command->transformation()); + + GLUniformCache* textureUniform = command->material().uniform(Material::TextureUniformName); + if (textureUniform && textureUniform->intValue(0) != 0) { + textureUniform->setIntValue(0); // GL_TEXTURE0 + } + } + + command->material().setBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + auto instanceBlock = command->material().uniformBlock(Material::InstanceBlockName); + instanceBlock->uniform(Material::TexRectUniformName)->setFloatValue(repeats, 0.0f, repeats, 0.0f); + instanceBlock->uniform(Material::SpriteSizeUniformName)->setFloatVector(size.Data()); + instanceBlock->uniform(Material::ColorUniformName)->setFloatVector(Colorf::White.Data()); + + Matrix4x4f worldMatrix = Matrix4x4f::Translation(centerBg.X, centerBg.Y, 0.0f); + worldMatrix.RotateZ(animTime * 0.3f); + worldMatrix.Translate(size.X * -0.5f, size.Y * -0.5f, 0.0f); + command->setTransformation(worldMatrix); + command->setLayer(120); + command->material().setTexture(*base->TextureDiffuse.get()); + + renderQueue.addCommand(command); + } + + DrawElement(MenuGlow, 0, center.X, 70.0f, 130, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.14f), 16.0f, 10.0f, true); + } + void MainMenu::TexturedBackgroundPass::Initialize() { bool notInitialized = (_view == nullptr); diff --git a/Sources/Jazz2/UI/Menu/MainMenu.h b/Sources/Jazz2/UI/Menu/MainMenu.h index 72e7e08d..9c48e4f5 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.h +++ b/Sources/Jazz2/UI/Menu/MainMenu.h @@ -190,6 +190,7 @@ namespace Jazz2::UI::Menu void PrepareTexturedBackground(); bool TryLoadBackgroundPreset(Preset preset); void RenderTexturedBackground(RenderQueue& renderQueue); + bool RenderLegacyBackground(RenderQueue& renderQueue); inline Canvas* GetActiveCanvas() { diff --git a/Sources/Jazz2/UI/Menu/MenuResources.h b/Sources/Jazz2/UI/Menu/MenuResources.h index 5f91400f..7bc5e9b7 100644 --- a/Sources/Jazz2/UI/Menu/MenuResources.h +++ b/Sources/Jazz2/UI/Menu/MenuResources.h @@ -33,5 +33,8 @@ namespace Jazz2::UI::Menu::Resources static constexpr AnimState GamepadRightShoulder = (AnimState)54; static constexpr AnimState GamepadRightStick = (AnimState)55; static constexpr AnimState GamepadRightTrigger = (AnimState)56; + static constexpr AnimState Menu16 = (AnimState)70; + static constexpr AnimState Menu32 = (AnimState)71; + static constexpr AnimState Menu128 = (AnimState)72; static constexpr AnimState LoriExistsCheck = (AnimState)80; } \ No newline at end of file diff --git a/Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp b/Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp index 89759fa6..ad7a4ae1 100644 --- a/Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp +++ b/Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp @@ -183,7 +183,7 @@ namespace Jazz2::UI::Menu _root->DrawElement(MenuGlow, 0, x, center.Y + 28.0f, IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.2f), (Utf8::GetLength(playerTypes[j]) + 3) * 0.4f, 2.2f, true); _root->DrawStringShadow(playerTypes[j], charOffset, x, center.Y + 28.0f, IMenuContainer::FontLayer, - Alignment::Center, playerColors[j], 1.0f, 0.4f, 0.55f, 0.55f, 0.8f, 0.9f); + Alignment::Center, playerColors[j], 1.0f, 0.4f, 0.9f, 0.9f, 0.8f, 0.9f); } else { _root->DrawStringShadow(playerTypes[j], charOffset, x, center.Y + 28.0f, IMenuContainer::FontLayer, Alignment::Center, Font::DefaultColor, 0.8f, 0.0f, 4.0f, 4.0f, 0.4f, 0.9f); @@ -204,7 +204,7 @@ namespace Jazz2::UI::Menu _root->DrawElement(MenuGlow, 0, center.X + (j - 1) * 100.0f, center.Y + 28.0f, IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.2f), (Utf8::GetLength(difficultyTypes[j]) + 3) * 0.4f, 2.2f, true); _root->DrawStringShadow(difficultyTypes[j], charOffset, center.X + (j - 1) * 100.0f, center.Y + 28.0f, IMenuContainer::FontLayer, - Alignment::Center, Colorf(0.45f, 0.45f, 0.45f, 0.5f), 1.0f, 0.4f, 0.55f, 0.55f, 0.8f, 0.9f); + Alignment::Center, Colorf(0.45f, 0.45f, 0.45f, 0.5f), 1.0f, 0.4f, 0.9f, 0.9f, 0.8f, 0.9f); } else { _root->DrawStringShadow(difficultyTypes[j], charOffset, center.X + (j - 1) * 100.0f, center.Y + 28.0f, IMenuContainer::FontLayer, Alignment::Center, Font::DefaultColor, 0.8f, 0.0f, 4.0f, 4.0f, 0.9f); diff --git a/Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp b/Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp index 840f154d..d8ab8870 100644 --- a/Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp +++ b/Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp @@ -37,7 +37,7 @@ namespace Jazz2::UI::Menu constexpr float topLine = 131.0f; _root->DrawElement(MenuDim, center.X, topLine - 2.0f, IMenuContainer::BackgroundLayer, - Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, 0.7f, 0.0f)); + Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, -0.7f, 0.7f)); _root->DrawElement(MenuLine, 0, center.X, topLine, IMenuContainer::MainLayer, Alignment::Center, Colorf::White, 1.6f); int32_t charOffset = 0; diff --git a/Sources/Main.cpp b/Sources/Main.cpp index dca78aa2..71507783 100644 --- a/Sources/Main.cpp +++ b/Sources/Main.cpp @@ -31,6 +31,7 @@ #include "Jazz2/UI/Menu/SimpleMessageSection.h" #include "Jazz2/Compatibility/JJ2Anims.h" +#include "Jazz2/Compatibility/JJ2Data.h" #include "Jazz2/Compatibility/JJ2Episode.h" #include "Jazz2/Compatibility/JJ2Level.h" #include "Jazz2/Compatibility/JJ2Strings.h" @@ -940,12 +941,18 @@ void GameEventHandler::RefreshCache() String animationsPath = fs::CombinePath(resolver.GetCachePath(), "Animations"_s); fs::RemoveDirectoryRecursive(animationsPath); - if (!Compatibility::JJ2Anims::Convert(animsPath, animationsPath, false)) { + Compatibility::JJ2Version version = Compatibility::JJ2Anims::Convert(animsPath, animationsPath); + if (version == Compatibility::JJ2Version::Unknown) { LOGE("Provided Jazz Jackrabbit 2 version is not supported. Make sure supported Jazz Jackrabbit 2 version is present in \"%s\" directory.", resolver.GetSourcePath().data()); _flags |= Flags::IsVerified; return; } + Compatibility::JJ2Data data; + if (data.Open(fs::CombinePath(resolver.GetSourcePath(), "Data.j2d"_s), false)) { + data.Convert(resolver.GetCachePath(), version); + } + RefreshCacheLevels(); LOGI("Cache was recreated"); diff --git a/Sources/nCine/Primitives/Vector2.h b/Sources/nCine/Primitives/Vector2.h index f55d4aa6..a35bc062 100644 --- a/Sources/nCine/Primitives/Vector2.h +++ b/Sources/nCine/Primitives/Vector2.h @@ -73,6 +73,7 @@ namespace nCine static T Dot(const Vector2& v1, const Vector2& v2); static Vector2 Lerp(const Vector2& a, const Vector2& b, float t); + static Vector2 FromAngleLength(T angle, T length); /// A vector with all zero elements static const Vector2 Zero; @@ -323,6 +324,12 @@ namespace nCine return Vector2(t * (b.X - a.X) + a.X, t * (b.Y - a.Y) + a.Y); } + template + inline Vector2 Vector2::FromAngleLength(T angle, T length) + { + return Vector2((T)sinf(angle) * length, (T)cosf(angle) * -length); + } + template const Vector2 Vector2::Zero(0, 0); template