allow to rotate buildings in place
This commit is contained in:
@@ -535,3 +535,245 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
||||
// No new production: inputs were consumed and not replenished.
|
||||
REQUIRE_FALSE(b->production.has_value());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// findRotateInPlaceTarget
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is empty",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
REQUIRE_FALSE(
|
||||
bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::East).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a queued belt (same type, different rotation)",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
const std::optional<EntityId> result =
|
||||
bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::North);
|
||||
REQUIRE(result.has_value());
|
||||
REQUIRE(*result == id);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a completed operational belt",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
REQUIRE(bs.allSites().empty());
|
||||
|
||||
const std::optional<EntityId> result =
|
||||
bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::South);
|
||||
REQUIRE(result.has_value());
|
||||
REQUIRE(*result == id);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building type differs",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
// Querying with Splitter at the same tile — type mismatch → nullopt.
|
||||
REQUIRE_FALSE(
|
||||
bs.findRotateInPlaceTarget(BuildingType::Splitter, QPoint(0, 0), Rotation::East).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprints only partially overlap",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
// Smelter at (0,0) occupies body tiles (0,0),(1,0),(0,1),(1,1).
|
||||
bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
// Ghost anchored at (1,0) would cover (1,0),(2,0),(1,1),(2,1):
|
||||
// only (1,0) and (1,1) are occupied — not a full coincidence.
|
||||
REQUIRE_FALSE(
|
||||
bs.findRotateInPlaceTarget(BuildingType::Smelter, QPoint(1, 0), Rotation::East).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-tile building with rotated ghost",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
// Smelter is a fully filled 2×2 footprint — rotating the ghost produces the
|
||||
// same four body tiles, so findRotateInPlaceTarget must still return the id.
|
||||
const EntityId id = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
const std::optional<EntityId> result =
|
||||
bs.findRotateInPlaceTarget(BuildingType::Smelter, QPoint(0, 0), Rotation::North);
|
||||
REQUIRE(result.has_value());
|
||||
REQUIRE(*result == id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// rotateInPlace
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a construction site",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
REQUIRE(bs.findSite(id)->rotation == Rotation::East);
|
||||
|
||||
bs.rotateInPlace(id, Rotation::North);
|
||||
|
||||
REQUIRE(bs.findSite(id)->rotation == Rotation::North);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of a queued site",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
const Tick completesAt = bs.findSite(id)->completesAt;
|
||||
REQUIRE(completesAt > 0);
|
||||
|
||||
bs.rotateInPlace(id, Rotation::South);
|
||||
|
||||
REQUIRE(bs.findSite(id)->completesAt == completesAt);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direction on an operational building",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
REQUIRE(bs.findBuilding(id) != nullptr);
|
||||
|
||||
const Building& before = *bs.findBuilding(id);
|
||||
REQUIRE(before.outputPorts[0].direction == Rotation::East);
|
||||
|
||||
bs.rotateInPlace(id, Rotation::North);
|
||||
|
||||
const Building& after = *bs.findBuilding(id);
|
||||
REQUIRE(after.rotation == Rotation::North);
|
||||
REQUIRE(after.outputPorts[0].direction == Rotation::North);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSystem so it still accepts items",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
EntityId nextId = 1;
|
||||
BuildingSystem bs(cfg, belts,
|
||||
[&nextId]() { return nextId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D) {},
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
|
||||
bs.rotateInPlace(id, Rotation::North);
|
||||
|
||||
// Belt tile must still be registered after rotation — items can be placed on it.
|
||||
REQUIRE(belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore")));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user