Files

484 lines
20 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Configs;
using UnityEngine;
namespace Data
{
/// <summary>
/// 表示模组包的基本信息。
/// </summary>
public class PackAbout
{
private const string RootElementName = "About";
private const string ElementName_Name = "name";
private const string ElementName_Description = "description";
private const string ElementName_Author = "author";
private const string ElementName_Version = "version";
private const string ElementName_PackID = "packID";
private const string ElementName_Sort = "sort";
private const string ElementName_Before = "before";
private const string ElementName_After = "after";
private const string ElementName_Necessary = "necessary";
public string Name { get; private set; }
public string Description { get; private set; }
public string Author { get; private set; }
public string Version { get; private set; }
public string PackID { get; private set; }
public string[] Necessary { get; private set; }
public string[] After { get; private set; }
public string[] Before { get; private set; }
/// <summary>
/// 使用静态方法从 XML 文档创建 PackAbout 实例。
/// </summary>
/// <param name="doc">XML 文档。</param>
/// <returns>初始化的 PackAbout 实例。</returns>
/// <exception cref="ArgumentException">当 XML 文档无效,根节点为空或不是 'About' 时抛出。</exception>
public static PackAbout FromXDocument(XDocument doc)
{
var aboutElement = doc.Element(RootElementName);
if (aboutElement == null)
throw new ArgumentException($"<color=red>XML 文档无效,根节点为空或不是 '{RootElementName}'。</color>");
PackAbout result = new();
result.Name = aboutElement.Element(ElementName_Name)?.Value ?? "Unknown";
result.Description = aboutElement.Element(ElementName_Description)?.Value ?? "Unknown";
result.Author = aboutElement.Element(ElementName_Author)?.Value ?? "Unknown";
result.Version = aboutElement.Element(ElementName_Version)?.Value ?? "Unknown";
result.PackID = aboutElement.Element(ElementName_PackID)?.Value ?? "Unknown";
var sortElement = aboutElement.Element(ElementName_Sort);
if (sortElement != null)
{
result.Before = GetElementValues(sortElement.Element(ElementName_Before));
result.After = GetElementValues(sortElement.Element(ElementName_After));
result.Necessary = GetElementValues(sortElement.Element(ElementName_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; // 负数为左对齐
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();
}
}
/// <summary>
/// 表示一个模组包的定义集合。
/// </summary>
public class DefinePack
{
private const string CoreNamespace = "Data.";
private const string RootElementName_About = "About";
private const string RootElementName_Define = "Define";
// 反射缓存
private static readonly Dictionary<string, Type> _typeCache = new();
private static readonly Dictionary<Type, ConstructorInfo> _constructorCache = new();
private static readonly Dictionary<Type, FieldInfo[]> _fieldCache = new();
private static readonly List<Assembly> _assembliesToSearch = new(); // 缓存要搜索的程序集
/// <summary>
/// 定义类别及其定义。
/// </summary>
public Dictionary<string, List<Define>> defines = new();
public PackAbout packAbout;
public string packID;
public string packRootPath;
public int priority = -1;
static DefinePack()
{
// 初始化要搜索的程序集。
_assembliesToSearch.AddRange(AppDomain.CurrentDomain.GetAssemblies());
}
public string Name =>
// 使用 PackAbout.Name 属性,若为空则返回默认值。
packAbout?.Name ?? "Unnamed Pack";
/// <summary>
/// 加载模组包。
/// </summary>
/// <param name="packPath">模组包的路径。</param>
/// <returns>如果模组包加载成功,则为 true否则为 false。</returns>
public bool LoadPack(string packPath)
{
packRootPath = Path.GetFullPath(packPath);
var packDatas = ConfigProcessor.LoadXmlFromPath(packPath);
var aboutXmls = FindDocumentsWithRootName(packDatas, RootElementName_About);
if (aboutXmls == null || aboutXmls.Count < 1)
{
Debug.LogError("<color=red>包缺少配置文件,加载跳过。</color>");
return false;
}
var aboutXml = aboutXmls[0];
packAbout = PackAbout.FromXDocument(aboutXml);
packID = packAbout.PackID;
if (aboutXmls.Count > 1)
Debug.LogWarning($"<color=yellow>{packAbout.Name} 包拥有多个配置文件,系统选择了加载序的第一个,请避免这种情况。</color>");
var defineXmls = FindDocumentsWithRootName(packDatas, RootElementName_Define);
foreach (var defineXml in defineXmls) LoadDefines(defineXml);
return true;
}
/// <summary>
/// 加载定义。
/// </summary>
/// <param name="defineDoc">包含定义的 XML 文档。</param>
private void LoadDefines(XDocument defineDoc)
{
var rootElement = defineDoc.Root;
if (rootElement == null)
{
Debug.LogWarning("<color=yellow>加载定义时发现一个XML文档的根元素为空跳过此文档。</color>");
return;
}
if (rootElement.Name != RootElementName_Define)
{
Debug.LogWarning(
$"<color=yellow>加载定义时发现XML文档根元素名称 '{rootElement.Name}' 不符合预期 '{RootElementName_Define}',跳过此文档。</color>");
return;
}
foreach (var element in rootElement.Elements())
{
var className = element.Name.ToString();
if (string.IsNullOrEmpty(className))
continue;
var def = LoadDefineClass(element, className);
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>
public static Define LoadDefineClass(XElement defineDoc, string className)
{
// 反射缓存和改进的类型查找
if (!_typeCache.TryGetValue(className, out var type))
{
// 尝试 CoreNamespace + className
var fullClassName = CoreNamespace + className;
type = _assembliesToSearch.Select(a => a.GetType(fullClassName)).FirstOrDefault(t => t != null);
// 如果未找到,尝试不使用 CoreNamespace
if (type == null)
type = _assembliesToSearch.Select(a => a.GetType(className)).FirstOrDefault(t => t != null);
if (type == null)
{
Debug.LogError($"<color=red>未定义的类型: {className}。</color>");
return null;
}
_typeCache[className] = type;
}
// 构造函数缓存
if (!_constructorCache.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
Debug.LogError($"<color=red>类 {className} 必须包含无参构造函数。</color>");
return null;
}
_constructorCache[type] = constructor;
}
// 创建实例
object instance;
try
{
instance = constructor.Invoke(null);
}
catch (Exception ex)
{
Debug.LogError($"<color=red>创建 {className} 实例失败: {ex.Message}。</color>");
return null;
}
// 检查是否继承自 Define
if (instance is not Define define)
{
Debug.LogError($"<color=red>类 {className} 必须继承自 Define。</color>");
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>
public static void DefaultInitDefine(Define define, XElement defineDoc, Type defineType)
{
// FieldInfo 缓存
if (!_fieldCache.TryGetValue(defineType, out var fields))
{
fields = defineType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
_fieldCache[defineType] = fields;
}
// 遍历字段并尝试从 XElement 中赋值
foreach (var field in fields)
{
// 查找对应的 XElement 子元素
var element = defineDoc.Element(field.Name);
if (element != null)
try
{
var value = ConvertXElementValueToType(element, field.FieldType);
field.SetValue(define, value);
}
catch (Exception ex)
{
Debug.LogWarning(
$"<color=yellow>设置字段时出错,字段名: {field.Name}; 值: {element.Value}; 错误: {ex.Message}。</color>");
}
}
var properties = defineType
.GetProperties(BindingFlags.Public | BindingFlags.Instance |
BindingFlags.NonPublic) // 也获取私有属性,但要求私有属性有 setter
.Where(p => p.CanWrite); // 只要可写就可以设置
foreach (var property in properties)
{
var element = defineDoc.Element(property.Name);
if (element != null)
try
{
var value = ConvertXElementValueToType(element, property.PropertyType);
property.SetValue(define, value);
}
catch (Exception ex)
{
Debug.LogWarning(
$"<color=yellow>设置属性 '{property.Name}' 时出错,值: '{element.Value}',错误: {ex.Message}。</color>");
}
}
}
/// <summary>
/// 将 XElement 值转换为特定类型。
/// </summary>
/// <param name="element">XElement 元素。</param>
/// <param name="targetType">目标类型。</param>
/// <returns>转换后的值。</returns>
private static object ConvertXElementValueToType(XElement element, Type targetType)
{
if (IsTypeInheritedFrom(targetType, typeof(Define)))
{
if (element.HasElements) return LoadDefineClass(element, targetType.Name);
// 引用另一个 Define
var reference = (Define)Activator.CreateInstance(targetType);
reference.isReferene = true;
reference.description = targetType.Name;
reference.label = element.Name.LocalName; // 使用元素的名称作为标签
reference.defName = element.Value;
return reference;
}
if (targetType.IsArray || typeof(IList).IsAssignableFrom(targetType))
return ProcessArrayField(targetType, element);
if (targetType.IsEnum) return Enum.Parse(targetType, element.Value);
return Convert.ChangeType(element.Value, targetType);
}
/// <summary>
/// 处理数组字段。
/// </summary>
/// <param name="fieldType">字段类型。</param>
/// <param name="element">XElement 元素。</param>
/// <returns>处理后的数组对象。</returns>
private static object ProcessArrayField(Type fieldType, XElement element)
{
// 获取集合的元素类型
var elementType = fieldType.IsArray
? fieldType.GetElementType()
: fieldType.GetGenericArguments().FirstOrDefault();
if (elementType == null)
{
Debug.LogWarning($"<color=yellow>无法确定类型为 {fieldType.Name} 的集合字段的元素类型。</color>");
return null;
}
var arrayElements = new List<object>();
// 遍历 XML 子元素
foreach (var liElement in element.Elements())
arrayElements.Add(ConvertXElementValueToType(liElement, elementType));
// 根据目标字段的类型构造结果
if (fieldType.IsArray)
{
var resultArray = Array.CreateInstance(elementType, arrayElements.Count);
for (var i = 0; i < arrayElements.Count; i++) resultArray.SetValue(arrayElements[i], i);
return resultArray;
}
if (typeof(IList).IsAssignableFrom(fieldType))
{
var listType = typeof(List<>).MakeGenericType(elementType);
var resultList = (IList)Activator.CreateInstance(listType);
foreach (var item in arrayElements) resultList.Add(item);
return resultList;
}
return null;
}
/// <summary>
/// 从 <see cref="List{XDocument}" /> 中查找指定根元素名称的文档。
/// </summary>
/// <param name="xmlDocuments">XML 文档列表。</param>
/// <param name="rootName">目标根元素名称。</param>
/// <returns>符合条件的 XML 文档列表。</returns>
public static List<XDocument> FindDocumentsWithRootName(List<XDocument> xmlDocuments, string rootName)
{
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");
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("未找到定义。");
return sb.ToString();
}
/// <summary>
/// 检查字段的类型是否继承自指定的类(严格派生,不包括基类本身)。
/// </summary>
/// <param name="field">字段信息。</param>
/// <param name="baseType">要检查的基类类型。</param>
/// <returns>如果字段的类型是基类的严格派生类,则返回 true否则返回 false。</returns>
public static bool IsFieldTypeInheritedFrom(FieldInfo field, Type baseType)
{
var fieldType = field.FieldType;
// 严格派生:不包括基类本身
return fieldType != null && fieldType != baseType && baseType.IsAssignableFrom(fieldType);
}
/// <summary>
/// 检查一个类型是否继承自指定的基类(严格派生,不包括基类本身)。
/// </summary>
/// <param name="type">要检查的类型。</param>
/// <param name="baseType">要检查的基类类型。</param>
/// <returns>如果类型是基类的严格派生类,则返回 true否则返回 false。</returns>
public static bool IsTypeInheritedFrom(Type type, Type baseType)
{
return type != null && type != baseType && baseType.IsAssignableFrom(type);
}
}
}