Files
aee-arcade/source/game/entities/concrete/sarcophagus.cpp
T

179 lines
6.9 KiB
C++

#include "game/entities/concrete/sarcophagus.hpp"
#include <random>
#include <string>
#include <vector>
#include "core/audio/audio.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/entities/entity_factory.hpp"
#include "game/entities/entity_manager.hpp"
#include "game/entities/event_bus.hpp"
#include "utils/defaults.hpp"
namespace {
// RNG local al mòdul, mateix patró que `mummy.cpp`: llavor fixa per
// facilitar la depuració. Si algun dia volem aleatoritat real entre
// partides, prou amb llegir-la de std::random_device.
auto rng() -> std::mt19937& {
static std::mt19937 k_rng_{0xCAFEU};
return k_rng_;
}
auto buildAnimation(const fkyaml::node& yaml, const std::string& key) -> AnimationComponent {
std::vector<int> frames = {0};
float fps = 1.0F;
bool loop = true;
if (yaml.contains("animations") && yaml["animations"].contains(key)) {
const auto& anim = yaml["animations"][key];
if (anim.contains("frames")) {
frames.clear();
for (const auto& f : anim["frames"]) {
// cppcheck-suppress useStlAlgorithm
frames.push_back(f.get_value<int>());
}
if (frames.empty()) { frames.push_back(0); }
}
if (anim.contains("fps")) { fps = anim["fps"].get_value<float>(); }
if (anim.contains("loop")) { loop = anim["loop"].get_value<bool>(); }
}
return {frames, fps, loop};
}
// Llig `tile_size: [w, h]` del YAML. Fallback [1,1] si no existeix.
void readTileSize(const fkyaml::node& yaml, int& w, int& h) {
w = 1;
h = 1;
if (!yaml.contains("tile_size")) { return; }
const auto& ts = yaml["tile_size"];
if (ts.is_sequence() && ts.size() == 2) {
w = ts[0].get_value<int>();
h = ts[1].get_value<int>();
}
}
} // namespace
Sarcophagus::Sarcophagus(const fkyaml::node& yaml, int anchor_col, int anchor_row, const BlockedPredicate& is_blocked)
: Sarcophagus(yaml) {
place(anchor_col, anchor_row, is_blocked);
}
Sarcophagus::Sarcophagus(const fkyaml::node& yaml)
: anim_closed_(buildAnimation(yaml, "closed")),
anim_opening_(buildAnimation(yaml, "opening")),
anim_open_(buildAnimation(yaml, "open")) {
name_ = yaml.contains("name") ? yaml["name"].get_value<std::string>() : std::string("sarcophagus");
collides_ = true;
interactable_ = true;
readTileSize(yaml, tile_size_w_, tile_size_h_);
anim_ = anim_closed_;
// Sons opcionals (bloc `sound:` del YAML). El basename val tant de
// clau al Resource::Cache com de path dins `Defaults::Paths::AUDIO_SOUNDS_DIR`.
// Mateix patró que `mummy.hurt.sound` i `player.jump.sound`.
if (yaml.contains("sound")) {
const auto& s = yaml["sound"];
if (s.contains("opening")) {
opening_sound_key_ = s["opening"].get_value<std::string>();
Resource::Cache::get().loadSound(opening_sound_key_, std::string{Defaults::Paths::AUDIO_SOUNDS_DIR} + opening_sound_key_);
}
if (s.contains("opening_pitch_spread")) {
opening_pitch_spread_ = s["opening_pitch_spread"].get_value<float>();
}
}
}
void Sarcophagus::place(int anchor_col, int anchor_row, const BlockedPredicate& is_blocked) {
anchor_col_ = anchor_col;
anchor_row_ = anchor_row;
tile_.col = anchor_col;
tile_.row = anchor_row;
computePerimeter(is_blocked);
visited_.clear();
}
void Sarcophagus::computePerimeter(const BlockedPredicate& is_blocked) {
perimeter_tiles_.clear();
// El perímetre ortogonal d'un rectangle (w,h) de tiles són les files
// de tiles immediatament N, S, E, O. No s'hi inclouen les cantonades
// diagonals (tile NO, NE, SO, SE), perquè el jugador fa passes pels
// quatre costats i no pot "tallar" per una diagonal: això garanteix
// que el recorregut completa només si passa per cada costat.
//
// - Fila Nord: (col, row-1) per col ∈ [col0, col0+w-1]
// - Fila Sud: (col, row+h) per col ∈ [col0, col0+w-1]
// - Columna O: (col0-1, row) per row ∈ [row0, row0+h-1]
// - Columna E: (col0+w, row) per row ∈ [row0, row0+h-1]
auto maybe_insert = [&](int col, int row) {
if (is_blocked && is_blocked(col, row)) { return; }
perimeter_tiles_.insert({col, row});
};
// Nord i Sud
for (int dc = 0; dc < tile_size_w_; ++dc) {
maybe_insert(anchor_col_ + dc, anchor_row_ - 1);
maybe_insert(anchor_col_ + dc, anchor_row_ + tile_size_h_);
}
// Oest i Est
for (int dr = 0; dr < tile_size_h_; ++dr) {
maybe_insert(anchor_col_ - 1, anchor_row_ + dr);
maybe_insert(anchor_col_ + tile_size_w_, anchor_row_ + dr);
}
}
void Sarcophagus::update(float delta_time) {
Entity::update(delta_time);
if (state_ == State::OPENING && anim_.has_value() && anim_->completed()) {
state_ = State::OPEN;
anim_ = anim_open_;
if (anim_.has_value()) { anim_->reset(); }
if (!opened_event_emitted_ && bus_ != nullptr) {
bus_->publish("sarcophagus_opened", static_cast<Entity*>(this));
opened_event_emitted_ = true;
}
}
}
void Sarcophagus::notifyTilePainted(int col, int row) {
if (state_ != State::CLOSED) { return; }
const TileKey KEY{.col = col, .row = row};
if (perimeter_tiles_.find(KEY) == perimeter_tiles_.end()) { return; }
visited_.insert(KEY);
if (visited_.size() >= perimeter_tiles_.size()) {
state_ = State::OPENING;
anim_ = anim_opening_;
if (anim_.has_value()) { anim_->reset(); }
if (!opening_sound_key_.empty() && Audio::get() != nullptr) {
// Variació de pitch/velocitat per evitar la fatiga d'escoltar
// el mateix sample idèntic a cada obertura. Si l'spread és 0
// (default), reproduïm amb ratio 1.0; en cas contrari sortegem
// uniformement [1 - spread, 1 + spread].
if (opening_pitch_spread_ > 0.0F) {
std::uniform_real_distribution<float> dist(1.0F - opening_pitch_spread_, 1.0F + opening_pitch_spread_);
Audio::get()->playSound(opening_sound_key_, Audio::Group::GAME, dist(rng()));
} else {
Audio::get()->playSound(opening_sound_key_, Audio::Group::GAME);
}
}
}
}
void Sarcophagus::onRemoved() {
// El manager comptabilitza els sarcòfags actius en `total_sarcs_` per
// saber quan ha d'obrir la porta automàticament. Quan el editor
// n'esborra un, cal informar-lo: sense aquesta crida, `total_sarcs_`
// quedaria descompassat i la porta no s'obriria mai. Substitueix el
// `getName() == "sarcophagus"` que el manager feia abans —
// polimorfisme net, no comparació de strings.
if (entity_manager_ != nullptr) {
entity_manager_->onSarcophagusRemoved();
}
}
REGISTER_ENTITY("sarcophagus", Sarcophagus)