#include "ConfigLoader.h" #include #include #include "toml.hpp" #include "TomlHelpers.h" namespace { // Known category→stat mappings for module stat modifier discovery. // addedKeySuffix: unit suffix appended before "_formula" for additive modifier keys only. // Multiplicative modifier keys are always dimensionless and carry no suffix. struct StatEntry { const char* category; const char* stat; const char* addedKeySuffix; }; static const StatEntry kKnownStats[] = { {"health", "hp", ""}, {"movement", "speed", "_mps"}, {"movement", "main_acceleration", "_mpss"}, {"movement", "maneuvering_acceleration", "_mpss"}, {"sensor", "sensor_range", "_m"}, {"weapon", "damage", ""}, {"weapon", "attack_range", "_m"}, {"weapon", "attack_rate", "_hz"}, {"salvage", "collection_range", "_m"}, {"salvage", "collection_rate", "_hz"}, {"cargo", "cargo_capacity", ""}, {"repair", "repair_rate", "_hz"}, {"repair", "repair_range", "_m"}, }; } // namespace ModulesConfig ConfigLoader::loadModules(const std::string& path) { const std::string file = "modules.toml"; toml::table tbl = utility::parseFile(path, file); ModulesConfig cfg; if (!tbl.contains("module")) { return cfg; } const toml::array& arr = utility::requireArray(tbl["module"], file, "module"); for (std::size_t i = 0; i < arr.size(); ++i) { const std::string elemPath = "module[" + std::to_string(i) + "]"; const toml::table* st = arr[i].as_table(); if (st == nullptr) { throw utility::makeError(file, elemPath, "not a table"); } toml::table& mt = const_cast(*st); ModuleDef def; def.id = utility::requireString(mt["id"], file, elemPath + ".id"); def.surfaceMask = utility::requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask"); def.productionTimeSeconds = utility::requireDouble( mt["production_time_seconds"], file, elemPath + ".production_time_seconds"); def.fillColor = utility::requireString(mt["fill_color"], file, elemPath + ".fill_color"); def.glyph = utility::requireString(mt["glyph"], file, elemPath + ".glyph"); if (mt.contains("tooltip")) { def.tooltip = utility::requireString(mt["tooltip"], file, elemPath + ".tooltip"); } // Materials { const toml::array& materials = utility::requireArray(mt["materials"], file, elemPath + ".materials"); def.materials = utility::parseIngredients(materials, file, elemPath + ".materials"); } // Stat modifiers from [module.] sub-tables for (const StatEntry& se : kKnownStats) { if (!mt.contains(se.category)) { continue; } const toml::table& catTable = utility::requireTable(mt[se.category], file, elemPath + "." + se.category); toml::table& catMt = const_cast(catTable); const std::string addedKey = std::string("added_") + se.stat + se.addedKeySuffix; const std::string multipliedKey = std::string("multiplied_") + se.stat + se.addedKeySuffix; if (catMt.contains(addedKey)) { ModuleStatModifier mod; mod.stat = se.stat; mod.modifierType = "additive"; mod.value = utility::requireDouble(catMt[addedKey], file, elemPath + "." + se.category + "." + addedKey); def.statModifiers.push_back(std::move(mod)); } if (catMt.contains(multipliedKey)) { ModuleStatModifier mod; mod.stat = se.stat; mod.modifierType = "multiplicative"; mod.value = utility::requireDouble(catMt[multipliedKey], file, elemPath + "." + se.category + "." + multipliedKey); def.statModifiers.push_back(std::move(mod)); } } // Weapon capability section: [module.weapon] with base stat formulas if (mt.contains("weapon")) { const std::string wPath = elemPath + ".weapon"; const toml::table& wTable = utility::requireTable(mt["weapon"], file, wPath); toml::table& wMt = const_cast(wTable); if (wMt.contains("damage") || wMt.contains("attack_range_m") || wMt.contains("attack_rate_hz")) { ModuleWeaponCapability cap; cap.damage = static_cast(utility::requireDouble(wMt["damage"], file, wPath + ".damage")); cap.attackRange_m = static_cast(utility::requireDouble(wMt["attack_range_m"], file, wPath + ".attack_range_m")); cap.attackRate_hz = static_cast(utility::requireDouble(wMt["attack_rate_hz"], file, wPath + ".attack_rate_hz")); def.weaponCapability = std::move(cap); } } // Salvage capability section: [module.salvage] with base stat formulas if (mt.contains("salvage")) { const std::string sPath = elemPath + ".salvage"; const toml::table& sTable = utility::requireTable(mt["salvage"], file, sPath); toml::table& sMt = const_cast(sTable); if (sMt.contains("collection_range_m") || sMt.contains("cargo_capacity") || sMt.contains("collection_rate_hz")) { ModuleSalvageCapability cap; cap.collectionRange_m = static_cast(utility::requireDouble(sMt["collection_range_m"], file, sPath + ".collection_range_m")); cap.cargoCapacity = static_cast(utility::requireDouble(sMt["cargo_capacity"], file, sPath + ".cargo_capacity")); cap.collectionRate_hz = static_cast(utility::requireDouble(sMt["collection_rate_hz"], file, sPath + ".collection_rate_hz")); def.salvageCapability = std::move(cap); } } // Repair capability section: [module.repair] with base stat formulas if (mt.contains("repair")) { const std::string rPath = elemPath + ".repair"; const toml::table& rTable = utility::requireTable(mt["repair"], file, rPath); toml::table& rMt = const_cast(rTable); if (rMt.contains("repair_rate_hz") || rMt.contains("repair_range_m")) { ModuleRepairCapability cap; cap.repairRate_hz = static_cast(utility::requireDouble(rMt["repair_rate_hz"], file, rPath + ".repair_rate_hz")); cap.repairAmountHp = static_cast(utility::requireDouble(rMt["repair_amount_hp"], file, rPath + ".repair_amount_hp")); cap.repairRange_m = static_cast(utility::requireDouble(rMt["repair_range_m"], file, rPath + ".repair_range_m")); def.repairCapability = std::move(cap); } } cfg.modules.push_back(std::move(def)); } return cfg; }