#pragma once #include #include #include #include #include "BuildingType.h" #include "GameConfig.h" #include "SchematicChoiceOption.h" class Hasher; // Owns schematic/unlock bookkeeping for one run: which ship, module, and // building schematics are unlocked (REQ-LOCK-EXPLICIT), which assembler recipe // schematics have been explicitly granted, and the implicit recipe/item unlock // sets derived from that state (REQ-LOCK-IMPLICIT). Reads config the same way // BuildingSystem does (a bound const reference to Simulation::m_config, which is // safe across restart because that member's storage address never changes — // reset() move-assigns into it rather than replacing it). // // Simulation forwards its isXUnlocked-style public queries here and drives // state changes (awarding an unlock group) here; the RNG-touching schematic // choice generation itself stays in Simulation (call ordering of m_rng is the // determinism backbone and must not move). class UnlockState { public: explicit UnlockState(const GameConfig& config); // Builds the granted-id sets and initializes all per-item unlock maps from // them (shared by the constructor and Simulation::reset). Ends with // recomputeUnlocked(). void initializeUnlockState(); // Ship schematic state query. bool isSchematicUnlocked(const std::string& shipId) const; // Module schematic state query. bool isModuleSchematicUnlocked(const std::string& moduleId) const; // Implicit recipe/item unlock queries (REQ-LOCK-IMPLICIT). bool isRecipeUnlocked(const std::string& recipeId) const; bool isItemUnlocked(const std::string& itemId) const; // Building unlock query (REQ-LOCK-BUILDING). True if the building type is not // gated by any unlock group, or its granting group has been awarded. bool isBuildingUnlocked(BuildingType type) const; // True if the unlock group has already been awarded to the player. bool isUnlockGroupAwarded(const std::string& groupId) const; // True if every prerequisite unlock group has been awarded (REQ-LOCK-PREREQ). bool prerequisitesSatisfied(const std::vector& requiredGroupIds) const; // Builds a schematic choice option for one unlock group (REQ-DEF-SCHEMATIC-DROP). SchematicChoiceOption makeUnlockOption(const UnlockGroupDef& group) const; // Awards the unlock group backing `chosen` (REQ-DEF-SCHEMATIC-DROP): marks // every granted ship/module/building schematic unlocked, records granted // recipe schematics, marks the group as awarded, and recomputes the implicit // unlock sets. Mirrors the non-artifact branch of the original // Simulation::applySchematicChoice exactly; callers still special-case // chosen.isArtifact themselves before calling this. void awardUnlockGroup(const SchematicChoiceOption& chosen); // Determinism helper (see Simulation::computeStateChecksum): folds unlock // state into the hasher via the same seven calls, in the same order, that // used to live at the Simulation::computeStateChecksum call site. void appendChecksum(Hasher& hasher) const; private: // Schematic unlock state (REQ-DEF-SCHEMATIC-DROP). struct SchematicState { bool unlocked; }; // Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state. void recomputeUnlocked(); // Result of the REQ-LOCK-IMPLICIT traversal. struct UnlockedSets { std::set itemIds; std::set recipeIds; }; // Pure REQ-LOCK-IMPLICIT traversal given hypothetical explicit-unlock sets. UnlockedSets computeUnlockedSets(const std::set& unlockedShipSchematicIds, const std::set& unlockedModuleSchematicIds, const std::set& unlockedRecipeSchematicIds) const; // Current explicit-unlock id sets, derived from m_schematicLevels / m_moduleSchematicLevels. std::set getUnlockedShipSchematicIds() const; std::set getUnlockedModuleSchematicIds() const; // Ids (sorted alphabetically by display name) of the recipes in // hypothetical.recipeIds that are not yet in m_unlockedRecipeIds. std::vector computeNewlyUnlockedRecipeIds(const UnlockedSets& hypothetical) const; // Determinism helpers — fold sub-state into the hasher in deterministic order. static void appendSchematicMap(Hasher& hasher, const std::map& levels); static void appendStringSet(Hasher& hasher, const std::set& ids); const GameConfig& m_config; std::map m_schematicLevels; std::map m_moduleSchematicLevels; std::map m_buildingLevels; // Unlock groups awarded so far (REQ-LOCK-EXPLICIT). Group ids. std::set m_awardedUnlockGroupIds; // Ids granted by some unlock group, per kind — cached from config at init. // An item starts locked iff it appears in the corresponding set. std::set m_grantedShipIds; std::set m_grantedModuleIds; std::set m_grantedBuildingIds; std::set m_grantedRecipeIds; // Explicitly unlocked assembler recipe schematics (REQ-LOCK-EXPLICIT). std::set m_unlockedRecipeSchematicIds; // Implicit unlock sets derived from schematic state (REQ-LOCK-IMPLICIT). std::set m_unlockedRecipeIds; std::set m_unlockedItemIds; };