#include "cutscene/CutsceneDatabase.h" #include "utils/Utils.h" #include namespace ZL { extern const char* CONST_ZIP_FILE; } namespace ZL::Cutscene { EasingType CutsceneDatabase::parseEasingType(const std::string& value) { if (value == "EaseInSine") return EasingType::EaseInSine; if (value == "EaseOutSine") return EasingType::EaseOutSine; if (value == "EaseInOutSine") return EasingType::EaseInOutSine; if (value == "EaseInQuad") return EasingType::EaseInQuad; if (value == "EaseOutQuad") return EasingType::EaseOutQuad; if (value == "EaseInOutQuad") return EasingType::EaseInOutQuad; if (value == "EaseInCubic") return EasingType::EaseInCubic; if (value == "EaseOutCubic") return EasingType::EaseOutCubic; if (value == "EaseInOutCubic") return EasingType::EaseInOutCubic; return EasingType::Linear; } CutsceneAnchor CutsceneDatabase::parseCutsceneAnchor(const std::string& value) { if (value == "TopLeft") return CutsceneAnchor::TopLeft; if (value == "TopRight") return CutsceneAnchor::TopRight; if (value == "BottomRight") return CutsceneAnchor::BottomRight; if (value == "BottomLeft") return CutsceneAnchor::BottomLeft; if (value == "Custom") return CutsceneAnchor::Custom; return CutsceneAnchor::Center; } CutsceneLine CutsceneDatabase::parseCutsceneLine(const json& j) { CutsceneLine line; line.speaker = j.value("speaker", ""); line.text = j.value("text", ""); line.portrait = j.value("portrait", ""); line.sfx = j.value("sfx", ""); line.background = j.value("background", ""); line.backgroundWidth = j.value("backgroundWidth", 0); line.backgroundHeight= j.value("backgroundHeight", 0); line.luaCallback = j.value("luaCallback", ""); line.durationMs = j.value("durationMs", 0); line.waitForConfirm = j.value("waitForConfirm", false); line.questUnlock = j.value("questUnlock", ""); line.questComplete = j.value("questComplete", ""); line.questFail = j.value("questFail", ""); line.objectiveComplete = j.value("objectiveComplete", ""); line.objectiveVisible = j.value("objectiveVisible", ""); return line; } CutsceneCameraPose CutsceneDatabase::parseCutsceneCameraPose(const json& j) { CutsceneCameraPose pose; pose.anchor = parseCutsceneAnchor(j.value("anchor", "Center")); pose.centerX = j.value("centerX", 0.5f); pose.centerY = j.value("centerY", 0.5f); pose.zoom = j.value("zoom", 1.0f); pose.rotationDeg = j.value("rotationDeg", 0.0f); return pose; } CutsceneCameraSegment CutsceneDatabase::parseCutsceneCameraSegment(const json& j) { CutsceneCameraSegment segment; segment.durationMs = j.value("durationMs", 0); segment.easing = parseEasingType(j.value("easing", "EaseInOutSine")); if (j.contains("from") && j["from"].is_object()) { segment.from = parseCutsceneCameraPose(j["from"]); } if (j.contains("to") && j["to"].is_object()) { segment.to = parseCutsceneCameraPose(j["to"]); } else { segment.to = segment.from; } return segment; } CutsceneImageCue CutsceneDatabase::parseCutsceneImageCue(const json& j) { CutsceneImageCue cue; cue.path = j.value("path", ""); cue.startMs = j.value("startMs", 0); cue.endMs = j.value("endMs", 0); cue.fadeInMs = j.value("fadeInMs", 0); cue.fadeOutMs = j.value("fadeOutMs", 0); return cue; } StaticCutsceneDefinition CutsceneDatabase::parseCutscene(const json& j) { StaticCutsceneDefinition cutscene; cutscene.id = j.value("id", ""); cutscene.background = j.value("background", ""); cutscene.backgroundWidth = j.value("backgroundWidth", 1280); cutscene.backgroundHeight= j.value("backgroundHeight", 720); cutscene.music = j.value("music", ""); cutscene.skippable = j.value("skippable", true); cutscene.durationMs = j.value("durationMs", 0); cutscene.fadeOutMs = j.value("fadeOutMs", 0); cutscene.fadeInMs = j.value("fadeInMs", 0); cutscene.endFadeOutMs = j.value("endFadeOutMs", 0); cutscene.endFadeInMs = j.value("endFadeInMs", 0); cutscene.onFadeInCallback= j.value("onFadeInCallback", ""); if (j.contains("cameraTrack") && j["cameraTrack"].is_array()) { for (const auto& item : j["cameraTrack"]) { cutscene.cameraTrack.push_back(parseCutsceneCameraSegment(item)); } } if (j.contains("images") && j["images"].is_array()) { for (const auto& item : j["images"]) { cutscene.images.push_back(parseCutsceneImageCue(item)); } } if (j.contains("lines") && j["lines"].is_array()) { for (const auto& item : j["lines"]) { cutscene.lines.push_back(parseCutsceneLine(item)); } } return cutscene; } bool CutsceneDatabase::loadFromFile(const std::string& path) { cutscenes.clear(); std::string raw; try { if (strlen(ZL::CONST_ZIP_FILE) == 0) { raw = readTextFile(path); } else { auto buf = readFileFromZIP(path, ZL::CONST_ZIP_FILE); if (buf.empty()) { std::cerr << "[cutscene] Failed to read " << path << " from zip\n"; throw std::runtime_error("Failed to load cutscene file: " + path); } raw.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { std::cerr << "[cutscene] Failed to open " << path << ": " << e.what() << "\n"; throw std::runtime_error("Failed to load cutscene file: " + path); } json root; try { root = json::parse(raw); } catch (const std::exception& e) { std::cerr << "[cutscene] JSON parse error in " << path << ": " << e.what() << "\n"; return false; } if (root.contains("cutscenes") && root["cutscenes"].is_array()) { for (const auto& item : root["cutscenes"]) { StaticCutsceneDefinition cutscene = parseCutscene(item); if (!cutscene.id.empty()) { cutscenes[cutscene.id] = std::move(cutscene); } } } return true; } const StaticCutsceneDefinition* CutsceneDatabase::findCutscene(const std::string& id) const { auto it = cutscenes.find(id); return (it != cutscenes.end()) ? &it->second : nullptr; } } // namespace ZL::Cutscene