#include "techconfig.h" #include "yamlparser.h" #include #include TechTreeManager::TechTreeManager() : m_maxColumn(0), m_maxRow(0) { } int TechTreeManager::computeTechDepth(const QString &techId, QMap &visited) const { if (visited.contains(techId)) { return visited.value(techId); } visited[techId] = 0; // Guard against cycles auto it = m_techs.find(techId); if (it == m_techs.end()) { return 0; } int maxPrereqDepth = -1; for (const QString &pre : it.value().prerequisites) { if (m_techs.contains(pre)) { int d = computeTechDepth(pre, visited); if (d > maxPrereqDepth) { maxPrereqDepth = d; } } } int depth = maxPrereqDepth + 1; visited[techId] = depth; return depth; } bool TechTreeManager::loadConfigFile(const QString &filePath) { YamlNode root = YamlParser::parseFile(filePath); if (root.children.isEmpty()) { qWarning() << "Failed to parse technologies config YAML:" << filePath; return false; } m_techs.clear(); m_orderedTechIds.clear(); m_maxColumn = 0; m_maxRow = 0; const YamlNode &techsNode = root.hasChild("technologies") ? root["technologies"] : root; QMap hasExplicitColumn; QMap hasExplicitRow; for (auto it = techsNode.children.begin(); it != techsNode.children.end(); ++it) { const YamlNode &node = it.value(); TechConfig cfg; cfg.id = node.hasChild("id") ? node["id"].asString() : it.key(); cfg.name = node.hasChild("name") ? node["name"].asString() : cfg.id; cfg.scienceCost = node.hasChild("science_cost") ? node["science_cost"].asInt(1000) : 1000; if (node.hasChild("cost") && !node.hasChild("science_cost")) { cfg.scienceCost = node["cost"].asInt(1000); } cfg.scienceCost = std::max(1, cfg.scienceCost); if (node.hasChild("prerequisites")) { cfg.prerequisites = node["prerequisites"].listValues; } if (node.hasChild("icon")) { cfg.iconPath = node["icon"].asString("images/resources/kennis.png"); } if (node.hasChild("description")) { cfg.description = node["description"].asString(""); } if (node.hasChild("column")) { cfg.column = node["column"].asInt(0); hasExplicitColumn[cfg.id] = true; } if (node.hasChild("row")) { cfg.row = node["row"].asInt(0); hasExplicitRow[cfg.id] = true; } m_techs[cfg.id] = cfg; } // Auto-calculate column and row for any tech without explicit coordinates QMap depthCache; QMap nextRowInCol; for (auto it = m_techs.begin(); it != m_techs.end(); ++it) { if (!hasExplicitColumn.value(it.key(), false)) { it.value().column = computeTechDepth(it.key(), depthCache); } if (hasExplicitRow.value(it.key(), false)) { nextRowInCol[it.value().column] = std::max(nextRowInCol.value(it.value().column, 0), it.value().row + 1); } } for (auto it = m_techs.begin(); it != m_techs.end(); ++it) { if (!hasExplicitRow.value(it.key(), false)) { int col = it.value().column; it.value().row = nextRowInCol.value(col, 0); nextRowInCol[col] = it.value().row + 1; } m_maxColumn = std::max(m_maxColumn, it.value().column); m_maxRow = std::max(m_maxRow, it.value().row); m_orderedTechIds.push_back(it.key()); } std::sort(m_orderedTechIds.begin(), m_orderedTechIds.end(), [&](const QString &a, const QString &b) { const TechConfig &ta = m_techs[a]; const TechConfig &tb = m_techs[b]; if (ta.column != tb.column) return ta.column < tb.column; if (ta.row != tb.row) return ta.row < tb.row; return ta.id < tb.id; }); qDebug() << "TechTreeManager loaded" << m_techs.size() << "technologies from" << filePath; return !m_techs.isEmpty(); } const TechConfig* TechTreeManager::getTech(const QString &id) const { auto it = m_techs.find(id); if (it != m_techs.end()) { return &it.value(); } return nullptr; }