Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa27308897 | ||
|
|
e58eb10d6d | ||
|
|
a29ea641ab | ||
|
|
28ff425c89 |
No files matched your search
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
|
#include "game/entities/concrete/whip_attack.hpp"
|
||||||
#include "game/entities/entity_factory.hpp"
|
#include "game/entities/entity_factory.hpp"
|
||||||
#include "game/entities/entity_manager.hpp"
|
#include "game/entities/entity_manager.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
@@ -65,6 +66,24 @@ namespace {
|
|||||||
// més d'una lane (cas marginal a velocitats típiques, però pot passar
|
// més d'una lane (cas marginal a velocitats típiques, però pot passar
|
||||||
// amb dt grans). Comparació estricta a un costat: no dispara quan la
|
// amb dt grans). Comparació estricta a un costat: no dispara quan la
|
||||||
// caixa ja es troba alineada a la lane (hi està després del snap).
|
// caixa ja es troba alineada a la lane (hi està després del snap).
|
||||||
|
// Mapatge WhipAttack::Direction → Mummy::Direction. Viuen amb noms i
|
||||||
|
// valors paral·lels però són dos enums distints (cada classe modela el
|
||||||
|
// seu propi domini). Conversió 1-a-1; `NONE` no apareix com a possible
|
||||||
|
// sortida del whip, però la deixem per coherència defensiva.
|
||||||
|
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
|
||||||
|
switch (d) {
|
||||||
|
case WhipAttack::Direction::NORTH:
|
||||||
|
return Mummy::Direction::NORTH;
|
||||||
|
case WhipAttack::Direction::SOUTH:
|
||||||
|
return Mummy::Direction::SOUTH;
|
||||||
|
case WhipAttack::Direction::EAST:
|
||||||
|
return Mummy::Direction::EAST;
|
||||||
|
case WhipAttack::Direction::WEST:
|
||||||
|
return Mummy::Direction::WEST;
|
||||||
|
}
|
||||||
|
return Mummy::Direction::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
auto findLaneCross(float prev, float delta, float base, float pitch, int max_idx) -> std::optional<float> {
|
auto findLaneCross(float prev, float delta, float base, float pitch, int max_idx) -> std::optional<float> {
|
||||||
const float NEXT = prev + delta;
|
const float NEXT = prev + delta;
|
||||||
if (delta > 0.0F) {
|
if (delta > 0.0F) {
|
||||||
@@ -520,6 +539,15 @@ void Mummy::applyChoice(Direction dir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Mummy::onWhipHit(WhipAttack* attack) -> bool {
|
||||||
|
// Defensiu: el filtre `shouldReceiveContact` ja salta DynamicHazards en
|
||||||
|
// SPAWN, però blindem aquesta crida si arribara per un camí no central.
|
||||||
|
if (isSpawning()) { return false; }
|
||||||
|
if (!whippable_ || attack == nullptr) { return false; }
|
||||||
|
enterHurt(whipDirToMummyDir(attack->getDirection()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Mummy::onPlayerContact(Player* player) {
|
void Mummy::onPlayerContact(Player* player) {
|
||||||
// Defensiu: el filtre central a EntityManager::checkPlayerCollisions ja
|
// Defensiu: el filtre central a EntityManager::checkPlayerCollisions ja
|
||||||
// salta DynamicHazards en SPAWN, però blindem la crida directa.
|
// salta DynamicHazards en SPAWN, però blindem la crida directa.
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ class Mummy : public DynamicHazard {
|
|||||||
// 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;
|
||||||
|
|
||||||
|
// Contacte amb el fueteig: si la mòmia és whippable, entra a HURT amb
|
||||||
|
// la direcció derivada del whip. Retorna true si efectivament ha
|
||||||
|
// rebut dany — el WhipAttack ho usa per al `already_hit_`. Substitueix
|
||||||
|
// el `dynamic_cast<Mummy*>` que feia el whip abans per discriminar.
|
||||||
|
auto onWhipHit(WhipAttack* attack) -> bool override;
|
||||||
|
|
||||||
// Activa l'estat HURT amb la direcció de fuga preferida `flee_dir`.
|
// Activa l'estat HURT amb la direcció de fuga preferida `flee_dir`.
|
||||||
// Si està bloquejada, prova perpendiculars; si totes estan bloquejades,
|
// Si està bloquejada, prova perpendiculars; si totes estan bloquejades,
|
||||||
// la mòmia queda quieta però en HURT durant tot el timer.
|
// la mòmia queda quieta però en HURT durant tot el timer.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "game/entities/entity_factory.hpp"
|
#include "game/entities/entity_factory.hpp"
|
||||||
|
#include "game/entities/entity_manager.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -124,4 +125,16 @@ void Sarcophagus::notifyTilePainted(int col, int row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
REGISTER_ENTITY("sarcophagus", Sarcophagus)
|
||||||
@@ -43,6 +43,7 @@ class Sarcophagus : public Interactable {
|
|||||||
|
|
||||||
void update(float delta_time) override;
|
void update(float delta_time) override;
|
||||||
void notifyTilePainted(int col, int row) override;
|
void notifyTilePainted(int col, int row) override;
|
||||||
|
void onRemoved() override;
|
||||||
|
|
||||||
[[nodiscard]] auto getState() const -> State { return state_; }
|
[[nodiscard]] auto getState() const -> State { return state_; }
|
||||||
[[nodiscard]] auto getPerimeterSize() const -> std::size_t { return perimeter_tiles_.size(); }
|
[[nodiscard]] auto getPerimeterSize() const -> std::size_t { return perimeter_tiles_.size(); }
|
||||||
|
|||||||
@@ -262,19 +262,10 @@ void WhipAttack::update(float delta_time) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
|
// (El mapping WhipAttack::Direction → Mummy::Direction viu ara a
|
||||||
switch (d) {
|
// mummy.cpp, on s'usa des de `Mummy::onWhipHit`. Aquí només queda el
|
||||||
case WhipAttack::Direction::NORTH:
|
// mapping a Player::Cardinal — usat per al friendly-fire entre
|
||||||
return Mummy::Direction::NORTH;
|
// jugadors, que no és polimòrfic via Entity.)
|
||||||
case WhipAttack::Direction::SOUTH:
|
|
||||||
return Mummy::Direction::SOUTH;
|
|
||||||
case WhipAttack::Direction::EAST:
|
|
||||||
return Mummy::Direction::EAST;
|
|
||||||
case WhipAttack::Direction::WEST:
|
|
||||||
return Mummy::Direction::WEST;
|
|
||||||
}
|
|
||||||
return Mummy::Direction::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto whipDirToPlayerCardinal(WhipAttack::Direction d) -> Player::Cardinal {
|
auto whipDirToPlayerCardinal(WhipAttack::Direction d) -> Player::Cardinal {
|
||||||
switch (d) {
|
switch (d) {
|
||||||
@@ -296,25 +287,24 @@ namespace {
|
|||||||
return STATE != Player::State::HURT && STATE != Player::State::EXITED && STATE != Player::State::DISABLED;
|
return STATE != Player::State::HURT && STATE != Player::State::EXITED && STATE != Player::State::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracta una mòmia candidata: si és whippable i la seua caja solapa
|
// Tracta una entitat candidata: si la seva caixa solapa amb
|
||||||
// amb `damage_rect`, la posa a HURT i la marca al set. Extret per
|
// `damage_rect`, delega la decisió a `entity->onWhipHit(attack)`.
|
||||||
// acotar la complexitat cognitiva d'`applyDamage` (clang-tidy llindar).
|
// Cada entitat decideix què fer (Mummy entra HURT, Torch retorna
|
||||||
// Rep un `DynamicHazard*` (l'iterador d'`applyDamage` ja filtra el
|
// false sense efecte, RollingRock no override → no-op). El retorn
|
||||||
// sac d'actors); només cal el dynamic_cast a Mummy per descartar
|
// true marca el set `already_hit` per evitar segons impactes dins del
|
||||||
// altres hermanes (RollingRock i futurs DynamicHazards no whippables).
|
// mateix swing. Polimorfisme net — sense dynamic_cast.
|
||||||
void hitMummyIfApplicable(
|
void hitEntityIfApplicable(
|
||||||
DynamicHazard* actor,
|
Entity* entity,
|
||||||
const SDL_FRect& damage_rect,
|
const SDL_FRect& damage_rect,
|
||||||
WhipAttack::Direction dir,
|
WhipAttack* attack,
|
||||||
std::unordered_set<void*>& already_hit) {
|
std::unordered_set<void*>& already_hit) {
|
||||||
if (actor == nullptr || actor->isDead()) { return; }
|
if (entity == nullptr || entity->isDead()) { return; }
|
||||||
if (already_hit.contains(static_cast<void*>(actor))) { return; }
|
if (already_hit.contains(static_cast<void*>(entity))) { return; }
|
||||||
auto* mummy = dynamic_cast<Mummy*>(actor);
|
const auto BOX = entity->getCollisionRect();
|
||||||
if (mummy == nullptr || !mummy->isWhippable()) { return; }
|
|
||||||
const auto BOX = mummy->getCollisionRect();
|
|
||||||
if (!SDL_HasRectIntersectionFloat(&damage_rect, &BOX)) { return; }
|
if (!SDL_HasRectIntersectionFloat(&damage_rect, &BOX)) { return; }
|
||||||
mummy->enterHurt(whipDirToMummyDir(dir));
|
if (entity->onWhipHit(attack)) {
|
||||||
already_hit.insert(static_cast<void*>(actor));
|
already_hit.insert(static_cast<void*>(entity));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hitPlayerIfApplicable(
|
void hitPlayerIfApplicable(
|
||||||
@@ -339,12 +329,14 @@ namespace {
|
|||||||
|
|
||||||
void WhipAttack::applyDamage() {
|
void WhipAttack::applyDamage() {
|
||||||
if (entity_manager_ != nullptr) {
|
if (entity_manager_ != nullptr) {
|
||||||
// Només els actors poden rebre el fuetejo (decoracions, sarcs,
|
// Iterem totes les entitats. La discriminació la fa cada una via
|
||||||
// forats... no són hits del whip). Iterem el sac `actors_`
|
// `onWhipHit`: Mummy entra HURT si és whippable, la resta retorna
|
||||||
// directament — sense recórrer tot el mapa per descartar-ne la
|
// false (no-op). El cost d'iterar antorxes/sarcs/forats és una
|
||||||
// major part.
|
// crida virtual buida cada una — menyspreable comparat amb la
|
||||||
for (auto* actor : entity_manager_->getActors()) {
|
// claredat del callsite. Si demà un sarc vol trencar-se al
|
||||||
hitMummyIfApplicable(actor, damage_rect_, direction_, already_hit_);
|
// fueteig, només cal override de `onWhipHit` allà.
|
||||||
|
for (auto* entity : entity_manager_->getEntities()) {
|
||||||
|
hitEntityIfApplicable(entity, damage_rect_, this, already_hit_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (players_ != nullptr) {
|
if (players_ != nullptr) {
|
||||||
|
|||||||
@@ -28,6 +28,31 @@ void Entity::notifyTilePainted(int /*col*/, int /*row*/) {
|
|||||||
// No-op per defecte; només `Sarcophagus` l'usa.
|
// No-op per defecte; només `Sarcophagus` l'usa.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Entity::onWhipHit(WhipAttack* /*attack*/) -> bool {
|
||||||
|
// No-op per defecte; les entitats no-whippables (Torch, Hole, Sarc,
|
||||||
|
// Door, RollingRock...) retornen `false`. `Mummy` n'és l'override
|
||||||
|
// efectiu. El caller usa el retorn per marcar `already_hit_` —
|
||||||
|
// entitats que no han rebut dany NO bloquegen futurs frames del whip.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entity::onJustSpawned() {
|
||||||
|
// No-op per defecte. `DynamicHazard` override per disparar el seu
|
||||||
|
// `triggerSpawnExit` (anima la fase EMERGING just després de néixer).
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entity::onRemoved() {
|
||||||
|
// No-op per defecte. `Sarcophagus` override per decrementar el
|
||||||
|
// comptador `total_sarcs_` del manager.
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Entity::shouldReceiveContact() const -> bool {
|
||||||
|
// Default: l'entitat rep contacte si és sòlida (`collides_`) o si és
|
||||||
|
// un trigger d'àrea (`triggers_on_overlap_`). `DynamicHazard` afegeix
|
||||||
|
// el filtre de SPAWN — mentre està emergint, ignora contactes.
|
||||||
|
return collides_ || triggers_on_overlap_;
|
||||||
|
}
|
||||||
|
|
||||||
auto Entity::getCollisionRect() const -> SDL_FRect {
|
auto Entity::getCollisionRect() const -> SDL_FRect {
|
||||||
if (!collision_box_.has_value()) { return pixel_rect_; }
|
if (!collision_box_.has_value()) { return pixel_rect_; }
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Player;
|
|||||||
class EventBus;
|
class EventBus;
|
||||||
class Surface;
|
class Surface;
|
||||||
class EntityManager;
|
class EntityManager;
|
||||||
|
class WhipAttack;
|
||||||
|
|
||||||
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors
|
// Entitat genèrica del món. Actua com a dada en comú per a tots els actors
|
||||||
// del joc (decoratius, interactables, perills mòbils i estàtics): manté
|
// del joc (decoratius, interactables, perills mòbils i estàtics): manté
|
||||||
@@ -86,6 +87,38 @@ class Entity {
|
|||||||
// (per marcar perímetre); per defecte no-op.
|
// (per marcar perímetre); per defecte no-op.
|
||||||
virtual void notifyTilePainted(int col, int row);
|
virtual void notifyTilePainted(int col, int row);
|
||||||
|
|
||||||
|
// L'entitat ha rebut un fueteig amb solapament. Es passa el punter al
|
||||||
|
// `WhipAttack` perquè la subclasse pugui llegir-ne direcció o atributs
|
||||||
|
// (mateix patró que `onPlayerContact(Player*)`). Retorna `true` si
|
||||||
|
// efectivament ha aplicat dany — el caller en marca el set
|
||||||
|
// `already_hit_` per evitar segons impactes dins del mateix swing.
|
||||||
|
// Default: no fa res, retorna `false`. Override a `Mummy` (whippable)
|
||||||
|
// i a futurs enemics whippables. `RollingRock` no override → roca
|
||||||
|
// immune al látigo per defecte.
|
||||||
|
virtual auto onWhipHit(WhipAttack* attack) -> bool;
|
||||||
|
|
||||||
|
// Hook just després de néixer al món. El manager el crida quan
|
||||||
|
// `addSpawn` / `spawnActor` / `spawnPendingActors` instancien una
|
||||||
|
// entitat. La base no fa res; `DynamicHazard` override per disparar
|
||||||
|
// el seu `triggerSpawnExit` (anima la fase EMERGING). Substitueix el
|
||||||
|
// `dynamic_cast<DynamicHazard*>(observer)->triggerSpawnExit()` que el
|
||||||
|
// manager feia abans — ara el dispatch és polimòrfic.
|
||||||
|
virtual void onJustSpawned();
|
||||||
|
|
||||||
|
// Hook just abans que el manager esborri l'entitat (via
|
||||||
|
// `removeEntity`, típicament al editor). Permet a subclasses
|
||||||
|
// notificar al manager o publicar events per mantenir invariants
|
||||||
|
// (p.ex. `Sarcophagus` decrementa `total_sarcs_`). Default no-op.
|
||||||
|
virtual void onRemoved();
|
||||||
|
|
||||||
|
// Decisió polimòrfica de si aquesta entitat ha de rebre el callback
|
||||||
|
// `onPlayerContact` aquest frame. La base permet contacte si `collides_`
|
||||||
|
// o `triggers_on_overlap_` estan posats. Subclasses poden afegir
|
||||||
|
// condicions transitòries (p.ex. `DynamicHazard` retorna `false` mentre
|
||||||
|
// està en SPAWN). Centralitza el filtre que abans vivia al manager
|
||||||
|
// amb dynamic_casts/isSpawning explícits.
|
||||||
|
[[nodiscard]] virtual auto shouldReceiveContact() const -> bool;
|
||||||
|
|
||||||
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
|
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
|
||||||
[[nodiscard]] auto getTilePos() const -> TilePos { return tile_; }
|
[[nodiscard]] auto getTilePos() const -> TilePos { return tile_; }
|
||||||
[[nodiscard]] auto getPixelRect() const -> SDL_FRect { return pixel_rect_; }
|
[[nodiscard]] auto getPixelRect() const -> SDL_FRect { return pixel_rect_; }
|
||||||
|
|||||||
@@ -74,19 +74,7 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
|||||||
entity->setEventBus(&bus_);
|
entity->setEventBus(&bus_);
|
||||||
entity->setEntityManager(this);
|
entity->setEntityManager(this);
|
||||||
Entity* raw = entity.get();
|
Entity* raw = entity.get();
|
||||||
// Enrutament per categoria semàntica: si l'entitat és (o deriva de)
|
entities_.push_back(std::move(entity));
|
||||||
// DynamicHazard va al sac `actors_`, la resta al sac `map_entities_`.
|
|
||||||
// Així els consumidors que només volen actors itera el sac petit i
|
|
||||||
// ens estalviem dynamic_casts dispersos al codi (colisió, whip...).
|
|
||||||
if (dynamic_cast<DynamicHazard*>(entity.get()) != nullptr) {
|
|
||||||
// Transferim l'ownership al unique_ptr<DynamicHazard> tipat fort.
|
|
||||||
// El `release()` consumeix el seu valor com a argument del ctor —
|
|
||||||
// sense crida nua que el linter pugui marcar com a ignorada.
|
|
||||||
std::unique_ptr<DynamicHazard> hazard{static_cast<DynamicHazard*>(entity.release())};
|
|
||||||
actors_.push_back(std::move(hazard));
|
|
||||||
} else {
|
|
||||||
map_entities_.push_back(std::move(entity));
|
|
||||||
}
|
|
||||||
// cppcheck-suppress returnDanglingLifetime
|
// cppcheck-suppress returnDanglingLifetime
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
@@ -102,18 +90,15 @@ void EntityManager::setDoor(Door* door) {
|
|||||||
auto EntityManager::removeEntity(Entity* observer) -> bool {
|
auto EntityManager::removeEntity(Entity* observer) -> bool {
|
||||||
if (observer == nullptr) { return false; }
|
if (observer == nullptr) { return false; }
|
||||||
const bool WAS_DOOR = (door_ == observer);
|
const bool WAS_DOOR = (door_ == observer);
|
||||||
const bool WAS_SARC = (observer->getName() == "sarcophagus");
|
// `onRemoved` és polimòrfic: cada subclasse decideix què cal
|
||||||
// Busquem en els dos sacs. El sarc i la door sempre viuen a
|
// notificar quan se l'esborra. Avui només Sarcophagus l'override per
|
||||||
// `map_entities_` per disseny; un actor (DynamicHazard) viu a `actors_`.
|
// decrementar el seu comptador. La door es detecta per comparació de
|
||||||
if (auto it = std::ranges::find_if(map_entities_, [observer](const auto& up) { return up.get() == observer; }); it != map_entities_.end()) {
|
// punter (és única, no cal RTTI).
|
||||||
map_entities_.erase(it);
|
observer->onRemoved();
|
||||||
} else if (auto it_a = std::ranges::find_if(actors_, [observer](const auto& up) { return up.get() == observer; }); it_a != actors_.end()) {
|
auto it = std::ranges::find_if(entities_, [observer](const auto& up) { return up.get() == observer; });
|
||||||
actors_.erase(it_a);
|
if (it == entities_.end()) { return false; }
|
||||||
} else {
|
entities_.erase(it);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (WAS_DOOR) { door_ = nullptr; }
|
if (WAS_DOOR) { door_ = nullptr; }
|
||||||
if (WAS_SARC && total_sarcs_ > 0) { --total_sarcs_; }
|
|
||||||
// No tocar `opened_sarcs_` aquí: l'editor congela el joc i no pot
|
// No tocar `opened_sarcs_` aquí: l'editor congela el joc i no pot
|
||||||
// disparar-se cap esdeveniment derivat. Quan el GameScene faça el
|
// disparar-se cap esdeveniment derivat. Quan el GameScene faça el
|
||||||
// reload posterior, els comptadors es re-deriven d'inici.
|
// reload posterior, els comptadors es re-deriven d'inici.
|
||||||
@@ -125,8 +110,7 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
|
|||||||
sarc->setEventBus(&bus_);
|
sarc->setEventBus(&bus_);
|
||||||
sarc->setEntityManager(this);
|
sarc->setEntityManager(this);
|
||||||
Sarcophagus* raw = sarc.get();
|
Sarcophagus* raw = sarc.get();
|
||||||
// El sarc és Interactable, viu al sac `map_entities_`.
|
entities_.push_back(std::move(sarc));
|
||||||
map_entities_.push_back(std::move(sarc));
|
|
||||||
++total_sarcs_;
|
++total_sarcs_;
|
||||||
// cppcheck-suppress returnDanglingLifetime
|
// cppcheck-suppress returnDanglingLifetime
|
||||||
return raw;
|
return raw;
|
||||||
@@ -134,34 +118,32 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
|
|||||||
|
|
||||||
void EntityManager::update(float delta_time) {
|
void EntityManager::update(float delta_time) {
|
||||||
// ATENCIÓ: si en el futur una entitat publica al bus events que
|
// ATENCIÓ: si en el futur una entitat publica al bus events que
|
||||||
// resulten en `entity_manager_.add()` (push_back als sacs), açò
|
// resulten en `entity_manager_.add()` (push_back a `entities_`), açò
|
||||||
// invalidaria l'iterador de la ranged-for. Avui no passa: els spawns
|
// invalidaria l'iterador de la ranged-for. Avui no passa: els spawns
|
||||||
// runtime venen de fora del cicle d'`update` (p.ex. el polvet del
|
// runtime venen de fora del cicle d'`update` (p.ex. el polvet del
|
||||||
// saltador es spawneja durant `Player::tick`, abans d'aquest mètode).
|
// saltador es spawneja durant `Player::tick`, abans d'aquest mètode).
|
||||||
for (auto& e : map_entities_) { e->update(delta_time); }
|
for (auto& e : entities_) { e->update(delta_time); }
|
||||||
for (auto& a : actors_) { a->update(delta_time); }
|
|
||||||
// Purga d'entitats marcades mortes aquest frame (efectes acabats,
|
// Purga d'entitats marcades mortes aquest frame (efectes acabats,
|
||||||
// p.ex. polvet d'aterratge). Les persistents (sarcs, momies, etc.)
|
// p.ex. polvet d'aterratge). Les persistents (sarcs, momies, etc.)
|
||||||
// mai no es marquen.
|
// mai no es marquen.
|
||||||
std::erase_if(map_entities_, [](const auto& e) { return e->isDead(); });
|
std::erase_if(entities_, [](const auto& e) { return e->isDead(); });
|
||||||
std::erase_if(actors_, [](const auto& a) { return a->isDead(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityManager::notifyPlayerTilePainted(int col, int row) {
|
void EntityManager::notifyPlayerTilePainted(int col, int row) {
|
||||||
// Fan-out als dos sacs. Avui només Sarcophagus consumeix (viu a
|
// Fan-out polimòrfic a totes les entitats: cada una decideix què fer
|
||||||
// map_entities_), però els actors també reben la notificació per
|
// (`Sarcophagus` actualitza el seu perímetre, la resta queden en
|
||||||
// si en el futur volen reaccionar (cost: una crida virtual buida).
|
// no-op). Cost d'iterar 40 elements amb crida virtual buida ~ 1 µs;
|
||||||
for (auto& e : map_entities_) { e->notifyTilePainted(col, row); }
|
// qui paga el cost guanya simplicitat al callsite.
|
||||||
for (auto& a : actors_) { a->notifyTilePainted(col, row); }
|
for (auto& e : entities_) { e->notifyTilePainted(col, row); }
|
||||||
}
|
}
|
||||||
|
|
||||||
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
|
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
|
||||||
// Cerca lineal sobre `map_entities_`: els floor decals trigger són
|
// Cerca lineal: només `Hole` (i futurs decals trigger) compleixen la
|
||||||
// exclusivament StaticHazards (Hole; en el futur pinchos plans,
|
// doble condició `triggersOnOverlap` + `isFloorDecal`. Costos típics
|
||||||
// plaques, charcos). Els actors no qualifiquen com a decals — saltem
|
// (poques desenes d'entitats) menyspreables, una crida per frame des
|
||||||
// directament aquest sac, sense `triggersOnOverlap`/`isFloorDecal` per
|
// dels consumidors actius (Player a l'aterratge, Mòmia a la decisió
|
||||||
// a cada un. La doble condició filtra dins de map_entities_.
|
// de direcció).
|
||||||
for (const auto& entity : map_entities_) {
|
for (const auto& entity : entities_) {
|
||||||
if (!entity->triggersOnOverlap()) { continue; }
|
if (!entity->triggersOnOverlap()) { continue; }
|
||||||
if (!entity->isFloorDecal()) { continue; }
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
const auto BOX = entity->getCollisionRect();
|
const auto BOX = entity->getCollisionRect();
|
||||||
@@ -172,27 +154,9 @@ auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto EntityManager::getEntities() const -> std::vector<Entity*> {
|
auto EntityManager::getEntities() const -> std::vector<Entity*> {
|
||||||
// Concatenació map + actors per retro-compat (editor, z-sort debug).
|
|
||||||
// Els callers que ja saben quin sac volen poden cridar
|
|
||||||
// `getMapEntities` o `getActors` i estalviar la construcció del temp.
|
|
||||||
std::vector<Entity*> out;
|
std::vector<Entity*> out;
|
||||||
out.reserve(map_entities_.size() + actors_.size());
|
out.reserve(entities_.size());
|
||||||
std::ranges::transform(map_entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
std::ranges::transform(entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
||||||
std::ranges::transform(actors_, std::back_inserter(out), [](const auto& a) -> Entity* { return a.get(); });
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto EntityManager::getMapEntities() const -> std::vector<Entity*> {
|
|
||||||
std::vector<Entity*> out;
|
|
||||||
out.reserve(map_entities_.size());
|
|
||||||
std::ranges::transform(map_entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto EntityManager::getActors() const -> std::vector<DynamicHazard*> {
|
|
||||||
std::vector<DynamicHazard*> out;
|
|
||||||
out.reserve(actors_.size());
|
|
||||||
std::ranges::transform(actors_, std::back_inserter(out), [](const auto& a) { return a.get(); });
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
|
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
#include "game/entities/hazard.hpp"
|
|
||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
class Sarcophagus;
|
class Sarcophagus;
|
||||||
class Door;
|
class Door;
|
||||||
class Player;
|
class Player;
|
||||||
|
class DynamicHazard;
|
||||||
|
|
||||||
// Contenidor i coordinador d'entitats d'un nivell. Responsabilitats:
|
// Contenidor i coordinador d'entitats d'un nivell. Responsabilitats:
|
||||||
// 1. `add` registra una entitat, la connecta al bus i manté propietat.
|
// 1. `add` registra una entitat, la connecta al bus i manté propietat.
|
||||||
@@ -74,14 +74,12 @@ class EntityManager {
|
|||||||
static void renderEntity(const Entity& entity);
|
static void renderEntity(const Entity& entity);
|
||||||
|
|
||||||
// Accés als punters observacionals a les entitats per a consumidors
|
// Accés als punters observacionals a les entitats per a consumidors
|
||||||
// externs que necessiten construir una llista ordenada (z-order).
|
// externs que necessiten iterar (z-order, aplicar dany, etc.). El
|
||||||
// `getEntities()` concatena els dos sacs interns (mapa + actors) per
|
// polimorfisme dins de cada entitat fa la discriminació: l'iterador
|
||||||
// a consumidors retro-compat (editor, z-sort debug). `getMapEntities`
|
// recorre tot i les entitats que no hagin de respondre tornen no-op
|
||||||
// i `getActors` exposen cada sac per separat — útil per a iteracions
|
// (p.ex. Torch::onWhipHit retorna false sense efecte). Sense sacs
|
||||||
// que ja saben quin tipus volen i estalvien filtres O(n).
|
// separats — el `if (entity_is_X)` viu dins de l'entitat, no del caller.
|
||||||
[[nodiscard]] auto getEntities() const -> std::vector<Entity*>;
|
[[nodiscard]] auto getEntities() const -> std::vector<Entity*>;
|
||||||
[[nodiscard]] auto getMapEntities() const -> std::vector<Entity*>;
|
|
||||||
[[nodiscard]] auto getActors() const -> std::vector<DynamicHazard*>;
|
|
||||||
|
|
||||||
// Comprova contacte del(s) jugador(s) amb entitats `collides()=true`.
|
// Comprova contacte del(s) jugador(s) amb entitats `collides()=true`.
|
||||||
// Si la caixa base del jugador solapa amb la caixa de col·lisió de
|
// Si la caixa base del jugador solapa amb la caixa de col·lisió de
|
||||||
@@ -160,7 +158,7 @@ class EntityManager {
|
|||||||
void setExpectedPlayerCount(int n);
|
void setExpectedPlayerCount(int n);
|
||||||
|
|
||||||
[[nodiscard]] auto getBus() -> EventBus& { return bus_; }
|
[[nodiscard]] auto getBus() -> EventBus& { return bus_; }
|
||||||
[[nodiscard]] auto entityCount() const -> std::size_t { return map_entities_.size() + actors_.size(); }
|
[[nodiscard]] auto entityCount() const -> std::size_t { return entities_.size(); }
|
||||||
[[nodiscard]] auto pendingActorCount() const -> std::size_t { return pending_actors_.size(); }
|
[[nodiscard]] auto pendingActorCount() const -> std::size_t { return pending_actors_.size(); }
|
||||||
[[nodiscard]] auto sarcophagusCount() const -> std::size_t { return total_sarcs_; }
|
[[nodiscard]] auto sarcophagusCount() const -> std::size_t { return total_sarcs_; }
|
||||||
[[nodiscard]] auto openedSarcophagusCount() const -> std::size_t { return opened_sarcs_; }
|
[[nodiscard]] auto openedSarcophagusCount() const -> std::size_t { return opened_sarcs_; }
|
||||||
@@ -173,19 +171,24 @@ class EntityManager {
|
|||||||
// sarcs. No-op si la porta ja està oberta o si el nivell no en té.
|
// sarcs. No-op si la porta ja està oberta o si el nivell no en té.
|
||||||
void forceOpenDoor();
|
void forceOpenDoor();
|
||||||
|
|
||||||
|
// Hook que `Sarcophagus::onRemoved` crida per decrementar el
|
||||||
|
// comptador `total_sarcs_`. Públic perquè el sarcòfag és la única
|
||||||
|
// entitat que necessita aquest manteniment, però l'API es manté
|
||||||
|
// genèrica: si en el futur Door volgués notificar quelcom semblant,
|
||||||
|
// afegim el seu propi callback.
|
||||||
|
void onSarcophagusRemoved() {
|
||||||
|
if (total_sarcs_ > 0) { --total_sarcs_; }
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EventBus bus_;
|
EventBus bus_;
|
||||||
// Dos sacs lògics:
|
// Un sol contenidor lògic per a totes les entitats instanciades. La
|
||||||
// `map_entities_` — decoració, static_hazards (Hole, FloorTrap,
|
// discriminació de tipus la fan els virtuals (`onPlayerContact`,
|
||||||
// WallTrap) i interactables (Sarcophagus, Door).
|
// `onWhipHit`, `shouldReceiveContact`, `notifyTilePainted`...) — el
|
||||||
// Cargades al loadFromLevel i persistents durant
|
// manager itera i cada entitat decideix què fa. Hi conviuen
|
||||||
// tota la sala.
|
// decoracions, static_hazards, interactables i dynamic_hazards
|
||||||
// `actors_` — DynamicHazards (Mummy, RollingRock, futurs
|
// (actors).
|
||||||
// enemics). Separats per eliminar dynamic_casts
|
std::vector<std::unique_ptr<Entity>> entities_;
|
||||||
// i preparar el spawn diferit (commit posterior).
|
|
||||||
// `add()` enruta automàticament segons sigui o no DynamicHazard.
|
|
||||||
std::vector<std::unique_ptr<Entity>> map_entities_;
|
|
||||||
std::vector<std::unique_ptr<DynamicHazard>> actors_;
|
|
||||||
// Cua d'actors declarats al YAML del mapa però encara no instanciats:
|
// Cua d'actors declarats al YAML del mapa però encara no instanciats:
|
||||||
// `loadFromLevel` hi empeny tots els DynamicHazards. `spawnPendingActors`
|
// `loadFromLevel` hi empeny tots els DynamicHazards. `spawnPendingActors`
|
||||||
// o `spawnActor` consumeixen aquesta cua (o creen un actor on-demand)
|
// o `spawnActor` consumeixen aquesta cua (o creen un actor on-demand)
|
||||||
|
|||||||
@@ -57,11 +57,10 @@ void EntityManager::renderEntity(const Entity& e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EntityManager::renderAll() {
|
void EntityManager::renderAll() {
|
||||||
// Sense ordenació per z: l'ordre és el d'inserció dins de cada sac.
|
// Sense ordenació per z: l'ordre és el d'inserció al vector. El
|
||||||
// El render del joc real (GameScene) construeix el seu propi vector
|
// render del joc real (GameScene) construeix el seu propi vector
|
||||||
// ordenat per `feet_y`; aquest helper queda per al editor i tests.
|
// ordenat per `feet_y`; aquest helper queda per al editor i tests.
|
||||||
for (const auto& e : map_entities_) { renderEntity(*e); }
|
for (const auto& e : entities_) { renderEntity(*e); }
|
||||||
for (const auto& a : actors_) { renderEntity(*a); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -100,22 +99,15 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sac de mapa: filtre per col·lisió / trigger. Tanquem el gap latent
|
// Filtre polimòrfic unificat: cada entitat decideix si ha de rebre
|
||||||
// de FloorTrap/WallTrap (que tenien `collides_=false` i mai no es
|
// contacte (`shouldReceiveContact`). Per defecte equival a `collides()
|
||||||
// disparaven) i obrim la porta a hazards no-físics com `Hole`.
|
// || triggersOnOverlap()`; `DynamicHazard` afegeix el filtre de SPAWN
|
||||||
for (const auto& entity : map_entities_) {
|
// (mentre està emergint torna false). El callsite no fa dynamic_cast
|
||||||
if (!entity->collides() && !entity->triggersOnOverlap()) { continue; }
|
// ni distingeix categories.
|
||||||
|
for (const auto& entity : entities_) {
|
||||||
|
if (!entity->shouldReceiveContact()) { continue; }
|
||||||
apply_contacts(entity.get());
|
apply_contacts(entity.get());
|
||||||
}
|
}
|
||||||
// Sac d'actors (DynamicHazards): tots fan dany quan són actius.
|
|
||||||
// Saltem els que estan en SPAWN: visibles però sense interacció
|
|
||||||
// durant la fase inicial de ROOM_INTRO i durant futurs spawns en
|
|
||||||
// runtime. Sense dynamic_cast: el tipus ja és DynamicHazard.
|
|
||||||
for (const auto& actor : actors_) {
|
|
||||||
if (actor->isSpawning()) { continue; }
|
|
||||||
if (!actor->collides() && !actor->triggersOnOverlap()) { continue; }
|
|
||||||
apply_contacts(actor.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -413,8 +405,7 @@ void EntityManager::reload(const std::string& level_yaml_path) {
|
|||||||
// subscripcions implícites desapareixen amb la seua vida (cap d'elles
|
// subscripcions implícites desapareixen amb la seua vida (cap d'elles
|
||||||
// se subscriu directament al bus — només publiquen). El bus i les
|
// se subscriu directament al bus — només publiquen). El bus i les
|
||||||
// subscripcions externes (PaintLayer, HUD, GameScene) no es toquen.
|
// subscripcions externes (PaintLayer, HUD, GameScene) no es toquen.
|
||||||
map_entities_.clear();
|
entities_.clear();
|
||||||
actors_.clear();
|
|
||||||
pending_actors_.clear();
|
pending_actors_.clear();
|
||||||
door_ = nullptr;
|
door_ = nullptr;
|
||||||
total_sarcs_ = 0;
|
total_sarcs_ = 0;
|
||||||
@@ -478,9 +469,9 @@ void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
|||||||
// perquè tots els sarcòfags ja estan registrats al fan-out (la
|
// perquè tots els sarcòfags ja estan registrats al fan-out (la
|
||||||
// subscripció del manager és al ctor, anterior a qualsevol load).
|
// subscripció del manager és al ctor, anterior a qualsevol load).
|
||||||
int decal_broadcast = 0;
|
int decal_broadcast = 0;
|
||||||
// Els decals de sòl (Hole, futurs pinchos plans...) són StaticHazards
|
// Els decals de sòl (Hole, futurs pinchos plans...) són StaticHazards;
|
||||||
// i viuen exclusivament a `map_entities_`. Saltem `actors_` per estalviar.
|
// iterem tot `entities_` i el filtre per `isFloorDecal()` els tria.
|
||||||
for (const auto& entity : map_entities_) {
|
for (const auto& entity : entities_) {
|
||||||
if (!entity->isFloorDecal()) { continue; }
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
const auto BOX = entity->getCollisionRect();
|
const auto BOX = entity->getCollisionRect();
|
||||||
const auto [COL, ROW] = Map::pixelToTile(BOX.x, BOX.y);
|
const auto [COL, ROW] = Map::pixelToTile(BOX.x, BOX.y);
|
||||||
@@ -495,22 +486,18 @@ void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
|||||||
Log::info("EntityManager", "{} tile_painted broadcast inicial (decals de sòl)", decal_broadcast);
|
Log::info("EntityManager", "{} tile_painted broadcast inicial (decals de sòl)", decal_broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info("EntityManager", "{} map entities + {} actors pending des de {}", loaded, pending_actors_.size(), level_yaml_path);
|
Log::info("EntityManager", "{} entitats + {} actors pending des de {}", loaded, pending_actors_.size(), level_yaml_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityManager::spawnPendingActors() {
|
void EntityManager::spawnPendingActors() {
|
||||||
// Itera els pending i els instancia via `addSpawn`. El dispatch d'`add()`
|
// Itera els pending i els instancia via `addSpawn`. El dispatch d'`add()`
|
||||||
// els enruta a `actors_`. El `triggerSpawnExit()` posterior arma el
|
// els enruta a `actors_`. `onJustSpawned()` és polimòrfic: la base
|
||||||
// comptador de SPAWN — la mòmia neix ja en EMERGING, sense esperes.
|
// no fa res, DynamicHazard l'override per disparar `triggerSpawnExit()`
|
||||||
|
// i entrar a EMERGING. Sense dynamic_cast: la decisió viu dins de
|
||||||
|
// cada entitat.
|
||||||
for (const auto& pa : pending_actors_) {
|
for (const auto& pa : pending_actors_) {
|
||||||
Entity* observer = addSpawn(pa.spawn_node);
|
Entity* observer = addSpawn(pa.spawn_node);
|
||||||
if (observer == nullptr) { continue; }
|
if (observer != nullptr) { observer->onJustSpawned(); }
|
||||||
// Per categoria YAML (`type: dynamic_hazard`) sabem que és
|
|
||||||
// DynamicHazard — el dynamic_cast aquí val zero i evita
|
|
||||||
// assumptions implícites sobre la jerarquia.
|
|
||||||
if (auto* dh = dynamic_cast<DynamicHazard*>(observer); dh != nullptr) {
|
|
||||||
dh->triggerSpawnExit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pending_actors_.clear();
|
pending_actors_.clear();
|
||||||
}
|
}
|
||||||
@@ -530,7 +517,9 @@ auto EntityManager::spawnActor(const std::string& type, int col, int row) -> Dyn
|
|||||||
}
|
}
|
||||||
Entity* observer = addSpawn(spawn);
|
Entity* observer = addSpawn(spawn);
|
||||||
if (observer == nullptr) { return nullptr; }
|
if (observer == nullptr) { return nullptr; }
|
||||||
auto* dh = dynamic_cast<DynamicHazard*>(observer);
|
// `onJustSpawned` polimòrfic: per a DynamicHazards arma EMERGING; per
|
||||||
if (dh != nullptr) { dh->triggerSpawnExit(); }
|
// a un type erroni (no dynamic_hazard) queda no-op i retornem nullptr
|
||||||
return dh;
|
// mitjançant el cast final, deixant la signatura `DynamicHazard*` neta.
|
||||||
|
observer->onJustSpawned();
|
||||||
|
return dynamic_cast<DynamicHazard*>(observer);
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,20 @@ class DynamicHazard : public Hazard {
|
|||||||
[[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; }
|
[[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; }
|
||||||
[[nodiscard]] auto getSpawnDurationS() const -> float { return spawn_duration_s_; }
|
[[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_
|
// Política de moviment per frame. La base desplaça el pixel_rect_
|
||||||
// proporcionalment a velocitat i direcció; les subclasses amb IA la
|
// proporcionalment a velocitat i direcció; les subclasses amb IA la
|
||||||
// sobreescriuen per triar nova direcció a les interseccions o rebotar.
|
// sobreescriuen per triar nova direcció a les interseccions o rebotar.
|
||||||
|
|||||||
@@ -1793,18 +1793,12 @@ void GameScene::render() {
|
|||||||
};
|
};
|
||||||
std::vector<Renderable> order;
|
std::vector<Renderable> order;
|
||||||
order.reserve(entity_manager_.entityCount() + players_.size());
|
order.reserve(entity_manager_.entityCount() + players_.size());
|
||||||
// Mergem els dos sacs (map + actors) i els jugadors en un sol
|
// Mergem entitats i jugadors en un sol vector temporal — el
|
||||||
// vector temporal — el `stable_sort` per `feet_y` posterior fa el
|
// `stable_sort` per `feet_y` posterior fa el z-order interleaving.
|
||||||
// z-order interleaving entre els tres orígens sense importar el
|
const auto ENTITIES = entity_manager_.getEntities();
|
||||||
// sac d'on venen.
|
std::ranges::transform(ENTITIES, std::back_inserter(order), [](Entity* e) {
|
||||||
const auto MAP = entity_manager_.getMapEntities();
|
|
||||||
std::ranges::transform(MAP, std::back_inserter(order), [](Entity* e) {
|
|
||||||
return Renderable{.feet_y = e->getFeetY(), .entity = e, .player = nullptr};
|
return Renderable{.feet_y = e->getFeetY(), .entity = e, .player = nullptr};
|
||||||
});
|
});
|
||||||
const auto ACTORS = entity_manager_.getActors();
|
|
||||||
std::ranges::transform(ACTORS, std::back_inserter(order), [](DynamicHazard* a) -> Renderable {
|
|
||||||
return Renderable{.feet_y = a->getFeetY(), .entity = a, .player = nullptr};
|
|
||||||
});
|
|
||||||
// Tots els jugadors entren al z-sort. Els fora de joc fan no-op a
|
// Tots els jugadors entren al z-sort. Els fora de joc fan no-op a
|
||||||
// `render()`; els actius (inclosos AIRBORNE durant l'spawn aeri)
|
// `render()`; els actius (inclosos AIRBORNE durant l'spawn aeri)
|
||||||
// es pinten amb el seu feet_y normal. La sombra que afecta la
|
// es pinten amb el seu feet_y normal. La sombra que afecta la
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace Texts {
|
namespace Texts {
|
||||||
constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte ARCADE — JailDesigner";
|
constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte ARCADE — JailDesigner";
|
||||||
constexpr const char* COPYRIGHT = "© 2026 JailDesigner";
|
constexpr const char* COPYRIGHT = "© 2026 JailDesigner";
|
||||||
constexpr const char* VERSION = "0.41.0"; // Single source of truth — Makefile el grep
|
constexpr const char* VERSION = "0.42.0"; // Single source of truth — Makefile el grep
|
||||||
} // namespace Texts
|
} // namespace Texts
|
||||||
|
|
||||||
namespace GameCanvas {
|
namespace GameCanvas {
|
||||||
|
|||||||
Reference in new issue
Block a user