#include "dialogue/DialogueDatabase.h" #include "utils/Utils.h" #include namespace ZL::Dialogue { NodeType DialogueDatabase::parseNodeType(const std::string& value) { if (value == "Choice") return NodeType::Choice; if (value == "Condition") return NodeType::Condition; if (value == "SetFlag") return NodeType::SetFlag; if (value == "Jump") return NodeType::Jump; if (value == "End") return NodeType::End; if (value == "CutsceneStart") return NodeType::CutsceneStart; return NodeType::Line; } ChoiceKind DialogueDatabase::parseChoiceKind(const std::string& value) { if (value == "Optional") return ChoiceKind::Optional; if (value == "Exit") return ChoiceKind::Exit; return ChoiceKind::Main; } ComparisonOp DialogueDatabase::parseComparisonOp(const std::string& value) { if (value == "==" || value == "Equals") return ComparisonOp::Equals; if (value == "!=" || value == "NotEquals") return ComparisonOp::NotEquals; if (value == ">=" || value == "GreaterOrEqual") return ComparisonOp::GreaterOrEqual; if (value == "<=" || value == "LessOrEqual") return ComparisonOp::LessOrEqual; return ComparisonOp::Exists; } EasingType DialogueDatabase::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 DialogueDatabase::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; } Condition DialogueDatabase::parseCondition(const json& j) { Condition c; c.flag = j.value("flag", ""); c.op = parseComparisonOp(j.value("op", "Exists")); c.value = j.value("value", 1); return c; } Effect DialogueDatabase::parseEffect(const json& j) { Effect e; e.flag = j.value("flag", ""); e.value = j.value("value", 1); e.relative = j.value("relative", false); return e; } Choice DialogueDatabase::parseChoice(const json& j) { Choice c; c.id = j.value("id", ""); c.text = j.value("text", ""); c.next = j.value("next", ""); c.kind = parseChoiceKind(j.value("kind", "Main")); c.consumeOnce = j.value("consumeOnce", false); if (j.contains("conditions") && j["conditions"].is_array()) { for (const auto& item : j["conditions"]) { c.conditions.push_back(parseCondition(item)); } } if (j.contains("effects") && j["effects"].is_array()) { for (const auto& item : j["effects"]) { c.effects.push_back(parseEffect(item)); } } return c; } Node DialogueDatabase::parseNode(const json& j) { Node node; node.id = j.value("id", ""); node.type = parseNodeType(j.value("type", "Line")); node.speaker = j.value("speaker", ""); node.text = j.value("text", ""); node.portrait = j.value("portrait", ""); node.next = j.value("next", ""); node.trueNext = j.value("trueNext", ""); node.falseNext = j.value("falseNext", ""); node.cutsceneId = j.value("cutsceneId", ""); if (j.contains("conditions") && j["conditions"].is_array()) { for (const auto& item : j["conditions"]) { node.conditions.push_back(parseCondition(item)); } } if (j.contains("effects") && j["effects"].is_array()) { for (const auto& item : j["effects"]) { node.effects.push_back(parseEffect(item)); } } if (j.contains("choices") && j["choices"].is_array()) { for (const auto& item : j["choices"]) { node.choices.push_back(parseChoice(item)); } } return node; } DialogueDefinition DialogueDatabase::parseDialogue(const json& j) { DialogueDefinition result; result.id = j.value("id", ""); result.displayName = j.value("displayName", result.id); result.startNode = j.value("start", ""); if (j.contains("nodes") && j["nodes"].is_array()) { for (const auto& item : j["nodes"]) { Node node = parseNode(item); if (!node.id.empty()) { result.nodes[node.id] = std::move(node); } } } return result; } CutsceneLine DialogueDatabase::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.durationMs = j.value("durationMs", 0); line.waitForConfirm = j.value("waitForConfirm", false); return line; } CutsceneCameraPose DialogueDatabase::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 DialogueDatabase::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; } StaticCutsceneDefinition DialogueDatabase::parseCutscene(const json& j) { StaticCutsceneDefinition cutscene; cutscene.id = j.value("id", ""); cutscene.background = j.value("background", ""); cutscene.music = j.value("music", ""); cutscene.skippable = j.value("skippable", true); cutscene.durationMs = j.value("durationMs", 0); if (j.contains("cameraTrack") && j["cameraTrack"].is_array()) { for (const auto& item : j["cameraTrack"]) { cutscene.cameraTrack.push_back(parseCutsceneCameraSegment(item)); } } if (j.contains("lines") && j["lines"].is_array()) { for (const auto& item : j["lines"]) { cutscene.lines.push_back(parseCutsceneLine(item)); } } return cutscene; } bool DialogueDatabase::loadFromFile(const std::string& path) { dialogues.clear(); cutscenes.clear(); const std::string raw = ZL::readTextFile(path); if (raw.empty()) { std::cerr << "[dialogue] Failed to read file: " << path << "\n"; return false; } json root; try { root = json::parse(raw); } catch (const std::exception& e) { std::cerr << "[dialogue] JSON parse error in " << path << ": " << e.what() << "\n"; return false; } if (root.contains("dialogues") && root["dialogues"].is_array()) { for (const auto& item : root["dialogues"]) { DialogueDefinition dialogue = parseDialogue(item); if (!dialogue.id.empty()) { dialogues[dialogue.id] = std::move(dialogue); } } } 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 !dialogues.empty(); } const DialogueDefinition* DialogueDatabase::findDialogue(const std::string& id) const { auto it = dialogues.find(id); return (it != dialogues.end()) ? &it->second : nullptr; } const StaticCutsceneDefinition* DialogueDatabase::findCutscene(const std::string& id) const { auto it = cutscenes.find(id); return (it != cutscenes.end()) ? &it->second : nullptr; } } // namespace ZL::Dialogue