#pragma once #ifdef _DEBUG #include #include #include #include class Entity; class EntityManager; class Surface; class Text; 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 i les entitats al YAML // (writers surgicals) i notifica. Si la persistència falla, emet la // notificació d'error. void exit(Tilemap& tilemap, const EntityManager& entities); // Helper: enter() si no actiu, exit() si actiu. Útil des de GameScene // quan detecta la tecla de toggle. void toggle(Tilemap& tilemap, const EntityManager& entities); [[nodiscard]] auto active() const -> bool { return active_; } // Cert quan, en mode editor, GameScene ha de pintar les entitats // de la sala. Falses en mode TILE (volem veure el tilemap pelat // per pintar-lo) i quan hi ha un picker obert (la caixa del picker // ja tapa la pantalla i el soroll visual addicional sobra). [[nodiscard]] auto shouldShowEntities() const -> bool { return active_ && mode_ != Mode::TILE && picker_ == Picker::NONE; } // 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(const Tilemap& tilemap, 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. Cas especial per a la porta: // el sprite ocupa 3 cols (col-1, col, col+1) sobre el mur superior; // per tant la columna central vàlida és [1, cols-2] — `tilemap` és // necessari per saber `cols`. auto tryMoveSelected(const Tilemap& tilemap, EntityManager& entities) const -> bool; // Sub-handlers de `updateHand`. Aïllats per mantenir baixa la // complexitat cognitiva del bucle d'esdeveniments. void handleHandLeftClick(const Tilemap& tilemap, 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); // HUD d'edició: franja superior amb mode, brush, cursor i comptadors // de constraints. Substitueix el HUD del joc mentre l'editor és actiu. void renderEditorHud(const EntityManager& entities) const; // Carrega la font 8bithud del cache. Lazy: al primer enter(). void ensureHudFont(); 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 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 editable_entities_; // Font compartida amb el Notifier/HUD: el surface el carrega Director // a l'arrencada, ací només prenem la referència del cache. shared_ptr // perquè `Text` accepta el surface per shared_ptr i així no copiem. std::shared_ptr hud_font_; // 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