implement ignore non-buildable buildings for blueprints

This commit is contained in:
2026-04-26 22:47:07 +02:00
parent ec9cbedf33
commit e5abc320a0
2 changed files with 89 additions and 2 deletions

View File

@@ -143,6 +143,28 @@ static GameConfig loadConfig()
return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR); return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR);
} }
// Mirrors BlueprintPanel::createBlueprintFromSelection's player-placeable filter:
// building types absent from buildings.toml (HQ, stations) or with playerPlaceable=false
// are silently excluded before the bounding-box center and offsets are computed.
static Blueprint buildBlueprintFiltered(const std::vector<BuildingSpec>& specs,
const GameConfig& cfg)
{
std::vector<BuildingSpec> filtered;
for (const BuildingSpec& s : specs)
{
for (const BuildingDef& def : cfg.buildings.buildings)
{
if (def.type == s.type)
{
if (def.playerPlaceable) { filtered.push_back(s); }
break;
}
}
// If the type is not in buildings (e.g. Hq, defence stations), it is skipped.
}
return buildBlueprint(filtered);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Offset computation // Offset computation
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -435,6 +457,51 @@ TEST_CASE("Blueprint: CCW rotation keeps belt adjacent to miner output port", "[
REQUIRE(bp.buildings[1].offset == bp.buildings[0].offset + QPoint(1, 0)); REQUIRE(bp.buildings[1].offset == bp.buildings[0].offset + QPoint(1, 0));
} }
// ---------------------------------------------------------------------------
// Player-placeable filter in blueprint creation
// ---------------------------------------------------------------------------
TEST_CASE("Blueprint creation: non-player-placeable building alone yields empty blueprint",
"[blueprint]")
{
const GameConfig cfg = loadConfig();
// Hq has no entry in buildings.toml, so it is treated as non-player-placeable.
const BuildingSpec hq{ QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Hq, Rotation::East };
const Blueprint bp = buildBlueprintFiltered({ hq }, cfg);
REQUIRE(bp.buildings.empty());
}
TEST_CASE("Blueprint creation: mixed selection keeps only player-placeable buildings",
"[blueprint]")
{
const GameConfig cfg = loadConfig();
const BuildingSpec belt{ QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Belt, Rotation::East };
const BuildingSpec hq { QPoint(-3, 0), {QPoint(-3, 0)}, BuildingType::Hq, Rotation::East };
const Blueprint bp = buildBlueprintFiltered({ belt, hq }, cfg);
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].type == BuildingType::Belt);
}
TEST_CASE("Blueprint creation: bounding box ignores non-player-placeable buildings",
"[blueprint]")
{
const GameConfig cfg = loadConfig();
// Belt at (-5, 0). HQ at (-3, 0) — excluded from the blueprint.
// If HQ were included: bboxX = [-5, -3], center.x = -4, belt offset = -1.
// With HQ excluded: bboxX = [-5, -5], center.x = -5, belt offset = 0.
const BuildingSpec belt{ QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Belt, Rotation::East };
const BuildingSpec hq { QPoint(-3, 0), {QPoint(-3, 0)}, BuildingType::Hq, Rotation::East };
const Blueprint bp = buildBlueprintFiltered({ belt, hq }, cfg);
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].offset == QPoint(0, 0));
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Simulation-level: blueprint placement places buildings at correct tiles // Simulation-level: blueprint placement places buildings at correct tiles
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -152,7 +152,15 @@ Blueprint BlueprintPanel::createBlueprintFromSelection() const
for (const EntityId id : m_selectedIds) for (const EntityId id : m_selectedIds)
{ {
const Building* b = m_sim->buildings().findBuilding(id); const Building* b = m_sim->buildings().findBuilding(id);
if (b) { entries.push_back({ b }); } if (!b) { continue; }
const bool placeable = [&]() {
for (const BuildingDef& def : m_config->buildings.buildings)
{
if (def.type == b->type) { return def.playerPlaceable; }
}
return false;
}();
if (placeable) { entries.push_back({ b }); }
} }
if (entries.empty()) { return Blueprint{}; } if (entries.empty()) { return Blueprint{}; }
@@ -235,7 +243,19 @@ void BlueprintPanel::rebuildButtons()
void BlueprintPanel::refreshButtonStates() void BlueprintPanel::refreshButtonStates()
{ {
m_createBtn->setEnabled(!m_selectedIds.empty()); const bool anyPlaceable = [&]() {
for (const EntityId id : m_selectedIds)
{
const Building* b = m_sim->buildings().findBuilding(id);
if (!b) { continue; }
for (const BuildingDef& def : m_config->buildings.buildings)
{
if (def.type == b->type) { return def.playerPlaceable; }
}
}
return false;
}();
m_createBtn->setEnabled(anyPlaceable);
for (int i = 0; i < static_cast<int>(m_blueprintButtons.size()); ++i) for (int i = 0; i < static_cast<int>(m_blueprintButtons.size()); ++i)
{ {