Decompiled source of BetterTeamUpgrades v2.1.2

BetterTeamUpgrades.dll

Decompiled a week ago
using 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("TeamUpgrades")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TeamUpgrades")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("ef74d5e5-8fe6-4b6a-86ed-0e29e12695bb")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BetterTeamUpgrades
{
	[BepInPlugin("MrBytesized.REPO.BetterTeamUpgrades", "Better Team Upgrades", "2.0.0")]
	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.0.0";

		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;

		private void Awake()
		{
			if ((Object)(object)instance == (Object)null)
			{
				instance = this;
			}
			Log = Logger.CreateLogSource("MrBytesized.REPO.BetterTeamUpgrades");
			harmony.PatchAll(typeof(StatsManagerInitPatch));
			Configuration.Init(((BaseUnityPlugin)this).Config);
			_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));
			}
		}
	}
	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;

			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 StatsManager.instance.dictionaryOfDictionaries)
						{
							if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
							{
								continue;
							}
							string key = dictionaryOfDictionary.Key;
							Dictionary<string, int> value = dictionaryOfDictionary.Value;
							int num = 0;
							foreach (string item in list)
							{
								if (value.TryGetValue(item, out var value2) && value2 > num)
								{
									num = value2;
								}
							}
							if (num <= 0)
							{
								continue;
							}
							bool flag = SharedUpgradesPatch.VanillaKeys.Contains(key);
							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;
								}
								if (flag)
								{
									string text3 = key.Substring("playerUpgrade".Length);
									component.RPC("TesterUpgradeCommandRPC", (RpcTarget)1, new object[3] { CS$<>8__locals0.id, text3, num3 });
									if (value.ContainsKey(CS$<>8__locals0.id))
									{
										value[CS$<>8__locals0.id] += num3;
									}
									else
									{
										value[CS$<>8__locals0.id] = num3;
									}
								}
								else
								{
									component.RPC("UpdateStatRPC", (RpcTarget)1, new object[3] { key, CS$<>8__locals0.id, num });
									value[CS$<>8__locals0.id] = num;
								}
								string arg = "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)
								{
									arg = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(val);
								}
								Plugin.Log.LogInfo((object)$"Late Join: Synced {key} for {arg} (+{num3})");
							}
						}
						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())
			{
				RunManager instance = RunManager.instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelMainMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelRecording) && !((Object)(object)instance.levelCurrent == (Object)(object)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 static HashSet<string> VanillaKeys = new HashSet<string>();

		private static Dictionary<string, int> _preUpgradeStats = new Dictionary<string, int>();

		private static string _targetSteamID;

		private static int _targetViewID;

		[HarmonyPrefix]
		public static void Prefix(ItemUpgrade __instance)
		{
			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;
			}
			_targetViewID = (int)AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID").GetValue(val);
			PlayerAvatar val2 = SemiFunc.PlayerAvatarGetFromPhotonID(_targetViewID);
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			_targetSteamID = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(val2);
			_preUpgradeStats.Clear();
			if (!((Object)(object)StatsManager.instance != (Object)null))
			{
				return;
			}
			foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in StatsManager.instance.dictionaryOfDictionaries)
			{
				if (dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
				{
					if (dictionaryOfDictionary.Value.TryGetValue(_targetSteamID, out var value2))
					{
						_preUpgradeStats[dictionaryOfDictionary.Key] = value2;
					}
					else
					{
						_preUpgradeStats[dictionaryOfDictionary.Key] = 0;
					}
				}
			}
		}

		[HarmonyPostfix]
		public static void Postfix(ItemUpgrade __instance)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || string.IsNullOrEmpty(_targetSteamID) || (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 StatsManager.instance.dictionaryOfDictionaries)
			{
				if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
				{
					continue;
				}
				int num = (dictionaryOfDictionary.Value.ContainsKey(_targetSteamID) ? dictionaryOfDictionary.Value[_targetSteamID] : 0);
				int num2 = (_preUpgradeStats.ContainsKey(dictionaryOfDictionary.Key) ? _preUpgradeStats[dictionaryOfDictionary.Key] : 0);
				if (num > num2)
				{
					int num3 = num - num2;
					string key = dictionaryOfDictionary.Key;
					Plugin.Log.LogInfo((object)$"Detected upgrade: {key} (+{num3}) for {_targetSteamID}");
					if (VanillaKeys.Contains(key))
					{
						string command = key.Substring("playerUpgrade".Length);
						DistributeVanillaUpgrade(component, command, num3);
					}
					else if (!Configuration.EnableCustomUpgradeSyncing.Value)
					{
						Plugin.Log.LogInfo((object)("Custom Upgrade Syncing is disabled. Skipping: " + key));
					}
					else
					{
						DistributeCustomUpgrade(component, key, num);
					}
				}
			}
			_targetSteamID = null;
			_targetViewID = -1;
			_preUpgradeStats.Clear();
		}

		private static void DistributeVanillaUpgrade(PhotonView punView, string command, int amount)
		{
			foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
			{
				if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null)
				{
					continue;
				}
				if (item.photonView.ViewID == _targetViewID)
				{
					Plugin.Log.LogInfo((object)("Skipping original upgrader: " + command + " for " + _targetSteamID));
					continue;
				}
				string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item);
				if (!string.IsNullOrEmpty(text))
				{
					punView.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { text, command, amount });
					Plugin.Log.LogInfo((object)("Synced Vanilla: " + command + " for " + text));
				}
			}
		}

		private static void DistributeCustomUpgrade(PhotonView punView, string dictionaryKey, int totalValue)
		{
			foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
			{
				if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null)
				{
					continue;
				}
				if (item.photonView.ViewID == _targetViewID)
				{
					Plugin.Log.LogInfo((object)("Skipping original upgrader: " + dictionaryKey + " for " + _targetSteamID));
					continue;
				}
				string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item);
				if (!string.IsNullOrEmpty(text))
				{
					punView.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { dictionaryKey, text, totalValue });
					Plugin.Log.LogInfo((object)("Synced Custom: " + dictionaryKey + " for " + text));
				}
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "Start")]
	public class StatsManagerInitPatch
	{
		[HarmonyPostfix]
		public static void Postfix(StatsManager __instance)
		{
			SharedUpgradesPatch.VanillaKeys.Clear();
			HashSet<string> hashSet = (from f in typeof(StatsManager).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				select f.Name).ToHashSet();
			foreach (string key in __instance.dictionaryOfDictionaries.Keys)
			{
				if (key.StartsWith("playerUpgrade") && hashSet.Contains(key))
				{
					SharedUpgradesPatch.VanillaKeys.Add(key);
				}
			}
			Plugin.Log.LogInfo((object)$"Auto-discovered {SharedUpgradesPatch.VanillaKeys.Count} vanilla upgrade keys.");
		}
	}
}
namespace BetterTeamUpgrades.Config
{
	internal class Configuration
	{
		public static ConfigEntry<bool> EnableSharedUpgradesPatch;

		public static ConfigEntry<bool> EnableLateJoinPlayerUpdateSyncPatch;

		public static ConfigEntry<bool> EnableCustomUpgradeSyncing;

		public static void Init(ConfigFile config)
		{
			EnableSharedUpgradesPatch = config.Bind<bool>("Upgrade Sync Settings", "EnableSharedUpgrades", true, "Enables Shared Upgrades for all supported Upgrades");
			EnableLateJoinPlayerUpdateSyncPatch = config.Bind<bool>("Late Join Settings", "EnableLateJoinPlayerUpgradeSync", false, "Enables Upgrade Sync for Late Joining Players");
			EnableCustomUpgradeSyncing = config.Bind<bool>("Extra Sync Settings", "EnableCustomUpgradeSyncing", true, "Enables Custom Upgrade Syncing for Modded Upgrades (may cause issues with some mods)");
		}
	}
}