diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 7822313..b5c7104 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -241,24 +241,6 @@ EntityId BuildingSystem::place(BuildingType type, QPoint anchor, int BuildingSystem::demolish(EntityId id) { - // Belt / splitter? - const std::map::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? for (std::deque::iterator it = m_constructionQueue.begin(); it != m_constructionQueue.end(); @@ -287,6 +269,10 @@ int BuildingSystem::demolish(EntityId 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); for (const QPoint& cell : it->bodyCells) { @@ -381,71 +367,65 @@ void BuildingSystem::tickConstruction(Tick currentTick) return; } - // Promote construction site — belts/splitters go into BeltSystem, others become Buildings. + // Promote construction site to an operational Building. + const BuildingDef* def = findBuildingDef(front.type); + const ParsedSurfaceMask mask = parseSurfaceMask( + def ? def->surfaceMask : std::vector{}, + front.rotation); + + Building building; + building.id = front.id; + building.anchor = front.anchor; + building.footprint = front.footprint; + building.rotation = front.rotation; + building.type = front.type; + building.hp = 100.0f; + building.maxHp = 100.0f; + building.recipeId = front.recipeId; + + for (const QPoint& cell : mask.bodyCells) + { + building.bodyCells.push_back(front.anchor + cell); + } + for (const Port& port : mask.outputPorts) + { + Port absPort; + absPort.tile = front.anchor + port.tile; + absPort.direction = port.direction; + building.outputPorts.push_back(absPort); + } + building.inputPorts = computeInputPorts(building); + + if (!building.recipeId.empty()) + { + if (building.type == BuildingType::Shipyard) + { + initShipyardBuffers(building); + } + else + { + const RecipeDef* recipe = findRecipe(building.recipeId, building.type); + if (recipe) + { + initBuffers(building, *recipe); + } + } + } + + // Register with BeltSystem before the move (mask stays valid). 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}; + m_belts.placeSplitter(front.anchor, + mask.outputPorts[0].direction, + mask.outputPorts[1].direction); } - else - { - const BuildingDef* def = findBuildingDef(front.type); - const ParsedSurfaceMask mask = parseSurfaceMask( - def ? def->surfaceMask : std::vector{}, - front.rotation); - Building building; - building.id = front.id; - building.anchor = front.anchor; - building.footprint = front.footprint; - building.rotation = front.rotation; - building.type = front.type; - building.hp = 100.0f; - building.maxHp = 100.0f; - building.recipeId = front.recipeId; - - for (const QPoint& cell : mask.bodyCells) - { - building.bodyCells.push_back(front.anchor + cell); - } - for (const Port& port : mask.outputPorts) - { - Port absPort; - absPort.tile = front.anchor + port.tile; - absPort.direction = port.direction; - building.outputPorts.push_back(absPort); - } - building.inputPorts = computeInputPorts(building); - - if (!building.recipeId.empty()) - { - if (building.type == BuildingType::Shipyard) - { - initShipyardBuffers(building); - } - else - { - const RecipeDef* recipe = findRecipe(building.recipeId, building.type); - if (recipe) - { - initBuffers(building, *recipe); - } - } - } - - m_buildings.push_back(std::move(building)); - } + m_buildings.push_back(std::move(building)); m_constructionQueue.pop_front(); @@ -774,15 +754,30 @@ std::vector BuildingSystem::allSites() const std::vector BuildingSystem::allBeltTiles() const { std::vector result; - result.reserve(m_beltEntities.size()); - for (const std::map::value_type& kv : m_beltEntities) + for (const Building& b : m_buildings) { + if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter) + { + continue; + } BeltTileInfo info; - info.id = kv.first; - info.tile = kv.second.tile; - info.type = kv.second.type; - info.directionA = kv.second.directionA; - info.directionB = kv.second.directionB; + info.id = b.id; + info.tile = b.bodyCells.empty() ? b.anchor : b.bodyCells[0]; + info.type = b.type; + if (!b.outputPorts.empty()) + { + 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); } return result; @@ -907,6 +902,10 @@ bool BuildingSystem::removeBuilding(EntityId 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) { m_tileOccupancy.erase({cell.x(), cell.y()}); diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index 7359da3..a06e4a2 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -23,8 +23,8 @@ // Manages building placement, construction queuing, and the per-tick // production loop (belt→building pull, production, building→belt push). -// Belt and Splitter types are forwarded to BeltSystem rather than stored -// as Building instances. +// All types including Belt and Splitter are stored as Building instances; +// BeltSystem owns the per-tile simulation data (item slots, flow). class BuildingSystem { public: @@ -106,14 +106,6 @@ public: void forEachBuilding(std::function fn); 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 RecipeDef* findRecipe(const std::string& id, BuildingType type) const; const ShipDef* findShipDef(const std::string& id) const; @@ -131,7 +123,6 @@ private: std::vector m_buildings; std::deque m_constructionQueue; - std::map m_beltEntities; // Maps every occupied body-cell coordinate to the entity that owns it. std::map, EntityId> m_tileOccupancy; diff --git a/src/test/BuildingTest.cpp b/src/test/BuildingTest.cpp index 959520f..d49b201 100644 --- a/src/test/BuildingTest.cpp +++ b/src/test/BuildingTest.cpp @@ -116,7 +116,9 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con runTicks(bs, belts, static_cast(secondsToTicks(1.0)) + 1, tick); 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]") diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index eb9cee0..bf3510f 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -396,10 +396,6 @@ EntityId GameWorldView::buildingAtTile(QPoint tile) const if (cell == tile) { return b.id; } } } - for (const BuildingSystem::BeltTileInfo& info : m_sim->buildings().allBeltTiles()) - { - if (info.tile == tile) { return info.id; } - } return kInvalidEntityId; } @@ -542,10 +538,23 @@ void GameWorldView::drawBuildings(QPainter& painter) painter.fillRect(tileRect(cell), bv.fill); } - const QPointF tl = tileToWidget(b.anchor); - const QRectF bboxRect(tl.x(), tl.y(), - b.footprint.width() * static_cast(tilePx()), - b.footprint.height() * static_cast(tilePx())); + // 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); + bboxRect = QRectF(tl.x(), tl.y(), + b.footprint.width() * static_cast(tilePx()), + b.footprint.height() * static_cast(tilePx())); + } painter.setPen(QPen(bv.outline, 1)); 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::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); 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); } } - 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