allow to rotate buildings in place

This commit is contained in:
2026-04-29 21:40:00 +02:00
parent 7e0104e9b8
commit 2770bf96be
4 changed files with 395 additions and 7 deletions

View File

@@ -797,6 +797,112 @@ bool BuildingSystem::isTileOccupied(QPoint tile) const
return m_tileOccupancy.count({tile.x(), tile.y()}) > 0;
}
std::optional<EntityId> BuildingSystem::findRotateInPlaceTarget(
BuildingType type, QPoint anchor, Rotation rot) const
{
const BuildingDef* def = findBuildingDef(type);
if (!def) { return std::nullopt; }
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rot);
if (mask.bodyCells.empty()) { return std::nullopt; }
// All body cells must be occupied by the same entity.
const QPoint firstAbs = anchor + mask.bodyCells[0];
const auto firstIt = m_tileOccupancy.find({firstAbs.x(), firstAbs.y()});
if (firstIt == m_tileOccupancy.end()) { return std::nullopt; }
const EntityId candidateId = firstIt->second;
for (const QPoint& rel : mask.bodyCells)
{
const QPoint abs = anchor + rel;
const auto it = m_tileOccupancy.find({abs.x(), abs.y()});
if (it == m_tileOccupancy.end() || it->second != candidateId)
{
return std::nullopt;
}
}
// Verify the candidate is the same building type with the same cell count.
for (const ConstructionSite& site : m_constructionQueue)
{
if (site.id != candidateId) { continue; }
if (site.type != type) { return std::nullopt; }
if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId;
}
for (const Building& b : m_buildings)
{
if (b.id != candidateId) { continue; }
if (b.type != type) { return std::nullopt; }
if (b.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId;
}
return std::nullopt;
}
void BuildingSystem::rotateInPlace(EntityId id, Rotation newRotation)
{
// Construction site path — just update rotation; no ports to recompute.
for (ConstructionSite& site : m_constructionQueue)
{
if (site.id == id)
{
site.rotation = newRotation;
return;
}
}
// Operational building path.
for (Building& b : m_buildings)
{
if (b.id != id) { continue; }
b.rotation = newRotation;
const BuildingDef* def = findBuildingDef(b.type);
if (!def) { return; }
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, newRotation);
b.outputPorts.clear();
for (const Port& port : mask.outputPorts)
{
Port absPort;
absPort.tile = b.anchor + port.tile;
absPort.direction = port.direction;
b.outputPorts.push_back(absPort);
}
b.inputPorts = computeInputPorts(b);
// Re-register with BeltSystem (items on tile are discarded).
if (b.type == BuildingType::Belt)
{
m_belts.removeTile(b.anchor);
m_belts.placeBelt(b.anchor, newRotation);
}
else if (b.type == BuildingType::Splitter)
{
m_belts.removeTile(b.anchor);
assert(mask.outputPorts.size() >= 2);
m_belts.placeSplitter(b.anchor,
mask.outputPorts[0].direction,
mask.outputPorts[1].direction);
}
else if (b.type == BuildingType::TunnelEntry)
{
m_belts.removeTile(b.anchor);
m_belts.placeTunnelEntry(b.anchor, newRotation, m_config.world.tunnelMaxDistance);
}
else if (b.type == BuildingType::TunnelExit)
{
m_belts.removeTile(b.anchor);
m_belts.placeTunnelExit(b.anchor, newRotation);
}
return;
}
}
const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
BuildingType type) const
{