#pragma once #include #include #include #include #include "core/rendering/sprite/animated_sprite.hpp" #include "game/map/map.hpp" class EventBus; class Player : public AnimatedSprite { public: // Mapeig de tecles per al moviment del jugador. Cada jugador local en pot // tenir unes diferents; així podem afegir-ne més sense tocar el codi del // Player, només instanciant amb Controls distints. struct Controls { SDL_Scancode up; SDL_Scancode down; SDL_Scancode left; SDL_Scancode right; }; // Geometria carregada des de `data/player/player.yaml`. `sprite_*` són // la mida del frame (sprite 12×24 amb perspectiva falsa: visualment, els // 12 píxels superiors són el cap i els 12 inferiors la base). // La caixa de col·lisió és un quadrat `collision_size` concèntric amb el // quadrat inferior del sprite (`base_size`). Els offsets els calculem // al load a partir d'aquests tres valors; el mapa de col·lisions i el // de dibuix són independents (el sprite pot sobresortir sense afectar). inline static int sprite_w = 0; inline static int sprite_h = 0; inline static int base_size = 0; inline static int collision_size = 0; inline static int collision_padding = 0; inline static int collision_x_offset = 0; inline static int collision_y_offset = 0; // Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor. static void loadFrom(const std::string& yaml_path); Player(const std::string& anim_key, float x, float y, const Controls& controls, const Map::Info& map_info, int paint_tile_index = -1); void update(float delta_time) override; // Y de la base del jugador (cara inferior de la caixa de colisió). Usada // per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere; // per sota → davant. [[nodiscard]] auto getFeetY() const -> float { return getPosY() + static_cast(collision_y_offset + collision_size); } // Injecta el bus del nivell. El Player publica `on_tile_entered` cada // vegada que el centre de la seua caixa de col·lisió entra en una // cel·la nova del tilemap, amb payload `PaintedTile{col,row,paint_tile_index_}`. // El PaintLayer filtra pintabilitat i re-publica `tile_painted` a qui // correspongui (sarcòfags). Si el bus no s'injecta, no emet res. void setEventBus(EventBus* bus) { bus_ = bus; } // Tile actual del jugador (centre de `baseBox()` convertit a // coordenades de tile del tilemap, usant Map::tile_size i // Map::tilemap_origin_*). Retorna (col, row). [[nodiscard]] auto getCurrentTile() const -> std::pair; // Rectangle de col·lisió del jugador, en coordenades del canvas. Usat // pel mapa de col·lisions i per les entitats que comproven contacte // amb el jugador (momies, trampes, porta). [[nodiscard]] auto getBaseBox() const -> SDL_FRect; // Override del hook de Sprite: `MovingSprite::tryMove` envia aquesta // caixa al callback de col·lisió. La base del Player és més petita // que el sprite (el cap pot sobresortir per murs/sarcòfags sense // bloquejar el moviment). [[nodiscard]] auto collisionBox() const -> SDL_FRect override { return getBaseBox(); } private: // Alias privat històric. Mantingut perquè viu dins de l'implementació // (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`. [[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); } // Clamp de la caixa de col·lisió al playfield (no al sprite sencer: // el cap pot sobresortir pel mur superior sense afectar el joc). void clampToPlayfield(); // Snap cap al passadís perpendicular a la direcció premuda. Si la caixa // de col·lisió està a ≤ collision_size/2 d'una lane, s'hi aproxima // gradualment (a velocitat de moviment). Permet canviar de direcció // sense haver d'alinear-se píxel-perfect en l'encreuament. Els dos // wrappers deleguen a `snapToLane` només canviant l'eix actiu. void snapToHorizontalLane(float delta_time); void snapToVerticalLane(float delta_time); // Nom d'animació per al `facing_` actual. Si `moving` és cert s'usa // la variant `walk*`, altrament la variant `stand*`. [[nodiscard]] auto animationName(bool moving) const -> const char*; void snapToLane( float delta_time, float base, float pitch, int max_idx, float (Player::*get_pos)() const, void (Player::*set_pos)(float)); Controls controls_; // Còpia plana de la geometria del mapa + punters a `collides`/ // `inside_playfield`. POD trivial; copiada al ctor i no muta durant // la vida del Player. Vol dir que aquest Player només és vàlid per // a un mapa concret — si el mapa canvia, la Scene ha de // reinstanciar-lo. Map::Info map_; // Direcció "cap on mira" el jugador. Governa quin conjunt d'animacions // s'aplica (lateral, amunt, avall) i si el sprite es flipeja o no. Els // moviments purament horitzontals o diagonals prenen `SIDE` (prevalença // lateral), els purament verticals fan `UP`/`DOWN`. En aturar-se es // preserva l'última orientació perquè la versió stand coincideixi. enum class Facing : std::uint8_t { SIDE, UP, DOWN }; Facing facing_{Facing::SIDE}; // Bus del nivell (opcional). Propietat externa (EntityManager); el // Player només en guarda un punter per publicar events. EventBus* bus_{nullptr}; // Últim tile publicat. Inicialitzats a un sentinel fora de rang perquè // el primer frame en què el bus estigui connectat ja emeti un event. int last_tile_col_{-9999}; int last_tile_row_{-9999}; // Índex de tile del tileset amb què aquest jugador pinta. Llegit del // YAML del mapa (`player1_paint_tile` / `player2_paint_tile`) i passat // al ctor per GameScene. -1 desactiva el pintat (p.ex. al test // d'arrencada o en un escenari que no vulgui feedback visual). int paint_tile_index_{-1}; };