feat(logo,menu): reiniciar des de servei → LOGO amb reset (atura música) i logo_jailgames.gif centrat

This commit is contained in:
JailDesigner committed 2026-05-06 19:54:30 +02:00
1 parent 8eaa91f309
commit f7ab6a641a
7 files changed
+92 -35

No files matched your search

+21 -2
View File
@@ -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<LogoScene>(placeholder_text_);
case SceneId::LOGO: {
const auto& p = std::get<LogoParams>(params);
return std::make_unique<LogoScene>(logo_full_surface_, p.reset);
}
case SceneId::TITLE:
return std::make_unique<TitleScene>(menu_title_text_, placeholder_text_, title_bg_surface_, mini_logo_surface_);
case SceneId::INTRO: {
+1
View File
@@ -33,6 +33,7 @@ class Director {
std::shared_ptr<Text> menu_title_text_;
std::shared_ptr<Surface> title_bg_surface_;
std::shared_ptr<Surface> mini_logo_surface_;
std::shared_ptr<Surface> logo_full_surface_;
void createSystemFolder();
void processEvents();
+19 -22
View File
@@ -5,26 +5,23 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#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<Text> font)
: font_(std::move(font)) {
std::cout << "[scene] " << SCENE_NAME << " start\n";
LogoScene::LogoScene(std::shared_ptr<Surface> 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();
+13 -7
View File
@@ -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<Text> font);
LogoScene(std::shared_ptr<Surface> logo, bool reset);
~LogoScene() override;
void update(int delta_ms) override;
void render() override;
private:
std::shared_ptr<Text> font_;
std::shared_ptr<Surface> logo_;
int timer_ms_{DURATION_MS};
};
+9 -1
View File
@@ -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};
+9 -3
View File
@@ -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(
+20
View File
@@ -6,11 +6,13 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#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<ExitRequest> {
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<ExitRequest> pending_request_;
};