109 lines
4.2 KiB
C++
109 lines
4.2 KiB
C++
#include "ConfigLoader.h"
|
|
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "TomlHelpers.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
// Validates unlocks.toml against the rest of the config (REQ-LOCK-EXPLICIT,
|
|
// REQ-LOCK-PREREQ): each granted id resolves to the right kind of definition
|
|
// (recipe grants must name assembler recipes), each grantable id is granted by
|
|
// at most one group, group ids are unique, every group grants at least one
|
|
// item, and every `requires` id names a defined group.
|
|
void validateUnlocks(const GameConfig& cfg)
|
|
{
|
|
const std::string file = "unlocks.toml";
|
|
|
|
std::unordered_set<std::string> shipIds;
|
|
for (const ShipDef& def : cfg.ships.ships) { shipIds.insert(def.id); }
|
|
std::unordered_set<std::string> moduleIds;
|
|
for (const ModuleDef& def : cfg.modules.modules) { moduleIds.insert(def.id); }
|
|
std::unordered_set<std::string> buildingIds;
|
|
for (const BuildingDef& def : cfg.buildings.buildings) { buildingIds.insert(def.id); }
|
|
std::unordered_set<std::string> assemblerRecipeIds;
|
|
for (const RecipeDef& def : cfg.recipes.recipes)
|
|
{
|
|
if (def.building == BuildingType::Assembler) { assemblerRecipeIds.insert(def.id); }
|
|
}
|
|
|
|
std::unordered_set<std::string> groupIds;
|
|
std::unordered_set<std::string> grantedShipIds;
|
|
std::unordered_set<std::string> grantedModuleIds;
|
|
std::unordered_set<std::string> grantedBuildingIds;
|
|
std::unordered_set<std::string> grantedRecipeIds;
|
|
|
|
const auto checkGrants = [&](const std::vector<std::string>& ids,
|
|
const std::unordered_set<std::string>& valid,
|
|
std::unordered_set<std::string>& granted,
|
|
const std::string& kind,
|
|
const std::string& gPath)
|
|
{
|
|
for (const std::string& id : ids)
|
|
{
|
|
if (valid.count(id) == 0)
|
|
{
|
|
throw utility::makeError(file, gPath, "grants unknown " + kind + " '" + id + "'");
|
|
}
|
|
if (!granted.insert(id).second)
|
|
{
|
|
throw utility::makeError(file, gPath,
|
|
"grants " + kind + " '" + id + "' which is already granted by another unlock group");
|
|
}
|
|
}
|
|
};
|
|
|
|
for (const UnlockGroupDef& group : cfg.unlocks.groups)
|
|
{
|
|
const std::string gPath = "unlock '" + group.id + "'";
|
|
if (!groupIds.insert(group.id).second)
|
|
{
|
|
throw utility::makeError(file, gPath, "duplicate unlock group id");
|
|
}
|
|
|
|
if (group.ships.empty() && group.modules.empty()
|
|
&& group.buildings.empty() && group.recipes.empty())
|
|
{
|
|
throw utility::makeError(file, gPath, "grants no items (must grant at least one)");
|
|
}
|
|
|
|
checkGrants(group.ships, shipIds, grantedShipIds, "ship", gPath);
|
|
checkGrants(group.modules, moduleIds, grantedModuleIds, "module", gPath);
|
|
checkGrants(group.buildings, buildingIds, grantedBuildingIds, "building", gPath);
|
|
checkGrants(group.recipes, assemblerRecipeIds, grantedRecipeIds, "assembler recipe", gPath);
|
|
}
|
|
|
|
// requires must reference defined group ids (checked once all group ids known).
|
|
for (const UnlockGroupDef& group : cfg.unlocks.groups)
|
|
{
|
|
for (const std::string& req : group.requiredGroupIds)
|
|
{
|
|
if (groupIds.count(req) == 0)
|
|
{
|
|
throw utility::makeError(file, "unlock '" + group.id + "'.requires",
|
|
"references unknown unlock group '" + req + "'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
GameConfig ConfigLoader::loadFromDirectory(const std::string& configDir)
|
|
{
|
|
GameConfig cfg;
|
|
cfg.world = loadWorld(configDir + "/world.toml");
|
|
cfg.buildings = loadBuildings(configDir + "/buildings.toml");
|
|
cfg.recipes = loadRecipes(configDir + "/recipes.toml");
|
|
cfg.ships = loadShips(configDir + "/ships.toml");
|
|
cfg.stations = loadStations(configDir + "/stations.toml");
|
|
cfg.modules = loadModules(configDir + "/modules.toml");
|
|
cfg.unlocks = loadUnlocks(configDir + "/unlocks.toml");
|
|
validateUnlocks(cfg);
|
|
cfg.threatCosts = computeThreatCostTable(cfg);
|
|
return cfg;
|
|
}
|