78 lines
3.0 KiB
C++
78 lines
3.0 KiB
C++
#include "game/entities/concrete/door.hpp"
|
|
|
|
#include <string>
|
|
|
|
#include "game/entities/entity_factory.hpp"
|
|
#include "game/entities/event_bus.hpp"
|
|
#include "game/entities/player.hpp"
|
|
#include "game/map/map.hpp"
|
|
|
|
Door::Door(const fkyaml::node& yaml)
|
|
: anim_closed_(AnimationComponent::fromYaml(yaml, "closed")),
|
|
anim_opening_(AnimationComponent::fromYaml(yaml, "opening")),
|
|
anim_open_(AnimationComponent::fromYaml(yaml, "open")) {
|
|
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("door");
|
|
collides_ = true;
|
|
interactable_ = true;
|
|
anim_ = anim_closed_;
|
|
}
|
|
|
|
void Door::setAnimation(const AnimationComponent& anim) {
|
|
anim_ = anim;
|
|
if (anim_.has_value()) { anim_->reset(); }
|
|
}
|
|
|
|
void Door::update(float delta_time) {
|
|
Entity::update(delta_time);
|
|
|
|
// Transició OPENING → OPEN: amb l'animació `opening` configurada com a
|
|
// loop=false, `completed()` passa a true quan s'ha recorregut l'últim
|
|
// frame. Per a animacions d'un sol frame, el component la considera
|
|
// completada immediatament (1 frame = 0 passos), així que la transició
|
|
// ocorre al pròxim tick.
|
|
if (state_ == State::OPENING && anim_.has_value() && anim_->completed()) {
|
|
state_ = State::OPEN;
|
|
setAnimation(anim_open_);
|
|
}
|
|
}
|
|
|
|
void Door::onPlayerContact(Player* player) {
|
|
// Només la porta OPEN despawna jugadors. CLOSED/OPENING ignoren el
|
|
// contacte (i a més, en CLOSED, el `Map::collides` ja bloqueja el
|
|
// moviment, així que el contacte no es dóna en condicions normals).
|
|
if (state_ != State::OPEN || player == nullptr || player->isExited()) { return; }
|
|
// Marquem el jugador com a EXITED i publiquem l'event d'agregació.
|
|
// El check `isExited` de la guarda anterior garanteix que només
|
|
// emetem l'event en la primera transició — `EntityManager` compta
|
|
// emissions per decidir quan publicar `level_complete`.
|
|
player->exit();
|
|
if (bus_ != nullptr) {
|
|
bus_->publish(Event{
|
|
.name = "player_exited",
|
|
.payload = std::monostate{},
|
|
.player_id = player->getId(),
|
|
});
|
|
}
|
|
}
|
|
|
|
void Door::open() {
|
|
if (state_ != State::CLOSED) { return; }
|
|
state_ = State::OPENING;
|
|
setAnimation(anim_opening_);
|
|
// Marquem el passadís com a transitable: a partir d'aquest tic
|
|
// `Map::collides` deixa de bloquejar el rect del passadís. La
|
|
// transició d'animació OPENING → OPEN encara dura el seu temps,
|
|
// però la geometria ja és permeable — coherent amb el feel de
|
|
// "la porta s'obri i el jugador pot avançar al moment".
|
|
Map::door_open = true;
|
|
// Edge case: si `opening` té ≤1 frame i és loop=true, mai es
|
|
// completarà. Per força, forcem loop=false del frame únic aquí. Els
|
|
// placeholders defineixen `loop: false` explícitament al YAML, així
|
|
// que aquest fallback només actua si algú dissenya malament.
|
|
if (anim_.has_value() && anim_->frameCount() <= 1 && anim_->loops()) {
|
|
anim_ = AnimationComponent::uniform({0}, 167.0F, false);
|
|
}
|
|
}
|
|
|
|
REGISTER_ENTITY("door", Door)
|