feat(mummy): naix en SPAWN, llig spawn.duration_s del YAML
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) <noreply@anthropic.com>
This commit is contained in:
1 parent
a6b2b02930
commit
041447eaa4
4 files changed
+49
No files matched your search
@@ -40,6 +40,14 @@ ai:
|
|||||||
type: corridor_wanderer
|
type: corridor_wanderer
|
||||||
direction_choice: random
|
direction_choice: random
|
||||||
initial_direction: east
|
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:
|
stats:
|
||||||
speed: 30
|
speed: 30
|
||||||
damage: 1
|
damage: 1
|
||||||
|
|||||||
@@ -122,6 +122,17 @@ Mummy::Mummy(const fkyaml::node& yaml)
|
|||||||
changeDirection(0.0F, 1.0F);
|
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<float>();
|
||||||
|
}
|
||||||
|
enterSpawn(spawn_duration_s);
|
||||||
|
|
||||||
// Configuració de l'estat HURT: opcional. Si el bloc no apareix, la
|
// Configuració de l'estat HURT: opcional. Si el bloc no apareix, la
|
||||||
// mòmia no entrarà mai a HURT efectiu (defensiu); el WhipAttack la
|
// mòmia no entrarà mai a HURT efectiu (defensiu); el WhipAttack la
|
||||||
// pot ignorar amb `isWhippable() == false` si volem que no reacció.
|
// pot ignorar amb `isWhippable() == false` si volem que no reacció.
|
||||||
@@ -510,6 +521,9 @@ void Mummy::applyChoice(Direction dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::onPlayerContact(Player* player) {
|
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; }
|
if (bus_ == nullptr) { return; }
|
||||||
const int PID = (player != nullptr) ? player->getId() : -1;
|
const int PID = (player != nullptr) ? player->getId() : -1;
|
||||||
bus_->publish(Event{
|
bus_->publish(Event{
|
||||||
@@ -534,6 +548,10 @@ void Mummy::onPlayerContact(Player* player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::enterHurt(Direction flee_dir) {
|
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;
|
state_ = State::HURT;
|
||||||
hurt_remaining_s_ = hurt_config_.duration_s;
|
hurt_remaining_s_ = hurt_config_.duration_s;
|
||||||
hurt_speed_ = (hurt_config_.speed_px_per_s > 0.0F) ? hurt_config_.speed_px_per_s : speed_;
|
hurt_speed_ = (hurt_config_.speed_px_per_s > 0.0F) ? hurt_config_.speed_px_per_s : speed_;
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ namespace {
|
|||||||
|
|
||||||
#define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__)
|
#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() {
|
void testFactory() {
|
||||||
std::cout << "[T1] factory\n";
|
std::cout << "[T1] factory\n";
|
||||||
auto& factory = EntityFactory::instance();
|
auto& factory = EntityFactory::instance();
|
||||||
@@ -442,6 +451,7 @@ namespace {
|
|||||||
// Mateixa col·locació que testMummyStep: corredor exterior
|
// Mateixa col·locació que testMummyStep: corredor exterior
|
||||||
// superior, lliure de sarcs, prou marge per fugir cap a la dreta.
|
// 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});
|
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
|
// 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).
|
// 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.
|
// la paret i fer-la sortir de HURT.
|
||||||
const float RIGHT_WALL = static_cast<float>(Map::playfield_x + Map::playfield_w);
|
const float RIGHT_WALL = static_cast<float>(Map::playfield_x + Map::playfield_w);
|
||||||
mummy.setPixelRect({.x = RIGHT_WALL - 12.0F, .y = 40.0F, .w = 12.0F, .h = 24.0F});
|
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
|
// La direcció preferida (RIGHT) està bloquejada al test
|
||||||
// d'`is_open`; entrarem amb una perpendicular o quieta.
|
// 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).
|
// 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});
|
mummy.setPixelRect({.x = 40.0F, .y = 160.0F, .w = 16.0F, .h = 32.0F});
|
||||||
REQUIRE(mummy.getCollisionRect().x == 40.0F);
|
REQUIRE(mummy.getCollisionRect().x == 40.0F);
|
||||||
|
exitMummySpawn(mummy);
|
||||||
|
|
||||||
mummy.enterHurt(Mummy::Direction::WEST);
|
mummy.enterHurt(Mummy::Direction::WEST);
|
||||||
REQUIRE(mummy.getState() == Mummy::State::HURT);
|
REQUIRE(mummy.getState() == Mummy::State::HURT);
|
||||||
@@ -571,6 +583,7 @@ namespace {
|
|||||||
mummy.setCollisionBox({.offset_x = 0, .offset_y = 16, .width = 16, .height = 16});
|
mummy.setCollisionBox({.offset_x = 0, .offset_y = 16, .width = 16, .height = 16});
|
||||||
// Mateix runway que T18.
|
// Mateix runway que T18.
|
||||||
mummy.setPixelRect({.x = 40.0F, .y = 160.0F, .w = 16.0F, .h = 32.0F});
|
mummy.setPixelRect({.x = 40.0F, .y = 160.0F, .w = 16.0F, .h = 32.0F});
|
||||||
|
exitMummySpawn(mummy);
|
||||||
|
|
||||||
mummy.enterHurt(Mummy::Direction::WEST);
|
mummy.enterHurt(Mummy::Direction::WEST);
|
||||||
for (int i = 0; i < 200 && mummy.getState() == Mummy::State::HURT; ++i) {
|
for (int i = 0; i < 200 && mummy.getState() == Mummy::State::HURT; ++i) {
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ namespace {
|
|||||||
|
|
||||||
#define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__)
|
#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).
|
// 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),
|
// 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.
|
// 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.
|
// = 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});
|
mummy_owned->setPixelRect({.x = OX + 24.0F, .y = OY, .w = 12.0F, .h = 24.0F});
|
||||||
auto* mummy_raw = static_cast<Mummy*>(manager.add(std::move(mummy_owned)));
|
auto* mummy_raw = static_cast<Mummy*>(manager.add(std::move(mummy_owned)));
|
||||||
|
exitMummySpawn(*mummy_raw);
|
||||||
|
|
||||||
auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F);
|
auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F);
|
||||||
whip->setWorldContext(&manager, nullptr);
|
whip->setWorldContext(&manager, nullptr);
|
||||||
@@ -258,6 +267,7 @@ namespace {
|
|||||||
// = OY, la collision queda a [OY+12, OY+24], 8 px d'overlap.
|
// = 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});
|
mummy_owned->setPixelRect({.x = OX + 24.0F, .y = OY, .w = 12.0F, .h = 24.0F});
|
||||||
auto* mummy_raw = static_cast<Mummy*>(manager.add(std::move(mummy_owned)));
|
auto* mummy_raw = static_cast<Mummy*>(manager.add(std::move(mummy_owned)));
|
||||||
|
exitMummySpawn(*mummy_raw);
|
||||||
|
|
||||||
auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F);
|
auto whip = makeWhip(WhipAttack::Direction::EAST, BOX, 48.0F);
|
||||||
whip->setWorldContext(&manager, nullptr);
|
whip->setWorldContext(&manager, nullptr);
|
||||||
|
|||||||
Reference in new issue
Block a user