feat(mummy): estat HURT amb fugida en línia recta i recuperació al timer
This commit is contained in:
1 parent
9ad21eb5bb
commit
5db3bb0bd0
3 files changed
+187
No files matched your search
@@ -38,3 +38,13 @@ ai:
|
|||||||
stats:
|
stats:
|
||||||
speed: 30
|
speed: 30
|
||||||
damage: 1
|
damage: 1
|
||||||
|
|
||||||
|
# Estat HURT: post-fuetada, fugint en línia recta. Si la direcció
|
||||||
|
# preferida (la del whip) està bloquejada, prova perpendiculars; si totes
|
||||||
|
# ho estan, queda quieta tot el `duration_ms` i després torna a ROAMING.
|
||||||
|
# `speed` opcional: si no apareix, s'usa `stats.speed`. El `whippable`
|
||||||
|
# habilita la reacció al látigo (false ⇒ el whip la ignora).
|
||||||
|
hurt:
|
||||||
|
duration_ms: 600
|
||||||
|
speed: 60
|
||||||
|
whippable: true
|
||||||
@@ -104,6 +104,22 @@ Mummy::Mummy(const fkyaml::node& yaml)
|
|||||||
} else if (initial == "down") {
|
} else if (initial == "down") {
|
||||||
changeDirection(0.0F, 1.0F);
|
changeDirection(0.0F, 1.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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ó.
|
||||||
|
if (yaml.contains("hurt")) {
|
||||||
|
const auto& h = yaml["hurt"];
|
||||||
|
if (h.contains("duration_ms")) {
|
||||||
|
hurt_config_.duration_s = static_cast<float>(h["duration_ms"].get_value<int>()) / 1000.0F;
|
||||||
|
}
|
||||||
|
if (h.contains("speed")) {
|
||||||
|
hurt_config_.speed_px_per_s = h["speed"].get_value<float>();
|
||||||
|
}
|
||||||
|
if (h.contains("whippable")) {
|
||||||
|
whippable_ = h["whippable"].get_value<bool>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mummy::changeDirection(float dx, float dy) {
|
void Mummy::changeDirection(float dx, float dy) {
|
||||||
@@ -305,4 +321,118 @@ void Mummy::onPlayerContact(Player* player) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mummy::enterHurt(Direction flee_dir) {
|
||||||
|
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_;
|
||||||
|
|
||||||
|
const Direction CHOSEN = chooseHurtDirection(flee_dir);
|
||||||
|
if (CHOSEN == Direction::NONE) {
|
||||||
|
// Cap direcció lliure: la mòmia queda quieta però en HURT durant
|
||||||
|
// tot el timer. Posem dir a 0 perquè `hurtStep` no es mou.
|
||||||
|
setDirection(0.0F, 0.0F);
|
||||||
|
} else {
|
||||||
|
applyChoice(CHOSEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mummy::chooseHurtDirection(Direction preferred) const -> Direction {
|
||||||
|
// Avenç ~1 frame a 60 FPS amb la velocitat de HURT. Tan curt com
|
||||||
|
// perquè un canvi sub-tile no decideixe; suficient perquè un mur
|
||||||
|
// adjacent (o sarc tocant la caja) es detecte.
|
||||||
|
const float TEST_SPEED = (hurt_config_.speed_px_per_s > 0.0F) ? hurt_config_.speed_px_per_s : speed_;
|
||||||
|
const float STEP = TEST_SPEED * (1.0F / 60.0F);
|
||||||
|
|
||||||
|
auto is_open = [this, STEP](Direction d) -> bool {
|
||||||
|
float dx = 0.0F;
|
||||||
|
float dy = 0.0F;
|
||||||
|
switch (d) {
|
||||||
|
case Direction::UP:
|
||||||
|
dy = -STEP;
|
||||||
|
break;
|
||||||
|
case Direction::DOWN:
|
||||||
|
dy = STEP;
|
||||||
|
break;
|
||||||
|
case Direction::LEFT:
|
||||||
|
dx = -STEP;
|
||||||
|
break;
|
||||||
|
case Direction::RIGHT:
|
||||||
|
dx = STEP;
|
||||||
|
break;
|
||||||
|
case Direction::NONE:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto BOX = candidateBox(dx, dy);
|
||||||
|
return Map::insidePlayfield(BOX) && !Map::collides(BOX);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preferred != Direction::NONE && is_open(preferred)) { return preferred; }
|
||||||
|
|
||||||
|
// Perpendiculars segons la direcció preferida (ordre arbitrari però
|
||||||
|
// consistent: "esquerra del whip" primer).
|
||||||
|
Direction left = Direction::NONE;
|
||||||
|
Direction right = Direction::NONE;
|
||||||
|
switch (preferred) {
|
||||||
|
case Direction::UP:
|
||||||
|
left = Direction::LEFT;
|
||||||
|
right = Direction::RIGHT;
|
||||||
|
break;
|
||||||
|
case Direction::DOWN:
|
||||||
|
left = Direction::RIGHT;
|
||||||
|
right = Direction::LEFT;
|
||||||
|
break;
|
||||||
|
case Direction::LEFT:
|
||||||
|
left = Direction::DOWN;
|
||||||
|
right = Direction::UP;
|
||||||
|
break;
|
||||||
|
case Direction::RIGHT:
|
||||||
|
left = Direction::UP;
|
||||||
|
right = Direction::DOWN;
|
||||||
|
break;
|
||||||
|
case Direction::NONE:
|
||||||
|
return Direction::NONE;
|
||||||
|
}
|
||||||
|
if (is_open(left)) { return left; }
|
||||||
|
if (is_open(right)) { return right; }
|
||||||
|
|
||||||
|
return Direction::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mummy::hurtStep(float delta_time) {
|
||||||
|
// Si està quieta (totes les direccions bloquejades a `enterHurt`), no
|
||||||
|
// avança ni surt: l'únic camí de sortida és el timer.
|
||||||
|
if (dir_x_ == 0.0F && dir_y_ == 0.0F) { return; }
|
||||||
|
|
||||||
|
const float DX = dir_x_ * hurt_speed_ * delta_time;
|
||||||
|
const float DY = dir_y_ * hurt_speed_ * delta_time;
|
||||||
|
const auto CANDIDATE = candidateBox(DX, DY);
|
||||||
|
if (Map::insidePlayfield(CANDIDATE) && !Map::collides(CANDIDATE)) {
|
||||||
|
pixel_rect_.x += DX;
|
||||||
|
pixel_rect_.y += DY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topa amb mur o sarc: surt del HURT immediatament. La IA normal
|
||||||
|
// re-decideix al pròxim step (no fem U-turn ni re-orientació aquí).
|
||||||
|
state_ = State::ROAMING;
|
||||||
|
hurt_remaining_s_ = 0.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mummy::update(float delta_time) {
|
||||||
|
if (state_ == State::HURT) {
|
||||||
|
// Avancem l'animació inline (no podem cridar `Entity::update` ni
|
||||||
|
// `DynamicHazard::update`: el primer salta una capa virtual i el
|
||||||
|
// segon dispararia `step()` amb la lògica d'IA). Volem només el
|
||||||
|
// tick d'animació + el moviment recta del HURT.
|
||||||
|
if (anim_.has_value()) { anim_->tick(delta_time); }
|
||||||
|
hurtStep(delta_time);
|
||||||
|
hurt_remaining_s_ -= delta_time;
|
||||||
|
if (hurt_remaining_s_ <= 0.0F && state_ == State::HURT) {
|
||||||
|
state_ = State::ROAMING;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DynamicHazard::update(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
REGISTER_ENTITY("mummy", Mummy)
|
REGISTER_ENTITY("mummy", Mummy)
|
||||||
@@ -21,6 +21,21 @@ class Mummy : public DynamicHazard {
|
|||||||
LEFT,
|
LEFT,
|
||||||
RIGHT };
|
RIGHT };
|
||||||
|
|
||||||
|
// Estat d'alt nivell. ROAMING = IA normal (corridor_wanderer); HURT =
|
||||||
|
// post-fuetada, fugint en línia recta durant `hurt_config_.duration_s`.
|
||||||
|
// Mentre dura el HURT no decideix a creuaments ni canvia de direcció;
|
||||||
|
// si topa amb mur o sarc, surt del HURT abans de l'expiració del timer.
|
||||||
|
enum class State : std::uint8_t { ROAMING,
|
||||||
|
HURT };
|
||||||
|
|
||||||
|
// Configuració de l'estat HURT, llegida del bloc `hurt:` del YAML.
|
||||||
|
// Si `duration_s == 0` la mòmia mai entra a HURT efectiu (defensiu).
|
||||||
|
// `speed_px_per_s == 0` ⇒ s'usa la velocitat normal de la mòmia.
|
||||||
|
struct HurtConfig {
|
||||||
|
float duration_s{0.0F};
|
||||||
|
float speed_px_per_s{0.0F};
|
||||||
|
};
|
||||||
|
|
||||||
explicit Mummy(const fkyaml::node& yaml);
|
explicit Mummy(const fkyaml::node& yaml);
|
||||||
|
|
||||||
// Hook d'IA. Donat un conjunt de direccions admissibles, decideix la
|
// Hook d'IA. Donat un conjunt de direccions admissibles, decideix la
|
||||||
@@ -39,9 +54,22 @@ class Mummy : public DynamicHazard {
|
|||||||
// En ambdós casos el frame de gir no consumeix avenç extra.
|
// En ambdós casos el frame de gir no consumeix avenç extra.
|
||||||
void step(float delta_time) override;
|
void step(float delta_time) override;
|
||||||
|
|
||||||
|
// Update sencer: afegim la branca HURT (avanç del timer + step recta).
|
||||||
|
// En estat ROAMING delega a `step()` com sempre via la base.
|
||||||
|
void update(float delta_time) override;
|
||||||
|
|
||||||
// Contacte amb el jugador: publica `player_hit` amb el dany.
|
// Contacte amb el jugador: publica `player_hit` amb el dany.
|
||||||
void onPlayerContact(Player* player) override;
|
void onPlayerContact(Player* player) override;
|
||||||
|
|
||||||
|
// Activa l'estat HURT amb la direcció de fuga preferida `flee_dir`.
|
||||||
|
// Si està bloquejada, prova perpendiculars; si totes estan bloquejades,
|
||||||
|
// la mòmia queda quieta però en HURT durant tot el timer.
|
||||||
|
void enterHurt(Direction flee_dir);
|
||||||
|
|
||||||
|
[[nodiscard]] auto getState() const -> State { return state_; }
|
||||||
|
[[nodiscard]] auto isWhippable() const -> bool { return whippable_; }
|
||||||
|
[[nodiscard]] auto getHurtRemainingS() const -> float { return hurt_remaining_s_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
|
// Caixa de col·lisió candidata si l'entitat es desplaçara (dx, dy).
|
||||||
[[nodiscard]] auto candidateBox(float dx, float dy) const -> SDL_FRect;
|
[[nodiscard]] auto candidateBox(float dx, float dy) const -> SDL_FRect;
|
||||||
@@ -90,9 +118,28 @@ class Mummy : public DynamicHazard {
|
|||||||
// S'usa al pool de creuament per afegir la recta com a candidata.
|
// S'usa al pool de creuament per afegir la recta com a candidata.
|
||||||
[[nodiscard]] auto currentDirection() const -> Direction;
|
[[nodiscard]] auto currentDirection() const -> Direction;
|
||||||
|
|
||||||
|
// Avança recta a `hurt_speed_ * delta_time`. Si la caixa candidata
|
||||||
|
// sortiria del playfield o trepitjaria un sarc, marca la sortida del
|
||||||
|
// HURT abans de l'expiració del timer (la mòmia no rebota).
|
||||||
|
void hurtStep(float delta_time);
|
||||||
|
|
||||||
|
// Tria la direcció definitiva del HURT donada la preferida (la del
|
||||||
|
// whip). Si la preferida està bloquejada, prova perpendiculars; si
|
||||||
|
// totes ho estan, retorna `NONE` (la mòmia queda quieta en HURT).
|
||||||
|
[[nodiscard]] auto chooseHurtDirection(Direction preferred) const -> Direction;
|
||||||
|
|
||||||
// Animacions carregades del YAML, una per cada facing. Per a horitzontal
|
// Animacions carregades del YAML, una per cada facing. Per a horitzontal
|
||||||
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta.
|
// només en cal una (`walk`) i el flip ja diferencia esquerra/dreta.
|
||||||
AnimationComponent anim_walk_down_;
|
AnimationComponent anim_walk_down_;
|
||||||
AnimationComponent anim_walk_up_;
|
AnimationComponent anim_walk_up_;
|
||||||
AnimationComponent anim_walk_lateral_;
|
AnimationComponent anim_walk_lateral_;
|
||||||
|
|
||||||
|
// Estat HURT i la seua configuració. `hurt_speed_` és l'efectiva
|
||||||
|
// (resultat d'aplicar el `speed` del bloc `hurt:` o, si val 0, la
|
||||||
|
// velocitat normal de la mòmia).
|
||||||
|
State state_{State::ROAMING};
|
||||||
|
HurtConfig hurt_config_;
|
||||||
|
bool whippable_{true};
|
||||||
|
float hurt_remaining_s_{0.0F};
|
||||||
|
float hurt_speed_{0.0F};
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user