diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index edd34da..ac671c8 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -35,6 +35,7 @@ set(EDITSCENE_SOURCES systems/RoomLayoutSystem.cpp systems/FurnitureLibrary.cpp systems/StartupMenuSystem.cpp + systems/PauseMenuSystem.cpp systems/PlayerControllerSystem.cpp systems/CharacterSlotSystem.cpp systems/CharacterRegistry.cpp @@ -205,6 +206,7 @@ set(EDITSCENE_HEADERS ui/CharacterClassDatabaseEditor.hpp systems/CharacterClassSystem.hpp systems/StartupMenuSystem.hpp + systems/PauseMenuSystem.hpp systems/PlayerControllerSystem.hpp systems/EditorUISystem.hpp systems/CellGridSystem.hpp diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 3614dd7..ebaee46 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -29,6 +29,7 @@ #include "systems/NormalDebugSystem.hpp" #include "systems/RoomLayoutSystem.hpp" #include "systems/StartupMenuSystem.hpp" +#include "systems/PauseMenuSystem.hpp" #include "systems/DialogueSystem.hpp" #include "systems/CharacterClassSystem.hpp" #include "systems/PregnancySystem.hpp" @@ -151,6 +152,13 @@ void ImGuiRenderListener::preViewportUpdate( sms->update(m_deltaTime); } + // Render pause menu in game mode (inside ImGui frame scope) + if (m_editorApp && + m_editorApp->getGameMode() == EditorApp::GameMode::Game && + m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Paused) { + PauseMenuSystem::getInstance().update(m_deltaTime); + } + // Render dialogue box (game mode or editor preview) DialogueSystem::getInstance().update(m_deltaTime); @@ -455,6 +463,7 @@ void EditorApp::setup() m_world, m_sceneMgr, this); DialogueSystem::getInstance().init(this); DialogueSystem::getInstance().loadSettings("dialogue.json"); + PauseMenuSystem::getInstance().init(this); m_characterClassSystem = std::make_unique( @@ -494,6 +503,7 @@ void EditorApp::setup() if (m_startupMenuSystem) m_startupMenuSystem->prepareFont(); DialogueSystem::getInstance().prepareFont(); + PauseMenuSystem::getInstance().prepareFont(); } // Now show the overlay — font atlas will be built with our font @@ -635,6 +645,8 @@ void EditorApp::setGamePlayState(GamePlayState state) setWindowGrab(true); } else if (state == GamePlayState::Menu) { setWindowGrab(false); + } else if (state == GamePlayState::Paused) { + setWindowGrab(false); } } } @@ -900,6 +912,8 @@ void EditorApp::createAxes() bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) { + bool paused = (m_gamePlayState == GamePlayState::Paused); + if (m_gameMode == GameMode::Editor) { // Update camera if (m_camera) { @@ -919,81 +933,83 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) m_characterSlotSystem->update(); } - /* --- Animation / procedural generation --- */ - if (m_animationTreeSystem) { - m_animationTreeSystem->update(evt.timeSinceLastFrame); - if (m_behaviorTreeSystem) - m_behaviorTreeSystem->update(evt.timeSinceLastFrame); - } - if (m_pathFollowingSystem) { - m_pathFollowingSystem->update(evt.timeSinceLastFrame); - } - if (m_proceduralMeshSystem) { - m_proceduralMeshSystem->update(); - } - - /* --- Static world generation (meshes + physics) --- */ - if (m_roomLayoutSystem) { - m_roomLayoutSystem->update(); - } - if (m_cellGridSystem) { - m_cellGridSystem->update(); - } - - /* --- Normal debug visualization (after geometry is built) --- */ - if (m_normalDebugSystem) { - m_normalDebugSystem->update(); - } - - /* --- NavMesh builds after static geometry is ready --- */ - if (m_navMeshSystem) { - m_navMeshSystem->update(evt.timeSinceLastFrame); - } - - /* --- Smart Object system (AI navigation to smart objects) --- */ - if (m_smartObjectSystem) { - m_smartObjectSystem->update(evt.timeSinceLastFrame); - } - - /* --- GOAP Planner system (plan generation) --- */ - if (m_goapPlannerSystem) { - m_goapPlannerSystem->update(evt.timeSinceLastFrame); - } - - /* --- GOAP Runner system (plan execution) --- */ - if (m_goapRunnerSystem) { - m_goapRunnerSystem->update(evt.timeSinceLastFrame); - } - - /* --- Actuator system (player interaction prompts) --- */ - if (m_actuatorSystem) { - m_actuatorSystem->update(evt.timeSinceLastFrame); - } - - /* --- Event Handler system (event-driven BTs) --- */ - if (m_eventHandlerSystem) { - m_eventHandlerSystem->update(evt.timeSinceLastFrame); - } - - /* --- Dynamic physics (characters after static world) --- */ - - if (m_characterSystem) { - m_characterSystem->update(evt.timeSinceLastFrame); - } - - /* --- Buoyancy system (before physics so impulse is integrated) --- */ - if (m_buoyancySystem) { - // Update camera position for water detection area - if (m_camera) { - Ogre::Vector3 cameraPos = m_camera->getPosition(); - m_buoyancySystem->setCameraPosition(cameraPos); + /* --- Gameplay systems (paused when in Paused state) --- */ + if (!paused) { + /* --- Animation / procedural generation --- */ + if (m_animationTreeSystem) { + m_animationTreeSystem->update(evt.timeSinceLastFrame); + if (m_behaviorTreeSystem) + m_behaviorTreeSystem->update(evt.timeSinceLastFrame); + } + if (m_pathFollowingSystem) { + m_pathFollowingSystem->update(evt.timeSinceLastFrame); + } + if (m_proceduralMeshSystem) { + m_proceduralMeshSystem->update(); } - m_buoyancySystem->update(evt.timeSinceLastFrame); - } - /* --- Main physics step --- */ - if (m_physicsSystem) { - m_physicsSystem->update(evt.timeSinceLastFrame); + /* --- Static world generation (meshes + physics) --- */ + if (m_roomLayoutSystem) { + m_roomLayoutSystem->update(); + } + if (m_cellGridSystem) { + m_cellGridSystem->update(); + } + + /* --- Normal debug visualization (after geometry is built) --- */ + if (m_normalDebugSystem) { + m_normalDebugSystem->update(); + } + + /* --- NavMesh builds after static geometry is ready --- */ + if (m_navMeshSystem) { + m_navMeshSystem->update(evt.timeSinceLastFrame); + } + + /* --- Smart Object system (AI navigation to smart objects) --- */ + if (m_smartObjectSystem) { + m_smartObjectSystem->update(evt.timeSinceLastFrame); + } + + /* --- GOAP Planner system (plan generation) --- */ + if (m_goapPlannerSystem) { + m_goapPlannerSystem->update(evt.timeSinceLastFrame); + } + + /* --- GOAP Runner system (plan execution) --- */ + if (m_goapRunnerSystem) { + m_goapRunnerSystem->update(evt.timeSinceLastFrame); + } + + /* --- Actuator system (player interaction prompts) --- */ + if (m_actuatorSystem) { + m_actuatorSystem->update(evt.timeSinceLastFrame); + } + + /* --- Event Handler system (event-driven BTs) --- */ + if (m_eventHandlerSystem) { + m_eventHandlerSystem->update(evt.timeSinceLastFrame); + } + + /* --- Dynamic physics (characters after static world) --- */ + if (m_characterSystem) { + m_characterSystem->update(evt.timeSinceLastFrame); + } + + /* --- Buoyancy system (before physics so impulse is integrated) --- */ + if (m_buoyancySystem) { + // Update camera position for water detection area + if (m_camera) { + Ogre::Vector3 cameraPos = m_camera->getPosition(); + m_buoyancySystem->setCameraPosition(cameraPos); + } + m_buoyancySystem->update(evt.timeSinceLastFrame); + } + + /* --- Main physics step --- */ + if (m_physicsSystem) { + m_physicsSystem->update(evt.timeSinceLastFrame); + } } /* --- Rendering support systems --- */ @@ -1127,6 +1143,13 @@ bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt) m_currentModifiers = evt.keysym.mod; if (m_gameMode == GameMode::Game) { + if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) { + if (m_gamePlayState == GamePlayState::Playing) + setGamePlayState(GamePlayState::Paused); + else if (m_gamePlayState == GamePlayState::Paused) + setGamePlayState(GamePlayState::Playing); + return true; + } bool pressed = true; switch (evt.keysym.sym) { case 'w': @@ -1242,6 +1265,11 @@ flecs::entity EditorApp::getSelectedEntity() const return flecs::entity::null(); } +PauseMenuSystem *EditorApp::getPauseMenuSystem() const +{ + return &PauseMenuSystem::getInstance(); +} + DialogueSystem *EditorApp::getDialogueSystem() const { return &DialogueSystem::getInstance(); diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index b3a8d23..0de9739 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -30,6 +30,7 @@ class CharacterSystem; class CellGridSystem; class RoomLayoutSystem; class StartupMenuSystem; +class PauseMenuSystem; class DialogueSystem; class PlayerControllerSystem; class BuoyancySystem; @@ -192,6 +193,7 @@ public: { return m_startupMenuSystem.get(); } + PauseMenuSystem *getPauseMenuSystem() const; DialogueSystem *getDialogueSystem() const; ActuatorSystem *getActuatorSystem() const { diff --git a/src/features/editScene/systems/PauseMenuSystem.cpp b/src/features/editScene/systems/PauseMenuSystem.cpp new file mode 100644 index 0000000..3ed2699 --- /dev/null +++ b/src/features/editScene/systems/PauseMenuSystem.cpp @@ -0,0 +1,181 @@ +#include "PauseMenuSystem.hpp" +#include "../EditorApp.hpp" +#include "../components/StartupMenu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PauseMenuSystem::PauseMenuSystem() + : m_editorApp(nullptr) +{ +} + +PauseMenuSystem::~PauseMenuSystem() = default; + +PauseMenuSystem &PauseMenuSystem::getInstance() +{ + static PauseMenuSystem instance; + return instance; +} + +void PauseMenuSystem::init(EditorApp *editorApp) +{ + m_editorApp = editorApp; +} + +void PauseMenuSystem::ensureFontLoaded(const Ogre::String &fontName, + float fontSize) +{ + if (m_fontLoaded && m_currentFontName == fontName && + m_currentFontSize == fontSize) + return; + + Ogre::ImGuiOverlay *overlay = m_editorApp->getImGuiOverlay(); + if (!overlay) + return; + + Ogre::FontPtr font; + try { + if (Ogre::FontManager::getSingleton().resourceExists( + "PauseMenuFont", "General")) { + Ogre::FontManager::getSingleton().remove("PauseMenuFont", + "General"); + } + font = Ogre::FontManager::getSingleton().create("PauseMenuFont", + "General"); + font->setType(Ogre::FontType::FT_TRUETYPE); + font->setSource(fontName); + font->setTrueTypeSize(fontSize); + font->setTrueTypeResolution(75); + font->addCodePointRange(Ogre::Font::CodePointRange(32, 255)); + font->load(); + } catch (...) { + Ogre::LogManager::getSingleton().logMessage( + "PauseMenuSystem: Failed to load font " + fontName); + m_menuFont = nullptr; + m_fontLoaded = false; + return; + } + + m_menuFont = overlay->addFont("PauseMenuFont", "General"); + m_currentFontName = fontName; + m_currentFontSize = fontSize; + m_fontLoaded = true; +} + +void PauseMenuSystem::prepareFont() +{ + if (!m_editorApp) + return; + + flecs::world *world = m_editorApp->getWorld(); + if (!world) + return; + + flecs::entity menuEntity = flecs::entity::null(); + world->query().each( + [&](flecs::entity e, StartupMenuComponent &) { + if (!menuEntity.is_alive()) + menuEntity = e; + }); + + if (menuEntity.is_alive()) { + auto &sm = menuEntity.get(); + Ogre::LogManager::getSingleton().logMessage( + "PauseMenuSystem: Using font=" + sm.fontName + + ", size=" + Ogre::StringConverter::toString(sm.fontSize)); + ensureFontLoaded(sm.fontName, sm.fontSize); + } else { + Ogre::LogManager::getSingleton().logMessage( + "PauseMenuSystem: No StartupMenuComponent found, using defaults"); + ensureFontLoaded("Kenney Bold.ttf", 36.0f); + } +} + +void PauseMenuSystem::update(float deltaTime) +{ + (void)deltaTime; + + if (!m_editorApp || m_editorApp->getGamePlayState() != + EditorApp::GamePlayState::Paused) + return; + + renderMenu(); +} + +void PauseMenuSystem::renderMenu() +{ + ImVec2 size = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_Always); + + ImVec4 bgColor = ImVec4(0.0f, 0.0f, 0.0f, 0.85f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, bgColor); + + ImGui::Begin("PauseMenu", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoFocusOnAppearing); + + if (m_menuFont) + ImGui::PushFont(m_menuFont); + + struct ButtonData { + const char *label; + std::function action; + }; + + std::vector buttons; + buttons.push_back({ "RETURN TO GAME", [&]() { + m_editorApp->setGamePlayState( + EditorApp::GamePlayState::Playing); + } }); + buttons.push_back({ "SAVE GAME", [&]() { + Ogre::LogManager::getSingleton().logMessage( + "Save game not implemented"); + } }); + buttons.push_back({ "OPTIONS", [&]() { + Ogre::LogManager::getSingleton().logMessage( + "Options not implemented"); + } }); + buttons.push_back({ "QUIT", [&]() { + Ogre::Root::getSingleton().queueEndRendering(); + } }); + + float buttonWidth = 0.0f; + float buttonsHeight = 0.0f; + float extraPixels = 20.0f; + for (const auto &b : buttons) { + ImVec2 textSize = ImGui::CalcTextSize(b.label); + float bwidth = textSize.x + + (ImGui::GetStyle().FramePadding.x * 2.0f) + + extraPixels; + float bheight = + textSize.y + (ImGui::GetStyle().FramePadding.y * 2.0f); + if (buttonWidth < bwidth) + buttonWidth = bwidth; + buttonsHeight += bheight + ImGui::GetStyle().ItemSpacing.y; + } + if (!buttons.empty()) + buttonsHeight -= ImGui::GetStyle().ItemSpacing.y; + + ImGui::SetCursorPosY((size.y - buttonsHeight) * 0.5f); + for (const auto &b : buttons) { + ImGui::SetCursorPosX((size.x - buttonWidth) * 0.5f); + if (ImGui::Button(b.label, ImVec2(buttonWidth, 0))) + b.action(); + } + + if (m_menuFont) + ImGui::PopFont(); + + ImGui::End(); + ImGui::PopStyleColor(); +} diff --git a/src/features/editScene/systems/PauseMenuSystem.hpp b/src/features/editScene/systems/PauseMenuSystem.hpp new file mode 100644 index 0000000..cbd335c --- /dev/null +++ b/src/features/editScene/systems/PauseMenuSystem.hpp @@ -0,0 +1,43 @@ +#ifndef EDITSCENE_PAUSEMENUSYSTEM_HPP +#define EDITSCENE_PAUSEMENUSYSTEM_HPP +#pragma once + +#include +#include + +class EditorApp; + +/** + * Singleton pause menu system. + * + * Renders a full-screen pause menu in game mode when EditorApp + * is in GamePlayState::Paused. Font settings are copied from + * the StartupMenuComponent configured in the scene. + */ +class PauseMenuSystem { +public: + static PauseMenuSystem &getInstance(); + + void init(EditorApp *editorApp); + void prepareFont(); + void update(float deltaTime); + +private: + PauseMenuSystem(); + ~PauseMenuSystem(); + + PauseMenuSystem(const PauseMenuSystem &) = delete; + PauseMenuSystem &operator=(const PauseMenuSystem &) = delete; + + void renderMenu(); + void ensureFontLoaded(const Ogre::String &fontName, float fontSize); + + EditorApp *m_editorApp = nullptr; + + bool m_fontLoaded = false; + Ogre::String m_currentFontName; + float m_currentFontSize = 0.0f; + ImFont *m_menuFont = nullptr; +}; + +#endif // EDITSCENE_PAUSEMENUSYSTEM_HPP