147 lines
5.7 KiB
C++
147 lines
5.7 KiB
C++
#include "building_editing/BuildingTarget.h"
|
|
|
|
#include "building/BuildingDef.h"
|
|
#include "building/PlacementWorld.h"
|
|
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
using namespace eve::building_editing;
|
|
|
|
namespace {
|
|
|
|
void registerHouse() {
|
|
eve::building::BuildingDefinition definition;
|
|
definition.id = "house";
|
|
definition.displayName = "House";
|
|
definition.footprintW = 2;
|
|
definition.footprintH = 2;
|
|
definition.rotationMode = "cardinal";
|
|
definition.tags = {"building", "home"};
|
|
eve::building::BuildingRegistry::registerBuilding(definition);
|
|
}
|
|
|
|
DomainOperation inverse(const DomainOperation& source) {
|
|
DomainOperation result = source;
|
|
result.type = source.inverseType;
|
|
result.payload = source.inverse;
|
|
return result;
|
|
}
|
|
|
|
BuildingInstanceSnapshot house(int id, int x, int y) {
|
|
BuildingInstanceSnapshot result;
|
|
result.instanceId = id;
|
|
result.buildingId = "house";
|
|
result.cellX = x;
|
|
result.cellY = y;
|
|
result.worldX = x * 10.0;
|
|
result.worldY = y * 10.0;
|
|
result.tags = {"building", "home"};
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("editor.building.preview_reports_snap_footprint_and_occupancy") {
|
|
registerHouse();
|
|
eve::building::PlacementWorld world(8, 8, 10.f);
|
|
BuildingPlacementTarget target("build-world", &world);
|
|
const auto preview = target.preview("house", 21.0, 31.0, 0.0, 91.0);
|
|
CHECK_EQ(static_cast<int>(preview.status), static_cast<int>(EditorStatus::Applied));
|
|
CHECK_EQ(preview.snappedCellX, 2);
|
|
CHECK_EQ(preview.snappedCellY, 3);
|
|
CHECK_EQ(preview.normalizedRotation, 90.0);
|
|
CHECK_EQ(preview.cells.size(), size_t{4});
|
|
for (const BuildingFootprintCell& cell : preview.cells) {
|
|
CHECK(cell.inBounds);
|
|
CHECK_EQ(cell.occupant, 0);
|
|
}
|
|
|
|
auto place = target.makePlace(house(target.nextAvailableInstanceId(), 2, 3));
|
|
REQUIRE(place.ok());
|
|
REQUIRE(target.applyDomainOperation(place.value()).ok());
|
|
const auto occupied = target.preview("house", 21.0, 31.0, 0.0, 0.0);
|
|
CHECK_EQ(static_cast<int>(occupied.status), static_cast<int>(EditorStatus::Rejected));
|
|
REQUIRE(!occupied.diagnostics.empty());
|
|
CHECK(occupied.cells[0].occupant > 0);
|
|
}
|
|
|
|
TEST_CASE("editor.building.place_move_remove_are_exactly_reversible") {
|
|
registerHouse();
|
|
eve::building::PlacementWorld world(12, 12, 10.f);
|
|
BuildingPlacementTarget target("reversible-world", &world);
|
|
BuildingInstanceSnapshot placed = house(77, 1, 1);
|
|
placed.properties["skin"] = "blue";
|
|
placed.garrison = {{"unit-1", "worker", {"builder"}}};
|
|
placed.garrisonRevision = 4;
|
|
auto place = target.makePlace(placed);
|
|
REQUIRE(place.ok());
|
|
REQUIRE(target.applyDomainOperation(place.value()).ok());
|
|
REQUIRE(world.hasBuilding(77));
|
|
CHECK_EQ(world.getBuildingProp(77, "skin"), "blue");
|
|
|
|
auto move = target.makeMove(77, 6, 5, 179.0);
|
|
REQUIRE(move.ok());
|
|
REQUIRE(target.applyDomainOperation(move.value()).ok());
|
|
CHECK_EQ(world.getBuildingCellX(77), 6);
|
|
CHECK_EQ(world.getBuildingCellY(77), 5);
|
|
CHECK_EQ(world.getBuildingRotation(77), 180.f);
|
|
REQUIRE(target.applyDomainOperation(inverse(move.value())).ok());
|
|
CHECK_EQ(world.getBuildingCellX(77), 1);
|
|
CHECK_EQ(world.getBuildingCellY(77), 1);
|
|
|
|
auto remove = target.makeRemove(77);
|
|
REQUIRE(remove.ok());
|
|
REQUIRE(target.applyDomainOperation(remove.value()).ok());
|
|
CHECK(!world.hasBuilding(77));
|
|
REQUIRE(target.applyDomainOperation(inverse(remove.value())).ok());
|
|
REQUIRE(world.hasBuilding(77));
|
|
const auto restored = target.instance(77);
|
|
REQUIRE(restored.ok());
|
|
CHECK_EQ(restored.value().properties.at("skin"), "blue");
|
|
CHECK_EQ(restored.value().garrison.size(), size_t{1});
|
|
CHECK_EQ(restored.value().garrison[0].id, "unit-1");
|
|
CHECK_EQ(restored.value().garrisonRevision, uint64_t{4});
|
|
}
|
|
|
|
TEST_CASE("editor.building.undo_restore_conflicts_instead_of_overwriting_occupancy") {
|
|
registerHouse();
|
|
eve::building::PlacementWorld world(8, 8, 10.f);
|
|
BuildingPlacementTarget target("conflict-world", &world);
|
|
auto first = target.makePlace(house(10, 1, 1));
|
|
REQUIRE(first.ok());
|
|
REQUIRE(target.applyDomainOperation(first.value()).ok());
|
|
auto remove = target.makeRemove(10);
|
|
REQUIRE(remove.ok());
|
|
REQUIRE(target.applyDomainOperation(remove.value()).ok());
|
|
auto replacement = target.makePlace(house(11, 1, 1));
|
|
REQUIRE(replacement.ok());
|
|
REQUIRE(target.applyDomainOperation(replacement.value()).ok());
|
|
const auto conflict = target.applyDomainOperation(inverse(remove.value()));
|
|
CHECK_EQ(static_cast<int>(conflict.code()), static_cast<int>(EditorStatus::Rejected));
|
|
CHECK(!world.hasBuilding(10));
|
|
CHECK(world.hasBuilding(11));
|
|
}
|
|
|
|
TEST_CASE("editor.building.snapshot_is_deterministic_and_complete") {
|
|
registerHouse();
|
|
eve::building::PlacementWorld world(8, 8, 10.f);
|
|
world.setId("settlement");
|
|
BuildingPlacementTarget target("snapshot-world", &world);
|
|
auto first = target.makePlace(house(3, 0, 0));
|
|
auto second = target.makePlace(house(9, 4, 4));
|
|
REQUIRE(first.ok());
|
|
REQUIRE(second.ok());
|
|
REQUIRE(target.applyDomainOperation(first.value()).ok());
|
|
REQUIRE(target.applyDomainOperation(second.value()).ok());
|
|
const EditorValue snapshot = target.snapshotValue();
|
|
const auto* root = snapshot.getIf<EditorValue::Object>();
|
|
REQUIRE(root);
|
|
CHECK_EQ(*root->at("worldId").getIf<std::string>(), "settlement");
|
|
const auto* instances = root->at("instances").getIf<EditorValue::Array>();
|
|
REQUIRE(instances);
|
|
CHECK_EQ(instances->size(), size_t{2});
|
|
CHECK_EQ(*instances->at(0).getIf<EditorValue::Object>()->at("instanceId").getIf<int64_t>(), int64_t{3});
|
|
CHECK_EQ(*instances->at(1).getIf<EditorValue::Object>()->at("instanceId").getIf<int64_t>(), int64_t{9});
|
|
}
|