| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- #include "troop_catalog_loader.h"
- #include "troop_catalog.h"
- #include "troop_config.h"
- #include <QCoreApplication>
- #include <QDir>
- #include <QFile>
- #include <QJsonArray>
- #include <QJsonDocument>
- #include <QJsonObject>
- #include <QJsonValue>
- #include <QLoggingCategory>
- #include <QVariant>
- namespace {
- [[nodiscard]] auto ensure_array(const QJsonValue &value) -> QJsonArray {
- if (value.isArray()) {
- return value.toArray();
- }
- return {};
- }
- [[nodiscard]] auto ensure_object(const QJsonValue &value) -> QJsonObject {
- if (value.isObject()) {
- return value.toObject();
- }
- return {};
- }
- [[nodiscard]] auto read_float(const QJsonObject &obj, const char *key,
- float fallback) -> float {
- if (!obj.contains(key)) {
- return fallback;
- }
- const auto value = obj.value(key);
- return static_cast<float>(value.toDouble(fallback));
- }
- [[nodiscard]] auto read_int(const QJsonObject &obj, const char *key,
- int fallback) -> int {
- if (!obj.contains(key)) {
- return fallback;
- }
- const auto value = obj.value(key);
- if (value.isDouble()) {
- return value.toInt(fallback);
- }
- if (value.isString()) {
- bool ok = false;
- const auto str = value.toString();
- const int result = str.toInt(&ok);
- return ok ? result : fallback;
- }
- return fallback;
- }
- [[nodiscard]] auto read_bool(const QJsonObject &obj, const char *key,
- bool fallback) -> bool {
- if (!obj.contains(key)) {
- return fallback;
- }
- return obj.value(key).toBool(fallback);
- }
- } // namespace
- namespace Game::Units {
- static constexpr const char *k_troop_list_key = "troops";
- static bool g_catalog_loaded = false;
- static auto logger() -> QLoggingCategory & {
- static QLoggingCategory category("TroopCatalogLoader");
- return category;
- }
- auto TroopCatalogLoader::resolve_data_path(const QString &relative) -> QString {
- const QString direct = QDir::current().filePath(relative);
- if (QFile::exists(direct)) {
- return direct;
- }
- const QString appDir = QCoreApplication::applicationDirPath();
- if (!appDir.isEmpty()) {
- const QString fromApp = QDir(appDir).filePath(relative);
- if (QFile::exists(fromApp)) {
- return fromApp;
- }
- const QString parent = QDir(appDir).filePath("../" + relative);
- if (QFile::exists(parent)) {
- return QDir(parent).canonicalPath();
- }
- }
- const QString resource_path = QStringLiteral(":/") + relative;
- if (QFile::exists(resource_path)) {
- return resource_path;
- }
- return {};
- }
- auto TroopCatalogLoader::load_default_catalog() -> bool {
- if (g_catalog_loaded) {
- return true;
- }
- const QString path = resolve_data_path("assets/data/troops/base.json");
- if (path.isEmpty()) {
- qCWarning(logger()) << "Failed to locate base troop catalog at"
- << "assets/data/troops/base.json";
- return false;
- }
- if (!load_from_file(path)) {
- return false;
- }
- g_catalog_loaded = true;
- return true;
- }
- auto TroopCatalogLoader::load_from_file(const QString &path) -> bool {
- QFile file(path);
- if (!file.open(QIODevice::ReadOnly)) {
- qCWarning(logger()) << "Unable to open troop catalog" << path << ":"
- << file.errorString();
- return false;
- }
- const QByteArray data = file.readAll();
- QJsonParseError parseError;
- const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- qCWarning(logger()) << "Failed to parse troop catalog" << path << ":"
- << parseError.errorString();
- return false;
- }
- const QJsonObject root = doc.object();
- const QJsonArray troops = ensure_array(root.value(k_troop_list_key));
- if (troops.isEmpty()) {
- qCWarning(logger()) << "Troop catalog" << path
- << "does not contain a 'troops' array";
- return false;
- }
- auto &catalog = TroopCatalog::instance();
- catalog.clear();
- for (const QJsonValue &value : troops) {
- const QJsonObject troop_obj = ensure_object(value);
- const QString troop_id = troop_obj.value("id").toString();
- if (troop_id.isEmpty()) {
- qCWarning(logger()) << "Encountered troop without id in" << path;
- continue;
- }
- const auto type_opt = tryParseTroopType(troop_id.toStdString());
- if (!type_opt.has_value()) {
- qCWarning(logger()) << "Unknown troop type" << troop_id << "in" << path;
- continue;
- }
- TroopClass troop_class{};
- troop_class.unit_type = *type_opt;
- troop_class.display_name =
- troop_obj.value("display_name").toString(troop_id).toStdString();
- const QJsonObject production = ensure_object(troop_obj.value("production"));
- troop_class.production.cost =
- read_int(production, "cost", troop_class.production.cost);
- troop_class.production.build_time =
- read_float(production, "build_time", troop_class.production.build_time);
- troop_class.production.priority =
- read_int(production, "priority", troop_class.production.priority);
- troop_class.production.is_melee =
- read_bool(production, "is_melee", troop_class.production.is_melee);
- const QJsonObject combat = ensure_object(troop_obj.value("combat"));
- troop_class.combat.health =
- read_int(combat, "health", troop_class.combat.health);
- troop_class.combat.max_health =
- read_int(combat, "max_health", troop_class.combat.max_health);
- troop_class.combat.speed =
- read_float(combat, "speed", troop_class.combat.speed);
- troop_class.combat.vision_range =
- read_float(combat, "vision_range", troop_class.combat.vision_range);
- troop_class.combat.ranged_range =
- read_float(combat, "ranged_range", troop_class.combat.ranged_range);
- troop_class.combat.ranged_damage =
- read_int(combat, "ranged_damage", troop_class.combat.ranged_damage);
- troop_class.combat.ranged_cooldown = read_float(
- combat, "ranged_cooldown", troop_class.combat.ranged_cooldown);
- troop_class.combat.melee_range =
- read_float(combat, "melee_range", troop_class.combat.melee_range);
- troop_class.combat.melee_damage =
- read_int(combat, "melee_damage", troop_class.combat.melee_damage);
- troop_class.combat.melee_cooldown =
- read_float(combat, "melee_cooldown", troop_class.combat.melee_cooldown);
- troop_class.combat.can_ranged =
- read_bool(combat, "can_ranged", troop_class.combat.can_ranged);
- troop_class.combat.can_melee =
- read_bool(combat, "can_melee", troop_class.combat.can_melee);
- troop_class.combat.max_stamina =
- read_float(combat, "max_stamina", troop_class.combat.max_stamina);
- troop_class.combat.stamina_regen_rate = read_float(
- combat, "stamina_regen_rate", troop_class.combat.stamina_regen_rate);
- troop_class.combat.stamina_depletion_rate =
- read_float(combat, "stamina_depletion_rate",
- troop_class.combat.stamina_depletion_rate);
- const QJsonObject visuals = ensure_object(troop_obj.value("visuals"));
- troop_class.visuals.render_scale =
- read_float(visuals, "render_scale", troop_class.visuals.render_scale);
- troop_class.visuals.selection_ring_size =
- read_float(visuals, "selection_ring_size",
- troop_class.visuals.selection_ring_size);
- troop_class.visuals.selection_ring_y_offset =
- read_float(visuals, "selection_ring_y_offset",
- troop_class.visuals.selection_ring_y_offset);
- troop_class.visuals.selection_ring_ground_offset =
- read_float(visuals, "selection_ring_ground_offset",
- troop_class.visuals.selection_ring_ground_offset);
- const QString default_renderer = QStringLiteral("troops/") + troop_id;
- troop_class.visuals.renderer_id =
- visuals.value("renderer_id").toString(default_renderer).toStdString();
- const QJsonObject formation = ensure_object(troop_obj.value("formation"));
- troop_class.individuals_per_unit = read_int(
- formation, "individuals_per_unit", troop_class.individuals_per_unit);
- troop_class.max_units_per_row =
- read_int(formation, "max_units_per_row", troop_class.max_units_per_row);
- catalog.register_class(std::move(troop_class));
- }
- TroopConfig::instance().refresh_from_catalog();
- return true;
- }
- } // namespace Game::Units
|