From 041447eaa4a01cd24727f0be1c3fe24777b228c0 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 11 May 2026 08:49:23 +0200 Subject: [PATCH] feat(mummy): naix en SPAWN, llig spawn.duration_s del YAML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La mòmia ara entra a l'estat SPAWN al construir-se: visible al pixel_rect_ inicial, sense moviment ni dany, esperant que algú extern (GameScene) li armi el comptador via `triggerSpawnExit()`. La durada (`spawn.duration_s` al YAML, default 0) controla quan passa a ROAMING una vegada armat el comptador. Posat a 0.6s a mummy.yaml. Aquest commit encara no enganxa el trigger: la mòmia es queda quieta tota la partida — comportament visual intermedi, esperat. El commit següent posarà el trigger a GameScene al final del text-exit de ROOM_INTRO. Tests de smoke i whip ajustats: les mòmies construïdes en stand-alone necessiten `triggerSpawnExit()` + un update gran abans d'interactuar amb `enterHurt`/contacto. Helper `exitMummySpawn` afegit a cada fitxer. Guards defensius a `enterHurt` i `onPlayerContact`: encara que el filtre central a EntityManager salta DynamicHazards en SPAWN, blindem la crida directa per a futurs camins (whip propers, scripts...). Co-Authored-By: Claude Opus 4.7 (1M context) --- data/entities/mummy/mummy.yaml | 8 ++++++++ source/game/entities/concrete/mummy.cpp | 18 ++++++++++++++++++ tests/test_entities_smoke.cpp | 13 +++++++++++++ tests/test_whip_attack.cpp | 10 ++++++++++ 4 files changed, 49 insertions(+) diff --git a/data/entities/mummy/mummy.yaml b/data/entities/mummy/mummy.yaml index ef1232d..61fba40 100644 --- a/data/entities/mummy/mummy.yaml +++ b/data/entities/mummy/mummy.yaml @@ -40,6 +40,14 @@ ai: type: corridor_wanderer direction_choice: random initial_direction: east + +# Estat SPAWN: la mòmia naix visible i quieta al pixel_rect_ inicial, +# sense col·lisió. El GameScene li arma el comptador al final del +# text-exit de ROOM_INTRO; passats `duration_s` segons des d'aleshores, +# transita a ROAMING i empieza a deambular. Sense aquest bloc, default +# 0 ⇒ al primer update post-trigger passa directament a ACTIVE. +spawn: + duration_s: 0.6 stats: speed: 30 damage: 1 diff --git a/source/game/entities/concrete/mummy.cpp b/source/game/entities/concrete/mummy.cpp index cb4d554..11d54d1 100644 --- a/source/game/entities/concrete/mummy.cpp +++ b/source/game/entities/concrete/mummy.cpp @@ -122,6 +122,17 @@ Mummy::Mummy(const fkyaml::node& yaml) changeDirection(0.0F, 1.0F); } + // Estat SPAWN inicial: la mòmia naix visible i quieta, esperant que el + // GameScene li armi el comptador via `triggerSpawnExit()` (cridat al + // final de la subfase text-exit de ROOM_INTRO). Si el YAML no declara + // `spawn.duration_s`, default 0 ⇒ al primer update post-trigger passa + // directament a ACTIVE. + float spawn_duration_s = 0.0F; + if (yaml.contains("spawn") && yaml["spawn"].contains("duration_s")) { + spawn_duration_s = yaml["spawn"]["duration_s"].get_value(); + } + enterSpawn(spawn_duration_s); + // Configuració de l'estat HURT: opcional. Si el bloc no apareix, la // mòmia no entrarà mai a HURT efectiu (defensiu); el WhipAttack la // pot ignorar amb `isWhippable() == false` si volem que no reacció. @@ -510,6 +521,9 @@ void Mummy::applyChoice(Direction dir) { } void Mummy::onPlayerContact(Player* player) { + // Defensiu: el filtre central a EntityManager::checkPlayerCollisions ja + // salta DynamicHazards en SPAWN, però blindem la crida directa. + if (isSpawning()) { return; } if (bus_ == nullptr) { return; } const int PID = (player != nullptr) ? player->getId() : -1; bus_->publish(Event{ @@ -534,6 +548,10 @@ void Mummy::onPlayerContact(Player* player) { } void Mummy::enterHurt(Direction flee_dir) { + // Defensiu: el whip no hauria de reaccionar contra una mòmia en SPAWN + // (el filtre de col·lisió la salta), però blindem per si algun camí + // futur crida directament. + if (isSpawning()) { return; } state_ = State::HURT; hurt_remaining_s_ = hurt_config_.duration_s; hurt_speed_ = (hurt_config_.speed_px_per_s > 0.0F) ? hurt_config_.speed_px_per_s : speed_; diff --git a/tests/test_entities_smoke.cpp b/tests/test_entities_smoke.cpp index 73acf4a..d94e13b 100644 --- a/tests/test_entities_smoke.cpp +++ b/tests/test_entities_smoke.cpp @@ -69,6 +69,15 @@ namespace { #define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__) + // Treu la mòmia de l'estat SPAWN inicial (Mummy::Mummy ara hi entra). + // Equival al que fa GameScene al final del text-exit de ROOM_INTRO: arma + // el comptador i consumeix la durada amb un update. Tests que operin + // directament sobre HURT/ROAMING l'han de cridar abans. + void exitMummySpawn(Mummy& mummy) { + mummy.triggerSpawnExit(); + mummy.update(mummy.getSpawnDurationS() + 0.001F); + } + void testFactory() { std::cout << "[T1] factory\n"; auto& factory = EntityFactory::instance(); @@ -442,6 +451,7 @@ namespace { // Mateixa col·locació que testMummyStep: corredor exterior // superior, lliure de sarcs, prou marge per fugir cap a la dreta. mummy.setPixelRect({.x = 60.0F, .y = 40.0F, .w = 12.0F, .h = 24.0F}); + exitMummySpawn(mummy); // Direcció preferida: dreta. Mòmia ha d'entrar en HURT i avançar // a la dreta (la velocitat de hurt és més alta que la base). @@ -474,6 +484,7 @@ namespace { // la paret i fer-la sortir de HURT. const float RIGHT_WALL = static_cast(Map::playfield_x + Map::playfield_w); mummy.setPixelRect({.x = RIGHT_WALL - 12.0F, .y = 40.0F, .w = 12.0F, .h = 24.0F}); + exitMummySpawn(mummy); // La direcció preferida (RIGHT) està bloquejada al test // d'`is_open`; entrarem amb una perpendicular o quieta. @@ -517,6 +528,7 @@ namespace { // expiri el timer (60 px/s · 0.6 s = 36 px de runway). mummy.setPixelRect({.x = 40.0F, .y = 160.0F, .w = 16.0F, .h = 32.0F}); REQUIRE(mummy.getCollisionRect().x == 40.0F); + exitMummySpawn(mummy); mummy.enterHurt(Mummy::Direction::WEST); REQUIRE(mummy.getState() == Mummy::State::HURT); @@ -571,6 +583,7 @@ namespace { mummy.setCollisionBox({.offset_x = 0, .offset_y = 16, .width = 16, .height = 16}); // Mateix runway que T18. mummy.setPixelRect({.x = 40.0F, .y = 160.0F, .w = 16.0F, .h = 32.0F}); + exitMummySpawn(mummy); mummy.enterHurt(Mummy::Direction::WEST); for (int i = 0; i < 200 && mummy.getState() == Mummy::State::HURT; ++i) { diff --git a/tests/test_whip_attack.cpp b/tests/test_whip_attack.cpp index 9b25b3d..7aef493 100644 --- a/tests/test_whip_attack.cpp +++ b/tests/test_whip_attack.cpp @@ -32,6 +32,14 @@ namespace { #define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__) + // Treu la mòmia de l'estat SPAWN inicial (Mummy::Mummy ara hi entra). Els + // tests del whip necessiten que la mòmia estigui ACTIVE per reaccionar + // al fueteig — sense això, `enterHurt` torna early per `isSpawning()`. + void exitMummySpawn(Mummy& mummy) { + mummy.triggerSpawnExit(); + mummy.update(mummy.getSpawnDurationS() + 0.001F); + } + // Caja de jugador "estàndard" AEE: 16×32 amb cantonada (x, y). // El damage_rect del whip s'alinearà al feet box (els últims 16 px), // i el draw_rect respectarà els offsets de la taula del disseny. @@ -200,6 +208,7 @@ namespace { // = OY, la collision queda a [OY+12, OY+24], 8 px d'overlap. mummy_owned->setPixelRect({.x = OX + 24.0F, .y = OY, .w = 12.0F, .h = 24.0F}); auto* mummy_raw = static_cast(manager.add(std::move(mummy_owned))); + exitMummySpawn(*mummy_raw); auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F); whip->setWorldContext(&manager, nullptr); @@ -258,6 +267,7 @@ namespace { // = OY, la collision queda a [OY+12, OY+24], 8 px d'overlap. mummy_owned->setPixelRect({.x = OX + 24.0F, .y = OY, .w = 12.0F, .h = 24.0F}); auto* mummy_raw = static_cast(manager.add(std::move(mummy_owned))); + exitMummySpawn(*mummy_raw); auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F); whip->setWorldContext(&manager, nullptr);