implicit item locking
This commit is contained in:
@@ -14,12 +14,14 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||
std::function<void(int)> addBuildingBlocks,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
std::mt19937& rng)
|
||||
: m_config(config)
|
||||
, m_belts(belts)
|
||||
, m_allocateBuildingId(std::move(allocateBuildingId))
|
||||
, m_addBuildingBlocks(std::move(addBuildingBlocks))
|
||||
, m_spawnShip(std::move(spawnShip))
|
||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||
, m_rng(rng)
|
||||
{
|
||||
}
|
||||
@@ -203,17 +205,19 @@ std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
|
||||
|
||||
std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe)
|
||||
{
|
||||
std::vector<const RecipeOutput*> eligible;
|
||||
std::vector<double> weights;
|
||||
weights.reserve(recipe.outputs.size());
|
||||
for (const RecipeOutput& out : recipe.outputs)
|
||||
{
|
||||
if (!m_isItemUnlocked(out.item)) { continue; }
|
||||
eligible.push_back(&out);
|
||||
weights.push_back(out.probability.value_or(1.0));
|
||||
}
|
||||
|
||||
std::discrete_distribution<int> dist(weights.begin(), weights.end());
|
||||
const int idx = dist(m_rng);
|
||||
if (eligible.empty()) { return {}; }
|
||||
|
||||
const RecipeOutput& chosen = recipe.outputs[static_cast<std::size_t>(idx)];
|
||||
std::discrete_distribution<int> dist(weights.begin(), weights.end());
|
||||
const RecipeOutput& chosen = *eligible[static_cast<std::size_t>(dist(m_rng))];
|
||||
std::vector<Item> result;
|
||||
Item item;
|
||||
item.type.id = chosen.item;
|
||||
@@ -660,6 +664,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
||||
if (building.type == BuildingType::ReprocessingPlant)
|
||||
{
|
||||
chosen = rollReprocessingOutput(*recipe);
|
||||
if (chosen.empty()) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
std::function<void(int)> addBuildingBlocks,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
std::mt19937& rng);
|
||||
|
||||
// -- Placement / demolish ------------------------------------------------
|
||||
@@ -134,6 +135,7 @@ private:
|
||||
std::function<void(int)> m_addBuildingBlocks;
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||
std::mt19937& m_rng;
|
||||
|
||||
std::vector<Building> m_buildings;
|
||||
|
||||
@@ -59,6 +59,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout,
|
||||
moduleLevels);
|
||||
},
|
||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||
m_rng);
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||
m_aiSystem = std::make_unique<AiSystem>();
|
||||
@@ -86,6 +87,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
m_moduleSchematicLevels[def.id] = state;
|
||||
}
|
||||
|
||||
recomputeUnlocked();
|
||||
placeInitialStructures();
|
||||
registerForEvents();
|
||||
}
|
||||
@@ -147,6 +149,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false, layout,
|
||||
moduleLevels);
|
||||
},
|
||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||
m_rng);
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||
m_aiSystem = std::make_unique<AiSystem>();
|
||||
@@ -174,6 +177,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_moduleSchematicLevels[def.id] = state;
|
||||
}
|
||||
|
||||
recomputeUnlocked();
|
||||
placeInitialStructures();
|
||||
}
|
||||
|
||||
@@ -544,6 +548,83 @@ void Simulation::awardSchematicDrop(int destroyedStationLevel)
|
||||
evt.wasNewUnlock = wasNew;
|
||||
evt.isModuleSchematic = isModule;
|
||||
m_schematicDropEvents.push_back(evt);
|
||||
|
||||
recomputeUnlocked();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Implicit unlock computation (REQ-LOCK-IMPLICIT)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Simulation::recomputeUnlocked()
|
||||
{
|
||||
m_unlockedItemIds.clear();
|
||||
m_unlockedRecipeIds.clear();
|
||||
|
||||
for (const ShipDef& def : m_config.ships.ships)
|
||||
{
|
||||
if (!isSchematicUnlocked(def.id)) { continue; }
|
||||
for (const RecipeIngredient& mat : def.schematic.materials)
|
||||
{
|
||||
m_unlockedItemIds.insert(mat.item);
|
||||
}
|
||||
}
|
||||
for (const ModuleDef& def : m_config.modules.modules)
|
||||
{
|
||||
if (!isModuleSchematicUnlocked(def.id)) { continue; }
|
||||
for (const RecipeIngredient& mat : def.materials)
|
||||
{
|
||||
m_unlockedItemIds.insert(mat.item);
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
for (const RecipeDef& recipe : m_config.recipes.recipes)
|
||||
{
|
||||
if (recipe.building != BuildingType::Miner
|
||||
&& recipe.building != BuildingType::Smelter
|
||||
&& recipe.building != BuildingType::Assembler)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool producesUnlocked = false;
|
||||
for (const RecipeOutput& out : recipe.outputs)
|
||||
{
|
||||
if (m_unlockedItemIds.count(out.item) > 0)
|
||||
{
|
||||
producesUnlocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!producesUnlocked) { continue; }
|
||||
|
||||
if (recipe.building == BuildingType::Miner
|
||||
|| recipe.building == BuildingType::Assembler)
|
||||
{
|
||||
m_unlockedRecipeIds.insert(recipe.id);
|
||||
}
|
||||
for (const RecipeIngredient& ing : recipe.inputs)
|
||||
{
|
||||
if (m_unlockedItemIds.insert(ing.item).second)
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Simulation::isRecipeUnlocked(const std::string& recipeId) const
|
||||
{
|
||||
return m_unlockedRecipeIds.count(recipeId) > 0;
|
||||
}
|
||||
|
||||
bool Simulation::isItemUnlocked(const std::string& itemId) const
|
||||
{
|
||||
return m_unlockedItemIds.count(itemId) > 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -70,6 +71,10 @@ public:
|
||||
int moduleSchematicLevel(const std::string& moduleId) const;
|
||||
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;
|
||||
|
||||
// Checks affordability, deducts building blocks, and places the building.
|
||||
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
@@ -131,6 +136,13 @@ private:
|
||||
std::map<std::string, SchematicState> m_schematicLevels;
|
||||
std::map<std::string, SchematicState> m_moduleSchematicLevels;
|
||||
|
||||
// Implicit unlock sets derived from schematic state (REQ-LOCK-IMPLICIT).
|
||||
std::set<std::string> m_unlockedRecipeIds;
|
||||
std::set<std::string> m_unlockedItemIds;
|
||||
|
||||
// Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state.
|
||||
void recomputeUnlocked();
|
||||
|
||||
EntityAdmin m_admin;
|
||||
BeltSystem m_beltSystem;
|
||||
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
||||
|
||||
Reference in New Issue
Block a user