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 FairTrophies v1.0.2
FairTrophies.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Hampus Toft")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © Hampus Toft")] [assembly: AssemblyDescription("Fair rare drops for Valheim: one bad-luck counter per character, shared by every star level.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+74e989517cd6d4ebb71acc8f8845e4c6e3d4857b")] [assembly: AssemblyProduct("FairTrophies")] [assembly: AssemblyTitle("FairTrophies")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 FairTrophies { internal static class CharacterCounters { internal const string CustomDataKey = "FairTrophies_counters"; internal static List<KeyValuePair<int, int>> RegisterKill(Player player, string creatureName, CharacterDrop table, int level) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Invalid comparison between Unknown and I4 player.m_customData.TryGetValue("FairTrophies_counters", out var value); SharedDropCounter sharedDropCounter = SharedDropCounter.Parse(value, () => Random.value); List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(); foreach (Drop drop in table.m_drops) { if ((Object)(object)drop.m_prefab == (Object)null || !SharedDropCounter.IsGoverned(drop.m_chance)) { continue; } string name = ((Object)drop.m_prefab).name; ItemDrop component = drop.m_prefab.GetComponent<ItemDrop>(); bool isTrophy = (Object)(object)component != (Object)null && (int)component.m_itemData.m_shared.m_itemType == 13; StarScaling starScaling = StarScaling.For(level, drop.m_levelMultiplier, name, isTrophy); bool flag = sharedDropCounter.RegisterKill(name, drop.m_chance, starScaling.Weight); float? remaining = sharedDropCounter.GetRemaining(name); Log.Counter($"{player.GetPlayerName()} killed {creatureName} (level {level}) -> {name} " + $"chance {drop.m_chance:0.###} rate x{starScaling.Weight} amount x{starScaling.AmountMultiplier}: " + (flag ? "DROP" : "no drop") + ", " + (remaining.HasValue ? (remaining.Value / drop.m_chance).ToString("0.0") : "-") + " base kills to next"); if (flag) { int num = VanillaAmount(drop, starScaling.AmountMultiplier); if (num > 0) { list.Add(new KeyValuePair<int, int>(StringExtensionMethods.GetStableHashCode(name), num)); } } } player.m_customData["FairTrophies_counters"] = sharedDropCounter.Serialize(); return list; } private static int VanillaAmount(Drop drop, int amountMultiplier) { int num = (drop.m_dontScale ? Random.Range(drop.m_amountMin, drop.m_amountMax) : Game.instance.ScaleDrops(drop.m_prefab, drop.m_amountMin, drop.m_amountMax)); num *= amountMultiplier; if (drop.m_onePerPlayer) { num = ZNet.instance.GetNrOfPlayers(); } return Mathf.Min(num, 100); } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] internal static class CharacterDropPatch { [HarmonyPrefix] private static void Prefix(CharacterDrop __instance, out List<Drop> __state) { __state = null; if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)36))) { return; } List<Drop> list = null; foreach (Drop drop in __instance.m_drops) { if (!((Object)(object)drop?.m_prefab != (Object)null) || !SharedDropCounter.IsGoverned(drop.m_chance) || list != null) { continue; } list = new List<Drop>(__instance.m_drops.Count); foreach (Drop drop2 in __instance.m_drops) { if ((Object)(object)drop2?.m_prefab == (Object)null || !SharedDropCounter.IsGoverned(drop2.m_chance)) { list.Add(drop2); } } } if (list != null) { __state = __instance.m_drops; __instance.m_drops = list; } } [HarmonyPostfix] private static void Postfix(CharacterDrop __instance, List<Drop> __state) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (__state != null) { Character component = ((Component)__instance).GetComponent<Character>(); int level = ((!Object.op_Implicit((Object)(object)component)) ? 1 : component.GetLevel()); Vector3 dropPoint = (Object.op_Implicit((Object)(object)component) ? (component.GetCenterPoint() + ((Component)__instance).transform.TransformVector(__instance.m_spawnOffset)) : ((Component)__instance).transform.position); KillRouting.ReportKill(StringExtensionMethods.GetStableHashCode(Utils.GetPrefabName(((Component)__instance).gameObject)), level, KillAttribution.LastPlayer(component), dropPoint, RagdollContext.Current, __instance.m_cheated); } } [HarmonyFinalizer] private static void Finalizer(CharacterDrop __instance, List<Drop> __state) { if (__state != null) { __instance.m_drops = __state; } } } [HarmonyPatch(typeof(Ragdoll), "SaveLootList")] internal static class RagdollContext { internal static ZDOID Current = ZDOID.None; private static readonly List<int> DeferredHashes = new List<int>(); private static readonly List<int> DeferredAmounts = new List<int>(); internal static void Defer(IList<int> hashes, IList<int> amounts) { DeferredHashes.AddRange(hashes); DeferredAmounts.AddRange(amounts); } [HarmonyPrefix] private static void Prefix(Ragdoll __instance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); Current = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO().m_uid : ZDOID.None); } [HarmonyPostfix] private static void Postfix(Ragdoll __instance) { if (DeferredHashes.Count != 0) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { KillRouting.AppendRagdollLoot(component.GetZDO(), DeferredHashes, DeferredAmounts); } } } [HarmonyFinalizer] private static void Finalizer() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Current = ZDOID.None; DeferredHashes.Clear(); DeferredAmounts.Clear(); } } internal static class KillAttribution { [HarmonyPatch(typeof(Character), "RPC_Damage")] private static class DamagePatch { [HarmonyPostfix] private static void Postfix(Character __instance, HitData hit) { if (hit == null || __instance.IsPlayer()) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null) { return; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner()) { long playerID = val.GetPlayerID(); if (playerID != 0L) { component.GetZDO().Set(LastPlayerKey, playerID); } } } } private static readonly int LastPlayerKey = StringExtensionMethods.GetStableHashCode("FairTrophies_lastPlayer"); internal static long LastPlayer(Character character) { ZNetView val = (Object.op_Implicit((Object)(object)character) ? ((Component)character).GetComponent<ZNetView>() : null); if (!((Object)(object)val != (Object)null) || !val.IsValid()) { return 0L; } return val.GetZDO().GetLong(LastPlayerKey, 0L); } } public sealed class SharedDropCounter { public const float Spread = 2f; private readonly Dictionary<string, float> remaining = new Dictionary<string, float>(); private readonly Func<float> random01; private const string FormatPrefix = "1|"; public const float PseudoDropThreshold = 0.3f; public int Count => remaining.Count; public SharedDropCounter(Func<float> random01) { this.random01 = random01 ?? throw new ArgumentNullException("random01"); } public bool RegisterKill(string item, float baseChance, float weight) { float num = baseChance * weight; if (num <= 0f) { return false; } if (num >= 1f) { return true; } if (!remaining.TryGetValue(item, out var value)) { value = RollInterval(); } value -= num; bool num2 = value <= 0f; if (num2) { value += RollInterval(); } remaining[item] = value; return num2; } public float? GetRemaining(string item) { if (!remaining.TryGetValue(item, out var value)) { return null; } return value; } public void Set(string item, float left) { remaining[item] = left; } public IReadOnlyDictionary<string, float> Snapshot() { return remaining; } private float RollInterval() { return random01() * 2f; } public string Serialize() { StringBuilder stringBuilder = new StringBuilder("1|"); bool flag = true; foreach (KeyValuePair<string, float> item in remaining) { if (!flag) { stringBuilder.Append(';'); } flag = false; stringBuilder.Append(item.Key).Append('=').Append(item.Value.ToString("R", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } public static SharedDropCounter Parse(string text, Func<float> random01) { SharedDropCounter sharedDropCounter = new SharedDropCounter(random01); if (string.IsNullOrEmpty(text) || !text.StartsWith("1|", StringComparison.Ordinal)) { return sharedDropCounter; } string[] array = text.Substring("1|".Length).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { int num = text2.LastIndexOf('='); if (num > 0 && float.TryParse(text2.Substring(num + 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !float.IsNaN(result) && !float.IsInfinity(result)) { sharedDropCounter.remaining[text2.Substring(0, num)] = result; } } return sharedDropCounter; } public static bool IsGoverned(float baseChance) { if (baseChance > 0f) { return baseChance <= 0.3f; } return false; } } public readonly struct StarScaling { public float Weight { get; } public int AmountMultiplier { get; } private StarScaling(float weight, int amountMultiplier) { Weight = weight; AmountMultiplier = amountMultiplier; } public static StarScaling For(int level, bool levelMultiplier, string itemName, bool isTrophy) { if (!levelMultiplier || level <= 1) { return new StarScaling(1f, 1); } int num = (int)Math.Pow(2.0, level - 1); if (!IsUniqueRareItem(itemName, isTrophy)) { return new StarScaling(1f, num); } return new StarScaling(num, 1); } public static bool IsUniqueRareItem(string itemName, bool isTrophy) { if (isTrophy) { return true; } if (string.IsNullOrEmpty(itemName)) { return false; } if (!itemName.StartsWith("AncientGemstone", StringComparison.Ordinal)) { return itemName.StartsWith("MoldArmor", StringComparison.Ordinal); } return true; } } internal static class FairTrophiesConfig { public static ConfigEntry<bool> LogCounters; public static void Initialize(ConfigFile config) { LogCounters = config.Bind<bool>("Debug", "LogCounters", false, "Log every counted kill: who was credited, the item, whether it dropped and how many kills are left."); } } [BepInPlugin("HampusToft.FairTrophies", "FairTrophies", "1.0.2")] public class FairTrophiesPlugin : BaseUnityPlugin { public const string PluginGUID = "HampusToft.FairTrophies"; public const string PluginName = "FairTrophies"; public const string PluginVersion = "1.0.2"; private Harmony harmony; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log.Source = ((BaseUnityPlugin)this).Logger; FairTrophiesConfig.Initialize(((BaseUnityPlugin)this).Config); harmony = new Harmony("HampusToft.FairTrophies"); harmony.PatchAll(typeof(FairTrophiesPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"FairTrophies 1.0.2 initialized"); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class RegisterRpcsPatch { [HarmonyPostfix] private static void Postfix(ZRoutedRpc __instance) { KillRouting.Register(__instance); } } internal static class Log { internal static ManualLogSource Source; internal static void Counter(string message) { if (FairTrophiesConfig.LogCounters != null && FairTrophiesConfig.LogCounters.Value) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)message); } } } internal static void Info(string message) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)message); } } internal static void Warning(string message) { ManualLogSource source = Source; if (source != null) { source.LogWarning((object)message); } } } internal static class KillRouting { private const string KillRpc = "FairTrophies_Kill"; private const string CountRpc = "FairTrophies_Count"; private const string DropRpc = "FairTrophies_Drop"; internal static void Register(ZRoutedRpc rpc) { rpc.Register<ZPackage>("FairTrophies_Kill", (Action<long, ZPackage>)RPC_Kill); rpc.Register<ZPackage>("FairTrophies_Count", (Action<long, ZPackage>)RPC_Count); rpc.Register<ZPackage>("FairTrophies_Drop", (Action<long, ZPackage>)RPC_Drop); } internal static void ReportKill(int creatureHash, int level, long creditedPlayer, Vector3 dropPoint, ZDOID ragdoll, bool cheated) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0); ZPackage val = new ZPackage(); val.Write(creatureHash); val.Write(level); val.Write(creditedPlayer); val.Write(num); val.Write(dropPoint); val.Write(ragdoll); val.Write(cheated); ZRoutedRpc.instance.InvokeRoutedRPC("FairTrophies_Kill", new object[1] { val }); } private static void RPC_Kill(long sender, ZPackage pkg) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (ZNet.instance.IsServer()) { int num = pkg.ReadInt(); int num2 = pkg.ReadInt(); long playerId = pkg.ReadLong(); long playerId2 = pkg.ReadLong(); Vector3 val = pkg.ReadVector3(); ZDOID val2 = pkg.ReadZDOID(); bool flag = pkg.ReadBool(); ZDO val3 = FindCharacter(playerId) ?? FindCharacter(playerId2) ?? NearestCharacter(val); if (val3 == null) { Log.Counter($"Kill of {num} at {val} has no online player to credit; rare drops skipped"); return; } ZPackage val4 = new ZPackage(); val4.Write(num); val4.Write(num2); val4.Write(val3.GetLong(ZDOVars.s_playerID, 0L)); val4.Write(sender); val4.Write(val); val4.Write(val2); val4.Write(flag); ZRoutedRpc.instance.InvokeRoutedRPC(val3.GetOwner(), "FairTrophies_Count", new object[1] { val4 }); } } private static void RPC_Count(long sender, ZPackage pkg) { //IL_0026: 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) //IL_002e: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!IsFromServer(sender)) { return; } int num = pkg.ReadInt(); int level = pkg.ReadInt(); long num2 = pkg.ReadLong(); long num3 = pkg.ReadLong(); Vector3 val = pkg.ReadVector3(); ZDOID val2 = pkg.ReadZDOID(); bool flag = pkg.ReadBool(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.GetPlayerID() != num2) { return; } GameObject prefab = ZNetScene.instance.GetPrefab(num); CharacterDrop val3 = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<CharacterDrop>() : null); if ((Object)(object)val3 == (Object)null) { return; } List<KeyValuePair<int, int>> list = CharacterCounters.RegisterKill(localPlayer, ((Object)prefab).name, val3, level); if (list.Count == 0) { return; } ZPackage val4 = new ZPackage(); val4.Write(val); val4.Write(val2); val4.Write(flag); val4.Write(list.Count); foreach (KeyValuePair<int, int> item in list) { val4.Write(item.Key); val4.Write(item.Value); } ZRoutedRpc.instance.InvokeRoutedRPC(num3, "FairTrophies_Drop", new object[1] { val4 }); } private static void RPC_Drop(long sender, ZPackage pkg) { //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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_00e0: Unknown result type (might be due to invalid IL or missing references) Vector3 val = pkg.ReadVector3(); ZDOID val2 = pkg.ReadZDOID(); bool flag = pkg.ReadBool(); int num = pkg.ReadInt(); int[] array = new int[num]; int[] array2 = new int[num]; for (int i = 0; i < num; i++) { array[i] = pkg.ReadInt(); array2[i] = pkg.ReadInt(); } if (!((ZDOID)(ref val2)).IsNone()) { if (val2 == RagdollContext.Current) { RagdollContext.Defer(array, array2); return; } ZDO zDO = ZDOMan.instance.GetZDO(val2); if (zDO != null && zDO.IsOwner()) { AppendRagdollLoot(zDO, array, array2); return; } } List<KeyValuePair<GameObject, int>> list = new List<KeyValuePair<GameObject, int>>(num); for (int j = 0; j < num; j++) { GameObject prefab = ZNetScene.instance.GetPrefab(array[j]); if ((Object)(object)prefab != (Object)null) { list.Add(new KeyValuePair<GameObject, int>(prefab, array2[j])); } } CharacterDrop.DropItems(list, val, 0.5f, flag); } internal static void AppendRagdollLoot(ZDO ragdollZdo, IList<int> hashes, IList<int> amounts) { int num = ragdollZdo.GetInt(ZDOVars.s_drops, 0); int num2 = 0; while (num2 < hashes.Count) { ragdollZdo.Set("drop_hash" + num, hashes[num2]); ragdollZdo.Set("drop_amount" + num, amounts[num2]); num2++; num++; } ragdollZdo.Set(ZDOVars.s_drops, num, false); } private static bool IsFromServer(long sender) { if (ZNet.instance.IsServer()) { return sender == ZDOMan.GetSessionID(); } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { return sender == serverPeer.m_uid; } return false; } private static ZDO FindCharacter(long playerId) { if (playerId == 0L) { return null; } foreach (ZDO allCharacterZDO in ZNet.instance.GetAllCharacterZDOS()) { if (allCharacterZDO.GetLong(ZDOVars.s_playerID, 0L) == playerId) { return allCharacterZDO; } } return null; } private static ZDO NearestCharacter(Vector3 point) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) ZDO result = null; float num = float.MaxValue; foreach (ZDO allCharacterZDO in ZNet.instance.GetAllCharacterZDOS()) { float num2 = Vector3.Distance(allCharacterZDO.GetPosition(), point); if (allCharacterZDO.GetLong(ZDOVars.s_playerID, 0L) != 0L && num2 < num) { result = allCharacterZDO; num = num2; } } return result; } } internal static class VersionCheck { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class OnNewConnectionPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (!__instance.IsServer()) { serverVersion = null; failureMessage = null; } peer.m_rpc.Register<string>("FairTrophies_Version", (Action<ZRpc, string>)delegate(ZRpc rpc, string version) { RPC_Version(__instance, rpc, version); }); peer.m_rpc.Invoke("FairTrophies_Version", new object[1] { "1.0.2" }); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class PeerInfoPatch { [HarmonyPrefix] private static bool Prefix(ZNet __instance, ZRpc rpc) { if (__instance.IsServer()) { if (ValidatedClients.Contains(rpc)) { return true; } Log.Warning("Rejecting " + rpc.GetSocket().GetEndPointString() + ": FairTrophies 1.0.2 is required on clients"); rpc.Invoke("Error", new object[1] { 3 }); return false; } if (serverVersion == "1.0.2") { return true; } failureMessage = ((serverVersion == null) ? "This server does not run FairTrophies.\nRemove FairTrophies 1.0.2 to join it." : ("FairTrophies version mismatch.\nServer: " + serverVersion + ", you: 1.0.2.")); Log.Warning(failureMessage.Replace('\n', ' ')); ConnectionStatus.Invoke() = (ConnectionStatus)3; return false; } } [HarmonyPatch(typeof(ZNet), "Disconnect")] private static class DisconnectPatch { [HarmonyPrefix] private static void Prefix(ZNetPeer peer) { if (peer?.m_rpc != null) { ValidatedClients.Remove(peer.m_rpc); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static class ZNetDestroyPatch { [HarmonyPostfix] private static void Postfix() { ValidatedClients.Clear(); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] private static class ShowConnectErrorPatch { [HarmonyPostfix] private static void Postfix(FejdStartup __instance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if (failureMessage != null && (int)ZNet.GetConnectionStatus() == 3) { __instance.m_connectionFailedError.text = failureMessage; } } } private const string VersionRpc = "FairTrophies_Version"; private static readonly HashSet<ZRpc> ValidatedClients = new HashSet<ZRpc>(); private static string serverVersion; private static string failureMessage; private static readonly FieldRef<ConnectionStatus> ConnectionStatus = AccessTools.StaticFieldRefAccess<ConnectionStatus>(AccessTools.Field(typeof(ZNet), "m_connectionStatus")); private static void RPC_Version(ZNet znet, ZRpc rpc, string version) { if (znet.IsServer()) { if (version == "1.0.2") { ValidatedClients.Add(rpc); return; } Log.Warning("Client " + rpc.GetSocket().GetEndPointString() + " runs FairTrophies " + version + ", server runs 1.0.2"); } else { serverVersion = version; } } } }