#include "Campaign.h" #include "utils/Utils.h" #include "external/nlohmann/json.hpp" #include namespace FRG { using json = nlohmann::json; static bool parseJsonFile(const std::string& path, const std::string& zipFile, json& out) { const std::string content = readLocalizedConfigFile(path, zipFile); if (content.empty()) { std::cerr << "[Campaign] Failed to read " << path << "\n"; return false; } try { out = json::parse(content); } catch (const std::exception& e) { std::cerr << "[Campaign] JSON parse error in " << path << ": " << e.what() << "\n"; return false; } return true; } static Eigen::Vector3f readVec3(const json& j) { if (!j.is_array() || j.size() != 3) return Eigen::Vector3f::Zero(); return Eigen::Vector3f(j[0].get(), j[1].get(), j[2].get()); } bool LocationConfig::fromFile(const std::string& path, const std::string& zipFile, LocationConfig& out) { json j; if (!parseJsonFile(path, zipFile, j)) return false; out = LocationConfig{}; out.id = j.value("id", ""); out.gameObjectsJsonPath = j.value("gameObjects", ""); out.interactiveObjectsJsonPath = j.value("interactiveObjects", ""); out.npcsJsonPath = j.value("npcs", ""); out.dialoguesJsonPath = j.value("dialogues", ""); out.navigationJsonPaths = j.value("navigation", std::vector{}); out.scriptPath = j.value("script", ""); out.teleportsJsonPath = j.value("teleports", ""); out.triggerZonesJsonPath = j.value("triggerZones", ""); out.lightsJsonPath = j.value("lights", ""); if (j.contains("playerPosition")) out.playerPosition = readVec3(j["playerPosition"]); out.lockInteractiveObjectsDuringTutorial = j.value("lockInteractiveObjectsDuringTutorial", false); out.loadingScreenPath = j.value("loadingScreen", ""); if (j.contains("taxi") && j["taxi"].is_object()) { const json& t = j["taxi"]; Taxi taxi; if (t.contains("triggerPosition")) taxi.triggerPosition = readVec3(t["triggerPosition"]); if (t.contains("initialPosition")) taxi.initialPosition = readVec3(t["initialPosition"]); if (t.contains("waypoints") && t["waypoints"].is_array()) { for (const auto& w : t["waypoints"]) taxi.waypoints.push_back(readVec3(w)); } taxi.initialRotation = t.value("initialRotation", taxi.initialRotation); taxi.waypointReachRadius = t.value("waypointReachRadius", taxi.waypointReachRadius); out.taxi = taxi; } if (out.id.empty()) { std::cerr << "[Campaign] Location config without an id: " << path << "\n"; return false; } return true; } std::string CampaignConfig::title(Language lang) const { auto it = titles.find(languageToCode(lang)); if (it != titles.end()) return it->second; return titles.empty() ? id : titles.begin()->second; } const LocationConfig* CampaignConfig::findLocation(const std::string& locationId) const { for (const auto& loc : locations) { if (loc.id == locationId) return &loc; } return nullptr; } std::string CampaignConfig::loadingScreenFor(const std::string& locationId) const { const LocationConfig* loc = findLocation(locationId); return (loc && !loc->loadingScreenPath.empty()) ? loc->loadingScreenPath : loadingScreenPath; } CampaignRegistry& CampaignRegistry::instance() { static CampaignRegistry reg; return reg; } void CampaignRegistry::loadFromJson(const std::string& jsonPath, const std::string& zipFile) { json j; if (!parseJsonFile(jsonPath, zipFile, j)) return; if (!j.contains("campaigns") || !j["campaigns"].is_array()) { std::cerr << "[Campaign] No 'campaigns' array in " << jsonPath << "\n"; return; } campaigns_.clear(); for (const auto& entry : j["campaigns"]) { CampaignConfig c; c.id = entry.value("id", ""); if (c.id.empty()) continue; c.titles = entry.value("title", std::map{}); c.locationConfigPaths = entry.value("locations", std::vector{}); c.startLocation = entry.value("startLocation", ""); c.startMusic = entry.value("startMusic", ""); c.startStatePath = entry.value("startState", ""); c.questsPath = entry.value("quests", ""); c.tutorial = entry.value("tutorial", false); c.networkPlayerSlots = entry.value("networkPlayerSlots", std::vector{}); c.networkCompatibility = entry.value("networkCompatibility", std::string()); c.loadingScreenPath = entry.value("loadingScreen", std::string(CampaignConfig::kDefaultLoadingScreen)); for (const std::string& path : c.locationConfigPaths) { LocationConfig loc; if (LocationConfig::fromFile(path, zipFile, loc)) c.locations.push_back(std::move(loc)); } campaigns_.push_back(std::move(c)); } logger() << "[Campaign] Loaded " << campaigns_.size() << " campaigns from " << jsonPath << "\n"; } const CampaignConfig* CampaignRegistry::find(const std::string& id) const { for (const auto& c : campaigns_) { if (c.id == id) return &c; } return nullptr; } } // namespace FRG