148 lines
5.4 KiB
C++
148 lines
5.4 KiB
C++
#include "game/entities/concrete/mummy.hpp"
|
|
|
|
#include <array>
|
|
#include <random>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#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>() : 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<float>()); }
|
|
if (s.contains("damage")) { setDamage(s["damage"].get_value<int>()); }
|
|
}
|
|
|
|
// 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<std::string>();
|
|
}
|
|
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<Direction>& allowed) -> Direction {
|
|
if (allowed.empty()) { return Direction::NONE; }
|
|
std::uniform_int_distribution<std::size_t> 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<Candidate, 4> 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<Direction> perp_allowed;
|
|
std::vector<Direction> 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)
|