diff --git a/.gitmodules b/.gitmodules index 180fd589..904295c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "GameResources"] path = GameResources - url = https://github.com/Estrol/O2GameResources + url = https://github.com/AlberttFrgk/O2GameResources diff --git a/Engine/include/Game.h b/Engine/include/Game.h index 4e764017..d31eb36c 100644 --- a/Engine/include/Game.h +++ b/Engine/include/Game.h @@ -89,5 +89,5 @@ class Game GameThread mLocalThread; std::mutex m_mutex; - std::condition_variable m_conditionVariable; + std::condition_variable m_cv; }; diff --git a/Engine/include/Rendering/Window.h b/Engine/include/Rendering/Window.h index 6ce2e40d..4f3b575e 100644 --- a/Engine/include/Rendering/Window.h +++ b/Engine/include/Rendering/Window.h @@ -26,7 +26,7 @@ class GameWindow float GetHeightScale(); void SetScaleOutput(bool value); - bool IsScaleOutput(); + bool IsScaleOutput() const; void SetWindowTitle(std::string &title); void SetWindowSubTitle(std::string &subTitle); diff --git a/Engine/include/Texture/Sprite2D.h b/Engine/include/Texture/Sprite2D.h index 479f2435..55025cdf 100644 --- a/Engine/include/Texture/Sprite2D.h +++ b/Engine/include/Texture/Sprite2D.h @@ -18,10 +18,10 @@ class Sprite2D public: Sprite2D() = default; - Sprite2D(std::vector textures, float delay = 1.0f); - Sprite2D(std::vector textures, float delay = 1.0f); - Sprite2D(std::vector textures, float delay = 1.0f); - Sprite2D(std::vector textures, float delay = 1.0f); + Sprite2D(std::vector textures, double delay = 1.0); + Sprite2D(std::vector textures, double delay = 1.0); + Sprite2D(std::vector textures, double delay = 1.0); + Sprite2D(std::vector textures, double delay = 1.0); ~Sprite2D(); @@ -37,15 +37,18 @@ class Sprite2D void DrawOnce(double delta, bool manual = false); Texture2D *GetTexture(); - void SetFPS(float fps); + void SetFPS(double fps); void Reset(); -private: double m_spritespeed = 1.0; + +private: float m_currentTime = 0; int m_currentIndex = 0; bool m_drawOnce = false; std::vector m_textures; + + void DrawInternal(double delta, bool playOnce, Rect* rect, bool manual); }; diff --git a/Engine/include/Texture/Texture2D.h b/Engine/include/Texture/Texture2D.h index 8596abd0..72a4d7fa 100644 --- a/Engine/include/Texture/Texture2D.h +++ b/Engine/include/Texture/Texture2D.h @@ -20,9 +20,9 @@ class Texture2D public: Texture2D(); - Texture2D(std::string fileName); - Texture2D(std::filesystem::path path); - Texture2D(uint8_t *fileData, size_t size); + Texture2D(const std::string& fileName); + Texture2D(const std::filesystem::path& path); + Texture2D(const uint8_t *fileData, size_t size); Texture2D(SDL_Texture *texture); Texture2D(Texture2D_Vulkan *texture); ~Texture2D(); @@ -51,7 +51,7 @@ class Texture2D Rect GetOriginalRECT(); void SetOriginalRECT(Rect size); - static Texture2D *FromTexture2D(Texture2D *tex); + // static Texture2D *FromTexture2D(Texture2D *tex); static Texture2D *FromBMP(uint8_t *fileData, size_t size); static Texture2D *FromBMP(std::string fileName); diff --git a/Engine/src/Configuration.cpp b/Engine/src/Configuration.cpp index 1628296e..530a53f9 100644 --- a/Engine/src/Configuration.cpp +++ b/Engine/src/Configuration.cpp @@ -86,7 +86,7 @@ void Configuration::Set(std::string key, std::string prop, std::string value) void Configuration::Font_SetPath(std::filesystem::path path) { - FontPath = path; + FontPath = std::filesystem::current_path() / "Resources"; } std::filesystem::path Configuration::Font_GetPath() diff --git a/Engine/src/Fonts/FontResources.cpp b/Engine/src/Fonts/FontResources.cpp index 43b91ba1..7fc7d8bf 100644 --- a/Engine/src/Fonts/FontResources.cpp +++ b/Engine/src/Fonts/FontResources.cpp @@ -1,4 +1,4 @@ -#pragma warning(disable : 4838) // Goddamit +#pragma warning(disable : 4838) // Goddamit #pragma warning(disable : 4309) #include @@ -25,9 +25,9 @@ // BEGIN FONT FALLBACK #include "FallbackFonts/arial.ttf.h" -#include "FallbackFonts/ch.ttf.h" -#include "FallbackFonts/jp.ttf.h" -#include "FallbackFonts/kr.ttf.h" +//#include "FallbackFonts/ch.ttf.h" +//#include "FallbackFonts/jp.ttf.h" +//#include "FallbackFonts/kr.ttf.h" #include // END FONT FALLBACK @@ -92,14 +92,37 @@ void FontResources::PreloadFontCaches() GameWindow *wnd = GameWindow::GetInstance(); - auto skinPath = Configuration::Font_GetPath(); - auto fontPath = skinPath / "Fonts"; + auto path = Configuration::Font_GetPath(); + auto fontPath = path / "Fonts"; auto normalfont = fontPath / "normal.ttf"; auto jpFont = fontPath / "jp.ttf"; auto krFont = fontPath / "kr.ttf"; auto chFont = fontPath / "ch.ttf"; + static const ImWchar glyphRanges[] = { // Optimized + (ImWchar)0x0020, (ImWchar)0x052F, + (ImWchar)0x2000, (ImWchar)0x27BF, + (ImWchar)0x2E80, (ImWchar)0x2FA1, + (ImWchar)0x1F300, (ImWchar)0x1FAFF, + (ImWchar)0x2660, (ImWchar)0x2663, + (ImWchar)0x2665, (ImWchar)0x2666, + (ImWchar)0x2600, (ImWchar)0x2606, + (ImWchar)0x2618, (ImWchar)0x2619, + (ImWchar)0x263A, (ImWchar)0x263B, + (ImWchar)0x2708, (ImWchar)0x2714, + (ImWchar)0x2728, (ImWchar)0x2734, + (ImWchar)0x2740, (ImWchar)0x274B, + (ImWchar)0x2756, (ImWchar)0x2758, + (ImWchar)0x2764, (ImWchar)0x2767, + (ImWchar)0x2794, (ImWchar)0x27BE, + (ImWchar)0x27F0, (ImWchar)0x27FF, + (ImWchar)0x2900, (ImWchar)0x297F, + (ImWchar)0x2A00, (ImWchar)0x2AFF, + (ImWchar)0x0000, (ImWchar)0x0000 + }; + + { float originScale = (wnd->GetBufferWidth() + wnd->GetBufferHeight()) / 15.6f; float targetScale = (wnd->GetWidth() + wnd->GetHeight()) / 15.6f; @@ -118,9 +141,9 @@ void FontResources::PreloadFontCaches() { if (std::filesystem::exists(normalfont)) { - Font.Font = io.Fonts->AddFontFromFileTTF((const char *)normalfont.u8string().c_str(), fontSize, &conf); + Font.Font = io.Fonts->AddFontFromFileTTF((const char *)normalfont.u8string().c_str(), fontSize, &conf, glyphRanges); // glyphRanges, fixing missing fonts } else { - Font.Font = io.Fonts->AddFontFromMemoryTTF((void *)get_arial_font_data(), get_arial_font_size(), fontSize, &conf); + Font.Font = io.Fonts->AddFontFromMemoryTTF((void *)get_arial_font_data(), get_arial_font_size(), fontSize, &conf, glyphRanges); } } @@ -151,7 +174,7 @@ void FontResources::PreloadFontCaches() case TextRegion::Chinese: { if (std::filesystem::exists(chFont)) { - io.Fonts->AddFontFromFileTTF((const char *)chFont.u8string().c_str(), fontSize, &conf, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); + io.Fonts->AddFontFromFileTTF((const char *)chFont.u8string().c_str(), fontSize, &conf, io.Fonts->GetGlyphRangesChineseFull()); } break; diff --git a/Engine/src/Game.cpp b/Engine/src/Game.cpp index cad829e1..b79c5c5b 100644 --- a/Engine/src/Game.cpp +++ b/Engine/src/Game.cpp @@ -16,7 +16,7 @@ constexpr auto kInputDefaultRate = 1000.0; constexpr auto kMenuDefaultRate = 60.0; -constexpr auto kAudioDefaultRate = 24.0; +constexpr auto kAudioDefaultRate = 60.0; namespace { @@ -42,13 +42,14 @@ namespace { { const double targetFrameTime = 1000.0 / MaxFrameRate; - double newTick = SDL_GetTicks(); + double newTick = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); double frameTime = newTick - curTick; if (frameTime < targetFrameTime) { double delayTime = targetFrameTime - frameTime; - SDL_Delay(static_cast(delayTime)); + std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(delayTime))); + std::this_thread::yield(); newTick += delayTime; } @@ -83,7 +84,7 @@ Game::~Game() if (m_notify) { std::unique_lock lock(m_mutex); - m_conditionVariable.wait(lock, [this] { return !m_notify; }); + m_cv.wait(lock, [this] { return !m_notify; }); } } @@ -369,13 +370,12 @@ void Game::CheckFont() void Game::Stop() { if (m_running) { - m_running = false; - { - std::lock_guard lock(m_mutex); // TODO: Fix game does not properly exit while crash (if not just revert back to SDL_Delay Sleep) + std::unique_lock lock(m_mutex); + m_running = false; m_notify = true; } - m_conditionVariable.notify_one(); + m_cv.notify_one(); } } diff --git a/Engine/src/Rendering/GameWindow.cpp b/Engine/src/Rendering/GameWindow.cpp index 9d2cf067..60bdbb19 100644 --- a/Engine/src/Rendering/GameWindow.cpp +++ b/Engine/src/Rendering/GameWindow.cpp @@ -193,7 +193,7 @@ void GameWindow::SetScaleOutput(bool value) m_scaleOutput = value; } -bool GameWindow::IsScaleOutput() +bool GameWindow::IsScaleOutput() const { return m_scaleOutput; } diff --git a/Engine/src/Rendering/Renderer.cpp b/Engine/src/Rendering/Renderer.cpp index 294d2065..6ffb4e3c 100644 --- a/Engine/src/Rendering/Renderer.cpp +++ b/Engine/src/Rendering/Renderer.cpp @@ -52,11 +52,11 @@ bool Renderer::Create(RendererMode mode, GameWindow *window, bool failed) break; } - case RendererMode::DIRECTX11: + /*case RendererMode::DIRECTX11: { rendererName = "direct3d11"; break; - } + }*/ case RendererMode::DIRECTX12: { @@ -96,10 +96,10 @@ bool Renderer::Create(RendererMode mode, GameWindow *window, bool failed) } SDL_SetHint(SDL_HINT_RENDER_DRIVER, rendererName.c_str()); - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); // STOP CHANGING THIS, LEAVE IT "as is" + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); } - m_renderer = SDL_CreateRenderer(window->GetWindow(), -1, SDL_RENDERER_ACCELERATED); + m_renderer = SDL_CreateRenderer(window->GetWindow(), -1, SDL_RENDERER_ACCELERATED /*| SDL_RENDERER_PRESENTVSYNC*/); if (!m_renderer) { throw SDLException(); } diff --git a/Engine/src/Rendering/Vulkan/Texture2DVulkan.cpp b/Engine/src/Rendering/Vulkan/Texture2DVulkan.cpp index 3ff2d798..414790f8 100644 --- a/Engine/src/Rendering/Vulkan/Texture2DVulkan.cpp +++ b/Engine/src/Rendering/Vulkan/Texture2DVulkan.cpp @@ -17,7 +17,7 @@ static int m_textureCount static std::mutex m_textureMutex; static std::unique_ptr m_dummyTexture; -Texture2D_Vulkan *CreateTexture() +Texture2D_Vulkan* CreateTexture() { // find the empty slot for (int i = 0; i < m_textureCount; i++) { @@ -53,7 +53,7 @@ uint32_t vkTexture::FindMemoryType(uint32_t type_filter, uint32_t properties) return findMemoryType(VulkanEngine::GetInstance()->_chosenGPU, type_filter, properties); } -Texture2D_Vulkan *vkTexture::TexLoadImage(std::filesystem::path imagePath) +Texture2D_Vulkan* vkTexture::TexLoadImage(std::filesystem::path imagePath) { std::fstream fs(imagePath, std::ios::binary | std::ios::in); if (!fs.is_open()) { @@ -79,6 +79,14 @@ void InternalLoad( size_t image_size = static_cast(tex_data->Width) * static_cast(tex_data->Height) * tex_data->Channels; + // Premultiply alpha + for (size_t i = 0; i < image_size; i += tex_data->Channels) { // Fix white line issue + float alpha = image_data[i + 3] / 255.0f; + image_data[i] = static_cast(image_data[i] * alpha); + image_data[i + 1] = static_cast(image_data[i + 1] * alpha); + image_data[i + 2] = static_cast(image_data[i + 2] * alpha); + } + VkResult err; { VkImageCreateInfo info = {}; @@ -135,8 +143,8 @@ void InternalLoad( { VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = VK_FILTER_NEAREST; - samplerInfo.minFilter = VK_FILTER_NEAREST; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; @@ -184,7 +192,7 @@ void InternalLoad( } { - void *map = NULL; + void* map = NULL; err = vkMapMemory(vulkan_driver->_device, tex_data->UploadBufferMemory, 0, image_size, 0, &map); if (err != VK_SUCCESS) { throw std::runtime_error("Vulkan: Failed to create mapped image memory"); @@ -240,17 +248,17 @@ void InternalLoad( use_barrier[0].subresourceRange.levelCount = 1; use_barrier[0].subresourceRange.layerCount = 1; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); - }); + }); } -Texture2D_Vulkan *vkTexture::TexLoadImage(void *buffer, size_t size) +Texture2D_Vulkan* vkTexture::TexLoadImage(void* buffer, size_t size) { auto vulkan_driver = VulkanEngine::GetInstance(); auto tex_data = CreateTexture(); tex_data->Channels = 4; - unsigned char *image_data = stbi_load_from_memory( - (uint8_t *)buffer, + unsigned char* image_data = stbi_load_from_memory( + (uint8_t*)buffer, (int)size, &tex_data->Width, &tex_data->Height, @@ -265,7 +273,7 @@ Texture2D_Vulkan *vkTexture::TexLoadImage(void *buffer, size_t size) return tex_data; } -Texture2D_Vulkan *vkTexture::GetDummyImage() +Texture2D_Vulkan* vkTexture::GetDummyImage() { if (m_dummyTexture) { return m_dummyTexture.get(); @@ -278,8 +286,8 @@ Texture2D_Vulkan *vkTexture::GetDummyImage() // Generate file 1 Pixel Bitmap Image, with header std::vector buffer = ImageGenerator::GenerateImage(40, 40, { 255, 255, 255, 255 }); - unsigned char *image_data = stbi_load_from_memory( - (uint8_t *)buffer.data(), + unsigned char* image_data = stbi_load_from_memory( + (uint8_t*)buffer.data(), (int)buffer.size(), &m_dummyTexture->Width, &m_dummyTexture->Height, @@ -297,18 +305,18 @@ Texture2D_Vulkan *vkTexture::GetDummyImage() return m_dummyTexture.get(); } -VkDescriptorSet vkTexture::GetVkDescriptorSet(Texture2D_Vulkan *handle) +VkDescriptorSet vkTexture::GetVkDescriptorSet(Texture2D_Vulkan* handle) { return handle->DS; } -void vkTexture::QueryTexture(Texture2D_Vulkan *handle, int &outWidth, int &outHeight) +void vkTexture::QueryTexture(Texture2D_Vulkan* handle, int& outWidth, int& outHeight) { outWidth = handle->Width; outHeight = handle->Height; } -void vkTexture::ReleaseTexture(Texture2D_Vulkan *tex_data) +void vkTexture::ReleaseTexture(Texture2D_Vulkan* tex_data) { if (tex_data == nullptr) { return; @@ -326,14 +334,14 @@ void vkTexture::ReleaseTexture(Texture2D_Vulkan *tex_data) ImGui_ImplVulkan_RemoveTexture(tex_data->DS); - auto it = std::find_if(m_textures.begin(), m_textures.end(), [&](auto &pair) { + auto it = std::find_if(m_textures.begin(), m_textures.end(), [&](auto& pair) { return pair.second != nullptr && pair.second->Id == tex_data->Id; - }); + }); if (it != m_textures.end()) { it->second.reset(); } - }); + }); } // This must be called from VulkanEngine! @@ -344,7 +352,7 @@ void vkTexture::Cleanup() std::cout << "[Info] Cleaning up Vulkan textures: " << m_textures.size() << std::endl; - for (auto &[id, tex_data] : m_textures) { + for (auto& [id, tex_data] : m_textures) { if (tex_data == nullptr) { continue; } diff --git a/Engine/src/Scenes/SceneManager.cpp b/Engine/src/Scenes/SceneManager.cpp index 2443a80d..ac651219 100644 --- a/Engine/src/Scenes/SceneManager.cpp +++ b/Engine/src/Scenes/SceneManager.cpp @@ -391,4 +391,4 @@ void SceneManager::Release() if (s_instance != nullptr) { delete s_instance; } -} +} \ No newline at end of file diff --git a/Engine/src/Texture/Color3.cpp b/Engine/src/Texture/Color3.cpp index 02c2917b..92307587 100644 --- a/Engine/src/Texture/Color3.cpp +++ b/Engine/src/Texture/Color3.cpp @@ -1,6 +1,8 @@ #include "Texture/Color3.h" #include #include +#include +#include template T clamp(T value, T min, T max) @@ -8,10 +10,6 @@ T clamp(T value, T min, T max) return std::min(std::max(value, min), max); } -#if defined(__GNUC__) || defined(__GNUG__) -#include -#endif - Color3::Color3(float r, float g, float b) { R = clamp(r, 0.0f, 1.0f); @@ -24,48 +22,50 @@ Color3 Color3::FromRGB(float r, float g, float b) return { r / 255.0f, g / 255.0f, b / 255.0f }; } -Color3 Color3::FromHSV(int hue, int saturnation, int value) +Color3 Color3::FromHSV(int hue, int saturation, int value) { - float R, G, B; + float h = static_cast(hue % 360); + float s = clamp(static_cast(saturation), 0.0f, 100.0f) / 100.0f; + float v = clamp(static_cast(value), 0.0f, 100.0f) / 100.0f; - float h = hue / 60.0f; - float s = saturnation / 100.0f; - float v = value / 100.0f; float c = v * s; - float x = c * (1.0f - std::abs(fmod(h, 2.0f) - 1.0f)); + float x = c * (1.0f - std::abs(std::fmod(h / 60.0f, 2.0f) - 1.0f)); float m = v - c; - if (h >= 0 && h < 1) { - R = c; - G = x; - B = 0; - } else if (h >= 1 && h < 2) { - R = x; - G = c; - B = 0; - } else if (h >= 2 && h < 3) { - R = 0; - G = c; - B = x; - } else if (h >= 3 && h < 4) { - R = 0; - G = x; - B = c; - } else if (h >= 4 && h < 5) { - R = x; - G = 0; - B = c; - } else if (h >= 5 && h < 6) { - R = c; - G = 0; - B = x; - } else { - R = 0; - G = 0; - B = 0; + float r, g, b; + + if (0 <= h && h < 60) { + r = c; + g = x; + b = 0; + } + else if (60 <= h && h < 120) { + r = x; + g = c; + b = 0; + } + else if (120 <= h && h < 180) { + r = 0; + g = c; + b = x; + } + else if (180 <= h && h < 240) { + r = 0; + g = x; + b = c; + } + else if (240 <= h && h < 300) { + r = x; + g = 0; + b = c; + } + else { + r = c; + g = 0; + b = x; } - return { R, G, B }; + return { r + m, g + m, b + m }; } Color3 Color3::FromHex(std::string hexValue) @@ -82,7 +82,7 @@ Color3 Color3::FromHex(std::string hexValue) return { 0, 0, 0 }; } - int r = std::stoi(hexValue.substr(0, 2), nullptr, 16); + int r = std::stoi(hexValue.substr(0, 2), nullptr, 16); int g = std::stoi(hexValue.substr(2, 2), nullptr, 16); int b = std::stoi(hexValue.substr(4, 2), nullptr, 16); @@ -111,27 +111,31 @@ std::string Color3::ToHex() } // operator -Color3 Color3::operator+(Color3 const &color) +Color3 Color3::operator+(Color3 const& color) { return { this->R + color.R, this->G + color.G, this->B + color.B }; } -Color3 Color3::operator-(Color3 const &color) +Color3 Color3::operator-(Color3 const& color) { return { this->R - color.R, this->G - color.G, this->B - color.B }; } -Color3 Color3::operator*(Color3 const &color) +Color3 Color3::operator*(Color3 const& color) { return { this->R * color.R, this->G * color.G, this->B * color.B }; } -Color3 Color3::operator/(Color3 const &color) +Color3 Color3::operator/(Color3 const& color) { + // Handle division by zero + if (color.R == 0 || color.G == 0 || color.B == 0) { + return { 0, 0, 0 }; + } return { this->R / color.R, this->G / color.G, this->B / color.B }; } -Color3 Color3::operator*(float const &value) +Color3 Color3::operator*(float const& value) { return { this->R * value, this->G * value, this->B * value }; -} \ No newline at end of file +} diff --git a/Engine/src/Texture/Sprite2D.cpp b/Engine/src/Texture/Sprite2D.cpp index c18e661e..1c793328 100644 --- a/Engine/src/Texture/Sprite2D.cpp +++ b/Engine/src/Texture/Sprite2D.cpp @@ -2,11 +2,11 @@ #include "Rendering/Window.h" #include "Texture/Texture2D.h" -Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::Sprite2D() +Sprite2D::Sprite2D(std::vector textures, double delay) : Sprite2D::Sprite2D() { m_textures = textures; m_spritespeed = delay; - m_currentTime = 0.0f; + m_currentTime = 0.0; m_currentIndex = 0; Size = UDim2::fromScale(1, 1); @@ -14,7 +14,7 @@ Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::S AnchorPoint = { 0, 0 }; } -Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::Sprite2D() +Sprite2D::Sprite2D(std::vector textures, double delay) : Sprite2D::Sprite2D() { m_spritespeed = delay; Size = UDim2::fromScale(1, 1); @@ -26,7 +26,7 @@ Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::S } } -Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::Sprite2D() +Sprite2D::Sprite2D(std::vector textures, double delay) : Sprite2D::Sprite2D() { m_spritespeed = delay; Size = UDim2::fromScale(1, 1); @@ -40,7 +40,7 @@ Sprite2D::Sprite2D(std::vector textures, float delay) : S } } -Sprite2D::Sprite2D(std::vector textures, float delay) : Sprite2D::Sprite2D() +Sprite2D::Sprite2D(std::vector textures, double delay) : Sprite2D::Sprite2D() { m_spritespeed = delay; Size = UDim2::fromScale(1, 1); @@ -64,38 +64,15 @@ void Sprite2D::Draw(double delta, bool manual) Draw(delta, nullptr, manual); } -void Sprite2D::Draw(double delta, Rect *rect, bool manual) // Original code, play image sprite loop +void Sprite2D::DrawInternal(double delta, bool playOnce, Rect* rect, bool manual) { - auto tex = m_textures[m_currentIndex]; - GameWindow *window = GameWindow::GetInstance(); + if (m_textures.empty()) return; // Safety check to ensure m_textures is not empty - double xPos = (window->GetBufferWidth() * Position.X.Scale) + (Position.X.Offset); - double yPos = (window->GetBufferHeight() * Position.Y.Scale) + (Position.Y.Offset); - - double xMPos = (window->GetBufferWidth() * Position2.X.Scale) + (Position2.X.Offset); - double yMPos = (window->GetBufferHeight() * Position2.Y.Scale) + (Position2.Y.Offset); - - xPos += xMPos; - yPos += yMPos; - - tex->Position = UDim2::fromOffset(xPos, yPos); - tex->AlphaBlend = AlphaBlend; - tex->Size = Size; - tex->AnchorPoint = AnchorPoint; - tex->Draw(rect, manual ? false : true); - - if (m_spritespeed > 0.0f) { - m_currentTime += static_cast(delta); - if (m_currentTime >= m_spritespeed) { - m_currentTime = 0.0f; - m_currentIndex = (m_currentIndex + 1) % m_textures.size(); - } - } -} - -void Sprite2D::DrawOnce(double delta, bool manual) { // Play whole image sprite once if (m_currentIndex >= m_textures.size()) { - return; + if (playOnce) { + return; // Stop if playing once and reached end + } + m_currentIndex = 0; // Reset index if looping } auto tex = m_textures[m_currentIndex]; @@ -114,66 +91,55 @@ void Sprite2D::DrawOnce(double delta, bool manual) { // Play whole image sprite tex->AlphaBlend = AlphaBlend; tex->Size = Size; tex->AnchorPoint = AnchorPoint; - tex->Draw(); + tex->Draw(rect, manual ? false : true); - if (m_spritespeed > 0.0f) { + if (m_spritespeed > 0.0) { m_currentTime += static_cast(delta); if (m_currentTime >= m_spritespeed) { - m_currentTime = 0.0f; + m_currentTime = 0.0; m_currentIndex++; - if (m_currentIndex == m_textures.size() && m_drawOnce) { - return; + if (m_currentIndex >= m_textures.size()) { + if (playOnce) { + m_currentIndex = m_textures.size() - 1; // Stop at the last frame if playing once + return; + } else { + m_currentIndex = 0; // Loop back to the first frame + } } } } } -void Sprite2D::DrawStop(double delta, bool manual) { // Play image sprite once and stop on last frame - if (m_currentIndex >= m_textures.size()) { - m_currentIndex = static_cast(m_textures.size()) - 1; - } - - auto tex = m_textures[m_currentIndex]; - GameWindow* window = GameWindow::GetInstance(); - - double xPos = (window->GetBufferWidth() * Position.X.Scale) + (Position.X.Offset); - double yPos = (window->GetBufferHeight() * Position.Y.Scale) + (Position.Y.Offset); - - double xMPos = (window->GetBufferWidth() * Position2.X.Scale) + (Position2.X.Offset); - double yMPos = (window->GetBufferHeight() * Position2.Y.Scale) + (Position2.Y.Offset); - - xPos += xMPos; - yPos += yMPos; +void Sprite2D::Draw(double delta, Rect* rect, bool manual) +{ + DrawInternal(delta, false, rect, manual); // Play loop +} - tex->Position = UDim2::fromOffset(xPos, yPos); - tex->AlphaBlend = AlphaBlend; - tex->Size = Size; - tex->AnchorPoint = AnchorPoint; - tex->Draw(); +void Sprite2D::DrawOnce(double delta, bool manual) +{ + DrawInternal(delta, true, nullptr, manual); // Play once +} - if (m_spritespeed > 0.0f) { - m_currentTime += static_cast(delta); - if (m_currentTime >= m_spritespeed) { - m_currentTime = 0.0f; - m_currentIndex++; +void Sprite2D::DrawStop(double delta, bool manual) +{ + DrawInternal(delta, true, nullptr, manual); - if (m_currentIndex == m_textures.size() && m_drawOnce) { - return; - } - } + if (m_currentIndex >= m_textures.size()) { // Play the stop on last frame + m_currentIndex = m_textures.size() - 1; } } - void Sprite2D::Reset() { m_currentIndex = 0; - m_currentTime = 0.0f; + m_currentTime = 0.0; } Texture2D *Sprite2D::GetTexture() { + if (m_textures.empty()) return nullptr; // Safety check to ensure m_textures is not empty + auto tex = m_textures[m_currentIndex]; tex->Position = Position; tex->Size = Size; @@ -182,7 +148,7 @@ Texture2D *Sprite2D::GetTexture() return tex; } -void Sprite2D::SetFPS(float fps) +void Sprite2D::SetFPS(double fps) { m_spritespeed = 1.0f / fps; -} \ No newline at end of file +} diff --git a/Engine/src/Texture/Texture2D.cpp b/Engine/src/Texture/Texture2D.cpp index 6de909ac..90570193 100644 --- a/Engine/src/Texture/Texture2D.cpp +++ b/Engine/src/Texture/Texture2D.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "../Data/Imgui/imgui_impl_vulkan.h" #include "../Rendering/Vulkan/Texture2DVulkan_Internal.h" @@ -15,6 +18,29 @@ #include #include +namespace { + // Premultiply alpha + SDL_Surface* PremultiplyAlpha(SDL_Surface* surface) { + if (surface->format->BytesPerPixel != 4) return surface; + + Uint32* pixels = (Uint32*)surface->pixels; + for (int y = 0; y < surface->h; ++y) { + for (int x = 0; x < surface->w; ++x) { + Uint32 pixel = pixels[y * surface->w + x]; + Uint8 r, g, b, a; + SDL_GetRGBA(pixel, surface->format, &r, &g, &b, &a); + + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + + pixels[y * surface->w + x] = SDL_MapRGBA(surface->format, r, g, b, a); + } + } + return surface; + } +} + Texture2D::Texture2D() { TintColor = { 1.0f, 1.0f, 1.0f }; @@ -35,91 +61,62 @@ Texture2D::Texture2D() Size = UDim2::fromScale(1, 1); } -Texture2D::Texture2D(std::string fileName) : Texture2D() +Texture2D::Texture2D(const std::string& fileName) : Texture2D() { - if (!std::filesystem::exists(fileName)) { - fileName = std::filesystem::current_path().string() + fileName; + std::string filePath = fileName; + if (!std::filesystem::exists(filePath)) { + filePath = std::filesystem::current_path().string() + fileName; } - if (!std::filesystem::exists(fileName)) { + if (!std::filesystem::exists(filePath)) { throw std::runtime_error(fileName + " not found!"); } - std::fstream fs(fileName, std::ios::binary | std::ios::in); - if (!fs.is_open()) { - throw std::runtime_error(fileName + " cannot opened!"); + std::ifstream file(filePath, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + throw std::runtime_error(fileName + " cannot be opened!"); } - fs.seekg(0, std::ios::end); - size_t size = fs.tellg(); - fs.seekg(0, std::ios::beg); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); - uint8_t *buffer = new uint8_t[size]; - fs.read((char *)buffer, size); - fs.close(); + std::vector buffer(size); + file.read(reinterpret_cast(buffer.data()), size); + file.close(); - Rotation = 0; - Transparency = 0.0f; - m_actualSize = { 0, 0, 0, 0 }; - m_bDisposeTexture = true; - TintColor = { 1.0f, 1.0f, 1.0f }; - - LoadImageResources(buffer, size); + LoadImageResources(buffer.data(), size); } -Texture2D::Texture2D(std::filesystem::path path) : Texture2D() -{ +Texture2D::Texture2D(const std::filesystem::path& path) : Texture2D() { if (!std::filesystem::exists(path)) { throw std::runtime_error(path.string() + " not found!"); } - std::fstream fs(path, std::ios::binary | std::ios::in); - if (!fs.is_open()) { - throw std::runtime_error(path.string() + " cannot opened!"); + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + throw std::runtime_error(path.string() + " cannot be opened!"); } - fs.seekg(0, std::ios::end); - size_t size = fs.tellg(); - fs.seekg(0, std::ios::beg); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); - uint8_t *buffer = new uint8_t[size]; - fs.read((char *)buffer, size); - fs.close(); - - Rotation = 0; - Transparency = 0.0f; - m_actualSize = { 0, 0, 0, 0 }; - m_bDisposeTexture = true; - TintColor = { 1.0f, 1.0f, 1.0f }; + std::vector buffer(size); + file.read(reinterpret_cast(buffer.data()), size); + file.close(); - LoadImageResources(buffer, size); + LoadImageResources(buffer.data(), size); } -// Do base constructor called before derived constructor? -// https://stackoverflow.com/questions/120547/what-are-the-rules-for-calling-the-superclass-constructor - -Texture2D::Texture2D(uint8_t *fileData, size_t size) : Texture2D() +Texture2D::Texture2D(const uint8_t* fileData, size_t size) : Texture2D() { - uint8_t *buffer = new uint8_t[size]; - memcpy(buffer, fileData, size); - - Rotation = 0; - Transparency = 0.0f; - m_actualSize = { 0, 0, 0, 0 }; - m_bDisposeTexture = true; - TintColor = { 1.0f, 1.0f, 1.0f }; - - m_sdl_tex = nullptr; - m_vk_tex = nullptr; - - LoadImageResources(buffer, size); + std::vector buffer(fileData, fileData + size); + LoadImageResources(buffer.data(), size); } Texture2D::Texture2D(SDL_Texture *texture) : Texture2D() { m_bDisposeTexture = false; m_sdl_tex = texture; - m_ready = true; } @@ -135,17 +132,13 @@ Texture2D::~Texture2D() { if (m_bDisposeTexture) { if (Renderer::GetInstance()->IsVulkan() && m_vk_tex) { - auto vk_tex = m_vk_tex; - - vkTexture::ReleaseTexture(vk_tex); - + vkTexture::ReleaseTexture(m_vk_tex); m_vk_tex = nullptr; } else { if (m_sdl_tex) { SDL_DestroyTexture(m_sdl_tex); m_sdl_tex = nullptr; } - if (m_sdl_surface) { SDL_FreeSurface(m_sdl_surface); m_sdl_surface = nullptr; @@ -169,11 +162,11 @@ void Texture2D::Draw(Rect *clipRect) Draw(clipRect, true); } -void Texture2D::Draw(Rect *clipRect, bool manualDraw) +void Texture2D::Draw(Rect* clipRect, bool manualDraw) { - Renderer *renderer = Renderer::GetInstance(); - auto window = GameWindow::GetInstance(); - bool scaleOutput = window->IsScaleOutput(); + Renderer* renderer = Renderer::GetInstance(); + auto window = GameWindow::GetInstance(); + bool scaleOutput = window->IsScaleOutput(); CalculateSize(); if (!m_ready) @@ -226,66 +219,41 @@ void Texture2D::Draw(Rect *clipRect, bool manualDraw) ImVec2 uv3(1.0f, 1.0f); // Bottom-right UV coordinate ImVec2 uv4(0.0f, 1.0f); // Bottom-left UV coordinate - ImU32 color = IM_COL32((uint8_t)(TintColor.R * 255), (uint8_t)(TintColor.G * 255), (uint8_t)(TintColor.B * 255), 255); // Probably fix for Color3 - - std::array vertexData; - - for (int i = 0; i < 6; i++) { - ImDrawVert &vertex = vertexData[i]; - switch (i) { - case 0: - vertex.pos = ImVec2(x1, y1); - vertex.uv = uv1; - break; - case 1: - vertex.pos = ImVec2(x2, y1); - vertex.uv = uv2; - break; - case 2: - vertex.pos = ImVec2(x2, y2); - vertex.uv = uv3; - break; - case 3: - vertex.pos = ImVec2(x1, y1); - vertex.uv = uv1; - break; - case 4: - vertex.pos = ImVec2(x2, y2); - vertex.uv = uv3; - break; - case 5: - vertex.pos = ImVec2(x1, y2); - vertex.uv = uv4; - break; - } - vertex.col = color; - } + ImU32 color = IM_COL32((uint8_t)(TintColor.R * 255), (uint8_t)(TintColor.G * 255), (uint8_t)(TintColor.B * 255), 255); + + std::array vertexData = {{ + {ImVec2(x1, y1), uv1, color}, + {ImVec2(x2, y1), uv2, color}, + {ImVec2(x2, y2), uv3, color}, + {ImVec2(x1, y1), uv1, color}, + {ImVec2(x2, y2), uv3, color}, + {ImVec2(x1, y2), uv4, color} + }}; SubmitQueueInfo info = {}; info.AlphaBlend = AlphaBlend; info.descriptor = imageId; - info.vertices = std::vector(vertexData.begin(), vertexData.end()); - info.indices = { 0, 1, 2, 3, 4, 5 }; + info.vertices = {vertexData.begin(), vertexData.end()}; + info.indices = {0, 1, 2, 3, 4, 5}; info.scissor = scissor; - // submit to queue vulkan_driver->queue_submit(info); } else { - SDL_FRect destRect = { m_calculatedSizeF.left, m_calculatedSizeF.top, m_calculatedSizeF.right, m_calculatedSizeF.bottom }; + SDL_FRect destRect = {m_calculatedSizeF.left, m_calculatedSizeF.top, m_calculatedSizeF.right, m_calculatedSizeF.bottom}; if (scaleOutput) { - destRect.x = destRect.x * window->GetWidthScale(); - destRect.y = destRect.y * window->GetHeightScale(); - destRect.w = destRect.w * window->GetWidthScale(); - destRect.h = destRect.h * window->GetHeightScale(); + destRect.x *= window->GetWidthScale(); + destRect.y *= window->GetHeightScale(); + destRect.w *= window->GetWidthScale(); + destRect.h *= window->GetHeightScale(); } - SDL_Rect originClip = {}; + SDL_Rect originClip = {}; SDL_BlendMode oldBlendMode = SDL_BLENDMODE_NONE; if (clipRect) { SDL_RenderGetClipRect(renderer->GetSDLRenderer(), &originClip); - SDL_Rect testClip = { clipRect->left, clipRect->top, clipRect->right - clipRect->left, clipRect->bottom - clipRect->top }; + SDL_Rect testClip = {clipRect->left, clipRect->top, clipRect->right - clipRect->left, clipRect->bottom - clipRect->top}; if (scaleOutput) { testClip.x = static_cast(testClip.x * window->GetWidthScale()); testClip.y = static_cast(testClip.y * window->GetHeightScale()); @@ -301,23 +269,15 @@ void Texture2D::Draw(Rect *clipRect, bool manualDraw) SDL_SetTextureBlendMode(m_sdl_tex, renderer->GetSDLBlendMode()); } - SDL_Color color = { (uint8_t)(TintColor.R * 255.0f), (uint8_t)(TintColor.G * 255.0f), (uint8_t)(TintColor.B * 255.0f), (uint8_t)255 }; + SDL_Color color = {(uint8_t)(TintColor.R * 255.0f), (uint8_t)(TintColor.G * 255.0f), (uint8_t)(TintColor.B * 255.0f), 255}; if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { - color.r = (uint8_t)(TintColor.B * 255.0f); - color.b = (uint8_t)(TintColor.R * 255.0f); + std::swap(color.r, color.b); } SDL_SetTextureColorMod(m_sdl_tex, color.r, color.g, color.b); SDL_SetTextureAlphaMod(m_sdl_tex, static_cast(255 - (Transparency / 100.0) * 255)); - int error = SDL_RenderCopyExF( - renderer->GetSDLRenderer(), - m_sdl_tex, - nullptr, - &destRect, - Rotation, - nullptr, - (SDL_RendererFlip)0); + int error = SDL_RenderCopyExF(renderer->GetSDLRenderer(), m_sdl_tex, nullptr, &destRect, Rotation, nullptr, SDL_FLIP_NONE); if (error != 0) { throw SDLException(); @@ -328,20 +288,16 @@ void Texture2D::Draw(Rect *clipRect, bool manualDraw) } if (clipRect) { - if (originClip.w == 0 || originClip.h == 0) { - SDL_RenderSetClipRect(renderer->GetSDLRenderer(), nullptr); - } else { - SDL_RenderSetClipRect(renderer->GetSDLRenderer(), &originClip); - } + SDL_RenderSetClipRect(renderer->GetSDLRenderer(), originClip.w == 0 || originClip.h == 0 ? nullptr : &originClip); } } } void Texture2D::CalculateSize() { - GameWindow *window = GameWindow::GetInstance(); - int wWidth = window->GetBufferWidth(); - int wHeight = window->GetBufferHeight(); + GameWindow* window = GameWindow::GetInstance(); + int wWidth = window->GetBufferWidth(); + int wHeight = window->GetBufferHeight(); float xPos = static_cast((wWidth * Position.X.Scale) + Position.X.Offset); float yPos = static_cast((wHeight * Position.Y.Scale) + Position.Y.Offset); @@ -349,8 +305,8 @@ void Texture2D::CalculateSize() float width = static_cast((m_actualSize.right * Size.X.Scale) + Size.X.Offset); float height = static_cast((m_actualSize.bottom * Size.Y.Scale) + Size.Y.Offset); - m_preAnchoredSize = { (LONG)xPos, (LONG)yPos, (LONG)width, (LONG)height }; - m_preAnchoredSizeF = { xPos, yPos, width, height }; + m_preAnchoredSize = {(LONG)xPos, (LONG)yPos, (LONG)width, (LONG)height}; + m_preAnchoredSizeF = {xPos, yPos, width, height}; float xAnchor = width * std::clamp((float)AnchorPoint.X, 0.0f, 1.0f); float yAnchor = height * std::clamp((float)AnchorPoint.Y, 0.0f, 1.0f); @@ -358,11 +314,11 @@ void Texture2D::CalculateSize() xPos -= xAnchor; yPos -= yAnchor; - m_calculatedSize = { (LONG)xPos, (LONG)yPos, (LONG)width, (LONG)height }; - m_calculatedSizeF = { xPos, yPos, width, height }; + m_calculatedSize = {(LONG)xPos, (LONG)yPos, (LONG)width, (LONG)height}; + m_calculatedSizeF = {xPos, yPos, width, height}; - AbsolutePosition = { xPos, yPos }; - AbsoluteSize = { width, height }; + AbsolutePosition = {xPos, yPos}; + AbsoluteSize = {width, height}; } Rect Texture2D::GetOriginalRECT() @@ -375,84 +331,37 @@ void Texture2D::SetOriginalRECT(Rect size) m_actualSize = size; } -Texture2D *Texture2D::FromTexture2D(Texture2D *tex) -{ - auto copy = new Texture2D(tex->m_sdl_tex); - copy->m_actualSize = tex->m_actualSize; - copy->Position = tex->Position; - copy->Size = tex->Size; - - return copy; -} - -Texture2D *Texture2D::FromBMP(uint8_t *fileData, size_t size) -{ - return nullptr; -} - -Texture2D *Texture2D::FromBMP(std::string fileName) -{ - return nullptr; -} - -Texture2D *Texture2D::FromJPEG(uint8_t *fileData, size_t size) -{ - return nullptr; -} - -Texture2D *Texture2D::FromJPEG(std::string fileName) -{ - return nullptr; -} - -Texture2D *Texture2D::FromPNG(uint8_t *fileData, size_t size) -{ - return nullptr; -} - -Texture2D *Texture2D::FromPNG(std::string fileName) -{ - return nullptr; -} - -void Texture2D::LoadImageResources(uint8_t *buffer, size_t size) +void Texture2D::LoadImageResources(uint8_t* buffer, size_t size) { if (Renderer::GetInstance()->IsVulkan()) { auto tex_data = vkTexture::TexLoadImage(buffer, size); - - m_actualSize = { 0, 0, tex_data->Width, tex_data->Height }; + m_actualSize = {0, 0, tex_data->Width, tex_data->Height}; m_vk_tex = tex_data; - m_bDisposeTexture = true; m_ready = true; } else { - SDL_RWops *rw = SDL_RWFromMem(buffer, (int)size); + SDL_RWops* rw = SDL_RWFromMem(buffer, static_cast(size)); + std::unique_ptr decompressed_surface(IMG_LoadTyped_RW(rw, 1, "PNG"), SDL_FreeSurface); - // check if buffer magic is BMP - if (buffer[0] == 0x42 && buffer[1] == 0x4D) { - m_sdl_surface = SDL_LoadBMP_RW(rw, 1); - } else { - m_sdl_surface = IMG_Load_RW(rw, 1); + if (!decompressed_surface) { + throw SDLException(); } - if (!m_sdl_surface) { + std::unique_ptr formatted_surface(SDL_ConvertSurfaceFormat(decompressed_surface.get(), SDL_PIXELFORMAT_ABGR8888, 0), SDL_FreeSurface); + + if (!formatted_surface) { throw SDLException(); } + m_sdl_surface = PremultiplyAlpha(formatted_surface.release()); // Fix white line issue m_sdl_tex = SDL_CreateTextureFromSurface(Renderer::GetInstance()->GetSDLRenderer(), m_sdl_surface); + if (!m_sdl_tex) { throw SDLException(); } - // sdl get texture resolution - int w, h; - SDL_QueryTexture(m_sdl_tex, nullptr, nullptr, &w, &h); - + m_actualSize = {0, 0, m_sdl_surface->w, m_sdl_surface->h}; m_bDisposeTexture = true; - m_actualSize = { 0, 0, w, h }; - m_ready = true; } - - delete[] buffer; -} +} \ No newline at end of file diff --git a/Engine/src/Threading/GameThread.cpp b/Engine/src/Threading/GameThread.cpp index 522abb31..f3f04fd9 100644 --- a/Engine/src/Threading/GameThread.cpp +++ b/Engine/src/Threading/GameThread.cpp @@ -88,3 +88,4 @@ void GameThread::Stop() } } } + diff --git a/Game/CMakeLists.txt b/Game/CMakeLists.txt index 8d74a4b9..f76580ad 100644 --- a/Game/CMakeLists.txt +++ b/Game/CMakeLists.txt @@ -70,27 +70,59 @@ add_executable(Game target_include_directories(Game PRIVATE "../Engine/include") if (WIN32) - add_custom_command(TARGET Game POST_BUILD + add_custom_command(TARGET Game POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/third-party/bin/x64-windows/Debug - $) + $ + ) # Create directory $/Skins/Default then # copy from ${CMAKE_SOURCE_DIR}/GameResources/Resources to $/Skins/Default - add_custom_command(TARGET Game POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory $/Skins/Default COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/GameResources/Resources - $/Skins/Default) + $/Skins/Default + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Notes + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Notes + $/Resources/Notes + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Fonts + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Fonts + $/Resources/Fonts + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Notes/Scripts + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Scripts + $/Resources/Notes/Scripts + ) else () add_custom_command(TARGET Game POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/Skins/Default - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/GameResources/Resources - $/Skins/Default) + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Skins/Default + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Resources + $/Skins/Default + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Notes + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Notes + $/Resources/Notes + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Fonts + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Fonts + $/Resources/Fonts + COMMAND ${CMAKE_COMMAND} -E make_directory + $/Resources/Notes/Scripts + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/GameResources/Scripts + $/Resources/Notes/Scripts + ) endif () if (MSVC) diff --git a/Game/src/Data/Chart.cpp b/Game/src/Data/Chart.cpp index 41c8c522..ee382ce9 100644 --- a/Game/src/Data/Chart.cpp +++ b/Game/src/Data/Chart.cpp @@ -7,6 +7,12 @@ #include #include #include +#include +#include +#include +#include +#include "../GameScenes.h" +#include "../EnvironmentSetup.hpp" float float_floor(float value) { @@ -17,7 +23,20 @@ float float_floor(float value) #endif } -double TimingInfo::CalculateBeat(double offset) +namespace { + std::string getFileExtension(const std::string& filename) { + size_t dotPos = filename.find_last_of('.'); + if (dotPos != std::string::npos) { + std::string ext = filename.substr(dotPos + 1); + // Convert extension to lowercase + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); }); + return ext; + } + return ""; // No extension found + } +} + +double TimingInfo::CalculateBeat(double offset) const { if (Type == TimingType::SV) { return 0; @@ -32,7 +51,7 @@ Chart::Chart() m_keyCount = 7; } -Chart::Chart(Osu::Beatmap &beatmap) +Chart::Chart(Osu::Beatmap& beatmap) // Refactor { if (!beatmap.IsValid()) { throw std::invalid_argument("Invalid osu beatmap!"); @@ -43,64 +62,79 @@ Chart::Chart(Osu::Beatmap &beatmap) } if (beatmap.CircleSize < 1 || beatmap.CircleSize > 7) { - throw std::invalid_argument("osu beatmap's Mania key must be 7"); + throw std::invalid_argument("osu beatmap's Mania key must be between 1 and 7"); } - // m_audio = beatmap.AudioFilename; m_title = std::u8string(beatmap.Title.begin(), beatmap.Title.end()); m_keyCount = (int)beatmap.CircleSize; m_artist = std::u8string(beatmap.Artist.begin(), beatmap.Artist.end()); + m_difname = std::u8string(beatmap.Version.begin(), beatmap.Version.end()); m_beatmapDirectory = beatmap.CurrentDir; - for (auto &event : beatmap.Events) { - switch (event.Type) { - case Osu::OsuEventType::Background: - { - std::string fileName = event.params[0]; - fileName.erase(std::remove(fileName.begin(), fileName.end(), '\"'), fileName.end()); + for (auto& event : beatmap.Events) { + if (event.Type == Osu::OsuEventType::Background) { + std::string fileName = event.params[0]; + fileName.erase(std::remove(fileName.begin(), fileName.end(), '\"'), fileName.end()); + m_backgroundFile = fileName; + break; + } + } - m_backgroundFile = fileName; - break; + for (auto& event : beatmap.Events) { + if (event.Type == Osu::OsuEventType::Sample) { + std::string fileName = event.params[1]; + if (fileName.front() == '\"' && fileName.back() == '\"') { + fileName = fileName.substr(1, fileName.size() - 2); } - case Osu::OsuEventType::Sample: - { - std::string fileName = event.params[1]; - fileName.erase(std::remove(fileName.begin(), fileName.end(), '\"'), fileName.end()); - - auto path = beatmap.CurrentDir / fileName; - if (std::filesystem::exists(path)) { - AutoSample sample = {}; - sample.StartTime = event.StartTime; - sample.Index = beatmap.GetCustomSampleIndex(fileName); - sample.Volume = 1; - sample.Pan = 0; - - m_autoSamples.push_back(sample); + auto path = beatmap.CurrentDir / fileName; + if (!std::filesystem::exists(path)) { + std::string extension = getFileExtension(fileName); + if (!extension.empty()) { + std::vector extensionsToTry = { ".ogg", ".wav", ".mp3" }; + for (const auto& ext : extensionsToTry) { + std::string newFileName = fileName.substr(0, fileName.find_last_of('.')) + ext; + path = beatmap.CurrentDir / newFileName; + if (std::filesystem::exists(path)) { + fileName = newFileName; + break; + } + } } + } - break; + if (std::filesystem::exists(path)) { + AutoSample sample = {}; + sample.StartTime = event.StartTime; + sample.Index = beatmap.GetCustomSampleIndex(fileName); + sample.Volume = 1; + sample.Pan = 0; + m_autoSamples.push_back(sample); + } else { + Logs::Puts("[Chart] Custom sample file not found: %s", fileName.c_str()); } } } - { - AutoSample sample = {}; - sample.StartTime = beatmap.AudioLeadIn; - sample.Index = beatmap.GetCustomSampleIndex(beatmap.AudioFilename); - sample.Volume = 1; - sample.Pan = 0; - - m_autoSamples.push_back(sample); + AutoSample autoSample = {}; + if (beatmap.AudioLeadIn = 0) { + autoSample.StartTime = beatmap.AudioLeadIn - 1; // Handle if offset 0 that causing audio delay + } + else { + autoSample.StartTime = beatmap.AudioLeadIn; } + autoSample.Index = beatmap.GetCustomSampleIndex(beatmap.AudioFilename); + autoSample.Volume = 1.0f; + autoSample.Pan = 0; + m_autoSamples.push_back(autoSample); - for (auto ¬e : beatmap.HitObjects) { + for (auto& note : beatmap.HitObjects) { NoteInfo info = {}; info.StartTime = note.StartTime; info.Type = NoteType::NORMAL; info.Keysound = note.KeysoundIndex; info.LaneIndex = static_cast(float_floor(note.X * static_cast(beatmap.CircleSize) / 512.0f)); - info.Volume = static_cast(note.Volume) / 100.0f; + info.Volume = note.Volume > 0 ? static_cast(note.Volume) / 100.0f : 1.0f; info.Pan = 0; if (note.Type == 128) { @@ -111,15 +145,12 @@ Chart::Chart(Osu::Beatmap &beatmap) m_notes.push_back(info); } - for (auto &timing : beatmap.TimingPoints) { - bool IsSV = timing.Inherited == 0 || timing.BeatLength < 0; - - if (IsSV) { + for (auto& timing : beatmap.TimingPoints) { + if (timing.Inherited == 0 || timing.BeatLength < 0) { TimingInfo info = {}; info.StartTime = timing.Offset; info.Value = std::clamp(-100.0f / timing.BeatLength, 0.1f, 10.0f); info.Type = TimingType::SV; - m_svs.push_back(info); } else { TimingInfo info = {}; @@ -127,63 +158,25 @@ Chart::Chart(Osu::Beatmap &beatmap) info.Value = 60000.0f / timing.BeatLength; info.TimeSignature = timing.TimeSignature; info.Type = TimingType::BPM; - m_bpms.push_back(info); } } for (int i = 0; i < beatmap.HitSamples.size(); i++) { - auto &keysound = beatmap.HitSamples[i]; - + auto& keysound = beatmap.HitSamples[i]; auto path = beatmap.CurrentDir / keysound; Sample sm = {}; sm.FileName = path; sm.Index = i; - m_samples.push_back(sm); } - for (auto ¬e : m_notes) { - switch (m_keyCount) { - case 4: - { - if (note.LaneIndex >= 2) { - note.LaneIndex += 3; - } - break; - } - - case 5: - { - if (note.LaneIndex == 3) { - note.LaneIndex += 1; - } else if (note.LaneIndex >= 4) { - note.LaneIndex += 2; - } - break; - } - } - } - - m_bpms[0].Beat = 0; - for (int i = 1; i < m_bpms.size(); i++) { - m_bpms[i].Beat = m_bpms[i - 1].CalculateBeat(m_bpms[i].StartTime); - } - - std::sort(m_autoSamples.begin(), m_autoSamples.end(), [](const AutoSample &a, const AutoSample &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_bpms.begin(), m_bpms.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_svs.begin(), m_svs.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); - + CalculateBeat(); + SortTimings(); NormalizeTimings(); + CheckFor7K(); + ComputeKeyCount(); ComputeHash(); } @@ -213,7 +206,7 @@ Chart::Chart(BMS::BMSFile &file) info.Type = NoteType::NORMAL; info.LaneIndex = note.Lane; info.Keysound = note.SampleIndex; - info.Volume = 1; + info.Volume = 1.0f; info.Pan = 0; if (note.EndTime != -1) { @@ -229,7 +222,7 @@ Chart::Chart(BMS::BMSFile &file) AutoSample sm = {}; sm.StartTime = note.StartTime; sm.Index = note.SampleIndex; - sm.Volume = 1; + sm.Volume = 1.0f; sm.Pan = 0; m_autoSamples.push_back(sm); @@ -273,7 +266,7 @@ Chart::Chart(BMS::BMSFile &file) AutoSample sm = {}; sm.StartTime = autoSample.StartTime; sm.Index = autoSample.SampleIndex; - sm.Volume = 1; + sm.Volume = 1.0f; sm.Pan = 0; m_autoSamples.push_back(sm); @@ -288,22 +281,9 @@ Chart::Chart(BMS::BMSFile &file) m_bpms.push_back(info); } - m_bpms[0].Beat = 0; - for (int i = 1; i < m_bpms.size(); i++) { - m_bpms[i].Beat = m_bpms[i - 1].CalculateBeat(m_bpms[i].StartTime); - } + CalculateBeat(); - std::sort(m_autoSamples.begin(), m_autoSamples.end(), [](const AutoSample &a, const AutoSample &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_bpms.begin(), m_bpms.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_svs.begin(), m_svs.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); + SortTimings(); PredefinedAudioLength = file.AudioLength; NormalizeTimings(); @@ -368,10 +348,7 @@ Chart::Chart(O2::OJN &file, int diffIndex) m_bpms.push_back(info); } - m_bpms[0].Beat = 0; - for (int i = 1; i < m_bpms.size(); i++) { - m_bpms[i].Beat = m_bpms[i - 1].CalculateBeat(m_bpms[i].StartTime); - } + CalculateBeat(); for (auto &autoSample : diff.AutoSamples) { AutoSample sm = {}; @@ -392,17 +369,7 @@ Chart::Chart(O2::OJN &file, int diffIndex) m_samples.push_back(sm); } - std::sort(m_autoSamples.begin(), m_autoSamples.end(), [](const AutoSample &a, const AutoSample &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_bpms.begin(), m_bpms.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); - - std::sort(m_svs.begin(), m_svs.end(), [](const TimingInfo &a, const TimingInfo &b) { - return a.StartTime < b.StartTime; - }); + SortTimings(); PredefinedAudioLength = diff.AudioLength; NormalizeTimings(); @@ -410,6 +377,31 @@ Chart::Chart(O2::OJN &file, int diffIndex) ComputeHash(); } + + +void Chart::CalculateBeat() +{ + m_bpms[0].Beat = 0; + for (size_t i = 1; i < m_bpms.size(); i++) { + m_bpms[i].Beat = m_bpms[i - 1].CalculateBeat(m_bpms[i].StartTime); + } +} + +void Chart::SortTimings() +{ + std::sort(m_autoSamples.begin(), m_autoSamples.end(), [](const AutoSample& a, const AutoSample& b) { + return a.StartTime < b.StartTime; + }); + + std::sort(m_bpms.begin(), m_bpms.end(), [](const TimingInfo& a, const TimingInfo& b) { + return a.StartTime < b.StartTime; + }); + + std::sort(m_svs.begin(), m_svs.end(), [](const TimingInfo& a, const TimingInfo& b) { + return a.StartTime < b.StartTime; + }); +} + Chart::~Chart() { } @@ -444,7 +436,7 @@ void Chart::ApplyMod(Mod mod, void *data) case Mod::RANDOM: { std::vector lanes(m_keyCount); - for (int i = 0; i < 7; i++) { + for (int i = 0; i < m_keyCount; i++) { lanes[i] = i; } @@ -453,12 +445,72 @@ void Chart::ApplyMod(Mod mod, void *data) std::shuffle(std::begin(lanes), std::end(lanes), rng); - for (auto ¬e : m_notes) { + for (auto& note : m_notes) { note.LaneIndex = lanes[note.LaneIndex]; } + + /*std::stringstream pattern; + for (int lane : lanes) { + pattern << lane; + } + + Logs::Puts("[Chart] Set lane pattern to: %s", pattern.str().c_str());*/ + + break; + } + + case Mod::PANIC: // pler + { + std::vector lanes(m_keyCount); + for (int i = 0; i < m_keyCount; i++) { + lanes[i] = i; + } + + auto rng = std::default_random_engine{}; + rng.seed((uint32_t)time(NULL)); + + std::unordered_map> measureLane; + + // Keep track of BPM changes + std::unordered_map measureBPM; + for (const auto& bpm : m_bpms) { + int measure = static_cast(bpm.StartTime / bpm.TimeSignature); + measureBPM[measure] = bpm.Value; + } + + // Shuffle lanes per measure + for (auto& note : m_notes) { + int measure = -1; + for (size_t i = 0; i < m_bpms.size(); i++) { + if (m_bpms[i].StartTime > note.StartTime) { + measure = static_cast(m_bpms[i - 1].CalculateBeat(note.StartTime) / m_bpms[i - 1].TimeSignature); + break; + } + } + + if (measure == -1) { + measure = static_cast(m_bpms.back().CalculateBeat(note.StartTime) / m_bpms.back().TimeSignature); + } + + if (measureLane.find(measure) == measureLane.end()) { + std::shuffle(std::begin(lanes), std::end(lanes), rng); + measureLane[measure] = lanes; + } + + note.LaneIndex = measureLane[measure][note.LaneIndex]; + + int nextMeasure = measure + 1; + if (note.EndTime > nextMeasure * measureBPM[nextMeasure]) { + if (measureLane.find(nextMeasure) == measureLane.end()) { + measureLane[nextMeasure] = measureLane[measure]; + } + } + } + break; } + case Mod::REARRANGE: { int *lanes = reinterpret_cast(data); @@ -542,6 +594,7 @@ float Chart::GetCommonBPM() void Chart::NormalizeTimings() { + EnvironmentSetup::SetInt("KeyCount", m_keyCount); std::vector result; float baseBPM = GetCommonBPM(); @@ -648,6 +701,16 @@ void Chart::NormalizeTimings() m_svs = result; } +void Chart::CheckFor7K() +{ + bool is7K = EnvironmentSetup::GetInt("KeyCount") == 7; + if (!is7K) + { + MsgBox::Show("Only7K", "Error", "Only 7K Mode Allowed!", MsgBoxType::OK); + SceneManager::ChangeScene(GameScene::SONGSELECT); + } +} + void Chart::ComputeKeyCount() { bool Lanes[7] = { false, false, false, false, false, false }; @@ -679,7 +742,6 @@ void Chart::ComputeKeyCount() else if (Lanes[0] && Lanes[1] && !Lanes[2] && !Lanes[3] && !Lanes[4] && Lanes[5] && Lanes[6]) { m_keyCount = 4; } - // Otherwise, the pattern does not match any of the known K values else { Logs::Puts("[Chart] Unknown lane pattern, fallback to 7K"); m_keyCount = 7; diff --git a/Game/src/Data/Chart.hpp b/Game/src/Data/Chart.hpp index fd5b25bc..14724cf4 100644 --- a/Game/src/Data/Chart.hpp +++ b/Game/src/Data/Chart.hpp @@ -52,7 +52,8 @@ struct TimingInfo float TimeSignature; TimingType Type; - double CalculateBeat(double offset); + //double CalculateBeat(double offset); + double CalculateBeat(double offset) const; }; struct Sample @@ -75,6 +76,7 @@ struct AutoSample enum class Mod { MIRROR, RANDOM, + PANIC, REARRANGE }; @@ -85,6 +87,8 @@ class Chart Chart(Osu::Beatmap &beatmap); Chart(BMS::BMSFile &bmsfile); Chart(O2::OJN &ojnfile, int diffIndex = 2); + void CalculateBeat(); + void SortTimings(); ~Chart(); void ApplyMod(Mod mod, void *data = NULL); @@ -102,6 +106,7 @@ class Chart std::vector m_backgroundBuffer; std::u8string m_title; std::u8string m_artist; + std::u8string m_difname; std::string m_audio; std::filesystem::path m_beatmapDirectory; @@ -113,6 +118,8 @@ class Chart std::vector m_samples; std::vector m_autoSamples; + void CheckFor7K(); + private: double PredefinedAudioLength = -1; diff --git a/Game/src/Data/OJM.cpp b/Game/src/Data/OJM.cpp index fd20194c..1d8b654a 100644 --- a/Game/src/Data/OJM.cpp +++ b/Game/src/Data/OJM.cpp @@ -9,16 +9,20 @@ constexpr int kM30Signature = 0x0030334D; constexpr int kOMCSignature = 0x00434D4F; constexpr int kOJMSignature = 0x004D4A4F; +const char MASK_SCRAMBLE1[] = { 0x73, 0x63, 0x72, 0x61, 0x6D, 0x62, 0x6C, 0x65, 0x31 }; +const char MASK_SCRAMBLE2[] = { 0x73, 0x63, 0x72, 0x61, 0x6D, 0x62, 0x6C, 0x65, 0x32 }; +const char MASK_DECODE[] = { 0x64, 0x65, 0x63, 0x6F, 0x64, 0x65 }; +const char MASK_DECRYPT[] = { 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74 }; const char MASK_NAMI[] = { 0x6E, 0x61, 0x6D, 0x69 }; const char MASK_0412[] = { 0x30, 0x34, 0x31, 0x32 }; void M30Xor(char *data, size_t sz, const char *xorKey) { for (int i = 0; i + 3 < sz; i += 4) { - data[i] ^= xorKey[0]; - data[i + 1] ^= xorKey[1]; - data[i + 2] ^= xorKey[2]; - data[i + 3] ^= xorKey[3]; + data[i] ^= xorKey[i % 4]; + data[i + 1] ^= xorKey[(i + 1) % 4]; + data[i + 2] ^= xorKey[(i + 2) % 4]; + data[i + 3] ^= xorKey[(i + 3) % 4]; } } @@ -204,18 +208,26 @@ void OJM::LoadM30Data(std::fstream &fs) fs.read((char *)buffer, SampleHeader.sampleSize); switch (Header.encryptionFlag) { - case 0: - break; - case 16: - { - M30Xor((char *)buffer, SampleHeader.sampleSize, MASK_NAMI); - break; - } - case 32: - { - M30Xor((char *)buffer, SampleHeader.sampleSize, MASK_0412); - break; - } + case 0: + break; + case 1: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_SCRAMBLE1); + break; + case 2: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_SCRAMBLE2); + break; + case 4: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_DECODE); + break; + case 8: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_DECRYPT); + break; + case 16: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_NAMI); + break; + case 32: + M30Xor((char*)buffer, SampleHeader.sampleSize, MASK_0412); + break; } // OGG Sample diff --git a/Game/src/Data/Util/Util.cpp b/Game/src/Data/Util/Util.cpp index f4db3980..14dcc3a8 100644 --- a/Game/src/Data/Util/Util.cpp +++ b/Game/src/Data/Util/Util.cpp @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include std::vector splitString(std::string &input, char delimeter) { @@ -130,51 +133,46 @@ void flipArray(uint8_t *arr, size_t size) } } -std::u8string CodepageToUtf8(const char *string, size_t str_len, const char *encoding) -{ - std::u8string result; - iconv_t conv = iconv_open("UTF-8", encoding); +std::u8string CodepageToUtf8(const char* string, size_t str_len, const char* encoding) { // Refactor + // Open iconv conversion descriptor + iconv_t conv = iconv_open("UTF-8", encoding); if (conv == (iconv_t)-1) { return u8""; } - // size_t inbytesleft = str_len; - // size_t outbytesleft = str_len * 4; - // char* inbuf = (char*)string; - // char* outbuf = (char*)malloc(outbytesleft); - // char* outbufptr = outbuf; - - // if (iconv(conv, &inbuf, &inbytesleft, &outbufptr, &outbytesleft) == (size_t)-1) { - // free(outbuf); - // iconv_close(conv); - // return std::u8string((const char8_t*)strerror(errno)); - // } - - // result = std::u8string((char8_t*)outbuf, str_len * 4 - outbytesleft); - // free(outbuf); - // iconv_close(conv); - // return result; - - size_t inbytesleft = str_len; - size_t outbytesleft = str_len * 4; - char *inbuf = (char *)string; - std::vector outbuf; - outbuf.resize(outbytesleft, 0); - - char *outbufptr = outbuf.data(); - - if (iconv(conv, &inbuf, &inbytesleft, &outbufptr, &outbytesleft) == (size_t)-1) { - iconv_close(conv); - - // return whatever we have left, even if it's not valid - size_t len = strlen(outbuf.data()); - std::u8string remaining = std::u8string((const char8_t *)outbuf.data(), len); - - return remaining; + std::u8string result; + size_t inbytesleft = str_len; + const char* inbuf = string; + size_t outbufsize = str_len * 4; // Initial output buffer size + std::vector outbuf(outbufsize); + + while (true) { + char* outbufptr = outbuf.data(); + size_t outbytesleft = outbuf.size(); + + // Reset errno to check for errors after iconv call + errno = 0; + + size_t ret = iconv(conv, (char**)&inbuf, &inbytesleft, &outbufptr, &outbytesleft); + if (ret != (size_t)-1) { + // Successfully converted + result.append((const char8_t*)outbuf.data(), outbuf.size() - outbytesleft); + break; + } + else if (errno == E2BIG) { + // Resize buffer and retry if buffer was too small + size_t processed = outbuf.size() - outbytesleft; + result.append((const char8_t*)outbuf.data(), processed); + outbufsize *= 2; + outbuf.resize(outbufsize); + } + else { + // Return whatever we have left, even if it's not valid + result.append((const char8_t*)outbuf.data(), outbuf.size() - outbytesleft); + break; + } } - result = std::u8string((char8_t *)outbuf.data(), str_len * 4 - outbytesleft); iconv_close(conv); - return result; -} +} \ No newline at end of file diff --git a/Game/src/Engine/Autoplay.cpp b/Game/src/Engine/Autoplay.cpp index 747c0fee..648ed1cf 100644 --- a/Game/src/Engine/Autoplay.cpp +++ b/Game/src/Engine/Autoplay.cpp @@ -1,13 +1,10 @@ #include "Autoplay.h" #include "./Timing/TimingBase.h" -constexpr int kReleaseDelay = 25; - -const double kBaseBPM = 240.0; -const double kMaxTicks = 192.0; +constexpr double kReleaseDelay = 33.34; const double kNoteCoolHitRatio = 6.0; -NoteInfo *GetNextHitObject(std::vector &hitObject, int index) +NoteInfo* GetNextHitObject(std::vector& hitObject, int index) { int lane = hitObject[index].LaneIndex; @@ -20,30 +17,30 @@ NoteInfo *GetNextHitObject(std::vector &hitObject, int index) return nullptr; } -double CalculateReleaseTime(NoteInfo *currentHitObject, NoteInfo *nextHitObject) +double CalculateReleaseTime(NoteInfo* currentHitObject, NoteInfo* nextHitObject) { if (currentHitObject->Type == NoteType::HOLD) { return currentHitObject->EndTime; } double Time = currentHitObject->Type == NoteType::HOLD ? currentHitObject->EndTime - : currentHitObject->StartTime; + : currentHitObject->StartTime; bool canDelayFully = nextHitObject == nullptr || - nextHitObject->StartTime > Time + kReleaseDelay; + nextHitObject->StartTime > Time + kReleaseDelay; return Time + (canDelayFully ? kReleaseDelay : (nextHitObject->StartTime - Time) * 0.9); } -std::vector Autoplay::CreateReplay(Chart *chart) +std::vector Autoplay::CreateReplay(Chart* chart) { std::vector result; TimingBase timingBase(chart->m_bpms, chart->m_svs, chart->InitialSvMultiplier); for (int i = 0; i < chart->m_notes.size(); i++) { - auto ¤tHitObject = chart->m_notes[i]; - auto nextHitObject = GetNextHitObject(chart->m_notes, i); + auto& currentHitObject = chart->m_notes[i]; + auto nextHitObject = GetNextHitObject(chart->m_notes, i); double HitTime = currentHitObject.StartTime; double ReleaseTime = CalculateReleaseTime(¤tHitObject, nextHitObject); @@ -51,56 +48,26 @@ std::vector Autoplay::CreateReplay(Chart *chart) double bpmAtHit = timingBase.GetBPMAt(HitTime); double bpmAtRelease = timingBase.GetBPMAt(ReleaseTime); - int tries = 0; - - while (true) { - double beat = kBaseBPM / kMaxTicks / bpmAtHit * 1000.0; - double cool = beat * kNoteCoolHitRatio; - double late_cool = -cool; - - double _HIT = currentHitObject.StartTime - HitTime; - - if (_HIT >= late_cool && _HIT <= cool) { - break; - } - - if (_HIT > cool) { - HitTime++; - } else { - HitTime--; - } + double beat = 60000.0 / bpmAtHit; + double coolWindow = beat * kNoteCoolHitRatio; + double hitError = currentHitObject.StartTime - HitTime; - if (++tries >= 500) { - break; - } + while (std::abs(hitError) > coolWindow) { + HitTime += hitError > 0 ? 1 : -1; + hitError = currentHitObject.StartTime - HitTime; } - tries = 0; - - while (true) { - double beat = kBaseBPM / kMaxTicks / bpmAtRelease * 1000.0; - double cool = beat * kNoteCoolHitRatio; - double late_cool = -cool; - - double _HIT = (currentHitObject.Type == NoteType::HOLD ? currentHitObject.EndTime : currentHitObject.StartTime) - ReleaseTime; - - if (_HIT >= late_cool && _HIT <= cool) { - break; - } - - if (_HIT > cool) { - ReleaseTime++; - } else { - ReleaseTime--; - } + beat = 60000.0 / bpmAtRelease; + coolWindow = beat * kNoteCoolHitRatio; + double releaseError = (currentHitObject.Type == NoteType::HOLD ? currentHitObject.EndTime : currentHitObject.StartTime) - ReleaseTime; - if (++tries >= 500) { - break; - } + while (std::abs(releaseError) > coolWindow) { + ReleaseTime += releaseError > 0 ? 1 : -1; + releaseError = (currentHitObject.Type == NoteType::HOLD ? currentHitObject.EndTime : currentHitObject.StartTime) - ReleaseTime; } - result.push_back({ HitTime, (int)currentHitObject.LaneIndex, ReplayHitType::KEY_DOWN }); - result.push_back({ ReleaseTime, (int)currentHitObject.LaneIndex, ReplayHitType::KEY_UP }); + result.push_back({ HitTime, static_cast(currentHitObject.LaneIndex), ReplayHitType::KEY_DOWN }); + result.push_back({ ReleaseTime, static_cast(currentHitObject.LaneIndex), ReplayHitType::KEY_UP }); } return result; diff --git a/Game/src/Engine/BGMPreview.cpp b/Game/src/Engine/BGMPreview.cpp index 5d9adc0b..1f37b144 100644 --- a/Game/src/Engine/BGMPreview.cpp +++ b/Game/src/Engine/BGMPreview.cpp @@ -71,7 +71,7 @@ void BGMPreview::Load() m_autoSamples.clear(); for (auto &sample : m_currentChart->m_autoSamples) { - m_autoSamples.push_back(sample); + m_autoSamples.emplace_back(sample); } for (auto ¬e : m_currentChart->m_notes) { @@ -82,7 +82,7 @@ void BGMPreview::Load() sm.Volume = note.Volume; sm.Pan = note.Pan; - m_autoSamples.push_back(sm); + m_autoSamples.emplace_back(sm); } } @@ -118,7 +118,7 @@ void BGMPreview::Update(double delta) auto &sample = m_autoSamples[i]; if (m_currentAudioPosition >= sample.StartTime) { if (sample.StartTime - m_currentAudioPosition < 5) { - GameAudioSampleCache::Play(sample.Index, (int)::round(sample.Volume * 50.0f), (int)::round(sample.Pan * 100.0f)); + GameAudioSampleCache::Play(sample.Index, (int)::round(sample.Volume * 100.0f), (int)::round(sample.Pan * 100.0f)); } m_currentSampleIndex++; diff --git a/Game/src/Engine/DrawableNote.cpp b/Game/src/Engine/DrawableNote.cpp index e10f6f6c..84562e1b 100644 --- a/Game/src/Engine/DrawableNote.cpp +++ b/Game/src/Engine/DrawableNote.cpp @@ -5,24 +5,21 @@ DrawableNote::DrawableNote(NoteImage* frame) : FrameTimer::FrameTimer() { - m_frames = std::vector(); + SetFPS(0.0); // HACK: Stop note glitching by force it to 0 FPS (idk why it still initialized by itself if i remove SETFPS) + m_frames.reserve(frame->Texture.size()); // Reserve memory for frames vector if (Renderer::GetInstance()->IsVulkan()) { - for (auto& frame : frame->VulkanTexture) { - m_frames.push_back(new Texture2D(frame)); + for (auto& texture : frame->VulkanTexture) { + m_frames.emplace_back(new Texture2D(texture)); + m_frames.back()->SetOriginalRECT(frame->TextureRect); // Set original RECT for each texture } } else { - for (auto& frame : frame->Texture) { - m_frames.push_back(new Texture2D(frame)); + for (auto& texture : frame->Texture) { + m_frames.emplace_back(new Texture2D(texture)); + m_frames.back()->SetOriginalRECT(frame->TextureRect); // Set original RECT for each texture } } AnchorPoint = { 0.0, 1.0 }; - - for (auto& _frame : m_frames) { - _frame->SetOriginalRECT(frame->TextureRect); - } - - SetFPS(0); // FIXME: i had to use this value otherwise it has glitch } diff --git a/Game/src/Engine/FrameTimer.cpp b/Game/src/Engine/FrameTimer.cpp index 246d455c..1d938bc3 100644 --- a/Game/src/Engine/FrameTimer.cpp +++ b/Game/src/Engine/FrameTimer.cpp @@ -2,57 +2,53 @@ #include "Rendering/Renderer.h" #include "Texture/Texture2D.h" -FrameTimer::FrameTimer() +FrameTimer::FrameTimer() : + Repeat(false), + m_currentFrame(0), + m_frameTime(1.0 / 60.0), + m_currentTime(0), + AlphaBlend(false), + Size(UDim2::fromScale(1, 1)), + TintColor({ 1.0f, 1.0f, 1.0f }) { - Repeat = false; - m_currentFrame = 0; - m_frameTime = 1.0f / 60; - m_currentTime = 0; - AlphaBlend = false; - Size = UDim2::fromScale(1, 1); - TintColor = { 1.0f, 1.0f, 1.0f }; } -FrameTimer::FrameTimer(std::vector frames) : FrameTimer::FrameTimer() +FrameTimer::FrameTimer(std::vector frames) : FrameTimer() { - m_frames = frames; + m_frames = std::move(frames); } -FrameTimer::FrameTimer(std::vector frames) : FrameTimer::FrameTimer() +FrameTimer::FrameTimer(std::vector frames) : FrameTimer() { - m_frames = std::vector(); - for (auto frame : frames) { - m_frames.push_back(new Texture2D(frame)); + for (const auto& frame : frames) { + m_frames.emplace_back(new Texture2D(frame)); } } -FrameTimer::FrameTimer(std::vector frames) : FrameTimer::FrameTimer() +FrameTimer::FrameTimer(std::vector frames) : FrameTimer() { - m_frames = std::vector(); - for (auto frame : frames) { - m_frames.push_back(new Texture2D(frame)); + for (const auto& frame : frames) { + m_frames.emplace_back(new Texture2D(frame)); } } -FrameTimer::FrameTimer(std::vector frames) : FrameTimer::FrameTimer() +FrameTimer::FrameTimer(std::vector frames) : FrameTimer() { - m_frames = std::vector(); - for (auto frame : frames) { - m_frames.push_back(new Texture2D(frame)); + for (const auto& frame : frames) { + m_frames.emplace_back(new Texture2D(frame)); } } -FrameTimer::FrameTimer(std::vector frames) : FrameTimer::FrameTimer() +FrameTimer::FrameTimer(std::vector frames) : FrameTimer() { - m_frames = std::vector(); - for (auto frame : frames) { - m_frames.push_back(new Texture2D(frame)); + for (const auto& frame : frames) { + m_frames.emplace_back(new Texture2D(frame)); } } FrameTimer::~FrameTimer() { - for (auto &f : m_frames) { + for (auto& f : m_frames) { delete f; } } @@ -62,13 +58,12 @@ void FrameTimer::Draw(double delta) Draw(delta, nullptr); } -void FrameTimer::Draw(double delta, Rect *clip) +void FrameTimer::Draw(double delta, Rect* clip) { m_currentTime += delta; if (m_currentTime >= m_frameTime) { m_currentTime -= m_frameTime; - m_currentFrame++; } @@ -79,21 +74,22 @@ void FrameTimer::Draw(double delta, Rect *clip) if (m_currentFrame < m_frames.size()) { CalculateSize(); - m_frames[m_currentFrame]->AlphaBlend = AlphaBlend; - m_frames[m_currentFrame]->TintColor = TintColor; + auto currentFrame = m_frames[m_currentFrame]; + currentFrame->AlphaBlend = AlphaBlend; + currentFrame->TintColor = TintColor; if (m_currentFrame != 0) { - m_frames[m_currentFrame]->Position = UDim2::fromOffset(AbsolutePosition.X, AbsolutePosition.Y); - m_frames[m_currentFrame]->Size = UDim2::fromOffset(AbsoluteSize.X, AbsoluteSize.Y); - m_frames[m_currentFrame]->AnchorPoint = { 0, 0 }; + currentFrame->Position = UDim2::fromOffset(AbsolutePosition.X, AbsolutePosition.Y); + currentFrame->Size = UDim2::fromOffset(AbsoluteSize.X, AbsoluteSize.Y); + currentFrame->AnchorPoint = { 0, 0 }; } - m_frames[m_currentFrame]->Draw(clip); + currentFrame->Draw(clip); } } -void FrameTimer::SetFPS(float fps) +void FrameTimer::SetFPS(double fps) { - m_frameTime = 1.0f / fps; + m_frameTime = 1.0 / fps; } void FrameTimer::ResetIndex() @@ -103,7 +99,7 @@ void FrameTimer::ResetIndex() void FrameTimer::LastIndex() { - m_currentFrame = (int)m_frames.size() - 1; + m_currentFrame = static_cast(m_frames.size()) - 1; } void FrameTimer::SetIndexAt(int idx) @@ -113,11 +109,12 @@ void FrameTimer::SetIndexAt(int idx) void FrameTimer::CalculateSize() { - m_frames[0]->AnchorPoint = AnchorPoint; - m_frames[0]->Size = Size; - m_frames[0]->Position = Position; - m_frames[0]->CalculateSize(); - - AbsoluteSize = m_frames[0]->AbsoluteSize; - AbsolutePosition = m_frames[0]->AbsolutePosition; + auto& firstFrame = *m_frames[0]; + firstFrame.AnchorPoint = AnchorPoint; + firstFrame.Size = Size; + firstFrame.Position = Position; + firstFrame.CalculateSize(); + + AbsoluteSize = firstFrame.AbsoluteSize; + AbsolutePosition = firstFrame.AbsolutePosition; } diff --git a/Game/src/Engine/FrameTimer.hpp b/Game/src/Engine/FrameTimer.hpp index 2d73dc62..b8846be7 100644 --- a/Game/src/Engine/FrameTimer.hpp +++ b/Game/src/Engine/FrameTimer.hpp @@ -16,11 +16,11 @@ class FrameTimer { public: FrameTimer(); - FrameTimer(std::vector frames); + FrameTimer(std::vector frames); FrameTimer(std::vector frames); FrameTimer(std::vector frames); - FrameTimer(std::vector frames); - FrameTimer(std::vector frames); + FrameTimer(std::vector frames); + FrameTimer(std::vector frames); ~FrameTimer(); bool Repeat; @@ -35,16 +35,17 @@ class FrameTimer Vector2 AbsoluteSize; void Draw(double delta); - void Draw(double delta, Rect *clip); - void SetFPS(float fps); + void Draw(double delta, Rect* clip); + void SetFPS(double fps); void ResetIndex(); void LastIndex(); void SetIndexAt(int idx); void CalculateSize(); + std::vector m_frames; + protected: - std::vector m_frames; int m_currentFrame; double m_frameTime; double m_currentTime; diff --git a/Game/src/Engine/GameTrack.cpp b/Game/src/Engine/GameTrack.cpp index b03396b1..12a3e84b 100644 --- a/Game/src/Engine/GameTrack.cpp +++ b/Game/src/Engine/GameTrack.cpp @@ -71,7 +71,7 @@ void GameTrack::Update(double delta) if (note->IsRemoveable()) { note->Release(); - m_noteCaches.push_back(note); + m_noteCaches.emplace_back(note); _note = m_inactive_notes.erase(_note); } else { note->Update(delta); diff --git a/Game/src/Engine/LuaScripting.cpp b/Game/src/Engine/LuaScripting.cpp index 949a2506..e34dac12 100644 --- a/Game/src/Engine/LuaScripting.cpp +++ b/Game/src/Engine/LuaScripting.cpp @@ -147,15 +147,30 @@ void GetLuaState(ScriptState &state, sol::table &table) state.init = table["init"]; } -void LuaScripting::TryLoadGroup(SkinGroup group) + +void LuaScripting::TryLoadGroup(SkinGroup group) // Not fully testes unless someone want make skin with lua script { - auto fullPath = m_lua_dir_path / m_expected_files[group]; + std::filesystem::path fullPath; + + if (group == SkinGroup::Notes && EnvironmentSetup::GetInt("NoteSkin") != 2) { + auto path = std::filesystem::current_path() / "Resources"; + if (EnvironmentSetup::GetInt("NoteSkin") == 1) { + fullPath = path / "Scripts" / "Notes.lua"; + } + else { + fullPath = path / "Scripts" / "Notes.lua"; + } + } + else { + fullPath = m_lua_dir_path / m_expected_files[group]; + } + if (!std::filesystem::exists(fullPath)) { throw std::runtime_error("Missing file: " + fullPath.string()); } m_states[group] = {}; - auto &state = m_states[group]; + auto& state = m_states[group]; state.state = sol::state(); if (state.state.lua_state() == nullptr) { diff --git a/Game/src/Engine/Note.cpp b/Game/src/Engine/Note.cpp index 547e299f..40f6561b 100644 --- a/Game/src/Engine/Note.cpp +++ b/Game/src/Engine/Note.cpp @@ -6,6 +6,7 @@ #include "GameAudioSampleCache.hpp" #include "NoteImageCacheManager.hpp" #include "RhythmEngine.hpp" +#include #define REMOVE_TIME 800 #define HOLD_COMBO_TICK 100 @@ -91,7 +92,7 @@ Note::Note(RhythmEngine *engine, GameTrack *track) m_hitPos = 0; m_relPos = 0; - m_keyVolume = 50; + m_keyVolume = 100; m_keyPan = 0; m_lastScoreTime = -1; @@ -152,6 +153,7 @@ void Note::Load(NoteInfoDesc *desc) m_relPos = 0; m_lastScoreTime = -1; + GetNoteSize(); } void Note::Update(double delta) @@ -208,121 +210,151 @@ void Note::Update(double delta) } } +// I ended refactor whole note code +void Note::DrawHead(double delta, double headPosY, int guideLineLength, Rect &playRect) { + m_head->Position = UDim2::fromOffset(m_laneOffset, headPosY); + m_head->SetIndexAt(m_engine->GetNoteImageIndex()); + m_head->Draw(delta, &playRect); + + if (guideLineLength > 0) { + m_trail_down->Position = m_head->Position; + m_trail_down->Size = UDim2::fromOffset(1, guideLineLength); + m_trail_down->AnchorPoint = { 0, 0 }; + m_trail_down->AlphaBlend = true; + m_trail_down->Draw(delta, &playRect); + + m_trail_down->Position = m_head->Position + UDim2::fromOffset(m_head->AbsoluteSize.X, 0); + m_trail_down->AnchorPoint = { 1, 0 }; + m_trail_down->AlphaBlend = true; + m_trail_down->Draw(delta, &playRect); + + m_trail_up->Position = m_head->Position + UDim2::fromOffset(0, -m_head->AbsoluteSize.Y); + m_trail_up->Size = UDim2::fromOffset(1, guideLineLength); + m_trail_up->AnchorPoint = { 0, 1 }; + m_trail_up->AlphaBlend = true; + m_trail_up->Draw(delta, &playRect); + + m_trail_up->Position = m_head->Position + UDim2::fromOffset(m_head->AbsoluteSize.X, -m_head->AbsoluteSize.Y); + m_trail_up->AnchorPoint = { 1, 1 }; + m_trail_up->AlphaBlend = true; + m_trail_up->Draw(delta, &playRect); + } +} + +void Note::DrawBody(double delta, double bodyPosY, double bodyHeight, Rect &playRect) { + m_body->Position = UDim2::fromOffset(m_laneOffset, bodyPosY - (m_head->AbsoluteSize.Y / 2.0)); + m_body->Size = { 1, 0, 0, bodyHeight }; + m_body->SetIndexAt(m_engine->GetNoteImageIndex()); + m_body->Draw(delta, &playRect); +} + +void Note::DrawTail(double delta, double tailPosY, int guideLineLength, Rect &playRect) { + m_tail->Position = UDim2::fromOffset(m_laneOffset, tailPosY); + m_tail->SetIndexAt(m_engine->GetNoteImageIndex()); + m_tail->Draw(delta, &playRect); + + if (guideLineLength > 0) { + m_trail_down->Position = m_tail->Position; + m_trail_down->Size = UDim2::fromOffset(1, guideLineLength); + m_trail_down->AnchorPoint = { 0, 0 }; + m_trail_down->AlphaBlend = true; + m_trail_down->Draw(delta, &playRect); + + m_trail_down->Position = m_tail->Position + UDim2::fromOffset(m_tail->AbsoluteSize.X, 0); + m_trail_down->AnchorPoint = { 1, 0 }; + m_trail_down->AlphaBlend = true; + m_trail_down->Draw(delta, &playRect); + + m_trail_up->Position = m_tail->Position + UDim2::fromOffset(0, -m_tail->AbsoluteSize.Y); + m_trail_up->Size = UDim2::fromOffset(1, guideLineLength); + m_trail_up->AnchorPoint = { 0, 1 }; + m_trail_up->AlphaBlend = true; + m_trail_up->Draw(delta, &playRect); + + m_trail_up->Position = m_tail->Position + UDim2::fromOffset(m_tail->AbsoluteSize.X, -m_tail->AbsoluteSize.Y); + m_trail_up->AnchorPoint = { 1, 1 }; + m_trail_up->AlphaBlend = true; + m_trail_up->Draw(delta, &playRect); + } +} + +void Note::SetTransparency() { + float transparency = 0.9f; + if (m_hitResult >= NoteResult::GOOD && m_state == NoteState::HOLD_ON_HOLDING) { + transparency = 1.0f; + } + else if (m_hitResult >= NoteResult::MISS && m_state == NoteState::HOLD_MISSED_ACTIVE) { + transparency = 0.7f; + } + m_head->TintColor = { transparency, transparency, transparency }; + m_body->TintColor = { transparency, transparency, transparency }; + m_tail->TintColor = { transparency, transparency, transparency }; +} + void Note::Render(double delta) { - if (IsRemoveable()) + if (IsRemoveable()) { return; - if (!m_drawAble) + } + if (!m_drawAble) { return; + } - auto resolution = m_engine->GetResolution(); - auto hitPos = m_engine->GetHitPosition(); + auto resolution = m_engine->GetResolution(); + auto hitPos = m_engine->GetHitPosition(); double trackPosition = m_engine->GetTrackPosition(); - int min = -100, max = hitPos + 25; + int min = -100, max = GameWindow::GetInstance()->GetBufferHeight(); auto playRect = m_engine->GetPlayRectangle(); int guideLineIndex = m_engine->GetGuideLineIndex(); - int guideLineLength = 24 * length_multiplier[guideLineIndex]; + double y1 = CalculateNotePosition(trackPosition, m_initialTrackPosition, 1000.0, m_engine->GetNotespeed(), false) / 1000.0; + double headPosY = lerp(0.0, static_cast(hitPos), static_cast(y1)); + bool isHeadVisible = isWithinRange(headPosY, min, max); + if (m_type == NoteType::HOLD) { - double y1 = CalculateNotePosition(trackPosition, m_initialTrackPosition, 1000.0, m_engine->GetNotespeed(), false) / 1000.0; double y2 = CalculateNotePosition(trackPosition, m_endTrackPosition, 1000.0, m_engine->GetNotespeed(), false) / 1000.0; + double tailPosY = lerp(0.0, static_cast(hitPos), static_cast(y2)); + bool isTailVisible = isWithinRange(tailPosY, min, max); - m_head->Position = UDim2::fromOffset(m_laneOffset, lerp(0.0, (double)hitPos, (float)y1)); - m_tail->Position = UDim2::fromOffset(m_laneOffset, lerp(0.0, (double)hitPos, (float)y2)); - - float Transparency = 0.9f; - - if (m_hitResult >= NoteResult::GOOD && m_state == NoteState::HOLD_ON_HOLDING) { - // m_head->Position.Y.Offset = hitPos; - Transparency = 1.0f; + if (EnvironmentSetup::GetInt("NewLN") == 1) { + tailPosY += m_tail->AbsoluteSize.Y; } - m_head->CalculateSize(); - m_tail->CalculateSize(); - - double headPos = m_head->AbsolutePosition.Y + (m_head->AbsoluteSize.Y / 2.0); - double tailPos = m_tail->AbsolutePosition.Y + (m_tail->AbsoluteSize.Y / 2.0); - - double height = headPos - tailPos; - double position = (height / 2.0) + tailPos; - - m_body->Position = UDim2::fromOffset(m_laneOffset, position); - m_body->Size = { 1, 0, 0, height }; - - m_body->TintColor = { Transparency, Transparency, Transparency }; - - bool b1 = isWithinRange(m_head->Position.Y.Offset, min, max); - bool b2 = isWithinRange(m_tail->Position.Y.Offset, min, max); + double bodyPosY = (headPosY + tailPosY) / 2.0; + double bodyHeight = std::abs(headPosY - (m_head->AbsoluteSize.Y / 2.0)) - (tailPosY - (m_head->AbsoluteSize.Y / 2.0)); - if (isCollision(m_tail->Position.Y.Offset, m_head->Position.Y.Offset, min, max)) { - m_body->SetIndexAt(m_engine->GetNoteImageIndex()); - m_body->Draw(delta, &playRect); - } - - if (b1) { - if (guideLineLength > 0) { - m_trail_down->Position = m_head->Position; - m_trail_down->Size = UDim2::fromOffset(1, guideLineLength); - m_trail_down->AnchorPoint = { 0, 0 }; - m_trail_down->Draw(delta, &playRect); + if (EnvironmentSetup::GetInt("LNBodyOnTop") == 1) { + if (isHeadVisible) { + DrawHead(delta, headPosY, guideLineLength, playRect); + } - m_trail_down->Position = m_head->Position + UDim2::fromOffset(m_head->AbsoluteSize.X, 0); - m_trail_down->AnchorPoint = { 1, 0 }; - m_trail_down->Draw(delta, &playRect); + if (isTailVisible) { + DrawTail(delta, tailPosY, guideLineLength, playRect); } - m_head->SetIndexAt(m_engine->GetNoteImageIndex()); - m_head->Draw(delta, &playRect); + SetTransparency(); + DrawBody(delta, bodyPosY, bodyHeight, playRect); } + else { + SetTransparency(); + DrawBody(delta, bodyPosY, bodyHeight, playRect); - if (b2) { - if (guideLineLength > 0) { - m_trail_up->Position = m_tail->Position + UDim2::fromOffset(0, -m_tail->AbsoluteSize.Y); - m_trail_up->Size = UDim2::fromOffset(1, guideLineLength); - m_trail_up->AnchorPoint = { 0, 1 }; - m_trail_up->Draw(delta, &playRect); - - m_trail_up->Position = m_tail->Position + UDim2::fromOffset(m_tail->AbsoluteSize.X, -m_tail->AbsoluteSize.Y); - m_trail_up->AnchorPoint = { 1, 1 }; - m_trail_up->Draw(delta, &playRect); + if (isTailVisible) { + DrawTail(delta, tailPosY, guideLineLength, playRect); } - m_tail->SetIndexAt(m_engine->GetNoteImageIndex()); - m_tail->Draw(delta, &playRect); - } - } else { - double y1 = CalculateNotePosition(trackPosition, m_initialTrackPosition, 1000.0, m_engine->GetNotespeed(), false) / 1000.0; - m_head->Position = UDim2::fromOffset(m_laneOffset, lerp(0.0, (double)hitPos, (float)y1)); - m_head->CalculateSize(); - - bool b1 = isWithinRange(m_head->Position.Y.Offset, min, max); - - if (b1) { - if (guideLineLength > 0) { - m_trail_down->Position = m_head->Position; - m_trail_down->Size = UDim2::fromOffset(1, guideLineLength); - m_trail_down->AnchorPoint = { 0, 0 }; - m_trail_down->Draw(delta, &playRect); - - m_trail_down->Position = m_head->Position + UDim2::fromOffset(m_head->AbsoluteSize.X, 0); - m_trail_down->AnchorPoint = { 1, 0 }; - m_trail_down->Draw(delta, &playRect); - - m_trail_up->Position = m_head->Position + UDim2::fromOffset(0, -m_head->AbsoluteSize.Y); - m_trail_up->Size = UDim2::fromOffset(1, guideLineLength); - m_trail_up->AnchorPoint = { 0, 1 }; - m_trail_up->Draw(delta, &playRect); - - m_trail_up->Position = m_head->Position + UDim2::fromOffset(m_head->AbsoluteSize.X, -m_head->AbsoluteSize.Y); - m_trail_up->AnchorPoint = { 1, 1 }; - m_trail_up->Draw(delta, &playRect); + if (isHeadVisible) { + DrawHead(delta, headPosY, guideLineLength, playRect); } + } + } + else { - m_head->SetIndexAt(m_engine->GetNoteImageIndex()); - m_head->Draw(delta, &playRect); + if (isHeadVisible) { + DrawHead(delta, headPosY, guideLineLength, playRect); } } } @@ -389,27 +421,38 @@ std::tuple Note::CheckHit() if (m_type == NoteType::NORMAL) { double time_to_end = m_engine->GetGameAudioPosition() - m_startTime; - auto result = judge->CalculateResult(this); + auto result = judge->CalculateResult(this); if (std::get(result)) { m_ignore = false; } + else if (time_to_end) { + m_ignore = true; + } return result; - } else { + } + else { if (m_state == NoteState::HOLD_PRE) { double time_to_end = m_engine->GetGameAudioPosition() - m_startTime; - auto result = judge->CalculateResult(this); + auto result = judge->CalculateResult(this); if (std::get(result)) { m_ignore = false; } + else if (time_to_end) { + m_ignore = true; + } return result; - } else if (m_state == NoteState::HOLD_MISSED_ACTIVE) { + } + else if (m_state == NoteState::HOLD_MISSED_ACTIVE) { double time_to_end = m_engine->GetGameAudioPosition() - m_endTime; - auto result = judge->CalculateResult(this); + auto result = judge->CalculateResult(this); if (std::get(result)) { m_ignore = false; } + else if (time_to_end) { + m_ignore = true; + } return result; } @@ -421,7 +464,7 @@ std::tuple Note::CheckHit() std::tuple Note::CheckRelease() { if (m_type == NoteType::HOLD) { - double time_to_end = m_engine->GetGameAudioPosition() - m_endTime; + double time_to_end = m_engine->GetGameAudioPosition() - m_endTime; JudgeBase *judge = m_engine->GetJudge(); if (m_state == NoteState::HOLD_ON_HOLDING || m_state == NoteState::HOLD_MISSED_ACTIVE) { @@ -429,6 +472,7 @@ std::tuple Note::CheckRelease() if (std::get(result)) { if (m_state == NoteState::HOLD_MISSED_ACTIVE) { + m_ignore = false; return { true, NoteResult::BAD }; } @@ -436,8 +480,15 @@ std::tuple Note::CheckRelease() } if (m_state == NoteState::HOLD_ON_HOLDING) { + if (time_to_end) { + m_ignore = true; + } return { true, NoteResult::MISS }; - } else { + } + else { + if (time_to_end) { + m_ignore = true; + } return { false, NoteResult::MISS }; } } @@ -489,7 +540,7 @@ void Note::OnRelease(NoteResult result) m_lastScoreTime = -1; if (result == NoteResult::MISS) { - GameAudioSampleCache::Stop(m_keysoundIndex); + //GameAudioSampleCache::Stop(m_keysoundIndex); m_state = NoteState::HOLD_MISSED_ACTIVE; m_track->HandleHoldScore(HoldResult::HoldBreak); @@ -521,12 +572,17 @@ void Note::SetDrawable(bool drawable) m_drawAble = drawable; } -bool Note::IsHoldEffectDrawable() +void Note::GetNoteSize() +{ + EnvironmentSetup::SetInt("NoteSize", static_cast(m_head->AbsoluteSize.Y)); +} + +bool Note::IsHoldEffectDrawable() const { return m_shouldDrawHoldEffect; } -bool Note::IsDrawable() +bool Note::IsDrawable() const { if (m_removeAble) return false; @@ -534,22 +590,22 @@ bool Note::IsDrawable() return m_drawAble; } -bool Note::IsRemoveable() +bool Note::IsRemoveable() const { return m_state == NoteState::DO_REMOVE; } -bool Note::IsPassed() +bool Note::IsPassed() const { - return m_state == NoteState::NORMAL_NOTE_PASSED || m_state == NoteState::HOLD_PASSED; + return m_state == NoteState::NORMAL_NOTE_PASSED || m_state == NoteState::HOLD_PASSED || IsRemoveable(); } -bool Note::IsHeadHit() +bool Note::IsHeadHit() const { return m_didHitHead; } -bool Note::IsTailHit() +bool Note::IsTailHit() const { return m_didHitTail; } diff --git a/Game/src/Engine/Note.hpp b/Game/src/Engine/Note.hpp index 8061113a..4fdd8510 100644 --- a/Game/src/Engine/Note.hpp +++ b/Game/src/Engine/Note.hpp @@ -52,6 +52,10 @@ class Note { void Load(NoteInfoDesc* desc); void Update(double delta); + void DrawHead(double delta, double headPosY, int guideLineLength, Rect& playRect); + void DrawBody(double delta, double bodyPosY, double bodyHeight, Rect& playRect); + void DrawTail(double delta, double tailPosY, int guideLineLength, Rect& playRect); + void SetTransparency(); void Render(double delta); double GetInitialTrackPosition() const; @@ -71,14 +75,16 @@ class Note { void SetXPosition(int x); void SetDrawable(bool drawable); - - bool IsHoldEffectDrawable(); - bool IsDrawable(); - bool IsRemoveable(); - bool IsPassed(); - bool IsHeadHit(); - bool IsTailHit(); + void GetNoteSize(); + + bool IsHoldEffectDrawable() const; + bool IsDrawable() const; + bool IsRemoveable() const; + bool IsPassed() const; + + bool IsHeadHit() const; + bool IsTailHit() const; void Release(); diff --git a/Game/src/Engine/NoteImageCacheManager.cpp b/Game/src/Engine/NoteImageCacheManager.cpp index d2a7d9bf..a5debe85 100644 --- a/Game/src/Engine/NoteImageCacheManager.cpp +++ b/Game/src/Engine/NoteImageCacheManager.cpp @@ -1,154 +1,114 @@ #include "NoteImageCacheManager.hpp" -#include #include -constexpr int MAX_OBJECTS = 50; +constexpr int MAX_OBJECTS = 64; -NoteImageCacheManager::NoteImageCacheManager() -{ - m_noteTextures = std::unordered_map>(); +NoteImageCacheManager::NoteImageCacheManager() { } -NoteImageCacheManager::~NoteImageCacheManager() -{ - for (auto &it : m_noteTextures) { - for (auto &it2 : it.second) { - delete it2; - } - } +NoteImageCacheManager::~NoteImageCacheManager() { - for (auto &it : m_holdTextures) { - for (auto &it2 : it.second) { - delete it2; - } - } +} - for (auto &it : m_trailTextures) { - for (auto &it2 : it.second) { - delete it2; - } +NoteImageCacheManager* NoteImageCacheManager::s_instance = nullptr; + +NoteImageCacheManager* NoteImageCacheManager::GetInstance() { + if (s_instance == nullptr) { + s_instance = new NoteImageCacheManager(); } + return s_instance; } -NoteImageCacheManager *NoteImageCacheManager::s_instance = nullptr; +void NoteImageCacheManager::Release() { + if (s_instance) { + // Clean up all note textures + for (auto& pair : s_instance->m_noteTextures) { + for (auto& note : pair.second) { + delete note; + } + } + s_instance->m_noteTextures.clear(); -void NoteImageCacheManager::Repool(DrawableNote *image, NoteImageType noteType) -{ - if (image == nullptr) - return; + // Clean up all hold textures + for (auto& pair : s_instance->m_holdTextures) { + for (auto& note : pair.second) { + delete note; + } + } + s_instance->m_holdTextures.clear(); - auto &it = m_noteTextures[noteType]; + // Clean up all trail textures + for (auto& pair : s_instance->m_trailTextures) { + for (auto& note : pair.second) { + delete note; + } + } + s_instance->m_trailTextures.clear(); - if (it.size() >= MAX_OBJECTS) { - delete image; - return; + delete s_instance; + s_instance = nullptr; } - - it.push_back(image); } -void NoteImageCacheManager::RepoolHold(DrawableNote *image, NoteImageType noteType) -{ - if (image == nullptr) +void NoteImageCacheManager::Repool(DrawableNote* image, NoteImageType noteType) { + if (image == nullptr || m_noteTextures[noteType].size() >= MAX_OBJECTS) return; - auto &it = m_holdTextures[noteType]; - - if (it.size() >= MAX_OBJECTS) { - delete image; - return; - } - - it.push_back(image); + m_noteTextures[noteType].push_back(image); } -void NoteImageCacheManager::RepoolTrail(DrawableNote *image, NoteImageType noteType) -{ - if (image == nullptr) +void NoteImageCacheManager::RepoolHold(DrawableNote* image, NoteImageType noteType) { + if (image == nullptr || m_holdTextures[noteType].size() >= MAX_OBJECTS) return; - auto &it = m_trailTextures[noteType]; + m_holdTextures[noteType].push_back(image); +} - if (it.size() >= MAX_OBJECTS) { - delete image; +void NoteImageCacheManager::RepoolTrail(DrawableNote* image, NoteImageType noteType) { + if (image == nullptr || m_trailTextures[noteType].size() >= MAX_OBJECTS) return; - } - it.push_back(image); + m_trailTextures[noteType].push_back(image); } -DrawableNote *NoteImageCacheManager::Depool(NoteImageType noteType) -{ - if (noteType >= NoteImageType::LANE_1 && noteType <= NoteImageType::LANE_7) { - auto &it = m_noteTextures[noteType]; - DrawableNote *image = nullptr; - if (it.size() > 0) { - image = it.back(); - it.pop_back(); - } else { - image = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); - image->Repeat = true; - } - - return image; +DrawableNote* NoteImageCacheManager::Depool(NoteImageType noteType) { + auto& textureMap = m_noteTextures[noteType]; + if (!textureMap.empty()) { + DrawableNote* note = textureMap.back(); + textureMap.pop_back(); + return note; } else { - return nullptr; + // Create a new note if the pool is empty + DrawableNote* note = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); + note->Repeat = true; + return note; } } -DrawableNote *NoteImageCacheManager::DepoolHold(NoteImageType noteType) -{ - if (noteType >= NoteImageType::HOLD_LANE_1 && noteType <= NoteImageType::HOLD_LANE_7) { - auto &it = m_holdTextures[noteType]; - DrawableNote *image = nullptr; - if (it.size() > 0) { - image = it.back(); - it.pop_back(); - } else { - image = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); - image->Repeat = true; - } - - return image; +DrawableNote* NoteImageCacheManager::DepoolHold(NoteImageType noteType) { + auto& textureMap = m_holdTextures[noteType]; + if (!textureMap.empty()) { + DrawableNote* note = textureMap.back(); + textureMap.pop_back(); + return note; } else { - return nullptr; + // Create a new hold note if the pool is empty + DrawableNote* note = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); + note->Repeat = true; + return note; } } -DrawableNote *NoteImageCacheManager::DepoolTrail(NoteImageType noteType) -{ - if (noteType >= NoteImageType::TRAIL_UP && noteType <= NoteImageType::TRAIL_DOWN) { - auto &it = m_trailTextures[noteType]; - DrawableNote *image = nullptr; - if (it.size() > 0) { - image = it.back(); - it.pop_back(); - } else { - image = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); - image->Repeat = true; - } - - return image; +DrawableNote* NoteImageCacheManager::DepoolTrail(NoteImageType noteType) { + auto& textureMap = m_trailTextures[noteType]; + if (!textureMap.empty()) { + DrawableNote* note = textureMap.back(); + textureMap.pop_back(); + return note; } else { - return nullptr; - } -} - -NoteImageCacheManager *NoteImageCacheManager::GetInstance() -{ - if (s_instance == nullptr) { - s_instance = new NoteImageCacheManager(); - } - - return s_instance; -} - -void NoteImageCacheManager::Release() -{ - if (s_instance) { - Logs::Puts("[NoteImageCacheManager] Release about: hold=%d, note=%d, trail=%d", s_instance->m_holdTextures.size(), s_instance->m_noteTextures.size(), s_instance->m_trailTextures.size()); - - delete s_instance; - s_instance = nullptr; + // Create a new trail note if the pool is empty + DrawableNote* note = new DrawableNote(GameNoteResource::GetNoteTexture(noteType)); + note->Repeat = true; + return note; } } diff --git a/Game/src/Engine/NoteImageCacheManager.hpp b/Game/src/Engine/NoteImageCacheManager.hpp index b72333e8..3850a41b 100644 --- a/Game/src/Engine/NoteImageCacheManager.hpp +++ b/Game/src/Engine/NoteImageCacheManager.hpp @@ -12,22 +12,22 @@ class NoteImageCacheManager ~NoteImageCacheManager(); // Store a note texture in the cache - void Repool(DrawableNote *image, NoteImageType noteType); - void RepoolHold(DrawableNote *image, NoteImageType noteType); - void RepoolTrail(DrawableNote *image, NoteImageType noteType); + void Repool(DrawableNote* image, NoteImageType noteType); + void RepoolHold(DrawableNote* image, NoteImageType noteType); + void RepoolTrail(DrawableNote* image, NoteImageType noteType); // Get a note texture from the cache if exists, otherwise create a new one - DrawableNote *Depool(NoteImageType noteType); - DrawableNote *DepoolHold(NoteImageType noteType); - DrawableNote *DepoolTrail(NoteImageType noteType); + DrawableNote* Depool(NoteImageType noteType); + DrawableNote* DepoolHold(NoteImageType noteType); + DrawableNote* DepoolTrail(NoteImageType noteType); - static NoteImageCacheManager *GetInstance(); + static NoteImageCacheManager* GetInstance(); static void Release(); private: - static NoteImageCacheManager *s_instance; + static NoteImageCacheManager* s_instance; - std::unordered_map> m_noteTextures; - std::unordered_map> m_holdTextures; - std::unordered_map> m_trailTextures; + std::unordered_map> m_noteTextures; + std::unordered_map> m_holdTextures; + std::unordered_map> m_trailTextures; }; \ No newline at end of file diff --git a/Game/src/Engine/RhythmEngine.cpp b/Game/src/Engine/RhythmEngine.cpp index 66635d28..fc2327fc 100644 --- a/Game/src/Engine/RhythmEngine.cpp +++ b/Game/src/Engine/RhythmEngine.cpp @@ -1,8 +1,9 @@ -#include "RhythmEngine.hpp" +#include "RhythmEngine.hpp" #include #include #include #include +#include #include "../EnvironmentSetup.hpp" #include "Configuration.h" @@ -17,9 +18,6 @@ #include "Judgements/BeatBasedJudge.h" #include "Judgements/MsBasedJudge.h" -#include -#include - #define MAX_BUFFER_TXT_SIZE 256 struct ManiaKeyState @@ -62,7 +60,10 @@ namespace { int trackOffset[] = { 5, 33, 55, 82, 114, 142, 164 }; } // namespace -RhythmEngine::RhythmEngine() +RhythmEngine::RhythmEngine() : + m_lanePos{ 0 }, + m_laneSize{ 0 }, + m_playRectangle{ 0, 0, 0, 0 } { m_currentAudioGamePosition = 0; m_currentVisualPosition = 0; @@ -70,8 +71,10 @@ RhythmEngine::RhythmEngine() m_rate = 1; m_offset = 0; m_scrollSpeed = 180; - + m_PlayTime = 0.0; m_timingPositionMarkers = std::vector(); + m_baseBPM = 0; + m_currentChart = nullptr; } RhythmEngine::~RhythmEngine() @@ -93,7 +96,7 @@ bool RhythmEngine::Load(Chart *chart) m_noteMaxImageIndex = 99; int currentX = m_laneOffset; - for (int i = 0; i < 7; i++) { + for (int i = 0; i < chart->m_keyCount; i++) { m_tracks.push_back(new GameTrack(this, i, currentX)); m_autoHitIndex[i] = 0; @@ -103,10 +106,10 @@ bool RhythmEngine::Load(Chart *chart) }); } - auto noteTex = GameNoteResource::GetNoteTexture(Key2Type[i]); + auto noteImage = GameNoteResource::GetNoteTexture(Key2Type[i]); - int size = noteTex->TextureRect.right; - m_noteMaxImageIndex = (std::min)(noteTex->MaxFrames, m_noteMaxImageIndex); + int size = noteImage->TextureRect.right; + m_noteMaxImageIndex = (std::min)(noteImage->MaxFrames, m_noteMaxImageIndex); m_lanePos[i] = static_cast(currentX); m_laneSize[i] = static_cast(size); @@ -124,9 +127,10 @@ bool RhythmEngine::Load(Chart *chart) chart->ApplyMod(Mod::MIRROR); } else if (EnvironmentSetup::GetInt("Random")) { chart->ApplyMod(Mod::RANDOM); + } else if (EnvironmentSetup::GetInt("Panic")) { + chart->ApplyMod(Mod::PANIC); } else if (EnvironmentSetup::GetInt("Rearrange")) { void *lane_data = EnvironmentSetup::GetObj("LaneData"); - chart->ApplyMod(Mod::REARRANGE, lane_data); } else if (EnvironmentSetup::GetInt("NoSV")) { isSV = true; @@ -218,14 +222,30 @@ bool RhythmEngine::Load(Chart *chart) m_rate = std::clamp(m_rate, 0.5, 2.0); } - m_title = chart->m_title; char buffer[MAX_BUFFER_TXT_SIZE]; - sprintf(buffer, "Lv.%d %s", chart->m_level, (const char *)chart->m_title.c_str()); - m_title = std::u8string(buffer, buffer + strlen(buffer)); + if (EnvironmentSetup::GetInt("SongType") == 1) { + m_title = chart->m_title; + std::string titleStr = std::string(chart->m_title.begin(), chart->m_title.end()); + std::snprintf(buffer, MAX_BUFFER_TXT_SIZE, "Lv.%d %s", chart->m_level, titleStr.c_str()); + } + else if (EnvironmentSetup::GetInt("SongType") == 2) { + m_title = chart->m_title; + std::string difnameStr = std::string(chart->m_difname.begin(), chart->m_difname.end()); + std::string titleStr = std::string(chart->m_title.begin(), chart->m_title.end()); + std::snprintf(buffer, MAX_BUFFER_TXT_SIZE, "[%s] %s", difnameStr.c_str(), titleStr.c_str()); + } + else { + m_title = chart->m_title; + std::string titleStr = std::string(chart->m_title.begin(), chart->m_title.end()); + std::snprintf(buffer, MAX_BUFFER_TXT_SIZE, "%d %s", chart->m_level, titleStr.c_str()); + } + + // Reset the u8string with the result of snprintf + m_title = std::u8string(buffer, buffer + std::strlen(buffer)); if (m_rate != 1.0) { memset(buffer, 0, MAX_BUFFER_TXT_SIZE); - sprintf(buffer, "[%.2fx] %s", m_rate, (const char *)m_title.c_str()); + sprintf(buffer, "[%.2fx] %s", m_rate, (const char*)m_title.c_str()); m_title = std::u8string(buffer, buffer + strlen(buffer)); } @@ -296,7 +316,7 @@ bool RhythmEngine::Load(Chart *chart) m_timingLineManager = chart->m_customMeasures.size() > 0 ? new TimingLineManager(this, chart->m_customMeasures) : new TimingLineManager(this); m_scoreManager = new ScoreManager(); - m_startClock = std::chrono::system_clock::now(); + //m_startClock = std::chrono::system_clock::now(); m_timingLineManager->Init(); m_state = GameState::NotGame; @@ -312,16 +332,25 @@ void RhythmEngine::SetKeys(Keys *keys) bool RhythmEngine::Start() { // no, use update event instead - m_currentAudioPosition -= 3000; + EnvironmentSetup::SetInt("NowPlaying", 1); + m_currentAudioPosition -= 5000; m_state = GameState::Playing; - - m_startClock = std::chrono::system_clock::now(); + //m_startClock = std::chrono::system_clock::now(); return true; } bool RhythmEngine::Stop() { m_state = GameState::PosGame; + GameAudioSampleCache::StopAll(); + return true; +} + + +bool RhythmEngine::Fail() +{ + m_state = GameState::Fail; + GameAudioSampleCache::StopAll(); return true; } @@ -338,6 +367,7 @@ void RhythmEngine::Update(double delta) // Since I'm coming from Roblox, and I had no idea how to Real-Time sync the audio // I decided to use this method again from Roblox project I did in past. double last = m_currentAudioPosition; + m_PlayTime += delta; m_currentAudioPosition += (delta * m_rate) * 1000; // check difference between last and current audio position @@ -347,13 +377,18 @@ void RhythmEngine::Update(double delta) // assert(false); // TODO: Handle this } - if (m_currentAudioPosition > m_audioLength + 2500) { // Avoid game ended too early + if (m_currentAudioPosition > m_audioLength + 3000) { // Avoid game ended too early + GameAudioSampleCache::StopAll(); m_state = GameState::PosGame; - ::printf("Audio stopped!\n"); } - if (static_cast(m_currentAudioPosition) % 1000 == 0) { + static std::chrono::steady_clock::time_point lastUpdateTime = std::chrono::steady_clock::now(); + auto currentTime = std::chrono::steady_clock::now(); + auto lastUpdate = std::chrono::duration_cast(currentTime - lastUpdateTime); + + if (lastUpdate.count() >= 100) { m_noteImageIndex = (m_noteImageIndex + 1) % m_noteMaxImageIndex; + lastUpdateTime = currentTime; } UpdateVirtualResolution(); @@ -392,9 +427,9 @@ void RhythmEngine::Update(double delta) } } - auto currentTime = std::chrono::system_clock::now(); - auto elapsedTime = std::chrono::duration_cast(currentTime - m_startClock); - m_PlayTime = static_cast(elapsedTime.count() - 2); + //auto currentTime = std::chrono::system_clock::now(); + //auto elapsedTime = std::chrono::duration_cast(currentTime - m_startClock); + //m_PlayTime = static_cast(elapsedTime.count() - 4); } void RhythmEngine::Render(double delta) @@ -402,7 +437,11 @@ void RhythmEngine::Render(double delta) if (m_state == GameState::NotGame || m_state == GameState::PosGame) return; - m_timingLineManager->Render(delta); + bool MeasureLine = EnvironmentSetup::GetInt("MeasureLine") == 1; + + if (MeasureLine) { + m_timingLineManager->Render(delta); + } for (auto &it : m_tracks) { it->Render(delta); @@ -411,13 +450,13 @@ void RhythmEngine::Render(double delta) void RhythmEngine::Input(double delta) { - if (m_state == GameState::NotGame || m_state == GameState::PosGame) + if (m_state == GameState::NotGame) return; } void RhythmEngine::OnKeyDown(const KeyState &state) { - if (m_state == GameState::NotGame || m_state == GameState::PosGame) + if (m_state == GameState::NotGame) return; if (state.key == Keys::F3) { @@ -441,7 +480,7 @@ void RhythmEngine::OnKeyDown(const KeyState &state) void RhythmEngine::OnKeyUp(const KeyState &state) { - if (m_state == GameState::NotGame || m_state == GameState::PosGame) + if (m_state == GameState::NotGame) return; if (!m_is_autoplay) { @@ -564,14 +603,14 @@ std::vector RhythmEngine::GetSVs() const return m_currentChart->m_svs; } -double RhythmEngine::GetElapsedTime() const -{ // Get game frame +double RhythmEngine::GetGameFrame() const +{ return static_cast(SDL_GetTicks()) / 1000.0; } int RhythmEngine::GetPlayTime() const -{ // Get game time - return m_PlayTime; +{ + return static_cast(m_PlayTime - 5); } int RhythmEngine::GetNoteImageIndex() @@ -682,7 +721,7 @@ ReplayFrameData RhythmEngine::GetAutoplayAtThisFrame(double offset) } } - return std::move(data); + return data; } const float *RhythmEngine::GetLaneSizes() const diff --git a/Game/src/Engine/RhythmEngine.hpp b/Game/src/Engine/RhythmEngine.hpp index 7e00cf97..6a3e6783 100644 --- a/Game/src/Engine/RhythmEngine.hpp +++ b/Game/src/Engine/RhythmEngine.hpp @@ -16,6 +16,7 @@ enum class GameState { NotGame, PreGame, Playing, + Fail, PosGame }; @@ -36,6 +37,7 @@ class RhythmEngine bool Start(); bool Stop(); + bool Fail(); bool Ready(); void Update(double delta); @@ -76,7 +78,7 @@ class RhythmEngine std::vector GetBPMs() const; std::vector GetSVs() const; - double GetElapsedTime() const; + double GetGameFrame() const; int GetPlayTime() const; int GetNoteImageIndex(); @@ -140,8 +142,8 @@ class RhythmEngine std::vector m_autoFrames; /* clock system */ - int m_PlayTime = 0; - std::chrono::system_clock::time_point m_startClock; + double m_PlayTime; + //std::chrono::system_clock::time_point m_startClock; TimingBase *m_timings = nullptr; JudgeBase *m_judge = nullptr; diff --git a/Game/src/Engine/ScoreManager.cpp b/Game/src/Engine/ScoreManager.cpp index b456ddf4..2f7a181b 100644 --- a/Game/src/Engine/ScoreManager.cpp +++ b/Game/src/Engine/ScoreManager.cpp @@ -3,6 +3,11 @@ #include #include "../EnvironmentSetup.hpp" +// Cool, Good, Bad, Miss (Divide by 10 from O2Jam) +const HealthDifficulty easy = {0.3f, 0.2f, -1.0f, -5.0f}; +const HealthDifficulty normal = {0.2f, 0.1f, -0.7f, -4.0f}; +const HealthDifficulty hard = {0.1f, 0.0f, -0.5f, -3.0f}; + ScoreManager::ScoreManager() { m_cool = 0; @@ -32,111 +37,92 @@ ScoreManager::~ScoreManager() { } -void ScoreManager::OnHit(NoteHitInfo info) // Fuck it, just leave it max HP 100 and divided that by 10 +void ScoreManager::OnHit(NoteHitInfo info) { + bool isPlaying = EnvironmentSetup::GetInt("NowPlaying") == 1; + bool isFail = EnvironmentSetup::GetInt("Failed") == 1; + + if (!isPlaying) { + return; + } + + HealthDifficulty health; int difficulty = EnvironmentSetup::GetInt("Difficulty"); switch (difficulty) { - case 0: // Easy - switch (info.Result) { - case NoteResult::COOL: - AddLife(0.3f); - m_jamGauge += 4.0f; - m_score += 200 + (10 * m_jamCombo); - m_cool++; + case 0: + health = easy; break; - case NoteResult::GOOD: - AddLife(0.2f); - m_jamGauge += 2.0f; - m_score += 100 + (5 * m_jamCombo); - m_good++; + case 1: + health = normal; break; - case NoteResult::BAD: // IDK if this correct or not (from old code) - if (m_numOfPills > 0) { - m_numOfPills = std::clamp(m_numOfPills - 1, 0, 5); - m_score += 200 + (10 * m_jamCombo); - m_cool++; - info.Result = NoteResult::COOL; - } - else { - AddLife(-1.0f); - m_jamGauge = 0.0f; - m_coolCombo = 0; - m_score += 4; - m_combo = 0; - m_bad++; - } + case 2: + health = hard; break; - default: - AddLife(-5.0f); - m_combo = 0; - m_jamCombo = 0; - m_jamGauge = 0.0f; - if (m_life > 0) { - m_score -= 10; - } - m_miss++; - break; - } - break; + } - case 1: // Normal - switch (info.Result) { - case NoteResult::COOL: - AddLife(0.2f); - m_jamGauge += 4.0f; - m_score += 200 + (10 * m_jamCombo); - m_cool++; - break; - case NoteResult::GOOD: - AddLife(0.1f); - m_jamGauge += 2.0f; - m_score += 100 + (5 * m_jamCombo); - m_good++; - break; - case NoteResult::BAD: // IDK if this correct or not (from old code) - if (m_numOfPills > 0) { - m_numOfPills = std::clamp(m_numOfPills - 1, 0, 5); - m_score += 200 + (10 * m_jamCombo); - m_cool++; - info.Result = NoteResult::COOL; - } - else { - AddLife(-0.7f); - m_jamGauge = 0.0f; + HandleHit(info, health); + + m_combo = std::clamp(m_combo, 0, INT_MAX); + if (!isFail) { + m_jamCombo = std::clamp(m_jamCombo, 0, INT_MAX); + m_jamGauge = std::clamp(m_jamGauge, 0.0f, 100.0f); + } + m_score = std::clamp(m_score, 0, INT_MAX); + + if (info.Result == NoteResult::COOL) { + m_coolCombo += 1; + if (!isFail) { + if (m_coolCombo > 15) { m_coolCombo = 0; - m_score += 4; - m_combo = 0; - m_bad++; + m_numOfPills = std::clamp(m_numOfPills + 1, 0, 5); } - break; - default: - AddLife(-4.0f); - m_combo = 0; - m_jamCombo = 0; - m_jamGauge = 0.0f; - if (m_life > 0) { - m_score -= 10; + } + } else { + m_coolCombo = 0; + } + + if (info.Result != NoteResult::BAD && info.Result != NoteResult::MISS) { + m_combo += 1; + m_maxCombo = std::max(m_maxCombo, m_combo); + } + + if (!isFail) { + if (m_jamGauge >= kMaxJamGauge) { + m_jamGauge = 0; + m_jamCombo += 1; + m_maxJamCombo = std::max(m_maxJamCombo, m_jamCombo); + + if (m_jamCallback) { + m_jamCallback(m_jamCombo); } - m_miss++; - break; } - break; + } + + if (m_callback) { + if (info.Result == NoteResult::MISS && m_life == 0) { + return; + } + + m_callback(info); + } +} - case 2: // Hard - switch (info.Result) { +void ScoreManager::HandleHit(NoteHitInfo& info, const HealthDifficulty& health) +{ + switch (info.Result) { case NoteResult::COOL: - AddLife(0.1f); + AddLife(health.cool); m_jamGauge += 4.0f; m_score += 200 + (10 * m_jamCombo); m_cool++; break; case NoteResult::GOOD: - AddLife(0.0f); + AddLife(health.good); m_jamGauge += 2.0f; m_score += 100 + (5 * m_jamCombo); m_good++; break; - case NoteResult::BAD: // IDK if this correct or not (from old code) + case NoteResult::BAD: if (m_numOfPills > 0) { m_numOfPills = std::clamp(m_numOfPills - 1, 0, 5); m_score += 200 + (10 * m_jamCombo); @@ -144,7 +130,7 @@ void ScoreManager::OnHit(NoteHitInfo info) // Fuck it, just leave it max HP 100 info.Result = NoteResult::COOL; } else { - AddLife(-0.5f); + AddLife(health.bad); m_jamGauge = 0.0f; m_coolCombo = 0; m_score += 4; @@ -153,7 +139,7 @@ void ScoreManager::OnHit(NoteHitInfo info) // Fuck it, just leave it max HP 100 } break; default: - AddLife(-3.0f); + AddLife(health.miss); m_combo = 0; m_jamCombo = 0; m_jamGauge = 0.0f; @@ -162,65 +148,34 @@ void ScoreManager::OnHit(NoteHitInfo info) // Fuck it, just leave it max HP 100 } m_miss++; break; - } - break; } +} - m_combo = std::clamp(m_combo, 0, INT_MAX); - m_jamCombo = std::clamp(m_jamCombo, 0, INT_MAX); - m_jamGauge = std::clamp(m_jamGauge, 0.0f, 100.0f); - m_score = std::clamp(m_score, 0, INT_MAX); - - if (info.Result == NoteResult::COOL) { - m_coolCombo += 1; +void ScoreManager::OnLongNoteHold(HoldResult result) +{ + bool isPlaying = EnvironmentSetup::GetInt("NowPlaying") == 1; + bool isFail = EnvironmentSetup::GetInt("Failed") == 1; - if (m_coolCombo > 15) { - m_coolCombo = 0; - m_numOfPills = std::clamp(m_numOfPills + 1, 0, 5); - } - } - else { - m_coolCombo = 0; + if (!isPlaying) { + return; } - if (info.Result != NoteResult::BAD && info.Result != NoteResult::MISS) { - m_combo += 1; - m_maxCombo = std::max(m_maxCombo, m_combo); + if (isFail) { + return; } - if (m_jamGauge >= kMaxJamGauge) { - m_jamGauge = 0; - m_jamCombo += 1; - m_maxJamCombo = std::max(m_maxJamCombo, m_jamCombo); - - if (m_jamCallback) { - m_jamCallback(m_jamCombo); + switch (result) { + case HoldResult::HoldBreak: + { + m_lnCombo = 0; + break; } - } - if (m_callback) { - if (info.Result == NoteResult::MISS && m_life == 0) { - return; + case HoldResult::HoldAdd: + { + m_lnCombo += 1; + break; } - - m_callback(info); - } -} - -void ScoreManager::OnLongNoteHold(HoldResult result) -{ - switch (result) { - case HoldResult::HoldBreak: - { - m_lnCombo = 0; - break; - } - - case HoldResult::HoldAdd: - { - m_lnCombo += 1; - break; - } } m_lnMaxCombo = (std::max)(m_lnCombo, m_lnMaxCombo); @@ -267,8 +222,11 @@ std::tuple ScoreManager:: void ScoreManager::AddLife(float sz) { - if (m_life > 0) { - m_life += sz; - m_life = std::clamp(m_life, 0.0f, 100.0f); + bool isFail = EnvironmentSetup::GetInt("Failed") == 1; + if (!isFail) { + if (m_life > 0) { + m_life += sz; + m_life = std::clamp(m_life, 0.0f, 100.0f); + } } -} \ No newline at end of file +} diff --git a/Game/src/Engine/ScoreManager.hpp b/Game/src/Engine/ScoreManager.hpp index d15c37d4..9c29e053 100644 --- a/Game/src/Engine/ScoreManager.hpp +++ b/Game/src/Engine/ScoreManager.hpp @@ -5,6 +5,13 @@ constexpr float kMaxJamGauge = 100; constexpr float kMaxLife = 100; +struct HealthDifficulty { + float cool; + float good; + float bad; + float miss; +}; + struct NoteHitInfo { NoteResult Result; @@ -21,6 +28,7 @@ class ScoreManager ~ScoreManager(); void OnHit(NoteHitInfo info); + void HandleHit(NoteHitInfo& info, const HealthDifficulty& health); void OnLongNoteHold(HoldResult result); void ListenHit(std::function); void ListenJam(std::function); diff --git a/Game/src/Engine/SkinManager.cpp b/Game/src/Engine/SkinManager.cpp index c1d49b60..4eb8750b 100644 --- a/Game/src/Engine/SkinManager.cpp +++ b/Game/src/Engine/SkinManager.cpp @@ -1,4 +1,5 @@ #include "SkinManager.hpp" +#include "../EnvironmentSetup.hpp" SkinManager *SkinManager::m_instance = nullptr; @@ -225,8 +226,20 @@ SkinManager::~SkinManager() void SkinManager::TryLoadGroup(SkinGroup group) { - m_skinConfigs[group] = std::make_unique( - GetPath() / m_expected_directory[group] / m_expected_skin_config[group], m_keyCount); + std::filesystem::path configPath; + + if (group == SkinGroup::Notes && EnvironmentSetup::GetInt("NoteSkin") != 2) { + auto path = std::filesystem::current_path() / "Resources"; + if (EnvironmentSetup::GetInt("NoteSkin") == 1) { + configPath = path / "Notes" / "Circle" / "Notes.ini"; + } else { + configPath = path / "Notes" / "Square" / "Notes.ini"; + } + } else { + configPath = GetPath() / m_expected_directory[group] / m_expected_skin_config[group]; + } + + m_skinConfigs[group] = std::make_unique(configPath, m_keyCount); } void SkinManager::Update(double delta) diff --git a/Game/src/Engine/Timing/TimingBase.cpp b/Game/src/Engine/Timing/TimingBase.cpp index 98a58f72..287b3230 100644 --- a/Game/src/Engine/Timing/TimingBase.cpp +++ b/Game/src/Engine/Timing/TimingBase.cpp @@ -7,7 +7,7 @@ TimingBase::TimingBase(std::vector &_timings, std::vector &timings, double offset) +static TimingInfo& FindTimingAt(std::vector& timings, double offset) { int min = 0, max = (int)timings.size() - 1; int left = min, right = max; @@ -16,13 +16,15 @@ TimingInfo &FindTimingAt(std::vector &timings, double offset) int mid = (left + right) / 2; bool afterMid = mid < 0 || timings[mid].StartTime < offset; - bool beforeMid = mid + 1 >= timings.size() || offset < timings[mid + 1].StartTime; + bool beforeMid = static_cast(mid) + 1 >= timings.size() || offset < timings[static_cast>::size_type>(mid) + 1].StartTime; if (afterMid && beforeMid) { return timings[mid]; - } else if (afterMid) { + } + else if (afterMid) { left = mid + 1; - } else { + } + else { right = mid - 1; } } @@ -37,7 +39,14 @@ double TimingBase::GetBeatAt(double offset) double TimingBase::GetBPMAt(double offset) { - return FindTimingAt(timings, offset).Value; + double BPM = FindTimingAt(timings, offset).Value; + + // Handle BPM overflow at int32_t range + if (BPM >= INT_MAX || BPM <= -INT_MAX) { + BPM = fmod(BPM, INT_MAX); + } + + return BPM; } double TimingBase::GetOffsetAt(double offset) diff --git a/Game/src/Engine/TimingLine.cpp b/Game/src/Engine/TimingLine.cpp index 1a8894ba..866975c2 100644 --- a/Game/src/Engine/TimingLine.cpp +++ b/Game/src/Engine/TimingLine.cpp @@ -1,6 +1,7 @@ #include "TimingLine.hpp" #include "RhythmEngine.hpp" #include "Texture/ResizableImage.h" +#include "../EnvironmentSetup.hpp" namespace { double CalculateLinePosition(double trackOffset, double offset, double noteSpeed, bool upscroll = false) @@ -62,15 +63,26 @@ void TimingLine::Render(double delta) double min = 0, max = hitPos; double pos_y = min + (max - min) * alpha; + int halfNoteSize; + + if (EnvironmentSetup::GetInt("MeasureLineType") == 1) { + halfNoteSize = static_cast(EnvironmentSetup::GetInt("NoteSize") / 2); + } + else { + halfNoteSize = 0; + } + m_line->Size = UDim2::fromOffset(m_imageSize, 1); - m_line->Position = UDim2::fromOffset(m_imagePos, pos_y); //+ start.Lerp(end, alpha); + m_line->Position = UDim2::fromOffset(m_imagePos, pos_y - halfNoteSize); //+ start.Lerp(end, alpha); if (m_line->Position.Y.Offset >= 0 && m_line->Position.Y.Offset < hitPos + 10) { + m_line->TintColor = { 0.7f, 0.7f, 0.7f }; m_line->Draw(&playRect); } } void TimingLine::Release() { + EnvironmentSetup::SetInt("HalfNoteSize", 0); delete m_line; } diff --git a/Game/src/EnvironmentSetup.cpp b/Game/src/EnvironmentSetup.cpp index f4a3733b..7aa320d5 100644 --- a/Game/src/EnvironmentSetup.cpp +++ b/Game/src/EnvironmentSetup.cpp @@ -1,64 +1,83 @@ #include "EnvironmentSetup.hpp" #include +#include namespace { std::unordered_map m_stores; - std::unordered_map m_storesPtr; + std::unordered_map m_storesPtr; std::unordered_map m_paths; std::unordered_map m_ints; + std::mutex m_mutex; } // namespace void EnvironmentSetup::OnExitCheck() { - for (auto &[key, value] : m_stores) { - value = ""; - } - - for (auto &[key, value] : m_paths) { - value = ""; - } - - for (auto &[key, value] : m_ints) { - value = 0; - } + std::lock_guard lock(m_mutex); + m_stores.clear(); + m_paths.clear(); + m_ints.clear(); } void EnvironmentSetup::Set(std::string key, std::string value) { - m_stores[key] = value; + std::lock_guard lock(m_mutex); + m_stores[key] = std::move(value); } std::string EnvironmentSetup::Get(std::string key) { - return m_stores[key]; + std::lock_guard lock(m_mutex); + auto it = m_stores.find(key); + if (it != m_stores.end()) { + return it->second; + } + return ""; } -void EnvironmentSetup::SetObj(std::string key, void *ptr) +void EnvironmentSetup::SetObj(std::string key, void* ptr) { + std::lock_guard lock(m_mutex); m_storesPtr[key] = ptr; } -void *EnvironmentSetup::GetObj(std::string key) +void* EnvironmentSetup::GetObj(std::string key) { - return m_storesPtr[key]; + std::lock_guard lock(m_mutex); + auto it = m_storesPtr.find(key); + if (it != m_storesPtr.end()) { + return it->second; + } + return nullptr; } void EnvironmentSetup::SetPath(std::string key, std::filesystem::path path) { - m_paths[key] = path; + std::lock_guard lock(m_mutex); + m_paths[key] = std::move(path); } std::filesystem::path EnvironmentSetup::GetPath(std::string key) { - return m_paths[key]; + std::lock_guard lock(m_mutex); + auto it = m_paths.find(key); + if (it != m_paths.end()) { + return it->second; + } + return {}; } void EnvironmentSetup::SetInt(std::string key, int value) { + std::lock_guard lock(m_mutex); m_ints[key] = value; } int EnvironmentSetup::GetInt(std::string key) { - return m_ints[key]; + std::lock_guard lock(m_mutex); + auto it = m_ints.find(key); + if (it != m_ints.end()) { + return it->second; + } + return 0; } diff --git a/Game/src/MyGame.cpp b/Game/src/MyGame.cpp index 316a4966..2eb2b500 100644 --- a/Game/src/MyGame.cpp +++ b/Game/src/MyGame.cpp @@ -68,7 +68,7 @@ bool MyGame::Init() /* Overlays */ SceneManager::AddOverlay(GameOverlay::SETTINGS, new SettingsOverlay()); - std::string title = std::string(O2GAME_TITLE) + " " + std::string(O2GAME_VERSION); + std::string title = std::string(O2GAME_TITLE) + "" + ""/*std::string(O2GAME_VERSION)*/; m_window->SetWindowTitle(title); if (EnvironmentSetup::GetPath("FILE").empty()) { diff --git a/Game/src/Resources/DefaultConfiguration.h b/Game/src/Resources/DefaultConfiguration.h index 0fbd3b2c..951ea0f8 100644 --- a/Game/src/Resources/DefaultConfiguration.h +++ b/Game/src/Resources/DefaultConfiguration.h @@ -7,10 +7,15 @@ std::string defaultConfiguration = "[game]\n" "framelimit = 240\n" // Default 144 but i set this for more reasonable "audiooffset = 0\n" "audiovolume = 100\n" - "autosound = 1\n" + "autosound = 0\n" "resolution = 1280x720\n" // Fix for most monitor "renderer = 0\n" - "guideline = 1\n\n" + "guideline = 1\n" + "background = 0\n" + "measureline = 1\n" + "measurelinetype = 0\n" + "newlongnote = 0\n" + "lnbodyontop = 0\n\n" "[keymapping]\n" "lane1 = A\n" @@ -42,4 +47,8 @@ std::string defaultConfiguration = "[game]\n" "folder =\n" "[gameplay]\n" - "notespeed = 225\n"; \ No newline at end of file + "notespeed = 250\n" + "difficulty = \n" + "modifiers =\n" + "arena =\n"; + diff --git a/Game/src/Resources/GameResources.cpp b/Game/src/Resources/GameResources.cpp index 01c043b9..a9b4f0b0 100644 --- a/Game/src/Resources/GameResources.cpp +++ b/Game/src/Resources/GameResources.cpp @@ -23,6 +23,7 @@ #endif #include "../Engine/SkinConfig.hpp" +#include "../EnvironmentSetup.hpp" #pragma warning(disable : 26451) @@ -393,9 +394,20 @@ namespace GameNoteResource { } bool IsVulkan = Renderer::GetInstance()->IsVulkan(); + std::filesystem::path skinNotePath; - auto skinPath = SkinManager::GetInstance()->GetPath(); - auto skinNotePath = skinPath / "Notes"; + if (EnvironmentSetup::GetInt("NoteSkin") == 1) { + auto path = std::filesystem::current_path() / "Resources"; + skinNotePath = path / "Notes" / "Circle"; + } + else if (EnvironmentSetup::GetInt("NoteSkin") == 2) { + auto skinPath = SkinManager::GetInstance()->GetPath(); + skinNotePath = skinPath / "Notes"; + } + else { + auto path = std::filesystem::current_path() / "Resources"; + skinNotePath = path / "Notes" / "Square"; + } auto manager = SkinManager::GetInstance(); diff --git a/Game/src/Scenes/GameplayScene.cpp b/Game/src/Scenes/GameplayScene.cpp index 2664e692..83fe8c11 100644 --- a/Game/src/Scenes/GameplayScene.cpp +++ b/Game/src/Scenes/GameplayScene.cpp @@ -27,6 +27,7 @@ #include "../GameScenes.h" #define AUTOPLAY_TEXT u8"Game currently on autoplay!" +#define GAMEINFO_TEXT u8"O2Clone Build Date: " __DATE__ " " __TIME__ struct MissInfo { @@ -34,7 +35,7 @@ struct MissInfo float beat; float hit_beat; float time; -}; +}; bool CheckSkinComponent(std::filesystem::path x) { @@ -48,6 +49,8 @@ GameplayScene::GameplayScene() : Scene::Scene() m_keyState = {}; m_game = nullptr; m_drawJam = false; + m_counter = 0.0; + lifeFillDuration = 0.0; } void GameplayScene::Update(double delta) @@ -70,23 +73,44 @@ void GameplayScene::Update(double delta) } if (!m_starting) { + EnvironmentSetup::SetInt("FillStart", 1); m_starting = true; m_game->Start(); } if (m_game->GetState() == GameState::PosGame && !m_ended) { + m_counter += delta; + m_drawExitButton = false; + m_doExit = false; + if (m_counter > 5.0) { + m_ended = true; + m_counter = 0.0; // Reset + SceneManager::DisplayFade(100, [] { + SceneManager::ChangeScene(GameScene::RESULT); + }); + } + } + + if (m_game->GetState() == GameState::Fail && !m_ended) { m_ended = true; + m_counter = 0.0; // Reset SceneManager::DisplayFade(100, [] { SceneManager::ChangeScene(GameScene::RESULT); }); } int difficulty = EnvironmentSetup::GetInt("Difficulty"); + float health = m_game->GetScoreManager()->GetLife(); if (difficulty >= 1 && m_starting) { - float health = m_game->GetScoreManager()->GetLife(); - if (health <= 0) { - m_game->Stop(); + m_game->Fail(); + EnvironmentSetup::SetInt("NowPlaying", 0); + EnvironmentSetup::SetInt("Failed", 1); + } + } + else { + if (health <= 0) { + EnvironmentSetup::SetInt("Failed", 1); } } @@ -96,6 +120,12 @@ void GameplayScene::Update(double delta) auto scores = m_game->GetScoreManager()->GetScore(); if (std::get<1>(scores) != 0 || std::get<2>(scores) != 0 || std::get<3>(scores) != 0 || std::get<4>(scores) != 0) { + if (m_game->GetState() == GameState::PosGame) { + EnvironmentSetup::SetInt("Failed", 0); + } + else { + EnvironmentSetup::SetInt("Failed", 1); + } SceneManager::DisplayFade(100, [] { SceneManager::ChangeScene(GameScene::RESULT); }); @@ -121,16 +151,14 @@ void GameplayScene::Render(double delta) int arena = EnvironmentSetup::GetInt("Arena"); - bool useSongBG = EnvironmentSetup::GetInt("Song BG") == 1; - bool blackBG = EnvironmentSetup::GetInt("Black BG") == 1; - - if (useSongBG) { + if (EnvironmentSetup::GetInt("Background") == 1) { auto songBG = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); if (songBG) { + songBG->TintColor = Color3::FromRGB(128, 128, 128); songBG->Draw(); } } - else if (blackBG) { + else if (EnvironmentSetup::GetInt("Background") == 2) { // Do nothing } else { @@ -184,35 +212,62 @@ void GameplayScene::Render(double delta) m_pills[i]->Draw(); } - auto curLifeTex = m_lifeBar->GetTexture(); // Move lifebar to here so it will not overlapping - curLifeTex->CalculateSize(); - - Rect rc = {}; - rc.left = static_cast(curLifeTex->AbsolutePosition.X); - rc.top = static_cast(curLifeTex->AbsolutePosition.Y); - rc.right = static_cast(rc.left + curLifeTex->AbsoluteSize.X); - rc.bottom = static_cast(rc.top + curLifeTex->AbsoluteSize.Y + 10); // Add + value because of the wiggle effect - float alpha = (float)(kMaxLife - m_game->GetScoreManager()->GetLife()) / kMaxLife; - - // Add wiggle effect - float yOffset = 0.0f; - // Wiggle effect after the first second - yOffset = sinf((float)m_game->GetElapsedTime() * 75.0f) * 10.0f; - - int topCur = (int)::round((1.0f - alpha) * rc.top + alpha * rc.bottom); - rc.top = topCur + static_cast(::round(yOffset)); - if (rc.top >= rc.bottom) { - rc.top = rc.bottom - 1; + bool fillstart = EnvironmentSetup::GetInt("FillStart") == 1; + + if (fillstart) { + lifeFillDuration += delta; + + float fillRatio = ((lifeFillDuration - 0.5) / 1.0 < 1.0f) ? static_cast((lifeFillDuration - 0.5) / 1.0) : 1.0f; + float alpha = 1.0f - fillRatio; + + auto curLifeTex = m_lifeBar->GetTexture(); + curLifeTex->CalculateSize(); + + Rect rc = {}; + rc.left = static_cast(curLifeTex->AbsolutePosition.X); + rc.top = static_cast(curLifeTex->AbsolutePosition.Y + curLifeTex->AbsoluteSize.Y * (1.0f - fillRatio)); + rc.right = static_cast(rc.left + curLifeTex->AbsoluteSize.X); + rc.bottom = static_cast(rc.top + curLifeTex->AbsoluteSize.Y); + + m_lifeBar->Draw(delta, &rc); + + if (lifeFillDuration > 1.5) { + EnvironmentSetup::SetInt("FillStart", 0); + } } + else { + lifeFillDuration = 0.0; + + float alpha = (float)(kMaxLife - m_game->GetScoreManager()->GetLife()) / kMaxLife; + + auto curLifeTex = m_lifeBar->GetTexture(); + curLifeTex->CalculateSize(); + + float offset = 10.0f; - m_lifeBar->Draw(delta, &rc); + Rect rc = {}; + rc.left = static_cast(curLifeTex->AbsolutePosition.X); + rc.top = static_cast(curLifeTex->AbsolutePosition.Y); + rc.right = static_cast(rc.left + curLifeTex->AbsoluteSize.X); + rc.bottom = static_cast(rc.top + curLifeTex->AbsoluteSize.Y + offset); + + double wiggle = sinf(static_cast(m_game->GetGameFrame()) * 60.0f) * offset; + + int topCur = (int)::round((1.0f - alpha) * rc.top + alpha * rc.bottom); + rc.top = topCur + static_cast(::round(wiggle)); + if (rc.top >= rc.bottom) { + rc.top = rc.bottom - 1; + } + + m_lifeBar->Draw(delta, &rc); + } if (m_drawJudge && m_judgement[m_judgeIndex] != nullptr) { m_judgement[m_judgeIndex]->Size = UDim2::fromScale(m_judgeSize, m_judgeSize); m_judgement[m_judgeIndex]->AnchorPoint = { 0.5, 0.5 }; m_judgement[m_judgeIndex]->Draw(); - m_judgeSize = std::clamp(m_judgeSize + (delta * 6), 0.6, 1.0); // Nice + m_judgeSize = std::clamp(m_judgeSize + (delta * 6), 0.4, 1.0); // Nice if ((m_judgeTimer += delta) > 0.60) { m_drawJudge = false; } @@ -229,16 +284,36 @@ void GameplayScene::Render(double delta) } } - if (m_drawCombo && std::get<7>(scores) > 0) { // This should be O2Jam Replication + if (m_drawCombo && std::get<7>(scores) > 0) { // O2Jam Replication by Albert Frengki! const double positionStart = 30.0; - const double decrement = 6.0; - double animationSpeed = 60.0; + double step = 6.0; + double animationSpeed = 90.0; + double maxStep = step; + double maxSpeed = animationSpeed; + + if (m_comboTimer > 1.0) { + animationSpeed += 10.0 * delta; + step += 1.0 * delta; + + } + else { + animationSpeed -= 10.0 * delta; + step -= 1.0 * delta; + } - double targetposition = positionStart - decrement * m_comboTimer * animationSpeed; - double currentposition = (targetposition > 0.0) ? targetposition : 0.0; + if (animationSpeed > maxSpeed) { + animationSpeed = maxSpeed; + } + + if (step > maxStep) { + step = maxStep; + } - m_comboLogo->Position2 = UDim2::fromOffset(0, currentposition / 3.0); - m_comboNum->Position2 = UDim2::fromOffset(0, currentposition); + double targetPosition = positionStart - step * m_comboTimer * animationSpeed; + double currentPosition = (targetPosition > 0.0) ? targetPosition : 0.0; + + m_comboLogo->Position2 = UDim2::fromOffset(0, currentPosition / 3.0); + m_comboNum->Position2 = UDim2::fromOffset(0, currentPosition); m_comboLogo->Draw(delta); m_comboNum->DrawNumber(std::get<7>(scores)); @@ -253,15 +328,36 @@ void GameplayScene::Render(double delta) if (m_drawLN && std::get<9>(scores) > 0) { const double positionStart = 5.0; - double animationSpeed = 60.0; + double step = 1.0; + double animationSpeed = 90.0; + double maxStep = step; + double maxSpeed = animationSpeed; - double targetposition = positionStart - m_lnTimer * animationSpeed; - double currentposition = (targetposition > 0.0) ? targetposition : 0.0; + if (m_comboTimer > 1.0) { + animationSpeed += 10.0 * delta; + step += 0.1 * delta; - m_lnLogo->Position2 = UDim2::fromOffset(0, currentposition); + } + else { + animationSpeed -= 10.0 * delta; + step -= 0.1 * delta; + } + + if (animationSpeed > maxSpeed) { + animationSpeed = maxSpeed; + } + + if (step > maxStep) { + step = maxStep; + } + + double targetPosition = positionStart - step * m_lnTimer * animationSpeed; + double currentPosition = (targetPosition > 0.0) ? targetPosition : 0.0; + + m_lnLogo->Position2 = UDim2::fromOffset(0, currentPosition); m_lnLogo->Draw(delta); - m_lnComboNum->Position2 = UDim2::fromOffset(0, currentposition); + m_lnComboNum->Position2 = UDim2::fromOffset(0, currentPosition); m_lnComboNum->DrawNumber(std::get<9>(scores)); m_lnTimer += delta; @@ -272,7 +368,6 @@ void GameplayScene::Render(double delta) } } - float gaugeVal = (float)m_game->GetScoreManager()->GetJamGauge() / kMaxJamGauge; if (gaugeVal > 0) { m_jamGauge->CalculateSize(); @@ -321,20 +416,41 @@ void GameplayScene::Render(double delta) m_waveGage->Draw(&rc); } - int PlayTime = std::clamp(m_game->GetPlayTime(), 0, INT_MAX); - int currentMinutes = PlayTime / 60; - int currentSeconds = PlayTime % 60; + // Fix if playtime sometimes slighly double draw + int PlayTime = 0; + int currentMinutes = 0 / 60; + int currentSeconds = 0 % 60; + + bool isPlaying = EnvironmentSetup::GetInt("NowPlaying") == 1; + if (isPlaying) // get timer if on play state + { + PlayTime = std::clamp(m_game->GetPlayTime(), 0, INT_MAX); + currentMinutes = PlayTime / 60; + currentSeconds = PlayTime % 60; + } + else { // get timer but this while game ended or failed + int lastPlayTime = PlayTime; + currentMinutes = lastPlayTime / 60; + currentSeconds = lastPlayTime % 60; + } - m_minuteNum->SetValue(currentMinutes); m_minuteNum->DrawNumber(currentMinutes); - m_secondNum->SetValue(currentSeconds); m_secondNum->DrawNumber(currentSeconds); for (int i = 0; i < 7; i++) { - m_hitEffect[i]->Draw(delta); + if (EnvironmentSetup::GetInt("NowPlaying") == 1) { + m_hitEffect[i]->Draw(delta); - if (m_drawHold[i]) { - m_holdEffect[i]->Draw(delta); + if (m_drawHold[i]) { + m_holdEffect[i]->Draw(delta); + } + } + else { + m_hitEffect[i]->LastIndex(); + + if (m_drawHold[i]) { + m_holdEffect[i]->LastIndex(); + } } } @@ -344,13 +460,16 @@ void GameplayScene::Render(double delta) m_title->Draw(m_game->GetTitle()); + m_gameInfo->Position = m_gameInfoPos; + m_gameInfo->Draw(GAMEINFO_TEXT); + if (m_autoPlay) { m_autoText->Position = m_autoTextPos; m_autoText->Draw(AUTOPLAY_TEXT); - m_autoTextPos.X.Offset -= delta * 50.0; - if (m_autoTextPos.X.Offset < (-m_autoTextSize + 20)) { - m_autoTextPos = UDim2::fromOffset(GameWindow::GetInstance()->GetBufferWidth(), 50); + m_autoTextPos.X.Offset -= delta * 30.0; + if (m_autoTextPos.X.Offset < (-m_autoTextSize + 30)) { + m_autoTextPos = UDim2::fromOffset(GameWindow::GetInstance()->GetBufferWidth(), 60); } } @@ -456,11 +575,14 @@ bool GameplayScene::Attach() m_title->AnchorPoint = { TitlePos[0].AnchorPointX, TitlePos[0].AnchorPointY }; m_title->Clip = { RectPos[0].X, RectPos[0].Y, RectPos[0].Width, RectPos[0].Height }; - m_autoText = std::make_unique(13); + m_autoText = std::make_unique(12); m_autoTextSize = m_autoText->CalculateSize(AUTOPLAY_TEXT); - m_autoTextPos = UDim2::fromOffset(GameWindow::GetInstance()->GetBufferWidth(), 50); + m_gameInfo = std::make_unique(8); + m_gameInfoSize = m_gameInfo->CalculateSize(GAMEINFO_TEXT); + m_gameInfoPos = UDim2::fromOffset(/*GameWindow::GetInstance()->GetBufferWidth()*/ 0, GameWindow::GetInstance()->GetBufferHeight() - 10); + m_PlayBG = std::make_unique(arenaPath / ("PlayingBG.png")); auto PlayBGPos = manager->Arena_GetPosition("PlayingBG"); // arena_conf.GetPosition("PlayingBG"); m_PlayBG->Position = UDim2::fromOffset(PlayBGPos[0].X, PlayBGPos[0].Y); @@ -933,11 +1055,6 @@ bool GameplayScene::Attach() if (IsHD) { segments = { - //{ 0.00f, 0.00f, { 0, 0, 0, 255 }, { 0, 0, 0, 255 } }, - //{ 0.00f, 0.00f, { 0, 0, 0, 255 }, { 0, 0, 0, 0 } }, - //{ 0.00f, 0.45f, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, - //{ 0.45f, 0.50f, { 0, 0, 0, 0 }, { 0, 0, 0, 255 } }, - //{ 0.50f, 1.00f, { 0, 0, 0, 255 }, { 0, 0, 0, 255 } } { 0.00f, 0.45f, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { 0.45f, 0.50f, { 0, 0, 0, 0 }, { 0, 0, 0, 255 } }, { 0.50f, 1.00f, { 0, 0, 0, 255 }, { 0, 0, 0, 255 } } @@ -969,10 +1086,10 @@ bool GameplayScene::Attach() m_laneHideImage->Size = UDim2::fromOffset(imageWidth, imageHeight); } - bool areVisualModsActive = IsHD || IsFL || IsSD; // Check if any of the VisualMods are active (Hidden or Flashlight) - bool areNoteModsActive = IsMR || IsRD || IsPC; // Check if any of the NoteMods are active (Mirror or Random) + bool VisualModEnabled = IsHD || IsFL || IsSD; // Check if any of the VisualMods are active (Hidden or Flashlight) + bool NoteModEnabled = IsMR || IsRD || IsPC; // Check if any of the NoteMods are active (Mirror or Random) - if (areVisualModsActive) { // Draw VisualMods (Hidden, Flashlight, and Sudden) + if (VisualModEnabled) { // Draw VisualMods (Hidden, Flashlight, and Sudden) if (IsHD) { std::string VisualModImage = "ModHidden.png"; auto VisualModfilename = playingPath / VisualModImage; @@ -1003,7 +1120,7 @@ bool GameplayScene::Attach() } } - if (areNoteModsActive) { // Draw NoteMods (Mirror, Random, and Panic) + if (NoteModEnabled) { // Draw NoteMods (Mirror, Random, and Panic) if (IsMR) { std::string NoteModImage = "ModMirror.png"; auto NoteModfilename = playingPath / NoteModImage; @@ -1098,12 +1215,6 @@ bool GameplayScene::Attach() m_game->GetScoreManager()->ListenLongNote(OnLongComboEvent); - if (arena != -1) { - auto obj = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); - if (obj) { - obj->TintColor = Color3::FromRGB(128, 128, 128); - } - } } catch (SDLException& e) { @@ -1187,12 +1298,10 @@ bool GameplayScene::Detach() m_title.reset(); m_exitButtonFunc.reset(); - int arena = EnvironmentSetup::GetInt("Arena"); - if (arena) { - auto obj = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); - if (obj) { - obj->TintColor = Color3::FromRGB(255, 255, 255); - } + + auto songBG = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); + if (songBG) { + songBG->TintColor = Color3::FromRGB(255, 255, 255); } SceneManager::GetInstance()->SetFrameLimitMode(FrameLimitMode::MENU); diff --git a/Game/src/Scenes/GameplayScene.h b/Game/src/Scenes/GameplayScene.h index 7377f0dc..077de1af 100644 --- a/Game/src/Scenes/GameplayScene.h +++ b/Game/src/Scenes/GameplayScene.h @@ -72,6 +72,7 @@ class GameplayScene : public Scene std::unique_ptr m_title; std::unique_ptr m_autoText; + std::unique_ptr m_gameInfo; std::unique_ptr m_laneHideImage; @@ -95,6 +96,7 @@ class GameplayScene : public Scene double m_judgeTimer; double m_comboTimer; double m_lnTimer; + double m_counter; /* other stuff */ double m_judgeSize; @@ -107,8 +109,14 @@ class GameplayScene : public Scene bool m_drawExitButton; bool m_doExit; + double lifeFillDuration; + /* auto text size */ bool m_autoPlay; int m_autoTextSize; UDim2 m_autoTextPos; + + /* game info */ + int m_gameInfoSize; + UDim2 m_gameInfoPos; }; \ No newline at end of file diff --git a/Game/src/Scenes/LoadingScene.cpp b/Game/src/Scenes/LoadingScene.cpp index c9e3d461..153e0405 100644 --- a/Game/src/Scenes/LoadingScene.cpp +++ b/Game/src/Scenes/LoadingScene.cpp @@ -18,7 +18,6 @@ #include "../GameScenes.h" #include "../Resources/GameDatabase.h" -// I don't know if this new changes causing memleak or not because i can't tell due my hardware limit LoadingScene::LoadingScene() { m_background = nullptr; @@ -39,7 +38,7 @@ void LoadingScene::Update(double delta) int diffIndex = EnvironmentSetup::GetInt("Difficulty"); bool IsO2Jam = false; - Chart* chart = (Chart*)EnvironmentSetup::GetObj("SONG"); + Chart *chart = (Chart *)EnvironmentSetup::GetObj("SONG"); if (chart == nullptr || chart->GetO2JamId() != songId) { if (!fucked) { std::filesystem::path file; @@ -47,8 +46,7 @@ void LoadingScene::Update(double delta) if (songId != -1) { file = GameDatabase::GetInstance()->GetPath(); file /= "o2ma" + std::to_string(songId) + ".ojn"; - } - else { + } else { file = EnvironmentSetup::GetPath("FILE"); IsFile = true; @@ -57,12 +55,17 @@ void LoadingScene::Update(double delta) EnvironmentSetup::SetInt("Autoplay", autoplay); EnvironmentSetup::Set("SongRate", rate); - EnvironmentSetup::SetInt("Song BG", 1); - EnvironmentSetup::SetInt("Difficulty", 2); // Hard difficulty it's fun (fucked) + + if (EnvironmentSetup::GetInt("FileOpen") == 1) { + LoadModifiers(); + } + else { + EnvironmentSetup::SetInt("Song BG", 1); + } } - const char* bmsfile[] = { ".bms", ".bme", ".bml", ".bmsc" }; - const char* ojnfile = ".ojn"; + const char *bmsfile[] = { ".bms", ".bme", ".bml", ".bmsc" }; + const char *ojnfile = ".ojn"; if (file.extension() == bmsfile[0] || file.extension() == bmsfile[1] || file.extension() == bmsfile[2] || file.extension() == bmsfile[3]) { BMS::BMSFile beatmap; @@ -75,8 +78,8 @@ void LoadingScene::Update(double delta) } chart = new Chart(beatmap); - } - else if (file.extension() == ojnfile) { + EnvironmentSetup::SetInt("SongType", 0); + } else if (file.extension() == ojnfile) { O2::OJN o2jamFile; o2jamFile.Load(file); @@ -89,10 +92,10 @@ void LoadingScene::Update(double delta) } chart = new Chart(o2jamFile, diffIndex); - + EnvironmentSetup::SetInt("SongType", 1); IsO2Jam = true; - } - else { + + } else { Osu::Beatmap beatmap(file); if (!beatmap.IsValid()) { @@ -102,12 +105,12 @@ void LoadingScene::Update(double delta) } chart = new Chart(beatmap); + EnvironmentSetup::SetInt("SongType", 2); } EnvironmentSetup::SetObj("SONG", chart); } - } - else { + } else { IsO2Jam = chart->GetO2JamId() == songId; // TODO: refactor this } @@ -127,14 +130,14 @@ void LoadingScene::Update(double delta) if (!fucked) { try { if (m_background == nullptr) { - GameWindow* window = GameWindow::GetInstance(); + GameWindow *window = GameWindow::GetInstance(); if (chart->m_backgroundFile.size() > 0 && std::filesystem::exists(dirPath)) { m_background = new Texture2D(dirPath.string()); m_background->Size = UDim2::fromOffset(window->GetBufferWidth(), window->GetBufferHeight()); } if (chart->m_backgroundBuffer.size() > 0 && m_background == nullptr) { - m_background = new Texture2D((uint8_t*)chart->m_backgroundBuffer.data(), chart->m_backgroundBuffer.size()); + m_background = new Texture2D((uint8_t *)chart->m_backgroundBuffer.data(), chart->m_backgroundBuffer.size()); m_background->Size = UDim2::fromOffset(window->GetBufferWidth(), window->GetBufferHeight()); } @@ -149,28 +152,34 @@ void LoadingScene::Update(double delta) } } } - } - catch (SDLException& e) { + } catch (SDLException &e) { MsgBox::Show("FailChart", "Error", "Failed to create texture: " + std::string(e.what())); fucked = true; } } - if (m_counter > 2.5 && chart != nullptr) { + if (m_counter > 3.0 && chart != nullptr) { if (IsFile) { EnvironmentSetup::SetObj("SongBackground", m_background); } - SceneManager::ChangeScene(GameScene::GAMEPLAY); - } - else { + bool is7K = EnvironmentSetup::GetInt("KeyCount") == 7; + if (!is7K) + { + MsgBox::Show("Only7K", "Error", "Only 7K Mode Allowed!", MsgBoxType::OK); + SceneManager::ChangeScene(GameScene::SONGSELECT); + } + else { + SceneManager::ChangeScene(GameScene::GAMEPLAY); + EnvironmentSetup::SetInt("FillStart", 1); + } + } else { if (fucked) { std::string songId = EnvironmentSetup::Get("Key"); if (songId.size() > 0) { if (m_counter > 1) { - SceneManager::ChangeScene(GameScene::MAINMENU); + SceneManager::ChangeScene(GameScene::SONGSELECT); } - } - else { + } else { if (MsgBox::GetResult("FailChart") == 4) { SceneManager::GetInstance()->StopGame(); } @@ -179,6 +188,33 @@ void LoadingScene::Update(double delta) } } +void LoadingScene::LoadModifiers() // For File Opened +{ + std::string difficultyValue = Configuration::Load("Gameplay", "Difficulty"); + int setDifficulty = std::stoi(difficultyValue); + switch (setDifficulty) { + case 0: // EZ + EnvironmentSetup::SetInt("Difficulty", 0); + break; + case 1: // NM + EnvironmentSetup::SetInt("Difficulty", 1); + break; + case 2: // HD + EnvironmentSetup::SetInt("Difficulty", 2); + break; + } + + std::string selectedMods = Configuration::Load("Gameplay", "Modifiers"); + std::istringstream iss(selectedMods); + std::string mod; + while (std::getline(iss, mod, ',')) { + EnvironmentSetup::SetInt(mod, 1); + } + + std::string arenaValue = Configuration::Load("Gameplay", "Arena"); + EnvironmentSetup::SetInt("Arena", std::stoi(arenaValue)); +} + void LoadingScene::Render(double delta) { if (m_background && is_ready) { @@ -190,12 +226,14 @@ bool LoadingScene::Attach() { SceneManager::DisplayFade(0, [] {}); + EnvironmentSetup::SetInt("FillStart", 0); + fucked = false; is_shown = false; is_ready = true; m_counter = 0; - m_background = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); + m_background = (Texture2D *)EnvironmentSetup::GetObj("SongBackground"); dont_dispose = m_background != nullptr; return true; } @@ -207,5 +245,7 @@ bool LoadingScene::Detach() m_background = nullptr; } + EnvironmentSetup::SetInt("FillStart", 1); + return true; -} \ No newline at end of file +} diff --git a/Game/src/Scenes/LoadingScene.h b/Game/src/Scenes/LoadingScene.h index 796cc64a..4fd27efd 100644 --- a/Game/src/Scenes/LoadingScene.h +++ b/Game/src/Scenes/LoadingScene.h @@ -10,6 +10,7 @@ class LoadingScene : public Scene ~LoadingScene(); void Update(double delta) override; + void LoadModifiers(); void Render(double delta) override; bool Attach() override; @@ -26,5 +27,5 @@ class LoadingScene : public Scene std::u8string m_title; double m_counter; - Texture2D* m_background; + Texture2D *m_background; }; \ No newline at end of file diff --git a/Game/src/Scenes/MainMenu.cpp b/Game/src/Scenes/MainMenu.cpp index 819ac3f4..823f8cdd 100644 --- a/Game/src/Scenes/MainMenu.cpp +++ b/Game/src/Scenes/MainMenu.cpp @@ -44,7 +44,7 @@ void MainMenu::Render(double delta) std::string title = std::string(O2GAME_TITLE) + " " + std::string(O2GAME_VERSION); ImGui::Text("%s", title.c_str()); - std::string text = "No Account!"; + std::string text = "Main Menu"; auto textWidth = ImGui::CalcTextSize(text.c_str()).x; ImGui::SameLine(MathUtil::ScaleVec2(ImVec2(windowNextSz.x, 0)).x - textWidth - 15); @@ -54,7 +54,7 @@ void MainMenu::Render(double delta) } { - ImFont *font = FontResources::GetButtonFont(); + ImFont* font = FontResources::GetButtonFont(); ImGui::PushFont(font); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); auto ButtonColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); @@ -70,22 +70,30 @@ void MainMenu::Render(double delta) ImGui::NewLine(); } + ImGui::NewLine(); ImGui::Spacing(); - if (ImGui::Button("Single player", ButtonSize)) { - nextScene = 0; - } - - ImGui::Spacing(); - if (ImGui::Button("Multi player", ButtonSize)) { - MsgBox::Show("Multiplayer", "Error", "Multiplayer is not implemented yet!"); - } + //if (ImGui::Button("Single player", ButtonSize)) { + // nextScene = 0; + //} ImGui::NewLine(); ImGui::Spacing(); - if (ImGui::Button("Editor", ButtonSize)) { - nextScene = 2; + + if (ImGui::Button("Play", ButtonSize)) { + nextScene = 0; } + // ImGui::Spacing(); + // if (ImGui::Button("Multi player", ButtonSize)) { + // MsgBox::Show("Multiplayer", "Error", "Multiplayer is not implemented yet!"); + //} + + //ImGui::NewLine(); + //ImGui::Spacing(); + //if (ImGui::Button("Editor", ButtonSize)) { + // nextScene = 2; + //} + ImGui::NewLine(); ImGui::Spacing(); if (ImGui::Button("Options", ButtonSize)) { @@ -107,41 +115,41 @@ void MainMenu::Render(double delta) if (nextScene != -1) { switch (nextScene) { - case 0: - { - SceneManager::DisplayFade(100, [this]() { - SceneManager::ChangeScene(GameScene::SONGSELECT); + case 0: + { + SceneManager::DisplayFade(100, [this]() { + SceneManager::ChangeScene(GameScene::SONGSELECT); }); - break; - } + break; + } - case 1: - { - break; - } + case 1: + { + break; + } - case 2: - { - SceneManager::DisplayFade(100, [this]() { - SceneManager::ChangeScene(GameScene::EDITOR); + case 2: + { + SceneManager::DisplayFade(100, [this]() { + SceneManager::ChangeScene(GameScene::EDITOR); }); - break; - } + break; + } - case 3: - { - SceneManager::OverlayShow(GameOverlay::SETTINGS); - break; - } + case 3: + { + SceneManager::OverlayShow(GameOverlay::SETTINGS); + break; + } - case 4: - { - SDL_Event e = {}; - e.type = SDL_QUIT; + case 4: + { + SDL_Event e = {}; + e.type = SDL_QUIT; - SDL_PushEvent(&e); - break; - } + SDL_PushEvent(&e); + break; + } } } } @@ -170,7 +178,7 @@ bool MainMenu::Attach() } if (bgm && !bgm->IsPlaying()) { - bgm->SetVolume(50); + bgm->SetVolume(100); bgm->Play(0, true); } } diff --git a/Game/src/Scenes/Overlays/Settings.cpp b/Game/src/Scenes/Overlays/Settings.cpp index 81d74ad6..d4a07a7e 100644 --- a/Game/src/Scenes/Overlays/Settings.cpp +++ b/Game/src/Scenes/Overlays/Settings.cpp @@ -1,4 +1,4 @@ -#include "Settings.h" +#include "Settings.h" #include #include #include @@ -35,19 +35,54 @@ static std::map Graphics = { { 1, "Vulkan" }, #if _WIN32 { 2, "DirectX-9" }, - { 3, "DirectX-11" }, - { 4, "DirectX-12" }, + { 3, "DirectX-12" }, #endif #if __APPLE__ - { 5, "Metal" }, + { 4, "Metal" }, #endif }; -static std::array LongNote = { "None", "Short", "Normal", "Long" }; -static std::array m_fps = { "30", "60", "75", "120", "144", "165", "180", "240", "360", "480", "600", "800", "1000", "Unlimited" }; +//#if _WIN32 +//{ 2, "DirectX-9" }, +//{ 3, "DirectX-11" }, +//{ 4, "DirectX-12" }, +//#endif +//#if __APPLE__ +//{ 5, "Metal" }, +//#endif +//}; +static std::array LongNote = { "None", "Short", "Normal", "Long" }; +//static std::array m_fps = { "30", "60", "75", "120", "144", "165", "180", "240", "360", "480", "600", "800", "1000", "Unlimited" }; +static std::array SelectedBackground = { "Arena", "Song", "Disable" }; +static std::array NoteSkin = { "Square", "Circle", "Custom" }; static std::vector m_resolutions = {}; +int currentFPSIndex = 0; +int customFPS = 0; + +namespace { + int GetScreenRefreshRate() { + SDL_DisplayMode displayMode; + if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) { + return 60; // Default to 60 if SDL can't catch refresh rate (pc issues) + } + return displayMode.refresh_rate; + } + + std::vector GetFpsOptions() { + int refreshRate = GetScreenRefreshRate(); + std::vector multipliers = { 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20 }; + std::vector fpsOptions; + + for (int multiplier : multipliers) { + fpsOptions.push_back(std::to_string(refreshRate * multiplier)); + } + fpsOptions.push_back("Unlimited"); + return fpsOptions; + } +} + void SettingsOverlay::Render(double delta) { auto &io = ImGui::GetIO(); @@ -117,7 +152,7 @@ void SettingsOverlay::Render(double delta) if (ImGui::BeginChild("###ChildSettingWnd", MathUtil::ScaleVec2(ImVec2(400, 250)))) { if (ImGui::BeginTabBar("OptionTabBar")) { if (ImGui::BeginTabItem("Inputs")) { - ImGui::Text("7 Keys Configuration"); + ImGui::Text("Keys Configuration"); for (int i = 0; i < 7; i++) { if (i != 0) { ImGui::SameLine(); @@ -133,7 +168,7 @@ void SettingsOverlay::Render(double delta) ImGui::NewLine(); - ImGui::Text("6 Keys Configuration"); + /*ImGui::Text("6 Keys Configuration"); for (int i = 0; i < 6; i++) { if (i != 0) { ImGui::SameLine(); @@ -177,7 +212,7 @@ void SettingsOverlay::Render(double delta) EnvironmentSetup::Set("Scene_KbLaneCount", "4_"); EnvironmentSetup::Set("Scene_KbKey", std::to_string(i + 1)); } - } + }*/ ImGui::EndTabItem(); } @@ -187,7 +222,8 @@ void SettingsOverlay::Render(double delta) int nextGraphicsIndex = -1; try { GraphicsIndex = std::stoi(Configuration::Load("Game", "Renderer").c_str()); - } catch (const std::invalid_argument &) { + } + catch (const std::invalid_argument&) { } ImGui::Text("Graphics"); @@ -243,25 +279,34 @@ void SettingsOverlay::Render(double delta) ImGui::NewLine(); + std::vector fpsOptions = GetFpsOptions(); ImGui::Text("FPS"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: setting unlimited FPS can cause PC to unstable!"); - if (ImGui::BeginCombo("###ComboBox2", m_fps[currentFPSIndex].c_str())) { - for (int i = 0; i < m_fps.size(); i++) { + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: setting unlimited FPS can cause PC to become unstable!"); + if (ImGui::BeginCombo("###ComboBox2", fpsOptions[currentFPSIndex].c_str())) { + for (int i = 0; i < fpsOptions.size(); i++) { bool isSelected = (currentFPSIndex == i); - if (ImGui::Selectable(m_fps[i].c_str(), &isSelected)) { + if (ImGui::Selectable(fpsOptions[i].c_str(), &isSelected)) { currentFPSIndex = i; + if (fpsOptions[i] == "Unlimited") { + customFPS = 9999; + Configuration::Set("Game", "FrameLimit", std::to_string(customFPS)); + } + else { + customFPS = 0; + Configuration::Set("Game", "FrameLimit", fpsOptions[i]); + } } if (isSelected) { ImGui::SetItemDefaultFocus(); } } - ImGui::EndCombo(); } ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Audio")) { @@ -272,7 +317,7 @@ void SettingsOverlay::Render(double delta) ImGui::Text("Audio Offset"); ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Warning: this will make keysounded sample as auto sample!"); - ImGui::SliderInt("###Slider1", ¤tOffset, -500, 500); + ImGui::SliderInt("###Slider1", ¤tOffset, -1000, 1000); ImGui::NewLine(); ImGui::Checkbox("Convert Sample to Auto Sample###Checkbox1", &convertAutoSound); @@ -285,10 +330,10 @@ void SettingsOverlay::Render(double delta) ImGui::Text("Guide Line Length"); for (int i = (int)LongNote.size() - 1; i >= 0; i--) { - bool is_combo_selected = currentGuideLineIndex == i; + bool selected = currentGuideLineIndex == i; ImGui::PushItemWidth(50); - if (ImGui::Checkbox(("###ComboCheck" + std::to_string(i)).c_str(), &is_combo_selected)) { + if (ImGui::Checkbox(("###ComboCheck" + std::to_string(i)).c_str(), &selected)) { currentGuideLineIndex = i; } @@ -307,14 +352,47 @@ void SettingsOverlay::Render(double delta) ImGui::NewLine(); ImGui::Text("Gameplay-Related Configuration"); - ImGui::Checkbox("Long Note Lighting###SetCheckbox1", &LongNoteLighting); + + ImGui::Checkbox("New Measure Line###SetCheckbox1", &MeasureLineType); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("When Long note on hold, change the lighting brightness to 100%% else 90%% brightness"); + ImGui::SetTooltip("Measure line position in the middle of the note; otherwise, it will be at the bottom of the note"); + } + if (MeasureLineType) { + EnvironmentSetup::SetInt("MeasureLineType", 1); + } + else { + EnvironmentSetup::SetInt("MeasureLineType", 0); + } + + ImGui::SameLine(); + + ImGui::Checkbox("New Long Note###SetCheckbox3", &NewLongNote); + if (NewLongNote) { // Leave everything untouched since no reason to make whole changes + EnvironmentSetup::SetInt("NewLN", 1); + } + else { + EnvironmentSetup::SetInt("NewLN", 0); + } + + ImGui::Checkbox("Disable Measure Line###SetCheckbox2", &MeasureLine); + if (MeasureLine) { + EnvironmentSetup::SetInt("MeasureLine", 0); + } + else { + EnvironmentSetup::SetInt("MeasureLine", 1); } - ImGui::Checkbox("Long Note Head Position at HitPos###SetCheckbox2", &LongNoteOnHitPos); + ImGui::SameLine(); + + ImGui::Checkbox("Long Note Body On Top###SetCheckbox3", &LNBodyOnTop); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("When Long note on hold, make the head position at hit position else keep going to bottom"); + ImGui::SetTooltip("If your note skin has issues, enable this option!"); + } + if (LNBodyOnTop) { + EnvironmentSetup::SetInt("LNBodyOnTop", 1); + } + else { + EnvironmentSetup::SetInt("LNBodyOnTop", 0); } ImGui::EndTabItem(); @@ -322,7 +400,7 @@ void SettingsOverlay::Render(double delta) if (ImGui::BeginTabItem("Skins")) { ImGui::Text("Current selected skin: "); - if (ImGui::BeginCombo("#SkinsComboBox", currentSkin.c_str())) { + if (ImGui::BeginCombo("##SkinsComboBox", currentSkin.c_str())) { for (int i = 0; i < skins.size(); i++) { bool isSelected = (currentSkin == skins[i]); @@ -338,6 +416,83 @@ void SettingsOverlay::Render(double delta) ImGui::EndCombo(); } + ImGui::NewLine(); + + ImGui::Text("Note Skin"); + for (int i = 0; i < NoteSkin.size(); ++i) { + bool selected = (NoteIndex == i); + + std::string tooltipText; + if (i == 0) { + //tooltipText = ""; + } + else if (i == 1) { + //tooltipText = ""; + } + else if (i == 2) { + tooltipText = "Using custom note image from skin folder"; + } + + if (ImGui::Checkbox(NoteSkin[i].c_str(), &selected)) { + NoteIndex = i; + } + + if (!tooltipText.empty() && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltipText.c_str()); + } + + ImGui::SameLine(); + } + + if (NoteIndex == 0) { + EnvironmentSetup::SetInt("NoteSkin", 0); + } + else if (NoteIndex == 1) { + EnvironmentSetup::SetInt("NoteSkin", 1); + } + else if (NoteIndex == 2) { + EnvironmentSetup::SetInt("NoteSkin", 2); + } + + ImGui::NewLine(); + ImGui::NewLine(); + + ImGui::Text("Background"); + for (int i = 0; i < SelectedBackground.size(); ++i) { + bool selected = (BackgroundIndex == i); + + std::string tooltipText; + if (i == 0) { + tooltipText = "Using arena image background inside Skin folder"; + } + else if (i == 1) { + tooltipText = "Using song image background inside O2Jam file / BMS folder / osu!mania beatmap folder"; + } + else if (i == 2) { + tooltipText = "Not using any background"; + } + + if (ImGui::Checkbox(SelectedBackground[i].c_str(), &selected)) { + BackgroundIndex = i; + } + + if (!tooltipText.empty() && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltipText.c_str()); + } + + ImGui::SameLine(); + } + + if (BackgroundIndex == 1) { + EnvironmentSetup::SetInt("Background", 1); + } + else if (BackgroundIndex == 2) { + EnvironmentSetup::SetInt("Background", 2); + } + else { + EnvironmentSetup::SetInt("Background", 0); + } + ImGui::EndTabItem(); } @@ -434,16 +589,23 @@ void SettingsOverlay::LoadConfiguration() } try { - auto value = Configuration::Load("Game", "FrameLimit"); - auto it = std::find(m_fps.begin(), m_fps.end(), value); - if (it == m_fps.end()) { - currentFPSIndex = 4; - } else { - currentFPSIndex = (int)(it - m_fps.begin()); + std::string frameLimit = Configuration::Load("Game", "FrameLimit"); + auto fpsOptions = GetFpsOptions(); + auto it = std::find(fpsOptions.begin(), fpsOptions.end(), frameLimit); + if (it != fpsOptions.end()) { + currentFPSIndex = static_cast(std::distance(fpsOptions.begin(), it)); + customFPS = 0; + } + else { + currentFPSIndex = static_cast(fpsOptions.size()) - 1; + customFPS = 9999; } - } catch (const std::invalid_argument &) { - currentFPSIndex = 4; } + catch (const std::invalid_argument&) { + customFPS = 9999; + } + + SceneManager::GetInstance()->SetFrameLimit(std::atof(Configuration::Load("Game", "FrameLimit").c_str())); try { currentGuideLineIndex = std::stoi(Configuration::Load("Game", "GuideLine").c_str()); @@ -451,12 +613,99 @@ void SettingsOverlay::LoadConfiguration() currentGuideLineIndex = 2; } - auto fps = m_fps[currentFPSIndex]; - if (fps == *(m_fps.end() - 1)) { - fps = "-1"; // Unlimited + try { + BackgroundIndex = std::stoi(Configuration::Load("Game", "Background").c_str()); + } catch (const std::invalid_argument&) { + BackgroundIndex = 0; + } + + if (BackgroundIndex == 1) { + EnvironmentSetup::SetInt("Background", 1); + } + else if (BackgroundIndex == 2) { + EnvironmentSetup::SetInt("Background", 2); + } + else { + EnvironmentSetup::SetInt("Background", 0); + } + + try { + NoteIndex = std::stoi(Configuration::Load("Game", "NoteSkin").c_str()); + } + catch (const std::invalid_argument&) { + NoteIndex = 2; + } + + if (NoteIndex == 0) { + EnvironmentSetup::SetInt("NoteSkin", 0); + } + else if (NoteIndex == 1) { + EnvironmentSetup::SetInt("NoteSkin", 1); + } + else if (NoteIndex == 2) { + EnvironmentSetup::SetInt("NoteSkin", 2); + } + + try { + int NewLNValue = std::stoi(Configuration::Load("Game", "NewLongNote")); + NewLongNote = (NewLNValue == 1); + } + catch (const std::invalid_argument&) { + NewLongNote = false; + } + + if (NewLongNote) { + EnvironmentSetup::SetInt("NewLN", 1); + } + else { + EnvironmentSetup::SetInt("NewLN", 0); + } + + try { + int MeasureLineTypeValue = std::stoi(Configuration::Load("Game", "MeasureLineType")); + MeasureLineType = (MeasureLineTypeValue == 1); + } + catch (const std::invalid_argument&) { + MeasureLineType = false; + } + + if (MeasureLineType) { + EnvironmentSetup::SetInt("MeasureLineType", 1); + } + else { + EnvironmentSetup::SetInt("MeasureLineType", 0); + } + + try { + int measureLineValue = std::stoi(Configuration::Load("Game", "MeasureLine")); + MeasureLine = (measureLineValue == 1); + } + catch (const std::invalid_argument&) { + MeasureLine = true; + } + + if (MeasureLine) { + EnvironmentSetup::SetInt("MeasureLine", 0); + } + else { + EnvironmentSetup::SetInt("MeasureLine", 1); + } + + try { + int LNBodyOnTopValue = std::stoi(Configuration::Load("Game", "LNBodyOnTop")); + LNBodyOnTop = (LNBodyOnTopValue == 1); + } + catch (const std::invalid_argument&) { + LNBodyOnTop = true; + } + + if (LNBodyOnTop) { + EnvironmentSetup::SetInt("LNBodyOnTop", 1); + } + else { + EnvironmentSetup::SetInt("LNBodyOnTop", 0); } - SceneManager::GetInstance()->SetFrameLimit(std::atof(fps.c_str())); currentSkin = Configuration::Load("Game", "Skin"); PreloadSkin(); } @@ -466,15 +715,22 @@ void SettingsOverlay::SaveConfiguration() Configuration::Set("Game", "AudioOffset", std::to_string(currentOffset)); Configuration::Set("Game", "AudioVolume", std::to_string(currentVolume)); Configuration::Set("Game", "AutoSound", std::to_string(convertAutoSound ? 1 : 0)); - Configuration::Set("Game", "FrameLimit", m_fps[currentFPSIndex]); Configuration::Set("Game", "GuideLine", std::to_string(currentGuideLineIndex)); - - auto frame = m_fps[currentFPSIndex]; - if (frame == m_fps[13]) { - frame = "9999"; + Configuration::Set("Game", "NoteSkin", std::to_string(NoteIndex)); + Configuration::Set("Game", "Background", std::to_string(BackgroundIndex)); + Configuration::Set("Game", "MeasureLine", std::to_string(MeasureLine ? 1 : 0)); + Configuration::Set("Game", "MeasureLineType", std::to_string(MeasureLineType ? 1 : 0)); + Configuration::Set("Game", "LNBodyOnTop", std::to_string(LNBodyOnTop ? 1 : 0)); + Configuration::Set("Game", "NewLongNote", std::to_string(NewLongNote ? 1 : 0)); + + if (currentFPSIndex == GetFpsOptions().size() - 1 && customFPS > 0) { + Configuration::Set("Game", "FrameLimit", std::to_string(customFPS)); + } + else { + Configuration::Set("Game", "FrameLimit", GetFpsOptions()[currentFPSIndex]); } - SceneManager::GetInstance()->SetFrameLimit(std::atof(frame.c_str())); + SceneManager::GetInstance()->SetFrameLimit(std::atof(Configuration::Load("Game", "FrameLimit").c_str())); if (currentSkin.empty()) { throw std::runtime_error("SKIN_NAME Undefined!"); diff --git a/Game/src/Scenes/Overlays/Settings.h b/Game/src/Scenes/Overlays/Settings.h index 16798421..70c1a4a1 100644 --- a/Game/src/Scenes/Overlays/Settings.h +++ b/Game/src/Scenes/Overlays/Settings.h @@ -24,8 +24,12 @@ class SettingsOverlay : public Overlay int currentOffset = 0; int currentResolutionIndex = 0; int currentGuideLineIndex = 0; - bool LongNoteLighting = false; - bool LongNoteOnHitPos = false; + bool MeasureLine = false; + bool LNBodyOnTop = false; + bool MeasureLineType = false; + int BackgroundIndex = 0; + int NoteIndex = 0; + bool NewLongNote = false; bool convertAutoSound = false; std::string currentSkin = ""; diff --git a/Game/src/Scenes/ResultScene.cpp b/Game/src/Scenes/ResultScene.cpp index f9895233..00cbcac9 100644 --- a/Game/src/Scenes/ResultScene.cpp +++ b/Game/src/Scenes/ResultScene.cpp @@ -132,13 +132,9 @@ void ResultScene::Render(double delta) } if (m_backButton) { - if (EnvironmentSetup::GetPath("FILE").empty()) { - SceneManager::DisplayFade(100, [] { - SceneManager::ChangeScene(GameScene::SONGSELECT); - }); - } else { - SceneManager::GetInstance()->StopGame(); - } + SceneManager::DisplayFade(100, [] { + SceneManager::ChangeScene(GameScene::SONGSELECT); + }); } if (m_retryButton) { @@ -154,25 +150,35 @@ bool ResultScene::Attach() SceneManager::DisplayFade(0, [] {}); m_backButton = m_retryButton = false; - Audio *audio = AudioManager::GetInstance()->Get("FINISH"); - if (!audio) { - auto BGMPath = SkinManager::GetInstance()->GetPath() / "Audio"; - BGMPath /= "FINISH.ogg"; + Audio* previousAudio = AudioManager::GetInstance()->Get("FINISH"); + if (previousAudio) { + previousAudio->Stop(); + AudioManager::GetInstance()->Remove("FINISH"); + } - if (std::filesystem::exists(BGMPath)) { - AudioManager::GetInstance()->Create("FINISH", BGMPath, &audio); - } + Audio* audio = nullptr; + auto BGMPath = SkinManager::GetInstance()->GetPath() / "Audio"; + + bool failed = EnvironmentSetup::GetInt("Failed") == 1; + if (failed && std::filesystem::exists(BGMPath / "FAILED.ogg")) { + BGMPath /= "FAILED.ogg"; + } + else { + BGMPath /= "FINISH.ogg"; } - if (audio) { - audio->SetVolume(100); - audio->Play(); + if (std::filesystem::exists(BGMPath)) { + AudioManager::GetInstance()->Create("FINISH", BGMPath, &audio); + if (audio) { + audio->SetVolume(100); + audio->Play(); + } } - Chart *chart = (Chart *)EnvironmentSetup::GetObj("SONG"); + Chart* chart = (Chart*)EnvironmentSetup::GetObj("SONG"); EnvironmentSetup::SetObj("SONG", nullptr); - m_background = (Texture2D *)EnvironmentSetup::GetObj("SongBackground"); + m_background = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); delete chart; @@ -181,15 +187,19 @@ bool ResultScene::Attach() bool ResultScene::Detach() { - Audio *audio = AudioManager::GetInstance()->Get("FINISH"); + Audio* audio = AudioManager::GetInstance()->Get("FINISH"); if (audio) { audio->Stop(); + AudioManager::GetInstance()->Remove("FINISH"); } if (!m_retryButton) { EnvironmentSetup::SetObj("SongBackground", nullptr); } + EnvironmentSetup::SetInt("Failed", 0); + EnvironmentSetup::SetInt("FillStart", 0); + m_background = nullptr; return true; -} +} \ No newline at end of file diff --git a/Game/src/Scenes/SongSelectScene.cpp b/Game/src/Scenes/SongSelectScene.cpp index be3703a1..391a0185 100644 --- a/Game/src/Scenes/SongSelectScene.cpp +++ b/Game/src/Scenes/SongSelectScene.cpp @@ -28,7 +28,26 @@ #include "../Data/Chart.hpp" #include "../Data/Util/Util.hpp" -static std::array Mods = { "Mirror", "Random", "Panic (FixMe)", "Rearrange", "Autoplay", "Hidden", "Flashlight", "Sudden", "Song BG", "Black BG" }; +static std::wstring OpenFilePrompt() { + OPENFILENAMEW ofn; + wchar_t szFile[MAX_PATH] = { 0 }; + + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = NULL; + ofn.lpstrFilter = L"All Supported Files (*.ojn;*.osu;*.bms;*.bme;*.bml;*)\0*.ojn;*.osu;*.bms;*.bme;*.bml\0"; + ofn.lpstrFile = szFile; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + if (GetOpenFileNameW(&ofn)) { + return szFile; + } + else { + return L""; + } +} + +static std::array Mods = { "Mirror", "Random", "Rearrange", "Autoplay", "Hidden", "Flashlight", "Sudden" }; static std::array Arena = { "Random", "Arena 1", "Arena 2", "Arena 3", "Arena 4", "Arena 5", "Arena 6", "Arena 7", "Arena 8", "Arena 9", "Arena 10", "Arena 11", "Arena 12" }; @@ -61,12 +80,13 @@ void SongSelectScene::Render(double delta) auto window = GameWindow::GetInstance(); bPlay = false; - bExitPopup = false; + bOpenFile = false; bOptionPopup = false; bSelectNewSong = false; bOpenSongContext = false; bOpenEditor = false; bOpenRearrange = false; + bQuit = false; bScaleOutput = window->IsScaleOutput(); auto windowNextSz = ImVec2((float)window->GetBufferWidth(), (float)window->GetBufferHeight()); @@ -90,19 +110,25 @@ void SongSelectScene::Render(double delta) nullptr, flags)) { if (ImGui::BeginMenuBar()) { - if (ImGui::Button("Back", MathUtil::ScaleVec2(ImVec2(50, 0)))) { - bExitPopup = true; + + if (ImGui::Button("Quit", MathUtil::ScaleVec2(ImVec2(50, 0)))) { + bQuit = true; + } + + if (ImGui::Button("Open File", MathUtil::ScaleVec2(ImVec2(50, 0)))) { + bOpenFile = true; } - if (ImGui::Button("Options", MathUtil::ScaleVec2(ImVec2(50, 0)))) { + if (ImGui::Button("Settings", MathUtil::ScaleVec2(ImVec2(50, 0)))) { bOptionPopup = true; } ImGuiIO &io = ImGui::GetIO(); - ImGui::Text("Song Selection...."); + ImGui::Text("Select Song"); - std::string text = "No Account!"; + /*std::string text = "No Account!";*/ + std::string text = "O2Jam Simulator, Base game by Estrol, Effect by Albet"; auto textWidth = ImGui::CalcTextSize(text.c_str()).x; ImGui::SameLine(MathUtil::ScaleVec2(ImVec2(windowNextSz.x, 0)).x - textWidth - 15); @@ -325,11 +351,26 @@ void SongSelectScene::Render(double delta) }); } - if (bExitPopup) { + if (bOpenFile) { + SaveModifiers(); SaveConfiguration(); - SceneManager::DisplayFade(100, [this]() { - SceneManager::ChangeScene(GameScene::MAINMENU); - }); + std::wstring songfile = OpenFilePrompt(); + if (!songfile.empty()) { + is_departing = true; + EnvironmentSetup::SetPath("FILE", songfile); + + // Restart the game + RestartGame(); + } + else { + MsgBox::Show("MustSelectFile", "Error", "You must select a file!", MsgBoxType::OK); + } + } + + if (bQuit) { + SDL_Event e = {}; + e.type = SDL_QUIT; + SDL_PushEvent(&e); } } @@ -384,10 +425,11 @@ void SongSelectScene::OnGameSelectMusic(double delta) auto music = GameDatabase::GetInstance(); auto window = GameWindow::GetInstance(); auto windowNextSz = ImVec2((float)window->GetBufferWidth(), (float)window->GetBufferHeight()); - int currentDifficulty = EnvironmentSetup::GetInt("Difficulty"); + currentDifficulty = EnvironmentSetup::GetInt("Difficulty"); // create child window if (ImGui::BeginChild("#Container1", MathUtil::ScaleVec2(ImVec2(200, 500)))) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 3)); if (ImGui::BeginChild("#SongSelectChild2", MathUtil::ScaleVec2(ImVec2(200, 200)), true)) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); @@ -408,15 +450,15 @@ void SongSelectScene::OnGameSelectMusic(double delta) ImGui::Text("Title\r"); imgui_extends::TextBackground(color, MathUtil::ScaleVec2(340, 0), "%s", (const char *)item.Title); - ImGui::Text("Artist\r"); + ImGui::Text("Artist / Composer\r"); imgui_extends::TextBackground(color, MathUtil::ScaleVec2(340, 0), "%s", (const char *)item.Artist); ImGui::Text("Notecharter\r"); imgui_extends::TextBackground(color, MathUtil::ScaleVec2(340, 0), "%s", (const char *)item.Noter); - ImGui::Text("Note count\r"); + ImGui::Text("Total Notes\r"); - int difficulty = EnvironmentSetup::GetInt("Difficulty"); + difficulty = EnvironmentSetup::GetInt("Difficulty"); int count = item.Id == -1 ? 0 : item.MaxNotes[difficulty]; imgui_extends::TextBackground(color, MathUtil::ScaleVec2(340, 0), "%d", count); @@ -431,7 +473,8 @@ void SongSelectScene::OnGameSelectMusic(double delta) } if (ImGui::BeginChild("#test2", MathUtil::ScaleVec2(ImVec2(200, 290)), true)) { - std::vector difficulty = { "EZ", "NM", "HD" }; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2)); + std::vector difficulty = { "EZ", "NM", "HD" }; ImGui::Text("Note difficulty"); for (int i = 0; i < difficulty.size(); i++) { @@ -443,7 +486,7 @@ void SongSelectScene::OnGameSelectMusic(double delta) } if (ImGui::Button(difficulty[i].c_str(), MathUtil::ScaleVec2(ImVec2(30, 30)))) { - EnvironmentSetup::SetInt("Difficulty", i); // 0 EZ, 1 NM, 2 HD + EnvironmentSetup::SetInt("Difficulty", i); // 0 EZ, 1 NM, 2 HD } if (index == i) { @@ -473,61 +516,47 @@ void SongSelectScene::OnGameSelectMusic(double delta) ImGui::PopItemWidth(); + ImGui::Text("Modifier"); for (int i = 0; i < Mods.size(); i++) { - auto &mod = Mods[i]; + auto& mod = Mods[i]; int value = EnvironmentSetup::GetInt(mod); if (value == 1) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.9f); ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Button); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, 1.0f)); } if (ImGui::Button(mod.c_str(), MathUtil::ScaleVec2(ImVec2(80, 0)))) { EnvironmentSetup::SetInt(mod, value == 1 ? 0 : 1); - switch (i) { // New Modifier + switch (i) { case 0: // Mirror EnvironmentSetup::SetInt(Mods[1], 0); // Random - EnvironmentSetup::SetInt(Mods[2], 0); // Panic - EnvironmentSetup::SetInt(Mods[3], 0); // Rearrange + EnvironmentSetup::SetInt(Mods[2], 0); // Rearrange break; case 1: // Random EnvironmentSetup::SetInt(Mods[0], 0); // Mirror - EnvironmentSetup::SetInt(Mods[2], 0); // Panic - EnvironmentSetup::SetInt(Mods[3], 0); // Rearrange + EnvironmentSetup::SetInt(Mods[2], 0); // Rearrange break; - case 2: // Panic + case 2: // Rearrange EnvironmentSetup::SetInt(Mods[0], 0); // Mirror EnvironmentSetup::SetInt(Mods[1], 0); // Random - EnvironmentSetup::SetInt(Mods[3], 0); // Rearrange - break; - case 3: // Rearrange - EnvironmentSetup::SetInt(Mods[0], 0); // Mirror - EnvironmentSetup::SetInt(Mods[1], 0); // Random - EnvironmentSetup::SetInt(Mods[2], 0); // Panic bOpenRearrange = true; // Open Rearrange Window break; - case 5: // Hidden - EnvironmentSetup::SetInt(Mods[6], 0); // Flashlight - EnvironmentSetup::SetInt(Mods[7], 0); // Sudden - break; - case 6: // Flashlight - EnvironmentSetup::SetInt(Mods[5], 0); // Hidden - EnvironmentSetup::SetInt(Mods[7], 0); // Sudden + case 4: // Hidden + EnvironmentSetup::SetInt(Mods[5], 0); // Flashlight + EnvironmentSetup::SetInt(Mods[6], 0); // Sudden break; - case 7: // Sudden - EnvironmentSetup::SetInt(Mods[5], 0); // Hidden - EnvironmentSetup::SetInt(Mods[6], 0); // Flashlight + case 5: // Flashlight + EnvironmentSetup::SetInt(Mods[4], 0); // Hidden + EnvironmentSetup::SetInt(Mods[6], 0); // Sudden break; - case 8: // Song Background - EnvironmentSetup::SetInt(Mods[9], 0); // Black Background - break; - case 9: // Black Background - EnvironmentSetup::SetInt(Mods[8], 0); // Song Background + case 6: // Sudden + EnvironmentSetup::SetInt(Mods[4], 0); // Hidden + EnvironmentSetup::SetInt(Mods[5], 0); // Flashlight break; } } @@ -544,7 +573,6 @@ void SongSelectScene::OnGameSelectMusic(double delta) } ImGui::NewLine(); - ImGui::PushItemWidth(ImGui::GetCurrentWindow()->Size.x - 15); ImGui::Text("Arena"); @@ -560,11 +588,16 @@ void SongSelectScene::OnGameSelectMusic(double delta) ImGui::EndCombo(); } + + ImGui::PopStyleVar(); + ImGui::PopItemWidth(); ImGui::EndChild(); } + ImGui::PopStyleVar(); + ImGui::EndChild(); } @@ -659,7 +692,7 @@ void SongSelectScene::OnGameSelectMusic(double delta) if (ImGui::BeginChild("###CHILD", ImVec2(0, 0), false, 0)) { ImGui::PushItemWidth(ImGui::GetWindowSize().x); ImGui::Text("Search:"); - ImGui::InputTextEx("###Search", "Search by Title, Artist, Noter or Id....", search, sizeof(search), ImVec2(0, 0), ImGuiInputTextFlags_AutoSelectAll); + ImGui::InputTextEx("###Search", "Search by Title, Artist, Notecharter or ID...", search, sizeof(search), ImVec2(0, 0), ImGuiInputTextFlags_AutoSelectAll); // if press Enter if (ImGui::IsKeyPressed(ImGuiKey_Enter)) { @@ -801,7 +834,7 @@ bool SongSelectScene::Attach() } if (bgm && !bgm->IsPlaying()) { - bgm->SetVolume(50); + bgm->SetVolume(100); bgm->Play(0, true); } } @@ -866,6 +899,23 @@ bool SongSelectScene::Attach() return true; } +void SongSelectScene::RestartGame() { // HACK: This is the only way to use Open File function by restart the game to use the file opened without path issues + wchar_t moduleFileName[MAX_PATH]; + GetModuleFileNameW(NULL, moduleFileName, MAX_PATH); + + std::wstring filePath = EnvironmentSetup::GetPath("FILE"); + + std::wstring commandLine = std::wstring(moduleFileName) + L" \"" + filePath + L"\""; + STARTUPINFOW si = { sizeof(si) }; + PROCESS_INFORMATION pi; + + if (CreateProcessW(NULL, const_cast(commandLine.c_str()), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { // Fix antivirus false detection + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + ExitProcess(0); + } +} + bool SongSelectScene::Detach() { if (m_bgm) { @@ -890,6 +940,25 @@ void SongSelectScene::SaveConfiguration() Configuration::Set("Gameplay", "Notespeed", std::to_string(static_cast(::round(currentSpeed * 100.0)))); } +void SongSelectScene::SaveModifiers() +{ + Configuration::Set("Gameplay", "Difficulty", std::to_string(currentDifficulty)); + + std::string selectedMods; + for (int i = 0; i < Mods.size(); i++) { + std::string mod = Mods[i]; + int value = EnvironmentSetup::GetInt(mod); + if (value == 1) { + if (!selectedMods.empty()) selectedMods += ","; + selectedMods += mod; + } + } + Configuration::Set("Gameplay", "Modifiers", selectedMods); + + int arenaValue = EnvironmentSetup::GetInt("Arena"); + Configuration::Set("Gameplay", "Arena", std::to_string(arenaValue)); +} + void SongSelectScene::LoadChartImage() { std::lock_guard lock(m_imageLock); diff --git a/Game/src/Scenes/SongSelectScene.h b/Game/src/Scenes/SongSelectScene.h index 9a798f00..ab5d4ce4 100644 --- a/Game/src/Scenes/SongSelectScene.h +++ b/Game/src/Scenes/SongSelectScene.h @@ -24,6 +24,7 @@ class SongSelectScene : public Scene void OnMouseDown(const MouseState &state) override; bool Attach() override; + void RestartGame(); bool Detach() override; protected: @@ -32,6 +33,7 @@ class SongSelectScene : public Scene private: void SaveConfiguration(); + void SaveModifiers(); void LoadChartImage(); int scene_index = 0; @@ -42,6 +44,9 @@ class SongSelectScene : public Scene bool isScrolled = false; float waitTime = 0; + int currentDifficulty; + int difficulty; + float currentSpeed = 2.25; float currentRate = 1.0; @@ -54,13 +59,14 @@ class SongSelectScene : public Scene bool imgui_modal_quit_confirm = false; bool bPlay = false; - bool bExitPopup = false; + bool bOpenFile = false; bool bOptionPopup = false; bool bSelectNewSong = false; bool bOpenSongContext = false; bool bOpenEditor = false; bool bOpenRearrange = false; bool bScaleOutput = true; + bool bQuit = false; char lanePos[8] = {}; diff --git a/Game/src/Version.h b/Game/src/Version.h index a7253686..395ce7bb 100644 --- a/Game/src/Version.h +++ b/Game/src/Version.h @@ -1,9 +1,9 @@ #pragma once -#define O2GAME_TITLE "Unnamed O2 Clone" +#define O2GAME_TITLE "O2Clone" #if _DEBUG #define O2GAME_VERSION "Debug" #else -#define O2GAME_VERSION "0.1.9" +#define O2GAME_VERSION "(O2Jam Simulator)" #endif \ No newline at end of file diff --git a/Game/src/main.cpp b/Game/src/main.cpp index 6d501ab9..77d9452c 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -4,11 +4,8 @@ // STD Headers #include #include -#include - -#if __linux__ -#include -#endif +#include +#include // Game Headers #include "./Data/Util/Util.hpp" @@ -20,14 +17,18 @@ #include "Configuration.h" #include "MsgBox.h" +// SDL Headers +#include + #if _WIN32 extern "C" { -__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; + __declspec(dllexport) DWORD IntelGpuPowerPreference = 0x00000002; } #endif -int Run(int argc, wchar_t **argv) +int Run(int argc, wchar_t** argv) { try { Configuration::SetDefaultConfiguration(defaultConfiguration); @@ -39,8 +40,6 @@ int Run(int argc, wchar_t **argv) #if _WIN32 if (SetCurrentDirectoryW((LPWSTR)parentPath.wstring().c_str()) == FALSE) { - std::cout << "GetLastError(): " << GetLastError() << ", with path: " << parentPath.string(); - MessageBoxA(NULL, "Failed to set directory!", "EstGame Error", MB_ICONERROR); return -1; } @@ -71,7 +70,7 @@ int Run(int argc, wchar_t **argv) if (std::filesystem::exists(argv[i]) && EnvironmentSetup::GetPath("FILE").empty()) { std::filesystem::path path = argv[i]; - + EnvironmentSetup::SetInt("FileOpen", 1); EnvironmentSetup::SetPath("FILE", path); } } @@ -82,7 +81,8 @@ int Run(int argc, wchar_t **argv) } return 0; - } catch (std::exception &e) { + } + catch (std::exception& e) { MsgBox::ShowOut("EstGame Error", e.what(), MsgBoxType::OK, MsgBoxFlags::BTN_ERROR); return -1; } @@ -94,42 +94,37 @@ int HandleStructualException(int code) MessageBoxA(NULL, ("Uncaught exception: " + std::to_string(code)).c_str(), "FATAL ERROR", MB_ICONERROR); return EXCEPTION_EXECUTE_HANDLER; } -#endif - -// // if not DEBUG -// #if !defined(_DEBUG) && _WIN32 -// int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { -// const char* retVal = setlocale(LC_ALL, "en_US.UTF-8"); -// if (retVal == nullptr) { -// MessageBoxA(NULL, "setlocale(): Failed to set locale!", "EstGame Error", MB_ICONERROR); -// return -1; -// } -// int argc = 0; -// wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); - -// int ret = 0; - -// __try { -// ret = Run(argc, argv); -// } - -// __except (HandleStructualException(GetExceptionCode())) { -// ret = -1; -// } +// SDL expects an SDL_main function as the entry point +int SDL_main(int argc, char* argv[]) +{ + const char* retVal = setlocale(LC_ALL, "en_US.UTF-8"); + if (retVal == nullptr) { + MessageBoxA(NULL, "setlocale(): Failed to set locale!", "EstGame Error", MB_ICONERROR); + return -1; + } -// LocalFree(argv); + // Convert command line arguments + int wargc = 0; + wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); -// return ret; -// } + int ret = 0; -// #else + __try { + ret = Run(wargc, wargv); + } + __except (HandleStructualException(GetExceptionCode())) { + ret = -1; + } -// #endif + LocalFree(wargv); -int main(int argc, char *argv[]) + return ret; +} +#else +int main(int argc, char* argv[]) { - const char *retVal = setlocale(LC_ALL, "en_US.UTF-8"); + const char* retVal = setlocale(LC_ALL, "en_US.UTF-8"); if (retVal == nullptr) { #if _WIN32 MessageBoxA(NULL, "setlocale(): Failed to set locale!", "EstGame Error", MB_ICONERROR); @@ -139,7 +134,7 @@ int main(int argc, char *argv[]) return -1; } - wchar_t **wargv = new wchar_t *[argc]; + wchar_t** wargv = new wchar_t* [argc]; for (int i = 0; i < argc; i++) { size_t len = mbstowcs(NULL, argv[i], 0) + 1; wargv[i] = new wchar_t[len]; @@ -148,11 +143,10 @@ int main(int argc, char *argv[]) int ret = 0; -#if _WIN32 & _MSC_VER & NDEBUG +#if _MSC_VER && !defined(NDEBUG) __try { ret = Run(argc, wargv); } - __except (HandleStructualException(GetExceptionCode())) { ret = -1; } @@ -168,13 +162,4 @@ int main(int argc, char *argv[]) return ret; } - -// #include "./Engine/LuaScripting.h" - -// int main() { -// LuaScripting lua = { std::filesystem::current_path() / "Skins" / "Default" / "Scripts" }; -// lua.Update(0.0); - -// auto val = lua.GetSprite(SkinGroup::Playing, "JamLogo"); -// return 0; -// } \ No newline at end of file +#endif diff --git a/GameResources b/GameResources deleted file mode 160000 index 30425e6f..00000000 --- a/GameResources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 30425e6f57b1cc0ee0464a5d992db9f18bdd76f0