Files
squad-rain-ops-mini/Plugins/PluginManager.cs

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;
}
}
}