#include "BalancingConfig.h" #include #include #include "toml.hpp" #include "Rotation.h" namespace { std::runtime_error makeError(const std::string& path, const std::string& why) { return std::runtime_error("balancing.toml: '" + path + "' " + why); } template int64_t requireInt(NodeView node, const std::string& path) { const std::optional value = node.template value(); if (!value) { throw makeError(path, "missing or not an integer"); } return *value; } template std::string requireString(NodeView node, const std::string& path) { const std::optional value = node.template value(); if (!value) { throw makeError(path, "missing or not a string"); } return *value; } Rotation parseRotation(const std::string& s) { if (s == "east") { return Rotation::East; } if (s == "south") { return Rotation::South; } if (s == "west") { return Rotation::West; } return Rotation::North; } } // namespace BalancingConfig loadBalancingConfig(const std::string& path) { toml::table root; try { root = toml::parse_file(path); } catch (const toml::parse_error& e) { throw std::runtime_error(std::string("balancing.toml: parse error: ") + e.what()); } const toml::array* arenaArray = root["arena"].as_array(); if (!arenaArray) { throw makeError("arena", "missing or not an array of tables"); } BalancingConfig config; for (std::size_t ai = 0; ai < arenaArray->size(); ++ai) { const toml::table* arenaTbl = (*arenaArray)[ai].as_table(); if (!arenaTbl) { throw makeError("arena[" + std::to_string(ai) + "]", "not a table"); } const std::string prefix = "arena[" + std::to_string(ai) + "]"; ArenaConfig arena; arena.name = requireString((*arenaTbl)["name"], prefix + ".name"); arena.heightTiles = static_cast( requireInt((*arenaTbl)["height_tiles"], prefix + ".height_tiles")); arena.playerBufferWidth_tiles = static_cast( requireInt((*arenaTbl)["player_buffer_width_tiles"], prefix + ".player_buffer_width_tiles")); arena.contestZoneWidth_tiles = static_cast( requireInt((*arenaTbl)["contest_zone_width_tiles"], prefix + ".contest_zone_width_tiles")); arena.enemyBufferWidth_tiles = static_cast( requireInt((*arenaTbl)["enemy_buffer_width_tiles"], prefix + ".enemy_buffer_width_tiles")); const toml::array* teamArray = (*arenaTbl)["team"].as_array(); if (!teamArray || teamArray->size() != 2) { throw makeError(prefix + ".team", "must contain exactly 2 teams"); } for (int ti = 0; ti < 2; ++ti) { const toml::table* teamTbl = (*teamArray)[static_cast(ti)].as_table(); if (!teamTbl) { throw makeError(prefix + ".team[" + std::to_string(ti) + "]", "not a table"); } const std::string tPrefix = prefix + ".team[" + std::to_string(ti) + "]"; ArenaTeamConfig& team = arena.teams[ti]; team.name = requireString((*teamTbl)["name"], tPrefix + ".name"); const toml::array* shipArray = (*teamTbl)["ship"].as_array(); if (shipArray) { for (std::size_t si = 0; si < shipArray->size(); ++si) { const toml::table* shipTbl = (*shipArray)[si].as_table(); if (!shipTbl) { throw makeError(tPrefix + ".ship[" + std::to_string(si) + "]", "not a table"); } const std::string sPrefix = tPrefix + ".ship[" + std::to_string(si) + "]"; ArenaShipEntry entry; entry.schematicId = requireString((*shipTbl)["schematic"], sPrefix + ".schematic"); entry.level = static_cast( requireInt((*shipTbl)["level"], sPrefix + ".level")); entry.count = static_cast( requireInt((*shipTbl)["count"], sPrefix + ".count")); const toml::array* modArray = (*shipTbl)["modules"].as_array(); if (modArray && !modArray->empty()) { ShipLayoutConfig layout; for (std::size_t mi = 0; mi < modArray->size(); ++mi) { const toml::table* modTbl = (*modArray)[mi].as_table(); if (!modTbl) { continue; } const std::optional type = (*modTbl)["type"].value(); const std::optional x = (*modTbl)["x"].value(); const std::optional y = (*modTbl)["y"].value(); const std::optional rotStr = (*modTbl)["rotation"].value(); if (!type || !x || !y || !rotStr) { continue; } PlacedModule pm; pm.moduleId = *type; pm.position = QPoint(static_cast(*x), static_cast(*y)); pm.rotation = parseRotation(*rotStr); layout.placedModules.push_back(std::move(pm)); } entry.layout = std::move(layout); } team.ships.push_back(entry); } } const toml::array* stationArray = (*teamTbl)["station"].as_array(); if (stationArray) { for (std::size_t si = 0; si < stationArray->size(); ++si) { const toml::table* stationTbl = (*stationArray)[si].as_table(); if (!stationTbl) { throw makeError(tPrefix + ".station[" + std::to_string(si) + "]", "not a table"); } const std::string sPrefix = tPrefix + ".station[" + std::to_string(si) + "]"; ArenaStationEntry entry; entry.stationType = requireString((*stationTbl)["type"], sPrefix + ".type"); entry.level = static_cast( requireInt((*stationTbl)["level"], sPrefix + ".level")); entry.position = QPoint( static_cast(requireInt((*stationTbl)["x"], sPrefix + ".x")), static_cast(requireInt((*stationTbl)["y"], sPrefix + ".y"))); if (entry.stationType != "player_station" && entry.stationType != "enemy_station") { throw makeError(sPrefix + ".type", "must be 'player_station' or 'enemy_station'"); } team.stations.push_back(entry); } } } config.arenas.push_back(arena); } return config; }