From 786025f720c05ae486c8c66d3a6114633ccd0dbf Mon Sep 17 00:00:00 2001 From: m0_75251201 Date: Wed, 5 Nov 2025 21:34:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=97=E5=87=BB=E9=9F=B3=E6=95=88?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B1=BB=E5=88=AB=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuckovMods.sln.DotSettings.user | 19 + .../obj/Release/HideCharacter.AssemblyInfo.cs | 2 +- .../HideCharacter.AssemblyInfoInputs.cache | 2 +- HideCharacter/obj/rider.project.restore.info | 2 +- HitFeedback/Api/ModConfigApi.cs | 488 +++++++++++ HitFeedback/Config.cs | 209 ++++- HitFeedback/DamageFeature.cs | 69 ++ HitFeedback/ModBehaviour.cs | 250 +++++- .../obj/Release/HitFeedback.AssemblyInfo.cs | 2 +- .../HitFeedback.AssemblyInfoInputs.cache | 2 +- ...HitFeedback.csproj.AssemblyReference.cache | Bin 91219 -> 120473 bytes ...HitFeedback.csproj.CoreCompileInputs.cache | 2 +- HitFeedback/obj/Release/HitFeedback.dll | Bin 10752 -> 24064 bytes HitFeedback/obj/rider.project.restore.info | 2 +- SceneSnapshot/PrintTool.cs | 793 +++++++++--------- .../obj/Release/SceneSnapshot.AssemblyInfo.cs | 2 +- .../SceneSnapshot.AssemblyInfoInputs.cache | 2 +- SceneSnapshot/obj/Release/SceneSnapshot.dll | Bin 15872 -> 14336 bytes SceneSnapshot/obj/rider.project.restore.info | 2 +- Theme/ModBehaviour.cs | 14 +- Theme/obj/Release/Theme.AssemblyInfo.cs | 2 +- .../Release/Theme.AssemblyInfoInputs.cache | 2 +- .../Theme.csproj.CoreCompileInputs.cache | 1 + .../Release/Theme.csproj.FileListAbsolute.txt | 7 + Theme/obj/Release/Theme.dll | Bin 0 -> 4096 bytes Theme/obj/rider.project.model.nuget.info | 1 + Theme/obj/rider.project.restore.info | 2 +- UIFrame/GameOriginMainMenuUI.cs | 73 ++ UIFrame/Manager/TextureManager.cs | 7 + UIFrame/ModBehaviour.cs | 56 +- UIFrame/Patch/PatchSceneLoaderLoadMainMenu.cs | 15 + UIFrame/Singleton.cs | 38 + UIFrame/UIFrame.csproj | 3 + UIFrame/UIFrameAPI.cs | 94 ++- UIFrame/UIFrameAPIComponent.cs | 21 + UIFrame/UIFrameWorker.cs | 65 ++ UIFrame/UIManager.cs | 7 - UIFrame/Utilities/GameObjectTool.cs | 66 ++ UIFrame/Utilities/ImageLoader.cs | 67 ++ UIFrame/obj/Release/UIFrame.AssemblyInfo.cs | 2 +- .../Release/UIFrame.AssemblyInfoInputs.cache | 2 +- UIFrame/obj/Release/UIFrame.assets.cache | Bin 182 -> 750 bytes .../UIFrame.csproj.AssemblyReference.cache | Bin 120473 -> 120808 bytes .../UIFrame.csproj.CoreCompileInputs.cache | 2 +- UIFrame/obj/Release/UIFrame.dll | Bin 4096 -> 11776 bytes UIFrame/obj/UIFrame.csproj.nuget.dgspec.json | 6 + UIFrame/obj/project.assets.json | 166 +++- UIFrame/obj/project.nuget.cache | 8 +- UIFrame/obj/project.packagespec.json | 2 +- UIFrame/obj/rider.project.restore.info | 2 +- 50 files changed, 2078 insertions(+), 501 deletions(-) create mode 100644 HitFeedback/Api/ModConfigApi.cs create mode 100644 HitFeedback/DamageFeature.cs create mode 100644 Theme/obj/Release/Theme.csproj.CoreCompileInputs.cache create mode 100644 Theme/obj/Release/Theme.csproj.FileListAbsolute.txt create mode 100644 Theme/obj/Release/Theme.dll create mode 100644 Theme/obj/rider.project.model.nuget.info create mode 100644 UIFrame/GameOriginMainMenuUI.cs create mode 100644 UIFrame/Manager/TextureManager.cs create mode 100644 UIFrame/Patch/PatchSceneLoaderLoadMainMenu.cs create mode 100644 UIFrame/Singleton.cs create mode 100644 UIFrame/UIFrameAPIComponent.cs create mode 100644 UIFrame/UIFrameWorker.cs delete mode 100644 UIFrame/UIManager.cs create mode 100644 UIFrame/Utilities/GameObjectTool.cs create mode 100644 UIFrame/Utilities/ImageLoader.cs diff --git a/DuckovMods.sln.DotSettings.user b/DuckovMods.sln.DotSettings.user index f745fa7..7861586 100644 --- a/DuckovMods.sln.DotSettings.user +++ b/DuckovMods.sln.DotSettings.user @@ -6,7 +6,10 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -17,14 +20,23 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -32,10 +44,17 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/HideCharacter/obj/Release/HideCharacter.AssemblyInfo.cs b/HideCharacter/obj/Release/HideCharacter.AssemblyInfo.cs index a890952..071b266 100644 --- a/HideCharacter/obj/Release/HideCharacter.AssemblyInfo.cs +++ b/HideCharacter/obj/Release/HideCharacter.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("折纸的小箱子")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.1")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.1+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.1+5d69efbc3f80a5422cef0884e02fb27adf20b467")] [assembly: System.Reflection.AssemblyProductAttribute("HideCharacter")] [assembly: System.Reflection.AssemblyTitleAttribute("HideCharacter")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.1")] diff --git a/HideCharacter/obj/Release/HideCharacter.AssemblyInfoInputs.cache b/HideCharacter/obj/Release/HideCharacter.AssemblyInfoInputs.cache index 7243162..d3fc787 100644 --- a/HideCharacter/obj/Release/HideCharacter.AssemblyInfoInputs.cache +++ b/HideCharacter/obj/Release/HideCharacter.AssemblyInfoInputs.cache @@ -1 +1 @@ -0c41c29df034d7303af5a922ba9d31f37af5b0419be39221c78f9e624f5d890a +769c2595fb11f290049896c9b627f3b39daef1dc9f833f4947c915142e78d8d2 diff --git a/HideCharacter/obj/rider.project.restore.info b/HideCharacter/obj/rider.project.restore.info index 38a6735..ea4a121 100644 --- a/HideCharacter/obj/rider.project.restore.info +++ b/HideCharacter/obj/rider.project.restore.info @@ -1 +1 @@ -17619928534493660 \ No newline at end of file +17623343068138064 \ No newline at end of file diff --git a/HitFeedback/Api/ModConfigApi.cs b/HitFeedback/Api/ModConfigApi.cs new file mode 100644 index 0000000..8d617ea --- /dev/null +++ b/HitFeedback/Api/ModConfigApi.cs @@ -0,0 +1,488 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +//替换为你的mod命名空间, 防止多个同名ModConfigAPI冲突 +namespace HitFeedback.Api { +/// +/// ModConfig 安全接口封装类 - 提供不抛异常的静态接口 +/// ModConfig Safe API Wrapper Class - Provides non-throwing static interfaces +/// +public static class ModConfigAPI +{ + public static string ModConfigName = "ModConfig"; + + //Ensure this match the number of ModConfig.ModBehaviour.VERSION + //这里确保版本号与ModConfig.ModBehaviour.VERSION匹配 + private const int ModConfigVersion = 1; + + private static string TAG = $"ModConfig_v{ModConfigVersion}"; + + private static Type modBehaviourType; + private static Type optionsManagerType; + public static bool isInitialized = false; + private static bool versionChecked = false; + private static bool isVersionCompatible = false; + + /// + /// 检查版本兼容性 + /// Check version compatibility + /// + private static bool CheckVersionCompatibility() + { + if (versionChecked) + return isVersionCompatible; + + try + { + // 尝试获取 ModConfig 的版本号 + // Try to get ModConfig version number + var versionField = modBehaviourType.GetField("VERSION", BindingFlags.Public | BindingFlags.Static); + if (versionField != null && versionField.FieldType == typeof(int)) + { + var modConfigVersion = (int)versionField.GetValue(null); + isVersionCompatible = (modConfigVersion == ModConfigVersion); + + if (!isVersionCompatible) + { + Debug.LogError($"[{TAG}] 版本不匹配!API版本: {ModConfigVersion}, ModConfig版本: {modConfigVersion}"); + return false; + } + + Debug.Log($"[{TAG}] 版本检查通过: {ModConfigVersion}"); + versionChecked = true; + return true; + } + else + { + // 如果找不到版本字段,发出警告但继续运行(向后兼容) + // If version field not found, warn but continue (backward compatibility) + Debug.LogWarning($"[{TAG}] 未找到版本信息字段,跳过版本检查"); + isVersionCompatible = true; + versionChecked = true; + return true; + } + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 版本检查失败: {ex.Message}"); + isVersionCompatible = false; + versionChecked = true; + return false; + } + } + + /// + /// 初始化 ModConfigAPI,检查必要的函数是否存在 + /// Initialize ModConfigAPI, check if necessary functions exist + /// + public static bool Initialize() + { + try + { + if (isInitialized) + return true; + + // 获取 ModBehaviour 类型 + // Get ModBehaviour type + modBehaviourType = FindTypeInAssemblies("ModConfig.ModBehaviour"); + if (modBehaviourType == null) + { + Debug.LogWarning($"[{TAG}] ModConfig.ModBehaviour 类型未找到,ModConfig 可能未加载"); + return false; + } + + // 获取 OptionsManager_Mod 类型 + // Get OptionsManager_Mod type + optionsManagerType = FindTypeInAssemblies("ModConfig.OptionsManager_Mod"); + if (optionsManagerType == null) + { + Debug.LogWarning($"[{TAG}] ModConfig.OptionsManager_Mod 类型未找到"); + return false; + } + + // 检查版本兼容性 + // Check version compatibility + if (!CheckVersionCompatibility()) + { + Debug.LogWarning($"[{TAG}] ModConfig version mismatch!!!"); + return false; + } + + // 检查必要的静态方法是否存在 + // Check if necessary static methods exist + string[] requiredMethods = { + "AddDropdownList", + "AddInputWithSlider", + "AddBoolDropdownList", + "AddOnOptionsChangedDelegate", + "RemoveOnOptionsChangedDelegate", + }; + + foreach (var methodName in requiredMethods) + { + var method = modBehaviourType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + if (method == null) + { + Debug.LogError($"[{TAG}] 必要方法 {methodName} 未找到"); + return false; + } + } + + isInitialized = true; + Debug.Log($"[{TAG}] ModConfigAPI 初始化成功"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 初始化失败: {ex.Message}"); + return false; + } + } + + /// + /// 在所有已加载的程序集中查找类型 + /// + private static Type FindTypeInAssemblies(string typeName) + { + try + { + // 获取当前域中的所有程序集 + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var assembly in assemblies) + { + try + { + // 检查程序集名称是否包含 ModConfig + if (assembly.FullName.Contains("ModConfig")) + { + Debug.Log($"[{TAG}] 找到 ModConfig 相关程序集: {assembly.FullName}"); + } + + // 尝试在该程序集中查找类型 + var type = assembly.GetType(typeName); + if (type != null) + { + Debug.Log($"[{TAG}] 在程序集 {assembly.FullName} 中找到类型 {typeName}"); + return type; + } + } + catch (Exception ex) + { + // 忽略单个程序集的查找错误 + continue; + } + } + + // 记录所有已加载的程序集用于调试 + Debug.LogWarning($"[{TAG}] 在所有程序集中未找到类型 {typeName},已加载程序集数量: {assemblies.Length}"); + foreach (var assembly in assemblies.Where(a => a.FullName.Contains("ModConfig"))) + { + Debug.Log($"[{TAG}] ModConfig 相关程序集: {assembly.FullName}"); + } + + return null; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 程序集扫描失败: {ex.Message}"); + return null; + } + } + + /// + /// 安全地添加选项变更事件委托 + /// Safely add options changed event delegate + /// + /// 事件处理委托,参数为变更的选项键名 + /// 是否成功添加 + public static bool SafeAddOnOptionsChangedDelegate(Action action) + { + if (!Initialize()) + return false; + + if (action == null) + { + Debug.LogWarning($"[{TAG}] 不能添加空的事件委托"); + return false; + } + + try + { + var method = modBehaviourType.GetMethod("AddOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { action }); + + Debug.Log($"[{TAG}] 成功添加选项变更事件委托"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 添加选项变更事件委托失败: {ex.Message}"); + return false; + } + } + + /// + /// 安全地移除选项变更事件委托 + /// Safely remove options changed event delegate + /// + /// 要移除的事件处理委托 + /// 是否成功移除 + public static bool SafeRemoveOnOptionsChangedDelegate(Action action) + { + if (!Initialize()) + return false; + + if (action == null) + { + Debug.LogWarning($"[{TAG}] 不能移除空的事件委托"); + return false; + } + + try + { + var method = modBehaviourType.GetMethod("RemoveOnOptionsChangedDelegate", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { action }); + + Debug.Log($"[{TAG}] 成功移除选项变更事件委托"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 移除选项变更事件委托失败: {ex.Message}"); + return false; + } + } + + /// + /// 安全地添加下拉列表配置项 + /// Safely add dropdown list configuration item + /// + public static bool SafeAddDropdownList(string modName, string key, string description, System.Collections.Generic.SortedDictionary options, Type valueType, object defaultValue) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + var method = modBehaviourType.GetMethod("AddDropdownList", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { modName, key, description, options, valueType, defaultValue }); + + Debug.Log($"[{TAG}] 成功添加下拉列表: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 添加下拉列表失败 {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// 安全地添加带滑条的输入框配置项 + /// Safely add input box with slider configuration item + /// + public static bool SafeAddInputWithSlider(string modName, string key, string description, Type valueType, object defaultValue, UnityEngine.Vector2? sliderRange = null) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + var method = modBehaviourType.GetMethod("AddInputWithSlider", BindingFlags.Public | BindingFlags.Static); + + // 处理可空参数 + // Handle nullable parameters + var parameters = sliderRange.HasValue ? + new object[] { modName, key, description, valueType, defaultValue, sliderRange.Value } : + new object[] { modName, key, description, valueType, defaultValue, null }; + + method.Invoke(null, parameters); + + Debug.Log($"[{TAG}] 成功添加滑条输入框: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 添加滑条输入框失败 {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// 安全地添加布尔下拉列表配置项 + /// Safely add boolean dropdown list configuration item + /// + public static bool SafeAddBoolDropdownList(string modName, string key, string description, bool defaultValue) + { + key = $"{modName}_{key}"; + + if (!Initialize()) + return false; + + try + { + var method = modBehaviourType.GetMethod("AddBoolDropdownList", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { modName, key, description, defaultValue }); + + Debug.Log($"[{TAG}] 成功添加布尔下拉列表: {modName}.{key}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 添加布尔下拉列表失败 {modName}.{key}: {ex.Message}"); + return false; + } + } + + /// + /// 安全地加载配置值 + /// Safely load configuration value + /// + /// 值的类型 + /// 配置键 + /// 默认值 + /// 加载的值或默认值 + public static T SafeLoad(string mod_name, string key, T defaultValue = default(T)) + { + key = $"{mod_name}_{key}"; + + if (!Initialize()) + return defaultValue; + + if (string.IsNullOrEmpty(key)) + { + Debug.LogWarning($"[{TAG}] 配置键不能为空"); + return defaultValue; + } + + try + { + var loadMethod = optionsManagerType.GetMethod("Load", BindingFlags.Public | BindingFlags.Static); + if (loadMethod == null) + { + Debug.LogError($"[{TAG}] 未找到 OptionsManager_Mod.Load 方法"); + return defaultValue; + } + + // 获取泛型方法 + var genericLoadMethod = loadMethod.MakeGenericMethod(typeof(T)); + var result = genericLoadMethod.Invoke(null, new object[] { key, defaultValue }); + + Debug.Log($"[{TAG}] 成功加载配置: {key} = {result}"); + return (T)result; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 加载配置失败 {key}: {ex.Message}"); + return defaultValue; + } + } + + /// + /// 安全地保存配置值 + /// Safely save configuration value + /// + /// 值的类型 + /// 配置键 + /// 要保存的值 + /// 是否保存成功 + public static bool SafeSave(string mod_name, string key, T value) + { + key = $"{mod_name}_{key}"; + + if (!Initialize()) + return false; + + if (string.IsNullOrEmpty(key)) + { + Debug.LogWarning($"[{TAG}] 配置键不能为空"); + return false; + } + + try + { + var saveMethod = optionsManagerType.GetMethod("Save", BindingFlags.Public | BindingFlags.Static); + if (saveMethod == null) + { + Debug.LogError($"[{TAG}] 未找到 OptionsManager_Mod.Save 方法"); + return false; + } + + // 获取泛型方法 + var genericSaveMethod = saveMethod.MakeGenericMethod(typeof(T)); + genericSaveMethod.Invoke(null, new object[] { key, value }); + + Debug.Log($"[{TAG}] 成功保存配置: {key} = {value}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"[{TAG}] 保存配置失败 {key}: {ex.Message}"); + return false; + } + } + + /// + /// 检查 ModConfig 是否可用 + /// Check if ModConfig is available + /// + public static bool IsAvailable() + { + return Initialize(); + } + + /// + /// 获取 ModConfig 版本信息(如果存在) + /// Get ModConfig version information (if exists) + /// + public static string GetVersionInfo() + { + if (!Initialize()) + return "ModConfig 未加载 | ModConfig not loaded"; + + try + { + // 尝试获取版本信息(如果 ModBehaviour 有相关字段或属性) + // Try to get version information (if ModBehaviour has related fields or properties) + var versionField = modBehaviourType.GetField("VERSION", BindingFlags.Public | BindingFlags.Static); + if (versionField != null && versionField.FieldType == typeof(int)) + { + var modConfigVersion = (int)versionField.GetValue(null); + var compatibility = (modConfigVersion == ModConfigVersion) ? "兼容" : "不兼容"; + return $"ModConfig v{modConfigVersion} (API v{ModConfigVersion}, {compatibility})"; + } + + var versionProperty = modBehaviourType.GetProperty("VERSION", BindingFlags.Public | BindingFlags.Static); + if (versionProperty != null) + { + var versionValue = versionProperty.GetValue(null); + return versionValue?.ToString() ?? "未知版本 | Unknown version"; + } + + return "ModConfig 已加载(版本信息不可用) | ModConfig loaded (version info unavailable)"; + } + catch + { + return "ModConfig 已加载(版本检查失败) | ModConfig loaded (version check failed)"; + } + } + + /// + /// 检查版本兼容性 + /// Check version compatibility + /// + public static bool IsVersionCompatible() + { + if (!Initialize()) + return false; + return isVersionCompatible; + } +} +} \ No newline at end of file diff --git a/HitFeedback/Config.cs b/HitFeedback/Config.cs index 667c589..f24e4ae 100644 --- a/HitFeedback/Config.cs +++ b/HitFeedback/Config.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using UnityEngine; // 假设在Unity环境中使用Debug.LogError @@ -9,78 +11,140 @@ namespace HitFeedback public KeyCode hotKey = KeyCode.F8; public Dictionary probability = new Dictionary(); + /// + /// 当伤害具有这些特性之一时,将播放音频反馈。 + /// + public HashSet audioDamageFeatures = new HashSet(); + public void LoadConfig(string filename) { if (!File.Exists(filename)) { Debug.LogError($"Config file not found: {filename}"); - return; // 如果文件不存在,就没必要继续了 + return; } - // 清空旧的概率数据,确保每次加载都是从新开始 probability.Clear(); - + audioDamageFeatures.Clear(); // 清空旧的音频伤害特性数据 try { using (var sr = new StreamReader(filename)) { string line; - var lineNumber = 0; // 用于错误报告 + var lineNumber = 0; + string currentSection = ""; // 用于解析节 while ((line = sr.ReadLine()) != null) { lineNumber++; - line = line.Trim(); // 移除行首尾的空白字符 - + line = line.Trim(); // 忽略空行和注释行 if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#")) { continue; } + // 处理节标题,例如 [General] 或 [AudioFeatures] + if (line.StartsWith("[") && line.EndsWith("]")) + { + currentSection = line.Substring(1, line.Length - 2).Trim(); + continue; // 跳过节标题行 + } + // 查找等号 var separatorIndex = line.IndexOf('='); if (separatorIndex == -1) { - Debug.LogWarning($"Skipping malformed line in config file '{filename}' at line {lineNumber}: No '=' found. Line: '{line}'"); + Debug.LogWarning( + $"Skipping malformed line in config file '{filename}' at line {lineNumber}: No '=' found. Line: '{line}'"); continue; } var key = line.Substring(0, separatorIndex).Trim(); var valueStr = line.Substring(separatorIndex + 1).Trim(); - - // 解析 hotKey - if (key.Equals("hotKey", System.StringComparison.OrdinalIgnoreCase)) + if (currentSection.Equals("General", StringComparison.OrdinalIgnoreCase)) { - try + // 解析 hotKey + if (key.Equals("hotKey", StringComparison.OrdinalIgnoreCase)) { - hotKey = (KeyCode)System.Enum.Parse(typeof(KeyCode), valueStr, true); - } - catch (System.ArgumentException) - { - Debug.LogError($"Invalid KeyCode '{valueStr}' in config file '{filename}' at line {lineNumber}. Using default F8."); - hotKey = KeyCode.F8; // 设置为默认值或保持不变 + try + { + hotKey = (KeyCode)Enum.Parse(typeof(KeyCode), valueStr, true); + } + catch (ArgumentException) + { + Debug.LogError( + $"Invalid KeyCode '{valueStr}' in config file '{filename}' at line {lineNumber}. Using default F8."); + hotKey = KeyCode.F8; + } } } - // 解析 probability 字典项 - else + else if (currentSection.Equals("Probabilities", StringComparison.OrdinalIgnoreCase)) { - if (float.TryParse(valueStr, out var probValue)) + // 解析 probability 字典项 + if (float.TryParse(valueStr, NumberStyles.Float, CultureInfo.InvariantCulture, + out var probValue)) { probability[key] = probValue; } else { - Debug.LogWarning($"Invalid float value '{valueStr}' for key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry."); + Debug.LogWarning( + $"Invalid float value '{valueStr}' for key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry."); } } + else if (currentSection.Equals("AudioFeatures", StringComparison.OrdinalIgnoreCase)) + { + // 解析 audioDamageFeatures 集合项 + // 键是 'feature' (或者可以随意定义,只要值是我们关心的) + // 值是 ExtendedDamageFeature 的枚举名称 + if (key.Equals("feature", StringComparison.OrdinalIgnoreCase)) // 假设所有特征都用同一个键"feature" + { + foreach (var featureName in valueStr.Split(new char[] { ',', '|' }, + StringSplitOptions.RemoveEmptyEntries)) + { + try + { + // 确保 ExtendedDamageFeature 是 [Flags] 枚举 + var feature = (DamageFeature)Enum.Parse(typeof(DamageFeature), + featureName.Trim(), true); + audioDamageFeatures.Add(feature); + } + catch (ArgumentException) + { + Debug.LogWarning( + $"Invalid ExtendedDamageFeature '{featureName.Trim()}' in config file '{filename}' at line {lineNumber}. Skipping entry."); + } + } + } + else + { + try + { + var feature = + (DamageFeature)Enum.Parse(typeof(DamageFeature), key, true); + if (bool.TryParse(valueStr, out bool includeFeature) && includeFeature) + { + audioDamageFeatures.Add(feature); + } + // 如果是 false,则不添加到集合,或者可以从集合中移除(如果默认是都包含) + } + catch (ArgumentException) + { + Debug.LogWarning( + $"Invalid ExtendedDamageFeature key '{key}' in config file '{filename}' at line {lineNumber}. Skipping entry."); + } + } + } + // 如果存在其他节,可以在这里添加 else if 处理 } } } - catch (System.Exception ex) + catch (Exception ex) { Debug.LogError($"Error reading config file '{filename}': {ex.Message}"); } } + /// /// 将当前配置存储到指定INI文件。 /// @@ -89,17 +153,17 @@ namespace HitFeedback { try { - // 确保目录存在 var directory = Path.GetDirectoryName(filename); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } + using (var sw = new StreamWriter(filename)) { sw.WriteLine("; HitFeedback Configuration File"); sw.WriteLine("; Generated by HitFeedback.Config class"); - sw.WriteLine(); // 空行 + sw.WriteLine(); sw.WriteLine("[General]"); sw.WriteLine($"hotKey = {hotKey.ToString()}"); sw.WriteLine(); @@ -108,22 +172,113 @@ namespace HitFeedback sw.WriteLine("[Probabilities]"); foreach (var kvp in probability) { - // 使用 InvariantCulture 确保浮点数的格式在不同区域设置下都一致 - sw.WriteLine($"{kvp.Key} = {kvp.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)}"); + sw.WriteLine($"{kvp.Key} = {kvp.Value.ToString(CultureInfo.InvariantCulture)}"); } } else { sw.WriteLine("; No probabilities currently configured."); } + + sw.WriteLine(); + if (audioDamageFeatures.Count > 0) + { + sw.WriteLine("[AudioFeatures]"); + // 假设每个特性一行,值为 true + foreach (var feature in audioDamageFeatures) + { + sw.WriteLine($"{feature.ToString()} = True"); + } + // 或者如果你想用一个键存储所有特性(用逗号或竖线分隔) + // sw.WriteLine($"features = {string.Join(",", audioDamageFeatures.Select(f => f.ToString()))}"); + } + else + { + sw.WriteLine("; No audio damage features currently configured."); + } + sw.WriteLine(); } + Debug.Log($"Config saved to: {filename}"); } - catch (System.Exception ex) + catch (Exception ex) { Debug.LogError($"Error saving config file '{filename}': {ex.Message}"); } } + /// + /// 根据DamageInfo的特性判断是否应该播放音频反馈。 + /// 如果DamageInfo的任何一个特性在audioDamageFeatures集合中,则返回true。 + /// + /// 要检查的DamageInfo对象。 + /// 如果应该播放音频反馈,则为true;否则为false。 + public bool ShouldPlayAudioFeedback(DamageInfo damageInfo) + { + if (audioDamageFeatures.Count == 0) + { + return false; + } + // 将DamageInfo的各种布尔属性/条件转换为DamageFeature组合 + DamageFeature currentDamageFeatures = DamageFeature.Undefined; + if (damageInfo.damageType == DamageTypes.normal) + { + currentDamageFeatures |= DamageFeature.NormalDamage; + } + else if (damageInfo.damageType == DamageTypes.realDamage) + { + currentDamageFeatures |= DamageFeature.RealDamage; + } + + if (damageInfo.isFromBuffOrEffect) + { + currentDamageFeatures |= DamageFeature.BuffOrEffectDamage; + } + if (damageInfo.ignoreArmor) + { + currentDamageFeatures |= DamageFeature.ArmorIgnoringDamage; + } + if (damageInfo.crit > 0) // crit > 0 表示是暴击 + { + currentDamageFeatures |= DamageFeature.CriticalDamage; + } + if (damageInfo.armorPiercing > 0) + { + currentDamageFeatures |= DamageFeature.ArmorPiercingDamage; + } + if (damageInfo.isExplosion) + { + currentDamageFeatures |= DamageFeature.ExplosionDamage; + } + if (damageInfo.armorBreak > 0) + { + currentDamageFeatures |= DamageFeature.ArmorBreakingDamage; + } + if (damageInfo.elementFactors != null && damageInfo.elementFactors.Count > 0) + { + currentDamageFeatures |= DamageFeature.ElementalDamage; + } + if (damageInfo.buffChance > 0 || damageInfo.buff != null) + { + currentDamageFeatures |= DamageFeature.OnHitBuffApply; + } + if (damageInfo.bleedChance > 0) + { + currentDamageFeatures |= DamageFeature.OnHitBleed; + } + + foreach (var configuredFeature in audioDamageFeatures) + { + if (configuredFeature == DamageFeature.Undefined) + { + continue; + } + if ((currentDamageFeatures & configuredFeature) == configuredFeature) + { + return true; + } + } + return false; + } } } diff --git a/HitFeedback/DamageFeature.cs b/HitFeedback/DamageFeature.cs new file mode 100644 index 0000000..c090450 --- /dev/null +++ b/HitFeedback/DamageFeature.cs @@ -0,0 +1,69 @@ +using System; + +namespace HitFeedback +{ + [Flags] + public enum DamageFeature + { + /// + /// 未指定或未分类的伤害特性。 + /// + Undefined = 0, + /// + /// 普通物理伤害或其他未特别定义的伤害(对应 DamageTypes.normal)。 + /// + NormalDamage = 1, + /// + /// 真实伤害,不受护甲或其他减伤效果影响(对应 DamageTypes.realDamage)。 + /// + RealDamage = 2, + /// + /// 伤害来自增益(Buff)或持续效果。 + /// (基于 DamageInfo.isFromBuffOrEffect) + /// + BuffOrEffectDamage = 4, + /// + /// 伤害无视目标的护甲。 + /// (基于 DamageInfo.ignoreArmor) + /// + ArmorIgnoringDamage = 8, + /// + /// 伤害是暴击伤害。 + /// (基于 DamageInfo.crit > 0) + /// + CriticalDamage = 16, + /// + /// 伤害包含护甲穿透效果。 + /// (基于 DamageInfo.armorPiercing > 0) + /// + ArmorPiercingDamage = 32, + /// + /// 伤害是爆炸类型。 + /// (基于 DamageInfo.isExplosion) + /// + ExplosionDamage = 64, + /// + /// 伤害具有护甲破坏效果。 + /// (基于 DamageInfo.armorBreak > 0) + /// + ArmorBreakingDamage = 128, + /// + /// 伤害可能带有元素效果(如果有 elementFactors 存在且非空)。 + /// + ElementalDamage = 256, + /// + /// 伤害可能附带Buff效果。 + /// (基于 DamageInfo.buffChance > 0 或 buff != null) + /// + /// + /// 注意:这个可以与 BuffOrEffectDamage 区分,BuffOrEffectDamage 是伤害本身来自Buff, + /// 而นี้是伤害造成时附带施加Buff的效果。 + /// + OnHitBuffApply = 512, + /// + /// 伤害可能附带流血效果。 + /// (基于 DamageInfo.bleedChance > 0) + /// + OnHitBleed = 1024, + } +} \ No newline at end of file diff --git a/HitFeedback/ModBehaviour.cs b/HitFeedback/ModBehaviour.cs index cf74c12..576378a 100644 --- a/HitFeedback/ModBehaviour.cs +++ b/HitFeedback/ModBehaviour.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; using Duckov; +using HitFeedback.Api; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; @@ -11,24 +12,26 @@ using Random = UnityEngine.Random; namespace HitFeedback { - public class ModBehaviour:Duckov.Modding.ModBehaviour + public class ModBehaviour : Duckov.Modding.ModBehaviour { public const string AudioFolderName = "audio"; public const string ConfigFileName = "config.ini"; public string audioFolderPath; public string configFilePath; - public Dictionary audioFilePath = new Dictionary(); - + public Dictionary audioProbability = new Dictionary(); + public Health health; - - public Config config=new Config(); - + + public Config config = new Config(); + public float totalWeight; + public const string MOD_SETTING_NAME = "受击反馈"; + private void Update() { - if (Input.GetKeyDown(KeyCode.F8)) + if (Input.GetKeyDown(config.hotKey)) { PlayRandomAudioClip(); } @@ -37,16 +40,16 @@ namespace HitFeedback protected override void OnAfterSetup() { LevelManager.OnLevelInitialized += OnSceneLoaded; - audioFolderPath=Path.Combine(info.path,AudioFolderName); - configFilePath=Path.Combine(info.path, ConfigFileName); + audioFolderPath = Path.Combine(info.path, AudioFolderName); + configFilePath = Path.Combine(info.path, ConfigFileName); FindWavFiles(); config.LoadConfig(configFilePath); ApplyConfig(); - foreach (var f in audioFilePath) - { - totalWeight+=f.Value; - } + + InitializeSetting(); + UpdateTotalWeight(); } + protected override void OnBeforeDeactivate() { LevelManager.OnLevelInitialized -= OnSceneLoaded; @@ -58,13 +61,22 @@ namespace HitFeedback SaveConfig(); } + private void UpdateTotalWeight() + { + totalWeight = 0; + foreach (var f in audioProbability) + { + totalWeight += f.Value; + } + } + private void ApplyConfig() { foreach (var f in config.probability) { - if (audioFilePath.ContainsKey(f.Key)) + if (audioProbability.ContainsKey(f.Key)) { - audioFilePath[f.Key] = f.Value; + audioProbability[f.Key] = f.Value; } } } @@ -72,28 +84,36 @@ namespace HitFeedback private void SaveConfig() { config.probability.Clear(); - foreach (var f in audioFilePath) + foreach (var f in audioProbability) { config.probability.Add(f.Key, f.Value); } config.SaveConfig(configFilePath); } + private void FindWavFiles() { - audioFilePath.Clear(); + audioProbability.Clear(); if (!Directory.Exists(audioFolderPath)) { return; } + try { - string[] files = Directory.GetFiles(audioFolderPath, "*.wav", SearchOption.TopDirectoryOnly); - if (files.Length > 0) + var audioFiles = new List(); + string[] wavFiles = Directory.GetFiles(audioFolderPath, "*.wav", SearchOption.TopDirectoryOnly); + audioFiles.AddRange(wavFiles); + string[] mp3Files = Directory.GetFiles(audioFolderPath, "*.mp3", SearchOption.TopDirectoryOnly); + audioFiles.AddRange(mp3Files); + string[] oggFiles = Directory.GetFiles(audioFolderPath, "*.ogg", SearchOption.TopDirectoryOnly); + audioFiles.AddRange(oggFiles); + if (audioFiles.Count > 0) { - foreach (string filePath in files) + foreach (var filePath in audioFiles) { - audioFilePath.Add(Path.GetFileName(filePath), 1); + audioProbability.Add(Path.GetFileName(filePath), 1); } } } @@ -120,8 +140,10 @@ namespace HitFeedback { if (health) { + health.OnHurtEvent.RemoveListener(OnHurtEvent); } + health = CharacterMainControl.Main?.Health; if (health) { @@ -132,15 +154,16 @@ namespace HitFeedback private void OnHurtEvent(DamageInfo damageInfo) { - PlayRandomAudioClip(); + if (config.ShouldPlayAudioFeedback(damageInfo)) + PlayRandomAudioClip(); } - + public void PlayRandomAudioClip() { - if (audioFilePath.Count > 0) + if (audioProbability.Count > 0) { var randomIndex = Random.Range(0, totalWeight); - foreach (var f in audioFilePath) + foreach (var f in audioProbability) { randomIndex -= f.Value; if (randomIndex <= 0) @@ -155,5 +178,182 @@ namespace HitFeedback Debug.LogWarning("Mod Feedback: No audio clips loaded to play."); } } + + public void InitializeSetting() + { + if (!Api.ModConfigAPI.Initialize()) + { + return; + } + + + foreach (var audio in audioProbability) + { + ModConfigAPI.SafeAddInputWithSlider(MOD_SETTING_NAME, audio.Key, $"音频\"{audio.Key}\"播放概率", + typeof(float), audio.Value, new Vector2(0, 100)); + } + + foreach (DamageFeature value in Enum.GetValues(typeof(DamageFeature))) + { + ModConfigAPI.SafeAddBoolDropdownList(MOD_SETTING_NAME, value.ToString(), + $"受到{ToSingleFeatureChineseString(value)}时触发", + config.audioDamageFeatures.Contains(value)); + } + + var hotkeyOptions = GenerateCommonKeyCodeOptions(); + ModConfigAPI.SafeAddDropdownList( + MOD_SETTING_NAME, + "hotKey", + "主动触发的热键", + hotkeyOptions, + typeof(int), + config.hotKey + ); + + + ModConfigAPI.SafeAddOnOptionsChangedDelegate(OnConfigChange); + } + + + + private void OnConfigChange(string key) + { + key = key[(MOD_SETTING_NAME.Length + 1)..]; + if (key == "hotKey") + { + config.hotKey = (KeyCode)ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, (int)(config.hotKey)); + return; + } + if (audioProbability.ContainsKey(key)) + { + var value = ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, audioProbability[key]); + audioProbability[key] = value; + } + + if (Enum.TryParse(key, out DamageFeature damageInfo)) + { + var current=config.audioDamageFeatures.Contains(damageInfo); + if (ModConfigAPI.SafeLoad(MOD_SETTING_NAME, key, current)) + { + config.audioDamageFeatures.Add(damageInfo); + } + else if (current) + { + config.audioDamageFeatures.Remove(damageInfo); + } + } + + UpdateTotalWeight(); + + } + + /// + /// 生成包含常用 KeyCode 的 SortedDictionary。 + /// + /// 一个 SortedDictionary,键是 KeyCode 的字符串表示,值是 KeyCode 枚举本身。 + private static SortedDictionary GenerateCommonKeyCodeOptions() + { + var options = new SortedDictionary(); + // 字母键 + for (var c = 'A'; c <= 'Z'; c++) + { + var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), c.ToString()); + options.Add(c.ToString(), keyCode); + } + + // 数字键(主键盘) + for (var i = 0; i <= 9; i++) + { + var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "Alpha" + i.ToString()); + options.Add(i.ToString(), keyCode); + } + + // 数字键盘 + for (var i = 0; i <= 9; i++) + { + var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "Keypad" + i.ToString()); + options.Add($"Num_{i}", keyCode); // 加前缀区分主键盘数字 + } + + // 功能键 + for (var i = 1; i <= 12; i++) + { + var keyCode = (int)(KeyCode)Enum.Parse(typeof(KeyCode), "F" + i.ToString()); + options.Add($"F{i}", keyCode); + } + + // 常用控制键 + options.Add("空格", (int)KeyCode.Space); + options.Add("回车", (int)KeyCode.Return); + options.Add("Esc", (int)KeyCode.Escape); + options.Add("Shift (左)", (int)KeyCode.LeftShift); + options.Add("Shift (右)", (int)KeyCode.RightShift); + options.Add("Ctrl (左)", (int)KeyCode.LeftControl); + options.Add("Ctrl (右)", (int)KeyCode.RightControl); + options.Add("Alt (左)", (int)KeyCode.LeftAlt); + options.Add("Alt (右)", (int)KeyCode.RightAlt); + options.Add("Tab", (int)KeyCode.Tab); + options.Add("Backspace", (int)KeyCode.Backspace); + options.Add("Delete", (int)KeyCode.Delete); + options.Add("Home", (int)KeyCode.Home); + options.Add("End", (int)KeyCode.End); + options.Add("PageUp", (int)KeyCode.PageUp); + options.Add("PageDown", (int)KeyCode.PageDown); + options.Add("插入", (int)KeyCode.Insert); + // 方向键 + options.Add("向上", (int)KeyCode.UpArrow); + options.Add("向下", (int)KeyCode.DownArrow); + options.Add("向左", (int)KeyCode.LeftArrow); + options.Add("向右", (int)KeyCode.RightArrow); + // 鼠标按键 + options.Add("鼠标左键", (int)KeyCode.Mouse0); + options.Add("鼠标右键", (int)KeyCode.Mouse1); + options.Add("鼠标中键", (int)KeyCode.Mouse2); + // 其他一些常用键 + options.Add("~", (int)KeyCode.BackQuote); + options.Add("-", (int)KeyCode.Minus); + options.Add("=", (int)KeyCode.Equals); + options.Add("[", (int)KeyCode.LeftBracket); + options.Add("]", (int)KeyCode.RightBracket); + options.Add("\\", (int)KeyCode.Backslash); + options.Add(";", (int)KeyCode.Semicolon); + options.Add("'", (int)KeyCode.Quote); + options.Add(",", (int)KeyCode.Comma); + options.Add(".", (int)KeyCode.Period); + options.Add("/", (int)KeyCode.Slash); + return options; + } + private static string ToSingleFeatureChineseString(DamageFeature feature) + { + switch (feature) + { + case DamageFeature.Undefined: + return "未指定特性"; + case DamageFeature.NormalDamage: + return "普通伤害"; + case DamageFeature.RealDamage: + return "真实伤害"; + case DamageFeature.BuffOrEffectDamage: + return "增益/效果伤害"; + case DamageFeature.ArmorIgnoringDamage: + return "无视护甲伤害"; + case DamageFeature.CriticalDamage: + return "暴击伤害"; + case DamageFeature.ArmorPiercingDamage: + return "护甲穿透伤害"; + case DamageFeature.ExplosionDamage: + return "爆炸伤害"; + case DamageFeature.ArmorBreakingDamage: + return "护甲破坏伤害"; + case DamageFeature.ElementalDamage: + return "元素伤害"; + case DamageFeature.OnHitBuffApply: + return "命中附加增益"; + case DamageFeature.OnHitBleed: + return "命中附加流血"; + default: + return "未知特性"; // 处理未定义或将来添加的特性 + } + } } } \ No newline at end of file diff --git a/HitFeedback/obj/Release/HitFeedback.AssemblyInfo.cs b/HitFeedback/obj/Release/HitFeedback.AssemblyInfo.cs index 71318e8..e283802 100644 --- a/HitFeedback/obj/Release/HitFeedback.AssemblyInfo.cs +++ b/HitFeedback/obj/Release/HitFeedback.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("HitFeedback")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")] [assembly: System.Reflection.AssemblyProductAttribute("HitFeedback")] [assembly: System.Reflection.AssemblyTitleAttribute("HitFeedback")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/HitFeedback/obj/Release/HitFeedback.AssemblyInfoInputs.cache b/HitFeedback/obj/Release/HitFeedback.AssemblyInfoInputs.cache index 5943c9c..462cb5e 100644 --- a/HitFeedback/obj/Release/HitFeedback.AssemblyInfoInputs.cache +++ b/HitFeedback/obj/Release/HitFeedback.AssemblyInfoInputs.cache @@ -1 +1 @@ -044e9b3ac36cbf242c64f55f40a9077d40727cb97e540d335e1e29dbc7dccff1 +fa83406df4fd7108a6156ab0271b4cddce6abc73c66cbcefdb2f1419643481ef diff --git a/HitFeedback/obj/Release/HitFeedback.csproj.AssemblyReference.cache b/HitFeedback/obj/Release/HitFeedback.csproj.AssemblyReference.cache index 1b2ae5d22663d469c5dc4265220b9632c4050780..2f0b834991a8b3518de8f82add44cc02b7b0294b 100644 GIT binary patch delta 4938 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 delta 27 jcmbQami_VxR#rB~Tn2{83q4&XFA(Ky4B8sR$e02Ef!+wY diff --git a/HitFeedback/obj/Release/HitFeedback.csproj.CoreCompileInputs.cache b/HitFeedback/obj/Release/HitFeedback.csproj.CoreCompileInputs.cache index b647b53..b7ef2d6 100644 --- a/HitFeedback/obj/Release/HitFeedback.csproj.CoreCompileInputs.cache +++ b/HitFeedback/obj/Release/HitFeedback.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -17059da2b4ba38151f18bb4edeeed66662c124b29efddf4c48967bc01f547046 +86c7d211b447f2631e25f9656fa8ef4b9b63f5a671cedbbe00854ccaccdd00b3 diff --git a/HitFeedback/obj/Release/HitFeedback.dll b/HitFeedback/obj/Release/HitFeedback.dll index 35fcd842579559ef208e717ec0fbd4f58436b360..a0ab00f8d1ef5c5c306456a1d7221ec7a2a0a3cd 100644 GIT binary patch literal 24064 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 J;s3b>{tqbs?79E| literal 10752 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 diff --git a/HitFeedback/obj/rider.project.restore.info b/HitFeedback/obj/rider.project.restore.info index b4b6f59..ea4a121 100644 --- a/HitFeedback/obj/rider.project.restore.info +++ b/HitFeedback/obj/rider.project.restore.info @@ -1 +1 @@ -17620100186005303 \ No newline at end of file +17623343068138064 \ No newline at end of file diff --git a/SceneSnapshot/PrintTool.cs b/SceneSnapshot/PrintTool.cs index 68fb704..3154fc9 100644 --- a/SceneSnapshot/PrintTool.cs +++ b/SceneSnapshot/PrintTool.cs @@ -1,490 +1,465 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text; using UnityEngine; -using UnityEngine.EventSystems; using UnityEngine.SceneManagement; +using UnityEngine.EventSystems; // 用于UI射线检测 +using System; +using System.IO; +using System.Text; +using System.Reflection; +using System.Collections; // 用于 IEnumerable +using System.Collections.Generic; // 用于 HashSet, List, Dictionary +using System.Linq; // 用于 OrderBy namespace SceneSnapshot { internal class PrintTool : MonoBehaviour { - private const string FOLDER_NAME = "GameObjectSnapshots"; + private const string BASE_FOLDER_NAME = "GameObjectSnapshots"; // 主文件夹名称 private const int MAX_REFLECTION_DEPTH = 3; // 最大反射深度,防止循环引用或过深的对象图 private const int MAX_COLLECTION_ELEMENTS_TO_PRINT = 5; // 集合最多打印的元素数量 private void Update() { - if (Input.GetKeyDown(KeyCode.F2)) CaptureAndPrintSceneInfo(); + if (Input.GetKeyDown(KeyCode.F2)) + { + CaptureAndPrintSnapshot(); + } } - private void CaptureAndPrintSceneInfo() + private void CaptureAndPrintSnapshot() { - var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); - var outputFolderPath = Path.Combine(desktopPath, FOLDER_NAME); + // 获取桌面路径 + string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + // 1. 创建主文件夹 (如果不存在) + string baseOutputPath = Path.Combine(desktopPath, BASE_FOLDER_NAME); try { - if (!Directory.Exists(outputFolderPath)) + if (!Directory.Exists(baseOutputPath)) { - Directory.CreateDirectory(outputFolderPath); - Debug.Log($"创建输出文件夹: {outputFolderPath}"); + Directory.CreateDirectory(baseOutputPath); + Debug.Log($"已创建主快照文件夹: {baseOutputPath}"); } } - catch (Exception ex) + catch (Exception e) { - Debug.LogError($"创建文件夹失败: {outputFolderPath} - {ex.Message}"); + Debug.LogError($"无法创建主快照文件夹 {baseOutputPath}: {e.Message}"); return; } - var activeSceneName = SceneManager.GetActiveScene().name; - var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); - var fileName = $"{activeSceneName}_FullSnapshot_{timestamp}.txt"; // 修改文件名以示区别 - var fullFilePath = Path.Combine(outputFolderPath, fileName); - - var sb = new StringBuilder(); - - sb.AppendLine("================================================="); - sb.AppendLine($"场景信息快照 - 活跃场景: {activeSceneName}"); - sb.AppendLine($"生成时间: {DateTime.Now}"); - sb.AppendLine("================================================="); - sb.AppendLine(); - - sb.AppendLine("--- 鼠标位置对象信息 ---"); - AppendMouseHoverObjectInfo(sb); - sb.AppendLine(); - - sb.AppendLine("--- 所有加载场景的活跃游戏对象层次结构及其组件 ---"); - - // 遍历所有已加载的场景 - for (var i = 0; i < SceneManager.sceneCount; i++) - { - var currentScene = SceneManager.GetSceneAt(i); - - // 打印场景名称作为分割线 - sb.AppendLine($"\n===== 场景: {currentScene.name} ===== " + - (currentScene == SceneManager.GetActiveScene() ? "(活跃场景)" : "")); - - GameObject[] rootObjects = currentScene.GetRootGameObjects(); - if (rootObjects.Length == 0) - sb.AppendLine(" - 该场景没有根游戏对象。"); - else - foreach (var go in rootObjects) - AppendGameObjectInfo(go, 0, sb); - } - - sb.AppendLine("================================================="); - + // 2. 在主文件夹内创建带时间戳的子文件夹 + string timestampFolderName = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + string currentSnapshotOutputPath = Path.Combine(baseOutputPath, timestampFolderName); try { - File.WriteAllText(fullFilePath, sb.ToString(), Encoding.UTF8); - Debug.Log($"场景信息已成功保存到: {fullFilePath}"); + if (!Directory.Exists(currentSnapshotOutputPath)) + { + Directory.CreateDirectory(currentSnapshotOutputPath); + } } - catch (Exception ex) + catch (Exception e) { - Debug.LogError($"保存文件失败: {fullFilePath} - {ex.Message}"); - } - } - - /// - /// 递归地将游戏对象的名称、活跃状态、组件及其子对象的层次结构追加到StringBuilder。 - /// **注意:此方法只会处理活跃状态为 activeSelf 的对象。** - /// - /// 要处理的游戏对象。 - /// 当前缩进级别。 - /// StringBuilder实例。 - private void AppendGameObjectInfo(GameObject go, int indentLevel, StringBuilder sb) - { - // 只有当对象自身是激活状态时才处理和打印 - if (!go || !go.activeSelf) return; - - var indent = new string(' ', indentLevel * 4); // 每个层级使用4个空格缩进 - - // 打印游戏对象名称和活跃状态 - sb.AppendLine( - $"{indent}[{go.name}] (ActiveSelf: {go.activeSelf}, ActiveInHierarchy: {go.activeInHierarchy})"); - - // 打印所有组件 - var components = go.GetComponents(); - foreach (var comp in components) - if (comp) // 某些组件可能在运行时被销毁 - sb.AppendLine($"{indent} - Component: {comp.GetType().Name}"); - - // 递归处理子对象 - foreach (Transform child in go.transform) AppendGameObjectInfo(child.gameObject, indentLevel + 1, sb); - } - - /// - /// 尝试检测鼠标位置下方的UI元素或场景对象,并将其路径和组件信息追加到StringBuilder。 - /// - /// StringBuilder实例。 - private void AppendMouseHoverObjectInfo(StringBuilder sb) - { - // 首先尝试Raycast UI元素 - var eventDataCurrentPosition = new PointerEventData(EventSystem.current); - eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y); - var results = new List(); - if (EventSystem.current) - { - EventSystem.current.RaycastAll(eventDataCurrentPosition, results); - } - - if (results.Count > 0) - { - // UI元素优先级更高 - var uiObject = results[0].gameObject; - var uiPath = GetGameObjectPath(uiObject); - sb.AppendLine($"鼠标下方UI路径: {uiPath}"); - sb.AppendLine($" - 所在场景: {uiObject.scene.name}"); - - // 添加UI对象组件信息 - sb.AppendLine(" - UI对象组件信息:"); - AppendGameObjectComponentInfo(sb, uiObject, " "); // 增加缩进 + Debug.LogError($"无法创建快照子文件夹 {currentSnapshotOutputPath}: {e.Message}"); return; } - // 如果没有UI元素,尝试Raycast场景对象 - if (Camera.main != null) - { - var ray = Camera.main.ScreenPointToRay(Input.mousePosition); - RaycastHit hit; - if (Physics.Raycast(ray, out hit)) - { - var sceneObjectPath = GetGameObjectPath(hit.collider.gameObject); - sb.AppendLine($"鼠标下方场景对象路径: {sceneObjectPath}"); - sb.AppendLine($" - 所在场景: {hit.collider.gameObject.scene.name}"); + Debug.Log($"开始生成场景快照到: {currentSnapshotOutputPath}"); - // 添加场景对象组件信息 - sb.AppendLine(" - 场景对象组件信息:"); - AppendGameObjectComponentInfo(sb, hit.collider.gameObject, " "); // 增加缩进 - return; + // Part 1: 打印所有对象的对象树及其组件 + PrintAllGameObjectsTree(currentSnapshotOutputPath); + + // Part 2: 打印鼠标位置对象的组件值 + PrintMouseHoveredObjectDetails(currentSnapshotOutputPath); + + Debug.Log("场景快照生成完毕!"); + } + + /// + /// 打印所有场景对象的对象树(包括DontDestroyOnLoad)及其组件。 + /// + private void PrintAllGameObjectsTree(string outputPath) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("--- 所有激活场景对象树 ---"); + sb.AppendLine("--------------------------\n"); + + // 用于存储按场景分组的根对象 + var sceneRootGameObjects = new Dictionary>(); + // 用于存储 DontDestroyOnLoad 对象 + var dontDestroyOnLoadRoots = new List(); + + // 1. 遍历所有加载的场景,获取其根对象 + for (int i = 0; i < SceneManager.sceneCount; i++) + { + Scene scene = SceneManager.GetSceneAt(i); + sceneRootGameObjects[scene] = new List(scene.GetRootGameObjects()); + } + + // 2. 查找 DontDestroyOnLoad 对象 + // DontDestroyOnLoad 对象不属于任何通过 SceneManager.GetSceneAt 获取的“普通”场景 + // 它们通常在特殊的 "DontDestroyOnLoad" 场景中(在Unity编辑器中可见),但在运行时无法直接通过 SceneManager.GetSceneAt 访问。 + // 因此,我们遍历所有活跃的GameObject,找出那些是根对象但又不属于任何已知场景的。 + GameObject[] allActiveGameObjectsInHierarchy = FindObjectsOfType(); // 获取所有活跃的GameObject + + foreach (GameObject go in allActiveGameObjectsInHierarchy) + { + if (go.transform.parent == null) // 这是一个根对象 + { + bool foundInLoadedScene = false; + foreach (var kvp in sceneRootGameObjects) + { + if (kvp.Value.Contains(go)) + { + foundInLoadedScene = true; + break; + } + } + + if (!foundInLoadedScene) + { + // 如果它不是任何已加载场景的根对象,那么它可能是DontDestroyOnLoad对象 + dontDestroyOnLoadRoots.Add(go); + } + } + } + + // 3. 打印普通场景的对象树 + foreach (var kvp in sceneRootGameObjects) + { + Scene currentScene = kvp.Key; + List roots = kvp.Value; + + sb.AppendLine($"=== 场景: {currentScene.name} (路径: {currentScene.path}, 已加载: {currentScene.isLoaded}) ===\n"); + + // 按名称排序根对象以保持输出一致性 + foreach (GameObject root in roots.OrderBy(g => g.name)) + { + PrintGameObjectRecursive(root, 0, sb, new HashSet()); + } + } + + // 4. 打印 DontDestroyOnLoad 对象的对象树 + if (dontDestroyOnLoadRoots.Count > 0) + { + // 检查是否已经有一个伪的 "DontDestroyOnLoad" 场景被 Unity 在某些情境下自动添加 + // 如果是,为了避免重复,且让输出更清晰,可以先尝试移除这些。 + // 但是在 FindObjectsOfType 之后再分组,这种方式更健壮,不用管它是否有“场景” + + sb.AppendLine("\n=== DontDestroyOnLoad 对象 ===\n"); + foreach (GameObject root in dontDestroyOnLoadRoots.OrderBy(g => g.name)) + { + PrintGameObjectRecursive(root, 0, sb, new HashSet()); + } + } + + string filePath = Path.Combine(outputPath, "SceneObjectTree.txt"); + File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); // 使用UTF8编码以支持更多字符 + Debug.Log($"场景对象树已保存到: {filePath}"); + } + + /// + /// 递归打印GameObject及其子级和组件。 + /// + private void PrintGameObjectRecursive(GameObject go, int depth, StringBuilder sb, HashSet visited) + { + // 防止循环引用或重复打印 + if (visited.Contains(go)) + { + sb.AppendLine($"{GetIndent(depth)}{go.name} (循环引用检测到!)"); + return; + } + visited.Add(go); + + string indent = GetIndent(depth); + sb.AppendLine($"{indent}GameObject: {go.name} (激活状态: {go.activeSelf}, 标签: {go.tag}, 层: {LayerMask.LayerToName(go.layer)})"); + + Component[] components = go.GetComponents(); + foreach (Component comp in components) + { + if (comp == null) continue; // 避免NRE,尽管不常见 + sb.AppendLine($"{indent} 组件: {comp.GetType().Name}"); + } + + for (int i = 0; i < go.transform.childCount; i++) + { + PrintGameObjectRecursive(go.transform.GetChild(i).gameObject, depth + 1, sb, visited); + } + + // 重要:在递归完成后通常不需要从visited中移除GameObject, + // 因为一个GameObject在对象树中只会以唯一的路径出现一次 + // (除非它以某种非常规方式被引用,但这不属于标准GameObject层级)。 + // 对于值类型或简单引用,可以在PrintObjectProperties中在处理完后移除。 + // 对于GameObject层级,一旦访问完其所有子节点,它在该“分支”的任务就完成了。 + // 如果不同根节点下可能会有相同的GameObject引用(例如通过Inspector引用), + // 那visited集合的作用是防止在*当前递归路径*中再次遇到同一个GameObject,从而避免死循环。 + // 对于整个场景树的打印,visited集合可以保持不变,因为我们不期望同一个GameObject作为不同根节点的子物体链中的一部分。 + // visited.Remove(go); // 对于GameObject树结构,这通常是不必要的,因为每个GameObject在树中只有一个父级。 + } + + /// + /// 打印鼠标位置对象的组件值。 + /// + private void PrintMouseHoveredObjectDetails(string outputPath) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("--- 鼠标悬停对象详细信息 ---"); + sb.AppendLine("----------------------------\n"); + + GameObject hoveredObject = GetHoveredObject(); + + if (hoveredObject != null) + { + sb.AppendLine($"悬停的GameObject: {hoveredObject.name} (激活状态: {hoveredObject.activeSelf}, 标签: {hoveredObject.tag}, 层: {LayerMask.LayerToName(hoveredObject.layer)})"); + sb.AppendLine($"组件及其值:\n"); + + Component[] components = hoveredObject.GetComponents(); + foreach (Component comp in components) + { + if (comp == null) continue; + sb.AppendLine($" === 组件: {comp.GetType().Name} ==="); + // 使用反射打印组件的字段和属性值 + PrintObjectProperties(comp, 0, sb, new HashSet(), " "); // 初始缩进4个空格 + sb.AppendLine(); } } else { - sb.AppendLine("警告: 场景中没有主摄像机(Camera.main)或未被标记为 'MainCamera'。无法检测鼠标下的场景对象。"); + sb.AppendLine("当前鼠标下方没有对象。"); } - sb.AppendLine("鼠标位置处没有检测到UI元素或场景对象。"); + string filePath = Path.Combine(outputPath, "MouseHoverObjectDetails.txt"); + File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); // 使用UTF8编码以支持更多字符 + Debug.Log($"鼠标悬停对象详情已保存到: {filePath}"); } /// - /// 为指定的GameObject追加其所有组件的信息。 + /// 获取鼠标下方的GameObject(优先UI,其次3D场景对象)。 /// - /// StringBuilder实例。 - /// 目标GameObject。 - /// 当前的缩进字符串。 - private void AppendGameObjectComponentInfo(StringBuilder sb, GameObject obj, string indent) + private GameObject GetHoveredObject() { - Component[] components = obj.GetComponents(); - if (components.Length == 0) + // 优先检测UI对象 + if (EventSystem.current != null) { - sb.AppendLine($"{indent} - 无组件"); + PointerEventData eventData = new PointerEventData(EventSystem.current); + eventData.position = Input.mousePosition; + List uiRaycastResults = new List(); + EventSystem.current.RaycastAll(eventData, uiRaycastResults); + + // 过滤掉非 interactable 的UI元素或者不包含 CanvasRenderer 的元素,可能更关注可见和可交互的UI + foreach (var result in uiRaycastResults) + { + if (result.gameObject != null && result.gameObject.GetComponent() != null) + { + return result.gameObject; // 返回第一个有效的UI元素 + } + } + } + else + { + Debug.LogWarning("场景中没有EventSystem,无法检测UI对象。请确保场景中存在一个EventSystem GameObject。"); + } + + // 如果没有UI对象,则检测3D场景对象 + if (Camera.main != null) + { + Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); + // 仅检测默认层,或可配置的层 + if (Physics.Raycast(ray, out RaycastHit hit)) + { + return hit.collider.gameObject; + } + } + else + { + Debug.LogWarning("场景中没有主摄像机(Tagged 'MainCamera'),无法进行3D射线检测。请确保主摄像机正确标记。"); + } + + return null; + } + + /// + /// 使用反射递归打印对象的字段和属性值。 + /// + /// 要打印的对象。 + /// 当前反射深度。 + /// StringBuilder用于构建输出。 + /// 用于检测循环引用的已访问对象集合。 + /// 当前缩进字符串。 + private void PrintObjectProperties(object obj, int currentDepth, StringBuilder sb, HashSet visitedObjects, string indent) + { + if (obj == null) + { + sb.AppendLine("null"); return; } - foreach (var component in components) + Type type = obj.GetType(); + + // 1. 处理基本类型、字符串、枚举 + // 注意:string是引用类型,但行为上通常被视为值类型,其ToString是其自身 + if (type.IsPrimitive || obj is string || type.IsEnum || obj is decimal || obj is DateTime) { - if (component == null) - { - sb.AppendLine($"{indent} - (空组件)"); - continue; - } - - // 尝试获取Behaviour的enabled状态 - string enabledStatus = (component is Behaviour behaviour) ? behaviour.enabled.ToString() : "N/A"; - sb.AppendLine($"{indent} - 组件: {component.GetType().Name} (Enabled: {enabledStatus})"); - AppendComponentPropertiesAndFields(sb, component, indent + " "); // 更深一层缩进,显示组件的属性/字段 - } - } - - /// - /// 利用反射为指定的组件追加其公共属性和字段的信息。 - /// - /// StringBuilder实例。 - /// 目标组件。 - /// 当前的缩进字符串。 - private void AppendComponentPropertiesAndFields(StringBuilder sb, Component component, string indent) - { - var bindingFlags = BindingFlags.Public | BindingFlags.Instance; - var componentType = component.GetType(); - // 收集所有公共实例属性和字段 - var members = new List(); - members.AddRange(componentType.GetProperties(bindingFlags)); - members.AddRange(componentType.GetFields(bindingFlags)); - bool hasPrintedAnything = false; - foreach (var member in members) - { - // 排除一些常见或特定组件上可能导致冗余或问题的属性/字段 - if (IsMemberToExclude(member, component)) continue; - object value = null; - string memberName = member.Name; - try - { - if (member is PropertyInfo pi) - { - if (pi.CanRead) // 确保属性可读 - { - value = pi.GetValue(component); - } - else - { - // 忽略不可读的属性 - continue; - } - } - else if (member is FieldInfo fi) - { - value = fi.GetValue(component); - } - } - catch (Exception ex) - { - // 捕获获取值时可能发生的异常 - sb.AppendLine($"{indent}- {memberName}: <获取失败: {ex.GetType().Name}>"); - hasPrintedAnything = true; - continue; - } - - // 格式化值进行显示,初始深度为0 - string formattedValue = FormatValueForDisplay(value, 0); - sb.AppendLine($"{indent}- {memberName}: {formattedValue}"); - hasPrintedAnything = true; + sb.AppendLine($"({type.Name}) {obj}"); // 显示类型名,更清晰 + return; } - if (!hasPrintedAnything) + // 2. 处理常见的Unity值类型(如Vector3, Quaternion, Color, Rect等) + // 这些类型通常有很好的ToString()方法,且不应过度深入反射其内部字段 + if (obj is Vector2 || obj is Vector3 || obj is Vector4 || obj is Quaternion || + obj is Color || obj is Color32 || obj is Rect || obj is Bounds || + obj is AnimationCurve || obj is LayerMask || obj is Matrix4x4) { - sb.AppendLine($"{indent}- (无公共属性或字段)"); + sb.AppendLine($"({type.Name}) {obj}"); // 显示类型名,更清晰 + return; } - } - - /// - /// 判断一个成员是否应该被排除在打印列表之外。 - /// 用于过滤掉冗余或可能导致深度递归的成员。 - /// - /// 要检查的MemberInfo。 - /// 该成员所属的组件实例。 - /// 如果应排除则返回true。 - private bool IsMemberToExclude(MemberInfo member, Component component) - { - // 排除从UnityEngine.Object继承的常见属性,这些通常是GameObject级别的元数据, - // 或者可能导致不必要的递归。 - // 特别是gameObject和transform,它们的类型就是GameObject和Transform,递归它们没有意义, - // 且其值就是组件所依附的GameObject和Transform,已经通过GetGameObjectPath显示了。 - switch (member.Name) + + // 3. 处理UnityEngine.Object类型(但不是Component或GameObject本身) + // 例如 Material, Texture, ScriptableObject等,通常只打印其名称或ToString()就足够 + if (typeof(UnityEngine.Object).IsAssignableFrom(type) && !(obj is Component) && !(obj is GameObject)) { - case "hideFlags": // Unity内部的标志,通常不需要显示 - case "name": // GameObject的名称,已在路径中显示 - case "tag": // GameObject的标签,可从GameObject直接获取 - case "layer": // GameObject的层,可从GameObject直接获取 - case "useGUILayout": // Unity内部GUI相关的,通常不作为组件值关心 - case "runInEditMode": // Unity编辑器模式相关,通常不作为组件值关心 - // 对于Component基类上的gameObject和transform属性,它们直接指向宿主对象和其Transform。 - // 打印它们本身就是重复的且可能误导(不是组件内部的独特“值”)。 - case "gameObject": - case "transform": - case "isStatic": // GameObject的isStatic状态 - return true; + // 对于这些Unity对象,ToString()通常会返回对象名和类型,足够了 + sb.AppendLine($"({type.Name}) {obj.ToString()}"); + return; } - - // 进一步过滤掉一些Unity内部或编辑器相关的属性,这些属性通常在运行时不提供有用的组件值信息。 - if (member.DeclaringType == typeof(Behaviour) || member.DeclaringType == typeof(MonoBehaviour)) - { - switch (member.Name) - { - case "isActiveAndEnabled": // 行为体的激活状态,通常与enabled一起考虑 - return true; - } - } - - return false; - } - - /// - /// 格式化对象值以进行显示,特别是针对Unity的常见类型,以避免循环打印和提供简洁输出。 - /// - /// 要格式化的值。 - /// 当前的递归深度。 - /// 格式化后的字符串。 - private string FormatValueForDisplay(object value, int currentDepth = 0) - { - if (value == null) return "null"; - Type type = value.GetType(); - // 深度限制检查:如果超过最大深度,则返回提示信息 + + // 4. 检查最大反射深度 if (currentDepth >= MAX_REFLECTION_DEPTH) { - // 对于集合,提供更具体一些的信息 - if (value is Array array1) return $"Array (Count: {array1.Length}, Max Depth Reached)"; - if (value is IList list1) return $"List (Count: {list1.Count}, Max Depth Reached)"; - if (value is IDictionary dictionary) return $"Dictionary (Count: {dictionary.Count}, Max Depth Reached)"; - return $"[{type.Name} (Max Depth Reached)]"; + sb.AppendLine($"{indent}... (达到最大反射深度)"); + return; } - // 1. 基本类型、字符串、枚举:直接ToString() - if (type.IsPrimitive || type == typeof(string) || type.IsEnum) + // 5. 检查循环引用(仅对引用类型有效,且不是字符串那种特殊引用类型) + if (!type.IsValueType && !type.IsPrimitive && !(obj is string)) { - return value.ToString(); + if (visitedObjects.Contains(obj)) + { + sb.AppendLine($"{indent}... (检测到循环引用: {type.Name})"); + return; + } + visitedObjects.Add(obj); // 标记为已访问 } - // 2. 常见的Unity结构体:特殊格式化,避免深度递归并提供简洁输出 - if (value is Vector2 vec2) return $"({vec2.x:F2}, {vec2.y:F2})"; - if (value is Vector3 vec3) return $"({vec3.x:F2}, {vec3.y:F2}, {vec3.z:F2})"; - if (value is Vector4 vec4) return $"({vec4.x:F2}, {vec4.y:F2}, {vec4.z:F2}, {vec4.w:F2})"; - - // Quaternion默认ToString()会显示x,y,z,w,但有时EulerAngles更直观。 - if (value is Quaternion q) - return - $"Q({q.x:F2}, {q.y:F2}, {q.z:F2}, {q.w:F2}) (Euler: {q.eulerAngles.x:F2}, {q.eulerAngles.y:F2}, {q.eulerAngles.z:F2})"; - - if (value is Color color) return $"RGBA({color.r:F2}, {color.g:F2}, {color.b:F2}, {color.a:F2})"; - if (value is Rect rect) - return $"Rect(Pos:({rect.xMin:F2},{rect.yMin:F2}) Size:({rect.width:F2},{rect.height:F2}))"; - if (value is Bounds bounds) - return - $"Bounds(Center:({bounds.center.x:F2},{bounds.center.y:F2},{bounds.center.z:F2}) Extents:({bounds.extents.x:F2},{bounds.extents.y:F2},{bounds.extents.z:F2}))"; - if (value is LayerMask layerMask) + // 6. 处理集合类型(数组、List、Dictionary等,但不包括字符串) + if (obj is IEnumerable enumerable) { - // LayerMask的值可能代表多个层,或一个单一层。LayerToName只能转换单一层。 - // 对于多个层,返回其原始值更有意义。 - return $"LayerMask(Value: {layerMask.value})"; + // 对于字典,直接打印IEnumerable会导致键值对混乱,需要特殊处理 + if (obj is IDictionary dictionary) + { + sb.AppendLine($"({type.Name}) Count={dictionary.Count} {{"); + int count = 0; + foreach (DictionaryEntry entry in dictionary) + { + if (count >= MAX_COLLECTION_ELEMENTS_TO_PRINT) + { + sb.AppendLine($"{indent + " "}...(已截断,显示了{MAX_COLLECTION_ELEMENTS_TO_PRINT}对键值)"); + break; + } + sb.Append($"{indent + " "}[Key]: "); + PrintObjectProperties(entry.Key, currentDepth + 1, sb, visitedObjects, indent + " "); // 额外的缩进 + sb.Append($"{indent + " "}[Value]: "); + PrintObjectProperties(entry.Value, currentDepth + 1, sb, visitedObjects, indent + " "); // 额外的缩进 + count++; + } + sb.AppendLine($"{indent}}}"); + } + else // 普通的IEnumerable(List, Array等) + { + int elementCount = 0; + if (obj is ICollection collection) + elementCount = collection.Count; + else if (obj is Array array) + elementCount = array.Length; + else // 对于无法直接获取Count的IEnumerable,需要遍历统计 + { + var list = new List(); + foreach (var item in enumerable) list.Add(item); + elementCount = list.Count; + enumerable = list; // 重新赋值为可以重复遍历的list + } + + sb.AppendLine($"({type.Name}) Count={elementCount} ["); + int count = 0; + foreach (var item in enumerable) + { + if (count >= MAX_COLLECTION_ELEMENTS_TO_PRINT) + { + sb.AppendLine($"{indent + " "}...(已截断,显示了{MAX_COLLECTION_ELEMENTS_TO_PRINT}个元素)"); + break; + } + sb.Append($"{indent + " "}- "); + PrintObjectProperties(item, currentDepth + 1, sb, visitedObjects, indent + " "); + count++; + } + sb.AppendLine($"{indent}]"); + } + + // 集合本身通常不直接导致循环引用其自身,其内部元素才可能。 + // 故在处理集合后可以从visitedObjects中移除集合对象,防止它阻止其他路径对它的访问。 + if (!type.IsValueType && !type.IsPrimitive && !(obj is string)) visitedObjects.Remove(obj); + return; } - // 3. GameObject/Component引用:只打印名称或类型,避免无限递归 - if (value is GameObject go) return $"GameObject:'{go.name}'"; - if (value is Component comp) - return $"Component:'{comp.GetType().Name}' on GameObject:'{comp.gameObject.name}'"; + // 7. 处理一般对象(类或结构体)的字段和属性 + sb.AppendLine($"({type.Name}) {{"); - // 4. 集合类型:现在会打印内容,调用专门的辅助方法 - if (value is Array array) + BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + // 字段 + FieldInfo[] fields = type.GetFields(flags); + foreach (FieldInfo field in fields) { - return FormatCollectionForDisplay(array, currentDepth + 1); + if (field.IsStatic) continue; // 跳过静态字段 + if (field.IsDefined(typeof(ObsoleteAttribute), true)) continue; // 跳过过时字段 + + string propertyIndent = indent + " "; + sb.Append($"{propertyIndent}{field.Name}: "); + try + { + object fieldValue = field.GetValue(obj); + PrintObjectProperties(fieldValue, currentDepth + 1, sb, visitedObjects, propertyIndent); + } + catch (Exception e) + { + sb.AppendLine($"<无法获取值: {e.Message}>"); + } } - if (value is IList list) + // 属性 + PropertyInfo[] properties = type.GetProperties(flags); + foreach (PropertyInfo prop in properties) { - return FormatCollectionForDisplay(list, currentDepth + 1); + // 跳过特殊名称属性(如Unity内部的hideFlags)、不可读属性、带索引器属性、过时属性 + if (prop.IsSpecialName || !prop.CanRead || prop.GetIndexParameters().Length > 0 || + prop.IsDefined(typeof(ObsoleteAttribute), true)) continue; + + string propertyIndent = indent + " "; + sb.Append($"{propertyIndent}{prop.Name}: "); + try + { + object propValue = prop.GetValue(obj); + PrintObjectProperties(propValue, currentDepth + 1, sb, visitedObjects, propertyIndent); + } + catch (Exception e) + { + sb.AppendLine($"<无法获取值: {e.Message}>"); + } } - if (value is IDictionary dict) - { - return FormatCollectionForDisplay(dict, currentDepth + 1); - } + sb.AppendLine($"{indent}}}"); - // 对于其他不可转换为Array/IList/IDictionary的IEnumerable,但又不是字符串的类型 - if (value is IEnumerable enumerable && !(value is string)) - { - return FormatCollectionForDisplay(enumerable, currentDepth + 1); - } - - // 5. 其他复杂引用类型:只打印其类型名称,默认不深入其内部 (除非深度允许,但在深度限制前这里只会显示类型名) - return type.Name; // 例如: Material, Texture2D等,只显示类型名 + // 在对象处理完毕后,从已访问集合中移除(如果它是引用类型), + // 这允许在对象图的不同路径中再次遇到它(如果需要),但防止当前路径的循环。 + if (!type.IsValueType && !type.IsPrimitive && !(obj is string)) visitedObjects.Remove(obj); } /// - /// 格式化集合对象以进行显示,支持深度限制和元素数量限制。 + /// 获取指定深度的缩进字符串。 /// - /// 要格式化的集合。 - /// 当前的递归深度。 - /// 格式化后的集合字符串。 - private string FormatCollectionForDisplay(IEnumerable collection, int currentDepth) + private string GetIndent(int depth) { - if (collection == null) return "null collection"; - Type collectionType = collection.GetType(); - StringBuilder sb = new StringBuilder(); - // 尝试获取集合的类型名,去除可能的反引号(用于泛型类型) - string collectionTypeName = collectionType.IsGenericType - ? collectionType.Name.Substring(0, collectionType.Name.IndexOf('`')) - : collectionType.Name.Replace("[]", ""); - sb.Append(collectionTypeName); - - sb.Append(" ["); - int i = 0; - foreach (var item in collection) - { - if (i >= MAX_COLLECTION_ELEMENTS_TO_PRINT) - { - sb.Append(", ..."); - break; - } - - if (i > 0) sb.Append(", "); - if (item is DictionaryEntry entry) // 针对非泛型IDictionary - { - sb.Append( - $"{{{FormatValueForDisplay(entry.Key, currentDepth + 1)}: {FormatValueForDisplay(entry.Value, currentDepth + 1)}}}"); - } - else - { - Type itemType = item?.GetType(); - // 针对泛型IDictionary,其元素是KeyValuePair - if (itemType != null && itemType.IsGenericType && - itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - // 使用反射获取Key和Value属性 - PropertyInfo keyProperty = itemType.GetProperty("Key"); - PropertyInfo valueProperty = itemType.GetProperty("Value"); - if (keyProperty != null && valueProperty != null) - { - object key = keyProperty.GetValue(item); - object value = valueProperty.GetValue(item); - sb.Append( - $"{{{FormatValueForDisplay(key, currentDepth + 1)}: {FormatValueForDisplay(value, currentDepth + 1)}}}"); - } - else - { - // Fallback in case Key/Value properties aren't found - sb.Append(FormatValueForDisplay(item, currentDepth + 1)); - } - } - else - { - // 格式化普通集合元素 - sb.Append(FormatValueForDisplay(item, currentDepth + 1)); - } - } - - i++; - } - - // 尝试获取集合的实际数量 - string countInfo = "N/A"; - if (collection is ICollection c) - { - countInfo = c.Count.ToString(); - } - else if (collection is Array a) - { - countInfo = a.Length.ToString(); - } - - sb.Append($"] (Count: {countInfo})"); - return sb.ToString(); + return new string(' ', depth * 2); } - - /// - /// 获取给定游戏对象的完整层次路径。 - /// - /// 要获取路径的游戏对象。 - /// 游戏对象的完整路径,例如 "Parent/Child/Object"。 - private string GetGameObjectPath(GameObject go) - { - if (go == null) return "N/A"; - - var path = go.name; - var currentTransform = go.transform; - - while (currentTransform.parent != null) - { - currentTransform = currentTransform.parent; - path = currentTransform.name + "/" + path; - } - - return path; - } - } -} \ No newline at end of file +} + diff --git a/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfo.cs b/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfo.cs index 2f2c13f..0c828c0 100644 --- a/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfo.cs +++ b/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("折纸的小箱子")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")] [assembly: System.Reflection.AssemblyProductAttribute("SceneSnapshot")] [assembly: System.Reflection.AssemblyTitleAttribute("SceneSnapshot")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0")] diff --git a/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfoInputs.cache b/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfoInputs.cache index 02ed8f4..527661e 100644 --- a/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfoInputs.cache +++ b/SceneSnapshot/obj/Release/SceneSnapshot.AssemblyInfoInputs.cache @@ -1 +1 @@ -532b9b8318b6010ae03f608127d55a2c59d0b50d9243b633f698b5d460668837 +4cb78221af3251625ca99f740da024b47f366123d3acdfd330074e35f359044e diff --git a/SceneSnapshot/obj/Release/SceneSnapshot.dll b/SceneSnapshot/obj/Release/SceneSnapshot.dll index fa5efa25804b4a086cd925c47440e8d4e85bc3ba..956694fb145318ea31cf9bc7b62b28844448bac6 100644 GIT binary patch literal 14336 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

