add balancing tool target

This commit is contained in:
2026-05-03 11:17:54 +02:00
parent 5153129909
commit a4427f7f67
12 changed files with 1164 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
#include "BalancingConfig.h"
#include <stdexcept>
#include <string>
#include "toml.hpp"
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;
}
} // 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 = static_cast<int>(
requireInt((*arenaTbl)["player_buffer_width"], prefix + ".player_buffer_width"));
arena.contestZoneWidth = static_cast<int>(
requireInt((*arenaTbl)["contest_zone_width"], prefix + ".contest_zone_width"));
arena.enemyBufferWidth = static_cast<int>(
requireInt((*arenaTbl)["enemy_buffer_width"], prefix + ".enemy_buffer_width"));
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"));
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;
}