#include "FactoryQueries.h" #include #include #include "PortGeometry.h" #include "SurfaceMask.h" #include "Item.h" #include "ItemType.h" const Building* findBuilding(const FactoryState& state, BuildingId id) { for (const Building& building : state.buildings) { if (building.id == id) { return &building; } } return nullptr; } Building* findBuilding(FactoryState& state, BuildingId id) { for (Building& building : state.buildings) { if (building.id == id) { return &building; } } return nullptr; } const ConstructionSite* findSite(const FactoryState& state, BuildingId id) { for (const ConstructionSite& site : state.constructionQueue) { if (site.id == id) { return &site; } } return nullptr; } std::vector getAllBuildings(const FactoryState& state) { return state.buildings; } std::vector getAllSites(const FactoryState& state) { return std::vector(state.constructionQueue.begin(), state.constructionQueue.end()); } int getProductionBuildingCount(const FactoryState& state) { int count = 0; for (const Building& b : state.buildings) { if (isProductionBuildingType(b.type)) { ++count; } } return count; } int getActiveProductionBuildingCount(const FactoryState& state) { int count = 0; for (const Building& b : state.buildings) { if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; } } return count; } bool isTileOccupied(const FactoryState& state, QPoint tile) { return state.grid.isOccupied(tile); } bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id) { const Building* building = findBuilding(state, id); return building && building->queuedForDeconstruction; } const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, BuildingType type) { const Building* best = nullptr; float bestDist = std::numeric_limits::max(); for (const Building& b : state.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 deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId) { Building* bay = findBuilding(state, bayId); if (!bay || bay->type != BuildingType::SalvageBay) { return false; } if (bay->queuedForDeconstruction) { return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE) } // Emerging scrap still counts against the bay's holding capacity // (REQ-MAT-OUTPUT-EMERGE). if (bay->getOutputItemCount() >= bay->outputBuffer.capacity) { return false; } bay->outputBuffer.items.push_back(Item{ItemType{"scrap"}}); return true; } std::vector getInputPorts(const FactoryState& state, const GameConfig& config, BuildingId id) { if (const Building* building = findBuilding(state, id)) { return building->inputPorts; } if (const ConstructionSite* site = findSite(state, id)) { // A site stores no ports; derive its output ports from the mask (absolute) // and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS). const BuildingDef* def = config.buildings.findBuildingDef(site->type); if (def == nullptr) { return {}; } const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, site->rotation); std::vector outputPortsAbsolute; outputPortsAbsolute.reserve(mask.outputPorts.size()); for (const Port& port : mask.outputPorts) { outputPortsAbsolute.push_back(Port{ site->anchor + port.tile, port.direction }); } return computeInputPorts(site->bodyCells, outputPortsAbsolute); } return {}; } std::optional getSiteSplitterInfo(const FactoryState& state, const GameConfig& config, BuildingId id) { for (const ConstructionSite& site : state.constructionQueue) { if (site.id != id) { continue; } if (site.type != BuildingType::Splitter) { return std::nullopt; } const BuildingDef* def = config.buildings.findBuildingDef(site.type); const ParsedSurfaceMask mask = parseSurfaceMask( def ? def->surfaceMask : std::vector{}, site.rotation); if (mask.outputPorts.size() < 2) { return std::nullopt; } BeltSystem::SplitterInfo info; info.outputA = mask.outputPorts[0].direction; info.outputB = mask.outputPorts[1].direction; info.filterA = site.splitterFilterA; info.filterB = site.splitterFilterB; return info; } return std::nullopt; } std::vector buildingsInBox(const FactoryState& state, QPoint cornerA, QPoint cornerB) { const int x0 = std::min(cornerA.x(), cornerB.x()); const int y0 = std::min(cornerA.y(), cornerB.y()); const int x1 = std::max(cornerA.x(), cornerB.x()); const int y1 = std::max(cornerA.y(), cornerB.y()); const auto covers = [&](const std::vector& bodyCells) { for (const QPoint& cell : bodyCells) { if (cell.x() >= x0 && cell.x() <= x1 && cell.y() >= y0 && cell.y() <= y1) { return true; } } return false; }; std::vector ids; for (const Building& building : getAllBuildings(state)) { if (covers(building.bodyCells)) { ids.push_back(building.id); } } for (const ConstructionSite& site : getAllSites(state)) { if (covers(site.bodyCells)) { ids.push_back(site.id); } } return ids; } TunnelTileMap collectTunnelTiles(const FactoryState& state) { // Index every tunnel entry/exit — built or still a construction site — by its // single-cell tile, so a just-placed tunnel (not yet constructed) is matchable // (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT). TunnelTileMap tunnels; for (const Building& building : getAllBuildings(state)) { if (building.type == BuildingType::TunnelEntry || building.type == BuildingType::TunnelExit) { tunnels[building.anchor] = TunnelTileInfo{building.type, building.rotation}; } } for (const ConstructionSite& site : getAllSites(state)) { if (site.type == BuildingType::TunnelEntry || site.type == BuildingType::TunnelExit) { tunnels[site.anchor] = TunnelTileInfo{site.type, site.rotation}; } } return tunnels; }