241 lines
7.0 KiB
C++
241 lines
7.0 KiB
C++
#include "FactoryQueries.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#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<Building> getAllBuildings(const FactoryState& state)
|
|
{
|
|
return state.buildings;
|
|
}
|
|
|
|
std::vector<ConstructionSite> getAllSites(const FactoryState& state)
|
|
{
|
|
return std::vector<ConstructionSite>(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<float>::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<Port> 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<Port> 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<BeltSystem::SplitterInfo>
|
|
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<std::string>{}, 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<BuildingId> 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<QPoint>& 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<BuildingId> 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;
|
|
}
|