#include "BuildingSystem.h" #include #include #include #include #include "SurfaceMask.h" BuildingSystem::BuildingSystem(const GameConfig& config, BeltSystem& belts, std::function allocateId, std::function addBuildingBlocks, std::function&)> spawnShip, std::mt19937& rng) : m_config(config) , m_belts(belts) , m_allocateId(std::move(allocateId)) , m_addBuildingBlocks(std::move(addBuildingBlocks)) , m_spawnShip(std::move(spawnShip)) , m_rng(rng) { } // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- const BuildingDef* BuildingSystem::findBuildingDef(BuildingType type) const { for (const BuildingDef& def : m_config.buildings.buildings) { if (def.type == type) { return &def; } } return nullptr; } const RecipeDef* BuildingSystem::findRecipe(const std::string& id, BuildingType type) const { for (const RecipeDef& recipe : m_config.recipes.recipes) { if (recipe.id == id && recipe.building == type) { return &recipe; } } return nullptr; } const ShipDef* BuildingSystem::findShipDef(const std::string& id) const { for (const ShipDef& def : m_config.ships.ships) { if (def.id == id) { return &def; } } return nullptr; } const ModuleDef* BuildingSystem::findModuleDef(const std::string& id) const { for (const ModuleDef& def : m_config.modules.modules) { if (def.id == id) { return &def; } } return nullptr; } void BuildingSystem::initBuffers(Building& b, const RecipeDef& recipe) const { b.inputBuffer.counts.clear(); b.inputBuffer.caps.clear(); for (const RecipeIngredient& ing : recipe.inputs) { const ItemType type{ing.item}; b.inputBuffer.counts[type] = 0; b.inputBuffer.caps[type] = 2 * ing.amount; } b.outputBuffer.items.clear(); if (b.type == BuildingType::ReprocessingPlant) { // 1× max-per-roll (REQ-MAT-OUTPUT-BUFFER-REPROCESSING). int maxAmount = 0; for (const RecipeOutput& out : recipe.outputs) { if (out.amount > maxAmount) { maxAmount = out.amount; } } b.outputBuffer.capacity = maxAmount; } else { // 2× per-cycle output. int totalAmount = 0; for (const RecipeOutput& out : recipe.outputs) { totalAmount += out.amount; } b.outputBuffer.capacity = 2 * totalAmount; } } void BuildingSystem::initShipyardBuffers(Building& b) const { b.inputBuffer.counts.clear(); b.inputBuffer.caps.clear(); b.outputBuffer.items.clear(); b.outputBuffer.capacity = 0; const ShipDef* def = findShipDef(b.recipeId); if (!def) { return; } for (const RecipeIngredient& ing : def->schematic.materials) { const ItemType type{ing.item}; b.inputBuffer.counts[type] = 0; b.inputBuffer.caps[type] = 2 * ing.amount; } if (b.shipLayout.has_value()) { for (const PlacedModule& pm : b.shipLayout->placedModules) { const ModuleDef* modDef = findModuleDef(pm.moduleId); if (!modDef) { continue; } for (const RecipeIngredient& ing : modDef->materials) { const ItemType type{ing.item}; b.inputBuffer.counts.try_emplace(type, 0); b.inputBuffer.caps[type] += 2 * ing.amount; } } } } std::vector BuildingSystem::computeInputPorts(const Building& b) const { // Build lookup sets for quick membership checks. std::set> bodySet; for (const QPoint& cell : b.bodyCells) { bodySet.insert({cell.x(), cell.y()}); } std::set> outputPortTiles; for (const Port& port : b.outputPorts) { outputPortTiles.insert({port.tile.x(), port.tile.y()}); } // Neighbour deltas and the corresponding "inward" belt direction. const int dx[4] = {-1, 1, 0, 0}; const int dy[4] = { 0, 0, -1, 1}; const Rotation inward[4] = { Rotation::East, // neighbour is to the West; belt flows East toward building Rotation::West, // neighbour is to the East; belt flows West toward building Rotation::South, // neighbour is above (row-1); belt flows South toward building Rotation::North // neighbour is below (row+1); belt flows North toward building }; std::set> seen; std::vector inputPorts; for (const QPoint& cell : b.bodyCells) { for (int i = 0; i < 4; ++i) { const int nx = cell.x() + dx[i]; const int ny = cell.y() + dy[i]; const std::pair neighbor = {nx, ny}; if (bodySet.count(neighbor)) { continue; } if (outputPortTiles.count(neighbor)){ continue; } if (seen.count(neighbor)) { continue; } seen.insert(neighbor); Port port; port.tile = QPoint(nx, ny); port.direction = inward[i]; inputPorts.push_back(port); } } return inputPorts; } std::vector BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe) { std::vector weights; weights.reserve(recipe.outputs.size()); for (const RecipeOutput& out : recipe.outputs) { weights.push_back(out.probability.value_or(1.0)); } std::discrete_distribution dist(weights.begin(), weights.end()); const int idx = dist(m_rng); const RecipeOutput& chosen = recipe.outputs[static_cast(idx)]; std::vector result; Item item; item.type.id = chosen.item; for (int i = 0; i < chosen.amount; ++i) { result.push_back(item); } return result; } // --------------------------------------------------------------------------- // Placement // --------------------------------------------------------------------------- EntityId BuildingSystem::place(BuildingType type, QPoint anchor, Rotation rotation, Tick currentTick) { const EntityId id = m_allocateId(); const BuildingDef* def = findBuildingDef(type); assert(def != nullptr); const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation); // Record tile occupancy for body cells. for (const QPoint& cell : mask.bodyCells) { const QPoint absCell = anchor + cell; m_tileOccupancy[{absCell.x(), absCell.y()}] = id; } // Build construction site. ConstructionSite site; site.id = id; site.anchor = anchor; site.footprint = mask.footprint; site.rotation = rotation; site.type = type; for (const QPoint& cell : mask.bodyCells) { site.bodyCells.push_back(anchor + cell); } if (m_constructionQueue.empty()) { site.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds); } // else: completesAt remains 0 (queued, not yet started). m_constructionQueue.push_back(std::move(site)); return id; } // --------------------------------------------------------------------------- // Demolish // --------------------------------------------------------------------------- int BuildingSystem::demolish(EntityId id) { // Construction queue? for (std::deque::iterator it = m_constructionQueue.begin(); it != m_constructionQueue.end(); ++it) { if (it->id == id) { const BuildingDef* def = findBuildingDef(it->type); for (const QPoint& cell : it->bodyCells) { m_tileOccupancy.erase({cell.x(), cell.y()}); } m_constructionQueue.erase(it); if (def) { return def->cost; } return 0; } } // Operational building? for (std::vector::iterator it = m_buildings.begin(); it != m_buildings.end(); ++it) { if (it->id == id) { if (it->type == BuildingType::Belt || it->type == BuildingType::Splitter || it->type == BuildingType::TunnelEntry || it->type == BuildingType::TunnelExit) { m_belts.removeTile(it->anchor); } const BuildingDef* def = findBuildingDef(it->type); for (const QPoint& cell : it->bodyCells) { m_tileOccupancy.erase({cell.x(), cell.y()}); } m_buildings.erase(it); if (def) { return def->cost * m_config.world.refundPercentage / 100; } return 0; } } return 0; } // --------------------------------------------------------------------------- // Set recipe // --------------------------------------------------------------------------- void BuildingSystem::setRecipe(EntityId id, const std::string& recipeId) { // Construction site: store recipe for when building completes. for (ConstructionSite& site : m_constructionQueue) { if (site.id == id) { site.recipeId = recipeId; site.shipLayout = std::nullopt; return; } } // Operational building: clear buffers and re-init. for (Building& building : m_buildings) { if (building.id == id) { building.recipeId = recipeId; building.shipLayout = std::nullopt; building.inputBuffer.counts.clear(); building.inputBuffer.caps.clear(); building.outputBuffer.items.clear(); building.outputBuffer.capacity = 0; building.production = std::nullopt; if (!recipeId.empty()) { if (building.type == BuildingType::Shipyard) { initShipyardBuffers(building); } else { const RecipeDef* recipe = findRecipe(recipeId, building.type); if (recipe) { initBuffers(building, *recipe); } } } return; } } } void BuildingSystem::setShipLayout(EntityId id, const ShipLayoutConfig& layout) { for (ConstructionSite& site : m_constructionQueue) { if (site.id == id) { site.shipLayout = layout; return; } } for (Building& building : m_buildings) { if (building.id == id) { if (building.production.has_value()) { building.production = std::nullopt; } building.shipLayout = layout; building.inputBuffer.counts.clear(); building.inputBuffer.caps.clear(); building.outputBuffer.items.clear(); building.outputBuffer.capacity = 0; if (!building.recipeId.empty() && building.type == BuildingType::Shipyard) { initShipyardBuffers(building); } return; } } } // --------------------------------------------------------------------------- // Tick hooks // --------------------------------------------------------------------------- void BuildingSystem::tickConstruction(Tick currentTick) { if (m_constructionQueue.empty()) { return; } ConstructionSite& front = m_constructionQueue.front(); // Guard: if somehow the front site was never started, start it now. if (front.completesAt == 0) { const BuildingDef* def = findBuildingDef(front.type); if (def) { front.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds); } return; } if (currentTick < front.completesAt) { return; } // 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; building.shipLayout = front.shipLayout; 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); } else if (front.type == BuildingType::Splitter) { assert(mask.outputPorts.size() >= 2); m_belts.placeSplitter(front.anchor, mask.outputPorts[0].direction, mask.outputPorts[1].direction); } else if (front.type == BuildingType::TunnelEntry) { m_belts.placeTunnelEntry(front.anchor, front.rotation, m_config.world.tunnelMaxDistance); } else if (front.type == BuildingType::TunnelExit) { m_belts.placeTunnelExit(front.anchor, front.rotation); } m_buildings.push_back(std::move(building)); m_constructionQueue.pop_front(); // Start next queued site if present. if (!m_constructionQueue.empty() && m_constructionQueue.front().completesAt == 0) { const BuildingDef* nextDef = findBuildingDef(m_constructionQueue.front().type); if (nextDef) { m_constructionQueue.front().completesAt = currentTick + secondsToTicks(nextDef->constructionTimeSeconds); } } } void BuildingSystem::tickBeltPull() { for (Building& building : m_buildings) { // HQ: pull building_block items and add to global stock. if (building.type == BuildingType::Hq) { for (const Port& port : building.inputPorts) { const std::optional peeked = m_belts.peekItem(port); if (peeked && peeked->id == "building_block") { const std::optional taken = m_belts.tryTakeItem(port); if (taken) { m_addBuildingBlocks(1); } } } continue; } if (building.recipeId.empty()) { continue; } if (building.type != BuildingType::Shipyard) { const RecipeDef* recipe = findRecipe(building.recipeId, building.type); if (!recipe || recipe->inputs.empty()) { continue; } } for (const Port& port : building.inputPorts) { const std::optional peeked = m_belts.peekItem(port); if (!peeked) { continue; } const ItemType& type = *peeked; // Accept only if this type is a required input and buffer has space. const std::map::const_iterator capIt = building.inputBuffer.caps.find(type); if (capIt == building.inputBuffer.caps.end() || capIt->second == 0) { continue; } const int current = [&]() -> int { const std::map::const_iterator it = building.inputBuffer.counts.find(type); return (it != building.inputBuffer.counts.end()) ? it->second : 0; }(); if (current >= capIt->second) { continue; } const std::optional taken = m_belts.tryTakeItem(port); if (taken) { building.inputBuffer.counts[taken->type]++; } } } } void BuildingSystem::tickProduction(Tick currentTick) { for (Building& building : m_buildings) { // Skip types without a recipe-based production loop. if (building.type == BuildingType::Belt || building.type == BuildingType::Splitter || building.type == BuildingType::Shipyard || building.type == BuildingType::SalvageBay || building.type == BuildingType::Hq || building.type == BuildingType::PlayerDefenceStation || building.type == BuildingType::EnemyDefenceStation) { continue; } if (building.recipeId.empty()) { continue; } const RecipeDef* recipe = findRecipe(building.recipeId, building.type); if (!recipe) { continue; } // If a production cycle is active, check for completion. if (building.production) { if (currentTick >= building.production->completesAt) { for (const Item& item : building.production->chosenOutputs) { building.outputBuffer.items.push_back(item); } building.production = std::nullopt; } // Whether we just completed or are still running, do not start // another cycle in the same tick. continue; } // Idle: check if a new cycle can start. // 1. All required inputs present? bool inputsOk = true; for (const RecipeIngredient& ing : recipe->inputs) { const ItemType type{ing.item}; const std::map::const_iterator it = building.inputBuffer.counts.find(type); const int have = (it != building.inputBuffer.counts.end()) ? it->second : 0; if (have < ing.amount) { inputsOk = false; break; } } if (!inputsOk) { continue; } // 2. Determine chosen outputs (roll for reprocessing). std::vector chosen; if (building.type == BuildingType::ReprocessingPlant) { chosen = rollReprocessingOutput(*recipe); } else { for (const RecipeOutput& out : recipe->outputs) { Item item; item.type.id = out.item; for (int i = 0; i < out.amount; ++i) { chosen.push_back(item); } } } // 3. Output buffer has space for chosen outputs? const int newSize = static_cast(building.outputBuffer.items.size()) + static_cast(chosen.size()); if (newSize > building.outputBuffer.capacity) { continue; } // 4. Consume inputs and start cycle. for (const RecipeIngredient& ing : recipe->inputs) { building.inputBuffer.counts[ItemType{ing.item}] -= ing.amount; } Production prod; prod.recipeId = building.recipeId; prod.completesAt = currentTick + secondsToTicks(recipe->durationSeconds); prod.chosenOutputs = std::move(chosen); building.production = std::move(prod); } } void BuildingSystem::tickShipyardProduction(Tick currentTick) { for (Building& building : m_buildings) { if (building.type != BuildingType::Shipyard) { continue; } if (building.recipeId.empty()) { continue; } const ShipDef* shipDef = findShipDef(building.recipeId); if (!shipDef) { continue; } // If a cycle is in progress, check for completion. if (building.production) { if (currentTick >= building.production->completesAt) { if (!building.outputPorts.empty()) { const Port& p = building.outputPorts[0]; const QVector2D spawnPos(p.tile.x() + 0.5f, p.tile.y() + 0.5f); m_spawnShip(building.recipeId, spawnPos, building.shipLayout); } building.production = std::nullopt; } continue; } // Build combined materials list (base + modules). std::map requiredMaterials; for (const RecipeIngredient& ing : shipDef->schematic.materials) { requiredMaterials[ing.item] += ing.amount; } if (building.shipLayout.has_value()) { for (const PlacedModule& pm : building.shipLayout->placedModules) { const ModuleDef* modDef = findModuleDef(pm.moduleId); if (!modDef) { continue; } for (const RecipeIngredient& ing : modDef->materials) { requiredMaterials[ing.item] += ing.amount; } } } // Idle: check if all combined materials are available. bool inputsOk = true; for (const std::pair& req : requiredMaterials) { const ItemType type{req.first}; const std::map::const_iterator it = building.inputBuffer.counts.find(type); const int have = (it != building.inputBuffer.counts.end()) ? it->second : 0; if (have < req.second) { inputsOk = false; break; } } if (!inputsOk) { continue; } // Consume combined materials and start the production cycle. for (const std::pair& req : requiredMaterials) { building.inputBuffer.counts[ItemType{req.first}] -= req.second; } double totalTime = shipDef->schematic.productionTimeSeconds; if (building.shipLayout.has_value()) { for (const PlacedModule& pm : building.shipLayout->placedModules) { const ModuleDef* modDef = findModuleDef(pm.moduleId); if (modDef) { totalTime += modDef->productionTimeSeconds; } } } Production prod; prod.recipeId = building.recipeId; prod.completesAt = currentTick + secondsToTicks(totalTime); building.production = std::move(prod); } } void BuildingSystem::tickBeltPush() { for (Building& building : m_buildings) { if (building.outputBuffer.items.empty()) { continue; } for (const Port& outputPort : building.outputPorts) { if (building.outputBuffer.items.empty()) { break; } const Item item = building.outputBuffer.items.front(); if (m_belts.tryPutItem(outputPort.tile, item, outputPort.direction)) { building.outputBuffer.items.erase(building.outputBuffer.items.begin()); } } } } // --------------------------------------------------------------------------- // Queries // --------------------------------------------------------------------------- const Building* BuildingSystem::findBuilding(EntityId id) const { for (const Building& building : m_buildings) { if (building.id == id) { return &building; } } return nullptr; } const ConstructionSite* BuildingSystem::findSite(EntityId id) const { for (const ConstructionSite& site : m_constructionQueue) { if (site.id == id) { return &site; } } return nullptr; } std::vector BuildingSystem::allBuildings() const { return m_buildings; } std::vector BuildingSystem::allSites() const { return std::vector(m_constructionQueue.begin(), m_constructionQueue.end()); } std::vector BuildingSystem::allBeltTiles() const { std::vector result; for (const Building& b : m_buildings) { if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter) { continue; } BeltTileInfo info; 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; } bool BuildingSystem::isTileOccupied(QPoint tile) const { return m_tileOccupancy.count({tile.x(), tile.y()}) > 0; } std::optional 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 { const Building* best = nullptr; float bestDist = std::numeric_limits::max(); for (const Building& b : m_buildings) { if (b.type != type) { continue; } QVector2D center(b.anchor.x() + b.footprint.width() / 2.0f, b.anchor.y() + b.footprint.height() / 2.0f); float dist = (center - worldPos).length(); if (dist < bestDist) { bestDist = dist; best = &b; } } return best; } bool BuildingSystem::deliverScrapToSalvageBay(EntityId bayId) { Building* bay = nullptr; for (Building& b : m_buildings) { if (b.id == bayId) { bay = &b; break; } } if (!bay || bay->type != BuildingType::SalvageBay) { return false; } if (static_cast(bay->outputBuffer.items.size()) >= bay->outputBuffer.capacity) { return false; } bay->outputBuffer.items.push_back(Item{ItemType{"scrap"}}); return true; } void BuildingSystem::healBuilding(EntityId id, float amount) { for (Building& b : m_buildings) { if (b.id == id) { b.hp = std::min(b.hp + amount, b.maxHp); return; } } } void BuildingSystem::damageBuilding(EntityId id, float amount) { for (Building& b : m_buildings) { if (b.id == id) { b.hp -= amount; return; } } } EntityId BuildingSystem::placeImmediate(BuildingType type, const std::vector& surfaceMask, QPoint anchor, Rotation rotation, float hp, float maxHp) { const EntityId id = m_allocateId(); const ParsedSurfaceMask mask = parseSurfaceMask(surfaceMask, rotation); Building building; building.id = id; building.anchor = anchor; building.footprint = mask.footprint; building.rotation = rotation; building.type = type; building.hp = hp; building.maxHp = maxHp; for (const QPoint& cell : mask.bodyCells) { const QPoint absCell = anchor + cell; building.bodyCells.push_back(absCell); m_tileOccupancy[{absCell.x(), absCell.y()}] = id; } for (const Port& port : mask.outputPorts) { Port absPort; absPort.tile = anchor + port.tile; absPort.direction = port.direction; building.outputPorts.push_back(absPort); } building.inputPorts = computeInputPorts(building); m_buildings.push_back(std::move(building)); return id; } bool BuildingSystem::removeBuilding(EntityId id) { for (std::vector::iterator it = m_buildings.begin(); it != m_buildings.end(); ++it) { if (it->id == id) { if (it->type == BuildingType::Belt || it->type == BuildingType::Splitter || it->type == BuildingType::TunnelEntry || it->type == BuildingType::TunnelExit) { m_belts.removeTile(it->anchor); } for (const QPoint& cell : it->bodyCells) { m_tileOccupancy.erase({cell.x(), cell.y()}); } m_buildings.erase(it); return true; } } return false; } void BuildingSystem::initStationWeapon(EntityId id, const StationWeapon& weapon) { for (Building& b : m_buildings) { if (b.id == id) { b.weapon = weapon; return; } } } void BuildingSystem::forEachBuilding(std::function fn) { for (Building& b : m_buildings) { fn(b); } } void BuildingSystem::registerTileOccupancy(const std::vector& cells, EntityId ownerPlaceholder) { for (const QPoint& cell : cells) { m_tileOccupancy[{cell.x(), cell.y()}] = ownerPlaceholder; } } void BuildingSystem::unregisterTileOccupancy(const std::vector& cells) { for (const QPoint& cell : cells) { m_tileOccupancy.erase({cell.x(), cell.y()}); } }