mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-05 21:15:52 +08:00
282 lines
11 KiB
C#
282 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Hosting;
|
|
using RainOpsMini.Helpers;
|
|
using RainOpsMini.Models;
|
|
using System.Xml.Linq;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Unicode;
|
|
using System.Reflection;
|
|
|
|
namespace RainOpsMini.Plugins
|
|
{
|
|
/// <summary>
|
|
/// 插件管理器,负责加载、保存和管理插件配置
|
|
/// </summary>
|
|
public class PluginManager
|
|
{
|
|
private readonly IEnumerable<IPlugin> _plugins;
|
|
|
|
private readonly string _configDirectory;
|
|
|
|
/// <summary>
|
|
/// 构造函数,初始化插件管理器
|
|
/// </summary>
|
|
/// <param name="plugins">注入的插件集合</param>
|
|
public PluginManager(IEnumerable<IPlugin> plugins)
|
|
{
|
|
_plugins = plugins;
|
|
_configDirectory = Path.Combine(Environment.CurrentDirectory, "PluginConfig");
|
|
// 确保配置目录存在
|
|
if (!Directory.Exists(_configDirectory))
|
|
{
|
|
Directory.CreateDirectory(_configDirectory);
|
|
}
|
|
// 启动时加载所有插件配置
|
|
LoadConfigs();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取所有插件的信息,包括名称、描述、启用状态和配置
|
|
/// </summary>
|
|
/// <returns>包含插件信息的匿名对象集合</returns>
|
|
public IEnumerable<object> GetPlugins()
|
|
{
|
|
return _plugins.Select(p => new
|
|
{
|
|
p.Name,
|
|
p.Description,
|
|
p.Category,
|
|
CategoryName = GetCategoryName(p.Category),
|
|
p.IsEnabled,
|
|
Config = p.GetConfig(),
|
|
// 获取配置属性的中文描述
|
|
Descriptions = GetConfigDescriptions(p.GetConfig().GetType())
|
|
});
|
|
}
|
|
|
|
private static string GetCategoryName(PluginCategory category)
|
|
{
|
|
return category switch
|
|
{
|
|
PluginCategory.BasicFunction => "基础功能",
|
|
PluginCategory.PointsFunction => "积分功能",
|
|
PluginCategory.JointBan => "联合封禁",
|
|
PluginCategory.CombatBuff => "战斗BUFF",
|
|
PluginCategory.AdvancedFunction => "高级功能",
|
|
_ => "未知分类"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据名称获取特定插件实例
|
|
/// </summary>
|
|
/// <param name="name">插件名称(不区分大小写)</param>
|
|
/// <returns>插件实例,如果未找到则返回 null</returns>
|
|
public IPlugin? GetPlugin(string name)
|
|
{
|
|
return _plugins.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新指定插件的配置并保存到文件
|
|
/// </summary>
|
|
/// <param name="name">插件名称</param>
|
|
/// <param name="config">新的配置 JSON 对象</param>
|
|
public void UpdateConfig(string name, JsonElement config)
|
|
{
|
|
var plugin = GetPlugin(name);
|
|
if (plugin != null)
|
|
{
|
|
plugin.UpdateConfig(config);
|
|
SaveConfig(plugin);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 重置指定插件的配置为默认值并保存
|
|
/// </summary>
|
|
/// <param name="name">插件名称</param>
|
|
public void ResetConfig(string name)
|
|
{
|
|
var plugin = GetPlugin(name);
|
|
if (plugin != null)
|
|
{
|
|
plugin.ResetConfig();
|
|
SaveConfig(plugin);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 加载所有插件的配置文件
|
|
/// </summary>
|
|
private void LoadConfigs()
|
|
{
|
|
foreach (var plugin in _plugins)
|
|
{
|
|
try
|
|
{
|
|
string filePath = Path.Combine(_configDirectory, $"{plugin.GetType().Name}.json");
|
|
|
|
if (File.Exists(filePath))
|
|
{
|
|
// 如果配置文件存在,读取并应用
|
|
string json = File.ReadAllText(filePath);
|
|
var doc = JsonDocument.Parse(json);
|
|
plugin.UpdateConfig(doc.RootElement);
|
|
}
|
|
else
|
|
{
|
|
// 如果配置文件不存在,保存默认配置
|
|
SaveConfig(plugin);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RainOpsLog.Log($"[PluginManager] 加载配置失败 {plugin.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将插件配置保存到 JSON 文件
|
|
/// </summary>
|
|
/// <param name="plugin">要保存配置的插件实例</param>
|
|
private void SaveConfig(IPlugin plugin)
|
|
{
|
|
try
|
|
{
|
|
var options = new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true,
|
|
// 确保中文不被转义
|
|
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
|
};
|
|
string json = JsonSerializer.Serialize(plugin.GetConfig(), options);
|
|
|
|
string filePath = Path.Combine(_configDirectory, $"{plugin.GetType().Name}.json");
|
|
File.WriteAllText(filePath, json, System.Text.Encoding.UTF8);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RainOpsLog.Log($"[PluginManager] 保存配置失败 {plugin.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从 XML 文档文件中获取配置属性的描述信息
|
|
/// </summary>
|
|
/// <param name="configType">配置类的类型</param>
|
|
/// <returns>属性名与描述的键值对字典</returns>
|
|
private Dictionary<string, string> GetConfigDescriptions(Type configType)
|
|
{
|
|
var descriptions = new Dictionary<string, string>();
|
|
|
|
// 尝试多个可能的路径寻找 XML 文档
|
|
string[] possiblePaths = new[]
|
|
{
|
|
Path.Combine(Environment.CurrentDirectory, "RainOpsMini.xml"),
|
|
Path.Combine(Directory.GetCurrentDirectory(), "RainOpsMini.xml"),
|
|
Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net6.0", "win-x64", "RainOpsMini.xml"),
|
|
Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net6.0", "RainOpsMini.xml")
|
|
};
|
|
|
|
string xmlPath = possiblePaths.FirstOrDefault(File.Exists);
|
|
|
|
if (xmlPath == null)
|
|
{
|
|
RainOpsLog.Log($"[PluginManager] Warning: RainOpsMini.xml not found in any expected location. BaseDir: {Environment.CurrentDirectory}, CurrentDir: {Directory.GetCurrentDirectory()}");
|
|
return descriptions;
|
|
}
|
|
|
|
try
|
|
{
|
|
var doc = XDocument.Load(xmlPath);
|
|
|
|
// 获取配置类及其引用的类型的描述
|
|
var typesToProcess = new HashSet<Type> { configType };
|
|
var processedTypes = new HashSet<Type>();
|
|
|
|
while (typesToProcess.Count > 0)
|
|
{
|
|
var currentType = typesToProcess.First();
|
|
typesToProcess.Remove(currentType);
|
|
|
|
if (processedTypes.Contains(currentType)) continue;
|
|
processedTypes.Add(currentType);
|
|
|
|
foreach (var prop in currentType.GetProperties())
|
|
{
|
|
// 构建 Member ID: P:Namespace.Class.Property
|
|
string typeFullName = currentType.FullName?.Replace("+", ".") ?? currentType.Name;
|
|
string memberId = $"P:{typeFullName}.{prop.Name}";
|
|
|
|
var summary = doc.Descendants("member")
|
|
.FirstOrDefault(m => m.Attribute("name")?.Value == memberId)
|
|
?.Element("summary")
|
|
?.Value
|
|
.Trim();
|
|
|
|
if (!string.IsNullOrEmpty(summary))
|
|
{
|
|
// 同时存储原始名称和 CamelCase 名称以最大化兼容性
|
|
descriptions[prop.Name] = summary;
|
|
|
|
string camelKey = JsonNamingPolicy.CamelCase.ConvertName(prop.Name);
|
|
if (camelKey != prop.Name)
|
|
{
|
|
descriptions[camelKey] = summary;
|
|
}
|
|
}
|
|
|
|
// 检查是否需要处理属性的类型(如果是自定义类或 List 中的类)
|
|
var propType = prop.PropertyType;
|
|
if (propType.IsGenericType && (propType.GetGenericTypeDefinition() == typeof(List<>) || propType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
|
{
|
|
var itemType = propType.GetGenericArguments()[0];
|
|
|
|
// 尝试获取 List 元素类型的类注释
|
|
string itemTypeFullName = itemType.FullName?.Replace("+", ".") ?? itemType.Name;
|
|
string itemTypeId = $"T:{itemTypeFullName}";
|
|
|
|
var itemTypeSummary = doc.Descendants("member")
|
|
.FirstOrDefault(m => m.Attribute("name")?.Value == itemTypeId)
|
|
?.Element("summary")
|
|
?.Value
|
|
.Trim();
|
|
|
|
if (!string.IsNullOrEmpty(itemTypeSummary))
|
|
{
|
|
descriptions[$"{prop.Name}$Item"] = itemTypeSummary;
|
|
string camelKey = JsonNamingPolicy.CamelCase.ConvertName(prop.Name);
|
|
if (camelKey != prop.Name)
|
|
{
|
|
descriptions[$"{camelKey}$Item"] = itemTypeSummary;
|
|
}
|
|
}
|
|
|
|
propType = itemType;
|
|
}
|
|
|
|
if (propType.IsClass && propType != typeof(string) && !processedTypes.Contains(propType) && propType.Namespace != null && propType.Namespace.StartsWith("RainOpsMini"))
|
|
{
|
|
typesToProcess.Add(propType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RainOpsLog.Log($"[PluginManager] 读取 XML 文档失败: {ex.Message}");
|
|
}
|
|
|
|
return descriptions;
|
|
}
|
|
}
|
|
}
|