diff --git a/data/music/playing.ogg b/data/music/playing.ogg new file mode 100644 index 0000000..5808941 Binary files /dev/null and b/data/music/playing.ogg differ diff --git a/data/music/room_intro.ogg b/data/music/room_intro.ogg new file mode 100644 index 0000000..f4bf4e2 Binary files /dev/null and b/data/music/room_intro.ogg differ diff --git a/data/music/room_outro.ogg b/data/music/room_outro.ogg new file mode 100644 index 0000000..8b3080d Binary files /dev/null and b/data/music/room_outro.ogg differ diff --git a/data/scenes/game.yaml b/data/scenes/game.yaml index 0f9e04d..285d25b 100644 --- a/data/scenes/game.yaml +++ b/data/scenes/game.yaml @@ -11,18 +11,17 @@ room_intro: text_hold_ms: 2000 # text quiet al centre text_exit_ms: 400 # text centre → fora per l'esquerra line_gap_px: -4 # px extra entre les dues línies (piràmide / sala) - player_spawn_delay_ms: 2400 # ms des de l'inici d'INTRO al spawn aeri. - player2_spawn_delay_ms: 2700 # només mode BOTH. Trenca la simultaneïtat. + # Delays d'spawn aeri **mesurats des de la fi de la música d'INTRO** + # (callback `Audio::setOnMusicEnded`). 0 = cau al moment exacte que + # `room_intro.ogg` acaba; valors > 0 escalonen els jugadors. En mode + # single-player s'usa només `player_spawn_delay_ms`. + player_spawn_delay_ms: 0 + player2_spawn_delay_ms: 300 # Preset de reverb (catàleg a `data/sounds.yaml`) aplicat al primer # LAND de cada jugador durant la cinemàtica d'entrada. La sala dona # "més detall" sonor a l'INTRO; els salts del joc i els respawns # post-mort cauen al so sec. Buit o absent = sense reverb. land_reverb: stone_hall - # So puntual disparat quan apareix el text d'INTRO (phase 2, - # text_enter). Es resol contra `data/sounds/`. Buit o - # absent = sense so. Es preloda al ctor de GameScene perquè el - # primer dispar no faça disc. - text_sound: intro_room.wav room_outro: text_enter_ms: 400 diff --git a/data/sounds/hurt.wav b/data/sounds/hurt.wav new file mode 100644 index 0000000..9c0ebd6 Binary files /dev/null and b/data/sounds/hurt.wav differ diff --git a/data/sounds/intro_room.wav b/data/sounds/intro_room.wav deleted file mode 100644 index 09556bf..0000000 Binary files a/data/sounds/intro_room.wav and /dev/null differ diff --git a/data/sounds/whip.wav b/data/sounds/whip.wav new file mode 100644 index 0000000..a1e7fb4 Binary files /dev/null and b/data/sounds/whip.wav differ diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 23da3ca..9ebc48d 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -132,9 +132,12 @@ Director::Director() { Resource::Cache::get().loadSurface(ROOM_TEXT_FONT_GIF_KEY, UiConfig::get().roomTextFont().gif); Resource::Cache::get().getSurface(ROOM_TEXT_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui")); - // Música de la piràmide 1 (primer i únic track de moment). Carreguem ací - // perquè forma part del preload d'assets, com la font i la paleta. - Resource::Cache::get().loadMusic("pyramid_1.ogg", "data/music/pyramid_1.ogg"); + // Pistes de música del cicle de sala (intro/playing/outro). Es preloden + // ací perquè formen part del preload d'assets, com la font i la paleta; + // la lògica de quina sona en cada estat viu a `GameScene`. + Resource::Cache::get().loadMusic("room_intro.ogg", "data/music/room_intro.ogg"); + Resource::Cache::get().loadMusic("playing.ogg", "data/music/playing.ogg"); + Resource::Cache::get().loadMusic("room_outro.ogg", "data/music/room_outro.ogg"); // Catàleg de presets d'efectes per als sons. Es carrega abans d'Audio // perquè els primers playSoundWith* (al començament del joc) ja diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index f6c77ac..5a790f0 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -378,11 +378,10 @@ GameScene::GameScene(Config config) players_.emplace_back(std::make_unique(ANIM_P1, ID_P1, p1_x, SPAWN.y, CONTROLS_P1, Map::makeInfo(), P1_PAINT)); players_.emplace_back(std::make_unique(ANIM_P2, ID_P2, p2_x, SPAWN.y, CONTROLS_P2, Map::makeInfo(), P2_PAINT)); - // Tots els slots arrenquen DISABLED. Els actius els reactivarà - // `updateRoomTransition` quan s'esgote el delay corresponent dins - // de ROOM_INTRO (`player_spawn_delay_ms` per al primer, i - // `player2_spawn_delay_ms` per al segon en mode BOTH). Els inactius - // es queden DISABLED tot el cicle de sala. + // Tots els slots arrenquen DISABLED. Els actius els reactivarà el + // callback de fi de música d'INTRO via `completeIntro()` (spawn aeri + // simultani) en arribar a la fi de la pista `room_intro.ogg`. Els + // inactius es queden DISABLED tot el cicle de sala. for (auto& p : players_) { p->disable(); } // PaintLayer: font única del pintat del nivell. Es crea després de @@ -465,21 +464,12 @@ GameScene::GameScene(Config config) // vegada; canvis requereixen reiniciar el joc. transition_config_ = RoomFsm::loadConfig(Defaults::GameScene::CONFIG_YAML); - // Preload del so del rètol d'INTRO (no hi ha manifest de sons; el - // pack agafa tot `data/` automàticament i ací registrem la clau - // al Resource::Cache perquè Audio::playSound la trobe). Si el camp - // del YAML està buit, no carreguem res — el dispar consultarà el - // cache i caurà al silenci sense crash. - if (!transition_config_.intro_text_sound.empty()) { - Resource::Cache::get().loadSound( - transition_config_.intro_text_sound, - "data/sounds/" + transition_config_.intro_text_sound); - } - - // Log d'estat inicial: la FSM arrenca a ROOM_INTRO. En aquesta fita - // les transicions són instantànies (sense fades ni text) i el primer - // tick d'`updateRoomTransition` ja saltarà a PLAYING. - std::cout << "[ROOM] state=ROOM_INTRO (initial)" << '\n'; + // Cinemàtica d'entrada de la primera sala: arrenca la pista + // `room_intro.ogg` i arma el callback de fi que farà spawn + salt + // a PLAYING. Cal cridar `enterRoomIntro` ací (i no només des + // d'`advanceToNextRoom`) perquè la primera entrada a la GameScene + // no passa pel flux d'avanç de sala. + enterRoomIntro(); } GameScene::~GameScene() = default; @@ -563,19 +553,41 @@ void GameScene::enterRoomIntro() { } state_ = RoomFsm::State::ROOM_INTRO; transition_ = {}; - room_intro_elapsed_ms_ = 0; + intro_completed_ = false; + intro_spawn_elapsed_ms_ = 0; players_spawned_in_intro_.fill(false); - intro_text_sound_played_ = false; - // La cinemàtica d'entrada és silenciosa per disseny: és INTRO qui - // posseeix l'aturada. L'OUTRO no toca la música — així el cicle - // OUTRO → INTRO transmet la sensació de "tall" en entrar a la sala - // nova, no de "fade-out" anterior. Si ja no sona res, stopMusic és - // no-op. - if (Audio::get() != nullptr) { Audio::get()->stopMusic(); } + outro_music_ended_ = false; + + // INTRO arrenca amb la pista `room_intro.ogg` (loop=0, una sola + // passada). El callback de fi de música és l'únic responsable de + // disparar el spawn dels jugadors i el salt a PLAYING — la durada + // real ve marcada per la pista, no per cap timer numèric. + auto* audio = Audio::get(); + if (audio == nullptr) { + // Sense Audio inicialitzat, no podem esperar cap callback. + // Completem síncronament perquè la sala no quede bloquejada. + std::cout << "[ROOM] INTRO: Audio absent — completant síncronament\n"; + completeIntro(); + return; + } + audio->stopMusic(); + audio->setOnMusicEnded([this]() { completeIntro(); }); + audio->playMusic("room_intro.ogg", 0); + if (Audio::getMusicState() != Audio::MusicState::PLAYING) { + // Fallback: la pista no s'ha pogut arrencar (resource absent, + // música deshabilitada, etc.). Netegem el callback per no + // deixar-lo penjat i fem el spawn + transició com si el track + // hagués acabat ja. Així cap fallada d'asset deixa la sala + // congelada al fons negre de l'INTRO. + std::cout << "[ROOM] INTRO: música no arrencada — completant síncronament\n"; + audio->setOnMusicEnded({}); + completeIntro(); + } } void GameScene::triggerPlayerSpawn(std::size_t slot_index) { - if (slot_index >= players_spawned_in_intro_.size()) { return; } + if (slot_index >= players_.size()) { return; } + if (!players_starts_active_[slot_index]) { return; } if (players_spawned_in_intro_[slot_index]) { return; } players_spawned_in_intro_[slot_index] = true; @@ -600,23 +612,76 @@ void GameScene::triggerPlayerSpawn(std::size_t slot_index) { std::cout << "[ROOM] spawn cinematic P" << (slot_index + 1) << ": airborne entry\n"; } +void GameScene::completeIntro() { + if (intro_completed_) { return; } + intro_completed_ = true; + intro_spawn_elapsed_ms_ = 0; + // Spawn dels slots amb delay <= 0 (el cas habitual de P1). Els + // altres cauen frame a frame dins `updateRoomTransition` a mesura + // que `intro_spawn_elapsed_ms_` supera el seu delay. Així P1 cau + // alhora que la música acaba i la transició a PLAYING; P2 (en + // mode BOTH) entra uns frames més tard amb el seu offset propi. + spawnDuePlayers(); + setState(RoomFsm::State::PLAYING); +} + +void GameScene::spawnDuePlayers() { + if (!intro_completed_) { return; } + const bool BOTH_ACTIVE = players_starts_active_[0] && players_starts_active_[1]; + for (std::size_t i = 0; i < players_starts_active_.size(); ++i) { + if (!players_starts_active_[i]) { continue; } + if (players_spawned_in_intro_[i]) { continue; } + // Single-player: el slot actiu (P1 o P2) sempre fa servir + // `player_spawn_delay_ms`. Mode BOTH: P1 fa player_spawn_delay_ms, + // P2 fa player2_spawn_delay_ms (offset respecte P1). + const int DELAY = (i == 1 && BOTH_ACTIVE) + ? transition_config_.player2_spawn_delay_ms + : transition_config_.player_spawn_delay_ms; + if (intro_spawn_elapsed_ms_ >= DELAY) { triggerPlayerSpawn(i); } + } +} + void GameScene::setState(RoomFsm::State new_state) { if (new_state == state_) { return; } std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n'; state_ = new_state; transition_ = {}; - // En entrar a un nou cicle d'INTRO, resetegem el timer i el flag de - // l'spawn aeri perquè es torne a disparar al delay configurat. + // Reset defensiu dels flags de cicle quan tornem a INTRO via aquest + // camí (no canònic — `enterRoomIntro` és el punt d'entrada normal). if (new_state == RoomFsm::State::ROOM_INTRO) { - room_intro_elapsed_ms_ = 0; + intro_completed_ = false; + intro_spawn_elapsed_ms_ = 0; players_spawned_in_intro_.fill(false); - intro_text_sound_played_ = false; + outro_music_ended_ = false; } - // En sortir d'INTRO cap a PLAYING, la música arrenca: és el moment - // en què la cinemàtica d'entrada acaba i el jugador agafa control. - // L'OUTRO no atura la música; ho fa el següent `enterRoomIntro`. - if (new_state == RoomFsm::State::PLAYING && Audio::get() != nullptr) { - Audio::get()->playMusic(config_.music_track, -1); + auto* audio = Audio::get(); + // PLAYING: arrenca la pista de joc en loop infinit. Netegem el + // callback de fi (residual d'INTRO, ja consumit) perquè un futur + // stopMusic no dispari lògica obsoleta. + if (new_state == RoomFsm::State::PLAYING && audio != nullptr) { + audio->setOnMusicEnded({}); + audio->playMusic(config_.music_track, -1); + } + // ROOM_OUTRO: corte sec de `playing.ogg`, arrenca `room_outro.ogg` + // (loop=0). El callback de fi marca `outro_music_ended_ = true`, + // que junt amb el final del text_exit gata el fade visual a negre + // dins `updateRoomTransition`. Si la música no arrenca, marquem el + // flag immediatament perquè el fade quedi gata només contra el text. + if (new_state == RoomFsm::State::ROOM_OUTRO) { + outro_music_ended_ = false; + if (audio == nullptr) { + std::cout << "[ROOM] OUTRO: Audio absent — fade gata només contra text\n"; + outro_music_ended_ = true; + } else { + audio->stopMusic(); + audio->setOnMusicEnded([this]() { outro_music_ended_ = true; }); + audio->playMusic("room_outro.ogg", 0); + if (Audio::getMusicState() != Audio::MusicState::PLAYING) { + std::cout << "[ROOM] OUTRO: música no arrencada — fade gata només contra text\n"; + audio->setOnMusicEnded({}); + outro_music_ended_ = true; + } + } } // L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara // `updateRoomTransition` quan la FSM detecta el final de l'OUTRO, @@ -627,43 +692,38 @@ void GameScene::setState(RoomFsm::State new_state) { } void GameScene::updateRoomTransition(int delta_ms) { - // Disparador d'spawn aeri: timer propi (independent de les fases de - // la FSM) que arrenca a l'entrada de ROOM_INTRO. La separació deixa - // el delay desacoblat del fade-in i, demà, reutilitzable per al - // respawn post-mort sense que la FSM de sala hi haja d'intervenir. - if (state_ == RoomFsm::State::ROOM_INTRO) { - room_intro_elapsed_ms_ += delta_ms; - // Cada slot actiu pot caure en un moment distint quan els dos - // estan actius (mode BOTH): P1 amb player_spawn_delay_ms i P2 - // amb player2_spawn_delay_ms. En single-player (P1 o P2 sols) - // l'slot actiu fa servir player_spawn_delay_ms — el segon valor - // queda inactiu, com ha demanat el disseny. - const bool BOTH_ACTIVE = players_starts_active_[0] && players_starts_active_[1]; - for (std::size_t i = 0; i < players_starts_active_.size(); ++i) { - if (!players_starts_active_[i]) { continue; } - if (players_spawned_in_intro_[i]) { continue; } - const int DELAY = (i == 1 && BOTH_ACTIVE) - ? transition_config_.player2_spawn_delay_ms - : transition_config_.player_spawn_delay_ms; - if (room_intro_elapsed_ms_ >= DELAY) { - triggerPlayerSpawn(i); - } - } + // Tick del timer d'spawn aeri escalonat. Comença al callback de fi + // de música d'INTRO (`completeIntro` posa intro_completed_=true) i + // segueix corrent cap a PLAYING fins que tots els slots actius hagin + // caigut. Cap cost extra un cop tots han caigut (`spawnDuePlayers` + // és no-op llavors). + if (intro_completed_) { + intro_spawn_elapsed_ms_ += delta_ms; + spawnDuePlayers(); } const auto NEXT = RoomFsm::advance(state_, transition_, transition_config_, delta_ms); - // Disparador del so del rètol: la FSM acaba d'avançar; si estem a - // INTRO i ja hem deixat enrere phase 0 (fade_in), reproduïm el so - // una sola vegada. El so es dispara al final del fade-in, anticipant - // el text que apareix a phase 2 (text_enter): així sembla que el so - // "anuncia" l'arribada del rètol enlloc de coincidir amb la seva - // entrada visual. - if (state_ == RoomFsm::State::ROOM_INTRO && transition_.phase >= 1 && !intro_text_sound_played_ && !transition_config_.intro_text_sound.empty() && Audio::get() != nullptr) { - Audio::get()->playSound(transition_config_.intro_text_sound, Audio::Group::GAME); - intro_text_sound_played_ = true; + // OUTRO: gate del fade visual sobre la doble condició (música + // acabada AND text fora). Si la FSM ha avançat a phase 3 (fade_out) + // pel timer del text però el callback de fi de música encara no + // ha disparat, congelem el comptador a 0 perquè el fade no comence + // a pintar el negre. Quan el callback alliberi `outro_music_ended_`, + // les pròximes crides a `advance` tornaran a acumular delta_ms i + // el fade arrencarà des de zero, polit. + if (state_ == RoomFsm::State::ROOM_OUTRO && + transition_.phase == 3 && + !outro_music_ended_) { + transition_.elapsed_ms = 0; } + if (NEXT != state_) { + // INTRO → PLAYING per timer: ignorat. El callback de fi de + // música d'INTRO és l'únic responsable d'aquesta transició. + if (state_ == RoomFsm::State::ROOM_INTRO && + NEXT == RoomFsm::State::PLAYING) { + return; + } // El final de l'OUTRO no es resol amb un setState normal: cal // demanar al LevelManager el següent nivell i carregar-lo abans // d'entrar a INTRO. `advanceToNextRoom` fa tot el bloc i acaba diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index d054eba..f3d7efe 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -104,30 +104,52 @@ class GameScene : public Scene { // queda DISABLED tot el cicle de sala. std::array players_starts_active_{false, false}; - // Mil·lisegons des de l'entrada en ROOM_INTRO, per disparar la - // cinemàtica de spawn aeri quan s'arriba al delay corresponent al - // slot. Es reseteja a `setState(ROOM_INTRO)`. Cada slot té el seu - // propi flag de "ja he caigut" — així podem caure en moments - // distints en mode BOTH (`player_spawn_delay_ms` per a P1 i - // `player2_spawn_delay_ms` per a P2). - int room_intro_elapsed_ms_{0}; - std::array players_spawned_in_intro_{false, false}; - - // One-shot del so del rètol d'INTRO (`text_sound` a game.yaml). - // Es disparen quan la FSM passa a phase 2 (text_enter); el flag - // evita repeticions si phase 2 s'estén multi-frame. Es reseteja a - // `enterRoomIntro` / `setState(ROOM_INTRO)`. Si la clau del so és - // buida o el cache no l'ha carregat, queda inutilitzat. - bool intro_text_sound_played_{false}; - // Cinemàtica de spawn d'un slot concret. Posa el slot indicat en // AIRBORNE des d'una altura fora de pantalla. Cridat per - // `updateRoomTransition` quan `room_intro_elapsed_ms_` supera el - // delay específic de l'slot. Idempotent (consulta el flag corresponent - // a `players_spawned_in_intro_`). També és el punt al qual es - // connectarà el respawn post-mort en el futur. + // `spawnDuePlayers` quan el timer post-callback supera el delay + // del slot. Idempotent (consulta `players_spawned_in_intro_`). + // També és el punt al qual es connectarà el respawn post-mort en + // el futur. void triggerPlayerSpawn(std::size_t slot_index); + // Cos del callback de fi de música d'INTRO. Idempotent: si ja s'ha + // disparat aquest cicle (`intro_completed_`), no fa res. Marca el + // flag, reseteja el timer post-callback i intenta spawn dels slots + // actius amb delay <= 0 ara mateix; els altres cauen frame a frame + // dins `updateRoomTransition` quan el timer els assoleix. Finalment + // commuta a PLAYING — la música del joc arrenca amb el primer slot. + void completeIntro(); + + // Itera els slots actius i, si el delay configurat ha expirat des + // del callback de fi de música d'INTRO, dispara `triggerPlayerSpawn`. + // Cridada cada frame durant la fase d'spawn escalonat (entre el + // callback i la caiguda de l'últim slot). No fa res si ja han + // caigut tots o si encara no estem dins la finestra (intro_completed_). + void spawnDuePlayers(); + + // Hi ha un únic slot global de callback de fi de música a `Ja::Engine`. + // Cada estat (INTRO, OUTRO) ha de registrar el seu en entrar i pot + // sobreescriure'l amb el del següent estat. Aquests flags marquen + // si el callback corresponent ja ha disparat: són la frontera entre + // el thread d'audio (que invoca el callback dins `Engine::update`) + // i la lògica de transició (`updateRoomTransition`). + bool intro_completed_{false}; + bool outro_music_ended_{false}; + + // Mil·lisegons acumulats des del callback de fi de música d'INTRO, + // per gatejar l'spawn aeri escalonat dels slots actius. P1 fa + // servir `player_spawn_delay_ms` (típicament 0); P2 en mode BOTH + // fa servir `player2_spawn_delay_ms` (offset respecte P1). Es + // reseteja a 0 dins `completeIntro` i no torna a córrer fins al + // següent cicle d'INTRO. + int intro_spawn_elapsed_ms_{0}; + + // Per slot: ja ha caigut aquest cicle d'INTRO? Garanteix + // idempotència de `triggerPlayerSpawn` davant múltiples ticks del + // timer mentre el delay no s'ha mogut. Es reseteja a `enterRoomIntro` + // i a `setState(ROOM_INTRO)` (camí defensiu no canònic). + std::array players_spawned_in_intro_{false, false}; + // Vista plana de punters crus — la fa servir Player com a llista // d'obstacles mòbils a la callback de col·lisió. Es construeix un sol // cop al ctor (els punters són estables perquè `players_` no es @@ -252,10 +274,11 @@ class GameScene : public Scene { void advanceToNextRoom(); // Inicia (o reinicia) el cicle d'entrada de sala: posa l'estat a - // ROOM_INTRO i reseteja `transition_`, `room_intro_elapsed_ms_` i el - // flag d'spawn aeri. Idempotent quan ja s'estava a INTRO (els - // timers tornen a començar des de zero — útil per als salts debug - // que recarreguen la sala estant ja en INTRO). + // ROOM_INTRO, reseteja `transition_` i `intro_completed_`, i comença + // la pista `room_intro.ogg` amb un callback de fi que dispararà + // `completeIntro()`. Si la càrrega de la música falla, dispara + // `completeIntro()` síncronament perquè la sala no quedi bloquejada. + // Idempotent quan ja s'estava a INTRO — útil per als salts debug. void enterRoomIntro(); #ifdef _DEBUG diff --git a/source/game/scenes/room_fsm.cpp b/source/game/scenes/room_fsm.cpp index 4d0ee0c..0b980f8 100644 --- a/source/game/scenes/room_fsm.cpp +++ b/source/game/scenes/room_fsm.cpp @@ -50,9 +50,6 @@ namespace RoomFsm { if (n.contains("land_reverb")) { cfg.intro_land_reverb_preset = n["land_reverb"].get_value(); } - if (n.contains("text_sound")) { - cfg.intro_text_sound = n["text_sound"].get_value(); - } } if (yaml.contains("room_outro")) { const auto& n = yaml["room_outro"]; diff --git a/source/game/scenes/room_fsm.hpp b/source/game/scenes/room_fsm.hpp index d15988c..f2d5069 100644 --- a/source/game/scenes/room_fsm.hpp +++ b/source/game/scenes/room_fsm.hpp @@ -47,23 +47,19 @@ namespace RoomFsm { // sense recompilar — útil per donar respir abans del nou rètol. int outro_black_hold_ms{0}; - // Mil·lisegons des de l'inici de ROOM_INTRO en què cau la - // cinemàtica d'spawn aeri dels jugadors actius. Específic per a - // l'entrada de sala — el respawn post-mort tindrà el seu propi - // timing quan toqui dissenyar-lo. Un valor habitual: la suma de - // fade_in + text_delay + text_enter + text_hold, perquè els - // players caiguin just quan el rètol comença a sortir. El - // fallback compilat (500 ms) deixa el spawn al final del fade-in - // si el YAML no defineix res — només pensat com a degradació. - int player_spawn_delay_ms{500}; + // Mil·lisegons des del callback de fi de la música d'INTRO + // (`room_intro.ogg`) en què cau l'spawn aeri del primer slot + // actiu. 0 = cau al moment exacte que la música acaba. El + // respawn post-mort tindrà el seu propi timing quan toqui + // dissenyar-lo. + int player_spawn_delay_ms{0}; // Delay independent per al segon jugador, **només quan els dos // slots estan actius** (mode BOTH). En single-player (P1 o P2 - // sols) s'usa sempre `player_spawn_delay_ms` per al jugador - // actiu. Si el YAML no fixa aquest valor, el `loadConfig` el - // copia de `player_spawn_delay_ms` perquè els dos players caiguin - // alhora — comportament històric del joc. - int player2_spawn_delay_ms{500}; + // sols) s'usa sempre `player_spawn_delay_ms`. Si el YAML no + // fixa aquest valor, `loadConfig` el copia de `player_spawn_delay_ms` + // perquè els dos players caiguin alhora — comportament neutre. + int player2_spawn_delay_ms{0}; // Píxels extra entre les dues línies del text d'INTRO (piràmide // damunt, sala davall). Es sumen al `lineHeight` natural del @@ -87,13 +83,6 @@ namespace RoomFsm { // cinemàtica d'entrada sona igual que un salt qualsevol). std::string intro_land_reverb_preset; - // Clau d'un so puntual (data/sounds/) que GameScene - // dispara just quan la cinemàtica d'INTRO arriba a la fase - // d'entrada del text (phase == 2). Buit o absent = sense so. - // Convenció Resource::Cache: el basename val tant de clau com - // de path (loadSound ho fa servir igual que el land_sound). - std::string intro_text_sound; - // Panel translúcid sota el rètol de transició (INTRO i OUTRO). // Centrat verticalment al centre del playfield, ocupa tota // l'amplada del canvas. Creix d'altura 0 → height_px durant la diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 7f03f7f..f182fa1 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -260,7 +260,7 @@ namespace Defaults { // així que Player::loadFrom només cal cridar-la amb un dels dos YAMLs. constexpr const char* PLAYER_SPRITE_P2 = "data/player/morcus.gif"; constexpr const char* PLAYER_YAML_P2 = "data/player/morcus.yaml"; - constexpr const char* MUSIC_TRACK = "pyramid_1.ogg"; + constexpr const char* MUSIC_TRACK = "playing.ogg"; } // namespace Pyramid } // namespace Defaults