#include "game/entities/concrete/mummy.hpp" #include #include #include #include #include "game/entities/entity_factory.hpp" #include "game/entities/event_bus.hpp" #include "game/map/map.hpp" namespace { // RNG local al mòdul: compartit per totes les mòmies (no cal reproducció // determinista a l'andamiatge). Es reinicialitza amb una llavor fixa // per facilitar la depuració — si algun dia volem aleatoritat real, // llegir-la de std::random_device. auto rng() -> std::mt19937& { static std::mt19937 k_rng_{1337}; return k_rng_; } } // namespace Mummy::Mummy(const fkyaml::node& yaml) { name_ = yaml.contains("name") ? yaml["name"].get_value() : std::string("mummy"); collides_ = true; interactable_ = false; // Velocitat i dany des del bloc `stats` (si existeix). if (yaml.contains("stats")) { const auto& s = yaml["stats"]; if (s.contains("speed")) { setSpeed(s["speed"].get_value()); } if (s.contains("damage")) { setDamage(s["damage"].get_value()); } } // Direcció inicial llegida del bloc `ai:`. Per defecte dreta, per // coherència amb el que fa RollingRock. Valors acceptats: right, left, // up, down. Qualsevol altre s'interpreta com a "sense direcció" (la // momia no es mou fins que step() no triï una direcció). std::string initial = "right"; if (yaml.contains("ai") && yaml["ai"].contains("initial_direction")) { initial = yaml["ai"]["initial_direction"].get_value(); } if (initial == "right") { setDirection(1.0F, 0.0F); } else if (initial == "left") { setDirection(-1.0F, 0.0F); } else if (initial == "up") { setDirection(0.0F, -1.0F); } else if (initial == "down") { setDirection(0.0F, 1.0F); } } auto Mummy::chooseDirection(const std::vector& allowed) -> Direction { if (allowed.empty()) { return Direction::NONE; } std::uniform_int_distribution dist(0, allowed.size() - 1); return allowed[dist(rng())]; } auto Mummy::candidateBox(float dx, float dy) const -> SDL_FRect { auto box = getCollisionRect(); box.x += dx; box.y += dy; return box; } void Mummy::step(float delta_time) { // Desplaçament nominal en aquest frame segons la direcció actual. const float DX = dir_x_ * speed_ * delta_time; const float DY = dir_y_ * speed_ * delta_time; // Si no té direcció (p.ex. YAML sense `initial_direction`), el frame és // no-op fins que algú la injecte amb `setDirection`. if (DX == 0.0F && DY == 0.0F) { return; } // Prova d'avançar. Si la caixa candidata és vàlida (dins del playfield // i sense trepitjar cap sarc), s'aplica el moviment i s'acaba el frame. const auto CANDIDATE = candidateBox(DX, DY); if (Map::insidePlayfield(CANDIDATE) && !Map::collides(CANDIDATE)) { pixel_rect_.x += DX; pixel_rect_.y += DY; return; } // Bloqueig: cal girar. Recollim les direccions perpendiculars a l'actual // que tinguin un pròxim tram lliure. Evitem U-turn llevat que no hi hagi // cap alternativa (comportament típic d'Oh Mummy!: no es recula sense // motiu). Si la direcció perpendicular també està bloquejada, no s'hi // inclou; si totes les perpendiculars estan bloquejades, afegim l'opcio // oposada com a últim recurs (cul-de-sac). const float STEP = speed_ * delta_time; struct Candidate { float dx; float dy; Direction dir; }; const std::array ALL{{ {.dx = STEP, .dy = 0.0F, .dir = Direction::RIGHT}, {.dx = -STEP, .dy = 0.0F, .dir = Direction::LEFT}, {.dx = 0.0F, .dy = -STEP, .dir = Direction::UP}, {.dx = 0.0F, .dy = STEP, .dir = Direction::DOWN}, }}; std::vector perp_allowed; std::vector uturn_allowed; for (const auto& c : ALL) { const bool IS_CURRENT = (c.dx > 0.0F && dir_x_ > 0.0F) || (c.dx < 0.0F && dir_x_ < 0.0F) || (c.dy > 0.0F && dir_y_ > 0.0F) || (c.dy < 0.0F && dir_y_ < 0.0F); const bool IS_UTURN = (c.dx > 0.0F && dir_x_ < 0.0F) || (c.dx < 0.0F && dir_x_ > 0.0F) || (c.dy > 0.0F && dir_y_ < 0.0F) || (c.dy < 0.0F && dir_y_ > 0.0F); if (IS_CURRENT) { continue; } // ja hem vist que està bloquejada const auto TEST = candidateBox(c.dx, c.dy); if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; } if (IS_UTURN) { uturn_allowed.push_back(c.dir); } else { perp_allowed.push_back(c.dir); } } const auto& pool = !perp_allowed.empty() ? perp_allowed : uturn_allowed; const auto CHOICE = chooseDirection(pool); switch (CHOICE) { case Direction::UP: setDirection(0.0F, -1.0F); break; case Direction::DOWN: setDirection(0.0F, 1.0F); break; case Direction::LEFT: setDirection(-1.0F, 0.0F); break; case Direction::RIGHT: setDirection(1.0F, 0.0F); break; case Direction::NONE: setDirection(0.0F, 0.0F); break; } // No avancem aquest frame: la direcció nova s'aplicarà al pròxim step. } void Mummy::onPlayerContact(Player* /*player*/) { if (bus_ != nullptr) { bus_->publish("player_hit", getDamage()); } } REGISTER_ENTITY("mummy", Mummy)