Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Soulcatcher Crossbow Fix v1.0.1
SoulcatcherCrossbowFix.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("SoulcatcherCrossbowFix")] [assembly: AssemblyConfiguration("release")] [assembly: AssemblyDescription("Fixes Crossbow interactions with Soulcatcher mod")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+61aedf875640c084f5973ccd858d0d43f753a701")] [assembly: AssemblyProduct("SoulcatcherCrossbowFix")] [assembly: AssemblyTitle("SoulcatcherCrossbowFix")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SoulcatcherCrossbowFix { [BepInPlugin("ru.custom.soulcatcher_crossbowfix", "Soulcatcher Crossbow Fix", "1.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(SEMan), "ModifyAttack")] private static class SEMan_ModifyAttack_Patch { private static void Postfix(SEMan __instance, SkillType skill, ref HitData hitData) { try { if (CfgEnableDamage == null || !CfgEnableDamage.Value) { return; } object obj = (object)((_fiSEMan_m_character != null) ? /*isinst with value type is only supported in some contexts*/: null); Player val = (Player)((obj is Player) ? obj : null); if (val == null) { return; } ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (!IsCrossbow(currentWeapon)) { return; } float effectPercent = GetEffectPercent(val, (CfgEffectNameHare != null) ? CfgEffectNameHare.Value : "Hare Soul Power"); if (effectPercent <= 0f) { DebugLogRate("[DMG] Hare value=0 (no gem or not applied)."); return; } float num = 1f + effectPercent / 100f; MultiplyDamage(ref hitData.m_damage, num); object arg = effectPercent; object arg2 = num; object arg3; if (currentWeapon == null) { arg3 = null; } else { GameObject dropPrefab = currentWeapon.m_dropPrefab; arg3 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } DebugLogRate($"[DMG] Applied Hare. value={arg:0.###}% mult={arg2:0.###} weapon={arg3}"); DebugDumpWeaponCustomDataRate(currentWeapon); } catch (Exception arg4) { LogError($"[DMG] ModifyAttack failed: {arg4}"); } } } [HarmonyPatch(typeof(ItemData), "GetWeaponLoadingTime")] private static class ItemData_GetWeaponLoadingTime_Patch { private static void Postfix(ItemData __instance, ref float __result) { try { if (CfgEnableReload == null || !CfgEnableReload.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || __instance != ((Humanoid)localPlayer).GetCurrentWeapon() || !IsCrossbow(__instance)) { return; } float effectPercent = GetEffectPercent(localPlayer, (CfgEffectNameDverger != null) ? CfgEffectNameDverger.Value : "Dverger Soul Power"); if (effectPercent <= 0f) { DebugLogRate("[RLD] Dverger value=0 (no gem or not applied)."); return; } float num = Mathf.Clamp01(1f - effectPercent / 100f); float num2 = __result; __result *= num; float num3 = Mathf.Max(0f, (CfgReloadMinTime != null) ? CfgReloadMinTime.Value : 0f); if (num3 > 0f) { __result = Mathf.Max(__result, num3); } object[] obj = new object[5] { effectPercent, num, num2, __result, null }; object obj2; if (__instance == null) { obj2 = null; } else { GameObject dropPrefab = __instance.m_dropPrefab; obj2 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } obj[4] = obj2; DebugLogRate(string.Format("[RLD] Applied Dverger. value={0:0.###}% timeMult={1:0.###} loading {2:0.###}->{3:0.###} weapon={4}", obj)); DebugDumpWeaponCustomDataRate(__instance); } catch (Exception arg) { LogError($"[RLD] GetWeaponLoadingTime failed: {arg}"); } } } [HarmonyPatch(typeof(StatusEffect), "GetTooltipString")] private static class StatusEffect_GetTooltipString_Patch { private static void Postfix(StatusEffect __instance, ref string __result) { try { if (string.IsNullOrEmpty(__result) || __result.IndexOf("crossbow", StringComparison.OrdinalIgnoreCase) < 0) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } string effectName = ((CfgEffectNameHare != null) ? CfgEffectNameHare.Value : "Hare Soul Power"); string effectName2 = ((CfgEffectNameDverger != null) ? CfgEffectNameDverger.Value : "Dverger Soul Power"); float effectPercent = GetEffectPercent(localPlayer, effectName); float effectPercent2 = GetEffectPercent(localPlayer, effectName2); if (!(effectPercent <= 0f) || !(effectPercent2 <= 0f)) { string text = InjectNumbers(__result, effectPercent, effectPercent2); if (!string.Equals(text, __result, StringComparison.Ordinal)) { __result = text; } } } catch (Exception arg) { LogError($"[UI] Active cells tooltip patch failed: {arg}"); } } private static string InjectNumbers(string tooltip, float harePct, float dverPct) { string[] array = tooltip.Split(new char[1] { '\n' }); bool flag = false; for (int i = 0; i < array.Length; i++) { string text = array[i]; string text2 = text.Trim(); if (dverPct > 0f && ContainsAll(text2, "crossbow", "reload")) { array[i] = ReplaceKeepIndent(text, $"Crossbow reload time reduced by {dverPct:0.##}%."); flag = true; } else if (harePct > 0f && ContainsAll(text2, "crossbow", "damage") && text2.IndexOf("reload", StringComparison.OrdinalIgnoreCase) < 0) { array[i] = ReplaceKeepIndent(text, $"Crossbow damage increased by {harePct:0.##}%."); flag = true; } } if (!flag) { return tooltip; } return string.Join("\n", array); } private static bool ContainsAll(string s, params string[] parts) { if (string.IsNullOrEmpty(s) || parts == null || parts.Length == 0) { return false; } for (int i = 0; i < parts.Length; i++) { if (s.IndexOf(parts[i], StringComparison.OrdinalIgnoreCase) < 0) { return false; } } return true; } private static string ReplaceKeepIndent(string originalLine, string newText) { if (string.IsNullOrEmpty(originalLine)) { return newText; } int i; for (i = 0; i < originalLine.Length && char.IsWhiteSpace(originalLine[i]); i++) { } if (i <= 0) { return newText; } return originalLine.Substring(0, i) + newText; } } private static ManualLogSource Log; private Harmony _harmony; private static ConfigEntry<bool> CfgEnableDamage; private static ConfigEntry<bool> CfgEnableReload; private static ConfigEntry<float> CfgReloadMinTime; private static ConfigEntry<bool> CfgDebug; private static ConfigEntry<bool> CfgDebugDumpWeaponCustomData; private static ConfigEntry<bool> CfgDebugDumpDiscovery; private static ConfigEntry<float> CfgDebugMinIntervalSec; private static ConfigEntry<string> CfgEffectNameHare; private static ConfigEntry<string> CfgEffectNameDverger; private static float _nextLogTime; private const string DefaultEffectHare = "Hare Soul Power"; private const string DefaultEffectDverger = "Dverger Soul Power"; private static MethodInfo _miGetEffectPowerGenericDef; private static Type _cfgTypeHare; private static Type _cfgTypeDverger; private static float _nextResolveAttemptTime; private static FieldInfo _fiSEMan_m_character; private void Awake() { //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CfgEnableDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableHareDamageBonus", true, "Enable Hare gem damage bonus for crossbows."); CfgEnableReload = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableDvergerReloadBonus", true, "Enable Dverger gem reload time reduction for crossbows."); CfgReloadMinTime = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MinReloadTime", 0.3f, "Minimum reload time for crossbows when the Dverger gem effect reduces it."); CfgEffectNameHare = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "HareEffectName", "Hare Soul Power", "Jewelcrafting effect name for Hare gem."); CfgEffectNameDverger = ((BaseUnityPlugin)this).Config.Bind<string>("Effects", "DvergerEffectName", "Dverger Soul Power", "Jewelcrafting effect name for Dverger gem."); CfgDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugEnabled", false, "Enable verbose debug logging."); CfgDebugDumpWeaponCustomData = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DumpWeaponCustomData", false, "Dump current weapon m_customData (spammy)."); CfgDebugDumpDiscovery = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DumpDiscovery", true, "Log discovery of GetEffectPower method and config types."); CfgDebugMinIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("Debug", "MinLogIntervalSeconds", 0.5f, "Rate limit for repeated debug logs."); _fiSEMan_m_character = AccessTools.Field(typeof(SEMan), "m_character"); ResolveJewelcraftingBindings(force: true); _harmony = new Harmony("ru.custom.soulcatcher_crossbowfix"); _harmony.PatchAll(); LogInfo("[Init] Loaded v1.0.1"); } private static bool IsCrossbow(ItemData weapon) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (weapon == null || weapon.m_shared == null) { return false; } if ((int)weapon.m_shared.m_skillType == 14) { return true; } string text = (Object.op_Implicit((Object)(object)weapon.m_dropPrefab) ? ((Object)weapon.m_dropPrefab).name : null); if (string.IsNullOrEmpty(text)) { return false; } return text.StartsWith("Crossbow", StringComparison.OrdinalIgnoreCase); } private static void MultiplyDamage(ref DamageTypes dmg, float mult) { dmg.m_damage *= mult; dmg.m_pierce *= mult; dmg.m_blunt *= mult; dmg.m_slash *= mult; dmg.m_fire *= mult; dmg.m_frost *= mult; dmg.m_lightning *= mult; dmg.m_poison *= mult; dmg.m_spirit *= mult; dmg.m_chop *= mult; dmg.m_pickaxe *= mult; } private static float GetEffectPercent(Player player, string effectName) { if ((Object)(object)player == (Object)null) { return 0f; } if (string.IsNullOrWhiteSpace(effectName)) { return 0f; } ResolveJewelcraftingBindings(force: false); if (_miGetEffectPowerGenericDef == null) { DebugLogRate("[JC] GetEffectPower<T> not resolved."); return 0f; } Type type = null; if (CfgEffectNameHare != null && string.Equals(effectName, CfgEffectNameHare.Value, StringComparison.OrdinalIgnoreCase)) { type = _cfgTypeHare; } else if (CfgEffectNameDverger != null && string.Equals(effectName, CfgEffectNameDverger.Value, StringComparison.OrdinalIgnoreCase)) { type = _cfgTypeDverger; } if (type == null) { if (effectName.IndexOf("Hare", StringComparison.OrdinalIgnoreCase) >= 0) { type = _cfgTypeHare; } if (effectName.IndexOf("Dver", StringComparison.OrdinalIgnoreCase) >= 0) { type = _cfgTypeDverger; } } if (type == null) { DebugLogRate("[JC] Config type not resolved for effect '" + effectName + "'."); return 0f; } try { object obj = _miGetEffectPowerGenericDef.MakeGenericMethod(type).Invoke(null, new object[2] { player, effectName }); if (obj == null) { return 0f; } float num = ExtractFirstFloat(type, obj); if (CfgDebug != null && CfgDebug.Value && CfgDebugDumpDiscovery != null && CfgDebugDumpDiscovery.Value) { LogInfo($"[JC] Effect '{effectName}' cfgType='{type.FullName}' value={num:0.###}"); } return num; } catch (Exception ex) { DebugLogRate("[JC] GetEffectPower invoke failed: " + ex.GetType().Name + ": " + ex.Message); return 0f; } } private static float ExtractFirstFloat(Type cfgType, object cfgObj) { try { FieldInfo field = cfgType.GetField("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(float)) { return (float)field.GetValue(cfgObj); } FieldInfo field2 = cfgType.GetField("Power", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null && field2.FieldType == typeof(float)) { return (float)field2.GetValue(cfgObj); } FieldInfo fieldInfo = cfgType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((FieldInfo x) => x.FieldType == typeof(float)); if (fieldInfo != null) { return (float)fieldInfo.GetValue(cfgObj); } PropertyInfo propertyInfo = cfgType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((PropertyInfo x) => x.PropertyType == typeof(float) && x.GetIndexParameters().Length == 0); if (propertyInfo != null) { return (float)propertyInfo.GetValue(cfgObj, null); } } catch { } return 0f; } private static void ResolveJewelcraftingBindings(bool force) { if (!force) { if (Time.time < _nextResolveAttemptTime) { return; } _nextResolveAttemptTime = Time.time + 1f; } bool num = _miGetEffectPowerGenericDef != null; bool flag = _cfgTypeHare != null && _cfgTypeDverger != null; if (num && flag) { return; } try { if (_miGetEffectPowerGenericDef == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] types; try { types = assembly.GetTypes(); } catch { continue; } foreach (Type type in types) { if (type == null) { continue; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo == null) && !(methodInfo.Name != "GetEffectPower") && methodInfo.IsGenericMethodDefinition) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(Player)) && !(parameters[1].ParameterType != typeof(string))) { _miGetEffectPowerGenericDef = methodInfo; break; } } } if (_miGetEffectPowerGenericDef != null) { break; } } if (_miGetEffectPowerGenericDef != null) { break; } } if (CfgDebug != null && CfgDebug.Value && CfgDebugDumpDiscovery != null && CfgDebugDumpDiscovery.Value) { LogInfo((_miGetEffectPowerGenericDef != null) ? ("[DISCOVERY] Found GetEffectPower<T>: " + _miGetEffectPowerGenericDef.DeclaringType?.FullName + "::" + _miGetEffectPowerGenericDef.Name) : "[DISCOVERY] GetEffectPower<T> not found (yet)."); } } } catch (Exception arg) { if (CfgDebug != null && CfgDebug.Value) { LogError($"[DISCOVERY] GetEffectPower<T> search failed: {arg}"); } } try { if (_cfgTypeHare == null) { _cfgTypeHare = FindTypeByFragments(new string[3] { "Hare", "Soul", "Power" }); } if (_cfgTypeDverger == null) { _cfgTypeDverger = FindTypeByFragments(new string[3] { "Dver", "Soul", "Power" }); } if (CfgDebug != null && CfgDebug.Value && CfgDebugDumpDiscovery != null && CfgDebugDumpDiscovery.Value) { LogInfo("[DISCOVERY] Hare cfgType: " + (_cfgTypeHare?.FullName ?? "NOT FOUND")); LogInfo("[DISCOVERY] Dverger cfgType: " + (_cfgTypeDverger?.FullName ?? "NOT FOUND")); } } catch (Exception arg2) { if (CfgDebug != null && CfgDebug.Value) { LogError($"[DISCOVERY] Config type search failed: {arg2}"); } } } private static Type FindTypeByFragments(string[] fragments) { foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies().OrderByDescending(delegate(Assembly a) { string? obj2 = a.GetName().Name ?? ""; int num = 0; if (obj2.IndexOf("Soulcatcher", StringComparison.OrdinalIgnoreCase) >= 0) { num += 2; } if (obj2.IndexOf("Jewelcrafting", StringComparison.OrdinalIgnoreCase) >= 0) { num++; } return num; })) { Type[] types; try { types = item.GetTypes(); } catch { continue; } Type[] array = types; foreach (Type type in array) { if (type == null) { continue; } string fullName = type.FullName; if (string.IsNullOrEmpty(fullName)) { continue; } bool flag = true; for (int j = 0; j < fragments.Length; j++) { if (fullName.IndexOf(fragments[j], StringComparison.OrdinalIgnoreCase) < 0) { flag = false; break; } } if (flag && (type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((FieldInfo f) => f.FieldType == typeof(float)) || type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((PropertyInfo p) => p.PropertyType == typeof(float) && p.GetIndexParameters().Length == 0))) { return type; } } } return null; } private static void LogInfo(string msg) { try { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)msg); } } catch { Debug.Log((object)msg); } } private static void LogError(string msg) { try { ManualLogSource log = Log; if (log != null) { log.LogError((object)msg); } } catch { Debug.LogError((object)msg); } } private static void DebugLogRate(string msg) { if (CfgDebug != null && CfgDebug.Value) { float num = ((CfgDebugMinIntervalSec != null) ? Mathf.Max(0.05f, CfgDebugMinIntervalSec.Value) : 0.5f); if (!(Time.time < _nextLogTime)) { _nextLogTime = Time.time + num; LogInfo(msg); } } } private static void DebugDumpWeaponCustomDataRate(ItemData weapon) { if (CfgDebug == null || !CfgDebug.Value || CfgDebugDumpWeaponCustomData == null || !CfgDebugDumpWeaponCustomData.Value || weapon == null) { return; } if (CfgDebugMinIntervalSec != null) { Mathf.Max(0.05f, CfgDebugMinIntervalSec.Value); } if (Time.time < _nextLogTime) { return; } if (weapon.m_customData == null || weapon.m_customData.Count == 0) { LogInfo("[DBG] weapon.m_customData is empty"); return; } foreach (KeyValuePair<string, string> customDatum in weapon.m_customData) { string text = customDatum.Value ?? ""; if (text.Length > 160) { text = text.Substring(0, 160) + "..."; } LogInfo("[DBG] customData: '" + customDatum.Key + "' = '" + text + "'"); } } } }