qCfC;;OiIRxXDMdw}goVy{HhW);d@0? zFJHM-QI7=@@77?@&lGW8ZLg`tTmoYwv7$d}ia=#wkhY8)G8jG*yVDDRwj#>7e zaeA3CH;?O5rBK=SEIpHw4~RK(_z?JkbyjwV&D=_oQBMs zuaVMf191$y9s)whM6{OjJkVl(Q@ejvbnaYq?z}Pg@#x%xf#}>PjJb!SbC06+S$w|0 zDpp759Y{5tHA#89XFj7!wC;m7wUq{I>^Q`HPFMT)6Y)$L@ROi;utjg^%LvctL)=xUo#U z^rDa;Z4-Vv&`h*hP?KynY#H0S-5MWFZXVy(Y;I|7X-Qh+&D*xMTFouvqb)a>W8*E& zqpe$SK%xsOU;F?-nqVV*4C@5ld7R(T4=d$3*xR@F;=v|0jklG{@p_9HPBzgZsT^mh?lkszo&O;BX|?onNIieyNju860iR(`L2X} zuhc@u)zg%U`W>|STW%u!yl!ssiFudo+*<~E3 z0KV1}kK%K<32GL5wuzHL3cVTGPxP4Y9taiCiBI}`?wPoTq)ExsjHe6tuN6;Y@BvOO zevIWa#}uB&c#c_E&0Quw@z|@qRP<2;ZNUs%X*+ZqM^uxv8TW1QpoyMVYQZ1tI}XY={I#Op z3>x#c&<)@kL%RjEQS@yEg|BTW$b#At7k+Y>LSGsliz8DPN2{m@$Lx1}?R0-f_5Q+w P`yH$E|EK?d>w*6Z0@%!% literal 15872 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 W?Emp+7<(Vv&dc>*KmXz)@IL`RiGUaY diff --git a/SceneSnapshot/obj/rider.project.restore.info b/SceneSnapshot/obj/rider.project.restore.info index 31df8f7..ea4a121 100644 --- a/SceneSnapshot/obj/rider.project.restore.info +++ b/SceneSnapshot/obj/rider.project.restore.info @@ -1 +1 @@ -17619862901687226 \ No newline at end of file +17623343068138064 \ No newline at end of file diff --git a/Theme/ModBehaviour.cs b/Theme/ModBehaviour.cs index 9888b64..eb45b27 100644 --- a/Theme/ModBehaviour.cs +++ b/Theme/ModBehaviour.cs @@ -1,16 +1,18 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEngine; namespace Theme { - public class ModBehaviour:Duckov.Modding.ModBehaviour + public class ModBehaviour : Duckov.Modding.ModBehaviour { + private StringBuilder _outputStringBuilder; protected override void OnAfterSetup() { - base.OnAfterSetup(); - } - protected override void OnBeforeDeactivate() - { - base.OnBeforeDeactivate(); + } + } } \ No newline at end of file diff --git a/Theme/obj/Release/Theme.AssemblyInfo.cs b/Theme/obj/Release/Theme.AssemblyInfo.cs index 4920b0d..7d9da60 100644 --- a/Theme/obj/Release/Theme.AssemblyInfo.cs +++ b/Theme/obj/Release/Theme.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Theme")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")] [assembly: System.Reflection.AssemblyProductAttribute("Theme")] [assembly: System.Reflection.AssemblyTitleAttribute("Theme")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Theme/obj/Release/Theme.AssemblyInfoInputs.cache b/Theme/obj/Release/Theme.AssemblyInfoInputs.cache index ba29f83..146bfd7 100644 --- a/Theme/obj/Release/Theme.AssemblyInfoInputs.cache +++ b/Theme/obj/Release/Theme.AssemblyInfoInputs.cache @@ -1 +1 @@ -9389535ab653bc716c7ef81ccb8542bee3753b2c1f4be9c952401b4ec0aa9c5d +c1519aee9b121f1df8699539b785671792d60ca0acb5c9d1d51d6039c2fa5de5 diff --git a/Theme/obj/Release/Theme.csproj.CoreCompileInputs.cache b/Theme/obj/Release/Theme.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..9a839d7 --- /dev/null +++ b/Theme/obj/Release/Theme.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +ff440d05ee1ce379e101695273a889a69b97880c4ff8ae9ffb67b8c73e02feef diff --git a/Theme/obj/Release/Theme.csproj.FileListAbsolute.txt b/Theme/obj/Release/Theme.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..ef228a9 --- /dev/null +++ b/Theme/obj/Release/Theme.csproj.FileListAbsolute.txt @@ -0,0 +1,7 @@ +D:\steam\steamapps\common\Escape from Duckov\Duckov_Data\Mods\Theme\Theme.dll +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.csproj.AssemblyReference.cache +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.GeneratedMSBuildEditorConfig.editorconfig +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.AssemblyInfoInputs.cache +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.AssemblyInfo.cs +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.csproj.CoreCompileInputs.cache +D:\vs_project\DuckovMods\Theme\obj\Release\Theme.dll diff --git a/Theme/obj/Release/Theme.dll b/Theme/obj/Release/Theme.dll new file mode 100644 index 0000000000000000000000000000000000000000..a350c9e88d29cc4e835361dc7fe719e9ff1c602f GIT binary patch literal 4096 zcmeHKPi!1l8UMXqduzLm6Q?0;2*qo!lNe$r&2F3kQj^))x9d@6 zXI3+_PAo)m`O^dc)I*OEmsWxUhYC1Uph_Hy#34Cwptnj~k&t?T144-K``*m1*LJA{ z5{F8B>-W9y-}~P8eecbC&nsVj6?p(7Y!4m)Z?PAcH~7QR7S)rV_|+tSIe!0{x75Y^ z&(znw*y=`MJ90Xf>jXiVSPgAOy}!+0wVzi%=|{z9K&ZzuXr!v3{~h%7eAHH({+0SmZkM{(qm zGqKH+kVGfghG3r2+mZG|8cM9V$tJ!g*@j>qSQugr*_FByB(@wz@dJx7vF1vI{{=jq zJGzen9(ytEbC3P-Ae}vXIDhvWbUAYDbD2D!vV~(Dz%VC{aju2hmljkuv*fW+wu|;` zadzg61TqSrxX8SwUjpv3|EbWmB=UlGEEd(#v^>*SYq&sKmVNsC)#WPt>*UWeg6Rc6 zY=|*|!_~Dzz_>7fSF<>r=FNyrnW^WjX&J?r)I?;!J(bp{09&2T6|iOS29D(R;~Gs0 z_yvBh3TUhMi1!KquTdrg92X#05M-Jb_OWOFV+l7+f)U+29up{))j4aRHmeAA@J; zmkqY@Fiyk9qnIat5;usau}*v(ZxYYq*TkR4?}!)i2jVXqd5yS&gBVwf_!=v{$d6;c zat-Zfw2MP(WUF62WWdEXwd(Uv?*6QdV(`tMT?v~#Uq6pPCvoBgO($xizOFkOb?tO& zVbigzJ@;0)VK0V}#!{!Dn@!!UB;25lUZSxWcDkOgqw_k@k&_Io21Zw7r>zGZs=Co@ zx1EMRBwOwI{!U3Hj&-NuZ`QqJ*s$(IZJnIsQLJx=(XDN-efx8aW=%)27Y2Kk%RwuQ zI!;2RkY;G&A^n%3eXd_R& zhUa_9kal5{_06i!b!zfNKy5QlbjQvH+F1&sF{@@beLu4qnCh0VUC9KOf=VmVQB5bk zE^dUqq}xlF*POZq#@6Y-TV5Z5AJ{Fy%+L@r$+OIgG-_t72Q*#W6E4g zz|(TX)N_ZgMNW4$43_S=x+_(xuSenSSkW#$70)X?VXrRLYuV>&W95+CLl8-aI3QM9u8(6wymUTSD!H{vDfYZt!@h`u>XJ1?a>s z&kAk!qG%sHOCP(Z+3WCsCXt&1cpTW;IfbbkSoJ+}v6HvM^?BgW={67%=`!&eYCOr; zu!Jk5mif|JCBIC3E`4?3?UDEYnV!tDDgtT$#grM`!x?M3syXsCYMTy?|weQ zrCEQ_9BG{{b8N_Yd(1l-ifU(_zJ<>+hn=>&GvChpI8M^zDZVu{SG_^cv+U+u+}l{JJk8Ch{gT8a|O2JkahTo WgO&cz@M>Cuk9acw5C1={z`p(); + title = GameObjectTool.FindChildByName(mainMenuContainer.transform, "MainTitle")?.GetComponent(); + linkMainMenu = true; + + + } + + public bool SetFont(TMP_FontAsset font) + { + if(allTexts == null || allTexts.Length == 0) + return false; + foreach (var text in allTexts) + { + text.font = font; + } + return true; + } + + public bool SetTitle(Sprite texture) + { + titleSprite=texture; + if(title==null) + return false; + title.sprite = texture; + return true; + } + } +} \ No newline at end of file diff --git a/UIFrame/Manager/TextureManager.cs b/UIFrame/Manager/TextureManager.cs new file mode 100644 index 0000000..96f7a02 --- /dev/null +++ b/UIFrame/Manager/TextureManager.cs @@ -0,0 +1,7 @@ +namespace UIFrame.Manager +{ + public class TextureManager + { + + } +} \ No newline at end of file diff --git a/UIFrame/ModBehaviour.cs b/UIFrame/ModBehaviour.cs index 298d1f3..b32d992 100644 --- a/UIFrame/ModBehaviour.cs +++ b/UIFrame/ModBehaviour.cs @@ -1,19 +1,69 @@ +using System; +using Duckov.Modding; using Duckov.Options.UI; +using Duckov.UI; +using Duckov.UI.Animations; +using Duckov.Utilities; +using HarmonyLib; using UnityEngine; namespace UIFrame { public class ModBehaviour:Duckov.Modding.ModBehaviour { + private const string MOD_ID="UIFrame"; + + private GameObject? workerObject; + + private Harmony? harmony; + protected override void OnAfterSetup() { - Debug.Log("OnAfterSetup"); - + CreateAPIObject(); + if (harmony == null) + { + harmony=new HarmonyLib.Harmony(MOD_ID); + } + harmony.PatchAll(); + Test(); } + protected override void OnBeforeDeactivate() { - Debug.Log("OnBeforeDeactivate"); + ClearAPIObject(); + harmony?.UnpatchAll(MOD_ID); + harmony = null; + } + + private void CreateAPIObject() + { + if(workerObject) + return; + workerObject = new GameObject($"{MOD_ID}_APIObject"); + workerObject.AddComponent(); + } + + private void ClearAPIObject() + { + if(!workerObject) + return; + Destroy(workerObject); + workerObject = null; + } + + private void Test() + { + if(!UIFrameAPI.Initialize()) + return; + if (UIFrameAPI.SetGameTitle(@"C:\Users\Lenovo\Pictures\异噬.png")) + { + Debug.Log("设置标题完成"); + } + else + { + Debug.Log("标题设置失败"); + } } } } \ No newline at end of file diff --git a/UIFrame/Patch/PatchSceneLoaderLoadMainMenu.cs b/UIFrame/Patch/PatchSceneLoaderLoadMainMenu.cs new file mode 100644 index 0000000..6f9bf3f --- /dev/null +++ b/UIFrame/Patch/PatchSceneLoaderLoadMainMenu.cs @@ -0,0 +1,15 @@ + + +using UnityEngine; + +namespace UIFrame.Patch +{ + [HarmonyLib.HarmonyPatch(typeof(SceneLoader), "LoadMainMenu")] + public class PatchSceneLoaderLoadMainMenu + { + public static void Postfix() + { + Debug.Log("LoadMainMenu called"); + } + } +} \ No newline at end of file diff --git a/UIFrame/Singleton.cs b/UIFrame/Singleton.cs new file mode 100644 index 0000000..8947427 --- /dev/null +++ b/UIFrame/Singleton.cs @@ -0,0 +1,38 @@ +using System; + +namespace UIFrame +{ + public abstract class Singleton where T : class + { + private static T? _instance; + private static readonly object _lock = new object(); // 用于多线程安全 + public static T? Instance + { + get + { + // 在多线程环境下,确保只有一个线程能够创建实例 + lock (_lock) + { + if (_instance == null) + { + + _instance = Activator.CreateInstance(typeof(T), true) as T; + if (_instance == null) + { + throw new InvalidOperationException($"无法为类型 {typeof(T).Name} 创建单例实例。" + + "请确保它有一个私有的无参构造函数。"); + } + } + return _instance; + } + } + } + protected Singleton() + { + if (_instance != null) + { + throw new InvalidOperationException($"试图创建第二个单例实例 {typeof(T).Name}。请通过 Singleton<{typeof(T).Name}>.Instance 访问。"); + } + } + } +} \ No newline at end of file diff --git a/UIFrame/UIFrame.csproj b/UIFrame/UIFrame.csproj index 6c99785..27f765c 100644 --- a/UIFrame/UIFrame.csproj +++ b/UIFrame/UIFrame.csproj @@ -18,5 +18,8 @@ + + + diff --git a/UIFrame/UIFrameAPI.cs b/UIFrame/UIFrameAPI.cs index 7ca993d..3db42b7 100644 --- a/UIFrame/UIFrameAPI.cs +++ b/UIFrame/UIFrameAPI.cs @@ -1,7 +1,99 @@ +using System.Collections.Generic; +using UIFrameAPI; +using UnityEngine; + +//改为自己的命名空间更好,这是一个画布单元,一个命名空间一个画布 namespace UIFrame { - public class UIFrameAPI + //反射虽然很好用,但我认为用组件传递高效 + public static class UIFrameAPI { + private static UIFrameAPIComponent? _apiComponent; + private static bool createdCanvas = false; + private static readonly string NameSpace = typeof(UIFrameAPI).Namespace ?? "UIFrame"; + + public static Dictionary textureCache = new Dictionary(); + public static Dictionary spriteCache = new Dictionary(); + ///

