using 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 + "'");
}
}
}
}