Implement config-driven unlock groups
Lift unlocking into first-class unlock groups defined in unlocks.toml, so one defence-station drop can grant a bundle of ships/modules/buildings/ recipes at once (e.g. the salvager module + salvage bay), and buildings can now be locked at game start and unlocked via a drop. Config: - New UnlocksConfig + ConfigLoader::loadUnlocks + validateUnlocks (unknown/ duplicate/non-assembler grants, unknown requires, empty group all fail load). - Drop unlock_at_station_level/unlock_requires from ship/module/recipe defs; add unlocked_at_start bool to recipes for graph-unreachable base recipes. Simulation: - Track awarded groups + per-building unlock state; an item starts unlocked iff no group grants it. Rework generateSchematicChoices/applySchematicChoice to operate on groups (grant all members at once). Extend state checksum. - isBuildingUnlocked query; tryPlaceBuilding rejects locked types. UI: - Build-menu buttons for locked buildings start hidden, revealed via a new UnlockedBuildingsChangedEvent (emitted from GameWorldView's poll loop). - Schematic choice dialog lists a group's granted items. - Blueprint placement excludes locked building types (REQ-LOCK-UI-BLUEPRINT). Data: unlocks.toml (app + test) reproduces the prior per-item gates as singleton groups and adds salvage_operations (salvager + salvage_bay, lvl 1) and reprocessing (reprocessing_plant, lvl 2). Tests: rewrote unlock/config/recipe-schematic/artifact/wave/shipyard tests for the group model; added UnlockGroupTest. All 443 cases pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
This commit is contained in:
@@ -82,6 +82,7 @@ BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWid
|
||||
std::make_shared<DemolishModeToggleRequestedEvent>());
|
||||
});
|
||||
|
||||
updateVisibility();
|
||||
registerForEvents();
|
||||
}
|
||||
|
||||
@@ -119,6 +120,17 @@ void BuildButtonGrid::updateAffordability()
|
||||
}
|
||||
}
|
||||
|
||||
void BuildButtonGrid::updateVisibility()
|
||||
{
|
||||
// A locked building type's button is hidden until its unlock group is awarded
|
||||
// (REQ-LOCK-BUILDING). Buttons keep their index so hotkeys/affordability stay
|
||||
// stable; they simply appear once the type is unlocked.
|
||||
for (std::size_t i = 0; i < m_buttons.size(); ++i)
|
||||
{
|
||||
m_buttons[i]->setVisible(m_sim->isBuildingUnlocked(m_types[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void BuildButtonGrid::clearActiveButton()
|
||||
{
|
||||
if (m_activeIndex)
|
||||
@@ -165,6 +177,12 @@ void BuildButtonGrid::handleEvent(std::shared_ptr<const BuildingBlocksChangedEve
|
||||
updateAffordability();
|
||||
}
|
||||
|
||||
void BuildButtonGrid::handleEvent(std::shared_ptr<const UnlockedBuildingsChangedEvent> /*event*/)
|
||||
{
|
||||
updateVisibility();
|
||||
updateAffordability();
|
||||
}
|
||||
|
||||
void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event)
|
||||
{
|
||||
m_demolishButton->setChecked(event->active);
|
||||
@@ -176,9 +194,10 @@ void BuildButtonGrid::handleEvent(std::shared_ptr<const BuildHotkeyPressedEvent>
|
||||
{
|
||||
if (m_types[i] == event->type)
|
||||
{
|
||||
// Equivalent to clicking the build button: a disabled (unaffordable)
|
||||
// button cannot be clicked, so the hotkey is likewise inert.
|
||||
if (m_buttons[i]->isEnabled())
|
||||
// Equivalent to clicking the build button: a disabled (unaffordable) or
|
||||
// hidden (locked, REQ-LOCK-BUILDING) button cannot be clicked, so the
|
||||
// hotkey is likewise inert.
|
||||
if (m_buttons[i]->isEnabled() && m_buttons[i]->isVisible())
|
||||
{
|
||||
onBuildButton(static_cast<int>(i));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user