fix issue where construction sites could be placed outside of game world and add tests
This commit is contained in:
@@ -235,12 +235,18 @@ std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe
|
||||
BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
Rotation rotation, Tick currentTick)
|
||||
{
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
|
||||
const BuildingDef* def = findBuildingDef(type);
|
||||
assert(def != nullptr);
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
|
||||
|
||||
// Reject placements that fall outside the world (REQ-BLD-PLACE-VALID).
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
}
|
||||
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
|
||||
// Record tile occupancy for body cells.
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
{
|
||||
@@ -270,6 +276,70 @@ BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
return id;
|
||||
}
|
||||
|
||||
bool BuildingSystem::bodyCellsWithinWorldBounds(const std::vector<QPoint>& bodyCells,
|
||||
QPoint anchor) const
|
||||
{
|
||||
const int heightTiles = m_config.world.heightTiles;
|
||||
const int leftEdgeX = -m_config.world.regions.asteroidWidth_tiles;
|
||||
for (const QPoint& cell : bodyCells)
|
||||
{
|
||||
const QPoint worldCell = anchor + cell;
|
||||
if (worldCell.y() < 0 || worldCell.y() >= heightTiles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (worldCell.x() < leftEdgeX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildingSystem::isPlacementValid(BuildingType type, QPoint anchor,
|
||||
Rotation rotation) const
|
||||
{
|
||||
const BuildingDef* def = findBuildingDef(type);
|
||||
if (def == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
|
||||
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Terrain: ship-dock (S) cells must sit in space (x >= 0); all other body
|
||||
// (A) cells must sit on the asteroid (x < 0). (REQ-BLD-PLACE-VALID)
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
{
|
||||
const QPoint worldCell = anchor + cell;
|
||||
bool isShipDock = false;
|
||||
for (const QPoint& dock : mask.shipDockCells)
|
||||
{
|
||||
if (dock == cell)
|
||||
{
|
||||
isShipDock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isShipDock)
|
||||
{
|
||||
if (worldCell.x() < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (worldCell.x() >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Demolish
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -40,11 +40,23 @@ public:
|
||||
std::mt19937& rng);
|
||||
|
||||
// -- Placement / demolish ------------------------------------------------
|
||||
// Returns the new entity id. Belt and Splitter register with BeltSystem
|
||||
// directly; other types enter the construction queue.
|
||||
// Returns the new entity id, or kInvalidBuildingId if the placement falls
|
||||
// outside the world bounds (vertical extent and asteroid left edge). Belt
|
||||
// and Splitter register with BeltSystem directly; other types enter the
|
||||
// construction queue. Terrain type (A vs S) is NOT checked here so that
|
||||
// tests can stage arbitrary layouts; the player-facing entry point
|
||||
// (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
|
||||
BuildingId place(BuildingType type, QPoint anchor, Rotation rotation,
|
||||
Tick currentTick);
|
||||
|
||||
// Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and
|
||||
// world-bounds rules: every ship-dock (S) cell sits in space (x >= 0), every
|
||||
// other body (A) cell sits on the asteroid (x < 0 and x >= the left edge),
|
||||
// and every cell has 0 <= y < world.height_tiles. There is no right-side
|
||||
// bound — space extends rightward. Tile occupancy is NOT checked here.
|
||||
bool isPlacementValid(BuildingType type, QPoint anchor,
|
||||
Rotation rotation) const;
|
||||
|
||||
// Remove a building or construction site by id. Returns the refund in
|
||||
// building blocks (floor(cost * refundPercentage / 100)). Returns 0 for
|
||||
// unknown ids.
|
||||
@@ -136,6 +148,9 @@ private:
|
||||
void initShipyardBuffers(Building& b) const;
|
||||
std::vector<Port> computeInputPorts(const Building& b) const;
|
||||
std::vector<Item> rollReprocessingOutput(const RecipeDef& recipe);
|
||||
bool bodyCellsWithinWorldBounds(
|
||||
const std::vector<QPoint>& bodyCells,
|
||||
QPoint anchor) const;
|
||||
|
||||
const GameConfig& m_config;
|
||||
BeltSystem& m_belts;
|
||||
|
||||
@@ -947,6 +947,11 @@ bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const
|
||||
|
||||
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||
{
|
||||
if (!m_buildingSystem->isPlacementValid(type, anchor, rotation))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
}
|
||||
|
||||
int cost = 0;
|
||||
for (const BuildingDef& def : m_config.buildings.buildings)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user