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 BetterTeamUpgrades v2.2.1
BetterTeamUpgrades.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BetterTeamUpgrades.Config; using BetterTeamUpgrades.Patches; using HarmonyLib; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BetterTeamUpgrades")] [assembly: AssemblyDescription("DLL for REPO mod.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("MrBytesized")] [assembly: AssemblyProduct("BetterTeamUpgrades")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("ef74d5e5-8fe6-4b6a-86ed-0e29e12695bb")] [assembly: AssemblyFileVersion("2.2.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("2.2.0.0")] namespace BetterTeamUpgrades { [BepInPlugin("MrBytesized.REPO.BetterTeamUpgrades", "Better Team Upgrades", "2.2.1")] public class Plugin : BaseUnityPlugin { private const string mod_guid = "MrBytesized.REPO.BetterTeamUpgrades"; private const string mod_name = "Better Team Upgrades"; private const string mod_version = "2.2.1"; private readonly Harmony harmony = new Harmony("MrBytesized.REPO.BetterTeamUpgrades"); private static Plugin instance; internal static ManualLogSource Log; private (ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description)[] _patchArray; public static ConfigFile PlguinConfig; internal static readonly object RandomLock = new object(); internal static readonly Random Random = new Random(); private void Awake() { if ((Object)(object)instance == (Object)null) { instance = this; } Log = Logger.CreateLogSource("MrBytesized.REPO.BetterTeamUpgrades"); PlguinConfig = ((BaseUnityPlugin)this).Config; harmony.PatchAll(typeof(StatsManagerInitPatch)); Configuration.Init(PlguinConfig); _patchArray = new(ConfigEntry<bool>, Action, Action, string)[2] { (Configuration.EnableSharedUpgradesPatch, delegate { harmony.PatchAll(typeof(SharedUpgradesPatch)); }, delegate { harmony.UnpatchSelf(typeof(SharedUpgradesPatch)); }, "Shared Upgrades"), (Configuration.EnableLateJoinPlayerUpdateSyncPatch, delegate { harmony.PatchAll(typeof(LateJoinPlayerUpgradeSyncPatch)); }, delegate { harmony.UnpatchSelf(typeof(LateJoinPlayerUpgradeSyncPatch)); }, "Late Join Player Upgrade Sync") }; (ConfigEntry<bool>, Action, Action, string)[] patchArray = _patchArray; for (int i = 0; i < patchArray.Length; i++) { var (configEntry, enablePatch, disablePatch, description) = patchArray[i]; UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description); configEntry.SettingChanged += delegate { UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description); }; } Log.LogInfo((object)"Better Team Upgrades mod has been activated"); } private void UpdatePatchFromConfig(ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description) { if (configEntry.Value) { try { enablePatch(); Log.LogInfo((object)(description + " patch enabled.")); return; } catch (Exception ex) { Log.LogError((object)("Failed to enable " + description + ": " + ex.Message)); return; } } try { disablePatch(); Log.LogInfo((object)(description + " patch disabled.")); } catch (Exception ex2) { Log.LogError((object)("Failed to disable " + description + ": " + ex2.Message)); } } internal static int Roll(int min, int max) { lock (RandomLock) { return Random.Next(min, max); } } public static IEnumerable<KeyValuePair<string, Dictionary<string, int>>> GetDictionaryOfDictionaries(StatsManager instance) { if ((Object)(object)instance == (Object)null) { return Enumerable.Empty<KeyValuePair<string, Dictionary<string, int>>>(); } FieldInfo fieldInfo = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries"); if (fieldInfo == null) { Log.LogError((object)"StatsManager.dictionaryOfDictionaries field not found via reflection!"); return Enumerable.Empty<KeyValuePair<string, Dictionary<string, int>>>(); } object value = fieldInfo.GetValue(instance); if (value == null) { return Enumerable.Empty<KeyValuePair<string, Dictionary<string, int>>>(); } return (IEnumerable<KeyValuePair<string, Dictionary<string, int>>>)value; } } public static class HarmonyExtensions { public static void UnpatchSelf(this Harmony harmony, Type patchClass) { HarmonyPatch[] array = patchClass.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).OfType<HarmonyPatch>().ToArray(); for (int i = 0; i < array.Length; i++) { HarmonyMethod info = ((HarmonyAttribute)array[i]).info; if (info == null) { Plugin.Log.LogWarning((object)("Invalid HarmonyPatch method info on class: " + patchClass.FullName)); continue; } MethodInfo methodInfo = ResolveOriginal(info); if (methodInfo == null) { Plugin.Log.LogWarning((object)("Original method not found for class patch: " + FormatInfo(info))); } else { harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)0, harmony.Id); } } MethodInfo[] methods = patchClass.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { HarmonyPatch[] array2 = methodInfo2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).OfType<HarmonyPatch>().ToArray(); if (array2.Length == 0) { continue; } array = array2; for (int j = 0; j < array.Length; j++) { HarmonyMethod info2 = ((HarmonyAttribute)array[j]).info; if (info2 == null) { Plugin.Log.LogWarning((object)("Invalid HarmonyPatch info on method: " + methodInfo2.DeclaringType.FullName + "." + methodInfo2.Name)); continue; } MethodInfo methodInfo3 = ResolveOriginal(info2); if (methodInfo3 == null) { Plugin.Log.LogWarning((object)("Original method not found for method-level patch: " + FormatInfo(info2))); } else { harmony.Unpatch((MethodBase)methodInfo3, (HarmonyPatchType)0, harmony.Id); } } } } private static MethodInfo ResolveOriginal(HarmonyMethod info) { if (info.method != null) { return info.method; } if (info.declaringType == null || string.IsNullOrEmpty(info.methodName)) { return null; } return AccessTools.Method(info.declaringType, info.methodName, info.argumentTypes, (Type[])null); } private static string FormatInfo(HarmonyMethod info) { string obj = ((info.declaringType != null) ? info.declaringType.FullName : "<null>"); string text = ((!string.IsNullOrEmpty(info.methodName)) ? info.methodName : "<null>"); return obj + "." + text; } } } namespace BetterTeamUpgrades.Patches { [HarmonyPatch(typeof(PlayerAvatar), "Start")] public class LateJoinPlayerUpgradeSyncPatch { [CompilerGenerated] private sealed class <>c__DisplayClass1_0 { public string id; public Func<PlayerAvatar, bool> <>9__2; internal bool <SyncWithDelay>b__2(PlayerAvatar p) { return SemiFunc.PlayerGetSteamID(p) == id; } } [CompilerGenerated] private sealed class <SyncWithDelay>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public PlayerAvatar newPlayer; private float <timeWaited>5__2; private float <timeout>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SyncWithDelay>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <timeWaited>5__2 = 0f; <timeout>5__3 = 10f; goto IL_006c; case 1: <>1__state = -1; <timeWaited>5__2 += 0.5f; goto IL_006c; case 2: { <>1__state = -1; if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return false; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { Plugin.Log.LogWarning((object)"Late Join: PunManager PhotonView not found."); return false; } string text = SemiFunc.PlayerGetSteamID(newPlayer); if (string.IsNullOrEmpty(text)) { Plugin.Log.LogWarning((object)$"Late Join: Timed out waiting for SteamID for player {newPlayer.photonView.ViewID}. Skipping sync."); return false; } string text2 = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(newPlayer); Plugin.Log.LogInfo((object)("Late Join: Player " + text2 + " (" + text + ") is ready. Starting sync...")); List<PlayerAvatar> source = SemiFunc.PlayerGetAll(); List<string> list = (from p in source select SemiFunc.PlayerGetSteamID(p) into id where !string.IsNullOrEmpty(id) select id).ToList(); foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in Plugin.GetDictionaryOfDictionaries(StatsManager.instance)) { if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade")) { continue; } string key = dictionaryOfDictionary.Key; Dictionary<string, int> value = dictionaryOfDictionary.Value; bool flag = SharedUpgradesPatch.VanillaKeys.Contains(key); string text3 = (flag ? "Vanilla Upgrade Settings" : "Modded Upgrade Settings"); string text4 = key.Replace("player", ""); if (!Plugin.PlguinConfig.Bind<bool>(text3, text4, true, "Enable upgrade syncing for " + key).Value) { Plugin.Log.LogInfo((object)("Late Join: Skipping " + key + " because config toggle '" + text3 + ":" + text4 + "' is disabled.")); continue; } if (!flag && !Configuration.EnableCustomUpgradeSyncing.Value) { Plugin.Log.LogInfo((object)("Late Join: Custom Upgrade Syncing is disabled. Skipping: " + key)); continue; } int num = 0; foreach (string item in list) { if (value.TryGetValue(item, out var value2) && value2 > num) { num = value2; } } if (num <= 0) { continue; } using List<string>.Enumerator enumerator2 = list.GetEnumerator(); while (enumerator2.MoveNext()) { <>c__DisplayClass1_0 CS$<>8__locals0 = new <>c__DisplayClass1_0 { id = enumerator2.Current }; int num2 = (value.ContainsKey(CS$<>8__locals0.id) ? value[CS$<>8__locals0.id] : 0); int num3 = num - num2; if (num3 <= 0) { continue; } for (int i = 0; i < num3; i++) { Plugin.Log.LogInfo((object)("Late Join: Considering sync " + key + " for " + CS$<>8__locals0.id + " (+1)")); int num4 = Plugin.Roll(0, 100); if (num4 >= Configuration.LateJoinUpgradeSyncChance.Value) { Plugin.Log.LogInfo((object)$"Late Join: Skipped syncing {key} for {CS$<>8__locals0.id} due to chance roll ({num4} >= {Configuration.LateJoinUpgradeSyncChance.Value})"); continue; } if (flag) { string text5 = key.Substring("playerUpgrade".Length); component.RPC("TesterUpgradeCommandRPC", (RpcTarget)1, new object[3] { CS$<>8__locals0.id, text5, 1 }); if (value.ContainsKey(CS$<>8__locals0.id)) { value[CS$<>8__locals0.id]++; } else { value[CS$<>8__locals0.id] = 1; } } else { component.RPC("UpdateStatRPC", (RpcTarget)1, new object[3] { key, CS$<>8__locals0.id, num2 + 1 }); value[CS$<>8__locals0.id] = num2 + 1; } string text6 = "Unknown"; PlayerAvatar val = ((IEnumerable<PlayerAvatar>)source).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => SemiFunc.PlayerGetSteamID(p) == CS$<>8__locals0.id)); if ((Object)(object)val != (Object)null) { text6 = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(val); } Plugin.Log.LogInfo((object)("Late Join: Synced " + key + " for " + text6 + " (+1)")); num2++; } } } Plugin.Log.LogInfo((object)("Late Join: Sync complete for " + text2 + ".")); return false; } IL_006c: if (string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(newPlayer)) && <timeWaited>5__2 < <timeout>5__3) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPostfix] private static void Postfix(PlayerAvatar __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelMainMenu) && !((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu) && !((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelRecording) && !((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelSplashScreen)) { ((MonoBehaviour)__instance).StartCoroutine(SyncWithDelay(__instance)); } } [IteratorStateMachine(typeof(<SyncWithDelay>d__1))] private static IEnumerator SyncWithDelay(PlayerAvatar newPlayer) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SyncWithDelay>d__1(0) { newPlayer = newPlayer }; } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] public class SharedUpgradesPatch { public struct UpgradeContext { public string SteamID; public int ViewID; public string PlayerName; public Dictionary<string, int> PreUpgradeStats; } public static HashSet<string> VanillaKeys = new HashSet<string>(); public static HashSet<string> ModdedKeys = new HashSet<string>(); [HarmonyPrefix] public static void Prefix(ItemUpgrade __instance, out UpgradeContext __state) { __state = default(UpgradeContext); if (!SemiFunc.IsMasterClientOrSingleplayer()) { return; } object? value = AccessTools.Field(typeof(ItemUpgrade), "itemToggle").GetValue(__instance); ItemToggle val = (ItemToggle)((value is ItemToggle) ? value : null); if ((Object)(object)val == (Object)null || !val.toggleState) { return; } int num = (int)AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID").GetValue(val); PlayerAvatar val2 = SemiFunc.PlayerAvatarGetFromPhotonID(num); if ((Object)(object)val2 == (Object)null) { return; } string playerName = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(val2); string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(val2); Dictionary<string, int> dictionary = new Dictionary<string, int>(); if ((Object)(object)StatsManager.instance != (Object)null) { foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in Plugin.GetDictionaryOfDictionaries(StatsManager.instance)) { if (dictionaryOfDictionary.Key.StartsWith("playerUpgrade")) { if (dictionaryOfDictionary.Value.TryGetValue(text, out var value2)) { dictionary[dictionaryOfDictionary.Key] = value2; } else { dictionary[dictionaryOfDictionary.Key] = 0; } } } } __state = new UpgradeContext { SteamID = text, ViewID = num, PlayerName = playerName, PreUpgradeStats = dictionary }; } [HarmonyPostfix] public static void Postfix(ItemUpgrade __instance, UpgradeContext __state) { if (!SemiFunc.IsMasterClientOrSingleplayer() || string.IsNullOrEmpty(__state.SteamID) || (Object)(object)PunManager.instance == (Object)null) { return; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { Plugin.Log.LogError((object)"SharedUpgrades: PunManager PhotonView not found!"); return; } foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in Plugin.GetDictionaryOfDictionaries(StatsManager.instance)) { if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade")) { continue; } bool flag = VanillaKeys.Contains(dictionaryOfDictionary.Key); string text = (flag ? "Vanilla Upgrade Settings" : "Modded Upgrade Settings"); string text2 = dictionaryOfDictionary.Key.Replace("player", ""); if (!Plugin.PlguinConfig.Bind<bool>(text, text2, true, "Enable shared upgrade syncing for " + dictionaryOfDictionary.Key).Value) { Plugin.Log.LogInfo((object)("SharedUpgrades: Skipping " + dictionaryOfDictionary.Key + " because config toggle '" + text + ":" + text2 + "' is disabled.")); continue; } int num = (dictionaryOfDictionary.Value.ContainsKey(__state.SteamID) ? dictionaryOfDictionary.Value[__state.SteamID] : 0); int num2 = (__state.PreUpgradeStats.ContainsKey(dictionaryOfDictionary.Key) ? __state.PreUpgradeStats[dictionaryOfDictionary.Key] : 0); if (num > num2) { int num3 = num - num2; string key = dictionaryOfDictionary.Key; Plugin.Log.LogInfo((object)$"Detected upgrade: {key} (+{num3}) for {__state.PlayerName}({__state.SteamID})"); int num4 = Plugin.Roll(0, 100); if (num4 >= Configuration.SharedUpgradeChance.Value) { Plugin.Log.LogInfo((object)$"Skipped syncing {key} due to chance roll ({num4} >= {Configuration.SharedUpgradeChance.Value})"); } else if (flag) { string command = key.Substring("playerUpgrade".Length); DistributeVanillaUpgrade(component, command, num3, __state); } else if (!Configuration.EnableCustomUpgradeSyncing.Value) { Plugin.Log.LogInfo((object)("Custom Upgrade Syncing is disabled. Skipping: " + key)); } else { DistributeCustomUpgrade(component, key, num, __state); } } } } private static void DistributeVanillaUpgrade(PhotonView punView, string command, int amount, UpgradeContext context) { foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null) { continue; } if (item.photonView.ViewID == context.ViewID) { Plugin.Log.LogInfo((object)("Skipping original upgrader: " + command + " for " + context.PlayerName + "(" + context.SteamID + ")")); continue; } string text = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(item); if (string.IsNullOrEmpty(text)) { text = "Unknown"; } string text2 = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item); if (!string.IsNullOrEmpty(text2)) { punView.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { text2, command, amount }); Plugin.Log.LogInfo((object)("Synced Vanilla: " + command + " for " + text + "(" + text2 + ")")); } } } private static void DistributeCustomUpgrade(PhotonView punView, string dictionaryKey, int totalValue, UpgradeContext context) { foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null) { continue; } if (item.photonView.ViewID == context.ViewID) { Plugin.Log.LogInfo((object)("Skipping original upgrader: " + dictionaryKey + " for " + context.PlayerName + "(" + context.SteamID + ")")); continue; } string text = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(item); if (string.IsNullOrEmpty(text)) { text = "Unknown"; } string text2 = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item); if (!string.IsNullOrEmpty(text2)) { punView.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { dictionaryKey, text2, totalValue }); Plugin.Log.LogInfo((object)("Synced Custom: " + dictionaryKey + " for " + text + "(" + text2 + ")")); } } } } [HarmonyPatch(typeof(StatsManager), "Start")] public class StatsManagerInitPatch { [HarmonyPostfix] public static void Postfix(StatsManager __instance) { SharedUpgradesPatch.VanillaKeys.Clear(); SharedUpgradesPatch.ModdedKeys.Clear(); HashSet<string> hashSet = (from f in typeof(StatsManager).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) select f.Name).ToHashSet(); foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in Plugin.GetDictionaryOfDictionaries(__instance)) { string key = dictionaryOfDictionary.Key; if (key.StartsWith("playerUpgrade")) { string text = key.Replace("player", ""); if (hashSet.Contains(key)) { SharedUpgradesPatch.VanillaKeys.Add(key); Plugin.PlguinConfig.Bind<bool>("Vanilla Upgrade Settings", text, true, "Enable shared upgrade syncing for " + key); } else { SharedUpgradesPatch.ModdedKeys.Add(key); Plugin.PlguinConfig.Bind<bool>("Modded Upgrade Settings", text, true, "Enable shared upgrade syncing for modded upgrade " + key); } } } Plugin.Log.LogInfo((object)$"Auto-discovered {SharedUpgradesPatch.VanillaKeys.Count} vanilla upgrade keys and {SharedUpgradesPatch.ModdedKeys.Count} modded upgrade keys."); } } } namespace BetterTeamUpgrades.Config { internal class Configuration { public static ConfigEntry<bool> EnableSharedUpgradesPatch; public static ConfigEntry<int> SharedUpgradeChance; public static ConfigEntry<bool> EnableLateJoinPlayerUpdateSyncPatch; public static ConfigEntry<int> LateJoinUpgradeSyncChance; public static ConfigEntry<bool> EnableCustomUpgradeSyncing; public static void Init(ConfigFile config) { //IL_0026: 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_0049: Expected O, but got Unknown //IL_0049: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_0097: Expected O, but got Unknown EnableSharedUpgradesPatch = config.Bind<bool>("Shared Upgrade Settings", "EnableSharedUpgrades", true, "Enables Shared Upgrades for all supported Upgrades"); SharedUpgradeChance = config.Bind<int>(new ConfigDefinition("Shared Upgrade Settings", "SharedUpgradeChance"), 100, new ConfigDescription("The percentage chance (0-100) that an upgrade will be shared with team members when purchased.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); EnableLateJoinPlayerUpdateSyncPatch = config.Bind<bool>("Late Join Settings", "EnableLateJoinPlayerUpgradeSync", false, "Enables Upgrade Sync for Late Joining Players"); LateJoinUpgradeSyncChance = config.Bind<int>(new ConfigDefinition("Late Join Settings", "LateJoinUpgradeSyncChance"), 100, new ConfigDescription("The percentage chance (0-100) that a late joining player will receive each upgrade their team members have.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); EnableCustomUpgradeSyncing = config.Bind<bool>("Extra Sync Settings", "EnableCustomUpgradeSyncing", true, "Enables Custom Upgrade Syncing for Modded Upgrades (may cause issues with some mods)"); } } }