Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Managers/SkillTreeManager.cs

401 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using Data;
using System.Collections.Generic;
using System.Linq;
namespace Managers
{
public class SkillTreeManager : Utils.Singleton<SkillTreeManager>, ILaunchManager, ISavableSingleton
{
public string StepDescription => "正在加载技能树";
// 内部数据结构,用于高效查询和图遍历
private Dictionary<string, SkillTreeDef> _skillNodesByDefName;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByTag;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByFaction;
private Dictionary<string, List<SkillTreeDef>> _childrenOfNode;
[Savable] public HashSet<string> UnlockedSkillTrees { get; set; } = new();
public SkillTreeManager()
{
SaveManager.Instance.RegisterSavable(this);
}
~SkillTreeManager()
{
SaveManager.Instance.UnregisterSavable(this);
}
public void Init()
{
// 假设 DefineManager.Instance 存在且 QueryDefinesByType 方法可用
// 如果 DefineManager 是单例,这里直接调用即可。
// 否则,需要确保 DefineManager 在此之前已被正确初始化。
var allSkillTreeNodes = DefineManager.Instance.QueryDefinesByType<SkillTreeDef>();
// 1. 初始化字典
_skillNodesByDefName = new Dictionary<string, SkillTreeDef>();
_skillNodesByTag = new Dictionary<string, List<SkillTreeDef>>();
_skillNodesByFaction = new Dictionary<string, List<SkillTreeDef>>();
_childrenOfNode = new Dictionary<string, List<SkillTreeDef>>();
// 2. 填充基本查询字典
foreach (var node in allSkillTreeNodes)
{
if (string.IsNullOrEmpty(node.defName))
{
// 可以选择抛出异常或记录警告,取决于项目的错误处理策略
// 例如: Log.Warning($"SkillTreeDef with empty defName found. Skipping.");
continue;
}
if (!_skillNodesByDefName.TryAdd(node.defName, node))
{
// 避免重复defName通常defName应该是唯一的
// 例如: Log.Error($"Duplicate SkillTreeDef defName '{node.defName}' found. Only the first one will be used.");
continue; // 跳过此重复节点
}
// 填充 _skillNodesByTag
if (!string.IsNullOrEmpty(node.tag))
{
if (!_skillNodesByTag.ContainsKey(node.tag))
{
_skillNodesByTag[node.tag] = new List<SkillTreeDef>();
}
_skillNodesByTag[node.tag].Add(node);
}
// 填充 _skillNodesByFaction
// 假设 AffiliationDef 也有 defName 属性作为唯一标识
if (node.faction != null && !string.IsNullOrEmpty(node.faction.defName))
{
if (!_skillNodesByFaction.ContainsKey(node.faction.defName))
{
_skillNodesByFaction[node.faction.defName] = new List<SkillTreeDef>();
}
_skillNodesByFaction[node.faction.defName].Add(node);
}
}
// 3. 填充 _childrenOfNode 字典(构建父 -> 子关系)
// 需要再次遍历,因为在填充 _skillNodesByDefName 之前prerequisites 中的引用可能尚未完全解析。
foreach (var childNode in allSkillTreeNodes)
{
if (childNode.prerequisites != null)
{
foreach (var prerequisite in childNode.prerequisites)
{
// 确保父节点在 _skillNodesByDefName 中存在,避免野指针或无效引用
if (prerequisite != null &&
_skillNodesByDefName.TryGetValue(prerequisite.defName, out var parentNode))
{
if (!_childrenOfNode.ContainsKey(parentNode.defName))
{
_childrenOfNode[parentNode.defName] = new List<SkillTreeDef>();
}
_childrenOfNode[parentNode.defName].Add(childNode);
}
else if (prerequisite != null)
{
// 记录警告prerequisites中引用的父节点找不到定义可能是数据配置错误
// 例如: Log.Warning($"SkillTreeDef '{childNode.defName}' references unknown prerequisite '{prerequisite.defName}'.");
}
}
}
}
}
public void Clear()
{
// 清理所有数据结构,释放内存
_skillNodesByDefName?.Clear();
_skillNodesByTag?.Clear();
_skillNodesByFaction?.Clear();
_childrenOfNode?.Clear();
// 将引用设为null有助于GC
_skillNodesByDefName = null;
_skillNodesByTag = null;
_skillNodesByFaction = null;
_childrenOfNode = null;
}
// --- 节点查询 ---
/// <summary>
/// 根据定义名查询技能树节点
/// </summary>
/// <param name="defName">技能树节点的定义名</param>
/// <returns>匹配的 SkillTreeDef如果未找到则为 null</returns>
public SkillTreeDef GetNodeByDefName(string defName)
{
if (string.IsNullOrEmpty(defName)) return null;
_skillNodesByDefName.TryGetValue(defName, out var node);
return node;
}
/// <summary>
/// 根据标签查询技能树节点列表
/// </summary>
/// <param name="tag">技能树节点的标签</param>
/// <returns>匹配的 SkillTreeDef 列表,如果未找到则返回空列表</returns>
public List<SkillTreeDef> GetNodesByTag(string tag)
{
if (string.IsNullOrEmpty(tag)) return new List<SkillTreeDef>();
_skillNodesByTag.TryGetValue(tag, out var nodes);
return nodes ?? new List<SkillTreeDef>();
}
/// <summary>
/// 根据派系名称查询技能树节点列表
/// </summary>
/// <param name="factionName">派系的定义名</param>
/// <returns>匹配的 SkillTreeDef 列表,如果未找到则返回空列表</returns>
public List<SkillTreeDef> GetNodesByFaction(string factionName)
{
if (string.IsNullOrEmpty(factionName)) return new List<SkillTreeDef>();
_skillNodesByFaction.TryGetValue(factionName, out var nodes);
return nodes ?? new List<SkillTreeDef>();
}
public string[] GetAllTag()
{
return _skillNodesByTag.Keys.ToArray();
}
// --- 父子关系查询 ---
/// <summary>
/// 判断 targetNode 是否是 baseNode 的直接父节点
/// </summary>
/// <param name="baseNode">作为父节点候选的节点</param>
/// <param name="targetNode">作为子节点候选的节点</param>
/// <returns>如果是直接父节点则为 true否则为 false</returns>
public bool IsDirectParentOf(SkillTreeDef baseNode, SkillTreeDef targetNode)
{
if (baseNode == null || targetNode == null || targetNode.prerequisites == null) return false;
return targetNode.prerequisites.Any(prerequisite =>
prerequisite != null && prerequisite.defName == baseNode.defName);
}
/// <summary>
/// 判断 targetNode 是否是 baseNode 的直接子节点
/// </summary>
/// <param name="baseNode">作为父节点的节点</param>
/// <param name="targetNode">作为子节点候选的节点</param>
/// <returns>如果是直接子节点则为 true否则为 false</returns>
public bool IsDirectChildOf(SkillTreeDef baseNode, SkillTreeDef targetNode)
{
if (baseNode == null || targetNode == null) return false;
// 查找 baseNode 的子节点列表中是否包含 targetNode
_childrenOfNode.TryGetValue(baseNode.defName, out var children);
return children != null && children.Any(c => c.defName == targetNode.defName);
}
/// <summary>
/// 判断 ancestor 是否是 descendant 的任何一级父节点(包括直接和间接)
/// </summary>
/// <param name="ancestor">祖先节点候选</param>
/// <param name="descendant">后代节点候选</param>
/// <returns>如果是祖先节点则为 true否则为 false</returns>
public bool IsAncestorOf(SkillTreeDef ancestor, SkillTreeDef descendant)
{
if (ancestor == null || descendant == null) return false;
if (ancestor.defName == descendant.defName) return false; // 自己不是自己的祖先
return GetAllAncestors(descendant).Any(n => n.defName == ancestor.defName);
}
/// <summary>
/// 判断 descendant 是否是 ancestor 的任何一级子节点(包括直接和间接)
/// </summary>
/// <param name="descendant">后代节点候选</param>
/// <param name="ancestor">祖先节点候选</param>
/// <returns>如果是后代节点则为 true否则为 false</returns>
public bool IsDescendantOf(SkillTreeDef descendant, SkillTreeDef ancestor)
{
if (descendant == null || ancestor == null) return false;
if (descendant.defName == ancestor.defName) return false; // 自己不是自己的后代
return GetAllDescendants(ancestor).Any(n => n.defName == descendant.defName);
}
// --- 获取所有相关节点 ---
/// <summary>
/// 获取所有直接父节点
/// </summary>
/// <param name="node">要查询的节点</param>
/// <returns>直接父节点列表,如果无父节点则返回空列表</returns>
public List<SkillTreeDef> GetAllDirectParents(SkillTreeDef node)
{
if (node == null || node.prerequisites == null) return new List<SkillTreeDef>();
// 确保返回的是真实存在于管理器中的节点,而不是可能无效的引用
return node.prerequisites.Where(p => p != null && _skillNodesByDefName.ContainsKey(p.defName))
.Select(p => _skillNodesByDefName[p.defName])
.ToList();
}
/// <summary>
/// 获取所有直接子节点
/// </summary>
/// <param name="node">要查询的节点</param>
/// <returns>直接子节点列表,如果无子节点则返回空列表</returns>
public List<SkillTreeDef> GetAllDirectChildren(SkillTreeDef node)
{
if (node == null) return new List<SkillTreeDef>();
_childrenOfNode.TryGetValue(node.defName, out var children);
return children ?? new List<SkillTreeDef>();
}
/// <summary>
/// 获取所有祖先节点(包括直接和间接父节点),使用 BFS 算法避免循环依赖
/// </summary>
/// <param name="node">要查询的节点</param>
/// <returns>所有祖先节点列表</returns>
public List<SkillTreeDef> GetAllAncestors(SkillTreeDef node)
{
if (node == null) return new List<SkillTreeDef>();
var ancestors = new HashSet<SkillTreeDef>(); // 使用HashSet避免重复和循环
var queue = new Queue<SkillTreeDef>();
// 将当前节点的直接父节点作为起点加入队列
if (node.prerequisites != null)
{
foreach (var directParentRef in node.prerequisites)
{
if (directParentRef != null &&
_skillNodesByDefName.TryGetValue(directParentRef.defName, out var parentNode))
{
if (!ancestors.Contains(parentNode))
{
ancestors.Add(parentNode);
queue.Enqueue(parentNode);
}
}
}
}
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (current.prerequisites != null)
{
foreach (var prerequisiteRef in current.prerequisites)
{
if (prerequisiteRef != null &&
_skillNodesByDefName.TryGetValue(prerequisiteRef.defName, out var actualParentNode))
{
if (!ancestors.Contains(actualParentNode))
{
ancestors.Add(actualParentNode);
queue.Enqueue(actualParentNode);
}
}
}
}
}
return ancestors.ToList();
}
/// <summary>
/// 获取所有后代节点(包括直接和间接子节点),使用 BFS 算法避免循环依赖
/// </summary>
/// <param name="node">要查询的节点</param>
/// <returns>所有后代节点列表</returns>
public List<SkillTreeDef> GetAllDescendants(SkillTreeDef node)
{
if (node == null) return new List<SkillTreeDef>();
var descendants = new HashSet<SkillTreeDef>(); // 使用HashSet避免重复和循环
var queue = new Queue<SkillTreeDef>();
// 将当前节点的直接子节点作为起点加入队列
_childrenOfNode.TryGetValue(node.defName, out var directChildren);
if (directChildren != null)
{
foreach (var child in directChildren)
{
if (!descendants.Contains(child))
{
descendants.Add(child);
queue.Enqueue(child);
}
}
}
while (queue.Count > 0)
{
var current = queue.Dequeue();
_childrenOfNode.TryGetValue(current.defName, out var children);
if (children != null)
{
foreach (var child in children)
{
if (!descendants.Contains(child))
{
descendants.Add(child);
queue.Enqueue(child);
}
}
}
}
return descendants.ToList();
}
/// <summary>
/// 确保技能树定义名称有效。
/// </summary>
/// <param name="skillTreeDefName">要验证的技能树定义名称。</param>
/// <exception cref="ArgumentException">如果技能树定义名称无效(不在 _skillNodesByDefName 中),则抛出。</exception>
private bool ValidateSkillTreeDefName(string skillTreeDefName)
{
return _skillNodesByDefName.ContainsKey(skillTreeDefName);
}
/// <summary>
/// 尝试解锁一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要解锁的技能树的定义名称。</param>
/// <returns>如果技能树成功解锁(之前未解锁),则为 true如果技能树已经解锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool UnlockSkillTree(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Add(skillTreeDefName);
}
/// <summary>
/// 尝试加锁(锁定)一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要加锁的技能树的定义名称。</param>
/// <returns>如果技能树成功加锁(之前已解锁),则为 true如果技能树已经加锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool LockSkillTree(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Remove(skillTreeDefName);
}
/// <summary>
/// 查询指定技能树是否已解锁。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要查询的技能树的定义名称。</param>
/// <returns>如果技能树已解锁,则为 true否则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool IsSkillTreeUnlocked(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Contains(skillTreeDefName);
}
}
}