store belts as buildings and fix issue that belts could not be selected
This commit is contained in:
@@ -241,24 +241,6 @@ EntityId BuildingSystem::place(BuildingType type, QPoint anchor,
|
|||||||
|
|
||||||
int BuildingSystem::demolish(EntityId id)
|
int BuildingSystem::demolish(EntityId id)
|
||||||
{
|
{
|
||||||
// Belt / splitter?
|
|
||||||
const std::map<EntityId, BeltEntry>::iterator beltIt = m_beltEntities.find(id);
|
|
||||||
if (beltIt != m_beltEntities.end())
|
|
||||||
{
|
|
||||||
const QPoint tile = beltIt->second.tile;
|
|
||||||
const BuildingType btype = beltIt->second.type;
|
|
||||||
m_belts.removeTile(tile);
|
|
||||||
m_tileOccupancy.erase({tile.x(), tile.y()});
|
|
||||||
m_beltEntities.erase(beltIt);
|
|
||||||
|
|
||||||
const BuildingDef* def = findBuildingDef(btype);
|
|
||||||
if (def)
|
|
||||||
{
|
|
||||||
return def->cost * m_config.world.refundPercentage / 100;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construction queue?
|
// Construction queue?
|
||||||
for (std::deque<ConstructionSite>::iterator it = m_constructionQueue.begin();
|
for (std::deque<ConstructionSite>::iterator it = m_constructionQueue.begin();
|
||||||
it != m_constructionQueue.end();
|
it != m_constructionQueue.end();
|
||||||
@@ -287,6 +269,10 @@ int BuildingSystem::demolish(EntityId id)
|
|||||||
{
|
{
|
||||||
if (it->id == id)
|
if (it->id == id)
|
||||||
{
|
{
|
||||||
|
if (it->type == BuildingType::Belt || it->type == BuildingType::Splitter)
|
||||||
|
{
|
||||||
|
m_belts.removeTile(it->anchor);
|
||||||
|
}
|
||||||
const BuildingDef* def = findBuildingDef(it->type);
|
const BuildingDef* def = findBuildingDef(it->type);
|
||||||
for (const QPoint& cell : it->bodyCells)
|
for (const QPoint& cell : it->bodyCells)
|
||||||
{
|
{
|
||||||
@@ -381,25 +367,7 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promote construction site — belts/splitters go into BeltSystem, others become Buildings.
|
// Promote construction site to an operational Building.
|
||||||
if (front.type == BuildingType::Belt)
|
|
||||||
{
|
|
||||||
m_belts.placeBelt(front.anchor, front.rotation);
|
|
||||||
m_beltEntities[front.id] = BeltEntry{front.anchor, BuildingType::Belt, front.rotation, front.rotation};
|
|
||||||
}
|
|
||||||
else if (front.type == BuildingType::Splitter)
|
|
||||||
{
|
|
||||||
const BuildingDef* def = findBuildingDef(front.type);
|
|
||||||
assert(def != nullptr);
|
|
||||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, front.rotation);
|
|
||||||
assert(mask.outputPorts.size() >= 2);
|
|
||||||
const Rotation outA = mask.outputPorts[0].direction;
|
|
||||||
const Rotation outB = mask.outputPorts[1].direction;
|
|
||||||
m_belts.placeSplitter(front.anchor, outA, outB);
|
|
||||||
m_beltEntities[front.id] = BeltEntry{front.anchor, BuildingType::Splitter, outA, outB};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const BuildingDef* def = findBuildingDef(front.type);
|
const BuildingDef* def = findBuildingDef(front.type);
|
||||||
const ParsedSurfaceMask mask = parseSurfaceMask(
|
const ParsedSurfaceMask mask = parseSurfaceMask(
|
||||||
def ? def->surfaceMask : std::vector<std::string>{},
|
def ? def->surfaceMask : std::vector<std::string>{},
|
||||||
@@ -444,8 +412,20 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_buildings.push_back(std::move(building));
|
// Register with BeltSystem before the move (mask stays valid).
|
||||||
|
if (front.type == BuildingType::Belt)
|
||||||
|
{
|
||||||
|
m_belts.placeBelt(front.anchor, front.rotation);
|
||||||
}
|
}
|
||||||
|
else if (front.type == BuildingType::Splitter)
|
||||||
|
{
|
||||||
|
assert(mask.outputPorts.size() >= 2);
|
||||||
|
m_belts.placeSplitter(front.anchor,
|
||||||
|
mask.outputPorts[0].direction,
|
||||||
|
mask.outputPorts[1].direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buildings.push_back(std::move(building));
|
||||||
|
|
||||||
m_constructionQueue.pop_front();
|
m_constructionQueue.pop_front();
|
||||||
|
|
||||||
@@ -774,15 +754,30 @@ std::vector<ConstructionSite> BuildingSystem::allSites() const
|
|||||||
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::allBeltTiles() const
|
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::allBeltTiles() const
|
||||||
{
|
{
|
||||||
std::vector<BeltTileInfo> result;
|
std::vector<BeltTileInfo> result;
|
||||||
result.reserve(m_beltEntities.size());
|
for (const Building& b : m_buildings)
|
||||||
for (const std::map<EntityId, BeltEntry>::value_type& kv : m_beltEntities)
|
|
||||||
{
|
{
|
||||||
|
if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
BeltTileInfo info;
|
BeltTileInfo info;
|
||||||
info.id = kv.first;
|
info.id = b.id;
|
||||||
info.tile = kv.second.tile;
|
info.tile = b.bodyCells.empty() ? b.anchor : b.bodyCells[0];
|
||||||
info.type = kv.second.type;
|
info.type = b.type;
|
||||||
info.directionA = kv.second.directionA;
|
if (!b.outputPorts.empty())
|
||||||
info.directionB = kv.second.directionB;
|
{
|
||||||
|
info.directionA = b.outputPorts[0].direction;
|
||||||
|
info.directionB = b.outputPorts[0].direction;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.directionA = b.rotation;
|
||||||
|
info.directionB = b.rotation;
|
||||||
|
}
|
||||||
|
if (b.type == BuildingType::Splitter && b.outputPorts.size() >= 2)
|
||||||
|
{
|
||||||
|
info.directionB = b.outputPorts[1].direction;
|
||||||
|
}
|
||||||
result.push_back(info);
|
result.push_back(info);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -907,6 +902,10 @@ bool BuildingSystem::removeBuilding(EntityId id)
|
|||||||
{
|
{
|
||||||
if (it->id == id)
|
if (it->id == id)
|
||||||
{
|
{
|
||||||
|
if (it->type == BuildingType::Belt || it->type == BuildingType::Splitter)
|
||||||
|
{
|
||||||
|
m_belts.removeTile(it->anchor);
|
||||||
|
}
|
||||||
for (const QPoint& cell : it->bodyCells)
|
for (const QPoint& cell : it->bodyCells)
|
||||||
{
|
{
|
||||||
m_tileOccupancy.erase({cell.x(), cell.y()});
|
m_tileOccupancy.erase({cell.x(), cell.y()});
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
// Manages building placement, construction queuing, and the per-tick
|
// Manages building placement, construction queuing, and the per-tick
|
||||||
// production loop (belt→building pull, production, building→belt push).
|
// production loop (belt→building pull, production, building→belt push).
|
||||||
// Belt and Splitter types are forwarded to BeltSystem rather than stored
|
// All types including Belt and Splitter are stored as Building instances;
|
||||||
// as Building instances.
|
// BeltSystem owns the per-tile simulation data (item slots, flow).
|
||||||
class BuildingSystem
|
class BuildingSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -106,14 +106,6 @@ public:
|
|||||||
void forEachBuilding(std::function<void(Building&)> fn);
|
void forEachBuilding(std::function<void(Building&)> fn);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct BeltEntry
|
|
||||||
{
|
|
||||||
QPoint tile;
|
|
||||||
BuildingType type; // Belt or Splitter
|
|
||||||
Rotation directionA; // Belt: its direction; Splitter: first output
|
|
||||||
Rotation directionB; // Splitter: second output; Belt: same as directionA
|
|
||||||
};
|
|
||||||
|
|
||||||
const BuildingDef* findBuildingDef(BuildingType type) const;
|
const BuildingDef* findBuildingDef(BuildingType type) const;
|
||||||
const RecipeDef* findRecipe(const std::string& id, BuildingType type) const;
|
const RecipeDef* findRecipe(const std::string& id, BuildingType type) const;
|
||||||
const ShipDef* findShipDef(const std::string& id) const;
|
const ShipDef* findShipDef(const std::string& id) const;
|
||||||
@@ -131,7 +123,6 @@ private:
|
|||||||
|
|
||||||
std::vector<Building> m_buildings;
|
std::vector<Building> m_buildings;
|
||||||
std::deque<ConstructionSite> m_constructionQueue;
|
std::deque<ConstructionSite> m_constructionQueue;
|
||||||
std::map<EntityId, BeltEntry> m_beltEntities;
|
|
||||||
|
|
||||||
// Maps every occupied body-cell coordinate to the entity that owns it.
|
// Maps every occupied body-cell coordinate to the entity that owns it.
|
||||||
std::map<std::pair<int, int>, EntityId> m_tileOccupancy;
|
std::map<std::pair<int, int>, EntityId> m_tileOccupancy;
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
|||||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
|
|
||||||
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
|
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
|
||||||
REQUIRE(bs.allBuildings().empty()); // belts do not create Building instances
|
REQUIRE(bs.allBuildings().size() == 1);
|
||||||
|
REQUIRE(bs.allBuildings()[0].type == BuildingType::Belt);
|
||||||
|
REQUIRE(bs.allBuildings()[0].anchor == QPoint(5, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
|
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
|
||||||
|
|||||||
@@ -396,10 +396,6 @@ EntityId GameWorldView::buildingAtTile(QPoint tile) const
|
|||||||
if (cell == tile) { return b.id; }
|
if (cell == tile) { return b.id; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const BuildingSystem::BeltTileInfo& info : m_sim->buildings().allBeltTiles())
|
|
||||||
{
|
|
||||||
if (info.tile == tile) { return info.id; }
|
|
||||||
}
|
|
||||||
return kInvalidEntityId;
|
return kInvalidEntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,10 +538,23 @@ void GameWorldView::drawBuildings(QPainter& painter)
|
|||||||
painter.fillRect(tileRect(cell), bv.fill);
|
painter.fillRect(tileRect(cell), bv.fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belts and splitters are 1×1 on their body cell; other buildings use
|
||||||
|
// the full footprint bounding box for their outline and glyph.
|
||||||
|
QRectF bboxRect;
|
||||||
|
const bool isBeltLike = (b.type == BuildingType::Belt
|
||||||
|
|| b.type == BuildingType::Splitter);
|
||||||
|
if (isBeltLike && !b.bodyCells.empty())
|
||||||
|
{
|
||||||
|
const QPointF tl = tileToWidget(b.bodyCells[0]);
|
||||||
|
bboxRect = QRectF(tl.x(), tl.y(), tilePx(), tilePx());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
const QPointF tl = tileToWidget(b.anchor);
|
const QPointF tl = tileToWidget(b.anchor);
|
||||||
const QRectF bboxRect(tl.x(), tl.y(),
|
bboxRect = QRectF(tl.x(), tl.y(),
|
||||||
b.footprint.width() * static_cast<qreal>(tilePx()),
|
b.footprint.width() * static_cast<qreal>(tilePx()),
|
||||||
b.footprint.height() * static_cast<qreal>(tilePx()));
|
b.footprint.height() * static_cast<qreal>(tilePx()));
|
||||||
|
}
|
||||||
|
|
||||||
painter.setPen(QPen(bv.outline, 1));
|
painter.setPen(QPen(bv.outline, 1));
|
||||||
painter.setBrush(Qt::NoBrush);
|
painter.setBrush(Qt::NoBrush);
|
||||||
@@ -576,27 +585,6 @@ void GameWorldView::drawBuildings(QPainter& painter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Belt and splitter tiles (stored separately from regular buildings)
|
|
||||||
for (const BuildingSystem::BeltTileInfo& info : m_sim->buildings().allBeltTiles())
|
|
||||||
{
|
|
||||||
const std::map<BuildingType, BuildingVisuals>::const_iterator it =
|
|
||||||
m_visuals->buildings.find(info.type);
|
|
||||||
if (it == m_visuals->buildings.end()) { continue; }
|
|
||||||
const BuildingVisuals& bv = it->second;
|
|
||||||
|
|
||||||
painter.setPen(Qt::NoPen);
|
|
||||||
painter.fillRect(tileRect(info.tile), bv.fill);
|
|
||||||
painter.setPen(QPen(bv.outline, 1));
|
|
||||||
painter.setBrush(Qt::NoBrush);
|
|
||||||
painter.drawRect(tileRect(info.tile));
|
|
||||||
|
|
||||||
drawPortGlyph(painter, info.tile, info.directionA, bv.outline);
|
|
||||||
if (info.type == BuildingType::Splitter)
|
|
||||||
{
|
|
||||||
drawPortGlyph(painter, info.tile, info.directionB, bv.outline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
painter.setOpacity(0.5);
|
painter.setOpacity(0.5);
|
||||||
for (const ConstructionSite& s : m_sim->buildings().allSites())
|
for (const ConstructionSite& s : m_sim->buildings().allSites())
|
||||||
{
|
{
|
||||||
@@ -782,17 +770,6 @@ void GameWorldView::drawOverlays(QPainter& painter)
|
|||||||
painter.fillRect(tileRect(cell), m_visuals->overlays.demolishTint);
|
painter.fillRect(tileRect(cell), m_visuals->overlays.demolishTint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
for (const BuildingSystem::BeltTileInfo& info : m_sim->buildings().allBeltTiles())
|
|
||||||
{
|
|
||||||
if (info.id == m_demolishHoverId)
|
|
||||||
{
|
|
||||||
painter.fillRect(tileRect(info.tile), m_visuals->overlays.demolishTint);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Box-select rectangle
|
// Box-select rectangle
|
||||||
|
|||||||
Reference in New Issue
Block a user