Files
Gen_Hack-and-Slash-Roguelit…/Client/Assets/Scripts/Data/DefinePack.cs
2025-07-16 13:05:04 +08:00

348 lines
14 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 System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Configs;
using UnityEngine;
using Object = System.Object;
namespace Data
{
public class PackAbout
{
public string name;
public string description;
public string author;
public string version;
public string packID;
public string[] necessary;
public string[] after;
public string[] before;
/// <summary>
/// 使用静态方法从 XML 文档创建 PackAbout 实例。
/// </summary>
/// <param name="doc">XML 文档。</param>
/// <returns>初始化的 PackAbout 实例。</returns>
public static PackAbout FromXDocument(XDocument doc)
{
var aboutElement = doc.Element("About");
if (aboutElement == null) throw new ArgumentException("XML 文档无效,根节点为空或不是 'About'。");
PackAbout result = new();
result.name = aboutElement.Element("name")?.Value ?? "Unknown";
result.description = aboutElement.Element("description")?.Value ?? "Unknown";
result.author = aboutElement.Element("author")?.Value ?? "Unknown";
result.version = aboutElement.Element("version")?.Value ?? "Unknown";
result.packID = aboutElement.Element("packID")?.Value ?? "Unknown";
var sortElement = aboutElement.Element("sort");
if (sortElement != null)
{
result.before = GetElementValues(sortElement.Element("before"));
result.after = GetElementValues(sortElement.Element("after"));
result.necessary = GetElementValues(sortElement.Element("necessary"));
}
else
{
result.before = Array.Empty<string>();
result.after = Array.Empty<string>();
result.necessary = Array.Empty<string>();
}
return result;
}
/// <summary>
/// 获取指定 XElement 下所有子元素的值并返回为字符串数组。
/// </summary>
/// <param name="element">父 XElement。</param>
/// <returns>字符串数组。</returns>
private static string[] GetElementValues(XElement element)
{
if (element == null || !element.HasElements) return Array.Empty<string>();
return element.Elements()
.Select(e => e.Value.Trim())
.ToArray();
}
public override string ToString()
{
// 定义字段标签和值的对齐格式(如左对齐,固定宽度)
const int labelWidth = -12; // 负数为左对齐
const int valueWidth = -30; // 负数为左对齐
// 使用StringBuilder高效拼接
var sb = new StringBuilder();
// 基础字段(单行)
sb.AppendLine($"{"Name:",labelWidth}{name,valueWidth}");
sb.AppendLine($"{"Description:",labelWidth}{description,valueWidth}");
sb.AppendLine($"{"Author:",labelWidth}{author,valueWidth}");
sb.AppendLine($"{"Version:",labelWidth}{version,valueWidth}");
sb.AppendLine($"{"PackID:",labelWidth}{packID,valueWidth}");
// 数组字段(多行,每项缩进)
sb.AppendLine(
$"{"Necessary:",labelWidth}{string.Join(", ", necessary ?? Array.Empty<string>()),valueWidth}");
sb.AppendLine($"{"After:",labelWidth}{string.Join(", ", after ?? Array.Empty<string>()),valueWidth}");
sb.AppendLine($"{"Before:",labelWidth}{string.Join(", ", before ?? Array.Empty<string>()),valueWidth}");
return sb.ToString();
}
}
public class DefinePack
{
private const string CoreNamespace = "Data.";
/// <summary>
/// define类别及其定义
/// </summary>
public Dictionary<string, List<Define>> defines=new();
public PackAbout packAbout;
public string packID;
public bool LoadPack(string packPath)
{
var packDatas = ConfigProcessor.LoadXmlFromPath(packPath);
var aboutXmls = FindDocumentsWithRootName(packDatas, "About");
if (aboutXmls == null || aboutXmls.Count < 1)
{
Debug.LogError("包缺少配置文件,加载跳过");
return false;
}
var aboutXml = aboutXmls[0];
packAbout = PackAbout.FromXDocument(aboutXml);
packID = packAbout.packID;
if (aboutXmls.Count > 1) Debug.LogWarning($"{packAbout.name}包拥有多个配置文件,系统选择了加载序的第一个,请避免这种情况");
var defineXmls = FindDocumentsWithRootName(packDatas, "Define");
// Debug.Log($"Define文件数量{defineXmls.Count}");
foreach (var defineXml in defineXmls) LoadDefines(defineXml);
return true;
}
private void LoadDefines(XDocument defineDoc)
{
var rootElement = defineDoc.Root;
if (rootElement == null || rootElement.Name != "Define")
return;
foreach (var element in rootElement.Elements())
{
var className = element.Name.ToString();
if (string.IsNullOrEmpty(className))
continue;
var def = LoadDefineClass(element,element.Name.ToString());
if (def == null)
continue;
def.packID = packID;
if (!defines.ContainsKey(className))
defines.Add(className, new List<Define>());
defines[className].Add(def);
}
}
/// <summary>
/// 根据指定的 XML 元素 (<paramref name="defineDoc"/>) 和类名 (<paramref name="className"/>),
/// 动态加载并初始化一个继承自 <see cref="Define"/> 的类实例。
/// </summary>
/// <param name="defineDoc">包含类定义的 XML 元素 (<see cref="XElement"/>)。</param>
/// <param name="className">目标类的全限定名或简短名称。</param>
/// <returns>
/// 如果成功加载并初始化,则返回对应的 <see cref="Define"/> 类实例;
/// 否则返回 null。
/// </returns>
/// <exception cref="ArgumentNullException">
/// 如果 <paramref name="defineDoc"/> 或 <paramref name="className"/> 为 null 或空字符串,则抛出此异常。
/// </exception>
/// <remarks>
/// 该方法通过反射动态加载指定类,并检查其是否继承自 <see cref="Define"/>。
/// 如果类存在且满足条件,则尝试调用其 <see cref="Define.Init(XElement)"/> 方法进行初始化。
/// 如果初始化失败,则使用默认初始化方法 (<see cref="DefaultInitDefine(Define, XElement, Type)"/>)。
/// </remarks>
public static Define LoadDefineClass(XElement defineDoc,string className)
{
var assembly = Assembly.GetExecutingAssembly();
Type type;
if (!className.Contains('.'))
{
// 尝试拼接默认命名空间
var fullClassName = CoreNamespace + className;
type = assembly.GetType(fullClassName);
// 如果拼接命名空间后仍找不到,尝试直接查找(可能是全局命名空间下的类)
if (type == null) type = assembly.GetType(className);
}
else
{
// 直接查找
type = assembly.GetType(className);
}
if (type == null)
{
Debug.LogError($"未定义的类型: {className}");
return null;
}
var constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
Debug.LogError($"{className} 必须包含无参构造函数");
return null;
}
// 3. 创建实例
object instance;
try
{
instance = Activator.CreateInstance(type);
}
catch (Exception ex)
{
Debug.LogError($"创建 {className} 实例失败: {ex.Message}");
return null;
}
// 4. 检查是否继承自 Define
if (instance is not Define define)
{
Debug.LogError($"{className} 必须继承自 Define");
return null;
}
if (define.Init(defineDoc)) return define;
DefaultInitDefine(define,defineDoc, type);
return define;
}
/// <summary>
/// 初始化指定的 <paramref name="define"/> 对象,根据 <paramref name="defineDoc"/> 中的 XML 元素内容,
/// 将对应的字段值赋给 <paramref name="define"/> 对象。
/// </summary>
/// <param name="define">需要初始化的对象实例。</param>
/// <param name="defineDoc">包含字段定义的 XML 元素 (<see cref="XElement"/>)。</param>
/// <param name="defineType">目标对象的类型 (<see cref="Type"/>)。</param>
/// <exception cref="ArgumentNullException">
/// 如果 <paramref name="define"/>、<paramref name="defineDoc"/> 或 <paramref name="defineType"/> 为 null则抛出此异常。
/// </exception>
/// <remarks>
/// 该方法会遍历 <paramref name="defineType"/> 的所有字段(包括公共和非公共字段),
/// 并尝试从 <paramref name="defineDoc"/> 中找到与字段名称匹配的子元素。
/// 如果找到匹配的子元素,则将其值转换为字段的类型并赋值给字段。
/// 如果字段类型继承自 <see cref="Define"/>,则递归调用 <see cref="LoadDefineClass(XElement, string)"/> 方法进行加载。
/// </remarks>
public static void DefaultInitDefine(Define define,XElement defineDoc,Type defineType)
{
var fields = defineType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
// 遍历字段并尝试从 XElement 中赋值
foreach (var field in fields)
{
// 查找对应的 XElement 子元素
var element = defineDoc.Element(field.Name);
if (element != null)
try
{
Object value;
if (IsFieldTypeInheritedFrom(field, typeof(Define)))
{
if (element.HasElements)
{
value = LoadDefineClass(element, field.FieldType.Name);
}
else
{
value = new DefineReference(field.FieldType.Name, element.Value, field.Name);
}
}
else
value = Convert.ChangeType(element.Value, field.FieldType);
field.SetValue(define, value);
}
catch (Exception ex)
{
Debug.LogWarning($"Error setting field {field.Name}: {ex.Message}");
}
}
}
/// <summary>
/// 从 List<c>XDocument</c> 中查找指定根元素名称的文档。
/// </summary>
/// <param name="xmlDocuments">XML 文档列表。</param>
/// <param name="rootName">目标根元素名称。</param>
/// <returns>符合条件的 XML 文档列表。</returns>
public static List<XDocument> FindDocumentsWithRootName(List<XDocument> xmlDocuments, string rootName)
{
// Using LINQ to Objects for a more concise solution
var result = xmlDocuments
.Where(doc => doc.Root != null && doc.Root.Name.LocalName == rootName)
.ToList();
return result;
}
public override string ToString()
{
// 对齐格式(左对齐,固定宽度)
const int labelWidth = -15;
const int valueWidth = -30;
var sb = new StringBuilder();
// 基础字段
sb.AppendLine($"{"PackID:",labelWidth}{packID,valueWidth}");
sb.AppendLine();
// PackAbout 对象
sb.AppendLine("=== PackAbout ===");
sb.AppendLine(packAbout?.ToString() ?? "N/A"); // 调用 PackAbout 的 ToString()
sb.AppendLine();
// 字典字段defines
sb.AppendLine("=== Defines ===");
if (defines != null && defines.Count > 0)
foreach (var kvp in defines)
{
sb.AppendLine($"【{kvp.Key}】"); // 输出字典的键(类别名)
foreach (var define in kvp.Value) // 遍历该类别下的所有 Define 对象
sb.AppendLine(define.ToString()); // 调用 Define 的 ToString()
sb.AppendLine(); // 每个类别后空一行
}
else
sb.AppendLine("No defines found.");
return sb.ToString();
}
/// <summary>
/// 检查字段的类型是否继承自指定的类
/// </summary>
/// <param name="field">字段信息</param>
/// <param name="baseType">要检查的基类类型</param>
/// <returns>如果字段的类型是基类或其派生类,则返回 true</returns>
public static bool IsFieldTypeInheritedFrom(FieldInfo field, Type baseType)
{
// 获取字段的类型
var fieldType = field.FieldType;
// 如果字段的类型为 null 或不是基类的派生类,则返回 false
if (!baseType.IsAssignableFrom(fieldType))
return false;
// 如果字段的类型直接是基类或其派生类,则返回 true
return fieldType != baseType && baseType.IsAssignableFrom(fieldType);
}
}
}