feat: 受击音效更新类别控制

This commit is contained in:
m0_75251201
2025-11-05 21:34:21 +08:00
parent 5d69efbc3f
commit 786025f720
50 changed files with 2078 additions and 501 deletions

View File

@@ -0,0 +1,488 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
//替换为你的mod命名空间, 防止多个同名ModConfigAPI冲突
namespace HitFeedback.Api {
/// <summary>
/// ModConfig 安全接口封装类 - 提供不抛异常的静态接口
/// ModConfig Safe API Wrapper Class - Provides non-throwing static interfaces
/// </summary>
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;
/// <summary>
/// 检查版本兼容性
/// Check version compatibility
/// </summary>
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;
}
}
/// <summary>
/// 初始化 ModConfigAPI检查必要的函数是否存在
/// Initialize ModConfigAPI, check if necessary functions exist
/// </summary>
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;
}
}
/// <summary>
/// 在所有已加载的程序集中查找类型
/// </summary>
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;
}
}
/// <summary>
/// 安全地添加选项变更事件委托
/// Safely add options changed event delegate
/// </summary>
/// <param name="action">事件处理委托,参数为变更的选项键名</param>
/// <returns>是否成功添加</returns>
public static bool SafeAddOnOptionsChangedDelegate(Action<string> 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;
}
}
/// <summary>
/// 安全地移除选项变更事件委托
/// Safely remove options changed event delegate
/// </summary>
/// <param name="action">要移除的事件处理委托</param>
/// <returns>是否成功移除</returns>
public static bool SafeRemoveOnOptionsChangedDelegate(Action<string> 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;
}
}
/// <summary>
/// 安全地添加下拉列表配置项
/// Safely add dropdown list configuration item
/// </summary>
public static bool SafeAddDropdownList(string modName, string key, string description, System.Collections.Generic.SortedDictionary<string, object> 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;
}
}
/// <summary>
/// 安全地添加带滑条的输入框配置项
/// Safely add input box with slider configuration item
/// </summary>
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;
}
}
/// <summary>
/// 安全地添加布尔下拉列表配置项
/// Safely add boolean dropdown list configuration item
/// </summary>
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;
}
}
/// <summary>
/// 安全地加载配置值
/// Safely load configuration value
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="key">配置键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>加载的值或默认值</returns>
public static T SafeLoad<T>(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;
}
}
/// <summary>
/// 安全地保存配置值
/// Safely save configuration value
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="key">配置键</param>
/// <param name="value">要保存的值</param>
/// <returns>是否保存成功</returns>
public static bool SafeSave<T>(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;
}
}
/// <summary>
/// 检查 ModConfig 是否可用
/// Check if ModConfig is available
/// </summary>
public static bool IsAvailable()
{
return Initialize();
}
/// <summary>
/// 获取 ModConfig 版本信息(如果存在)
/// Get ModConfig version information (if exists)
/// </summary>
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)";
}
}
/// <summary>
/// 检查版本兼容性
/// Check version compatibility
/// </summary>
public static bool IsVersionCompatible()
{
if (!Initialize())
return false;
return isVersionCompatible;
}
}
}

View File

@@ -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<string, float> probability = new Dictionary<string, float>();
/// <summary>
/// 当伤害具有这些特性之一时,将播放音频反馈。
/// </summary>
public HashSet<DamageFeature> audioDamageFeatures = new HashSet<DamageFeature>();
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}");
}
}
/// <summary>
/// 将当前配置存储到指定INI文件。
/// </summary>
@@ -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}");
}
}
/// <summary>
/// 根据DamageInfo的特性判断是否应该播放音频反馈。
/// 如果DamageInfo的任何一个特性在audioDamageFeatures集合中则返回true。
/// </summary>
/// <param name="damageInfo">要检查的DamageInfo对象。</param>
/// <returns>如果应该播放音频反馈则为true否则为false。</returns>
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;
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
namespace HitFeedback
{
[Flags]
public enum DamageFeature
{
/// <summary>
/// 未指定或未分类的伤害特性。
/// </summary>
Undefined = 0,
/// <summary>
/// 普通物理伤害或其他未特别定义的伤害(对应 DamageTypes.normal
/// </summary>
NormalDamage = 1,
/// <summary>
/// 真实伤害,不受护甲或其他减伤效果影响(对应 DamageTypes.realDamage
/// </summary>
RealDamage = 2,
/// <summary>
/// 伤害来自增益Buff或持续效果。
/// (基于 DamageInfo.isFromBuffOrEffect)
/// </summary>
BuffOrEffectDamage = 4,
/// <summary>
/// 伤害无视目标的护甲。
/// (基于 DamageInfo.ignoreArmor)
/// </summary>
ArmorIgnoringDamage = 8,
/// <summary>
/// 伤害是暴击伤害。
/// (基于 DamageInfo.crit > 0)
/// </summary>
CriticalDamage = 16,
/// <summary>
/// 伤害包含护甲穿透效果。
/// (基于 DamageInfo.armorPiercing > 0)
/// </summary>
ArmorPiercingDamage = 32,
/// <summary>
/// 伤害是爆炸类型。
/// (基于 DamageInfo.isExplosion)
/// </summary>
ExplosionDamage = 64,
/// <summary>
/// 伤害具有护甲破坏效果。
/// (基于 DamageInfo.armorBreak > 0)
/// </summary>
ArmorBreakingDamage = 128,
/// <summary>
/// 伤害可能带有元素效果(如果有 elementFactors 存在且非空)。
/// </summary>
ElementalDamage = 256,
/// <summary>
/// 伤害可能附带Buff效果。
/// (基于 DamageInfo.buffChance > 0 或 buff != null)
/// </summary>
/// <remarks>
/// 注意:这个可以与 BuffOrEffectDamage 区分BuffOrEffectDamage 是伤害本身来自Buff
/// 而นี้是伤害造成时附带施加Buff的效果。
/// </remarks>
OnHitBuffApply = 512,
/// <summary>
/// 伤害可能附带流血效果。
/// (基于 DamageInfo.bleedChance > 0)
/// </summary>
OnHitBleed = 1024,
}
}

View File

@@ -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<string,float> audioFilePath = new Dictionary<string,float>();
public Dictionary<string, float> audioProbability = new Dictionary<string, float>();
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>();
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();
}
/// <summary>
/// 生成包含常用 KeyCode 的 SortedDictionary。
/// </summary>
/// <returns>一个 SortedDictionary键是 KeyCode 的字符串表示,值是 KeyCode 枚举本身。</returns>
private static SortedDictionary<string, object> GenerateCommonKeyCodeOptions()
{
var options = new SortedDictionary<string, object>();
// 字母键
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 "未知特性"; // 处理未定义或将来添加的特性
}
}
}
}

View File

@@ -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")]

View File

@@ -1 +1 @@
044e9b3ac36cbf242c64f55f40a9077d40727cb97e540d335e1e29dbc7dccff1
fa83406df4fd7108a6156ab0271b4cddce6abc73c66cbcefdb2f1419643481ef

View File

@@ -1 +1 @@
17059da2b4ba38151f18bb4edeeed66662c124b29efddf4c48967bc01f547046
86c7d211b447f2631e25f9656fa8ef4b9b63f5a671cedbbe00854ccaccdd00b3

View File

@@ -1 +1 @@
17620100186005303
17623343068138064