Files
dota_factory/src/balancing/BalancingConfig.cpp
2026-06-06 20:46:36 +02:00

207 lines
7.6 KiB
C++

#include "BalancingConfig.h"
#include <stdexcept>
#include <string>
#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 <typename NodeView>
int64_t requireInt(NodeView node, const std::string& path)
{
const std::optional<int64_t> value = node.template value<int64_t>();
if (!value)
{
throw makeError(path, "missing or not an integer");
}
return *value;
}
template <typename NodeView>
std::string requireString(NodeView node, const std::string& path)
{
const std::optional<std::string> value = node.template value<std::string>();
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<int>(
requireInt((*arenaTbl)["height_tiles"], prefix + ".height_tiles"));
arena.playerBufferWidth_tiles = static_cast<int>(
requireInt((*arenaTbl)["player_buffer_width_tiles"], prefix + ".player_buffer_width_tiles"));
arena.contestZoneWidth_tiles = static_cast<int>(
requireInt((*arenaTbl)["contest_zone_width_tiles"], prefix + ".contest_zone_width_tiles"));
arena.enemyBufferWidth_tiles = static_cast<int>(
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<std::size_t>(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<int>(
requireInt((*shipTbl)["level"], sPrefix + ".level"));
entry.count = static_cast<int>(
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<std::string> type =
(*modTbl)["type"].value<std::string>();
const std::optional<int64_t> x =
(*modTbl)["x"].value<int64_t>();
const std::optional<int64_t> y =
(*modTbl)["y"].value<int64_t>();
const std::optional<std::string> rotStr =
(*modTbl)["rotation"].value<std::string>();
if (!type || !x || !y || !rotStr) { continue; }
PlacedModule pm;
pm.moduleId = *type;
pm.position = QPoint(static_cast<int>(*x),
static_cast<int>(*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<int>(
requireInt((*stationTbl)["level"], sPrefix + ".level"));
entry.position = QPoint(
static_cast<int>(requireInt((*stationTbl)["x"], sPrefix + ".x")),
static_cast<int>(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;
}