feat(editor): col·locació d'entitats amb snap per tipus i bloqueig de solapaments
This commit is contained in:
1 parent
94213f9fd6
commit
9b09ccb765
5 files changed
+165
-38
No files matched your search
+117
-11
@@ -5,6 +5,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "core/input/key_config.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
@@ -16,9 +17,12 @@
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/editor/editor_config.hpp"
|
||||
#include "game/entities/entity.hpp"
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_loader.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/levels/level_manager.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
#include "game/map/tilemap.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
#include "utils/defines.hpp"
|
||||
@@ -291,7 +295,7 @@ void Editor::handleHotkeys() {
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::update(int /*delta_ms*/, Tilemap& tilemap) {
|
||||
void Editor::update(int /*delta_ms*/, Tilemap& tilemap, EntityManager& entities) {
|
||||
if (!active_) { return; }
|
||||
|
||||
handleHotkeys();
|
||||
@@ -331,10 +335,10 @@ void Editor::update(int /*delta_ms*/, Tilemap& tilemap) {
|
||||
updateTileBrush(tilemap, CX, CY);
|
||||
break;
|
||||
case Mode::ENTITY:
|
||||
updateEntityBrush(CX, CY);
|
||||
updateEntityBrush(entities, CX, CY);
|
||||
break;
|
||||
case Mode::HAND:
|
||||
updateHand(CX, CY);
|
||||
updateHand(entities, CX, CY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -353,18 +357,120 @@ void Editor::updateTileBrush(Tilemap& tilemap, int /*canvas_x*/, int /*canvas_y*
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(readability-convert-member-functions-to-static)
|
||||
// Stubs: la lògica concreta es construeix als passos següents
|
||||
// (entity placement, hand mode). Mantenim la signatura per estabilitat
|
||||
// d'API i perquè update() pugui despatxar sense branques especials.
|
||||
void Editor::updateEntityBrush(int /*canvas_x*/, int /*canvas_y*/) {
|
||||
// Pas 5: col·locació amb snap per tipus + bloqueig de solapaments.
|
||||
namespace {
|
||||
|
||||
// Cel·la del tilemap → rect píxel d'una cel·la unitat.
|
||||
auto tileCellRect(int col, int row, int tile_size) -> SDL_FRect {
|
||||
return {
|
||||
.x = static_cast<float>((col * tile_size) + Map::tilemap_origin_x),
|
||||
.y = static_cast<float>((row * tile_size) + Map::tilemap_origin_y),
|
||||
.w = static_cast<float>(tile_size),
|
||||
.h = static_cast<float>(tile_size),
|
||||
};
|
||||
}
|
||||
|
||||
auto rectsOverlap(const SDL_FRect& a, const SDL_FRect& b) -> bool {
|
||||
return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
|
||||
}
|
||||
|
||||
// Per a un sarc viu, recupera la seva (sarc_col, sarc_row) invertint
|
||||
// `getCollisionRect` (que coincideix exactament amb `Map::getSarcRect`
|
||||
// — vegeu placeSarcophagus). No necessitem getters específics al
|
||||
// Sarcophagus: la geometria és reversible.
|
||||
auto sarcGridFromRect(const SDL_FRect& rect) -> std::pair<int, int> {
|
||||
if (Map::pitch_x <= 0 || Map::pitch_y <= 0) { return {-1, -1}; }
|
||||
const int SC = (static_cast<int>(rect.x) - Map::origin_x) / Map::pitch_x;
|
||||
const int SR = (static_cast<int>(rect.y) - Map::origin_y) / Map::pitch_y;
|
||||
return {SC, SR};
|
||||
}
|
||||
|
||||
// Cel·la del cursor → cel·la sarc (4×5). Retorna {-1,-1} si cau fora
|
||||
// de la rejilla. Cap dependència de l'editor: pur càlcul de Map.
|
||||
auto cursorToSarcCell(int cursor_col, int cursor_row) -> std::pair<int, int> {
|
||||
if (Map::pitch_x <= 0 || Map::pitch_y <= 0) { return {-1, -1}; }
|
||||
const int CURSOR_PX = (cursor_col * Map::tile_size) + Map::tilemap_origin_x;
|
||||
const int CURSOR_PY = (cursor_row * Map::tile_size) + Map::tilemap_origin_y;
|
||||
const int SC = (CURSOR_PX - Map::origin_x) / Map::pitch_x;
|
||||
const int SR = (CURSOR_PY - Map::origin_y) / Map::pitch_y;
|
||||
if (SC < 0 || SC >= Map::grid_cols || SR < 0 || SR >= Map::grid_rows) {
|
||||
return {-1, -1};
|
||||
}
|
||||
return {SC, SR};
|
||||
}
|
||||
|
||||
auto sarcAlreadyAt(const std::vector<Entity*>& existing, int sc, int sr) -> bool {
|
||||
return std::ranges::any_of(existing, [sc, sr](const Entity* e) {
|
||||
if (e->getName() != "sarcophagus") { return false; }
|
||||
const auto [ESC, ESR] = sarcGridFromRect(e->getCollisionRect());
|
||||
return ESC == sc && ESR == sr;
|
||||
});
|
||||
}
|
||||
|
||||
auto doorExists(const std::vector<Entity*>& existing) -> bool {
|
||||
return std::ranges::any_of(existing, [](const Entity* e) { return e->getName() == "door"; });
|
||||
}
|
||||
|
||||
auto cellOccupied(const std::vector<Entity*>& existing, const SDL_FRect& dest) -> bool {
|
||||
return std::ranges::any_of(existing,
|
||||
[&dest](const Entity* e) { return rectsOverlap(dest, e->getCollisionRect()); });
|
||||
}
|
||||
|
||||
// Composa el spawn YAML per al brush actual. Retorna empty si el snap
|
||||
// no és vàlid (fora de rejilla, solapament, o duplicat de porta).
|
||||
auto buildSpawnYaml(const std::string& brush, int cursor_col, int cursor_row, const std::vector<Entity*>& existing) -> std::string {
|
||||
std::ostringstream y;
|
||||
y << "type: " << brush << '\n';
|
||||
if (brush == "sarcophagus") {
|
||||
const auto [SC, SR] = cursorToSarcCell(cursor_col, cursor_row);
|
||||
if (SC < 0 || sarcAlreadyAt(existing, SC, SR)) { return {}; }
|
||||
y << "sarc_position: [" << SC << ", " << SR << "]\n";
|
||||
return y.str();
|
||||
}
|
||||
if (brush == "door") {
|
||||
if (doorExists(existing)) { return {}; }
|
||||
return y.str();
|
||||
}
|
||||
const auto DEST = tileCellRect(cursor_col, cursor_row, Map::tile_size);
|
||||
if (cellOccupied(existing, DEST)) { return {}; }
|
||||
y << "position: [" << cursor_col << ", " << cursor_row << "]\n";
|
||||
return y.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Editor::updateEntityBrush(EntityManager& entities, int /*canvas_x*/, int /*canvas_y*/) {
|
||||
for (const auto& ev : EventBuffer::events) {
|
||||
if (ev.type != SDL_EVENT_MOUSE_BUTTON_DOWN) { continue; }
|
||||
if (ev.button.button == SDL_BUTTON_LEFT) {
|
||||
tryPlaceEntity(entities);
|
||||
} else if (ev.button.button == SDL_BUTTON_RIGHT) {
|
||||
// Click dret: descarta el brush però es queda en Mode::ENTITY.
|
||||
// Per sortir cap a un altre mode, utilitza T (tile picker) o
|
||||
// H (mà): manté la simetria amb el comportament del brush
|
||||
// de tiles.
|
||||
brush_entity_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::updateHand(int /*canvas_x*/, int /*canvas_y*/) {
|
||||
// cppcheck-suppress constParameterReference
|
||||
// Manté firma simètrica amb les altres branques d'update; la lògica
|
||||
// real arriba al pas 6.
|
||||
void Editor::updateHand(EntityManager& /*entities*/, int /*canvas_x*/, int /*canvas_y*/) {
|
||||
// Pas 6: hover, select, move, delete, marc d'entitat seleccionada.
|
||||
}
|
||||
// NOLINTEND(readability-convert-member-functions-to-static)
|
||||
|
||||
auto Editor::tryPlaceEntity(EntityManager& entities) -> bool {
|
||||
if (brush_entity_.empty() || cursor_col_ < 0 || cursor_row_ < 0) { return false; }
|
||||
const auto STR = buildSpawnYaml(brush_entity_, cursor_col_, cursor_row_, entities.getEntities());
|
||||
if (STR.empty()) { return false; }
|
||||
try {
|
||||
auto node = fkyaml::node::deserialize(STR.begin(), STR.end());
|
||||
return entities.addSpawn(node) != nullptr;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::updateEntityPicker(int canvas_x, int canvas_y) {
|
||||
const auto LAY = layoutEntityPicker(static_cast<int>(editable_entities_.size()));
|
||||
|
||||
Reference in new issue
Block a user