From f7ab6a641a0c42d013017d42cea95a6211cfa512 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 6 May 2026 19:54:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(logo,menu):=20reiniciar=20des=20de=20serve?= =?UTF-8?q?i=20=E2=86=92=20LOGO=20amb=20reset=20(atura=20m=C3=BAsica)=20i?= =?UTF-8?q?=20logo=5Fjailgames.gif=20centrat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/system/director.cpp | 23 +++++++++++++++-- source/core/system/director.hpp | 1 + source/game/scenes/logo_scene.cpp | 41 ++++++++++++++----------------- source/game/scenes/logo_scene.hpp | 20 +++++++++------ source/game/scenes/scene_flow.hpp | 10 +++++++- source/game/ui/service_menu.cpp | 12 ++++++--- source/game/ui/service_menu.hpp | 20 +++++++++++++++ 7 files changed, 92 insertions(+), 35 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 2bb4c85..5add947 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -224,6 +224,13 @@ Director::Director() { auto mini_logo_surface = Resource::Cache::get().getSurface(MINI_LOGO_KEY); mini_logo_surface_ = mini_logo_surface; + // Logo sencer per a l'escena LOGO (centrat damunt fons negre). També + // conserva la seua paleta nadiua: els colors finals del logo viuen + // dins del propi GIF i no han de passar per la paleta UI. + constexpr const char* LOGO_FULL_KEY = "logo_jailgames.gif"; + Resource::Cache::get().loadSurface(LOGO_FULL_KEY, "data/logo/logo_jailgames.gif"); + logo_full_surface_ = Resource::Cache::get().getSurface(LOGO_FULL_KEY); + // Fons sencer de la TITLE (320×240). Igual que el minilogo, conserva // la seua paleta nadiua perquè el GIF ja porta els colors finals. constexpr const char* TITLE_BG_KEY = "title_bg.gif"; @@ -393,6 +400,16 @@ void Director::processMenuInput() { void Director::switchSceneIfRequested() { if (!current_scene_) { return; } + // Prioritat al menú: si un item del ServiceMenu ha encuat una + // transició (típicament "Reiniciar" → LOGO amb reset), s'aplica + // primer. Substitueix l'`ExitRequest` que pugui haver publicat + // l'escena en aquest mateix frame — l'usuari ha cancel·lat l'estat + // de joc explícitament i no ens interessa mantenir-ne la sortida. + if (auto menu_req = ServiceMenu::get().takePendingSceneRequest(); menu_req.has_value()) { + std::cout << "Director: scene transition (menu) → " << toString(menu_req->next) << '\n'; + current_scene_ = buildScene(menu_req->next, menu_req->params); + return; + } // L'escena ha publicat un `ExitRequest` dins de `exit_request_`. // Director el desempaqueta i construeix la nova escena via // `buildScene`. Si la `std::any_cast` falla (tipus inesperat), el @@ -415,8 +432,10 @@ auto Director::buildScene(SceneId id, const SceneParams& params) -> std::unique_ // ctors antics no l'utilitzen directament. Quan totes estiguen // migrades a `ExitRequest`, aquestes lambdes desapareixeran. switch (id) { - case SceneId::LOGO: - return std::make_unique(placeholder_text_); + case SceneId::LOGO: { + const auto& p = std::get(params); + return std::make_unique(logo_full_surface_, p.reset); + } case SceneId::TITLE: return std::make_unique(menu_title_text_, placeholder_text_, title_bg_surface_, mini_logo_surface_); case SceneId::INTRO: { diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index 467326b..2269b10 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -33,6 +33,7 @@ class Director { std::shared_ptr menu_title_text_; std::shared_ptr title_bg_surface_; std::shared_ptr mini_logo_surface_; + std::shared_ptr logo_full_surface_; void createSystemFolder(); void processEvents(); diff --git a/source/game/scenes/logo_scene.cpp b/source/game/scenes/logo_scene.cpp index 189fc2e..3d14719 100644 --- a/source/game/scenes/logo_scene.cpp +++ b/source/game/scenes/logo_scene.cpp @@ -5,26 +5,23 @@ #include #include #include -#include #include +#include "core/audio/audio.hpp" #include "core/input/global_inputs.hpp" #include "core/rendering/notifier.hpp" #include "core/rendering/render_info.hpp" #include "core/rendering/screen.hpp" -#include "core/rendering/text.hpp" +#include "core/rendering/surface.hpp" #include "core/system/event_buffer.hpp" #include "core/ui/ui_config.hpp" #include "game/options.hpp" #include "game/scenes/placeholder_input.hpp" #include "game/scenes/scene_flow.hpp" -#include "utils/defaults.hpp" #include "utils/defines.hpp" namespace { - constexpr int NAME_Y_OFFSET = -8; - constexpr int TIMER_Y_OFFSET = 8; // TODO: migrar a un logger unificat quan es decideixi (veure INC-03 a // docs/audit-core-rendering.md). De moment std::cout per rastrejar el // flux d'escenes als smoke runs. @@ -32,9 +29,19 @@ namespace { } // namespace -LogoScene::LogoScene(std::shared_ptr font) - : font_(std::move(font)) { - std::cout << "[scene] " << SCENE_NAME << " start\n"; +LogoScene::LogoScene(std::shared_ptr logo, bool reset) + : logo_(std::move(logo)) { + std::cout << "[scene] " << SCENE_NAME << (reset ? " start (reset)\n" : " start\n"); + // RESET = "Reiniciar" del menú: tornem al punt zero del joc, així que + // qualsevol música o so en marxa (la del joc, els sons d'UI, etc.) ha + // d'apagar-se abans que el LOGO comenci. En el flux normal no toquem + // res — la música de TITLE pot seguir sonant durant l'attract-out. + if (reset) { + if (auto* audio = Audio::get(); audio != nullptr) { + audio->stopMusic(); + audio->stopAllSounds(); + } + } } LogoScene::~LogoScene() = default; @@ -64,20 +71,10 @@ void LogoScene::render() { constexpr std::uint32_t BLACK_ARGB = 0xFF000000U; Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BLACK_ARGB); - if (font_) { - const int SECS_LEFT = std::max(1, (timer_ms_ + 999) / 1000); - font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, - GameCanvas::CENTER_X, - GameCanvas::CENTER_Y + NAME_Y_OFFSET, - SCENE_NAME, - 1, - UiConfig::get().notificationText()); - font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, - GameCanvas::CENTER_X, - GameCanvas::CENTER_Y + TIMER_Y_OFFSET, - std::to_string(SECS_LEFT), - 1, - UiConfig::get().notificationText()); + if (logo_) { + const int X = (GameCanvas::WIDTH - logo_->getWidth()) / 2; + const int Y = (GameCanvas::HEIGHT - logo_->getHeight()) / 2; + logo_->render(X, Y); } Notifier::get().render(); diff --git a/source/game/scenes/logo_scene.hpp b/source/game/scenes/logo_scene.hpp index 51fc314..4b57a95 100644 --- a/source/game/scenes/logo_scene.hpp +++ b/source/game/scenes/logo_scene.hpp @@ -4,23 +4,29 @@ #include "core/scene/scene.hpp" -class Text; +class Surface; -// Escena placeholder del logotip. Durada fixa (5 s) o skip amb qualsevol tecla -// no reservada. Quan expira, publica un `ExitRequest` cap a TITLE; Director -// és qui construeix la nova escena (l'escena no coneix els altres tipus i -// es pot exercir en tests sense arrossegar dependències). +// Escena del logotip de l'editora. Durada fixa (5 s) o skip amb qualsevol +// tecla no reservada. Quan expira, publica un `ExitRequest` cap a TITLE; +// Director és qui construeix la nova escena (l'escena no coneix els altres +// tipus i es pot exercir en tests sense arrossegar dependències). +// +// Pinta el GIF `data/logo/logo_jailgames.gif` centrat dins del canvas +// lògic, amb la seua paleta nadiua (no s'aplica la paleta UI). Si arriba +// amb `reset=true` (acció "Reiniciar" del menú de servei), atura la +// música i tots els sons al constructor — així el següent LOGO arrenca +// silenciós encara que veníssem del joc en marxa. class LogoScene : public Scene { public: static constexpr int DURATION_MS = 5000; - explicit LogoScene(std::shared_ptr font); + LogoScene(std::shared_ptr logo, bool reset); ~LogoScene() override; void update(int delta_ms) override; void render() override; private: - std::shared_ptr font_; + std::shared_ptr logo_; int timer_ms_{DURATION_MS}; }; diff --git a/source/game/scenes/scene_flow.hpp b/source/game/scenes/scene_flow.hpp index e43212c..46facd5 100644 --- a/source/game/scenes/scene_flow.hpp +++ b/source/game/scenes/scene_flow.hpp @@ -30,7 +30,15 @@ enum class SceneId : std::uint8_t { // rep cap dada de l'anterior; en cas contrari, només els camps que // fluixen pel canal de transició (els recursos, defaults i config // interna els resol Director quan construeix). -struct LogoParams {}; +struct LogoParams { + // Quan val `true`, indica que aquesta entrada al LOGO és una reinicialització + // completa (acció "Reiniciar" del menú de servei): l'escena haurà d'aturar + // música i sons abans de mostrar-se. En el flux normal LOGO→TITLE→… es + // queda a `false` perquè qualsevol música prèvia (p. ex. la pista de + // TITLE durant l'attract-out) continui sonant fins que la nova escena la + // substituïsca. + bool reset{false}; +}; struct TitleParams {}; struct IntroParams { DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH}; diff --git a/source/game/ui/service_menu.cpp b/source/game/ui/service_menu.cpp index 36b08f0..f459fbb 100644 --- a/source/game/ui/service_menu.cpp +++ b/source/game/ui/service_menu.cpp @@ -1168,12 +1168,18 @@ auto ServiceMenu::buildSystem() -> Page { page.items.push_back(makeLabel( std::string("ver.") + Texts::VERSION + " (" + Version::GIT_HASH + ")")); - // Reiniciar el joc: placeholder sense efecte fins que Director exposi - // un pas de restart en calent (fora d'abast d'aquesta iteració). + // Reiniciar el joc: tornem al LOGO amb `reset=true` perquè LogoScene + // ature música i sons abans de mostrar-se. Tanquem el menú a la + // mateixa acció — Director recollirà la transició al final del frame + // via `takePendingSceneRequest()` i reconstruirà l'escena. page.items.push_back(makeAction( "menu.items.restart", []() { - // TODO: connectar amb un hipotètic Director::requestRestart(). + ServiceMenu::get().requestSceneTransition(ExitRequest{ + .next = SceneId::LOGO, + .params = LogoParams{.reset = true}, + }); + ServiceMenu::get().close(); })); page.items.push_back(makeAction( diff --git a/source/game/ui/service_menu.hpp b/source/game/ui/service_menu.hpp index acb2943..4496a2c 100644 --- a/source/game/ui/service_menu.hpp +++ b/source/game/ui/service_menu.hpp @@ -6,11 +6,13 @@ #include #include #include +#include #include #include #include "core/input/define_buttons.hpp" #include "core/rendering/surface.hpp" // Palette +#include "game/scenes/scene_flow.hpp" class Text; @@ -73,6 +75,19 @@ class ServiceMenu { // `define_buttons_->enable(...)` de coffee_crisis_arcade_edition. void startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path); + // Bústia de transició d'escena des del menú: alguns items (p.ex. + // "Reiniciar" a Sistema) no es poden expressar com a `ExitRequest` de + // l'escena actual perquè el menú és global i no té handle a l'escena. + // Director consulta `takePendingSceneRequest()` cada frame i, si hi ha + // valor, força la transició — el mateix camí que per a `ExitRequest` + // d'una escena, però el dispara el menú directament. + void requestSceneTransition(ExitRequest req) { pending_request_ = req; } + [[nodiscard]] auto takePendingSceneRequest() -> std::optional { + auto out = pending_request_; + pending_request_.reset(); + return out; + } + private: // --- Struct d'un item d'una pàgina --- // @@ -289,4 +304,9 @@ class ServiceMenu { // persisteix la nova configuració via `GamepadBindings`. Replica el // patró `define_buttons_` de coffee_crisis_arcade_edition. DefineButtons define_buttons_; + + // Sol·licitud de canvi d'escena pendent (vore `requestSceneTransition`). + // La consumeix Director al final del frame; si l'item no força la + // transició, queda buida. + std::optional pending_request_; };