Implement config-driven unlock groups so that multiple things can be unlocked at once (including buildings)

This commit is contained in:
2026-07-22 21:34:59 +02:00
parent a8a6a04f1e
commit a9082c57f3
36 changed files with 1005 additions and 543 deletions

View File

@@ -67,6 +67,7 @@
#include "BuilderModeExitedEvent.h"
#include "BlueprintModeExitedEvent.h"
#include "BuildingBlocksChangedEvent.h"
#include "UnlockedBuildingsChangedEvent.h"
#include "ExpansionCostChangedEvent.h"
#include "GameSpeedChangedEvent.h"
#include "SchematicChoicesAvailableEvent.h"
@@ -367,6 +368,20 @@ void GameWorldView::onFrame()
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BossWaveUpdatedEvent>(newBoss, newCountdown));
}
// Unlocked building set changes only after a drop is applied or on Restart
// (REQ-LOCK-BUILDING); the build grid rebuilds its visible buttons.
int newUnlockedBuildingCount = 0;
for (const BuildingDef& def : m_sim->getConfig().buildings.buildings)
{
if (m_sim->isBuildingUnlocked(def.type)) { ++newUnlockedBuildingCount; }
}
if (newUnlockedBuildingCount != m_lastUnlockedBuildingCount)
{
m_lastUnlockedBuildingCount = newUnlockedBuildingCount;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<UnlockedBuildingsChangedEvent>());
}
}
// Sim-state polls are input sources for the live game; in replay these are
@@ -801,13 +816,18 @@ void GameWorldView::placeBlueprintAtTile(QPoint center)
for (const BlueprintBuilding& bb : bp.buildings)
{
// Locked building types are excluded from this placement entirely
// (REQ-LOCK-BUILDING, REQ-LOCK-UI-BLUEPRINT): not validity-checked here.
if (!m_sim->isBuildingUnlocked(bb.type)) { continue; }
if (!isValidPlacement(bb.type, center + bb.offset, bb.rotation)) { return; }
}
// Cost only applies to buildings that are genuinely new (not rotate-in-place).
// Cost only applies to buildings that are genuinely new (not rotate-in-place),
// and excludes locked building types (REQ-LOCK-UI-BLUEPRINT).
int totalCost = 0;
for (const BlueprintBuilding& bb : bp.buildings)
{
if (!m_sim->isBuildingUnlocked(bb.type)) { continue; }
if (m_sim->getBuildings().findRotateInPlaceTarget(
bb.type, center + bb.offset, bb.rotation).has_value())
{
@@ -820,6 +840,7 @@ void GameWorldView::placeBlueprintAtTile(QPoint center)
for (const BlueprintBuilding& bb : bp.buildings)
{
if (!m_sim->isBuildingUnlocked(bb.type)) { continue; }
const QPoint anchor = center + bb.offset;
const std::optional<BuildingId> rotateTarget =
m_sim->getBuildings().findRotateInPlaceTarget(bb.type, anchor, bb.rotation);
@@ -1946,6 +1967,9 @@ void GameWorldView::drawOverlays(QPainter& painter)
{
for (const BlueprintBuilding& bb : m_blueprintMode->buildings)
{
// Locked building types are omitted from the blueprint (REQ-LOCK-BUILDING,
// REQ-LOCK-UI-BLUEPRINT), so they are not ghosted either.
if (!m_sim->isBuildingUnlocked(bb.type)) { continue; }
const QPoint anchor = m_blueprintGhostTile + bb.offset;
const bool valid = isValidPlacement(bb.type, anchor, bb.rotation);
drawBuildingGhost(painter, bb.type, anchor, bb.rotation, valid,