Files

466 lines
21 KiB
C#
Raw Permalink 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 System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; // 引入 Task
using Data;
using Utils;
namespace Managers
{
public class SkillTreeManager : Singleton<SkillTreeManager>, ILaunchManager, ISavableSingleton
{
// 用于缓存解锁技能树的 SkillTreeDef 列表,提高频繁访问的效率
private List<SkillTreeDef> _cachedUnlockedSkillTreesList;
private Dictionary<string, List<SkillTreeDef>> _childrenOfNode;
// 内部数据结构,用于高效查询和图遍历
private Dictionary<string, SkillTreeDef> _skillNodesByDefName;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByTag;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByFaction;
public SkillTreeManager()
{
// 遵循不检查单例的原则,假定 SaveManager.Instance 始终可用。
SaveManager.Instance.RegisterSavable(this);
}
// 解锁技能树的 HashSet由 Savable 属性标记,用于存档
[Savable] public HashSet<string> UnlockedSkillTrees { get; set; } = new();
/// <summary>
/// 获取所有已解锁的技能树定义列表。此列表是缓存的,性能高效。
/// </summary>
public List<SkillTreeDef> UnlockedSkillTreesList =>
_cachedUnlockedSkillTreesList ?? RebuildUnlockedSkillTreesCache();
/// <summary>
/// 指示管理器是否已完成初始化。
/// </summary>
public bool Completed { get; set; } // 新增:实现 ILaunchManager 接口的 Completed 属性
public string StepDescription => "正在加载技能树";
/// <summary>
/// 初始化技能树管理器,加载并解析所有技能树定义。
/// </summary>
/// <returns>一个表示异步操作完成的 Task。</returns>
public async Task Init() // 接口变更:方法签名变为 async Task
{
// 增加检查,防止重复初始化
if (Completed) return;
// 遵循不检查单例的原则,假定 DefineManager.Instance 始终可用。
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>>();
if (allSkillTreeNodes == null)
{
Completed = true; // 即使没有定义,也认为初始化流程已完成
return;
}
// 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))
{
// 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}'.");
}
// 4. 初始化解锁技能树的缓存列表
RebuildUnlockedSkillTreesCache();
Completed = true; // 在所有初始化逻辑完成后设置 Completed 为 true
// 由于 Init 方法内部当前没有真正的异步操作,不需要显式 await Task.CompletedTask;
// 编译器会为同步的 async Task 方法自动生成一个已完成的 Task。
}
/// <summary>
/// 清空技能树管理器加载的所有数据结构。
/// </summary>
public void Clear()
{
// 清理所有数据结构,释放内存
_skillNodesByDefName?.Clear();
_skillNodesByTag?.Clear();
_skillNodesByFaction?.Clear();
_childrenOfNode?.Clear();
// 清理解锁技能树的缓存列表
_cachedUnlockedSkillTreesList?.Clear();
_cachedUnlockedSkillTreesList = null;
// 将引用设为null有助于GC
_skillNodesByDefName = null;
_skillNodesByTag = null;
_skillNodesByFaction = null;
_childrenOfNode = null;
// 重要UnlockedSkillTrees 是 Savable 属性,它的状态由 SaveManager 管理,
// 通常不应在 Clear 方法中清空。Clear 仅清理运行时数据结构。
Completed = false; // 清理后将 Completed 置为 false
}
/// <summary>
/// 当一个技能树被成功解锁时触发。
/// </summary>
public event Action<SkillTreeDef> OnSkillTreeUnlocked;
/// <summary>
/// 当一个技能树被成功锁定时触发。
/// </summary>
public event Action<SkillTreeDef> OnSkillTreeLocked;
/// <summary>
/// 根据当前 UnlockedSkillTrees 集合重建解锁技能树的缓存列表。
/// </summary>
private List<SkillTreeDef> RebuildUnlockedSkillTreesCache()
{
// 在尝试查询技能定义之前,确保 _skillNodesByDefName 字典已初始化。
// 这可以防止在 Init() 尚未完全完成时访问缓存导致的空引用问题。
if (_skillNodesByDefName == null)
{
// 返回空列表是更温和的处理方式,表示目前没有可用的技能定义。
_cachedUnlockedSkillTreesList = new List<SkillTreeDef>();
return _cachedUnlockedSkillTreesList;
}
_cachedUnlockedSkillTreesList = new List<SkillTreeDef>(UnlockedSkillTrees.Count); // 预分配容量
foreach (var skillTreeDefName in UnlockedSkillTrees)
if (_skillNodesByDefName.TryGetValue(skillTreeDefName, out var skillDef) && skillDef != null)
_cachedUnlockedSkillTreesList.Add(skillDef);
return _cachedUnlockedSkillTreesList;
}
// --- 节点查询 ---
/// <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?.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>
/// 获取指定名称的技能树定义,如果名称无效或未找到,则抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要获取的技能树定义名称。</param>
/// <returns>对应的 SkillTreeDef 对象。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 为空或未找到对应的 SkillTreeDef。</exception>
private SkillTreeDef GetValidSkillTreeDef(string skillTreeDefName)
{
// `TryGetValue` 成功但 `node` 仍为null理论上不发生但为安全起见
if (string.IsNullOrEmpty(skillTreeDefName)
|| !_skillNodesByDefName.TryGetValue(skillTreeDefName, out var node)
|| node == null)
throw new ArgumentException($"Invalid or unknown SkillTreeDef name: '{skillTreeDefName}'.",
nameof(skillTreeDefName));
return node;
}
/// <summary>
/// 尝试解锁一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要解锁的技能树的定义名称。</param>
/// <returns>如果技能树成功解锁(之前未解锁),则为 true如果技能树已经解锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool UnlockSkillTree(string skillTreeDefName)
{
var skillDef = GetValidSkillTreeDef(skillTreeDefName); // 使用新的验证方法,如果无效会抛出异常
if (UnlockedSkillTrees.Add(skillTreeDefName))
{
RebuildUnlockedSkillTreesCache(); // 技能树状态改变,重建缓存
OnSkillTreeUnlocked?.Invoke(skillDef); // 触发解锁事件
return true;
}
return false;
}
/// <summary>
/// 尝试加锁(锁定)一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要加锁的技能树的定义名称。</param>
/// <returns>如果技能树成功加锁(之前已解锁),则为 true如果技能树已经加锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool LockSkillTree(string skillTreeDefName)
{
var skillDef = GetValidSkillTreeDef(skillTreeDefName); // 使用新的验证方法,如果无效会抛出异常
if (UnlockedSkillTrees.Remove(skillTreeDefName))
{
RebuildUnlockedSkillTreesCache(); // 技能树状态改变,重建缓存
OnSkillTreeLocked?.Invoke(skillDef); // 触发锁定事件
return true;
}
return false;
}
/// <summary>
/// 查询指定技能树是否已解锁。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要查询的技能树的定义名称。</param>
/// <returns>如果技能树已解锁,则为 true否则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool IsSkillTreeUnlocked(string skillTreeDefName)
{
// GetValidSkillTreeDef 会在 defName 无效时抛出 ArgumentException
// 如果成功返回,说明 skillTreeDefName 是有效的
GetValidSkillTreeDef(skillTreeDefName);
return UnlockedSkillTrees.Contains(skillTreeDefName);
}
}
}