depend on factory data instead of BuildingSystem in the AI path

This commit is contained in:
2026-08-05 06:44:49 +02:00
parent 46932e4abf
commit 0408336cf9
17 changed files with 238 additions and 126 deletions

View File

@@ -6,6 +6,7 @@
#include <random>
#include <set>
#include "FactoryQueries.h"
#include "PortGeometry.h"
#include "StateChecksum.h"
#include "SurfaceMask.h"
@@ -1264,87 +1265,37 @@ void BuildingSystem::forEachIncomingItem(
const Building* BuildingSystem::findBuilding(BuildingId id) const
{
for (const Building& building : m_state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
return ::findBuilding(m_state, id);
}
Building* BuildingSystem::findBuildingMutable(BuildingId id)
{
for (Building& building : m_state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
return ::findBuilding(m_state, id);
}
const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
{
for (const ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
return &site;
}
}
return nullptr;
return ::findSite(m_state, id);
}
std::vector<Building> BuildingSystem::getAllBuildings() const
{
return m_state.buildings;
return ::getAllBuildings(m_state);
}
std::vector<ConstructionSite> BuildingSystem::getAllSites() const
{
return std::vector<ConstructionSite>(m_state.constructionQueue.begin(),
m_state.constructionQueue.end());
return ::getAllSites(m_state);
}
namespace
{
bool isProductionBuildingType(BuildingType type)
{
switch (type)
{
case BuildingType::Miner:
case BuildingType::Smelter:
case BuildingType::Assembler:
case BuildingType::ReprocessingPlant:
case BuildingType::Shipyard:
return true;
default:
return false;
}
}
} // namespace
int BuildingSystem::getProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type)) { ++count; }
}
return count;
return ::getProductionBuildingCount(m_state);
}
int BuildingSystem::getActiveProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
}
return count;
return ::getActiveProductionBuildingCount(m_state);
}
std::vector<const RecipeDef*>
@@ -1522,7 +1473,7 @@ std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() cons
bool BuildingSystem::isTileOccupied(QPoint tile) const
{
return m_state.grid.isOccupied(tile);
return ::isTileOccupied(m_state, tile);
}
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
@@ -1643,53 +1594,12 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
BuildingType type) const
{
const Building* best = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (const Building& b : m_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;
return ::findNearestBuilding(m_state, worldPos, type);
}
bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
{
Building* bay = nullptr;
for (Building& b : m_state.buildings)
{
if (b.id == bayId)
{
bay = &b;
break;
}
}
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;
return ::deliverScrapToSalvageBay(m_state, bayId);
}
BuildingId BuildingSystem::placeImmediate(BuildingType type,

View File

@@ -14,6 +14,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h
@@ -39,6 +40,7 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsCalculator.cpp

View File

@@ -0,0 +1,122 @@
#include "FactoryQueries.h"
#include <limits>
#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);
}
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;
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <QPoint>
#include <QVector2D>
#include "Building.h"
#include "BuildingId.h"
#include "BuildingType.h"
#include "FactoryState.h"
// Queries and operations over the factory's world data that need nothing but that
// data — no config, no belts, no RNG. Free functions rather than BuildingSystem
// methods so that callers depend on the data they read instead of on the system
// that happens to tick it (see FactoryState.h).
//
// A helper belongs here only if it is a pure function of FactoryState. Anything
// needing GameConfig or the asteroid bound stays on BuildingSystem for now.
// The building with the given id, or nullptr when no building has it. Construction
// sites are not buildings yet — use findSite for those.
const Building* findBuilding(const FactoryState& state, BuildingId id);
Building* findBuilding(FactoryState& state, BuildingId id);
// The queued construction site with the given id, or nullptr.
const ConstructionSite* findSite(const FactoryState& state, BuildingId id);
std::vector<Building> getAllBuildings(const FactoryState& state);
std::vector<ConstructionSite> getAllSites(const FactoryState& state);
// REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed
// (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings.
int getProductionBuildingCount(const FactoryState& state);
// REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above that
// currently has an active production cycle.
int getActiveProductionBuildingCount(const FactoryState& state);
bool isTileOccupied(const FactoryState& state, QPoint tile);
// The nearest building of the given type to a world position, or nullptr when
// none exists. Distance is measured to the building's footprint centre.
const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,
BuildingType type);
// Hands one scrap to a Salvage Bay's output buffer (REQ-BLD-SALVAGE-BAY). Fails
// if the id is not a Salvage Bay, it is queued for deconstruction
// (REQ-BLD-DECON-QUEUE), or its holding capacity is already taken — emerging
// scrap counts against that capacity (REQ-MAT-OUTPUT-EMERGE).
bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId);

View File

@@ -260,10 +260,10 @@ void Simulation::tick()
m_shipSystem->clearMovementIntents();
// Score-based behavior selection: evaluate, select winner, execute (sets
// movement intent + preferred module targets only — no world mutation).
m_aiSystem->tick(m_admin, *m_buildingSystem, *m_debrisSystem);
m_aiSystem->tick(m_admin, m_factoryState, *m_debrisSystem);
// Module systems perform the world mutation (collection/delivery, healing).
// Each emits its tool beams and applies its own delayed (mid-beam) effects.
m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, *m_buildingSystem, m_beamFiredEvents);
m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, m_factoryState, m_beamFiredEvents);
m_repairSystem->tick(m_currentTick, m_beamFiredEvents);
// Step 8: combat resolution