+ /// 初始化API + /// + /// + public static bool Initialize() + { + if (_apiComponent!=null) + return true; + _apiComponent = Object.FindObjectOfType(); + return _apiComponent; + } + /// + /// 设置标题图片(游戏中的标题是图片) + /// + /// 图片路径 + /// + public static bool SetGameTitle(string imageFilePath) + { + var texture=LoadSprite(imageFilePath); + if(texture==null) + { + return false; + } + return _apiComponent&&_apiComponent.SetTitleImage(texture); + } + /// + /// 设置标题图片(游戏中的标题是图片) + /// + /// 贴图 + /// + public static bool SetGameTitle(Sprite sprite) + { + return _apiComponent&&_apiComponent.SetTitleImage(sprite); + } + + /// + /// 加载图片文件为Texture2D + /// Texture2D实际存储了图片,图片的数据会上传显卡 + /// 此函数默认加载后不保留内存备份,即不可读像素 + /// 函数会建立文件到图片的索引缓存 + /// + /// + /// + public static Texture2D? LoadImage(string imageFilePath) + { + if (!textureCache.ContainsKey(imageFilePath)) + { + var texture=_apiComponent.LoadTexture(imageFilePath); + if (texture != null) + { + textureCache[imageFilePath] = texture; + } + else + { + Debug.LogError($"加载图片:{imageFilePath}失败"); + return null; + } + } + return textureCache[imageFilePath]; + } + /// + /// 加载图片为Sprite + /// 直接简单的调用LoadImage再创建一个代表此图片的Sprite + /// Sprite只是表明了图片的处理方式,所以为了灵活性建议自己创建 + /// 此函数会缓存地址到Sprite的索引 + /// + /// + /// + public static Sprite? LoadSprite(string imageFilePath) + { + var texture=LoadImage(imageFilePath); + if (texture==null) + return null; + if (!spriteCache.ContainsKey(imageFilePath)) + { + spriteCache[imageFilePath] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); + } + return spriteCache[imageFilePath]; + } + } } \ No newline at end of file diff --git a/UIFrame/UIFrameAPIComponent.cs b/UIFrame/UIFrameAPIComponent.cs new file mode 100644 index 0000000..0a0b5b7 --- /dev/null +++ b/UIFrame/UIFrameAPIComponent.cs @@ -0,0 +1,21 @@ +using TMPro; +using UnityEngine; + +namespace UIFrameAPI +{ + public abstract class UIFrameAPIComponent:MonoBehaviour + { + public abstract bool CreateCanvas(string name); + + //设置游戏主菜单的原版标题 + public abstract bool SetTitleImage(Sprite sprite); + + //创建一个TMP字体 + public abstract TMP_FontAsset CreateFontAsset(string fontFilePath); + + //设置游戏字体 + public abstract bool SetFont(TMP_FontAsset font); + + public abstract Texture2D? LoadTexture(string imageFilePath); + } +} \ No newline at end of file diff --git a/UIFrame/UIFrameWorker.cs b/UIFrame/UIFrameWorker.cs new file mode 100644 index 0000000..f9054ae --- /dev/null +++ b/UIFrame/UIFrameWorker.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using TMPro; +using UIFrameAPI; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TextCore.LowLevel; + +namespace UIFrame +{ + public class UIFrameWorker : UIFrameAPIComponent + { + public GameOriginMainMenuUI gameOriginMainMenuUI = new GameOriginMainMenuUI(); + public Dictionary canvasDic = new Dictionary(); + + + private void Awake() + { + gameOriginMainMenuUI.Initialize(); + } + + private void OnDestroy() + { + gameOriginMainMenuUI.Cleanup(); + } + + + public override bool CreateCanvas(string name) + { + return false; + } + + public override bool SetTitleImage(Sprite sprite) + { + return gameOriginMainMenuUI.SetTitle(sprite); + } + + + public override TMP_FontAsset CreateFontAsset(string fontFilePath) + { + var font = Font.CreateDynamicFontFromOSFont(fontFilePath, 24); + var tmpFont = TMP_FontAsset.CreateFontAsset( + font, + samplingPointSize: 72, // 采样点大小,影响字体质量 + atlasPadding: 4, // 图集内字符间距 + renderMode: GlyphRenderMode.SDFAA, // 推荐使用 SDF 抗锯齿模式 + atlasWidth: 1024, // 图集宽度 (2的幂) + atlasHeight: 1024, // 图集高度 (2的幂) + atlasPopulationMode: AtlasPopulationMode.Dynamic, // 动态填充 + enableMultiAtlasSupport: true // 启用多图集支持 + ); + return tmpFont; + } + + public override bool SetFont(TMP_FontAsset font) + { + return gameOriginMainMenuUI.SetFont(font); + } + + public override Texture2D? LoadTexture(string imageFilePath) + { + return Utilities.ImageLoader.LoadImageFromFile(imageFilePath); + } + } +} \ No newline at end of file diff --git a/UIFrame/UIManager.cs b/UIFrame/UIManager.cs deleted file mode 100644 index 69328db..0000000 --- a/UIFrame/UIManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UIFrame -{ - public static class UIManager - { - - } -} \ No newline at end of file diff --git a/UIFrame/Utilities/GameObjectTool.cs b/UIFrame/Utilities/GameObjectTool.cs new file mode 100644 index 0000000..33e28cc --- /dev/null +++ b/UIFrame/Utilities/GameObjectTool.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UIFrame.Utilities +{ + public class GameObjectTool + { + /// + /// 在指定父对象下,查找第一个匹配名称的子GameObject(可以是孙子、曾孙等) + /// + /// 要查找的父Transform。 + /// 要查找的GameObject的名称。 + /// 找到的GameObject,如果未找到则返回null。 + public static GameObject? FindChildByName(Transform parent, string name) + { + // 查找父对象本身是否就是目标对象 + if (parent.name.Equals(name)) + { + return parent.gameObject; + } + // 遍历所有直接子对象 + foreach (Transform child in parent) + { + // 检查当前子对象是否是目标对象 + if (child.name.Equals(name)) + { + return child.gameObject; + } + // 递归查找子对象的子对象 + var found = FindChildByName(child, name); + if (found) + { + return found; + } + } + return null; + } + /// + /// 在指定父对象下,查找所有匹配名称的子GameObject(可以是孙子、曾孙等),不区分大小写。 + /// + /// 要查找的父Transform。 + /// 要查找的GameObject的名称。 + /// 所有找到的GameObject列表。 + public static List FindChildrenByName(Transform parent, string name) + { + var foundObjects = new List(); + FindChildrenByNameRecursive(parent, name, foundObjects); + return foundObjects; + } + private static void FindChildrenByNameRecursive(Transform currentTransform, string name, List foundObjects) + { + // 检查当前对象是否是目标对象 + if (currentTransform.name.Equals(name)) + { + foundObjects.Add(currentTransform.gameObject); + } + // 遍历所有子对象并递归查找 + foreach (Transform child in currentTransform) + { + FindChildrenByNameRecursive(child, name, foundObjects); + } + } + + + } +} \ No newline at end of file diff --git a/UIFrame/Utilities/ImageLoader.cs b/UIFrame/Utilities/ImageLoader.cs new file mode 100644 index 0000000..e051358 --- /dev/null +++ b/UIFrame/Utilities/ImageLoader.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using UnityEngine; + +namespace UIFrame.Utilities +{ + public class ImageLoader + { + /// + /// 从指定文件路径加载图片并创建 Texture2D。 + /// 支持常用的图片格式 (如 .png, .jpg, .jpeg, .bmp, .tga) + /// + /// 图片文件的绝对路径。 + /// 如果为true,则创建一个新的Texture2D对象并加载图片数据。如果为false,它会尝试加载到默认的空白Texture2D对象上,但通常建议使用true。 + /// 指定纹理是否加载为线性颜色空间 (true) 或 sRGB 颜色空间 (false)。 + /// 加载成功的 Texture2D 对象,如果失败则返回 null。 + public static Texture2D? LoadImageFromFile(string filePath, bool createNewTexture = true, bool linear = false) + { + if (string.IsNullOrEmpty(filePath)) + { + Debug.LogError("ImageLoader: 图片文件路径为空或无效。"); + return null; + } + + if (!File.Exists(filePath)) + { + Debug.LogError($"ImageLoader: 文件不存在于路径: {filePath}"); + return null; + } + + Texture2D? texture = null; + try + { + var fileData = File.ReadAllBytes(filePath); + if (createNewTexture) + { + texture = new Texture2D(2, 2, TextureFormat.RGBA32, false, linear); + } + else if (texture == null) + { + Debug.LogError("ImageLoader: 未能提供现有Texture2D用于加载,且createNewTexture为false。"); + return null; + } + + var success = texture.LoadImage(fileData, true); + + if (!success) + { + Debug.LogError($"ImageLoader: 无法加载图片数据到Texture2D。请检查文件是否为有效的图片格式或是否损坏: {filePath}"); + UnityEngine.Object.Destroy(texture); // 销毁失败的纹理对象 + return null; + } + return texture; + } + catch (Exception ex) + { + Debug.LogError($"ImageLoader: 加载图片时发生错误: {filePath} - {ex.Message}"); + if (texture != null) + { + UnityEngine.Object.Destroy(texture); // 发生异常时销毁已创建的纹理 + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/UIFrame/obj/Release/UIFrame.AssemblyInfo.cs b/UIFrame/obj/Release/UIFrame.AssemblyInfo.cs index 1a1154a..d064d97 100644 --- a/UIFrame/obj/Release/UIFrame.AssemblyInfo.cs +++ b/UIFrame/obj/Release/UIFrame.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("UIFrame")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0206a83f56b5a794fe2f173b4a047cc4f0d4cd90")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5d69efbc3f80a5422cef0884e02fb27adf20b467")] [assembly: System.Reflection.AssemblyProductAttribute("UIFrame")] [assembly: System.Reflection.AssemblyTitleAttribute("UIFrame")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/UIFrame/obj/Release/UIFrame.AssemblyInfoInputs.cache b/UIFrame/obj/Release/UIFrame.AssemblyInfoInputs.cache index b32f3f6..2106e86 100644 --- a/UIFrame/obj/Release/UIFrame.AssemblyInfoInputs.cache +++ b/UIFrame/obj/Release/UIFrame.AssemblyInfoInputs.cache @@ -1 +1 @@ -eb1c4a5bd10a6004eb2efc2bf4354383e0389ddf5e8c9585686a601d75e8a73a +2fde23c3fee254f6fc8f4689686f8631f1cec9f5b900327d5b657c6830a03267 diff --git a/UIFrame/obj/Release/UIFrame.assets.cache b/UIFrame/obj/Release/UIFrame.assets.cache index 3bc61fd5734238b816e31e9f02228d45e223add3..48390a8a9e43e1d744801ff7af9b553e01e8cbfe 100644 GIT binary patch literal 750 zcmcIiyG{Z@6h&PGK_G_2*lA-sJYp>Dj3{m-gn&>@G%I_74YRXkXCM+fYdd4dKWO4# z@DJ4T8QzOaveHmF$;s?}?3_F2Ozp0GxgUu{K3dQlXL8sD%?DPyZ*@U&;XJXP<-497Gaov<$j(@S z)0YITv?XoJw~H*#^S42lEyPZUfe0x!5YZsNzEM3{L8jmgs%Jtmt3!u!hmEn);8G}{ ztsPIYYuC|KqRSkFe#tEdCBj`q3=u~V(H2C4$Q zIxK^(1!7V0pG19j#mYHiScyuE>nnqaba*b)Ue_`kF1OHqR5H30ELF2~&y>N9j?w4b zU!QYfpN*>JLfE)ia9Gwj^0>8LA(yi6C<#Vdw&Q9(G@m{9(sen G@9zh4`OW_T literal 182 zcmWIWc6a1qU|`tkf6pR7^>y?2&?|1byTa6EACz)R^Ifz$dcfUWKcSa>6HpDYfC;Ej z!PzP%v^ce>IL0S6FTX55MlY{4J+&mJATc>RF+H_724t9oi&acnad1XrQEH4|se5Wk J092NcO#m8lB`yE} diff --git a/UIFrame/obj/Release/UIFrame.csproj.AssemblyReference.cache b/UIFrame/obj/Release/UIFrame.csproj.AssemblyReference.cache index 2f0b834991a8b3518de8f82add44cc02b7b0294b..df21979eaca6e36741be2ead8693f3e43b988e42 100644 GIT binary patch delta 310 zcmbQami@(gc2+jVi3|*r7kau(ULeZZ7_>ErkvXN_*(xTqIJKxa#wRr|zbrpSFRwH` zwIrq>F*!RiJ+(L{Co@SeBe5tqKd(}+C^ap{NY6yiFb2fROD!obNz6-0EJ`ubGl((p zfT~K#$zfz<0D^gI#gZQ03y*Y{A;~40IHn ZOLIy}i&7`oXeds8;1INZS_&g`5dgdGW9$F` delta 38 ucmaE{o_*$8c2+jV2@DLA7kau(ULeZZ7_>ErkvV1h>25~H?N3t~nTr4hq78xo diff --git a/UIFrame/obj/Release/UIFrame.csproj.CoreCompileInputs.cache b/UIFrame/obj/Release/UIFrame.csproj.CoreCompileInputs.cache index c51f270..9455748 100644 --- a/UIFrame/obj/Release/UIFrame.csproj.CoreCompileInputs.cache +++ b/UIFrame/obj/Release/UIFrame.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -26cd918e8bf85dee638761fbef4e2117b1734f804498932e0dc10264a1740877 +8df4ddcbe4b7016ad51bf9e0b9ea8b606b36f4d0bbae7f1650b893a49036873e diff --git a/UIFrame/obj/Release/UIFrame.dll b/UIFrame/obj/Release/UIFrame.dll index 066342ae87d5ef6e3e73ee329ec66cf62466b3d1..d78d46f2c550bf2f228f220d39c4563b65a69c2f 100644 GIT binary patch literal 11776 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 bnK%x^?Hm5pAKm#A`XRM>AaO%%7`Ock1iAA5 diff --git a/UIFrame/obj/UIFrame.csproj.nuget.dgspec.json b/UIFrame/obj/UIFrame.csproj.nuget.dgspec.json index 086340a..bb8c193 100644 --- a/UIFrame/obj/UIFrame.csproj.nuget.dgspec.json +++ b/UIFrame/obj/UIFrame.csproj.nuget.dgspec.json @@ -49,6 +49,12 @@ "frameworks": { "netstandard2.1": { "targetAlias": "netstandard2.1", + "dependencies": { + "Lib.Harmony": { + "target": "Package", + "version": "[2.4.1, )" + } + }, "imports": [ "net461", "net462", diff --git a/UIFrame/obj/project.assets.json b/UIFrame/obj/project.assets.json index 7f9867a..1f9dacb 100644 --- a/UIFrame/obj/project.assets.json +++ b/UIFrame/obj/project.assets.json @@ -1,11 +1,165 @@ { "version": 3, "targets": { - ".NETStandard,Version=v2.1": {} + ".NETStandard,Version=v2.1": { + "Lib.Harmony/2.4.1": { + "type": "package", + "dependencies": { + "Lib.Harmony.Ref": "2.4.1" + }, + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + } + }, + "Lib.Harmony.Ref/2.4.1": { + "type": "package", + "dependencies": { + "System.Reflection.Emit": "4.7.0" + }, + "compile": { + "ref/netstandard2.0/0Harmony.dll": { + "related": ".xml" + } + } + }, + "System.Reflection.Emit/4.7.0": { + "type": "package", + "compile": { + "ref/netstandard2.1/_._": {} + }, + "runtime": { + "lib/netstandard2.1/_._": {} + } + } + } + }, + "libraries": { + "Lib.Harmony/2.4.1": { + "sha512": "iLTZi/kKKB18jYEIwReZSx2xXyVUh4J1swReMgvYBBBn4tzA1Nd0PJlVyntY5BDdSiXSxzmvjc/3OYfFs0YwFg==", + "type": "package", + "path": "lib.harmony/2.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "HarmonyLogo.png", + "LICENSE", + "README.md", + "lib.harmony.2.4.1.nupkg.sha512", + "lib.harmony.nuspec", + "lib/net35/0Harmony.dll", + "lib/net35/0Harmony.pdb", + "lib/net35/0Harmony.xml", + "lib/net452/0Harmony.dll", + "lib/net452/0Harmony.pdb", + "lib/net452/0Harmony.xml", + "lib/net472/0Harmony.dll", + "lib/net472/0Harmony.pdb", + "lib/net472/0Harmony.xml", + "lib/net48/0Harmony.dll", + "lib/net48/0Harmony.pdb", + "lib/net48/0Harmony.xml", + "lib/net5.0/0Harmony.dll", + "lib/net5.0/0Harmony.pdb", + "lib/net5.0/0Harmony.xml", + "lib/net6.0/0Harmony.dll", + "lib/net6.0/0Harmony.pdb", + "lib/net6.0/0Harmony.xml", + "lib/net7.0/0Harmony.dll", + "lib/net7.0/0Harmony.pdb", + "lib/net7.0/0Harmony.xml", + "lib/net8.0/0Harmony.dll", + "lib/net8.0/0Harmony.pdb", + "lib/net8.0/0Harmony.xml", + "lib/net9.0/0Harmony.dll", + "lib/net9.0/0Harmony.pdb", + "lib/net9.0/0Harmony.xml", + "lib/netcoreapp3.0/0Harmony.dll", + "lib/netcoreapp3.0/0Harmony.pdb", + "lib/netcoreapp3.0/0Harmony.xml", + "lib/netcoreapp3.1/0Harmony.dll", + "lib/netcoreapp3.1/0Harmony.pdb", + "lib/netcoreapp3.1/0Harmony.xml", + "lib/netstandard2.0/_._" + ] + }, + "Lib.Harmony.Ref/2.4.1": { + "sha512": "+u1y2Qd6OlSUQ8JtrsrSo3adnAsrXMJ2YPYtbW+FAmdPI5yw34M9VX4bKl8ZwRuUzaGzZIz+oGMbn/yS4fWtZw==", + "type": "package", + "path": "lib.harmony.ref/2.4.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "HarmonyLogo.png", + "LICENSE", + "README.md", + "lib.harmony.ref.2.4.1.nupkg.sha512", + "lib.harmony.ref.nuspec", + "ref/netstandard2.0/0Harmony.dll", + "ref/netstandard2.0/0Harmony.xml" + ] + }, + "System.Reflection.Emit/4.7.0": { + "sha512": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==", + "type": "package", + "path": "system.reflection.emit/4.7.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.dll", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.1/System.Reflection.Emit.dll", + "lib/netstandard1.1/System.Reflection.Emit.xml", + "lib/netstandard1.3/System.Reflection.Emit.dll", + "lib/netstandard2.0/System.Reflection.Emit.dll", + "lib/netstandard2.0/System.Reflection.Emit.xml", + "lib/netstandard2.1/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.1/System.Reflection.Emit.dll", + "ref/netstandard1.1/System.Reflection.Emit.xml", + "ref/netstandard1.1/de/System.Reflection.Emit.xml", + "ref/netstandard1.1/es/System.Reflection.Emit.xml", + "ref/netstandard1.1/fr/System.Reflection.Emit.xml", + "ref/netstandard1.1/it/System.Reflection.Emit.xml", + "ref/netstandard1.1/ja/System.Reflection.Emit.xml", + "ref/netstandard1.1/ko/System.Reflection.Emit.xml", + "ref/netstandard1.1/ru/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hans/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hant/System.Reflection.Emit.xml", + "ref/netstandard2.0/System.Reflection.Emit.dll", + "ref/netstandard2.0/System.Reflection.Emit.xml", + "ref/netstandard2.1/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Reflection.Emit.dll", + "runtimes/aot/lib/netcore50/System.Reflection.Emit.xml", + "system.reflection.emit.4.7.0.nupkg.sha512", + "system.reflection.emit.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + } }, - "libraries": {}, "projectFileDependencyGroups": { - ".NETStandard,Version=v2.1": [] + ".NETStandard,Version=v2.1": [ + "Lib.Harmony >= 2.4.1" + ] }, "packageFolders": { "C:\\Users\\Lenovo\\.nuget\\packages\\": {}, @@ -56,6 +210,12 @@ "frameworks": { "netstandard2.1": { "targetAlias": "netstandard2.1", + "dependencies": { + "Lib.Harmony": { + "target": "Package", + "version": "[2.4.1, )" + } + }, "imports": [ "net461", "net462", diff --git a/UIFrame/obj/project.nuget.cache b/UIFrame/obj/project.nuget.cache index 46bb9aa..b0173c9 100644 --- a/UIFrame/obj/project.nuget.cache +++ b/UIFrame/obj/project.nuget.cache @@ -1,8 +1,12 @@ { "version": 2, - "dgSpecHash": "A4ZumVBkozg=", + "dgSpecHash": "b0JcKXS9rW0=", "success": true, "projectFilePath": "D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj", - "expectedPackageFiles": [], + "expectedPackageFiles": [ + "C:\\Users\\Lenovo\\.nuget\\packages\\lib.harmony\\2.4.1\\lib.harmony.2.4.1.nupkg.sha512", + "C:\\Users\\Lenovo\\.nuget\\packages\\lib.harmony.ref\\2.4.1\\lib.harmony.ref.2.4.1.nupkg.sha512", + "C:\\Users\\Lenovo\\.nuget\\packages\\system.reflection.emit\\4.7.0\\system.reflection.emit.4.7.0.nupkg.sha512" + ], "logs": [] } \ No newline at end of file diff --git a/UIFrame/obj/project.packagespec.json b/UIFrame/obj/project.packagespec.json index 477c4e3..c67fbaf 100644 --- a/UIFrame/obj/project.packagespec.json +++ b/UIFrame/obj/project.packagespec.json @@ -1 +1 @@ -"restore":{"projectUniqueName":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","projectName":"UIFrame","projectPath":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","outputPath":"D:\\vs_project\\DuckovMods\\UIFrame\\obj\\","projectStyle":"PackageReference","fallbackFolders":["D:\\vsShare\\NuGetPackages"],"originalTargetFrameworks":["netstandard2.1"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.306\\RuntimeIdentifierGraph.json"}} \ No newline at end of file +"restore":{"projectUniqueName":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","projectName":"UIFrame","projectPath":"D:\\vs_project\\DuckovMods\\UIFrame\\UIFrame.csproj","outputPath":"D:\\vs_project\\DuckovMods\\UIFrame\\obj\\","projectStyle":"PackageReference","fallbackFolders":["D:\\vsShare\\NuGetPackages"],"originalTargetFrameworks":["netstandard2.1"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","dependencies":{"Lib.Harmony":{"target":"Package","version":"[2.4.1, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.306\\RuntimeIdentifierGraph.json"}} \ No newline at end of file diff --git a/UIFrame/obj/rider.project.restore.info b/UIFrame/obj/rider.project.restore.info index cb867e2..ea4a121 100644 --- a/UIFrame/obj/rider.project.restore.info +++ b/UIFrame/obj/rider.project.restore.info @@ -1 +1 @@ -17620796287866771 \ No newline at end of file +17623343068138064 \ No newline at end of file