| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- #include "map_catalog.h"
- #include "campaign_loader.h"
- #include "json_keys.h"
- #include "mission_loader.h"
- #include "utils/resource_utils.h"
- #include <QDir>
- #include <QFile>
- #include <QFileInfo>
- #include <QJsonArray>
- #include <QJsonDocument>
- #include <QJsonObject>
- #include <QJsonParseError>
- #include <QSet>
- #include <QStringList>
- #include <QTimer>
- #include <QVariantMap>
- #include <algorithm>
- #include <qdir.h>
- #include <qfiledevice.h>
- #include <qglobal.h>
- #include <qjsonarray.h>
- #include <qjsondocument.h>
- #include <qjsonobject.h>
- #include <qjsonvalue.h>
- #include <qlist.h>
- #include <qobject.h>
- #include <qset.h>
- #include <qstringliteral.h>
- #include <qstringview.h>
- #include <qtimer.h>
- #include <qtmetamacros.h>
- namespace Game::Map {
- using namespace JsonKeys;
- namespace {
- auto resolve_mission_file_path(const QString &mission_id) -> QString {
- const QStringList search_paths = {
- QStringLiteral("assets/missions/%1.json"),
- QStringLiteral("../assets/missions/%1.json"),
- QStringLiteral("../../assets/missions/%1.json"),
- QStringLiteral(":/assets/missions/%1.json"),
- QStringLiteral("/assets/missions/%1.json"),
- QStringLiteral("/../assets/missions/%1.json")};
- for (const auto &pattern : search_paths) {
- QString candidate = pattern.arg(mission_id);
- candidate = Utils::Resources::resolveResourcePath(candidate);
- if (QFileInfo::exists(candidate)) {
- return candidate;
- }
- }
- return {};
- }
- auto collect_campaign_files() -> QStringList {
- const QStringList search_paths = {QStringLiteral("assets/campaigns"),
- QStringLiteral("../assets/campaigns"),
- QStringLiteral("../../assets/campaigns"),
- QStringLiteral(":/assets/campaigns"),
- QStringLiteral("/assets/campaigns"),
- QStringLiteral("/../assets/campaigns")};
- QStringList files;
- QSet<QString> seen;
- for (const auto &path : search_paths) {
- const QString resolved = Utils::Resources::resolveResourcePath(path);
- QDir campaigns_dir(resolved);
- if (!campaigns_dir.exists()) {
- continue;
- }
- const QStringList entries = campaigns_dir.entryList(
- QStringList() << "*.json", QDir::Files, QDir::Name);
- for (const auto &entry : entries) {
- const QString campaign_path = campaigns_dir.filePath(entry);
- if (!seen.contains(campaign_path)) {
- seen.insert(campaign_path);
- files.append(campaign_path);
- }
- }
- }
- return files;
- }
- auto load_campaign_map_paths() -> QSet<QString> {
- QSet<QString> map_paths;
- const QStringList campaign_files = collect_campaign_files();
- for (const auto &campaign_path : campaign_files) {
- Game::Campaign::CampaignDefinition campaign;
- QString error;
- if (!Game::Campaign::CampaignLoader::loadFromJsonFile(campaign_path,
- campaign, &error)) {
- qWarning() << "Failed to load campaign for map filtering:"
- << campaign_path << error;
- continue;
- }
- for (const auto &mission : campaign.missions) {
- const QString mission_file =
- resolve_mission_file_path(mission.mission_id);
- if (mission_file.isEmpty()) {
- qWarning() << "Missing mission file for campaign map filtering:"
- << mission.mission_id;
- continue;
- }
- Game::Mission::MissionDefinition mission_def;
- if (!Game::Mission::MissionLoader::loadFromJsonFile(
- mission_file, mission_def, &error)) {
- qWarning() << "Failed to load mission for map filtering:"
- << mission_file << error;
- continue;
- }
- const QString map_path =
- Utils::Resources::resolveResourcePath(mission_def.map_path);
- if (!map_path.isEmpty()) {
- map_paths.insert(map_path);
- }
- }
- }
- return map_paths;
- }
- } // namespace
- MapCatalog::MapCatalog(QObject *parent) : QObject(parent) {}
- auto MapCatalog::availableMaps() -> QVariantList {
- QVariantList list;
- const QSet<QString> campaign_map_paths = load_campaign_map_paths();
- const QString maps_root =
- Utils::Resources::resolveResourcePath(QStringLiteral(":/assets/maps"));
- QDir const maps_dir(maps_root);
- if (!maps_dir.exists()) {
- return list;
- }
- QStringList const files =
- maps_dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
- for (const QString &f : files) {
- QString const path =
- Utils::Resources::resolveResourcePath(maps_dir.filePath(f));
- if (campaign_map_paths.contains(path)) {
- continue;
- }
- QFile file(path);
- QString name = f;
- QString desc;
- QSet<int> player_ids;
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray const data = file.readAll();
- file.close();
- QJsonParseError err;
- QJsonDocument const doc = QJsonDocument::fromJson(data, &err);
- if (err.error == QJsonParseError::NoError && doc.isObject()) {
- QJsonObject obj = doc.object();
- if (obj.contains(NAME) && obj[NAME].isString()) {
- name = obj[NAME].toString();
- }
- if (obj.contains(DESCRIPTION) && obj[DESCRIPTION].isString()) {
- desc = obj[DESCRIPTION].toString();
- }
- if (obj.contains(SPAWNS) && obj[SPAWNS].isArray()) {
- QJsonArray const spawns = obj[SPAWNS].toArray();
- for (const QJsonValue &spawn_val : spawns) {
- if (spawn_val.isObject()) {
- QJsonObject spawn = spawn_val.toObject();
- if (spawn.contains(PLAYER_ID)) {
- int const player_id = spawn[PLAYER_ID].toInt();
- if (player_id > 0) {
- player_ids.insert(player_id);
- }
- }
- }
- }
- }
- }
- }
- QVariantMap entry;
- entry[NAME] = name;
- entry[DESCRIPTION] = desc;
- entry["path"] = path;
- entry["playerCount"] = player_ids.size();
- QVariantList player_id_list;
- QList<int> sorted_ids = player_ids.values();
- std::sort(sorted_ids.begin(), sorted_ids.end());
- for (int const id : sorted_ids) {
- player_id_list.append(id);
- }
- entry["player_ids"] = player_id_list;
- QString thumbnail;
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray const data = file.readAll();
- file.close();
- QJsonParseError err;
- QJsonDocument const doc = QJsonDocument::fromJson(data, &err);
- if (err.error == QJsonParseError::NoError && doc.isObject()) {
- QJsonObject obj = doc.object();
- if (obj.contains(THUMBNAIL) && obj[THUMBNAIL].isString()) {
- thumbnail = obj[THUMBNAIL].toString();
- }
- }
- }
- if (thumbnail.isEmpty()) {
- QString const base_name = QFileInfo(f).baseName();
- QString const thumb_candidate = Utils::Resources::resolveResourcePath(
- QString(":/assets/maps/%1_thumb.png").arg(base_name));
- if (QFileInfo::exists(thumb_candidate)) {
- thumbnail = thumb_candidate;
- } else {
- thumbnail = "";
- }
- }
- entry["thumbnail"] = thumbnail;
- list.append(entry);
- }
- return list;
- }
- void MapCatalog::load_maps_async() {
- if (m_loading) {
- return;
- }
- m_maps.clear();
- m_pendingFiles.clear();
- ensure_campaign_map_paths_loaded();
- m_loading = true;
- emit loading_changed(true);
- const QString maps_root =
- Utils::Resources::resolveResourcePath(QStringLiteral(":/assets/maps"));
- QDir const maps_dir(maps_root);
- if (!maps_dir.exists()) {
- m_loading = false;
- emit loading_changed(false);
- emit all_maps_loaded();
- return;
- }
- m_pendingFiles =
- maps_dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
- if (m_pendingFiles.isEmpty()) {
- m_loading = false;
- emit loading_changed(false);
- emit all_maps_loaded();
- return;
- }
- QTimer::singleShot(0, this, &MapCatalog::load_next_map);
- }
- void MapCatalog::load_next_map() {
- if (m_pendingFiles.isEmpty()) {
- m_loading = false;
- emit loading_changed(false);
- emit all_maps_loaded();
- return;
- }
- QString const file_name = m_pendingFiles.takeFirst();
- const QString maps_root =
- Utils::Resources::resolveResourcePath(QStringLiteral(":/assets/maps"));
- QDir const maps_dir(maps_root);
- QString const path =
- Utils::Resources::resolveResourcePath(maps_dir.filePath(file_name));
- if (!m_campaign_map_paths.contains(path)) {
- QVariantMap const entry = loadSingleMap(path);
- if (!entry.isEmpty()) {
- m_maps.append(entry);
- emit map_loaded(entry);
- }
- }
- if (!m_pendingFiles.isEmpty()) {
- QTimer::singleShot(10, this, &MapCatalog::load_next_map);
- } else {
- m_loading = false;
- emit loading_changed(false);
- emit all_maps_loaded();
- }
- }
- auto MapCatalog::loadSingleMap(const QString &path) -> QVariantMap {
- const QString resolved_path = Utils::Resources::resolveResourcePath(path);
- QFile file(resolved_path);
- QString name = QFileInfo(resolved_path).fileName();
- QString desc;
- QSet<int> player_ids;
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray const data = file.readAll();
- file.close();
- QJsonParseError err;
- QJsonDocument const doc = QJsonDocument::fromJson(data, &err);
- if (err.error == QJsonParseError::NoError && doc.isObject()) {
- QJsonObject obj = doc.object();
- if (obj.contains(NAME) && obj[NAME].isString()) {
- name = obj[NAME].toString();
- }
- if (obj.contains(DESCRIPTION) && obj[DESCRIPTION].isString()) {
- desc = obj[DESCRIPTION].toString();
- }
- if (obj.contains(SPAWNS) && obj[SPAWNS].isArray()) {
- QJsonArray const spawns = obj[SPAWNS].toArray();
- for (const QJsonValue &spawn_val : spawns) {
- if (spawn_val.isObject()) {
- QJsonObject spawn = spawn_val.toObject();
- if (spawn.contains(PLAYER_ID)) {
- int const player_id = spawn[PLAYER_ID].toInt();
- if (player_id > 0) {
- player_ids.insert(player_id);
- }
- }
- }
- }
- }
- }
- }
- QVariantMap entry;
- entry[NAME] = name;
- entry[DESCRIPTION] = desc;
- entry["path"] = resolved_path;
- entry["playerCount"] = player_ids.size();
- QVariantList player_id_list;
- QList<int> sorted_ids = player_ids.values();
- std::sort(sorted_ids.begin(), sorted_ids.end());
- for (int const id : sorted_ids) {
- player_id_list.append(id);
- }
- entry["player_ids"] = player_id_list;
- QString thumbnail;
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray const data = file.readAll();
- file.close();
- QJsonParseError err;
- QJsonDocument const doc = QJsonDocument::fromJson(data, &err);
- if (err.error == QJsonParseError::NoError && doc.isObject()) {
- QJsonObject obj = doc.object();
- if (obj.contains(THUMBNAIL) && obj[THUMBNAIL].isString()) {
- thumbnail = obj[THUMBNAIL].toString();
- }
- }
- }
- if (thumbnail.isEmpty()) {
- QString const base_name = QFileInfo(resolved_path).baseName();
- QString const thumb_candidate = Utils::Resources::resolveResourcePath(
- QString(":/assets/maps/%1_thumb.png").arg(base_name));
- if (QFileInfo::exists(thumb_candidate)) {
- thumbnail = thumb_candidate;
- } else {
- thumbnail = "";
- }
- }
- entry["thumbnail"] = thumbnail;
- return entry;
- }
- void MapCatalog::ensure_campaign_map_paths_loaded() {
- if (m_campaign_map_paths_loaded) {
- return;
- }
- m_campaign_map_paths = load_campaign_map_paths();
- m_campaign_map_paths_loaded = true;
- }
- } // namespace Game::Map
|