#pragma once #include #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; } // Transició directa a ACTIVE, sense passar pel countdown. Pensat // per a subclasses amb SPAWN dirigit per animació: gestionen el // seu propi `update()` mentre `isSpawning()` i criden ací quan la // seua animació d'aparició ha completat. void endSpawn() { spawn_state_ = SpawnState::ACTIVE; } [[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; } [[nodiscard]] auto getSpawnDurationS() const -> float { return spawn_duration_s_; } // Just després d'instanciar-se al món (via spawnPendingActors / // spawnActor), l'actor entra automàticament en la fase EMERGING: // arma el comptador i comença l'animació de sortida. Substitueix el // dynamic_cast que el manager feia abans per cridar `triggerSpawnExit`. void onJustSpawned() override { triggerSpawnExit(); } // Mentre està en SPAWN (HIDDEN/EMERGING), l'actor NO rep contacte // ni amb el jugador ni amb attacks: visible però "no juga". Quan // passa a ACTIVE, recupera el comportament estàndard d'Entity. [[nodiscard]] auto shouldReceiveContact() const -> bool override { if (isSpawning()) { return false; } return Entity::shouldReceiveContact(); } // 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}; };