show implicitly unlocked items in schematic unlock dialog

This commit is contained in:
2026-06-13 18:01:59 +02:00
parent 5317f35198
commit 3716c2b734
6 changed files with 230 additions and 12 deletions

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <vector>
enum class SchematicType
{
@@ -19,4 +20,9 @@ struct SchematicChoiceOption
std::string displayName;
bool isNewUnlock;
int targetLevel;
// Display names of items produced by recipes that would newly become
// implicitly unlocked (REQ-LOCK-IMPLICIT) if this option is selected.
// Deduplicated and sorted alphabetically; empty if none.
std::vector<std::string> newlyUnlockedItemNames;
};

View File

@@ -579,6 +579,9 @@ void Simulation::generateSchematicChoices(int destroyedStationLevel)
const int numChoices = std::min(static_cast<int>(pool.size()), 3);
m_pendingSchematicChoices.clear();
const std::set<std::string> currentShipIds = getUnlockedShipSchematicIds();
const std::set<std::string> currentModuleIds = getUnlockedModuleSchematicIds();
for (int i = 0; i < numChoices; ++i)
{
std::uniform_int_distribution<int> dist(0, static_cast<int>(pool.size()) - 1 - i);
@@ -622,6 +625,28 @@ void Simulation::generateSchematicChoices(int destroyedStationLevel)
}
}
// REQ-DEF-SCHEMATIC-DROP: preview recipes newly implicitly unlocked by this option.
std::set<std::string> hypotheticalShipIds = currentShipIds;
std::set<std::string> hypotheticalModuleIds = currentModuleIds;
std::set<std::string> hypotheticalRecipeSchematicIds = m_unlockedRecipeSchematicIds;
if (entry.type == DropType::Ship && option.isNewUnlock)
{
hypotheticalShipIds.insert(entry.id);
}
else if (entry.type == DropType::Module && option.isNewUnlock)
{
hypotheticalModuleIds.insert(entry.id);
}
else if (entry.type == DropType::Recipe)
{
hypotheticalRecipeSchematicIds.insert(entry.id);
}
const UnlockedSets hypothetical = computeUnlockedSets(
hypotheticalShipIds, hypotheticalModuleIds, hypotheticalRecipeSchematicIds);
option.newlyUnlockedItemNames = computeNewlyUnlockedItemNames(hypothetical);
m_pendingSchematicChoices.push_back(option);
}
}
@@ -654,34 +679,64 @@ void Simulation::applySchematicChoice(int choiceIndex)
void Simulation::recomputeUnlocked()
{
m_unlockedItemIds.clear();
m_unlockedRecipeIds.clear();
const UnlockedSets result = computeUnlockedSets(
getUnlockedShipSchematicIds(), getUnlockedModuleSchematicIds(), m_unlockedRecipeSchematicIds);
m_unlockedItemIds = result.itemIds;
m_unlockedRecipeIds = result.recipeIds;
}
std::set<std::string> Simulation::getUnlockedShipSchematicIds() const
{
std::set<std::string> ids;
for (const auto& [id, state] : m_schematicLevels)
{
if (state.unlocked) { ids.insert(id); }
}
return ids;
}
std::set<std::string> Simulation::getUnlockedModuleSchematicIds() const
{
std::set<std::string> ids;
for (const auto& [id, state] : m_moduleSchematicLevels)
{
if (state.unlocked) { ids.insert(id); }
}
return ids;
}
Simulation::UnlockedSets Simulation::computeUnlockedSets(
const std::set<std::string>& unlockedShipSchematicIds,
const std::set<std::string>& unlockedModuleSchematicIds,
const std::set<std::string>& unlockedRecipeSchematicIds) const
{
UnlockedSets result;
for (const ShipDef& def : m_config.ships.ships)
{
if (!isSchematicUnlocked(def.id)) { continue; }
if (unlockedShipSchematicIds.count(def.id) == 0) { continue; }
for (const RecipeIngredient& mat : def.schematic.materials)
{
m_unlockedItemIds.insert(mat.item);
result.itemIds.insert(mat.item);
}
}
for (const ModuleDef& def : m_config.modules.modules)
{
if (!isModuleSchematicUnlocked(def.id)) { continue; }
if (unlockedModuleSchematicIds.count(def.id) == 0) { continue; }
for (const RecipeIngredient& mat : def.materials)
{
m_unlockedItemIds.insert(mat.item);
result.itemIds.insert(mat.item);
}
}
for (const RecipeDef& def : m_config.recipes.recipes)
{
if (def.building == BuildingType::Assembler
&& def.unlockAtStationLevel.has_value()
&& m_unlockedRecipeSchematicIds.count(def.id) > 0)
&& unlockedRecipeSchematicIds.count(def.id) > 0)
{
for (const RecipeOutput& out : def.outputs)
{
m_unlockedItemIds.insert(out.item);
result.itemIds.insert(out.item);
}
}
}
@@ -700,14 +755,14 @@ void Simulation::recomputeUnlocked()
}
if (recipe.building == BuildingType::Assembler
&& recipe.unlockAtStationLevel.has_value()
&& m_unlockedRecipeSchematicIds.count(recipe.id) == 0)
&& unlockedRecipeSchematicIds.count(recipe.id) == 0)
{
continue;
}
bool producesUnlocked = false;
for (const RecipeOutput& out : recipe.outputs)
{
if (m_unlockedItemIds.count(out.item) > 0)
if (result.itemIds.count(out.item) > 0)
{
producesUnlocked = true;
break;
@@ -718,17 +773,38 @@ void Simulation::recomputeUnlocked()
if (recipe.building == BuildingType::Miner
|| recipe.building == BuildingType::Assembler)
{
m_unlockedRecipeIds.insert(recipe.id);
result.recipeIds.insert(recipe.id);
}
for (const RecipeIngredient& ing : recipe.inputs)
{
if (m_unlockedItemIds.insert(ing.item).second)
if (result.itemIds.insert(ing.item).second)
{
changed = true;
}
}
}
}
return result;
}
std::vector<std::string> Simulation::computeNewlyUnlockedItemNames(const UnlockedSets& hypothetical) const
{
std::set<std::string> itemNames;
for (const std::string& recipeId : hypothetical.recipeIds)
{
if (m_unlockedRecipeIds.count(recipeId) > 0) { continue; }
for (const RecipeDef& def : m_config.recipes.recipes)
{
if (def.id != recipeId) { continue; }
for (const RecipeOutput& out : def.outputs)
{
itemNames.insert(toDisplayName(out.item));
}
break;
}
}
return std::vector<std::string>(itemNames.begin(), itemNames.end());
}
bool Simulation::isRecipeUnlocked(const std::string& recipeId) const

View File

@@ -154,6 +154,26 @@ private:
// Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state.
void recomputeUnlocked();
// Result of the REQ-LOCK-IMPLICIT traversal.
struct UnlockedSets
{
std::set<std::string> itemIds;
std::set<std::string> recipeIds;
};
// Pure REQ-LOCK-IMPLICIT traversal given hypothetical explicit-unlock sets.
UnlockedSets computeUnlockedSets(const std::set<std::string>& unlockedShipSchematicIds,
const std::set<std::string>& unlockedModuleSchematicIds,
const std::set<std::string>& unlockedRecipeSchematicIds) const;
// Current explicit-unlock id sets, derived from m_schematicLevels / m_moduleSchematicLevels.
std::set<std::string> getUnlockedShipSchematicIds() const;
std::set<std::string> getUnlockedModuleSchematicIds() const;
// Display names (deduplicated, alphabetical) of output items of recipes in
// hypothetical.recipeIds that are not yet in m_unlockedRecipeIds.
std::vector<std::string> computeNewlyUnlockedItemNames(const UnlockedSets& hypothetical) const;
EntityAdmin m_admin;
BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem;