#pragma once #include #include #include #include #include "game/entities/animation_component.hpp" // Forward-declaracions: mantenim aquest header lleuger. `Player` viu a // `source/game/entities/player.hpp` i només es referencia per referència // dins `onPlayerContact`; `EventBus` és opcional a cada entitat i viu al // seu header propi. `Surface` és el backend de render paletitzat del motor. class Player; class EventBus; class Surface; // 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é // posició en tiles i píxels, un component d'animació opcional i flags de // comportament (collides, interactable). Les subclasses intermèdies // afegeixen semàntica (veure `decoration.hpp`, `interactable.hpp`, // `hazard.hpp`) i les concretes materialitzen cada tipus (Sarcophagus, // Torch, Mummy, etc.). // // Decisió de disseny: `Entity` NO hereta d'`AnimatedSprite`. L'animació és // un component (composició), no una herència, per dues raons: (a) no totes // les entitats en necessiten (trampes invisibles, triggers), i (b) lligar // el cicle de vida al de `MovingSprite` imposaria semàntica de moviment // contínu a actors estàtics. Quan s'hi cablege el render real, es podrà // optar per tenir un `Sprite` intern també com a component opcional. class Entity { public: struct TilePos { int col{0}; int row{0}; }; // Sub-rectangle de col·lisió dins de `pixel_rect_`. Pensat per a // personatges amb falsa perspectiva (sprite alt, caixa AABB petita // encastada a baix) — mateixa convenció que el Player. `offset_x/y` // són relatius a la cantonada superior-esquerra del sprite. Si l'entitat // no té `CollisionBox`, `getCollisionRect()` retorna tot el `pixel_rect_`. struct CollisionBox { int offset_x{0}; int offset_y{0}; int width{0}; int height{0}; }; Entity() = default; Entity(std::string name, TilePos tile, SDL_FRect pixel_rect); virtual ~Entity() = default; Entity(const Entity&) = delete; Entity(Entity&&) = delete; auto operator=(const Entity&) -> Entity& = delete; auto operator=(Entity&&) -> Entity& = delete; // Avanç lògic per frame. La base només avança l'animació si existeix; // les subclasses poden afegir IA, cicles, moviment, etc. virtual void update(float delta_time); // Render a la pantalla. La base és no-op: l'andamiatge no cableja encara // amb el `Screen` real; un cop integrat al `GameScene`, les subclasses // o un mòdul de render dedicat dibuixaran sprites segons // `anim_.currentFrame()`. virtual void render(); // El jugador ha xocat amb aquesta entitat (col·lisió o proximitat). // Per defecte no fa res; `Hazard` aplica dany, `Door` oberta publica // `"level_complete"`, etc. Es passa per punter (nullable) perquè els // smoke tests poden disparar el contacte sense un Player real, i // perquè en el futur podria disparar-se per triggers d'àrea sense // jugador específic. virtual void onPlayerContact(Player* player); // Hook que el `EntityManager` invoca per a cada entitat cada vegada // que un tile es pinta (via PaintLayer, com a conseqüència d'un // jugador trepitjant un sòl pintable). Només `Sarcophagus` ho consumeix // (per marcar perímetre); per defecte no-op. virtual void notifyTilePainted(int col, int row); [[nodiscard]] auto getName() const -> const std::string& { return name_; } [[nodiscard]] auto getTilePos() const -> TilePos { return tile_; } [[nodiscard]] auto getPixelRect() const -> SDL_FRect { return pixel_rect_; } [[nodiscard]] auto collides() const -> bool { return collides_; } [[nodiscard]] auto interactable() const -> bool { return interactable_; } // Associar el bus una sola vegada (propietat externa, p.ex. // `EntityManager`). Entitats que no publiquen events (Torch, trampes // estàtiques de moment) el poden ignorar. void setEventBus(EventBus* bus) { bus_ = bus; } // Assigna la caixa de render en píxels. La invoca `EntityManager` durant // `loadFromLevel` un cop sap la posició definitiva de l'entitat al mapa. void setPixelRect(SDL_FRect rect) { pixel_rect_ = rect; } // Assigna la surface per al dibuix. `shared_ptr` perquè les surfaces // viuen al `Resource::Cache` i es comparteixen. Si no s'assigna, // `render()` no fa res. void setSurface(std::shared_ptr surface) { surface_ = std::move(surface); } // Accessor públic (la viewable rep per tipus complet `shared_ptr`, però // el valor pot ser nul). L'usa `EntityManager::renderAll` per dibuixar // sense dependre de la dispatch virtual — el dibuix és centralitzat al // manager per tal de confinar la dependència cap a `Surface`. [[nodiscard]] auto getSurface() const -> const std::shared_ptr& { return surface_; } // Accessor del component d'animació. `EntityManager::renderAll` el // consulta per decidir quin frame del strip vertical clipa al blit. // Entitats sense animació retornen `nullopt` → frame 0 per defecte. [[nodiscard]] auto getAnimation() const -> const std::optional& { return anim_; } // Caixa de col·lisió: per defecte és el `pixel_rect_` sencer (entitats // AABB-aligned). Si s'ha configurat un `CollisionBox`, retorna el // sub-rect dins del sprite — vida útil: validar col·lisions amb el // jugador o el mapa sense que la caixa inclogui "l'alçada" del sprite. [[nodiscard]] auto getCollisionRect() const -> SDL_FRect; // Y dels peus: la vora inferior de la caixa de col·lisió. La usarà el // Z-order per ordenar entitats i jugador de menys a més profunditat // (REND-01, quan s'implemente). [[nodiscard]] auto getFeetY() const -> float; // El loader li assigna una caixa explícita llegida del YAML; sense // aquesta crida, l'entitat queda sense `CollisionBox` i la caixa de // col·lisió coincideix amb el sprite. void setCollisionBox(CollisionBox box) { collision_box_ = box; } // Accés a la caixa crua (offsets + mida) — el loader la consulta per // a col·locar el sprite quan `anchor: feet` ha d'alinear la caja (no // el sprite) amb el tile. Nullopt si l'entitat no en té. [[nodiscard]] auto getCollisionBox() const -> const std::optional& { return collision_box_; } // Flip del sprite. Les entitats estàtiques mai no el toquen (queda a // SDL_FLIP_NONE); les dinàmiques que miren cap als costats (p. ex. // Mummy en direcció horitzontal) el canvien quan gira. El manager // el passa a Surface::render. [[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; } void setFlip(SDL_FlipMode flip) { flip_ = flip; } protected: std::string name_; TilePos tile_{}; SDL_FRect pixel_rect_{}; std::optional anim_; std::optional collision_box_; std::shared_ptr surface_{nullptr}; bool collides_{false}; bool interactable_{false}; SDL_FlipMode flip_{SDL_FLIP_NONE}; EventBus* bus_{nullptr}; };