show implicitly unlocked items in schematic unlock dialog
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user