171 lines
6.8 KiB
C++
171 lines
6.8 KiB
C++
#pragma once
|
||
|
||
#ifdef _DEBUG
|
||
|
||
#include <cstdint>
|
||
#include <memory>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
class Entity;
|
||
class EntityManager;
|
||
class Surface;
|
||
class Tilemap;
|
||
|
||
// Mode editor modal del GameScene. Només disponible en Debug — en Release
|
||
// aquesta classe no existeix (el .cpp es compila a un object buit i cap
|
||
// codi del joc final en fa referència).
|
||
//
|
||
// L'editor NO gestiona la tecla de toggle pròpia (`editor_toggle`): això
|
||
// ho fa GameScene per poder decidir la coexistència amb la pausa F11
|
||
// (mútuament excloents). L'editor s'encarrega de:
|
||
// - Estat propi (mode actiu, brush per mode, picker obert).
|
||
// - Input del ratolí i tecles internes (`T`, `E`, `H`, `Supr`, `ESC`).
|
||
// - Render del border blau + brush preview + picker.
|
||
// - Persistència del tilemap i de les entitats al YAML del repo en sortir.
|
||
//
|
||
// Modes del cursor (`Mode`):
|
||
// - TILE : el clic esquerre pinta el `brush_tile_` al tilemap.
|
||
// - ENTITY : el clic esquerre col·loca una instància de `brush_entity_`.
|
||
// - HAND : selecciona/mou/borra entitats existents.
|
||
//
|
||
// Pickers (`Picker`):
|
||
// - TILE_PICKER (`T`) : selecciona índex de tile.
|
||
// - ENTITY_PICKER (`E`) : selecciona tipus d'entitat (nom registrat al
|
||
// factory).
|
||
// Mútuament excloents: obrir un tanca l'altre.
|
||
class Editor {
|
||
public:
|
||
// Entra al mode editor: notifica i marca `active=true`. La congelació
|
||
// d'entitats / jugador la gestiona GameScene consultant `active()`.
|
||
void enter();
|
||
|
||
// Surt del mode editor: persisteix el tilemap al YAML (writer surgical)
|
||
// i notifica. Si la persistència falla, emet la notificació d'error.
|
||
void exit(Tilemap& tilemap);
|
||
|
||
// Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene
|
||
// quan detecta la tecla de toggle.
|
||
void toggle(Tilemap& tilemap);
|
||
|
||
[[nodiscard]] auto active() const -> bool { return active_; }
|
||
|
||
// Processa input propi (brush, picker). No-op si no actiu.
|
||
// `entities` cal perquè els modes ENTITY i HAND modifiquen el manager
|
||
// en directe (col·loca/mou/borra entitats); en TILE no s'usa.
|
||
void update(int delta_ms, Tilemap& tilemap, EntityManager& entities);
|
||
|
||
// Pinta el border blau i, segons mode i picker, brush preview, marc
|
||
// d'entitat seleccionada o el picker. No-op si no actiu.
|
||
// `entities` és necessari per al marc d'entitat hovered/seleccionada
|
||
// en Mode::HAND; en altres modes no s'usa.
|
||
void render(const Tilemap& tilemap, const EntityManager& entities) const;
|
||
|
||
private:
|
||
// Mode del cursor: determina què fa el clic esquerre sobre el mapa.
|
||
enum class Mode : std::uint8_t { TILE,
|
||
ENTITY,
|
||
HAND };
|
||
|
||
// Quin picker està obert (si n'hi ha). Mútuament excloents.
|
||
enum class Picker : std::uint8_t { NONE,
|
||
TILE,
|
||
ENTITY };
|
||
|
||
// Consum de hotkeys pròpies (T, E, H, ESC). Modifica `mode_` / `picker_`.
|
||
// Es crida al principi d'`update` perquè altres handlers no vegin
|
||
// tecles ja interpretades pel propi editor.
|
||
void handleHotkeys();
|
||
|
||
// Aplica una tecla i retorna true si l'editor l'ha consumit. Aïllat
|
||
// per mantenir baixa la complexitat cognitiva de `handleHotkeys` —
|
||
// l'iteració del buffer queda neta i la lògica de transició viu en
|
||
// un sol lloc.
|
||
auto applyHotkey(int scancode) -> bool;
|
||
|
||
// Branques d'update segons (mode, picker). Només té sentit cridar-les
|
||
// des d'`update`.
|
||
void updateTileBrush(Tilemap& tilemap, int canvas_x, int canvas_y);
|
||
void updateEntityBrush(EntityManager& entities, int canvas_x, int canvas_y);
|
||
void updateHand(EntityManager& entities, int canvas_x, int canvas_y);
|
||
void updateTilePicker(const Tilemap& tilemap, int canvas_x, int canvas_y);
|
||
void updateEntityPicker(int canvas_x, int canvas_y);
|
||
|
||
// Intent de col·locació segons brush_entity_ a la cel·la del cursor.
|
||
// Construeix un node spawn sintètic, fa el snap concret per tipus
|
||
// (sarc → rejilla 4×5; door → fila del mur i, de moment, columna
|
||
// canònica) i comprova solapaments. No-op si la cel·la està ocupada
|
||
// o el snap no és vàlid. Retorna true si s'ha col·locat.
|
||
auto tryPlaceEntity(EntityManager& entities) -> bool;
|
||
|
||
// Mou l'entitat seleccionada a la cel·la del cursor: borra l'antiga
|
||
// i crida `addSpawn` amb la nova posició. Reusa la mateixa lògica
|
||
// de snap/solapament que la col·locació nova, excloent l'entitat
|
||
// que es mou del check de solapament.
|
||
auto tryMoveSelected(EntityManager& entities) const -> bool;
|
||
|
||
// Sub-handlers de `updateHand`. Aïllats per mantenir baixa la
|
||
// complexitat cognitiva del bucle d'esdeveniments.
|
||
void handleHandLeftClick(EntityManager& entities);
|
||
void handleHandRightClick();
|
||
|
||
// Borra l'entitat seleccionada. La validació de constraints
|
||
// (1 puerta, ≥1 sarc) es fa al pas 8.
|
||
void deleteSelected(EntityManager& entities);
|
||
|
||
// Render del marc d'una entitat: 1 px sobre el seu pixel_rect.
|
||
static void renderEntityFrame(const Entity& e, std::uint32_t argb);
|
||
|
||
void renderTilePicker(const Tilemap& tilemap) const;
|
||
void renderEntityPicker(const Tilemap& tilemap) const;
|
||
|
||
bool active_{false};
|
||
Mode mode_{Mode::TILE};
|
||
Picker picker_{Picker::NONE};
|
||
|
||
// Cursor en cel·les del tilemap. -1/-1 = fora del mapa.
|
||
int cursor_col_{-1};
|
||
int cursor_row_{-1};
|
||
|
||
// Brush actiu per Mode::TILE (índex al tileset; -1 = sense brush).
|
||
int brush_tile_{-1};
|
||
// Estat del TilePicker.
|
||
int tile_picker_scroll_{0};
|
||
int tile_picker_hover_{-1};
|
||
|
||
// Brush actiu per Mode::ENTITY (nom registrat al factory; "" = cap).
|
||
std::string brush_entity_;
|
||
// Estat de l'EntityPicker.
|
||
int entity_picker_scroll_{0};
|
||
int entity_picker_hover_{-1}; // Índex a la llista de noms.
|
||
|
||
// Entrada editable del picker. Cada item recorda el nom (clau de
|
||
// factory + YAML), el surface del sprite (frame 0 com a icona) i la
|
||
// mida de cada frame al GIF — necessària per clipar el primer frame
|
||
// quan el GIF és un strip multi-frame.
|
||
struct EditableEntity {
|
||
std::string name;
|
||
std::shared_ptr<Surface> sprite;
|
||
int frame_w{0};
|
||
int frame_h{0};
|
||
};
|
||
|
||
// Construïda lazy al primer enter() i preservada entre sessions:
|
||
// el cost és I/O + parse de N plantilles YAML, però `EntityLoader`
|
||
// ja les memoritza, així que rellegir només resoldria per sortida i
|
||
// entrada al editor — no val la pena.
|
||
void ensureEditableEntities();
|
||
|
||
std::vector<EditableEntity> editable_entities_;
|
||
|
||
// Selecció en Mode::HAND. Treballem amb índexs sobre el snapshot de
|
||
// `EntityManager::getEntities()` perquè és el que ens dóna l'API
|
||
// pública. Després de cada mutació (move/delete) ho recalculem; com
|
||
// que l'editor congela el joc, el snapshot només canvia per accions
|
||
// pròpies.
|
||
int hand_hover_idx_{-1};
|
||
int hand_selected_idx_{-1};
|
||
};
|
||
|
||
#endif // _DEBUG
|