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 4c259f58..d31eb36c 100644 --- a/Engine/include/Game.h +++ b/Engine/include/Game.h @@ -87,4 +87,7 @@ class Game GameThread mRenderThread; GameThread mAudioThread; GameThread mLocalThread; -}; \ No newline at end of file + + std::mutex m_mutex; + std::condition_variable m_cv; +}; diff --git a/Engine/include/Rendering/Threading/GameThread.h b/Engine/include/Rendering/Threading/GameThread.h index 8b61218c..e15a4378 100644 --- a/Engine/include/Rendering/Threading/GameThread.h +++ b/Engine/include/Rendering/Threading/GameThread.h @@ -2,6 +2,7 @@ #include #include #include +#include class GameThread { @@ -19,6 +20,8 @@ class GameThread bool m_run; bool m_background; + std::mutex m_mutex; + std::thread m_thread; std::function m_main_cb; 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 82ec986c..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(); @@ -33,15 +33,22 @@ class Sprite2D void Draw(double delta, bool manual = false); void Draw(double delta, Rect *rect, bool manual = false); + void DrawStop(double delta, bool manual = false); + void DrawOnce(double delta, bool manual = false); Texture2D *GetTexture(); - void SetFPS(float fps); + void SetFPS(double fps); void Reset(); + double m_spritespeed = 1.0; + private: - double m_delay = 1.0; 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 8194d65a..b79c5c5b 100644 --- a/Engine/src/Game.cpp +++ b/Engine/src/Game.cpp @@ -16,9 +16,10 @@ constexpr auto kInputDefaultRate = 1000.0; constexpr auto kMenuDefaultRate = 60.0; -constexpr auto kAudioDefaultRate = 24.0; +constexpr auto kAudioDefaultRate = 60.0; namespace { + bool InitSDL() { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) { @@ -39,21 +40,23 @@ namespace { double FrameLimit(double MaxFrameRate) { - double newTick = SDL_GetTicks(); - double targetTick = lastTick + 1000.0 / MaxFrameRate; - - // If the frame rate is too high, wait to avoid shaky animations - if (newTick < targetTick) { - double delayTicks = targetTick - newTick; - SDL_Delay(static_cast(delayTicks)); - newTick += delayTicks; + const double targetFrameTime = 1000.0 / MaxFrameRate; + + 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; + std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(delayTime))); + std::this_thread::yield(); + newTick += delayTime; } double delta = (newTick - curTick) / 1000.0; - curTick = newTick; - // Update lastTick for the next frame lastTick = curTick; + curTick = newTick; return delta; } @@ -79,10 +82,9 @@ Game::~Game() if (m_running) { Stop(); - // Wait for threads to finish - // TODO: Figure a way to do this without using sleep - while (m_notify) { - SDL_Delay(500); + if (m_notify) { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this] { return !m_notify; }); } } @@ -153,8 +155,8 @@ void Game::Run() mAudioThread.Run([&] { double delta = FrameLimit(kAudioDefaultRate); AudioManager::GetInstance()->Update(delta); - }, - true); + }, + true); std::mutex m1, m2; @@ -169,7 +171,7 @@ void Game::Run() switch (m_frameLimitMode) { case FrameLimitMode::GAME: { - delta = FrameLimit(m_frameLimit); + delta = FrameLimit(m_frameLimit); break; } @@ -180,34 +182,34 @@ void Game::Run() } } - CheckFont(); + CheckFont(); - Update(delta); + Update(delta); - UpdateFade(delta); + UpdateFade(delta); - if (!m_minimized && m_renderer->BeginRender()) { - if (frameWithoutSwapchain > 0) { - Logs::Puts("[Game] Game iterate %d times without swap chain", frameWithoutSwapchain); - frameWithoutSwapchain = 0; - } + if (!m_minimized && m_renderer->BeginRender()) { + if (frameWithoutSwapchain > 0) { + Logs::Puts("[Game] Game iterate %d times without swap chain", frameWithoutSwapchain); + frameWithoutSwapchain = 0; + } - std::lock_guard lock(m1); + std::lock_guard lock(m1); - Render(delta); - MsgBox::Draw(); - DrawFade(delta); + Render(delta); + MsgBox::Draw(); + DrawFade(delta); - DrawConsole(); - m_renderer->EndRender(); + DrawConsole(); + m_renderer->EndRender(); - frames++; + frames++; } else { - frameWithoutSwapchain++; - } + frameWithoutSwapchain++; + } } else { - FrameLimit(15.0f); - } + FrameLimit(15.0f); + } }, true); @@ -217,7 +219,7 @@ void Game::Run() } else { m_sceneManager->OnKeyUp(state); } - }); + }); m_inputManager->ListenMouseEvent([&](const MouseState &state) { if (state.isDown) { @@ -225,23 +227,23 @@ void Game::Run() } else { m_sceneManager->OnMouseUp(state); } - }); + }); m_minimized = false; mLocalThread.Run([&] { double delta = 0; switch (m_frameLimitMode) { - case FrameLimitMode::GAME: - { - delta = FrameLimit(m_threadMode == ThreadMode::MULTI_THREAD ? kInputDefaultRate : m_frameLimit); - break; - } + case FrameLimitMode::GAME: + { + delta = FrameLimit(m_threadMode == ThreadMode::MULTI_THREAD ? kInputDefaultRate : m_frameLimit); + break; + } - case FrameLimitMode::MENU: - { - delta = FrameLimit(kMenuDefaultRate); - break; - } + case FrameLimitMode::MENU: + { + delta = FrameLimit(kMenuDefaultRate); + break; + } } m_imguiInterval += delta; @@ -249,9 +251,9 @@ void Game::Run() SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { - case SDL_QUIT: - MsgBox::Show("Quit", "Quit confirmation", "Are you sure you want to quit?", MsgBoxType::YESNO); - break; + case SDL_QUIT: + MsgBox::Show("Quit", "Quit confirmation", "Are you sure you want to quit?", MsgBoxType::YESNO); + break; } m_minimized = SDL_GetWindowFlags(m_window->GetWindow()) & SDL_WINDOW_MINIMIZED; @@ -305,8 +307,8 @@ void Game::Run() frames = 0; time = 0; } - }, - false); + }, + false); while (m_running) { mLocalThread.Update(); @@ -367,9 +369,17 @@ void Game::CheckFont() void Game::Stop() { - m_running = false; + if (m_running) { + { + std::unique_lock lock(m_mutex); + m_running = false; + m_notify = true; + } + m_cv.notify_one(); + } } + void Game::SetThreadMode(ThreadMode mode) { m_threadMode = mode; 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 283a83ee..ac651219 100644 --- a/Engine/src/Scenes/SceneManager.cpp +++ b/Engine/src/Scenes/SceneManager.cpp @@ -111,9 +111,6 @@ void SceneManager::Update(double delta) void SceneManager::Render(double delta) { - m_renderId = std::this_thread::get_id(); - m_ready_change_state = false; - if (m_currentScene) m_currentScene->Render(delta); if (m_currentOverlay && !MsgBox::Any()) { @@ -127,9 +124,9 @@ void SceneManager::Render(double delta) ImGui::OpenPopup(title.c_str()); if (ImGui::BeginPopupModal( - title.c_str(), - nullptr, - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize)) { + title.c_str(), + nullptr, + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize)) { m_currentOverlay->Render(delta); ImGui::EndPopup(); @@ -147,8 +144,31 @@ void SceneManager::Render(double delta) } if (m_nextScene != nullptr) { - m_ready_change_state = true; - m_cv.notify_one(); + if (m_currentOverlay) { + // Delay scene change until the overlay is closed + return; + } + + if (m_currentScene) { + if (!m_currentScene->Detach()) { + MsgBox::ShowOut("EstEngine Error", "Failed to detach current scene", MsgBoxType::OK, MsgBoxFlags::BTN_ERROR); + m_parent->Stop(); + return; + } + } + + if (!m_nextScene->Attach()) { + MsgBox::ShowOut("EstEngine Error", "Failed to init next scene", MsgBoxType::OK, MsgBoxFlags::BTN_ERROR); + m_parent->Stop(); + return; + } + + m_currentScene = m_nextScene; + m_nextScene = nullptr; + + if (m_onSceneChange) { + m_onSceneChange(); + } } } @@ -211,7 +231,10 @@ void SceneManager::ChangeScene(int idx) if (s_instance == nullptr) throw std::runtime_error(notInitialized); - s_instance->IChangeScene(idx); + // Load the next scene asynchronously + std::thread([idx]() { + s_instance->IChangeScene(idx); + }).detach(); } void SceneManager::AddOverlay(int Idx, Overlay *overlay) @@ -261,7 +284,6 @@ void SceneManager::IChangeScene(int idx) { if (m_scenes.find(idx) == m_scenes.end()) { std::string msg = "Failed to find SceneId: " + std::to_string(idx); - MsgBox::ShowOut("EstEngine Error", msg.c_str(), MsgBoxType::OK, MsgBoxFlags::BTN_ERROR); return; } @@ -270,6 +292,8 @@ void SceneManager::IChangeScene(int idx) m_currentSceneId = idx; m_nextScene = m_scenes[idx].get(); + m_ready_change_state = true; + m_cv.notify_one(); } void SceneManager::SetParent(Game *parent) @@ -308,7 +332,7 @@ void SceneManager::DisplayFade(int transparency, std::function callback) } callback(); - }).detach(); + }).detach(); } void SceneManager::ExecuteAfter(int ms_time, std::function callback) @@ -333,17 +357,17 @@ void SceneManager::GameExecuteAfter(ExecuteThread thread, int ms_time, std::func game->GetMainThread()->QueueAction(callback); } else { switch (thread) { - case ExecuteThread::UPDATE: - { - game->GetRenderThread()->QueueAction(callback); - break; - } + case ExecuteThread::UPDATE: + { + game->GetRenderThread()->QueueAction(callback); + break; + } - case ExecuteThread::WINDOW: - { - game->GetMainThread()->QueueAction(callback); - break; - } + case ExecuteThread::WINDOW: + { + game->GetMainThread()->QueueAction(callback); + break; + } } } } @@ -367,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 8b014825..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_delay = delay; - m_currentTime = 0.0f; + m_spritespeed = delay; + m_currentTime = 0.0; m_currentIndex = 0; Size = UDim2::fromScale(1, 1); @@ -14,9 +14,9 @@ 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_delay = delay; + m_spritespeed = delay; Size = UDim2::fromScale(1, 1); Position = UDim2::fromOffset(0, 0); AnchorPoint = { 0, 0 }; @@ -26,9 +26,9 @@ 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_delay = delay; + m_spritespeed = delay; Size = UDim2::fromScale(1, 1); Position = UDim2::fromOffset(0, 0); AnchorPoint = { 0, 0 }; @@ -40,9 +40,9 @@ 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_delay = delay; + m_spritespeed = delay; Size = UDim2::fromScale(1, 1); Position = UDim2::fromOffset(0, 0); AnchorPoint = { 0, 0 }; @@ -64,10 +64,19 @@ void Sprite2D::Draw(double delta, bool manual) Draw(delta, nullptr, manual); } -void Sprite2D::Draw(double delta, Rect *rect, bool manual) +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 + + if (m_currentIndex >= m_textures.size()) { + if (playOnce) { + return; // Stop if playing once and reached end + } + m_currentIndex = 0; // Reset index if looping + } + + 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); @@ -84,23 +93,53 @@ void Sprite2D::Draw(double delta, Rect *rect, bool manual) tex->AnchorPoint = AnchorPoint; tex->Draw(rect, manual ? false : true); - if (m_delay > 0.0f) { + if (m_spritespeed > 0.0) { m_currentTime += static_cast(delta); - if (m_currentTime >= m_delay) { - m_currentTime = 0.0f; - m_currentIndex = (m_currentIndex + 1) % m_textures.size(); + if (m_currentTime >= m_spritespeed) { + m_currentTime = 0.0; + m_currentIndex++; + + 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::Draw(double delta, Rect* rect, bool manual) +{ + DrawInternal(delta, false, rect, manual); // Play loop +} + +void Sprite2D::DrawOnce(double delta, bool manual) +{ + DrawInternal(delta, true, nullptr, manual); // Play once +} + +void Sprite2D::DrawStop(double delta, bool manual) +{ + DrawInternal(delta, true, nullptr, manual); + + 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; @@ -109,7 +148,7 @@ Texture2D *Sprite2D::GetTexture() return tex; } -void Sprite2D::SetFPS(float fps) +void Sprite2D::SetFPS(double fps) { - m_delay = 1.0f / fps; -} \ No newline at end of file + m_spritespeed = 1.0f / fps; +} diff --git a/Engine/src/Texture/Texture2D.cpp b/Engine/src/Texture/Texture2D.cpp index 5fd5b3ed..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_WHITE; - - 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 e60f32ab..f3f04fd9 100644 --- a/Engine/src/Threading/GameThread.cpp +++ b/Engine/src/Threading/GameThread.cpp @@ -20,39 +20,62 @@ void GameThread::Run(std::function callback, bool background) m_background = background; if (background) { - m_thread = std::thread([&] { + m_thread = std::thread([this] { while (m_run) { - m_main_cb(); - - if (m_queue_cb.size()) { - std::function queue_cb = m_queue_cb.back(); - m_queue_cb.pop_back(); + std::function main_cb; + { + std::lock_guard lock(m_mutex); + main_cb = m_main_cb; + } + if (main_cb) { + main_cb(); + } + std::function queue_cb; + { + std::lock_guard lock(m_mutex); + if (!m_queue_cb.empty()) { + queue_cb = m_queue_cb.front(); + m_queue_cb.pop_back(); + } + } + if (queue_cb) { queue_cb(); } } - }); + }); } } void GameThread::Update() { - if (m_background) { - return; - } - - m_main_cb(); - - if (m_queue_cb.size()) { - std::function queue_cb = m_queue_cb.back(); - m_queue_cb.pop_back(); + if (!m_background) { + std::function main_cb; + { + std::lock_guard lock(m_mutex); + main_cb = m_main_cb; + } + if (main_cb) { + main_cb(); + } - queue_cb(); + std::function queue_cb; + { + std::lock_guard lock(m_mutex); + if (!m_queue_cb.empty()) { + queue_cb = m_queue_cb.back(); + m_queue_cb.pop_back(); + } + } + if (queue_cb) { + queue_cb(); + } } } void GameThread::QueueAction(std::function callback) { + std::lock_guard lock(m_mutex); m_queue_cb.push_back(callback); } @@ -60,6 +83,9 @@ void GameThread::Stop() { if (m_background) { m_run = false; - m_thread.join(); + if (m_thread.joinable()) { + m_thread.join(); + } } } + 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 2f98fd57..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) { @@ -223,13 +216,13 @@ Chart::Chart(BMS::BMSFile &file) // check if overlap lastTime if (info.StartTime < lastTime[info.LaneIndex]) { - Logs::Puts("[Chart] overlapped note found at %.2f ms and conflict with %.2f ms", info.StartTime, lastTime[info.LaneIndex]); + Logs::Puts("[Chart] Overlapped note found on file %d at %.2f ms and conflict with %.2f ms", m_beatmapDirectory, info.StartTime, lastTime[info.LaneIndex]); if (note.SampleIndex != -1) { 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(); @@ -341,7 +321,7 @@ Chart::Chart(O2::OJN &file, int diffIndex) // check if overlap lastTime if (info.StartTime < lastTime[info.LaneIndex]) { - Logs::Puts("[Chart] overlapped note found at %.2f ms and conflict with %.2f ms", info.StartTime, lastTime[info.LaneIndex]); + Logs::Puts("[Chart] Overlapped note found on file o2ma%d at %.2f ms and conflict with %.2f ms", O2JamId, info.StartTime, lastTime[info.LaneIndex]); if (note.SampleRefId != -1) { AutoSample sm = {}; @@ -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 c6a38ced..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]; } } @@ -182,7 +186,7 @@ void OJM::LoadM30Data(std::fstream &fs) fs.read((char *)&Header, sizeof(M30Header)); - for (int i = 0; i < Header.sampleSize; i++) { + for (int i = 0; i < Header.sampleCount; i++) { // This fix no sound in few OJM struct M30SampleHeader { char sampleName[32]; @@ -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 4b36fffb..84562e1b 100644 --- a/Game/src/Engine/DrawableNote.cpp +++ b/Game/src/Engine/DrawableNote.cpp @@ -3,25 +3,23 @@ #include "Rendering/Renderer.h" #include "Texture/Texture2D.h" -DrawableNote::DrawableNote(NoteImage *frame) : FrameTimer::FrameTimer() +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)); + } + else { + 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(30); } 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 7b83c4c3..12a3e84b 100644 --- a/Game/src/Engine/GameTrack.cpp +++ b/Game/src/Engine/GameTrack.cpp @@ -42,7 +42,7 @@ void GameTrack::Update(double delta) auto ¬e = *_note; if (note->IsPassed()) { - m_inactive_notes.push_back(note); + m_inactive_notes.emplace_back(note); _note = m_notes.erase(_note); } else { if (!note->IsDrawable()) { @@ -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); @@ -212,7 +212,7 @@ void GameTrack::AddNote(NoteInfoDesc *desc) m_keySound = note->GetKeysoundId(); } - m_notes.push_back(std::move(note)); + m_notes.emplace_back(std::move(note)); } void GameTrack::ListenEvent(std::function callback) 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/O2NumericTexture.cpp b/Game/src/Engine/O2NumericTexture.cpp index a2251acd..3233076b 100644 --- a/Game/src/Engine/O2NumericTexture.cpp +++ b/Game/src/Engine/O2NumericTexture.cpp @@ -15,7 +15,7 @@ O2NumericTexture::O2NumericTexture(OJS *ojs) auto frame = ojs->Frames[i].get(); auto tex = new O2Texture(frame); - m_numericsTexture.push_back(tex); + m_numericsTexture.emplace_back(tex); m_numbericsWidth[i] = tex->GetOriginalRECT(); } } \ No newline at end of file diff --git a/Game/src/Engine/RhythmEngine.cpp b/Game/src/Engine/RhythmEngine.cpp index fef99697..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()); + //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 4c450188..2f7a181b 100644 --- a/Game/src/Engine/ScoreManager.cpp +++ b/Game/src/Engine/ScoreManager.cpp @@ -1,8 +1,13 @@ #include "ScoreManager.hpp" #include #include -// TODO: Make Proper O2Jam Health Rules with MAXHP 1000 instead 100 -// This health system look like on Hard only, not good +#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; @@ -34,72 +39,43 @@ ScoreManager::~ScoreManager() void ScoreManager::OnHit(NoteHitInfo info) { - switch (info.Result) { + bool isPlaying = EnvironmentSetup::GetInt("NowPlaying") == 1; + bool isFail = EnvironmentSetup::GetInt("Failed") == 1; - case NoteResult::COOL: - { - AddLife(0.1f); - m_jamGauge += 4; - m_score += 200 + (10 * m_jamCombo); - m_cool++; - break; - } + if (!isPlaying) { + return; + } - case NoteResult::GOOD: - { - AddLife(0); - m_jamGauge += 2; - m_score += 100 + (5 * m_jamCombo); - m_good++; + HealthDifficulty health; + int difficulty = EnvironmentSetup::GetInt("Difficulty"); + switch (difficulty) { + case 0: + health = easy; break; - } - - case NoteResult::BAD: - { - 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.5); - m_jamGauge = 0; - m_coolCombo = 0; - m_score += 4; - m_combo = 0; - m_bad++; - } + case 1: + health = normal; break; - } - - default: - { - AddLife(-3); - m_combo = 0; - m_jamCombo = 0; - m_jamGauge = 0; - - if (m_life > 0) { - m_score -= 10; - } - - m_miss++; + case 2: + health = hard; break; - } } + HandleHit(info, health); + 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); + 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 (m_coolCombo > 15) { - m_coolCombo = 0; - m_numOfPills = std::clamp(m_numOfPills + 1, 0, 5); + if (!isFail) { + if (m_coolCombo > 15) { + m_coolCombo = 0; + m_numOfPills = std::clamp(m_numOfPills + 1, 0, 5); + } } } else { m_coolCombo = 0; @@ -110,13 +86,15 @@ void ScoreManager::OnHit(NoteHitInfo info) m_maxCombo = std::max(m_maxCombo, m_combo); } - if (m_jamGauge >= kMaxJamGauge) { - m_jamGauge = 0; - m_jamCombo += 1; - m_maxJamCombo = std::max(m_maxJamCombo, m_jamCombo); + 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); + if (m_jamCallback) { + m_jamCallback(m_jamCombo); + } } } @@ -129,8 +107,63 @@ void ScoreManager::OnHit(NoteHitInfo info) } } +void ScoreManager::HandleHit(NoteHitInfo& info, const HealthDifficulty& health) +{ + switch (info.Result) { + case NoteResult::COOL: + AddLife(health.cool); + m_jamGauge += 4.0f; + m_score += 200 + (10 * m_jamCombo); + m_cool++; + break; + case NoteResult::GOOD: + AddLife(health.good); + m_jamGauge += 2.0f; + m_score += 100 + (5 * m_jamCombo); + m_good++; + break; + case NoteResult::BAD: + 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(health.bad); + m_jamGauge = 0.0f; + m_coolCombo = 0; + m_score += 4; + m_combo = 0; + m_bad++; + } + break; + default: + AddLife(health.miss); + m_combo = 0; + m_jamCombo = 0; + m_jamGauge = 0.0f; + if (m_life > 0) { + m_score -= 10; + } + m_miss++; + break; + } +} + void ScoreManager::OnLongNoteHold(HoldResult result) { + bool isPlaying = EnvironmentSetup::GetInt("NowPlaying") == 1; + bool isFail = EnvironmentSetup::GetInt("Failed") == 1; + + if (!isPlaying) { + return; + } + + if (isFail) { + return; + } + switch (result) { case HoldResult::HoldBreak: { @@ -189,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); + } } } 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 cde9b389..83fe8c11 100644 --- a/Game/src/Scenes/GameplayScene.cpp +++ b/Game/src/Scenes/GameplayScene.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "Configuration.h" #include "Game.h" @@ -26,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 { @@ -33,7 +35,7 @@ struct MissInfo float beat; float hit_beat; float time; -}; +}; bool CheckSkinComponent(std::filesystem::path x) { @@ -47,8 +49,8 @@ GameplayScene::GameplayScene() : Scene::Scene() m_keyState = {}; m_game = nullptr; m_drawJam = false; - m_wiggleTime = 0; - m_wiggleOffset = 0; + m_counter = 0.0; + lifeFillDuration = 0.0; } void GameplayScene::Update(double delta) @@ -58,7 +60,8 @@ void GameplayScene::Update(double delta) if (EnvironmentSetup::GetInt("Key") >= 0) { m_ended = true; SceneManager::ChangeScene(GameScene::SONGSELECT); - } else { + } + else { if (MsgBox::GetResult("GameplayError") == 4) { m_ended = true; SceneManager::GetInstance()->StopGame(); @@ -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,13 +120,20 @@ 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); - }); - } else { + }); + } + else { SceneManager::DisplayFade(100, [] { SceneManager::ChangeScene(GameScene::SONGSELECT); - }); + }); } } @@ -118,26 +149,29 @@ void GameplayScene::Render(double delta) ImguiUtil::NewFrame(); - bool is_flhd_enabled = m_laneHideImage.get() != nullptr; - int arena = EnvironmentSetup::GetInt("Arena"); - if (arena != -1) { - m_PlayBG->Draw(); - } else { - auto songBG = (Texture2D *)EnvironmentSetup::GetObj("SongBackground"); + if (EnvironmentSetup::GetInt("Background") == 1) { + auto songBG = (Texture2D*)EnvironmentSetup::GetObj("SongBackground"); if (songBG) { + songBG->TintColor = Color3::FromRGB(128, 128, 128); songBG->Draw(); } } + else if (EnvironmentSetup::GetInt("Background") == 2) { + // Do nothing + } + else { + m_PlayBG->Draw(); + } - m_Playfooter->Draw(); m_Playfield->Draw(); - if (!is_flhd_enabled) { - m_targetBar->Draw(delta); - } + m_Playfooter->Draw(); + + m_targetBar->Draw(delta); + m_targetBar->AlphaBlend = true; - for (auto &[lane, pressed] : m_keyState) { + for (auto& [lane, pressed] : m_keyState) { if (pressed) { m_keyLighting[lane]->AlphaBlend = true; m_keyLighting[lane]->Draw(); @@ -147,9 +181,13 @@ void GameplayScene::Render(double delta) m_game->Render(delta); - if (is_flhd_enabled) { + // Draw Mods + if (m_noteMod) { + m_noteMod->Draw(); + } + if (m_visualMod) { + m_visualMod->Draw(); m_laneHideImage->Draw(); - m_targetBar->Draw(delta); } auto scores = m_game->GetScoreManager()->GetScore(); @@ -174,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 = (int)curLifeTex->AbsolutePosition.X; - rc.top = (int)curLifeTex->AbsolutePosition.Y; - rc.right = rc.left + (int)curLifeTex->AbsoluteSize.X; - rc.bottom = rc.top + (int)curLifeTex->AbsoluteSize.Y + 5; // Need to add + value because wiggle effect =w= - 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) * 5.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; - m_lifeBar->Draw(delta, &rc); + float alpha = (float)(kMaxLife - m_game->GetScoreManager()->GetLife()) / kMaxLife; + + auto curLifeTex = m_lifeBar->GetTexture(); + curLifeTex->CalculateSize(); + + float offset = 10.0f; + + 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.5, 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; } @@ -211,7 +276,7 @@ void GameplayScene::Render(double delta) if (m_drawJam) { if (std::get<5>(scores) > 0) { m_jamNum->DrawNumber(std::get<5>(scores)); - m_jamLogo->Draw(delta); + m_jamLogo->DrawStop(delta); // Example for DrawStop } if ((m_jamTimer += delta) > 0.60) { @@ -219,59 +284,80 @@ void GameplayScene::Render(double delta) } } - if (m_drawCombo && std::get<7>(scores) > 0) { - m_amplitude = 30.0; - m_wiggleTime = 60 * m_comboTimer; + if (m_drawCombo && std::get<7>(scores) > 0) { // O2Jam Replication by Albert Frengki! + const double positionStart = 30.0; + double step = 6.0; + double animationSpeed = 90.0; + double maxStep = step; + double maxSpeed = animationSpeed; - double decrement = 6.0; // Calculate the decrement for each frame - double totalDecrement = decrement * m_wiggleTime; // Calculate the total decrement based on the current frame/time + if (m_comboTimer > 1.0) { + animationSpeed += 10.0 * delta; + step += 1.0 * delta; - double currentAmplitude = m_amplitude - totalDecrement; // Adjust the amplitude based on the total decrement + } + else { + animationSpeed -= 10.0 * delta; + step -= 1.0 * delta; + } - if (currentAmplitude < 0.0) { - currentAmplitude = 0.0; // Stopper like this due compiler issues + if (animationSpeed > maxSpeed) { + animationSpeed = maxSpeed; } - if (m_comboTimer > 1.0) { // Check if the previous animation hasn't finished - m_comboTimer = 0.0; // Reset the timer - m_amplitude += decrement; // Increase the starting amplitude for the next animation + if (step > maxStep) { + step = maxStep; } - m_comboLogo->Position2 = UDim2::fromOffset(0, currentAmplitude / 3.0); - m_comboLogo->Draw(delta); + 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_comboNum->Position2 = UDim2::fromOffset(0, currentAmplitude); + m_comboLogo->Draw(delta); m_comboNum->DrawNumber(std::get<7>(scores)); m_comboTimer += delta; - if (m_comboTimer > 1.0) { + + if (m_comboTimer >= 1.0) { m_comboTimer = 0.0; m_drawCombo = false; } } - if (m_drawLN && std::get<9>(scores) > 0) { // Same Animation logic like DrawCombo - m_amplitude = 5.0; - m_wiggleTime = 60 * m_lnTimer; + if (m_drawLN && std::get<9>(scores) > 0) { + const double positionStart = 5.0; + double step = 1.0; + double animationSpeed = 90.0; + double maxStep = step; + double maxSpeed = animationSpeed; - double decrement = 1.0; - double totalDecrement = decrement * m_wiggleTime; + if (m_comboTimer > 1.0) { + animationSpeed += 10.0 * delta; + step += 0.1 * delta; - double currentAmplitude = m_amplitude - totalDecrement; + } + else { + animationSpeed -= 10.0 * delta; + step -= 0.1 * delta; + } - if (currentAmplitude < 0.0) { - currentAmplitude = 0.0; + if (animationSpeed > maxSpeed) { + animationSpeed = maxSpeed; } - if (m_lnTimer > 1.0) { - m_lnTimer = 0.0; - m_amplitude += decrement; + if (step > maxStep) { + step = maxStep; } - m_lnLogo->Position2 = UDim2::fromOffset(0, currentAmplitude); + 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, currentAmplitude); + m_lnComboNum->Position2 = UDim2::fromOffset(0, currentPosition); m_lnComboNum->DrawNumber(std::get<9>(scores)); m_lnTimer += delta; @@ -279,7 +365,6 @@ void GameplayScene::Render(double delta) if (m_lnTimer > 1.0) { m_lnTimer = 0.0; m_drawLN = false; - m_lnLogo->Reset(); } } @@ -299,7 +384,8 @@ void GameplayScene::Render(double delta) }; m_jamGauge->Draw(&rc); - } else { + } + else { // Fill from left to right lerp = static_cast(std::lerp(0.0, m_jamGauge->AbsoluteSize.X, gaugeVal)); Rect rc = { @@ -330,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); + } + } + else { + m_hitEffect[i]->LastIndex(); - if (m_drawHold[i]) { - m_holdEffect[i]->Draw(delta); + if (m_drawHold[i]) { + m_holdEffect[i]->LastIndex(); + } } } @@ -353,20 +460,24 @@ 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); } } int idx = 2; try { idx = std::stoi(Configuration::Load("Game", "GuideLine")); - } catch (const std::invalid_argument &) { + } + catch (const std::invalid_argument&) { idx = 2; } @@ -381,7 +492,7 @@ void GameplayScene::Input(double delta) m_game->Input(delta); } -void GameplayScene::OnKeyDown(const KeyState &state) +void GameplayScene::OnKeyDown(const KeyState& state) { if (m_resourceFucked) return; @@ -389,7 +500,7 @@ void GameplayScene::OnKeyDown(const KeyState &state) m_game->OnKeyDown(state); } -void GameplayScene::OnKeyUp(const KeyState &state) +void GameplayScene::OnKeyUp(const KeyState& state) { if (m_resourceFucked) return; @@ -397,7 +508,7 @@ void GameplayScene::OnKeyUp(const KeyState &state) m_game->OnKeyUp(state); } -void GameplayScene::OnMouseDown(const MouseState &state) +void GameplayScene::OnMouseDown(const MouseState& state) { } @@ -422,7 +533,8 @@ bool GameplayScene::Attach() try { LaneOffset = std::stoi(manager->GetSkinProp("Game", "LaneOffset", "5")); HitPos = std::stoi(manager->GetSkinProp("Game", "HitPos", "480")); - } catch (const std::invalid_argument &) { + } + catch (const std::invalid_argument&) { throw std::runtime_error("Invalid parameter on Skin::Game::LaneOffset or Skin::Game::HitPos"); } @@ -463,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); @@ -681,11 +796,11 @@ bool GameplayScene::Attach() auto OnButtonHover = [&](int state) { m_drawExitButton = state; - }; + }; auto OnButtonClick = [&]() { m_doExit = true; - }; + }; m_exitButtonFunc = std::make_unique