move two placement queries out of the view into PlacementRules

This commit is contained in:
2026-08-05 20:47:38 +02:00
parent 3a1951559d
commit dc83add5c6
4 changed files with 129 additions and 84 deletions

View File

@@ -119,3 +119,82 @@ std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, con
return std::nullopt;
}
bool canPlaceBuilding(const FactoryState& state, const GameConfig& config,
BuildingType type, QPoint anchor, Rotation rotation)
{
// Terrain and world-bounds validity first (REQ-BLD-PLACE-VALID); occupancy is
// the extra rule this adds.
if (!isPlacementValid(state, config, type, anchor, rotation))
{
return false;
}
const BuildingDef* def = config.buildings.findBuildingDef(type);
if (!def) { return false; }
const ParsedSurfaceMask parsed = parseSurfaceMask(def->surfaceMask, rotation);
bool anyOccupied = false;
for (const QPoint& relativeCell : parsed.bodyCells)
{
if (isTileOccupied(state, anchor + relativeCell))
{
anyOccupied = true;
break;
}
}
if (anyOccupied)
{
// Occupied is still placeable when what is there is the same building being
// re-oriented (REQ-BLD-ROTATE-IN-PLACE).
return findRotateInPlaceTarget(state, config, type, anchor, rotation).has_value();
}
return true;
}
std::vector<BeltDragResolved> resolveBeltDragPath(const std::vector<BeltPathTile>& path,
const FactoryState& state,
const GameConfig& config,
int buildingBlocksStock)
{
std::vector<BeltDragResolved> resolved;
resolved.reserve(path.size());
const BuildingDef* def = config.buildings.findBuildingDef(BuildingType::Belt);
const int beltCost = (def != nullptr) ? def->cost : 0;
int spent = 0;
for (const BeltPathTile& entry : path)
{
BeltDragResolved item;
const std::optional<BuildingId> rotateTarget =
findRotateInPlaceTarget(state, config, BuildingType::Belt,
entry.tile, entry.rotation);
if (rotateTarget.has_value())
{
// A tile holding only a belt (or belt site) is re-oriented, no cost.
item.action = BeltTileAction::RotateInPlace;
item.affordable = true;
item.rotateId = rotateTarget;
}
else if (canPlaceBuilding(state, config, BuildingType::Belt,
entry.tile, entry.rotation))
{
// Empty, valid cell: a new belt, subject to cumulative affordability.
item.action = BeltTileAction::PlaceNew;
item.affordable = (spent + beltCost <= buildingBlocksStock);
item.rotateId = std::nullopt;
if (item.affordable) { spent += beltCost; }
}
else
{
// Occupied by a non-belt building/site, or otherwise invalid terrain.
item.action = BeltTileAction::Invalid;
item.affordable = false;
item.rotateId = std::nullopt;
}
resolved.push_back(item);
}
return resolved;
}