implicit item locking
This commit is contained in:
@@ -47,6 +47,7 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
[this]() { return allocateBuildingId(); },
|
||||
[](int) {},
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
m_rng);
|
||||
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -70,6 +70,7 @@ struct Fixture
|
||||
[this]() { return nextBuildingId++; },
|
||||
[this](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
, ships(cfg, admin)
|
||||
, scraps(admin)
|
||||
|
||||
@@ -79,6 +79,7 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -104,6 +105,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Belt, QPoint(5, 5), Rotation::East, 0);
|
||||
@@ -132,6 +134,7 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -152,6 +155,7 @@ TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -185,6 +189,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -202,6 +207,7 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -223,6 +229,7 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -247,6 +254,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -276,6 +284,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -305,6 +314,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -344,6 +354,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
// Smelter mask ["AA ","AA>"] → body (0,0),(1,0),(0,1),(1,1).
|
||||
@@ -385,6 +396,7 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -424,6 +436,7 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -464,6 +477,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
||||
@@ -494,6 +508,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
|
||||
@@ -552,6 +567,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
REQUIRE_FALSE(
|
||||
@@ -570,6 +586,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a que
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -592,6 +609,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -618,6 +636,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -639,6 +658,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprin
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
// Smelter at (0,0) occupies body tiles (0,0),(1,0),(0,1),(1,1).
|
||||
@@ -662,6 +682,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
// Smelter is a fully filled 2×2 footprint — rotating the ghost produces the
|
||||
@@ -690,6 +711,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -712,6 +734,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -735,6 +758,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
@@ -765,6 +789,7 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
|
||||
[&nextBuildingId]() { return nextBuildingId++; },
|
||||
[&stock](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng);
|
||||
|
||||
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
@@ -72,6 +72,7 @@ struct CombatFixture
|
||||
[this]() { return nextBuildingId++; },
|
||||
[](int){},
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
, combat(cfg)
|
||||
{
|
||||
|
||||
@@ -560,7 +560,12 @@ void GameWorldView::placeBlueprintAtTile(QPoint center)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sim->buildings().setRecipe(id, bb.recipeId);
|
||||
const bool needsUnlockCheck = bb.type == BuildingType::Miner
|
||||
|| bb.type == BuildingType::Assembler;
|
||||
if (!needsUnlockCheck || m_sim->isRecipeUnlocked(bb.recipeId))
|
||||
{
|
||||
m_sim->buildings().setRecipe(id, bb.recipeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -296,12 +296,15 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
{
|
||||
for (const RecipeDef& recipe : m_config->recipes.recipes)
|
||||
{
|
||||
if (recipe.building == b->type)
|
||||
if (recipe.building != b->type) { continue; }
|
||||
if ((b->type == BuildingType::Miner || b->type == BuildingType::Assembler)
|
||||
&& !m_sim->isRecipeUnlocked(recipe.id))
|
||||
{
|
||||
m_recipeCombo->addItem(
|
||||
QString::fromStdString(recipe.id),
|
||||
QString::fromStdString(recipe.id));
|
||||
continue;
|
||||
}
|
||||
m_recipeCombo->addItem(
|
||||
QString::fromStdString(recipe.id),
|
||||
QString::fromStdString(recipe.id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +651,7 @@ void SelectedBuildingPanel::buildSplitterFilters(QPoint splitterTile)
|
||||
list->clear();
|
||||
for (const std::string& itemId : items)
|
||||
{
|
||||
if (!m_sim->isItemUnlocked(itemId)) { continue; }
|
||||
QListWidgetItem* row = new QListWidgetItem(
|
||||
QString::fromStdString(itemId), list);
|
||||
const bool checked = filter.empty()
|
||||
|
||||
Reference in New Issue
Block a user