Please disclose if any significant portion of your mod was created 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 PVPVEserver v6.9.6
BepInEx/plugins/JewelcraftingKarmaDrops.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jewelcrafting; using Jewelcrafting.GemEffects; using Jewelcrafting.LootSystem; using Microsoft.CodeAnalysis; using ServerSync; 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("sighsorry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Restricts Jewelcrafting creature-death loot and applies narrow Jewelcrafting compatibility fixes.")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+efbdeeb3b6eb978567aa8af3378051ffbbfacb07")] [assembly: AssemblyProduct("Jewelcrafting Karma Drops")] [assembly: AssemblyTitle("JewelcraftingKarmaDrops")] [assembly: AssemblyVersion("1.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 JewelcraftingKarmaDrops { [HarmonyPatch(typeof(Character), "RPC_Damage", new Type[] { typeof(long), typeof(HitData) })] internal static class CharacterRpcDamagePatch { [HarmonyPriority(800)] private static void Prefix(Character __instance, HitData hit, out KarmaDeathAttribution.RpcDamageScope __state) { __state = KarmaDeathAttribution.BeginRpcDamage(__instance, hit); } private static Exception? Finalizer(Exception? __exception, KarmaDeathAttribution.RpcDamageScope __state) { KarmaDeathAttribution.EndRpcDamage(__state); return __exception; } } [HarmonyPatch(typeof(Character), "ApplyDamage", new Type[] { typeof(HitData), typeof(bool), typeof(bool), typeof(DamageModifier) })] internal static class CharacterApplyDamagePatch { private static void Prefix(Character __instance, HitData hit, out KarmaDeathAttribution.ApplyDamageScope __state) { __state = KarmaDeathAttribution.BeginApplyDamage(__instance, hit); } private static void Postfix(Character __instance, HitData hit, KarmaDeathAttribution.ApplyDamageScope __state) { KarmaDeathAttribution.CompleteApplyDamage(__instance, hit, __state); } } [HarmonyPatch(typeof(SE_Poison), "AddDamage", new Type[] { typeof(float) })] internal static class PoisonSourcePatch { private static void Prefix(SE_Poison __instance, float damage, out bool __state) { __state = damage >= ValheimPrivateFields.GetPoisonDamageLeft(__instance); } private static void Postfix(SE_Poison __instance, bool __state) { KarmaDeathAttribution.RecordPoisonSource(__instance, __state); } } [HarmonyPatch(typeof(SE_Burning), "AddFireDamage", new Type[] { typeof(float) })] internal static class FireSourcePatch { private static void Prefix(SE_Burning __instance, out bool __state) { __state = ValheimPrivateFields.GetFireDamageLeft(__instance) <= 0f; } private static void Postfix(SE_Burning __instance, bool __state, bool __result) { KarmaDeathAttribution.RecordFireSource(__instance, __state, __result); } } [HarmonyPatch(typeof(SE_Burning), "AddSpiritDamage", new Type[] { typeof(float) })] internal static class SpiritSourcePatch { private static void Prefix(SE_Burning __instance, out bool __state) { __state = ValheimPrivateFields.GetSpiritDamageLeft(__instance) <= 0f; } private static void Postfix(SE_Burning __instance, bool __state, bool __result) { KarmaDeathAttribution.RecordSpiritSource(__instance, __state, __result); } } [HarmonyPatch(typeof(SE_Poison), "UpdateStatusEffect", new Type[] { typeof(float) })] internal static class PoisonTickPatch { private static void Prefix(SE_Poison __instance, out KarmaDeathAttribution.DelayedTickScope __state) { __state = KarmaDeathAttribution.BeginPoisonTick(__instance); } private static Exception? Finalizer(Exception? __exception, KarmaDeathAttribution.DelayedTickScope __state) { KarmaDeathAttribution.EndDelayedTick(__state); return __exception; } } [HarmonyPatch(typeof(SE_Burning), "UpdateStatusEffect", new Type[] { typeof(float) })] internal static class BurningTickPatch { private static void Prefix(SE_Burning __instance, out KarmaDeathAttribution.DelayedTickScope __state) { __state = KarmaDeathAttribution.BeginBurningTick(__instance); } private static Exception? Finalizer(Exception? __exception, KarmaDeathAttribution.DelayedTickScope __state) { KarmaDeathAttribution.EndDelayedTick(__state); return __exception; } } [HarmonyPatch(typeof(Character), "Heal")] internal static class CharacterHealPatch { private static void Postfix(Character __instance) { KarmaDeathAttribution.ClearRecoveredDeathCredit(__instance); } } [HarmonyPatch(typeof(Character), "SetHealth", new Type[] { typeof(float) })] internal static class CharacterSetHealthPatch { private static void Postfix(Character __instance) { KarmaDeathAttribution.ClearRecoveredDeathCredit(__instance); } } [HarmonyPatch(typeof(Character), "OnDestroy")] internal static class CharacterOnDestroyPatch { private static void Prefix(Character __instance) { JewelcraftingDropGate.EndDeath(__instance); KarmaDeathAttribution.ForgetCharacter(__instance); } } [HarmonyPatch(typeof(ZNetScene), "OnDestroy")] internal static class ZNetSceneOnDestroyPatch { private static void Prefix() { JewelcraftingDropGate.ResetRuntimeState(); KarmaDeathAttribution.Reset(); } } internal static class JewelcraftingDropGate { private sealed class DeathEligibility { internal Character Target; internal bool Allowed; } [HarmonyPatch(typeof(Character), "OnDeath")] private static class DeathScopePatch { [HarmonyPriority(800)] private static void Prefix(Character __instance) { BeginDeath(__instance); } [HarmonyPriority(0)] private static Exception? Finalizer(Character __instance, Exception? __exception) { EndDeath(__instance); KarmaDeathAttribution.ForgetCharacter(__instance); return __exception; } } [HarmonyPatch(typeof(EquipmentDrops), "DoDrop")] private static class RequireKarmaKillForEquipmentDrops { private static bool Prefix(CharacterDrop characterDrop) { return IsAllowed(characterDrop); } } [HarmonyPatch] private static class RequireKarmaKillForChestDrops { private static MethodBase TargetMethod() { return RequireMethod("Jewelcrafting.LootSystem.ChestDrops+AddGemChestDrop", "Postfix"); } private static bool Prefix(object[] __args) { if (__args.Length != 0) { object obj = __args[0]; CharacterDrop val = (CharacterDrop)((obj is CharacterDrop) ? obj : null); if (val != null) { return IsAllowed(val); } } return false; } } private static readonly Dictionary<Func<Character, IEnumerable<Drop>>, Func<Character, IEnumerable<Drop>>> ownWrappers = new Dictionary<Func<Character, IEnumerable<Drop>>, Func<Character, IEnumerable<Drop>>>(); private static readonly Dictionary<int, DeathEligibility> currentDeaths = new Dictionary<int, DeathEligibility>(); internal static void Register() { WrapLootDelegates(); } internal static void Reset() { List<Func<Character, IEnumerable<Drop>>> loot = LootAdder.Loot; for (int i = 0; i < loot.Count; i++) { Func<Character, IEnumerable<Drop>> func = loot[i]; for (int j = 0; j < 8; j++) { if (!ownWrappers.TryGetValue(func, out Func<Character, IEnumerable<Drop>> value)) { break; } func = value; } loot[i] = func; } ownWrappers.Clear(); currentDeaths.Clear(); } internal static void ResetRuntimeState() { currentDeaths.Clear(); } internal static void BeginDeath(Character character) { WrapLootDelegates(); currentDeaths[((Object)character).GetInstanceID()] = new DeathEligibility { Target = character, Allowed = KarmaDeathAttribution.IsKarmaEligibleKill(character) }; } internal static void EndDeath(Character? character) { if (!((Object)(object)character == (Object)null)) { int instanceID = ((Object)character).GetInstanceID(); if (currentDeaths.TryGetValue(instanceID, out DeathEligibility value) && value.Target == character) { currentDeaths.Remove(instanceID); } } } internal static bool IsDeathInProgress(Character? character) { if ((Object)(object)character != (Object)null && currentDeaths.TryGetValue(((Object)character).GetInstanceID(), out DeathEligibility value)) { return value.Target == character; } return false; } private static bool IsAllowed(Character? character) { if ((Object)(object)character == (Object)null) { return false; } if (currentDeaths.TryGetValue(((Object)character).GetInstanceID(), out DeathEligibility value) && value.Target == character) { return value.Allowed; } return KarmaDeathAttribution.IsKarmaEligibleKill(character); } private static bool IsAllowed(CharacterDrop? characterDrop) { if ((Object)(object)characterDrop != (Object)null) { Character character = ValheimPrivateFields.GetCharacter(characterDrop); if (character != null) { return IsAllowed(character); } } return false; } private static void WrapLootDelegates() { List<Func<Character, IEnumerable<Drop>>> loot = LootAdder.Loot; for (int i = 0; i < loot.Count; i++) { Func<Character, IEnumerable<Drop>> original = loot[i]; if (!ownWrappers.ContainsKey(original)) { Func<Character, IEnumerable<Drop>> key = (loot[i] = (Character character) => (!IsAllowed(character)) ? Enumerable.Empty<Drop>() : original(character)); ownWrappers[key] = original; } } } private static MethodInfo RequireMethod(string typeName, string methodName) { return AccessTools.DeclaredMethod(AccessTools.TypeByName(typeName) ?? throw new MissingMethodException(typeName, methodName), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeName, methodName); } } internal static class KarmaDeathAttribution { internal enum DelayedAttributionKind { Unknown, Unattributed, Exact, Ambiguous } internal readonly struct DelayedAttribution { internal DelayedAttributionKind Kind { get; } internal ZDOID Source { get; } internal bool SourceWasPlayer { get; } internal bool IsExact { get { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (Kind == DelayedAttributionKind.Exact) { return Source != ZDOID.None; } return false; } } internal DelayedAttribution(DelayedAttributionKind kind, ZDOID source, bool sourceWasPlayer = false) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Kind = kind; Source = source; SourceWasPlayer = sourceWasPlayer; } internal static DelayedAttribution FromSource(ZDOID source, bool sourceWasPlayer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!(source == ZDOID.None)) { return new DelayedAttribution(DelayedAttributionKind.Exact, source, sourceWasPlayer); } return new DelayedAttribution(DelayedAttributionKind.Unattributed, ZDOID.None); } } private sealed class DelayedSourceLedger { internal Character Target; internal SE_Poison? PoisonStatus; internal DelayedAttribution Poison; internal SE_Burning? FireStatus; internal DelayedAttribution Fire; internal SE_Burning? SpiritStatus; internal DelayedAttribution Spirit; } private readonly struct DelayedDeathCredit { internal Character Target { get; } internal ZDOID Source { get; } internal bool SourceWasPlayer { get; } internal DelayedDeathCredit(Character target, ZDOID source, bool sourceWasPlayer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Target = target; Source = source; SourceWasPlayer = sourceWasPlayer; } } internal readonly struct RpcDamageScope { private RpcDamageContext Previous { get; } internal bool Changed { get; } internal RpcDamageScope(RpcDamageContext previous) { Previous = previous; Changed = true; } internal void Restore() { if (Changed) { currentRpcDamage = Previous; } } } internal readonly struct DelayedTickScope { private DelayedTickContext Previous { get; } internal bool Changed { get; } internal DelayedTickScope(DelayedTickContext previous) { Previous = previous; Changed = true; } internal void Restore() { if (Changed) { currentDelayedTick = Previous; } } } internal readonly struct ApplyDamageScope { internal bool EligibleAtEntry { get; } internal float HealthBefore { get; } internal ApplyDamageScope(bool eligibleAtEntry, float healthBefore) { EligibleAtEntry = eligibleAtEntry; HealthBefore = healthBefore; } } internal struct RpcDamageContext { internal Character? Target; internal HitData? Hit; } internal struct DelayedTickContext { internal Character? Target; internal HitType HitType; internal DelayedAttribution Attribution; } private readonly struct FinalAttribution { internal ZDOID Source { get; } internal bool SourceWasPlayer { get; } internal bool HasSource => Source != ZDOID.None; internal FinalAttribution(ZDOID source, bool sourceWasPlayer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Source = source; SourceWasPlayer = sourceWasPlayer; } } private static readonly Dictionary<int, DelayedSourceLedger> delayedSourceLedgers = new Dictionary<int, DelayedSourceLedger>(); private static readonly Dictionary<int, DelayedDeathCredit> pendingDelayedDeathCredits = new Dictionary<int, DelayedDeathCredit>(); [ThreadStatic] private static RpcDamageContext currentRpcDamage; [ThreadStatic] private static DelayedTickContext currentDelayedTick; internal static RpcDamageScope BeginRpcDamage(Character target, HitData hit) { RpcDamageContext previous = currentRpcDamage; currentRpcDamage = new RpcDamageContext { Target = target, Hit = hit }; return new RpcDamageScope(previous); } internal static void EndRpcDamage(RpcDamageScope scope) { scope.Restore(); } internal static void RecordPoisonSource(SE_Poison status, bool accepted) { if (accepted && !((Object)(object)status == (Object)null) && !((Object)(object)((StatusEffect)status).m_character == (Object)null)) { Character character = ((StatusEffect)status).m_character; DelayedSourceLedger ledger = GetLedger(character); ledger.PoisonStatus = status; ledger.Poison = GetCurrentDelayedAttribution(character); } } internal static void RecordFireSource(SE_Burning status, bool poolWasEmpty, bool accepted) { RecordAccumulatingSource(status, poolWasEmpty, accepted, spirit: false); } internal static void RecordSpiritSource(SE_Burning status, bool poolWasEmpty, bool accepted) { RecordAccumulatingSource(status, poolWasEmpty, accepted, spirit: true); } internal static DelayedTickScope BeginPoisonTick(SE_Poison status) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) DelayedTickContext previous = currentDelayedTick; Character val = ((StatusEffect)(status?)).m_character; DelayedAttribution attribution = default(DelayedAttribution); if ((Object)(object)val != (Object)null && delayedSourceLedgers.TryGetValue(((Object)val).GetInstanceID(), out DelayedSourceLedger value) && value.Target == val && value.PoisonStatus == status) { attribution = value.Poison; } currentDelayedTick = new DelayedTickContext { Target = val, HitType = (HitType)7, Attribution = attribution }; return new DelayedTickScope(previous); } internal static DelayedTickScope BeginBurningTick(SE_Burning status) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) DelayedTickContext previous = currentDelayedTick; Character val = ((StatusEffect)(status?)).m_character; DelayedAttribution fire = default(DelayedAttribution); DelayedAttribution spirit = default(DelayedAttribution); if ((Object)(object)val != (Object)null && delayedSourceLedgers.TryGetValue(((Object)val).GetInstanceID(), out DelayedSourceLedger value) && value.Target == val) { if (value.FireStatus == status) { fire = value.Fire; } if (value.SpiritStatus == status) { spirit = value.Spirit; } } bool hasFire = (Object)(object)status != (Object)null && ValheimPrivateFields.GetFireDamagePerHit(status) > 0f; bool hasSpirit = (Object)(object)status != (Object)null && ValheimPrivateFields.GetSpiritDamagePerHit(status) > 0f; currentDelayedTick = new DelayedTickContext { Target = val, HitType = (HitType)5, Attribution = CombineTickAttribution(fire, hasFire, spirit, hasSpirit) }; return new DelayedTickScope(previous); } internal static void EndDelayedTick(DelayedTickScope scope) { scope.Restore(); } internal static ApplyDamageScope BeginApplyDamage(Character target, HitData hit) { bool eligibleAtEntry = (Object)(object)target != (Object)null && hit != null && !target.IsDebugFlying() && !target.IsDead() && !target.IsTeleporting() && !target.InCutscene(); float healthBefore = (((Object)(object)target == (Object)null) ? 0f : Mathf.Max(0f, target.GetHealth())); return new ApplyDamageScope(eligibleAtEntry, healthBefore); } internal static void CompleteApplyDamage(Character target, HitData hit, ApplyDamageScope scope) { if (scope.EligibleAtEntry) { CaptureDelayedDeathCredit(target, hit, scope.HealthBefore); } } internal static void ClearRecoveredDeathCredit(Character character) { if ((Object)(object)character != (Object)null && character.GetHealth() > 0f) { pendingDelayedDeathCredits.Remove(((Object)character).GetInstanceID()); } } internal static bool IsKarmaEligibleKill(Character? dead) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)dead == (Object)null || dead.IsPlayer() || dead.IsTamed()) { return false; } FinalAttribution finalAttribution = CaptureFinalAttribution(dead); bool sourceIsPlayer; if (finalAttribution.HasSource && finalAttribution.Source != dead.GetZDOID()) { return TryValidatePlayerSideSource(finalAttribution.Source, out sourceIsPlayer); } return false; } internal static void ForgetCharacter(Character? character) { if (!((Object)(object)character == (Object)null)) { int instanceID = ((Object)character).GetInstanceID(); delayedSourceLedgers.Remove(instanceID); pendingDelayedDeathCredits.Remove(instanceID); } } internal static void Reset() { delayedSourceLedgers.Clear(); pendingDelayedDeathCredits.Clear(); currentRpcDamage = default(RpcDamageContext); currentDelayedTick = default(DelayedTickContext); } private static void RecordAccumulatingSource(SE_Burning status, bool poolWasEmpty, bool accepted, bool spirit) { if (accepted && !((Object)(object)status == (Object)null) && !((Object)(object)((StatusEffect)status).m_character == (Object)null)) { Character character = ((StatusEffect)status).m_character; DelayedSourceLedger ledger = GetLedger(character); DelayedAttribution currentDelayedAttribution = GetCurrentDelayedAttribution(character); if (spirit) { bool flag = ledger.SpiritStatus == status; ledger.Spirit = (poolWasEmpty ? currentDelayedAttribution : MergeAttribution(flag ? ledger.Spirit : default(DelayedAttribution), currentDelayedAttribution)); ledger.SpiritStatus = status; } else { bool flag2 = ledger.FireStatus == status; ledger.Fire = (poolWasEmpty ? currentDelayedAttribution : MergeAttribution(flag2 ? ledger.Fire : default(DelayedAttribution), currentDelayedAttribution)); ledger.FireStatus = status; } } } private static DelayedAttribution MergeAttribution(DelayedAttribution current, DelayedAttribution incoming) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (current.Kind == DelayedAttributionKind.Exact && incoming.Kind == DelayedAttributionKind.Exact && current.Source == incoming.Source) { return DelayedAttribution.FromSource(current.Source, current.SourceWasPlayer || incoming.SourceWasPlayer); } if (current.Kind == DelayedAttributionKind.Unattributed && incoming.Kind == DelayedAttributionKind.Unattributed) { return current; } return new DelayedAttribution(DelayedAttributionKind.Ambiguous, ZDOID.None); } private static DelayedSourceLedger GetLedger(Character target) { int instanceID = ((Object)target).GetInstanceID(); if (!delayedSourceLedgers.TryGetValue(instanceID, out DelayedSourceLedger value) || value.Target != target) { value = new DelayedSourceLedger { Target = target }; delayedSourceLedgers[instanceID] = value; } return value; } private static DelayedAttribution GetCurrentDelayedAttribution(Character target) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (currentRpcDamage.Target != target || currentRpcDamage.Hit == null) { return DelayedAttribution.FromSource(ZDOID.None, sourceWasPlayer: false); } HitData? hit = currentRpcDamage.Hit; Character attacker = hit.GetAttacker(); return DelayedAttribution.FromSource(hit.m_attacker, (Object)(object)attacker != (Object)null && attacker.IsPlayer()); } private static DelayedAttribution CombineTickAttribution(DelayedAttribution fire, bool hasFire, DelayedAttribution spirit, bool hasSpirit) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!hasFire) { if (!hasSpirit) { return default(DelayedAttribution); } return spirit; } if (!hasSpirit) { return fire; } if (fire.Kind == DelayedAttributionKind.Exact && spirit.Kind == DelayedAttributionKind.Exact && fire.Source == spirit.Source) { return DelayedAttribution.FromSource(fire.Source, fire.SourceWasPlayer || spirit.SourceWasPlayer); } if (fire.Kind == DelayedAttributionKind.Unattributed && spirit.Kind == DelayedAttributionKind.Unattributed) { return fire; } return new DelayedAttribution(DelayedAttributionKind.Ambiguous, ZDOID.None); } private static void CaptureDelayedDeathCredit(Character target, HitData hit, float healthBefore) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null) && hit != null && !(healthBefore <= 0f) && !(target.GetHealth() > 0f) && ValheimPrivateFields.GetLastHit(target) == hit) { int instanceID = ((Object)target).GetInstanceID(); if (currentDelayedTick.Target != target || currentDelayedTick.HitType != hit.m_hitType) { pendingDelayedDeathCredits.Remove(instanceID); return; } DelayedAttribution attribution = currentDelayedTick.Attribution; pendingDelayedDeathCredits[instanceID] = new DelayedDeathCredit(target, attribution.IsExact ? attribution.Source : ZDOID.None, attribution.IsExact && attribution.SourceWasPlayer); } } private static FinalAttribution CaptureFinalAttribution(Character dead) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) int instanceID = ((Object)dead).GetInstanceID(); if (pendingDelayedDeathCredits.TryGetValue(instanceID, out var value) && value.Target == dead) { if (!(value.Source == ZDOID.None)) { return new FinalAttribution(value.Source, value.SourceWasPlayer); } return default(FinalAttribution); } HitData? lastHit = ValheimPrivateFields.GetLastHit(dead); Character val = ((lastHit != null) ? lastHit.GetAttacker() : null); ZDOID val2 = lastHit?.m_attacker ?? ZDOID.None; if (val2 == ZDOID.None && (Object)(object)val != (Object)null) { val2 = val.GetZDOID(); } if (!(val2 == ZDOID.None)) { return new FinalAttribution(val2, (Object)(object)val != (Object)null && val.IsPlayer()); } return default(FinalAttribution); } private static bool TryValidatePlayerSideSource(ZDOID sourceId, out bool sourceIsPlayer) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Invalid comparison between Unknown and I4 //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Invalid comparison between Unknown and I4 //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) sourceIsPlayer = false; if (sourceId == ZDOID.None || (Object)(object)ZNetScene.instance == (Object)null) { return false; } if ((Object)(object)Player.m_localPlayer != (Object)null && ((Character)Player.m_localPlayer).GetZDOID() == sourceId) { sourceIsPlayer = true; return true; } ZNet instance = ZNet.instance; if ((Object)(object)instance != (Object)null) { foreach (ZNetPeer peer in instance.GetPeers()) { if (peer != null && peer.IsReady() && peer.m_characterID == sourceId) { sourceIsPlayer = true; return true; } } if (!instance.IsServer()) { foreach (PlayerInfo player in instance.GetPlayerList()) { if (player.m_characterID == sourceId) { sourceIsPlayer = true; return true; } } } } Character val = TryFindCharacter(sourceId); if ((Object)(object)val != (Object)null) { if (val.IsPlayer()) { return sourceIsPlayer = (Object)(object)instance != (Object)null && !instance.IsServer() && val.GetZDOID() == sourceId; } if (!val.IsTamed()) { return (int)val.GetFaction() == 11; } return true; } if (ZDOMan.instance == null) { return false; } ZDO zDO = ZDOMan.instance.GetZDO(sourceId); if (zDO == null) { return false; } if (zDO.GetBool(ZDOVars.s_tamed, false)) { return true; } GameObject prefab = ZNetScene.instance.GetPrefab(zDO.GetPrefab()); Character val2 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Character>() : null); if ((Object)(object)val2 != (Object)null && !val2.IsPlayer()) { return (int)val2.GetFaction() == 11; } return false; } private static Character? TryFindCharacter(ZDOID sourceId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (sourceId == ZDOID.None || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject val = ZNetScene.instance.FindInstance(sourceId); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent<Character>(); } } internal static class LuckyCompat { private enum LifecyclePhase { Idle, Installing, Installed, CleanupPending } private sealed class LuckyDropRollState { private readonly List<DropFieldSnapshot> snapshots = new List<DropFieldSnapshot>(); private bool restored; internal void Capture(Drop drop) { snapshots.Add(new DropFieldSnapshot(drop)); } internal void Restore() { if (!restored) { for (int num = snapshots.Count - 1; num >= 0; num--) { snapshots[num].Restore(); } restored = true; } } } private readonly struct DropFieldSnapshot { private readonly Drop drop; private readonly float chance; private readonly int amountMin; private readonly int amountMax; internal DropFieldSnapshot(Drop drop) { this.drop = drop; chance = drop.m_chance; amountMin = drop.m_amountMin; amountMax = drop.m_amountMax; } internal void Restore() { drop.m_chance = chance; drop.m_amountMin = amountMin; drop.m_amountMax = amountMax; } } private sealed class OriginalPatchRegistration { internal string Owner { get; } private int Priority { get; } private string[] Before { get; } private string[] After { get; } private bool Debug { get; } private string? DebugEmitPath { get; } private bool WrapTryCatch { get; } internal OriginalPatchRegistration(Patch patch) { if (string.IsNullOrWhiteSpace(patch.owner)) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix has no Harmony owner."); } Owner = patch.owner; Priority = patch.priority; Before = patch.before?.ToArray() ?? Array.Empty<string>(); After = patch.after?.ToArray() ?? Array.Empty<string>(); Debug = patch.debug; DebugEmitPath = patch.debugEmitPath; WrapTryCatch = patch.wrapTryCatch; } internal HarmonyMethod CreateHarmonyMethod(MethodInfo patchMethod) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown return new HarmonyMethod(patchMethod) { priority = Priority, before = Before.ToArray(), after = After.ToArray(), debug = Debug, debugEmitPath = DebugEmitPath, wrapTryCatch = WrapTryCatch }; } internal bool MatchesMetadata(Patch patch) { if (patch.priority == Priority && (patch.before ?? Array.Empty<string>()).SequenceEqual(Before) && (patch.after ?? Array.Empty<string>()).SequenceEqual(After) && patch.debug == Debug && patch.debugEmitPath == DebugEmitPath) { return patch.wrapTryCatch == WrapTryCatch; } return false; } } private const string HarmonyOwner = "sighsorry.jewelcraftingkarmadrops.luckycompat"; private const string JewelcraftingGuid = "org.bepinex.plugins.jewelcrafting"; private const string DropNSpawnHarmonyOwner = "sighsorry.DropNSpawn"; private const string LuckyZdoPrefix = "Jewelcrafting Lucky "; private const string TooltipMarker = "$jc_lucky_luck:"; private const string DamageDoneTooltipMarker = "$jc_lucky_damage_done:"; private const string DamageReceivedTooltipMarker = "$jc_lucky_damage_received:"; private const float OriginalMaximumDropBonusPercent = 250f; private const float OriginalMaximumDamageDealtReductionPercent = 80f; private const float OriginalMaximumDamageTakenIncreasePercent = 300f; private static readonly Version minimumCompatibleJewelcraftingVersion = new Version(2, 0, 1); private static readonly Version latestVerifiedJewelcraftingVersion = new Version(2, 0, 5); private static readonly HashSet<string> emptyBlacklist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly object lifecycleLock = new object(); private static Harmony? harmony; private static LifecyclePhase lifecyclePhase; private static MethodInfo[] auxiliaryPatchTargets = Array.Empty<MethodInfo>(); private static FieldRef<Character, ZNetView>? characterNetView; private static MethodInfo? characterOnDeathTarget; private static MethodInfo? originalKillPostfixTarget; private static MethodInfo? replacementKillPostfixTarget; private static OriginalPatchRegistration? originalKillPatchRegistration; private static bool criticalWrapperUncertain; private static HashSet<string> dropPrefabBlacklist = emptyBlacklist; private static bool blacklistHandlerRegistered; internal static void TryInstall(ManualLogSource logger) { lock (lifecycleLock) { TryInstallLocked(logger); } } private static void TryInstallLocked(ManualLogSource logger) { //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Expected O, but got Unknown //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) if (lifecyclePhase == LifecyclePhase.Installed || lifecyclePhase == LifecyclePhase.Installing) { return; } if (lifecyclePhase == LifecyclePhase.CleanupPending) { RemoveBlacklistHandler(); dropPrefabBlacklist = emptyBlacklist; if (!CleanupPatches(logger)) { logger.LogWarning((object)"Lucky compatibility cleanup is still pending; a second installation was not attempted."); return; } lifecyclePhase = LifecyclePhase.Idle; characterNetView = null; } try { Version version = ValidateSupportedJewelcraftingVersion(); MethodInfo methodInfo = RequireMethod(AccessTools.Inner(typeof(Lucky), "TrackCreatureKills") ?? throw new TypeLoadException("Jewelcrafting.GemEffects.Lucky+TrackCreatureKills was not found."), "Postfix", typeof(void), typeof(Character)); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(Character), "OnDeath", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(Character).FullName, "OnDeath"); MethodInfo methodInfo3 = RequireMethod(typeof(CharacterDrop), "GenerateDropList", typeof(List<KeyValuePair<GameObject, int>>)); MethodInfo methodInfo4 = RequireMethod(typeof(Lucky), "Chance", typeof(float), typeof(Character), typeof(string)); MethodInfo methodInfo5 = RequireMethod(typeof(Lucky), "ChanceMultiplier", typeof(float), typeof(Player)); MethodInfo methodInfo6 = RequireMethod(typeof(Lucky), "DamageDoneMultiplier", typeof(float)); MethodInfo methodInfo7 = RequireMethod(typeof(Lucky), "DamageReceivedMultiplier", typeof(float)); MethodInfo methodInfo8 = RequireMethod(typeof(Lucky), "GetTooltipString", typeof(string)); MethodInfo methodInfo9 = RequirePatchMethod("ReplaceTrackCreatureKills"); ValidateOriginalKillPostfixIsUnpatched(methodInfo); ValidateOriginalKillPostfixContract(methodInfo, methodInfo5); characterNetView = AccessTools.FieldRefAccess<Character, ZNetView>("m_nview"); OriginalPatchRegistration originalPatchRegistration = CaptureOriginalKillPatch(methodInfo2, methodInfo, methodInfo9); if (originalPatchRegistration.Owner == "sighsorry.jewelcraftingkarmadrops.luckycompat") { throw new InvalidOperationException("Jewelcrafting's Lucky postfix unexpectedly uses the compatibility Harmony owner."); } ValidateConfiguration(); RefreshDropPrefabBlacklist(); lifecyclePhase = LifecyclePhase.Installing; characterOnDeathTarget = methodInfo2; originalKillPostfixTarget = methodInfo; replacementKillPostfixTarget = methodInfo9; originalKillPatchRegistration = originalPatchRegistration; criticalWrapperUncertain = false; auxiliaryPatchTargets = new MethodInfo[6] { methodInfo3, methodInfo4, methodInfo5, methodInfo6, methodInfo7, methodInfo8 }; Harmony val = new Harmony("sighsorry.jewelcraftingkarmadrops.luckycompat"); harmony = val; val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, PatchMethod("ApplyDropBonusCap"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo5, PatchMethod("RejectMissingPlayerChanceMultiplier"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, PatchMethod("ApplyDamageDealtReductionCap"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, PatchMethod("ApplyDamageTakenIncreaseCap"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, PatchMethod("DisplayCurrentAndMaximumInItemTooltip"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo3, DropRollPatchMethod("ApplyLuckyToCurrentDropRoll"), (HarmonyMethod)null, (HarmonyMethod)null, DropRollPatchMethod("RestoreDropsAfterCurrentRoll"), (HarmonyMethod)null); ValidateCriticalStateBeforeReplacement(methodInfo2, methodInfo, methodInfo9, originalPatchRegistration); Harmony val2 = new Harmony(originalPatchRegistration.Owner); criticalWrapperUncertain = true; val2.Unpatch((MethodBase)methodInfo2, methodInfo); criticalWrapperUncertain = false; criticalWrapperUncertain = true; val2.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, originalPatchRegistration.CreateHarmonyMethod(methodInfo9), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); criticalWrapperUncertain = false; ValidateInstalledPatches(methodInfo2, methodInfo, originalPatchRegistration, methodInfo3, methodInfo4, methodInfo5, methodInfo6, methodInfo7, methodInfo8); Plugin.LuckyDropPrefabBlacklist.SettingChanged += OnBlacklistSettingChanged; blacklistHandlerRegistered = true; lifecyclePhase = LifecyclePhase.Installed; if (version.CompareTo(latestVerifiedJewelcraftingVersion) > 0) { logger.LogWarning((object)($"Jewelcrafting {version} is newer than the directly verified " + $"{latestVerifiedJewelcraftingVersion}; its Lucky runtime contract matched, " + "so forward-compatible Lucky support was enabled.")); } logger.LogInfo((object)"Applied Lucky caps, item tooltip, tame-drop, creature-prefab, drop-blacklist, and current-roll drop compatibility patches."); } catch (Exception arg) { lifecyclePhase = LifecyclePhase.CleanupPending; RemoveBlacklistHandler(); dropPrefabBlacklist = emptyBlacklist; if (CleanupPatches(logger)) { lifecyclePhase = LifecyclePhase.Idle; characterNetView = null; } logger.LogWarning((object)$"Lucky compatibility patch was not applied; Karma Drops remains active: {arg}"); } } internal static void Shutdown(ManualLogSource? logger = null) { lock (lifecycleLock) { RemoveBlacklistHandler(); dropPrefabBlacklist = emptyBlacklist; if (lifecyclePhase != LifecyclePhase.Idle || harmony != null || HasCriticalPatchState()) { lifecyclePhase = LifecyclePhase.CleanupPending; if (CleanupPatches(logger)) { lifecyclePhase = LifecyclePhase.Idle; characterNetView = null; } else if (logger != null) { logger.LogWarning((object)"Lucky compatibility cleanup remains pending and will be retried on the next shutdown call."); } } } } private static Version ValidateSupportedJewelcraftingVersion() { if (!Chainloader.PluginInfos.TryGetValue("org.bepinex.plugins.jewelcrafting", out var value)) { throw new InvalidOperationException("Jewelcrafting plugin metadata was not available for Lucky compatibility validation."); } Version version = value.Metadata.Version; if (version.CompareTo(minimumCompatibleJewelcraftingVersion) < 0) { throw new NotSupportedException("Lucky compatibility requires Jewelcrafting " + $"{minimumCompatibleJewelcraftingVersion} or newer; found {version}."); } return version; } private static void ValidateOriginalKillPostfixIsUnpatched(MethodInfo originalKillPostfix) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)originalKillPostfix); Patch[] array = ((patchInfo == null) ? Array.Empty<Patch>() : patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers) .Concat(patchInfo.ILManipulators) .ToArray()); if (array.Length == 0) { return; } string text = string.Join(", ", from owner in (from patch in array select patch.owner into owner where !string.IsNullOrWhiteSpace(owner) select owner).Distinct() orderby owner select owner); throw new InvalidOperationException("Jewelcrafting's private Lucky creature-kill postfix is already patched" + ((text.Length == 0) ? "." : (" by: " + text + "."))); } private static void ValidateOriginalKillPostfixContract(MethodInfo originalKillPostfix, MethodInfo chanceMultiplier) { if (!originalKillPostfix.IsStatic) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix is no longer static."); } FieldInfo lastHit = RequireField(typeof(Character), "m_lastHit"); FieldInfo netView = RequireField(typeof(Character), "m_nview"); FieldInfo dropList = RequireField(typeof(CharacterDrop), "m_drops"); FieldInfo chance = RequireField(typeof(Drop), "m_chance"); FieldInfo amountMin = RequireField(typeof(Drop), "m_amountMin"); FieldInfo amountMax = RequireField(typeof(Drop), "m_amountMax"); MethodInfo getAttacker = RequireMethod(typeof(HitData), "GetAttacker", typeof(Character)); MethodInfo invokeRpc = RequireMethod(typeof(ZNetView), "InvokeRPC", typeof(void), typeof(string), typeof(object[])); MethodInfo floorToInt = RequireMethod(typeof(Mathf), "FloorToInt", typeof(int), typeof(float)); MethodInfo randomValue = AccessTools.PropertyGetter(typeof(Random), "value") ?? throw new MissingMethodException(typeof(Random).FullName, "get_value"); List<CodeInstruction> originalInstructions = PatchProcessor.GetOriginalInstructions((MethodBase)originalKillPostfix, (ILGenerator)null); if (originalInstructions.Count == 0) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix has no readable IL body."); } int num = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsParameterlessGetComponent<CharacterDrop>(instruction.operand), "GetComponent<CharacterDrop>() call"); int num2 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, lastHit), "Character.m_lastHit load"); int num3 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => Calls(instruction, getAttacker), "HitData.GetAttacker() call"); int num4 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Isinst && object.Equals(instruction.operand, typeof(Player)), "Player type test"); int num5 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => Calls(instruction, chanceMultiplier), "Lucky.ChanceMultiplier(Player) call"); int num6 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, netView), "Character.m_nview load"); int num7 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && object.Equals(instruction.operand, "Jewelcrafting Lucky"), "Lucky RPC name"); int num8 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsEmptyObjectArrayCall(instruction.operand), "Array.Empty<object>() call"); int num9 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => Calls(instruction, invokeRpc), "ZNetView.InvokeRPC(string, object[]) call"); int num10 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, dropList), "CharacterDrop.m_drops load"); int num11 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => Calls(instruction, floorToInt), "Mathf.FloorToInt(float) call"); int num12 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => Calls(instruction, randomValue), "UnityEngine.Random.value read"); int num13 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsDropListEnumeratorCall(instruction, "GetEnumerator"), "CharacterDrop.Drop list GetEnumerator() call"); int num14 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsDropEnumeratorCall(instruction, "get_Current", typeof(Drop)), "CharacterDrop.Drop enumerator Current read"); int num15 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsDropEnumeratorCall(instruction, "MoveNext", typeof(bool)), "CharacterDrop.Drop enumerator MoveNext() call"); int num16 = RequireSingleInstruction(originalInstructions, (CodeInstruction instruction) => IsInterfaceDisposeCall(instruction), "CharacterDrop.Drop enumerator Dispose() call"); int[] array = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Cgt, 2, "greater-than comparisons"); int[] array2 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, chance), 4, "CharacterDrop.Drop.m_chance loads"); int[] array3 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Stfld && object.Equals(instruction.operand, chance), 2, "CharacterDrop.Drop.m_chance writes"); int[] array4 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, amountMin), 1, "CharacterDrop.Drop.m_amountMin loads"); int[] array5 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Stfld && object.Equals(instruction.operand, amountMin), 1, "CharacterDrop.Drop.m_amountMin writes"); int[] array6 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Ldfld && object.Equals(instruction.operand, amountMax), 1, "CharacterDrop.Drop.m_amountMax loads"); int[] array7 = RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Stfld && object.Equals(instruction.operand, amountMax), 1, "CharacterDrop.Drop.m_amountMax writes"); RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Mul, 3, "drop amount/chance multiplications"); RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Stfld || instruction.opcode == OpCodes.Stsfld, 4, "total field writes"); RequireInstructionCount(originalInstructions, (CodeInstruction instruction) => instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt, 11, "total method calls"); int[] array8 = new int[23] { num, num2, num3, num4, num5, array[0], num6, num7, num8, num9, num10, num13, num14, array3[0], array2[1], array[1], num11, num12, array5[0], array7[0], array3[1], num15, num16 }; if (array8.Zip(array8.Skip(1), (int left, int right) => left < right).Any((bool inOrder) => !inOrder)) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix changed its core execution order."); } if (array2[0] >= array3[0] || array3[0] >= array2[1] || array4[0] >= array5[0] || array6[0] >= array7[0]) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix changed its drop field read/write shape."); } if (!PreviousMeaningfulInstructionIs(originalInstructions, array3[0], OpCodes.Mul) || !PreviousMeaningfulInstructionIs(originalInstructions, array5[0], OpCodes.Mul) || !PreviousMeaningfulInstructionIs(originalInstructions, array7[0], OpCodes.Mul) || !PreviousMeaningfulInstructionLoadsOne(originalInstructions, array[0]) || !PreviousMeaningfulInstructionLoadsOne(originalInstructions, array[1]) || !PreviousMeaningfulInstructionLoadsOne(originalInstructions, array3[1])) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix changed its drop mutation semantics."); } } private static FieldInfo RequireField(Type type, string fieldName) { return AccessTools.DeclaredField(type, fieldName) ?? throw new MissingFieldException(type.FullName, fieldName); } private static int RequireSingleInstruction(IReadOnlyList<CodeInstruction> instructions, Func<CodeInstruction, bool> predicate, string description) { return RequireInstructionCount(instructions, predicate, 1, description)[0]; } private static int[] RequireInstructionCount(IReadOnlyList<CodeInstruction> instructions, Func<CodeInstruction, bool> predicate, int expectedCount, string description) { int[] array = (from index in Enumerable.Range(0, instructions.Count) where predicate(instructions[index]) select index).ToArray(); if (array.Length != expectedCount) { throw new InvalidOperationException($"Jewelcrafting's Lucky creature-kill postfix expected {expectedCount} " + $"{description}, but found {array.Length}."); } return array; } private static bool Calls(CodeInstruction instruction, MethodInfo method) { if (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) { return object.Equals(instruction.operand, method); } return false; } private static bool IsParameterlessGetComponent<T>(object? operand) { if (operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(Component) && methodInfo.Name == "GetComponent" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().SequenceEqual(new Type[1] { typeof(T) })) { return methodInfo.GetParameters().Length == 0; } return false; } private static bool IsEmptyObjectArrayCall(object? operand) { if (operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(Array) && methodInfo.Name == "Empty" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().SequenceEqual(new Type[1] { typeof(object) })) { return methodInfo.GetParameters().Length == 0; } return false; } private static bool IsDropListEnumeratorCall(CodeInstruction instruction, string methodName) { if ((instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && instruction.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(List<Drop>) && methodInfo.Name == methodName && methodInfo.ReturnType == typeof(List<Drop>.Enumerator)) { return methodInfo.GetParameters().Length == 0; } return false; } private static bool IsDropEnumeratorCall(CodeInstruction instruction, string methodName, Type returnType) { if ((instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && instruction.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(List<Drop>.Enumerator) && methodInfo.Name == methodName && methodInfo.ReturnType == returnType) { return methodInfo.GetParameters().Length == 0; } return false; } private static bool IsInterfaceDisposeCall(CodeInstruction instruction) { if ((instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && instruction.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(IDisposable) && methodInfo.Name == "Dispose" && methodInfo.ReturnType == typeof(void)) { return methodInfo.GetParameters().Length == 0; } return false; } private static bool PreviousMeaningfulInstructionIs(IReadOnlyList<CodeInstruction> instructions, int index, OpCode expectedOpcode) { int num = PreviousMeaningfulInstructionIndex(instructions, index); if (num >= 0) { return instructions[num].opcode == expectedOpcode; } return false; } private static bool PreviousMeaningfulInstructionLoadsOne(IReadOnlyList<CodeInstruction> instructions, int index) { int num = PreviousMeaningfulInstructionIndex(instructions, index); if (num < 0) { return false; } CodeInstruction val = instructions[num]; if (val.opcode == OpCodes.Ldc_R4 && val.operand is float num2) { return Math.Abs(num2 - 1f) < float.Epsilon; } return false; } private static int PreviousMeaningfulInstructionIndex(IReadOnlyList<CodeInstruction> instructions, int index) { for (int num = index - 1; num >= 0; num--) { if (instructions[num].opcode != OpCodes.Nop) { return num; } } return -1; } private static MethodInfo RequireMethod(Type type, string methodName, Type returnType, params Type[] argumentTypes) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, methodName, argumentTypes, (Type[])null) ?? throw new MissingMethodException(type.FullName, methodName); if (methodInfo.ReturnType != returnType) { throw new MissingMethodException(type.FullName + "." + methodName + " has return type " + methodInfo.ReturnType.FullName + "; expected " + returnType.FullName + "."); } return methodInfo; } private static HarmonyMethod PatchMethod(string methodName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown return new HarmonyMethod(RequirePatchMethod(methodName)); } private static HarmonyMethod DropRollPatchMethod(string methodName) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(RequirePatchMethod(methodName)); val.priority = 0; val.after = new string[1] { "sighsorry.DropNSpawn" }; return val; } private static MethodInfo RequirePatchMethod(string methodName) { return AccessTools.DeclaredMethod(typeof(LuckyCompat), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(LuckyCompat).FullName, methodName); } private static OriginalPatchRegistration CaptureOriginalKillPatch(MethodInfo characterOnDeath, MethodInfo originalKillPostfix, MethodInfo replacementKillPostfix) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)characterOnDeath); Patch[] array = patchInfo?.Postfixes.Where((Patch patch) => patch.PatchMethod == originalKillPostfix).ToArray() ?? Array.Empty<Patch>(); if (array.Length != 1) { throw new InvalidOperationException("Expected exactly one Jewelcrafting Lucky creature-kill postfix on " + $"Character.OnDeath, but found {array.Length}."); } if (patchInfo.Postfixes.Any((Patch patch) => patch.PatchMethod == replacementKillPostfix)) { throw new InvalidOperationException("The Lucky replacement postfix is already installed on Character.OnDeath."); } return new OriginalPatchRegistration(array[0]); } private static void ValidateCriticalStateBeforeReplacement(MethodInfo characterOnDeath, MethodInfo originalKillPostfix, MethodInfo replacementKillPostfix, OriginalPatchRegistration originalRegistration) { ValidateOriginalKillPostfixIsUnpatched(originalKillPostfix); Patch[] array = FindPostfixes(characterOnDeath, originalKillPostfix); if (array.Length != 1 || array[0].owner != originalRegistration.Owner || !originalRegistration.MatchesMetadata(array[0])) { throw new InvalidOperationException("Jewelcrafting's Lucky creature-kill postfix changed before replacement."); } if (FindPostfixes(characterOnDeath, replacementKillPostfix).Length != 0) { throw new InvalidOperationException("The Lucky replacement postfix appeared before installation completed."); } } private static void ValidateInstalledPatches(MethodInfo characterOnDeath, MethodInfo originalKillPostfix, OriginalPatchRegistration originalRegistration, MethodInfo generateDropList, MethodInfo chance, MethodInfo chanceMultiplier, MethodInfo damageDoneMultiplier, MethodInfo damageReceivedMultiplier, MethodInfo tooltip) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)characterOnDeath); if (patchInfo != null && patchInfo.Postfixes.Any((Patch val) => val.PatchMethod == originalKillPostfix)) { throw new InvalidOperationException("Jewelcrafting's original Lucky creature-kill postfix remained installed."); } Patch patch = RequireOwnedPatch(characterOnDeath, RequirePatchMethod("ReplaceTrackCreatureKills"), prefix: false, originalRegistration.Owner); if (!originalRegistration.MatchesMetadata(patch)) { throw new InvalidOperationException("The Lucky replacement postfix did not retain Jewelcrafting's original patch metadata."); } ValidateDropRollPatchOrdering(RequireOwnedPatch(generateDropList, RequirePatchMethod("ApplyLuckyToCurrentDropRoll"), prefix: true, "sighsorry.jewelcraftingkarmadrops.luckycompat"), "prefix"); ValidateDropRollPatchOrdering(RequireOwnedFinalizer(generateDropList, RequirePatchMethod("RestoreDropsAfterCurrentRoll"), "sighsorry.jewelcraftingkarmadrops.luckycompat"), "finalizer"); RequireOwnedPatch(chance, RequirePatchMethod("ApplyDropBonusCap"), prefix: false, "sighsorry.jewelcraftingkarmadrops.luckycompat"); RequireOwnedPatch(chanceMultiplier, RequirePatchMethod("RejectMissingPlayerChanceMultiplier"), prefix: true, "sighsorry.jewelcraftingkarmadrops.luckycompat"); RequireOwnedPatch(damageDoneMultiplier, RequirePatchMethod("ApplyDamageDealtReductionCap"), prefix: false, "sighsorry.jewelcraftingkarmadrops.luckycompat"); RequireOwnedPatch(damageReceivedMultiplier, RequirePatchMethod("ApplyDamageTakenIncreaseCap"), prefix: false, "sighsorry.jewelcraftingkarmadrops.luckycompat"); RequireOwnedPatch(tooltip, RequirePatchMethod("DisplayCurrentAndMaximumInItemTooltip"), prefix: false, "sighsorry.jewelcraftingkarmadrops.luckycompat"); } private static Patch RequireOwnedPatch(MethodBase target, MethodInfo patchMethod, bool prefix, string owner) { Patches patchInfo = Harmony.GetPatchInfo(target); object source; if (!prefix) { IEnumerable<Patch> enumerable = patchInfo?.Postfixes; source = enumerable ?? Enumerable.Empty<Patch>(); } else { IEnumerable<Patch> enumerable = patchInfo?.Prefixes; source = enumerable ?? Enumerable.Empty<Patch>(); } Patch[] array = ((IEnumerable<Patch>)source).Where((Patch patch) => patch.PatchMethod == patchMethod).ToArray(); if (array.Length != 1 || array[0].owner != owner) { throw new InvalidOperationException("Harmony did not retain exactly one " + owner + " patch " + patchMethod.Name + " on " + target.DeclaringType?.FullName + "." + target.Name + "."); } return array[0]; } private static Patch RequireOwnedFinalizer(MethodBase target, MethodInfo patchMethod, string owner) { Patch[] array = Harmony.GetPatchInfo(target)?.Finalizers.Where((Patch patch) => patch.PatchMethod == patchMethod).ToArray() ?? Array.Empty<Patch>(); if (array.Length != 1 || array[0].owner != owner) { throw new InvalidOperationException("Harmony did not retain exactly one " + owner + " finalizer " + patchMethod.Name + " on " + target.DeclaringType?.FullName + "." + target.Name + "."); } return array[0]; } private static void ValidateDropRollPatchOrdering(Patch patch, string patchKind) { if (patch.priority != 0 || !(patch.after ?? Array.Empty<string>()).Contains("sighsorry.DropNSpawn")) { throw new InvalidOperationException("The Lucky current-roll " + patchKind + " did not retain its DropNSpawn-late ordering metadata."); } } private static bool CleanupPatches(ManualLogSource? logger) { if (!ReconcileCriticalKillPatch(logger)) { return false; } return RemoveAuxiliaryPatches(logger); } private static bool ReconcileCriticalKillPatch(ManualLogSource? logger) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (!HasCriticalPatchState()) { return true; } MethodInfo methodInfo = characterOnDeathTarget; MethodInfo methodInfo2 = originalKillPostfixTarget; MethodInfo methodInfo3 = replacementKillPostfixTarget; OriginalPatchRegistration originalPatchRegistration = originalKillPatchRegistration; if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || originalPatchRegistration == null) { if (logger != null) { logger.LogWarning((object)"Cannot reconcile Jewelcrafting's Lucky creature-kill postfix because its saved registration is incomplete."); } return false; } try { Patch[] array = FindPostfixes(methodInfo, methodInfo2); if (array.Length != 0 && (array.Length != 1 || array[0].owner != originalPatchRegistration.Owner || !originalPatchRegistration.MatchesMetadata(array[0]))) { if (logger != null) { logger.LogWarning((object)"Did not alter Lucky creature-kill patches because the current original registration conflicts with the saved registration."); } return false; } if (FindPostfixes(methodInfo, methodInfo3).Length != 0) { criticalWrapperUncertain = true; try { new Harmony(originalPatchRegistration.Owner).Unpatch((MethodBase)methodInfo, methodInfo3); criticalWrapperUncertain = false; } catch (Exception arg) { if (logger != null) { logger.LogWarning((object)$"Could not remove the exact Lucky replacement postfix: {arg}"); } } } if (FindPostfixes(methodInfo, methodInfo3).Length != 0) { if (logger != null) { logger.LogWarning((object)"The Lucky replacement postfix is still registered; the original postfix was not restored alongside it."); } return false; } Patch[] array2 = FindPostfixes(methodInfo, methodInfo2); if (array2.Length == 0) { criticalWrapperUncertain = true; try { new Harmony(originalPatchRegistration.Owner).Patch((MethodBase)methodInfo, (HarmonyMethod)null, originalPatchRegistration.CreateHarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); criticalWrapperUncertain = false; } catch (Exception arg2) { if (logger != null) { logger.LogWarning((object)$"Could not restore Jewelcrafting's Lucky creature-kill postfix: {arg2}"); } } } array2 = FindPostfixes(methodInfo, methodInfo2); if (array2.Length != 1 || array2[0].owner != originalPatchRegistration.Owner || !originalPatchRegistration.MatchesMetadata(array2[0])) { if (logger != null) { logger.LogWarning((object)"Jewelcrafting's Lucky creature-kill postfix is not restored exactly once with its saved metadata."); } return false; } if (criticalWrapperUncertain) { try { new Harmony(originalPatchRegistration.Owner).Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); criticalWrapperUncertain = false; } catch (Exception arg3) { if (logger != null) { logger.LogWarning((object)$"Could not rebuild Character.OnDeath after a Lucky patch mutation failure: {arg3}"); } return false; } } array2 = FindPostfixes(methodInfo, methodInfo2); if (FindPostfixes(methodInfo, methodInfo3).Length != 0 || array2.Length != 1 || array2[0].owner != originalPatchRegistration.Owner || !originalPatchRegistration.MatchesMetadata(array2[0])) { if (logger != null) { logger.LogWarning((object)"Character.OnDeath did not settle into the expected original-only Lucky patch state."); } return false; } ClearCriticalPatchState(); return true; } catch (Exception arg4) { if (logger != null) { logger.LogWarning((object)$"Could not reconcile Jewelcrafting's Lucky creature-kill postfix: {arg4}"); } return false; } } private static bool RemoveAuxiliaryPatches(ManualLogSource? logger) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) try { if (Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.luckycompat") && auxiliaryPatchTargets.Length == 0) { if (logger != null) { logger.LogWarning((object)"Cannot safely rebuild Lucky auxiliary wrappers because their saved targets are missing."); } return false; } Harmony val = (harmony = (Harmony?)(((object)harmony) ?? ((object)new Harmony("sighsorry.jewelcraftingkarmadrops.luckycompat")))); try { val.UnpatchSelf(); } catch (Exception arg) { if (logger != null) { logger.LogWarning((object)$"Could not fully remove Lucky auxiliary patches by owner: {arg}"); } } bool flag = true; foreach (MethodInfo item in auxiliaryPatchTargets.Distinct()) { try { val.Patch((MethodBase)item, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception arg2) { flag = false; if (logger != null) { logger.LogWarning((object)$"Could not rebuild Lucky auxiliary target {item.DeclaringType?.FullName}.{item.Name}: {arg2}"); } } } if (!flag || Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.luckycompat")) { if (logger != null) { logger.LogWarning((object)"Lucky auxiliary patches are not fully removed; cleanup state was retained for retry."); } return false; } auxiliaryPatchTargets = Array.Empty<MethodInfo>(); harmony = null; return true; } catch (Exception arg3) { if (logger != null) { logger.LogWarning((object)$"Could not verify Lucky auxiliary patch cleanup: {arg3}"); } return false; } } private static Patch[] FindPostfixes(MethodBase target, MethodInfo patchMethod) { return Harmony.GetPatchInfo(target)?.Postfixes.Where((Patch patch) => patch.PatchMethod == patchMethod).ToArray() ?? Array.Empty<Patch>(); } private static bool HasCriticalPatchState() { if (!(characterOnDeathTarget != null) && !(originalKillPostfixTarget != null) && !(replacementKillPostfixTarget != null) && originalKillPatchRegistration == null) { return criticalWrapperUncertain; } return true; } private static void ClearCriticalPatchState() { characterOnDeathTarget = null; originalKillPostfixTarget = null; replacementKillPostfixTarget = null; originalKillPatchRegistration = null; criticalWrapperUncertain = false; } private static void ValidateConfiguration() { _ = Plugin.MaximumLuckyDropBonusPercent.Value; _ = Plugin.MaximumLuckyDamageDealtReductionPercent.Value; _ = Plugin.MaximumLuckyDamageTakenIncreasePercent.Value; _ = Plugin.LuckyDropPrefabBlacklist.Value; } private static void ReplaceTrackCreatureKills(Character __instance) { if (!Object.op_Implicit((Object)(object)__instance) || __instance.IsTamed() || !Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<CharacterDrop>())) { return; } HitData? lastHit = ValheimPrivateFields.GetLastHit(__instance); Character obj = ((lastHit != null) ? lastHit.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); if (val != null && HasActiveLuckyEffect(val)) { ZNetView? netView = GetNetView((Character?)(object)val); if (netView != null) { netView.InvokeRPC("Jewelcrafting Lucky", Array.Empty<object>()); } } } private static void ApplyLuckyToCurrentDropRoll(CharacterDrop __instance, out LuckyDropRollState? __state) { __state = null; if (!Object.op_Implicit((Object)(object)__instance) || __instance.m_drops == null) { return; } Character character = ValheimPrivateFields.GetCharacter(__instance); if (!Object.op_Implicit((Object)(object)character) || character.IsTamed() || !JewelcraftingDropGate.IsDeathInProgress(character)) { return; } HitData? lastHit = ValheimPrivateFields.GetLastHit(character); Character obj = ((lastHit != null) ? lastHit.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); if (val == null) { return; } float num = Lucky.ChanceMultiplier(val); if (num <= 1f) { return; } LuckyDropRollState luckyDropRollState = (__state = new LuckyDropRollState()); foreach (Drop drop in __instance.m_drops) { if (!CanMultiplyDrop(drop)) { continue; } luckyDropRollState.Capture(drop); drop.m_chance *= num; if (drop.m_chance > 1f) { int num2 = Mathf.FloorToInt(drop.m_chance); if (Random.value < drop.m_chance - (float)num2) { num2++; } drop.m_amountMin *= num2; drop.m_amountMax *= num2; drop.m_chance = 1f; } } } private static Exception? RestoreDropsAfterCurrentRoll(LuckyDropRollState? __state, Exception? __exception) { __state?.Restore(); return __exception; } private static bool RejectMissingPlayerChanceMultiplier(Player? __0, ref float __result) { if (Object.op_Implicit((Object)(object)__0)) { return true; } __result = 1f; return false; } private static void ApplyDropBonusCap(ref float __result) { __result = Mathf.Min(__result, DropBonusCap()); } private static void ApplyDamageDealtReductionCap(ref float __result) { __result = Mathf.Max(__result, 1f - DamageDealtReductionCap()); } private static void ApplyDamageTakenIncreaseCap(ref float __result) { __result = Mathf.Min(__result, 1f + DamageTakenIncreaseCap()); } private static void DisplayCurrentAndMaximumInItemTooltip(Lucky __instance, ref string __result) { if (Object.op_Implicit((Object)(object)__instance) && !((Object)(object)((StatusEffect)__instance).m_character != (Object)null) && __result.LastIndexOf("$jc_lucky_luck:", StringComparison.Ordinal) >= 0 && __result.IndexOf("$jc_lucky_damage_done:", StringComparison.Ordinal) >= 0 && __result.IndexOf("$jc_lucky_damage_received:", StringComparison.Ordinal) >= 0) { Player localPlayer = Player.m_localPlayer; int luckyStacks = GetLuckyStacks(localPlayer, ((Object)__instance).name); int num = Lucky.MaxStacks((Character)(object)localPlayer); float value = Mathf.Min((float)Mathf.Min(luckyStacks, num) * 0.02f, DropBonusCap()); float value2 = Mathf.Min((float)num * 0.02f, DropBonusCap()); float value3 = Mathf.Min((float)luckyStacks * 0.01f, DamageDealtReductionCap()); float value4 = DamageDealtReductionCap(); float value5 = Mathf.Min((float)luckyStacks * 0.03f, DamageTakenIncreaseCap()); float value6 = DamageTakenIncreaseCap(); __result = ReplaceTooltipLine(__result, "$jc_lucky_luck:", "$jc_lucky_luck: <color=orange>+" + FormatPercent(value) + " / +" + FormatPercent(value2) + "</color>"); __result = ReplaceTooltipLine(__result, "$jc_lucky_damage_done:", "$jc_lucky_damage_done: <color=orange>-" + FormatPercent(value3) + " / -" + FormatPercent(value4) + "</color>"); __result = ReplaceTooltipLine(__result, "$jc_lucky_damage_received:", "$jc_lucky_damage_received: <color=orange>+" + FormatPercent(value5) + " / +" + FormatPercent(value6) + "</color>"); } } private static string ReplaceTooltipLine(string tooltip, string marker, string replacement) { int num = tooltip.IndexOf(marker, StringComparison.Ordinal); if (num < 0) { return tooltip; } int num2 = tooltip.IndexOf('\n', num); if (num2 < 0) { return tooltip.Substring(0, num) + replacement; } string text = ((num2 > num && tooltip[num2 - 1] == '\r') ? "\r\n" : "\n"); return tooltip.Substring(0, num) + replacement + text + tooltip.Substring(num2 + 1); } private static string FormatPercent(float value) { return value.ToString("0.##%", CultureInfo.CurrentCulture); } private static bool HasActiveLuckyEffect(Player player) { ZNetView netView = GetNetView((Character?)(object)player); if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)netView)) { return false; } ZDO zdo = netView.GetZDO(); if (zdo != null) { return Lucky.SeNames.Any((string seName) => zdo.GetInt("Jewelcrafting Lucky " + seName, 0) > 0); } return false; } private static int GetLuckyStacks(Player? player, string statusEffectName) { ZNetView netView = GetNetView((Character?)(object)player); if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)netView)) { return 0; } ZDO zDO = netView.GetZDO(); if (zDO != null) { return Math.Max(0, zDO.GetInt("Jewelcrafting Lucky " + statusEffectName, 0)); } return 0; } private static ZNetView? GetNetView(Character? character) { if (!((Object)(object)character == (Object)null) && characterNetView != null) { return characterNetView.Invoke(character); } return null; } private static bool CanMultiplyDrop(Drop? drop) { GameObject val = drop?.m_prefab; if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val.GetComponentInChildren<Character>(true) != (Object)null) { return false; } string name = ((Object)val).name; return !dropPrefabBlacklist.Contains(name); } private static float DropBonusCap() { return ReadPercentCap(Plugin.MaximumLuckyDropBonusPercent.Value, 250f); } private static float DamageDealtReductionCap() { return ReadPercentCap(Plugin.MaximumLuckyDamageDealtReductionPercent.Value, 80f); } private static float DamageTakenIncreaseCap() { return ReadPercentCap(Plugin.MaximumLuckyDamageTakenIncreasePercent.Value, 300f); } private static float ReadPercentCap(float configuredPercent, float originalMaximumPercent) { if (float.IsNaN(configuredPercent)) { configuredPercent = originalMaximumPercent; } return Mathf.Clamp(configuredPercent, 0f, originalMaximumPercent) / 100f; } private static void OnBlacklistSettingChanged(object? sender, EventArgs eventArgs) { RefreshDropPrefabBlacklist(); } private static void RefreshDropPrefabBlacklist() { dropPrefabBlacklist = new HashSet<string>(from entry in (Plugin.LuckyDropPrefabBlacklist.Value ?? string.Empty).Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) select entry.Trim() into entry where entry.Length > 0 select entry, StringComparer.OrdinalIgnoreCase); } private static void RemoveBlacklistHandler() { if (blacklistHandlerRegistered) { Plugin.LuckyDropPrefabBlacklist.SettingChanged -= OnBlacklistSettingChanged; blacklistHandlerRegistered = false; } } } internal static class MagicRepairCompat { private const float RepairInterval = 60f; private const string HarmonyOwner = "sighsorry.jewelcraftingkarmadrops.magicrepaircompat"; internal static void TryInstall(ManualLogSource logger) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown Harmony val = null; try { Type type = AccessTools.TypeByName("Jewelcrafting.GemEffects.MagicRepair") ?? throw new TypeLoadException("Jewelcrafting.GemEffects.MagicRepair was not found."); MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "UpdateStatusEffect", new Type[1] { typeof(float) }, (Type[])null) ?? throw new MissingMethodException(type.FullName, "UpdateStatusEffect"); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(MagicRepairCompat), "BeforeUpdateStatusEffect", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(MagicRepairCompat).FullName, "BeforeUpdateStatusEffect"); val = new Harmony("sighsorry.jewelcraftingkarmadrops.magicrepaircompat"); val.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); logger.LogInfo((object)"Applied the Jewelcrafting Magic Repair lifecycle compatibility patch."); } catch (Exception arg) { if (val != null) { try { val.UnpatchSelf(); } catch (Exception arg2) { logger.LogWarning((object)$"Could not fully remove a partial Magic Repair compatibility patch: {arg2}"); } } logger.LogWarning((object)$"Magic Repair compatibility patch was not applied; Karma Drops remains active: {arg}"); } } private static void BeforeUpdateStatusEffect(StatusEffect __instance, float dt, ref float ___m_tickTimer, GameObject[]? ___m_startEffectInstances) { if (!(___m_tickTimer + dt < 60f)) { Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)__instance.m_character != (Object)(object)localPlayer || ___m_startEffectInstances == null || !Object.op_Implicit((Object)(object)ZNetScene.instance)) { ___m_tickTimer = 0f - dt; } } } } [BepInPlugin("sighsorry.jewelcraftingkarmadrops", "Jewelcrafting Karma Drops", "1.2.0")] [BepInDependency("org.bepinex.plugins.jewelcrafting", "2.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "sighsorry.jewelcraftingkarmadrops"; public const string PluginName = "Jewelcrafting Karma Drops"; public const string PluginVersion = "1.2.0"; private const string JewelcraftingGuid = "org.bepinex.plugins.jewelcrafting"; private static readonly ConfigSync ConfigSynchronizer = new ConfigSync("sighsorry.jewelcraftingkarmadrops") { DisplayName = "Jewelcrafting Karma Drops", CurrentVersion = "1.2.0", MinimumRequiredVersion = "1.2.0", ModRequired = true }; private Harmony? harmony; internal static ConfigEntry<float> MaximumLuckyDropBonusPercent { get; private set; } = null; internal static ConfigEntry<float> MaximumLuckyDamageDealtReductionPercent { get; private set; } = null; internal static ConfigEntry<float> MaximumLuckyDamageTakenIncreasePercent { get; private set; } = null; internal static ConfigEntry<string> LuckyDropPrefabBlacklist { get; private set; } = null; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown try { BindSyncedConfiguration(); ValheimPrivateFields.Validate(); harmony = new Harmony("sighsorry.jewelcraftingkarmadrops"); harmony.PatchAll(typeof(Plugin).Assembly); JewelcraftingDropGate.Register(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Jewelcrafting creature-death loot now requires a CreatureManager Karma-style player-side kill (player, tamed creature, PlayerSpawned creature, or an exact attributed poison/fire/spirit DoT)."); } catch (Exception arg) { CleanupCore("rolling back partial initialization"); ((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Failed to initialize {0}: {1}", "Jewelcrafting Karma Drops", arg)); throw; } TryInstallCompatibility("Magic Repair", delegate { MagicRepairCompat.TryInstall(((BaseUnityPlugin)this).Logger); }); TryInstallCompatibility("Lucky", delegate { LuckyCompat.TryInstall(((BaseUnityPlugin)this).Logger); }); TryInstallCompatibility("socket-limit", delegate { SocketLimitCompat.TryInstall(((BaseUnityPlugin)this).Logger); }); TryInstallCompatibility("synergy YAML", delegate { SynergyYamlCompat.TryInstall(((BaseUnityPlugin)this).Logger); }); } private void OnDestroy() { TryCleanup("Magic Repair compatibility", delegate { Harmony.UnpatchID("sighsorry.jewelcraftingkarmadrops.magicrepaircompat"); }); TryCleanup("Lucky compatibility", delegate { LuckyCompat.Shutdown(((BaseUnityPlugin)this).Logger); }); TryCleanup("socket-limit compatibility", delegate { SocketLimitCompat.Shutdown(((BaseUnityPlugin)this).Logger); }); TryCleanup("synergy YAML compatibility", delegate { SynergyYamlCompat.Shutdown(((BaseUnityPlugin)this).Logger); }); CleanupCore("shutting down"); } private void BindSyncedConfiguration() { ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Lock Configuration", true, "Lock synchronized gameplay settings so that only server administrators can change them."); ConfigSynchronizer.AddLockingConfigEntry<bool>(val); MaximumLuckyDropBonusPercent = BindSynced("2 - Lucky", "Maximum Lucky Drop Bonus (%)", 250f, 0f, 250f, "Maximum drop bonus contributed by each active Lucky status effect."); MaximumLuckyDamageDealtReductionPercent = BindSynced("2 - Lucky", "Maximum Lucky Damage Dealt Reduction (%)", 80f, 0f, 80f, "Maximum outgoing-damage reduction applied by each active Lucky status effect."); MaximumLuckyDamageTakenIncreasePercent = BindSynced("2 - Lucky", "Maximum Lucky Damage Taken Increase (%)", 300f, 0f, 300f, "Maximum incoming-damage increase applied by each active Lucky status effect."); LuckyDropPrefabBlacklist = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Lucky", "Lucky Drop Prefab Blacklist", string.Empty, "Comma-separated internal prefab names whose CharacterDrop entries must not receive the Lucky multiplier. Creature prefabs are always excluded."); ConfigSynchronizer.AddConfigEntry<string>(LuckyDropPrefabBlacklist); } private ConfigEntry<float> BindSynced(string section, string key, float defaultValue, float minimum, float maximum, string description) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown ConfigEntry<float> val = ((BaseUnityPlugin)this).Config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(minimum, maximum), Array.Empty<object>())); ConfigSynchronizer.AddConfigEntry<float>(val); return val; } private void TryInstallCompatibility(string name, Action install) { try { install(); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"{name} compatibility was not installed; Karma Drops remains active: {arg}"); } } private void CleanupCore(string operation) { TryCleanup("Jewelcrafting drop gate", JewelcraftingDropGate.Reset, operation); TryCleanup("core Harmony patches", delegate { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; }, operation); TryCleanup("Karma death attribution", KarmaDeathAttribution.Reset, operation); } private void TryCleanup(string name, Action cleanup, string operation = "shutting down") { try { cleanup(); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Failed to clean up {name} while {operation}: {arg}"); } } } internal static class SocketLimitCompat { private enum PatchResult { None, Patched, AlreadyFixed } private const string HarmonyOwner = "sighsorry.jewelcraftingkarmadrops.socketlimitcompat"; private const int CorrectLastTableLevelIndex = 3; private static PatchResult patchResult; private static bool installed; internal static void TryInstall(ManualLogSource logger) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown if (installed) { return; } Harmony val = null; try { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(GemStones), "CanAddMoreSockets", new Type[1] { typeof(ItemData) }, (Type[])null) ?? throw new MissingMethodException(typeof(GemStones).FullName, "CanAddMoreSockets(ItemDrop.ItemData)"); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(SocketLimitCompat), "FixTableLevelIndexCap", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(SocketLimitCompat).FullName, "FixTableLevelIndexCap"); patchResult = PatchResult.None; val = new Harmony("sighsorry.jewelcraftingkarmadrops.socketlimitcompat"); val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null); switch (patchResult) { case PatchResult.Patched: installed = true; logger.LogInfo((object)"Applied the Jewelcrafting level-4 Gemcutters Table socket-limit compatibility patch."); break; case PatchResult.AlreadyFixed: val.UnpatchSelf(); logger.LogInfo((object)"Jewelcrafting already uses the level-4 Gemcutters Table socket-limit setting; the compatibility patch was not needed."); break; default: throw new InvalidOperationException("The socket-limit transpiler completed without reporting a result."); } } catch (Exception arg) { if (val != null) { try { val.UnpatchSelf(); installed = false; } catch (Exception arg2) { installed = true; logger.LogWarning((object)$"Could not fully remove a partial socket-limit compatibility patch: {arg2}"); } } else { installed = false; } logger.LogWarning((object)$"Socket-limit compatibility patch was not applied; Karma Drops remains active: {arg}"); } } internal static void Shutdown(ManualLogSource logger) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!installed) { return; } try { new Harmony("sighsorry.jewelcraftingkarmadrops.socketlimitcompat").UnpatchSelf(); installed = false; } catch (Exception arg) { logger.LogWarning((object)$"Could not remove the socket-limit compatibility patch during shutdown: {arg}"); } } private static IEnumerable<CodeInstruction> FixTableLevelIndexCap(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); FieldInfo fieldInfo = AccessTools.Field(typeof(Jewelcrafting), "maxSocketsTableLevel") ?? throw new MissingFieldException(typeof(Jewelcrafting).FullName, "maxSocketsTableLevel"); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Math), "Min", new Type[2] { typeof(int), typeof(int) }, (Type[])null) ?? throw new MissingMethodException(typeof(Math).FullName, "Min(Int32, Int32)"); int num = -1; int num2 = -1; for (int i = 0; i < list.Count; i++) { if (!CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false)) { continue; } int num3 = NextNonNop(list, i + 1); if (num3 < 0 || (!CodeInstructionExtensions.LoadsConstant(list[num3], 2L) && !CodeInstructionExtensions.LoadsConstant(list[num3], 3L))) { continue; } bool flag = false; int num4 = Math.Min(list.Count, num3 + 10); for (int j = num3 + 1; j < num4; j++) { if (CodeInstructionExtensions.Calls(list[j], methodInfo)) { flag = true; break; } } if (flag) { if (num >= 0) { throw new InvalidOperationException("Found more than one maxSocketsTableLevel index-cap pattern."); } num = num3; num2 = (CodeInstructionExtensions.LoadsConstant(list[num3], 2L) ? 2 : 3); } } if (num < 0) { throw new InvalidOperationException("Could not find the maxSocketsTableLevel Math.Min index-cap pattern."); } if (num2 == 3) { patchResult = PatchResult.AlreadyFixed; return list; } list[num].opcode = OpCodes.Ldc_I4_3; list[num].operand = null; patchResult = PatchResult.Patched; return list; } private static int NextNonNop(IReadOnlyList<CodeInstruction> codes, int start) { for (int i = start; i < codes.Count; i++) { if (codes[i].opcode != OpCodes.Nop) { return i; } } return -1; } } internal static class SynergyYamlCompat { private enum ProbeState { BugPresent, Fixed, Incompatible } private const string HarmonyOwner = "sighsorry.jewelcraftingkarmadrops.synergyyamlcompat"; private const string ProbeKey = "__karmadrops_synergy_unset_probe_9f67b5c1__"; private static readonly object Sync = new object(); private static Harmony? harmony; private static ManualLogSource? logger; private static bool installed; private static bool warnedPostfixFailure; internal static void TryInstall(ManualLogSource log) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown lock (Sync) { if (installed) { return; } logger = log; warnedPostfixFailure = false; if (Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")) { new Harmony("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat").UnpatchSelf(); if (Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")) { throw new InvalidOperationException("Failed to remove stale synergy YAML compatibility patches before installation."); } } MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(EffectDef), "Parse", new Type[2] { typeof(object), typeof(List<string>).MakeByRefType() }, (Type[])null) ?? throw new MissingMethodException(typeof(EffectDef).FullName, "Parse"); if (methodInfo.ReturnType != typeof(ParseResult)) { throw new InvalidOperationException("Unexpected " + typeof(EffectDef).FullName + ".Parse return type '" + methodInfo.ReturnType.FullName + "'."); } if (EffectDef.ValidEffects.ContainsKey("__karmadrops_synergy_unset_probe_9f67b5c1__")) { throw new InvalidOperationException("The synergy compatibility probe key '__karmadrops_synergy_unset_probe_9f67b5c1__' is already registered as an effect."); } switch (Probe()) { case ProbeState.Fixed: log.LogInfo((object)"Jewelcrafting already accepts synergy 'unset' markers without a false effect-name error; the synergy YAML compatibility patch is not needed."); break; default: throw new InvalidOperationException("Jewelcrafting synergy 'unset' parsing does not match the supported runtime contract."); case ProbeState.BugPresent: { Harmony val = new Harmony("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat"); try { MethodInfo postfix = AccessTools.DeclaredMethod(typeof(SynergyYamlCompat), "AfterParse", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(SynergyYamlCompat).FullName, "AfterParse"); val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(postfix) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo == null || !patchInfo.Postfixes.Any((Patch patch) => patch.owner == "sighsorry.jewelcraftingkarmadrops.synergyyamlcompat" && patch.PatchMethod == postfix)) { throw new InvalidOperationException("Harmony did not register the synergy YAML compatibility postfix."); } if (Probe() != ProbeState.Fixed) { throw new InvalidOperationException("Jewelcrafting synergy 'unset' parsing did not pass its post-install semantic probe."); } harmony = val; installed = true; log.LogInfo((object)"Jewelcrafting synergy YAML compatibility enabled: exact '<synergy id>: unset' definitions now survive hot reload and server synchronization."); break; } catch (Exception ex) { Exception ex2 = null; try { val.UnpatchSelf(); if (Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")) { ex2 = new InvalidOperationException("Harmony patches remain after rolling back synergy YAML compatibility."); } } catch (Exception ex3) { ex2 = ex3; } harmony = null; installed = false; if (ex2 != null) { throw new AggregateException("Failed to install and fully roll back synergy YAML compatibility.", ex, ex2); } throw; } } } } } internal static void Shutdown(ManualLogSource? log = null) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) lock (Sync) { if (!installed && !Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")) { logger = null; warnedPostfixFailure = false; return; } ((Harmony)(((object)harmony) ?? ((object)new Harmony("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")))).UnpatchSelf(); if (Harmony.HasAnyPatches("sighsorry.jewelcraftingkarmadrops.synergyyamlcompat")) { throw new InvalidOperationException("Failed to remove all synergy YAML compatibility patches."); } harmony = null; installed = false; logger = null; warnedPostfixFailure = false; if (log != null) { log.LogInfo((object)"Jewelcrafting synergy YAML compatibility disabled."); } } } private static void AfterParse(object? __0, ref List<string> __1, ref ParseResult __result) { try { if (!(__0 is Dictionary<object, object> dictionary) || __1 == null || __result.Synergy == null) { return; } foreach (KeyValuePair<object, object> item in dictionary) { if (item.Value is string text && !(text != "unset")) { string text2 = item.Key?.ToString() ?? string.Empty; if (text2.Length != 0 && __result.Synergy.TryGetValue(text2, out var value) && !(value.Name != string.Empty)) { __1.Remove(BuildInvalidEffectError(text2)); } } } } catch (Exception arg) { if (!warnedPostfixFailure) { warnedPostfixFailure = true; ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)("Failed to inspect a Jewelcrafting synergy 'unset' definition; " + $"the original parser result was preserved: {arg}")); } } } } private static ProbeState Probe() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) List<string> list = default(List<string>); if (!EffectDef.Parse((object)new Dictionary<object, object> { ["__karmadrops_synergy_unset_probe_9f67b5c1__"] = "unset" }, ref list).Synergy.TryGetValue("__karmadrops_synergy_unset_probe_9f67b5c1__", out var value) || !(value.Name == string.Empty)) { return ProbeState.Incompatible; } if (list.Count == 0) { return ProbeState.Fixed; } if (list.Count != 1 || !string.Equals(list[0], BuildInvalidEffectError("__karmadrops_synergy_unset_probe_9f67b5c1__"), StringComparison.Ordinal)) { return ProbeState.Incompatible; } return ProbeState.BugPresent; } private static string BuildInvalidEffectError(string key) { return "'" + key + "' is not a valid effect name. Valid effects are: '" + string.Join("', '", EffectDef.ValidEffects.Keys) + "'. There also can be a 'gems' section to define gem spawn chances."; } } internal static class ValheimPrivateFields { private static readonly FieldRef<Character, HitData> characterLastHit; private static readonly FieldRef<SE_Poison, float> poisonDamageLeft; private static readonly FieldRef<SE_Burning, float> burningFireDamageLeft; private static readonly FieldRef<SE_Burning, float> burningFireDamagePerHit; private static readonly FieldRef<SE