Files
aee-arcade/source/game/entities/hazard.hpp
T
JailDesignerandClaude Opus 4.7 a6b2b02930 feat(hazard): afegeix estat SPAWN a DynamicHazard
DynamicHazard guanya un mecanisme de SPAWN amb dues fases: SPAWNING
(visible, sense moviment ni dany, comptador en pausa) i ACTIVE (lògica
normal). `enterSpawn(duration_s)` deixa l'entitat congelada esperant
indefinidament; `triggerSpawnExit()` arma el comptador i, en arribar a
zero, transita a ACTIVE.

Per defecte les noves DynamicHazard arrenquen en ACTIVE — backward-compat
amb subclasses existents (RollingRock) que encara no usen el mecanisme.

EntityManager::checkPlayerCollisions salta DynamicHazards en SPAWN: no
disparen onPlayerContact, així no fan dany ni triggers durant la fase.

Aquest commit només afegeix la infraestructura — cap subclasse l'usa
encara, comportament del joc inalterat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:43:38 +02:00

137 lines
4.9 KiB
C++

#pragma once
#include <cstdint>
#include "game/entities/entity.hpp"
// Base d'entitats perilloses: al contacte amb el jugador, apliquen dany.
// De moment `Player` no té HP exposat; l'andamiatge deixa el hook llest
// (`damage_` + `onPlayerContact` publicant un event `"player_hit"`) perquè
// la lògica real de vides es cable-je quan s'integri al GameScene.
class Hazard : public Entity {
public:
Hazard() { collides_ = true; }
[[nodiscard]] auto getDamage() const -> int { return damage_; }
void setDamage(int d) { damage_ = d; }
protected:
int damage_{1};
};
// Perill estàtic: sempre al mateix tile. Suport opcional de cicle
// actiu/inactiu (trampes que oscil·len). Quan `active_=false`, el contacte
// no fa dany (però la detecció de col·lisió es manté: pot bloquejar o no
// segons la configuració).
class StaticHazard : public Hazard {
public:
StaticHazard() = default;
void setCycle(float active_s, float inactive_s) {
active_duration_ = active_s;
inactive_duration_ = inactive_s;
has_cycle_ = (active_s > 0.0F) || (inactive_s > 0.0F);
}
void update(float delta_time) override {
Entity::update(delta_time);
if (!has_cycle_) { return; }
timer_ += delta_time;
const float PERIOD = active_ ? active_duration_ : inactive_duration_;
if (timer_ >= PERIOD) {
timer_ -= PERIOD;
active_ = !active_;
}
}
[[nodiscard]] auto isActive() const -> bool { return active_; }
protected:
bool has_cycle_{false};
bool active_{true};
float active_duration_{0.0F};
float inactive_duration_{0.0F};
float timer_{0.0F};
};
// Perill dinàmic: es mou. Velocitat expressada en píxels/segon i una
// direcció (vec2). Les subclasses decideixen la política de moviment
// (IA corridor_wanderer, línia recta, etc.) sobreescribint `step(dt)`.
//
// Estat SPAWN: tota DynamicHazard pot estar en SPAWNING (visible, sense
// moviment ni dany) fins que algú extern arme el countdown amb
// `triggerSpawnExit()`. Quan el comptador arriba a zero passa a ACTIVE i
// recupera la lògica normal (`step()` per frame). Per defecte arrenca en
// ACTIVE — backward-compat amb subclasses que encara no usen el mecanisme.
// El filtre central de col·lisió a `EntityManager::checkPlayerCollisions`
// salta les entitats en SPAWN: per això el contacte no fa dany ni dispara
// triggers durant aquesta fase.
class DynamicHazard : public Hazard {
public:
enum class SpawnState : std::uint8_t { SPAWNING,
ACTIVE };
DynamicHazard() = default;
void setSpeed(float px_per_s) { speed_ = px_per_s; }
[[nodiscard]] auto getSpeed() const -> float { return speed_; }
void setDirection(float dx, float dy) {
dir_x_ = dx;
dir_y_ = dy;
}
[[nodiscard]] auto getDirX() const -> float { return dir_x_; }
[[nodiscard]] auto getDirY() const -> float { return dir_y_; }
// Entra en SPAWNING amb una duració determinada. El comptador NO
// descompta fins que algú crida `triggerSpawnExit()` — l'entitat queda
// visible i quieta esperant indefinidament.
void enterSpawn(float duration_s) {
spawn_state_ = SpawnState::SPAWNING;
spawn_duration_s_ = duration_s;
spawn_remaining_s_ = duration_s;
spawn_exit_armed_ = false;
}
// Arma el comptador del SPAWN. A partir d'aquí, cada `update(dt)`
// descompta `dt` fins arribar a 0 i transitar a ACTIVE.
void triggerSpawnExit() { spawn_exit_armed_ = true; }
[[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; }
[[nodiscard]] auto getSpawnDurationS() const -> float { return spawn_duration_s_; }
// Política de moviment per frame. La base desplaça el pixel_rect_
// proporcionalment a velocitat i direcció; les subclasses amb IA la
// sobreescriuen per triar nova direcció a les interseccions o rebotar.
virtual void step(float delta_time) {
pixel_rect_.x += dir_x_ * speed_ * delta_time;
pixel_rect_.y += dir_y_ * speed_ * delta_time;
}
void update(float delta_time) override {
if (spawn_state_ == SpawnState::SPAWNING) {
// Sense `step()`: l'entitat es queda quieta al pixel_rect_
// inicial. `Entity::update` tic d'animació igual: si en algun
// moment posem un GIF de spawn aquí ja anirà.
Entity::update(delta_time);
if (spawn_exit_armed_) {
spawn_remaining_s_ -= delta_time;
if (spawn_remaining_s_ <= 0.0F) {
spawn_state_ = SpawnState::ACTIVE;
}
}
return;
}
Entity::update(delta_time);
step(delta_time);
}
protected:
float speed_{0.0F};
float dir_x_{0.0F};
float dir_y_{0.0F};
SpawnState spawn_state_{SpawnState::ACTIVE};
float spawn_duration_s_{0.0F};
float spawn_remaining_s_{0.0F};
bool spawn_exit_armed_{false};
};