From e0c76d4508aef3cd10c1b93f372bd94d4817c3c7 Mon Sep 17 00:00:00 2001 From: Death Killer <884052+deathkiller@users.noreply.github.com> Date: Sun, 10 Dec 2023 15:19:30 +0100 Subject: [PATCH] Episode logos, fixed Esc in main menu, improved Sounds menu, custom levels list async loading, minor changes --- .../Actors/Solid/PowerUpMorphMonitor.cpp | 8 +- Sources/Jazz2/Compatibility/JJ2Anims.h | 2 +- Sources/Jazz2/Compatibility/JJ2Episode.cpp | 61 ++++++++++--- Sources/Jazz2/Compatibility/JJ2Episode.h | 14 ++- Sources/Jazz2/Compatibility/JJ2Strings.cpp | 6 +- Sources/Jazz2/ContentResolver.cpp | 18 +++- Sources/Jazz2/ContentResolver.h | 4 +- Sources/Jazz2/PreferencesCache.cpp | 4 + Sources/Jazz2/Resources.h | 1 + Sources/Jazz2/UI/Menu/AboutSection.cpp | 18 ++-- Sources/Jazz2/UI/Menu/BeginSection.cpp | 4 +- .../UI/Menu/CustomLevelSelectSection.cpp | 87 +++++++++++++------ .../Jazz2/UI/Menu/CustomLevelSelectSection.h | 9 ++ .../Jazz2/UI/Menu/EpisodeSelectSection.cpp | 22 +++-- Sources/Jazz2/UI/Menu/IMenuContainer.h | 1 + Sources/Jazz2/UI/Menu/InGameMenu.cpp | 8 ++ Sources/Jazz2/UI/Menu/InGameMenu.h | 1 + .../Jazz2/UI/Menu/LanguageSelectSection.cpp | 2 +- Sources/Jazz2/UI/Menu/MainMenu.cpp | 8 ++ Sources/Jazz2/UI/Menu/MainMenu.h | 1 + .../Jazz2/UI/Menu/SoundsOptionsSection.cpp | 35 +++++--- Sources/Jazz2/UI/Menu/SoundsOptionsSection.h | 2 + Sources/Main.cpp | 12 +-- Sources/Shared/Containers/ArrayView.h | 22 ++++- Sources/Shared/Containers/Pair.h | 67 ++++++++++---- Sources/Shared/Containers/SequenceHelpers.h | 41 +++++++++ Sources/Shared/Containers/StaticArray.h | 79 ++++++++++++++++- cmake/ncine_headers.cmake | 1 + 28 files changed, 420 insertions(+), 118 deletions(-) create mode 100644 Sources/Shared/Containers/SequenceHelpers.h diff --git a/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.cpp b/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.cpp index dd20900f..f1bd1eac 100644 --- a/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.cpp +++ b/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.cpp @@ -43,8 +43,8 @@ namespace Jazz2::Actors::Solid auto& players = _levelHandler->GetPlayers(); for (auto& player : players) { std::optional playerType = GetTargetType(player->GetPlayerType()); - if (playerType.has_value()) { - switch (playerType.value()) { + if (playerType) { + switch (*playerType) { case PlayerType::Jazz: PreloadMetadataAsync("Interactive/PlayerJazz"_s); break; case PlayerType::Spaz: PreloadMetadataAsync("Interactive/PlayerSpaz"_s); break; case PlayerType::Lori: PreloadMetadataAsync("Interactive/PlayerLori"_s); break; @@ -108,8 +108,8 @@ namespace Jazz2::Actors::Solid void PowerUpMorphMonitor::DestroyAndApplyToPlayer(Player* player) { std::optional playerType = GetTargetType(player->GetPlayerType()); - if (playerType.has_value()) { - player->MorphTo(playerType.value()); + if (playerType) { + player->MorphTo(*playerType); DecreaseHealth(INT32_MAX, player); PlaySfx("Break"_s); diff --git a/Sources/Jazz2/Compatibility/JJ2Anims.h b/Sources/Jazz2/Compatibility/JJ2Anims.h index 32322249..04365354 100644 --- a/Sources/Jazz2/Compatibility/JJ2Anims.h +++ b/Sources/Jazz2/Compatibility/JJ2Anims.h @@ -19,7 +19,7 @@ namespace Jazz2::Compatibility class JJ2Anims // .j2a { public: - static constexpr uint16_t CacheVersion = 11; + static constexpr uint16_t CacheVersion = 12; static bool Convert(const StringView& path, const StringView& targetPath, bool isPlus); diff --git a/Sources/Jazz2/Compatibility/JJ2Episode.cpp b/Sources/Jazz2/Compatibility/JJ2Episode.cpp index 78fa1b63..4b8d2648 100644 --- a/Sources/Jazz2/Compatibility/JJ2Episode.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Episode.cpp @@ -1,4 +1,6 @@ #include "JJ2Episode.h" +#include "JJ2Anims.h" +#include "JJ2Anims.Palettes.h" #include "../ContentResolver.h" #include "../../nCine/Base/Algorithms.h" @@ -9,6 +11,16 @@ using namespace Death::IO; namespace Jazz2::Compatibility { + JJ2Episode::JJ2Episode() + : Position(0), TitleWidth(0), TitleHeight(0) + { + } + + JJ2Episode::JJ2Episode(const String& name, const String& displayName, const String& firstLevel, int32_t position) + : Name(name), DisplayName(displayName), FirstLevel(firstLevel), Position(position), TitleWidth(0), TitleHeight(0) + { + } + bool JJ2Episode::Open(const StringView& path) { auto s = fs::Open(path, FileAccessMode::Read); @@ -67,30 +79,32 @@ namespace Jazz2::Compatibility FirstLevel = String(tmpBuffer, length); lowercaseInPlace(FirstLevel); - // TODO: Episode images are not supported yet /*int32_t width =*/ s->ReadValue(); /*int32_t height =*/ s->ReadValue(); /*int32_t unknown2 =*/ s->ReadValue(); /*int32_t unknown3 =*/ s->ReadValue(); - /*int32_t titleWidth =*/ s->ReadValue(); - /*int32_t titleHeight =*/ s->ReadValue(); + TitleWidth = s->ReadValue(); + TitleHeight = s->ReadValue(); /*int32_t unknown4 =*/ s->ReadValue(); /*int32_t unknown5 =*/ s->ReadValue(); - // TODO - /*{ - int imagePackedSize = s->ReadValue(); - int imageUnpackedSize = width * height; - JJ2Block imageBlock(s, imagePackedSize, imageUnpackedSize); + { + int32_t imagePackedSize = s->ReadValue(); + //int imageUnpackedSize = width * height; + //JJ2Block imageBlock(s, imagePackedSize, imageUnpackedSize); //episode.image = ConvertIndicesToRgbaBitmap(width, height, imageBlock, false); + + // Skip it for now + s->Seek(imagePackedSize, SeekOrigin::Current); } { - int titleLightPackedSize = s->ReadValue(); - int titleLightUnpackedSize = titleWidth * titleHeight; + int32_t titleLightPackedSize = s->ReadValue(); + int32_t titleLightUnpackedSize = TitleWidth * TitleHeight; JJ2Block titleLightBlock(s, titleLightPackedSize, titleLightUnpackedSize); - episode.titleLight = ConvertIndicesToRgbaBitmap(titleWidth, titleHeight, titleLightBlock, true); - }*/ + TitleData = std::make_unique(titleLightUnpackedSize); + titleLightBlock.ReadRawBytes(TitleData.get(), titleLightUnpackedSize); + } //{ // int titleDarkPackedSize = r.ReadInt32(); // int titleDarkUnpackedSize = titleWidth * titleHeight; @@ -161,5 +175,28 @@ namespace Jazz2::Compatibility so->WriteValue(0); so->WriteValue(0); } + + // Write episode logo + so->WriteValue((uint16_t)TitleWidth); + so->WriteValue((uint16_t)TitleHeight); + + uint32_t titlePixelsCount = TitleWidth * TitleHeight; + std::unique_ptr titlePixels = std::make_unique(titlePixelsCount * 4); + for (uint32_t i = 0; i < titlePixelsCount; i++) { + uint8_t colorIdx = TitleData[i]; + + // Remove shadow + if (colorIdx == 63 || colorIdx == 143) { + colorIdx = 0; + } + + const Color& src = MenuPalette[colorIdx]; + titlePixels[i * 4] = src.R; + titlePixels[i * 4 + 1] = src.G; + titlePixels[i * 4 + 2] = src.B; + titlePixels[i * 4 + 3] = src.A; + } + + JJ2Anims::WriteImageToFileInternal(so, titlePixels.get(), TitleWidth, TitleHeight, 4); } } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Episode.h b/Sources/Jazz2/Compatibility/JJ2Episode.h index 1c4aae63..385d20e1 100644 --- a/Sources/Jazz2/Compatibility/JJ2Episode.h +++ b/Sources/Jazz2/Compatibility/JJ2Episode.h @@ -23,19 +23,15 @@ namespace Jazz2::Compatibility String FirstLevel; String PreviousEpisode; String NextEpisode; + int32_t TitleWidth; + int32_t TitleHeight; + std::unique_ptr TitleData; - JJ2Episode() { } - - JJ2Episode(const String& name, const String& displayName, const String& firstLevel, int position) - : Name(name), DisplayName(displayName), FirstLevel(firstLevel), Position(position) - { - } + JJ2Episode(); + JJ2Episode(const String& name, const String& displayName, const String& firstLevel, int32_t position); bool Open(const StringView& path); void Convert(const String& targetPath, std::function levelTokenConversion = nullptr, std::function episodeNameConversion = nullptr, std::function(JJ2Episode*)> episodePrevNext = nullptr); - - private: - //std::unique_ptr _titleLight; }; } \ No newline at end of file diff --git a/Sources/Jazz2/Compatibility/JJ2Strings.cpp b/Sources/Jazz2/Compatibility/JJ2Strings.cpp index dc5dd640..64425067 100644 --- a/Sources/Jazz2/Compatibility/JJ2Strings.cpp +++ b/Sources/Jazz2/Compatibility/JJ2Strings.cpp @@ -8,7 +8,7 @@ using namespace Death::Containers::Literals; using namespace Death::IO; using namespace nCine; -static constexpr uint16_t Windows1250_Utf8[256] = { +static const uint16_t Windows1250_Utf8[256] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, @@ -27,7 +27,7 @@ static constexpr uint16_t Windows1250_Utf8[256] = { 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 }; -static constexpr uint32_t DefaultFontColors[] = { +static const uint32_t DefaultFontColors[] = { 0x707485, 0x409062, 0x629040, @@ -239,7 +239,7 @@ namespace Jazz2::Compatibility colorFrozen = false; colorIndex = 0; } - } else if (current == '|' && colorRandom) { + } else if (current == '|') { // Skip one color if (!colorFrozen) { colorIndex++; diff --git a/Sources/Jazz2/ContentResolver.cpp b/Sources/Jazz2/ContentResolver.cpp index 07a9eb27..af216fb6 100644 --- a/Sources/Jazz2/ContentResolver.cpp +++ b/Sources/Jazz2/ContentResolver.cpp @@ -1229,7 +1229,7 @@ namespace Jazz2 } } - std::optional ContentResolver::GetEpisode(const StringView& name) + std::optional ContentResolver::GetEpisode(const StringView& name, bool withLogo) { String fullPath = fs::CombinePath({ GetContentPath(), "Episodes"_s, name + ".j2e"_s }); if (!fs::IsReadableFile(fullPath)) { @@ -1238,7 +1238,7 @@ namespace Jazz2 return GetEpisodeByPath(fullPath); } - std::optional ContentResolver::GetEpisodeByPath(const StringView& path) + std::optional ContentResolver::GetEpisodeByPath(const StringView& path, bool withLogo) { auto s = fs::Open(path, FileAccessMode::Read); if (s->GetSize() < 16) { @@ -1274,6 +1274,20 @@ namespace Jazz2 episode.NextEpisode = String(NoInit, nameLength); s->Read(episode.NextEpisode.data(), nameLength); + if (withLogo && !_isHeadless) { + std::uint16_t titleWidth = s->ReadValue(); + std::uint16_t titleHeight = s->ReadValue(); + if (titleWidth > 0 && titleHeight > 0) { + std::unique_ptr pixels = std::make_unique(titleWidth * titleHeight); + ReadImageFromFile(s, (std::uint8_t*)pixels.get(), titleWidth, titleHeight, 4); + + episode.TitleLogo = std::make_unique(path.data(), Texture::Format::RGBA8, titleWidth, titleHeight); + episode.TitleLogo->loadFromTexels((unsigned char*)pixels.get(), 0, 0, titleWidth, titleHeight); + episode.TitleLogo->setMinFiltering(SamplerFilter::Nearest); + episode.TitleLogo->setMagFiltering(SamplerFilter::Nearest); + } + } + return episode; } diff --git a/Sources/Jazz2/ContentResolver.h b/Sources/Jazz2/ContentResolver.h index b8eb61ef..b4787bd9 100644 --- a/Sources/Jazz2/ContentResolver.h +++ b/Sources/Jazz2/ContentResolver.h @@ -73,8 +73,8 @@ namespace Jazz2 bool TryLoadLevel(const StringView& path, GameDifficulty difficulty, LevelDescriptor& descriptor); void ApplyDefaultPalette(); - std::optional GetEpisode(const StringView& name); - std::optional GetEpisodeByPath(const StringView& path); + std::optional GetEpisode(const StringView& name, bool withLogo = false); + std::optional GetEpisodeByPath(const StringView& path, bool withLogo = false); std::unique_ptr GetMusic(const StringView& path); UI::Font* GetFont(FontType fontType); Shader* GetShader(PrecompiledShader shader); diff --git a/Sources/Jazz2/PreferencesCache.cpp b/Sources/Jazz2/PreferencesCache.cpp index d87580d9..e33c98d9 100644 --- a/Sources/Jazz2/PreferencesCache.cpp +++ b/Sources/Jazz2/PreferencesCache.cpp @@ -447,6 +447,10 @@ namespace Jazz2 void PreferencesCache::RemoveEpisodeContinue(const StringView& episodeName) { + if (episodeName.empty() || episodeName == "unknown"_s) { + return; + } + _episodeContinue.erase(String::nullTerminatedView(episodeName)); } diff --git a/Sources/Jazz2/Resources.h b/Sources/Jazz2/Resources.h index d6cca88a..6c222754 100644 --- a/Sources/Jazz2/Resources.h +++ b/Sources/Jazz2/Resources.h @@ -112,6 +112,7 @@ namespace Jazz2 String PreviousEpisode; String NextEpisode; std::uint16_t Position; + std::unique_ptr TitleLogo; Episode() noexcept; }; diff --git a/Sources/Jazz2/UI/Menu/AboutSection.cpp b/Sources/Jazz2/UI/Menu/AboutSection.cpp index 71ef0b20..8e173ca2 100644 --- a/Sources/Jazz2/UI/Menu/AboutSection.cpp +++ b/Sources/Jazz2/UI/Menu/AboutSection.cpp @@ -36,7 +36,7 @@ #endif #if defined(WITH_SDL) -# define _i6 ", SDL" +# define _i6 ", SDL2" #else # define _i6 "" #endif @@ -87,19 +87,25 @@ # define _i13 "" #endif -#if defined(DEATH_CPU_USE_RUNTIME_DISPATCH) -# define _i14 ", GNU IFUNC" +#if defined(WITH_MULTIPLAYER) +# define _i14 ", ENet" #else # define _i14 "" #endif -#if defined(WITH_TRACY) -# define _i15 "\n\nTracy integration is enabled!" +#if defined(DEATH_CPU_USE_RUNTIME_DISPATCH) +# define _i15 ", GNU IFUNC" #else # define _i15 "" #endif -#define ADDITIONAL_INFO _i1 _i2 _i3 _i4 _i5 _i6 _i7 _i8 _i9 _i10 _i11 _i12 _i13 _i14 _i15 +#if defined(WITH_TRACY) +# define _i16 "\n\nTracy integration is enabled!" +#else +# define _i16 "" +#endif + +#define ADDITIONAL_INFO _i1 _i2 _i3 _i4 _i5 _i6 _i7 _i8 _i9 _i10 _i11 _i12 _i13 _i14 _i15 _i16 using namespace Jazz2::UI::Menu::Resources; diff --git a/Sources/Jazz2/UI/Menu/BeginSection.cpp b/Sources/Jazz2/UI/Menu/BeginSection.cpp index 7b39e1d8..3c71a3f8 100644 --- a/Sources/Jazz2/UI/Menu/BeginSection.cpp +++ b/Sources/Jazz2/UI/Menu/BeginSection.cpp @@ -121,10 +121,10 @@ namespace Jazz2::UI::Menu ExecuteSelected(); } else if (_root->ActionHit(PlayerActions::Menu)) { #if !defined(DEATH_TARGET_EMSCRIPTEN) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) - if (_selectedIndex != (int32_t)Item::Quit) { + if (_selectedIndex != (int32_t)_items.size() - 1) { _root->PlaySfx("MenuSelect"_s, 0.6f); _animation = 0.0f; - _selectedIndex = (int32_t)Item::Quit; + _selectedIndex = (int32_t)_items.size() - 1; } #endif } else if (_root->ActionHit(PlayerActions::Up)) { diff --git a/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp b/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp index 4d9522c8..389c3286 100644 --- a/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp +++ b/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp @@ -17,32 +17,23 @@ namespace Jazz2::UI::Menu CustomLevelSelectSection::CustomLevelSelectSection() : _selectedIndex(0), _animation(0.0f), _y(0.0f), _height(0.0f), _pressedCount(0), _noiseCooldown(0.0f) { - auto& resolver = ContentResolver::Get(); - - // Search both "Content/Episodes/" and "Cache/Episodes/" - fs::Directory dir(fs::CombinePath({ resolver.GetContentPath(), "Episodes"_s, "unknown"_s }), fs::EnumerationOptions::SkipDirectories); - while (true) { - StringView item = dir.GetNext(); - if (item == nullptr) { - break; - } - - AddLevel(item); - } - - fs::Directory dirCache(fs::CombinePath({ resolver.GetCachePath(), "Episodes"_s, "unknown"_s }), fs::EnumerationOptions::SkipDirectories); - while (true) { - StringView item = dirCache.GetNext(); - if (item == nullptr) { - break; - } - - AddLevel(item); - } +#if defined(WITH_THREADS) && !defined(DEATH_TARGET_EMSCRIPTEN) + _indexingThread.Run([](void* arg) { + auto* section = static_cast(arg); + section->AddCustomLevels(); + }, this); +#else + AddCustomLevels(); +#endif + } - quicksort(_items.begin(), _items.end(), [](const ItemData& a, const ItemData& b) -> bool { - return (a.LevelName < b.LevelName); - }); + CustomLevelSelectSection::~CustomLevelSelectSection() + { +#if defined(WITH_THREADS) && !defined(DEATH_TARGET_EMSCRIPTEN) + // Indicate that indexing thread should stop + _selectedIndex = -1; + _indexingThread.Join(); +#endif } Recti CustomLevelSelectSection::GetClipRectangle(const Vector2i& viewSize) @@ -151,7 +142,8 @@ namespace Jazz2::UI::Menu float column1 = viewSize.X * 0.25f; float column2 = viewSize.X * 0.52f; - for (int32_t i = 0; i < _items.size(); i++) { + std::size_t itemsCount = _items.size(); + for (int32_t i = 0; i < itemsCount; i++) { _items[i].Y = center.Y; if (center.Y > TopLine - ItemHeight && center.Y < bottomLine + ItemHeight) { @@ -180,7 +172,7 @@ namespace Jazz2::UI::Menu if (_items[0].Y < TopLine + ItemHeight / 2) { _root->DrawElement(MenuGlow, 0, center.X, TopLine, 900, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 30.0f, 5.0f); } - if (_items[_items.size() - 1].Y > bottomLine - ItemHeight / 2) { + if (_items[itemsCount - 1].Y > bottomLine - ItemHeight / 2) { _root->DrawElement(MenuGlow, 0, center.X, bottomLine, 900, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 30.0f, 5.0f); } } @@ -223,7 +215,8 @@ namespace Jazz2::UI::Menu } float halfW = viewSize.X * 0.5f; - for (int32_t i = 0; i < _items.size(); i++) { + std::size_t itemsCount = _items.size(); + for (int32_t i = 0; i < itemsCount; i++) { if (std::abs(_touchLast.X - halfW) < 150.0f && std::abs(_touchLast.Y - _items[i].Y) < 22.0f) { if (_selectedIndex == i) { ExecuteSelected(); @@ -258,6 +251,44 @@ namespace Jazz2::UI::Menu } } + void CustomLevelSelectSection::AddCustomLevels() + { + auto& resolver = ContentResolver::Get(); + + // Search both "Content/Episodes/" and "Cache/Episodes/" + fs::Directory dir(fs::CombinePath({ resolver.GetContentPath(), "Episodes"_s, "unknown"_s }), fs::EnumerationOptions::SkipDirectories); + while (_selectedIndex >= 0) { + StringView item = dir.GetNext(); + if (item == nullptr) { + break; + } + + AddLevel(item); + } + + if (_selectedIndex < 0) { + return; + } + + fs::Directory dirCache(fs::CombinePath({ resolver.GetCachePath(), "Episodes"_s, "unknown"_s }), fs::EnumerationOptions::SkipDirectories); + while (_selectedIndex >= 0) { + StringView item = dirCache.GetNext(); + if (item == nullptr) { + break; + } + + AddLevel(item); + } + + if (_selectedIndex < 0) { + return; + } + + quicksort(_items.begin(), _items.end(), [](const ItemData& a, const ItemData& b) -> bool { + return (a.LevelName < b.LevelName); + }); + } + void CustomLevelSelectSection::AddLevel(const StringView& levelFile) { if (fs::GetExtension(levelFile) != "j2l"_s) { diff --git a/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.h b/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.h index 6baf2a0b..25238e67 100644 --- a/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.h +++ b/Sources/Jazz2/UI/Menu/CustomLevelSelectSection.h @@ -2,12 +2,17 @@ #include "MenuSection.h" +#if defined(WITH_THREADS) && !defined(DEATH_TARGET_EMSCRIPTEN) +# include "../../../nCine/Threading/Thread.h" +#endif + namespace Jazz2::UI::Menu { class CustomLevelSelectSection : public MenuSection { public: CustomLevelSelectSection(); + ~CustomLevelSelectSection(); Recti GetClipRectangle(const Vector2i& viewSize) override; @@ -38,9 +43,13 @@ namespace Jazz2::UI::Menu float _touchTime; int32_t _pressedCount; float _noiseCooldown; +#if defined(WITH_THREADS) && !defined(DEATH_TARGET_EMSCRIPTEN) + Thread _indexingThread; +#endif void ExecuteSelected(); void EnsureVisibleSelected(); + void AddCustomLevels(); void AddLevel(const StringView& levelFile); }; } \ No newline at end of file diff --git a/Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp b/Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp index 94c63233..11d4564d 100644 --- a/Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp +++ b/Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp @@ -155,12 +155,18 @@ namespace Jazz2::UI::Menu float expandedAnimation2 = std::min(_expandedAnimation * 6.0f, 1.0f); float expandedAnimation3 = (expandedAnimation2 * expandedAnimation2 * (3.0f - 2.0f * expandedAnimation2)); - _root->DrawElement(MenuGlow, 0, centerX, item.Y, IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.4f * size), (Utf8::GetLength(item.Item.Description.DisplayName) + 3) * 0.5f * size, 4.0f * size, true); + if (item.Item.Description.TitleLogo != nullptr) { + Vector2i titleSize = item.Item.Description.TitleLogo->size() / 2; + _root->DrawTexture(*item.Item.Description.TitleLogo, centerX, item.Y + 2.2f, IMenuContainer::FontLayer + 9, Alignment::Center, Vector2f(titleSize.X, titleSize.Y) * size * (1.0f - expandedAnimation3 * 0.2f) * 1.02f, Colorf(0.0f, 0.0f, 0.0f, 0.26f - expandedAnimation3 * 0.1f)); + _root->DrawTexture(*item.Item.Description.TitleLogo, centerX, item.Y, IMenuContainer::FontLayer + 10, Alignment::Center, Vector2f(titleSize.X, titleSize.Y) * size * (1.0f - expandedAnimation3 * 0.2f), Colorf(1.0f, 1.0f, 1.0f, 1.0f - expandedAnimation3 * 0.4f)); + } else { + _root->DrawElement(MenuGlow, 0, centerX, item.Y, IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.4f * size), (Utf8::GetLength(item.Item.Description.DisplayName) + 3) * 0.5f * size, 4.0f * size, true); - Colorf nameColor = Font::RandomColor; - nameColor.SetAlpha(0.5f - expandedAnimation3 * 0.15f); - _root->DrawStringShadow(item.Item.Description.DisplayName, charOffset, centerX, item.Y, IMenuContainer::FontLayer + 10, - Alignment::Center, nameColor, size, 0.7f, 1.1f, 1.1f, 0.4f, 0.9f); + Colorf nameColor = Font::RandomColor; + nameColor.SetAlpha(0.5f - expandedAnimation3 * 0.15f); + _root->DrawStringShadow(item.Item.Description.DisplayName, charOffset, centerX, item.Y, IMenuContainer::FontLayer + 10, + Alignment::Center, nameColor, size, 0.7f, 1.1f, 1.1f, 0.4f, 0.9f); + } if ((item.Item.Flags & EpisodeDataFlags::CanContinue) == EpisodeDataFlags::CanContinue) { float expandX = centerX + (item.Item.Description.DisplayName.size() + 3) * 2.8f * size + 40.0f; @@ -341,8 +347,8 @@ namespace Jazz2::UI::Menu } auto& resolver = ContentResolver::Get(); - std::optional description = resolver.GetEpisodeByPath(episodeFile); - if (description.has_value()) { + std::optional description = resolver.GetEpisodeByPath(episodeFile, true); + if (description) { #if defined(SHAREWARE_DEMO_ONLY) // Check if specified episode is unlocked, used only if compiled with SHAREWARE_DEMO_ONLY if (description->Name == "prince"_s && (PreferencesCache::UnlockedEpisodes & UnlockableEpisodes::FormerlyAPrince) == UnlockableEpisodes::None) return; @@ -354,7 +360,7 @@ namespace Jazz2::UI::Menu #endif auto& episode = _items.emplace_back(); - episode.Item.Description = std::move(description.value()); + episode.Item.Description = std::move(*description); episode.Item.Flags = EpisodeDataFlags::None; if (!resolver.LevelExists(episode.Item.Description.Name, episode.Item.Description.FirstLevel)) { diff --git a/Sources/Jazz2/UI/Menu/IMenuContainer.h b/Sources/Jazz2/UI/Menu/IMenuContainer.h index ad6c2141..12e29f24 100644 --- a/Sources/Jazz2/UI/Menu/IMenuContainer.h +++ b/Sources/Jazz2/UI/Menu/IMenuContainer.h @@ -58,6 +58,7 @@ namespace Jazz2::UI::Menu virtual void DrawElement(AnimState state, float x, float y, uint16_t z, Alignment align, const Colorf& color, const Vector2f& size, const Vector4f& texCoords) = 0; virtual void DrawSolid(float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color, bool additiveBlending = false) = 0; + virtual void DrawTexture(const Texture& texture, float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color) = 0; virtual Vector2f MeasureString(const StringView& text, float scale = 1.0f, float charSpacing = 1.0f, float lineSpacing = 1.0f) = 0; virtual void DrawStringShadow(const StringView& text, int32_t& charOffset, float x, float y, uint16_t z, Alignment align, const Colorf& color, float scale = 1.0f, float angleOffset = 0.0f, float varianceX = 4.0f, float varianceY = 4.0f, diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.cpp b/Sources/Jazz2/UI/Menu/InGameMenu.cpp index 703ad1ad..5f302938 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.cpp +++ b/Sources/Jazz2/UI/Menu/InGameMenu.cpp @@ -337,6 +337,14 @@ namespace Jazz2::UI::Menu currentCanvas->DrawSolid(adjustedPos, z, size, color, additiveBlending); } + void InGameMenu::DrawTexture(const Texture& texture, float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color) + { + Canvas* currentCanvas = GetActiveCanvas(); + Vector2f adjustedPos = Canvas::ApplyAlignment(align, Vector2f(x - currentCanvas->ViewSize.X * 0.5f, currentCanvas->ViewSize.Y * 0.5f - y), size); + + currentCanvas->DrawTexture(texture, adjustedPos, z, size, Vector4f(1.0f, 0.0f, -1.0f, 1.0f), color); + } + Vector2f InGameMenu::MeasureString(const StringView& text, float scale, float charSpacing, float lineSpacing) { return _smallFont->MeasureString(text, scale, charSpacing, lineSpacing); diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.h b/Sources/Jazz2/UI/Menu/InGameMenu.h index 5263a855..9b656d10 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.h +++ b/Sources/Jazz2/UI/Menu/InGameMenu.h @@ -50,6 +50,7 @@ namespace Jazz2::UI::Menu void DrawElement(AnimState state, float x, float y, uint16_t z, Alignment align, const Colorf& color, const Vector2f& size, const Vector4f& texCoords) override; void DrawSolid(float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color, bool additiveBlending = false) override; + void DrawTexture(const Texture& texture, float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color) override; Vector2f MeasureString(const StringView& text, float scale = 1.0f, float charSpacing = 1.0f, float lineSpacing = 1.0f) override; void DrawStringShadow(const StringView& text, int32_t& charOffset, float x, float y, uint16_t z, Alignment align, const Colorf& color, float scale = 1.0f, float angleOffset = 0.0f, float varianceX = 4.0f, float varianceY = 4.0f, diff --git a/Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp b/Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp index 01708f61..bbefc7f2 100644 --- a/Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp +++ b/Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp @@ -13,7 +13,7 @@ namespace Jazz2::UI::Menu auto& resolver = ContentResolver::Get(); auto& defaultLanguage = _items.emplace_back(); - defaultLanguage.Item.DisplayName = "English"_s; + defaultLanguage.Item.DisplayName = "English \f[c:0x707070]· en"_s; // Search both "Content/Translations/" and "Cache/Translations/" fs::Directory dir(fs::CombinePath(resolver.GetContentPath(), "Translations"_s), fs::EnumerationOptions::SkipDirectories); diff --git a/Sources/Jazz2/UI/Menu/MainMenu.cpp b/Sources/Jazz2/UI/Menu/MainMenu.cpp index 0950ceb9..9710cddc 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.cpp +++ b/Sources/Jazz2/UI/Menu/MainMenu.cpp @@ -456,6 +456,14 @@ namespace Jazz2::UI::Menu currentCanvas->DrawSolid(adjustedPos, z, size, color, additiveBlending); } + void MainMenu::DrawTexture(const Texture& texture, float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color) + { + Canvas* currentCanvas = GetActiveCanvas(); + Vector2f adjustedPos = Canvas::ApplyAlignment(align, Vector2f(x - currentCanvas->ViewSize.X * 0.5f, currentCanvas->ViewSize.Y * 0.5f - y), size); + + currentCanvas->DrawTexture(texture, adjustedPos, z, size, Vector4f(1.0f, 0.0f, -1.0f, 1.0f), color); + } + Vector2f MainMenu::MeasureString(const StringView& text, float scale, float charSpacing, float lineSpacing) { return _smallFont->MeasureString(text, scale, charSpacing, lineSpacing); diff --git a/Sources/Jazz2/UI/Menu/MainMenu.h b/Sources/Jazz2/UI/Menu/MainMenu.h index db735b6a..3fca5f7c 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.h +++ b/Sources/Jazz2/UI/Menu/MainMenu.h @@ -66,6 +66,7 @@ namespace Jazz2::UI::Menu void DrawElement(AnimState state, float x, float y, uint16_t z, Alignment align, const Colorf& color, const Vector2f& size, const Vector4f& texCoords) override; void DrawSolid(float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color, bool additiveBlending = false) override; + void DrawTexture(const Texture& texture, float x, float y, uint16_t z, Alignment align, const Vector2f& size, const Colorf& color) override; Vector2f MeasureString(const StringView& text, float scale = 1.0f, float charSpacing = 1.0f, float lineSpacing = 1.0f) override; void DrawStringShadow(const StringView& text, int32_t& charOffset, float x, float y, uint16_t z, Alignment align, const Colorf& color, float scale = 1.0f, float angleOffset = 0.0f, float varianceX = 4.0f, float varianceY = 4.0f, diff --git a/Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp b/Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp index 744bb3e0..eb0aaaea 100644 --- a/Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp +++ b/Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp @@ -9,7 +9,7 @@ using namespace Jazz2::UI::Menu::Resources; namespace Jazz2::UI::Menu { SoundsOptionsSection::SoundsOptionsSection() - : _selectedIndex(0), _animation(0.0f), _isDirty(false) + : _selectedIndex(0), _animation(0.0f), _isDirty(false), _pressedCooldown(0.0f), _pressedCount(0) { // TRANSLATORS: Menu item in Options > Sounds section _items[(int32_t)Item::MasterVolume].Name = _("Master Volume"); @@ -39,6 +39,9 @@ namespace Jazz2::UI::Menu if (_animation < 1.0f) { _animation = std::min(_animation + timeMult * 0.016f, 1.0f); } + if (_pressedCooldown < 1.0f) { + _pressedCooldown = std::min(_pressedCooldown + timeMult * 0.008f, 1.0f); + } if (_root->ActionHit(PlayerActions::Menu)) { _root->PlaySfx("MenuSelect"_s, 0.5f); @@ -60,20 +63,26 @@ namespace Jazz2::UI::Menu } else { _selectedIndex = 0; } - } else if (_root->ActionHit(PlayerActions::Left) || _root->ActionHit(PlayerActions::Right)) { - float* value; - switch (_selectedIndex) { - default: - case (int32_t)Item::MasterVolume: value = &PreferencesCache::MasterVolume; break; - case (int32_t)Item::SfxVolume: value = &PreferencesCache::SfxVolume; break; - case (int32_t)Item::MusicVolume: value = &PreferencesCache::MusicVolume; break; - } + } else if (_root->ActionPressed(PlayerActions::Left) || _root->ActionPressed(PlayerActions::Right)) { + if (_pressedCooldown >= 1.0f - (_pressedCount * 0.096f) || _root->ActionHit(PlayerActions::Left) || _root->ActionHit(PlayerActions::Right)) { + float* value; + switch (_selectedIndex) { + default: + case (int32_t)Item::MasterVolume: value = &PreferencesCache::MasterVolume; break; + case (int32_t)Item::SfxVolume: value = &PreferencesCache::SfxVolume; break; + case (int32_t)Item::MusicVolume: value = &PreferencesCache::MusicVolume; break; + } - *value = std::clamp(*value + (_root->ActionHit(PlayerActions::Left) ? -0.03f : 0.03f), 0.0f, 1.0f); + *value = std::clamp(*value + (_root->ActionPressed(PlayerActions::Left) ? -0.03f : 0.03f), 0.0f, 1.0f); - _root->ApplyPreferencesChanges(ChangedPreferencesType::Audio); - _root->PlaySfx("MenuSelect"_s, 0.6f); - _isDirty = true; + _root->ApplyPreferencesChanges(ChangedPreferencesType::Audio); + _root->PlaySfx("MenuSelect"_s, 0.6f); + _isDirty = true; + _pressedCooldown = 0.0f; + _pressedCount = std::min(_pressedCount + 6, 10); + } + } else { + _pressedCount = 0; } } diff --git a/Sources/Jazz2/UI/Menu/SoundsOptionsSection.h b/Sources/Jazz2/UI/Menu/SoundsOptionsSection.h index 854e3477..9998c3b5 100644 --- a/Sources/Jazz2/UI/Menu/SoundsOptionsSection.h +++ b/Sources/Jazz2/UI/Menu/SoundsOptionsSection.h @@ -33,5 +33,7 @@ namespace Jazz2::UI::Menu int32_t _selectedIndex; float _animation; bool _isDirty; + float _pressedCooldown; + int32_t _pressedCount; }; } \ No newline at end of file diff --git a/Sources/Main.cpp b/Sources/Main.cpp index a04bbf9f..bfbaa1c6 100644 --- a/Sources/Main.cpp +++ b/Sources/Main.cpp @@ -509,10 +509,10 @@ void GameEventHandler::ChangeLevel(LevelInitialization&& levelInit) PreferencesCache::RemoveEpisodeContinue(levelInit.LastEpisodeName); std::optional lastEpisode = ContentResolver::Get().GetEpisode(levelInit.LastEpisodeName); - if (lastEpisode.has_value()) { + if (lastEpisode) { // Redirect to next episode std::optional nextEpisode = ContentResolver::Get().GetEpisode(lastEpisode->NextEpisode); - if (nextEpisode.has_value()) { + if (nextEpisode) { levelInit.EpisodeName = lastEpisode->NextEpisode; levelInit.LevelName = nextEpisode->FirstLevel; } @@ -1422,7 +1422,7 @@ void GameEventHandler::WriteCacheDescriptor(const StringView& path, std::uint64_ void GameEventHandler::SaveEpisodeEnd(const LevelInitialization& levelInit) { - if (levelInit.LastEpisodeName.empty()) { + if (levelInit.LastEpisodeName.empty() || levelInit.LastEpisodeName == "unknown"_s) { return; } @@ -1454,13 +1454,13 @@ void GameEventHandler::SaveEpisodeEnd(const LevelInitialization& levelInit) void GameEventHandler::SaveEpisodeContinue(const LevelInitialization& levelInit) { if (levelInit.EpisodeName.empty() || levelInit.LevelName.empty() || - levelInit.EpisodeName == "unknown"_s || + levelInit.EpisodeName == "unknown"_s || levelInit.Difficulty == GameDifficulty::Multiplayer || (levelInit.EpisodeName == "prince"_s && levelInit.LevelName == "trainer"_s)) { return; } std::optional currentEpisode = ContentResolver::Get().GetEpisode(levelInit.EpisodeName); - if (!currentEpisode.has_value() || currentEpisode->FirstLevel == levelInit.LevelName) { + if (!currentEpisode || currentEpisode->FirstLevel == levelInit.LevelName) { return; } @@ -1474,7 +1474,7 @@ void GameEventHandler::SaveEpisodeContinue(const LevelInitialization& levelInit) } if (playerCount == 1) { - auto episodeContinue = PreferencesCache::GetEpisodeContinue(levelInit.EpisodeName, true); + auto* episodeContinue = PreferencesCache::GetEpisodeContinue(levelInit.EpisodeName, true); episodeContinue->LevelName = levelInit.LevelName; episodeContinue->State.Flags = EpisodeContinuationFlags::None; if (levelInit.CheatsUsed) { diff --git a/Sources/Shared/Containers/ArrayView.h b/Sources/Shared/Containers/ArrayView.h index 55ab0250..dddc80eb 100644 --- a/Sources/Shared/Containers/ArrayView.h +++ b/Sources/Shared/Containers/ArrayView.h @@ -783,6 +783,16 @@ namespace Death::Containers } private: +#if DEATH_CXX_STANDARD > 201402 + // There doesn't seem to be a way to call those directly, and I can't find any practical use of std::tuple_size, + // tuple_element etc. on C++11 and C++14, so this is defined only for newer standards. + template constexpr friend T& get(StaticArrayView value) { + return value._data[index]; + } + // As the view is non-owning, a rvalue doesn't imply that its contents are able to be moved out. Thus, unlike StaticArray or Pair/Triple, + // it takes the view by value and has no difference in behavior depending on whether the input is T&, const T& or T&&. +#endif + T* _data; }; @@ -911,4 +921,14 @@ namespace Death::Containers static_assert(end_ <= size_, "Slice out of range"); return StaticArrayView{_data + begin_}; } -} \ No newline at end of file +} + +/* C++17 structured bindings */ +#if DEATH_CXX_STANDARD > 201402 +namespace std +{ + // Note that `size` can't be used as it may conflict with std::size() in C++17 + template struct tuple_size> : integral_constant {}; + template struct tuple_element> { typedef T type; }; +} +#endif \ No newline at end of file diff --git a/Sources/Shared/Containers/Pair.h b/Sources/Shared/Containers/Pair.h index 6ed9e5e3..377750ae 100644 --- a/Sources/Shared/Containers/Pair.h +++ b/Sources/Shared/Containers/Pair.h @@ -1,6 +1,7 @@ // Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, // 2017, 2018, 2019, 2020, 2021, 2022, 2023 // Vladimír Vondruš and contributors +// Copyright © 2022 Stanislaw Halik // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), @@ -196,28 +197,20 @@ namespace Death::Containers } /** @brief First element */ - DEATH_CONSTEXPR14 F& first()& { - return _first; - } + DEATH_CONSTEXPR14 F& first() & { return _first; } + /** @overload */ // Not F&& because that'd cause nasty dangling reference issues in common code. - DEATH_CONSTEXPR14 F first()&& { - return std::move(_first); - } - constexpr const F& first() const& { - return _first; - } + DEATH_CONSTEXPR14 F first() && { return std::move(_first); } + /** @overload */ + constexpr const F& first() const & { return _first; } /** @brief Second element */ - DEATH_CONSTEXPR14 S& second()& { - return _second; - } + DEATH_CONSTEXPR14 S& second() & { return _second; } + /** @overload */ // Not S&& because that'd cause nasty dangling reference issues in common code. - DEATH_CONSTEXPR14 S second()&& { - return std::move(_second); - } - constexpr const S& second() const& { - return _second; - } + DEATH_CONSTEXPR14 S second() && { return std::move(_second); } + /** @overload */ + constexpr const S& second() const & { return _second; } // No const&& overloads right now. There's one theoretical use case, where an API could return a `const Pair`, // and then if there would be a `first() const&&` overload returning a `T` (and not `T&&`), it could get picked @@ -225,6 +218,32 @@ namespace Death::Containers // return a const value, so this isn't handled at the moment. private: + // For the conversion constructor + template friend class Pair; + +#if DEATH_CXX_STANDARD > 201402 + // There doesn't seem to be a way to call those directly, and I can't find any practical use of std::tuple_size, + // tuple_element etc. on C++11 and C++14, so this is defined only for newer standards. + template::type* = nullptr> constexpr friend const F& get(const Pair& value) { + return value._first; + } + template::type* = nullptr> DEATH_CONSTEXPR14 friend F& get(Pair& value) { + return value._first; + } + template::type* = nullptr> DEATH_CONSTEXPR14 friend F&& get(Pair&& value) { + return std::move(value._first); + } + template::type* = nullptr> constexpr friend const S& get(const Pair& value) { + return value._second; + } + template::type* = nullptr> DEATH_CONSTEXPR14 friend S& get(Pair& value) { + return value._second; + } + template::type* = nullptr> DEATH_CONSTEXPR14 friend S&& get(Pair&& value) { + return std::move(value._second); + } +#endif + F _first; S _second; }; @@ -241,4 +260,14 @@ namespace Death::Containers template inline auto pair(T&& other) -> decltype(Implementation::DeducedPairConverter::type>::from(std::forward(other))) { return Implementation::DeducedPairConverter::type>::from(std::forward(other)); } -} \ No newline at end of file +} + +/* C++17 structured bindings */ +#if DEATH_CXX_STANDARD > 201402 +namespace std +{ + template struct tuple_size> : integral_constant {}; + template struct tuple_element<0, Death::Containers::Pair> { typedef F type; }; + template struct tuple_element<1, Death::Containers::Pair> { typedef S type; }; +} +#endif \ No newline at end of file diff --git a/Sources/Shared/Containers/SequenceHelpers.h b/Sources/Shared/Containers/SequenceHelpers.h new file mode 100644 index 00000000..ed4c6504 --- /dev/null +++ b/Sources/Shared/Containers/SequenceHelpers.h @@ -0,0 +1,41 @@ +// Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, +// 2017, 2018, 2019, 2020, 2021, 2022, 2023 +// Vladimír Vondruš and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#pragma once + +#include "../Common.h" + +namespace Death::Containers::Implementation +{ + template struct Sequence {}; + + template struct SequenceConcat; + template struct SequenceConcat, Sequence> { + typedef Sequence Type; + }; + + template struct GenerateSequence : + SequenceConcat::Type, + typename GenerateSequence::Type> {}; + template<> struct GenerateSequence<1> { typedef Sequence<0> Type; }; + template<> struct GenerateSequence<0> { typedef Sequence<> Type; }; +} \ No newline at end of file diff --git a/Sources/Shared/Containers/StaticArray.h b/Sources/Shared/Containers/StaticArray.h index d9054074..f463e625 100644 --- a/Sources/Shared/Containers/StaticArray.h +++ b/Sources/Shared/Containers/StaticArray.h @@ -23,6 +23,7 @@ #pragma once #include "../CommonBase.h" +#include "SequenceHelpers.h" #include "Tags.h" #include @@ -117,6 +118,33 @@ namespace Death::Containers static_assert(sizeof...(args) == size_, "Containers::StaticArray: Wrong number of initializers"); } + /** + * @brief In-place construct an array by copying the elements from a fixed-size array + * + * Compared to @ref StaticArray(InPlaceInitT, Args&&... args) doesn't + * require the elements to have explicitly specified type. The array + * elements are copied to the array constructor, if you have a + * non-copyable type or want to move the elements, use + * @ref StaticArray(InPlaceInitT, T(&&)[size_]) instead. Same as + * @ref StaticArray(const T(&)[size_]). + */ + explicit StaticArray(InPlaceInitT, const T(&data)[size_]) : StaticArray{InPlaceInit, typename Implementation::GenerateSequence::Type{}, data} {} + +#if !defined(DEATH_MSVC2017_COMPATIBILITY) + /** + * @brief In-place construct an array by moving the elements from a fixed-size array + * + * Compared to @ref StaticArray(InPlaceInitT, Args&&... args) doesn't + * require the elements to have explicitly specified type. Same as + * @ref StaticArray(T(&&)[size_]). + * @partialsupport Not available on + * @ref DEATH_MSVC2015_COMPATIBILITY "MSVC 2015" and + * @ref DEATH_MSVC2017_COMPATIBILITY "MSVC 2017" as these + * compilers don't support moving arrays. + */ + explicit StaticArray(InPlaceInitT, T(&&data)[size_]) : StaticArray{InPlaceInit, typename Implementation::GenerateSequence::Type{}, std::move(data)} {} +#endif + /** * @brief Construct a value-initialized array * @@ -131,6 +159,26 @@ namespace Death::Containers */ template::value>::type> /*implicit*/ StaticArray(First&& first, Next&&... next) : StaticArray{InPlaceInit, std::forward(first), std::forward(next)...} {} + /** + * @brief In-place construct an array by copying the elements from a fixed-size array + * + * Alias to @ref StaticArray(InPlaceInitT, const T(&)[size_]). + */ + explicit StaticArray(const T(&data)[size_]) : StaticArray{InPlaceInit, data} {} + +#if !defined(DEATH_MSVC2017_COMPATIBILITY) + /** + * @brief In-place construct an array by moving the elements from a fixed-size array + * + * Alias to @ref StaticArray(InPlaceInitT, T(&&)[size_]). + * @partialsupport Not available on + * @ref DEATH_MSVC2015_COMPATIBILITY "MSVC 2015" and + * @ref DEATH_MSVC2017_COMPATIBILITY "MSVC 2017" as these + * compilers don't support moving arrays. + */ + explicit StaticArray(T(&&data)[size_]) : StaticArray{InPlaceInit, std::move(data)} {} +#endif + /** @brief Copy constructor */ StaticArray(const StaticArray& other) noexcept(std::is_nothrow_copy_constructible::value); @@ -405,6 +453,25 @@ namespace Death::Containers } private: +#if DEATH_CXX_STANDARD > 201402 + // There doesn't seem to be a way to call those directly, and I can't find any practical use of std::tuple_size, + // tuple_element etc. on C++11 and C++14, so this is defined only for newer standards. + template friend T& get(StaticArray& value) { + return value._data[index]; + } + template friend const T& get(const StaticArray& value) { + return value._data[index]; + } + template friend T&& get(StaticArray&& value) { + return std::move(value._data[index]); + } +#endif + + template explicit StaticArray(InPlaceInitT, Implementation::Sequence, const T(&data)[sizeof...(sequence)]) : _data{data[sequence]...} {} +#if !defined(DEATH_MSVC2017_COMPATIBILITY) + template explicit StaticArray(InPlaceInitT, Implementation::Sequence, T(&&data)[sizeof...(sequence)]) : _data{std::move(data[sequence])...} {} +#endif + union { T _data[size_]; }; @@ -583,4 +650,14 @@ namespace Death::Containers template struct ErasedStaticArrayViewConverter> : StaticArrayViewConverter> {}; template struct ErasedStaticArrayViewConverter> : StaticArrayViewConverter> {}; } -} \ No newline at end of file +} + +/* C++17 structured bindings */ +#if DEATH_CXX_STANDARD > 201402 +namespace std +{ + // Note that `size` can't be used as it may conflict with std::size() in C++17 + template struct tuple_size> : integral_constant {}; + template struct tuple_element> { typedef T type; }; +} +#endif \ No newline at end of file diff --git a/cmake/ncine_headers.cmake b/cmake/ncine_headers.cmake index c2ba6317..1475cf92 100644 --- a/cmake/ncine_headers.cmake +++ b/cmake/ncine_headers.cmake @@ -18,6 +18,7 @@ set(HEADERS ${NCINE_SOURCE_DIR}/Shared/Containers/GrowableArray.h ${NCINE_SOURCE_DIR}/Shared/Containers/Pair.h ${NCINE_SOURCE_DIR}/Shared/Containers/Reference.h + ${NCINE_SOURCE_DIR}/Shared/Containers/SequenceHelpers.h ${NCINE_SOURCE_DIR}/Shared/Containers/SmallVector.h ${NCINE_SOURCE_DIR}/Shared/Containers/StaticArray.h ${NCINE_SOURCE_DIR}/Shared/Containers/String.h