Decompiled source of SharedUpgradesPlus v1.3.1

SharedUpgrades++.dll

Decompiled 7 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using SharedUpgrades__.Configuration;
using SharedUpgrades__.Models;
using SharedUpgrades__.Services;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("Vippy")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1+b6ecc940065b3d032d090282e54a364f64c74104")]
[assembly: AssemblyProduct("SharedUpgrades++")]
[assembly: AssemblyTitle("SharedUpgrades++")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SharedUpgrades__
{
	internal static class BuildInfo
	{
		public const string Version = "1.3.1";
	}
	[BepInPlugin("Vippy.SharedUpgradesPlus", "SharedUpgradesPlus", "1.3.1")]
	public class SharedUpgrades__ : BaseUnityPlugin
	{
		internal static SharedUpgrades__ Instance { get; private set; }

		internal static ManualLogSource Logger => Instance.BaseLogger;

		private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			PluginConfig.Init(((BaseUnityPlugin)this).Config);
			Patch();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded! LogLevel={PluginConfig.LoggingLevel.Value}");
		}

		internal static void LogAlways(string msg)
		{
			Logger.LogInfo((object)msg);
		}

		internal static void LogInfo(string msg)
		{
			if (ConfigService.IsDebugLoggingEnabled())
			{
				Logger.LogInfo((object)msg);
			}
		}

		internal static void LogVerbose(string msg)
		{
			if (ConfigService.IsVerboseLoggingEnabled())
			{
				Logger.LogDebug((object)msg);
			}
		}

		internal void Patch()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0026: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace SharedUpgrades__.Services
{
	public static class ConfigService
	{
		private static readonly Dictionary<string, ConfigEntry<bool>> upgradeToggles = new Dictionary<string, ConfigEntry<bool>>();

		private static readonly Dictionary<string, ConfigEntry<int>> limitSliders = new Dictionary<string, ConfigEntry<int>>();

		private static readonly HashSet<string> _disabledByDefault = new HashSet<string> { "playerUpgradeObjectValue", "playerUpgradeObjectDurability" };

		public static bool IsSharedUpgradesEnabled()
		{
			return PluginConfig.EnableSharedUpgrades.Value;
		}

		public static bool IsModdedUpgradesEnabled()
		{
			return PluginConfig.EnableModdedUpgrades.Value;
		}

		public static bool IsLateJoinSyncEnabled()
		{
			return PluginConfig.EnableLateJoinSync.Value;
		}

		public static bool IsSharedUpgradeHealEnabled()
		{
			return PluginConfig.EnableSharedUpgradeHeal.Value;
		}

		public static bool IsShareNotificationEnabled()
		{
			return PluginConfig.EnableShareNotification.Value;
		}

		public static int SharedUpgradesChancePercentage()
		{
			return PluginConfig.SharedUpgradeChance.Value;
		}

		public static bool RollSharedUpgradesChance()
		{
			return Roll(PluginConfig.SharedUpgradeChance.Value);
		}

		private static bool Roll(int chance)
		{
			return Random.Range(0, 100) < chance;
		}

		public static bool IsDebugLoggingEnabled()
		{
			return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Debug;
		}

		public static bool IsVerboseLoggingEnabled()
		{
			return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Verbose;
		}

		public static bool IsUpgradeEnabled(string upgradeKey)
		{
			if (upgradeToggles.TryGetValue(upgradeKey, out ConfigEntry<bool> value))
			{
				return value.Value;
			}
			return true;
		}

		public static int UpgradeShareLimit(string upgradeKey)
		{
			if (limitSliders.TryGetValue(upgradeKey, out ConfigEntry<int> value))
			{
				return value.Value;
			}
			return 0;
		}

		public static void LoadModsIntoConfig()
		{
			if (PluginConfig.ConfigFile != null)
			{
				RegisterToggles(RegistryService.Instance.VanillaUpgrades, "Vanilla Upgrades");
				RegisterToggles(RegistryService.Instance.ModdedUpgrades, "Modded Upgrades");
			}
		}

		private static void RegisterToggles(IEnumerable<Upgrade> upgrades, string section)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			foreach (Upgrade upgrade in upgrades)
			{
				if (!upgradeToggles.ContainsKey(upgrade.Name))
				{
					upgradeToggles[upgrade.Name] = PluginConfig.ConfigFile.Bind<bool>(section, upgrade.CleanName, !_disabledByDefault.Contains(upgrade.Name), "Enable sharing for " + upgrade.CleanName);
					limitSliders[upgrade.Name] = PluginConfig.ConfigFile.Bind<int>(section, upgrade.CleanName + " Share Limit", 0, new ConfigDescription("Others won't receive this upgrade past this level (0 = unlimited)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
				}
			}
		}
	}
	public static class DiscoveryService
	{
		public static DiscoveredUpgradesResult DiscoveredUpgrades(StatsManager statsManager)
		{
			HashSet<string> hashSet = new HashSet<string>();
			HashSet<string> hashSet2 = new HashSet<string>();
			foreach (string item in statsManager.dictionaryOfDictionaries.Keys.Where((string key) => key.StartsWith("playerUpgrade")))
			{
				if (AccessTools.Field(typeof(StatsManager), item) != null)
				{
					hashSet.Add(item);
				}
				else
				{
					hashSet2.Add(item);
				}
			}
			return new DiscoveredUpgradesResult(hashSet, hashSet2);
		}
	}
	public static class DistributionService
	{
		private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		public static bool IsDistributing { get; private set; }

		public static void DistributeUpgrade(UpgradeContext context, string upgradeKey, int difference, int currentValue)
		{
			SharedUpgrades__.LogVerbose($"[Distribute] {context.PlayerName} bought {upgradeKey} (+{difference})");
			int num = ConfigService.UpgradeShareLimit(upgradeKey);
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null)
			{
				SharedUpgrades__.Logger.LogWarning((object)"[Distribute] PhotonView not found on PunManager, can't distribute.");
				return;
			}
			bool flag = RegistryService.Instance.IsVanilla(upgradeKey);
			if (!flag && !ConfigService.IsModdedUpgradesEnabled())
			{
				SharedUpgrades__.LogInfo("[Distribute] " + upgradeKey + " is modded and modded upgrades are off, skipping.");
				return;
			}
			if (!ConfigService.IsUpgradeEnabled(upgradeKey))
			{
				SharedUpgrades__.LogInfo("[Distribute] " + upgradeKey + " is disabled in config, skipping.");
				return;
			}
			string text = (flag ? new Upgrade(upgradeKey).CleanName : null);
			List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
			int num2 = ConfigService.SharedUpgradesChancePercentage();
			SharedUpgrades__.LogVerbose($"[Distribute] {upgradeKey} (+{difference}) — {list.Count} player(s), limit={num}, chance={num2}%");
			IsDistributing = true;
			int num3 = 0;
			int num4 = 0;
			try
			{
				foreach (PlayerAvatar item in list)
				{
					if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == context.ViewID)
					{
						continue;
					}
					string text2 = (string)_steamID.GetValue(item);
					if (string.IsNullOrEmpty(text2))
					{
						continue;
					}
					int value = 0;
					if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value2))
					{
						value2.TryGetValue(text2, out value);
					}
					SharedUpgrades__.LogVerbose($"[Distribute]   {item.playerName} — level={value}, limit={num}");
					if (num > 0 && num <= value)
					{
						SharedUpgrades__.LogInfo($"[Distribute]   {item.playerName} hit share limit ({num}), skipping.");
						num4++;
						continue;
					}
					if (!ConfigService.RollSharedUpgradesChance())
					{
						SharedUpgrades__.LogInfo($"[Distribute]   {item.playerName} roll failed ({num2}%), skipping.");
						num4++;
						continue;
					}
					if (flag)
					{
						SharedUpgrades__.LogVerbose("[Distribute]   sending TesterUpgradeCommandRPC to " + item.playerName);
						component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { text2, text, difference });
					}
					else
					{
						SharedUpgrades__.LogVerbose("[Distribute]   sending UpdateStatRPC to " + item.playerName);
						component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, text2, currentValue });
					}
					SharedUpgrades__.LogInfo($"[Distribute]   sent {upgradeKey} (+{difference}) to {item.playerName}.");
					num3++;
				}
			}
			catch (Exception ex)
			{
				SharedUpgrades__.Logger.LogError((object)("[Distribute] exception distributing " + upgradeKey + " for " + context.PlayerName + ": " + ex.Message));
			}
			finally
			{
				IsDistributing = false;
			}
			SharedUpgrades__.LogVerbose($"[Distribute] done — {upgradeKey}: sent={num3}, skipped={num4}");
			HealBuyer(context, upgradeKey, difference);
		}

		private static void HealBuyer(UpgradeContext context, string upgradeKey, int difference)
		{
			if (upgradeKey != "playerUpgradeHealth" || !ConfigService.IsSharedUpgradeHealEnabled())
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(context.SteamID);
			if (!((Object)(object)val == (Object)null))
			{
				int num = val.playerHealth.maxHealth + 20 * difference - val.playerHealth.health;
				SharedUpgrades__.LogVerbose($"[Distribute] healing {context.PlayerName} — max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}");
				if (num > 0)
				{
					val.playerHealth.HealOther(num, false);
				}
			}
		}
	}
	public class NetworkCallbackService : MonoBehaviourPunCallbacks
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass12_0
		{
			public Player joiningPlayer;

			public Func<PlayerAvatar, bool> <>9__0;

			internal bool <WaitAndSync>b__0(PlayerAvatar p)
			{
				int result;
				if ((Object)(object)p.photonView != (Object)null)
				{
					Player owner = p.photonView.Owner;
					result = ((((owner != null) ? new int?(owner.ActorNumber) : null) == joiningPlayer.ActorNumber) ? 1 : 0);
				}
				else
				{
					result = 0;
				}
				return (byte)result != 0;
			}
		}

		[CompilerGenerated]
		private sealed class <WaitAndSync>d__12 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Player joiningPlayer;

			public NetworkCallbackService <>4__this;

			private <>c__DisplayClass12_0 <>8__1;

			private float <elapsed>5__2;

			private PlayerAvatar <avatar>5__3;

			private string <steamID>5__4;

			private Dictionary<string, int> <teamSnapshot>5__5;

			private bool <tumbleReady>5__6;

			private bool <grabberReady>5__7;

			private bool <healthReady>5__8;

			private bool <statsReady>5__9;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WaitAndSync>d__12(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<avatar>5__3 = null;
				<steamID>5__4 = null;
				<teamSnapshot>5__5 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b3: Expected O, but got Unknown
				//IL_037a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0384: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass12_0();
					<>8__1.joiningPlayer = joiningPlayer;
					<elapsed>5__2 = 0f;
					<avatar>5__3 = null;
					<steamID>5__4 = string.Empty;
					SharedUpgrades__.LogVerbose($"Waiting for {<>8__1.joiningPlayer.NickName} to be ready (max {12f}s)...");
					goto IL_028f;
				case 1:
					<>1__state = -1;
					<elapsed>5__2 += 0.3f;
					if (<avatar>5__3 == null)
					{
						<avatar>5__3 = ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetAll()).FirstOrDefault((Func<PlayerAvatar, bool>)delegate(PlayerAvatar p)
						{
							int result;
							if ((Object)(object)p.photonView != (Object)null)
							{
								Player owner = p.photonView.Owner;
								result = ((((owner != null) ? new int?(owner.ActorNumber) : null) == <>8__1.joiningPlayer.ActorNumber) ? 1 : 0);
							}
							else
							{
								result = 0;
							}
							return (byte)result != 0;
						});
					}
					if ((Object)(object)<avatar>5__3 != (Object)null)
					{
						<steamID>5__4 = (string)_steamID.GetValue(<avatar>5__3);
					}
					<tumbleReady>5__6 = (Object)(object)<avatar>5__3 != (Object)null && _tumble.GetValue(<avatar>5__3) != null;
					<grabberReady>5__7 = (Object)(object)<avatar>5__3 != (Object)null && _physGrabber.GetValue(<avatar>5__3) != null;
					<healthReady>5__8 = (Object)(object)<avatar>5__3 != (Object)null && _playerHealth.GetValue(<avatar>5__3) != null;
					<statsReady>5__9 = !string.IsNullOrEmpty(<steamID>5__4) && StatsManager.instance.playerUpgradeStrength.ContainsKey(<steamID>5__4);
					SharedUpgrades__.LogVerbose($"{<>8__1.joiningPlayer.NickName} not ready yet ({<elapsed>5__2:F1}s) — tumble={<tumbleReady>5__6}, grabber={<grabberReady>5__7}, health={<healthReady>5__8}, stats={<statsReady>5__9}");
					if (!(!string.IsNullOrEmpty(<steamID>5__4) & <tumbleReady>5__6 & <grabberReady>5__7 & <healthReady>5__8 & <statsReady>5__9))
					{
						goto IL_028f;
					}
					goto IL_02a5;
				case 2:
					<>1__state = -1;
					<teamSnapshot>5__5 = SnapshotService.SnapshotTeamMaxLevels(<steamID>5__4);
					SharedUpgrades__.LogVerbose(<>8__1.joiningPlayer.NickName + " — snapshot taken (exclude=" + <steamID>5__4 + "), starting sync.");
					<>2__current = SyncService.ApplyTeamSnapshot(<avatar>5__3, <steamID>5__4, <teamSnapshot>5__5);
					<>1__state = 3;
					return true;
				case 3:
					{
						<>1__state = -1;
						return false;
					}
					IL_02a5:
					if ((Object)(object)<avatar>5__3 == (Object)null || string.IsNullOrEmpty(<steamID>5__4) || _tumble.GetValue(<avatar>5__3) == null || _physGrabber.GetValue(<avatar>5__3) == null || _playerHealth.GetValue(<avatar>5__3) == null || !StatsManager.instance.playerUpgradeStrength.ContainsKey(<steamID>5__4))
					{
						SharedUpgrades__.Logger.LogWarning((object)$"Late join: timed out waiting for {<>8__1.joiningPlayer.NickName} after {12f}s, skipping sync.");
						return false;
					}
					SharedUpgrades__.LogVerbose(<>8__1.joiningPlayer.NickName + " (" + <steamID>5__4 + ") is ready, waiting for stats to stabilize...");
					<>2__current = (object)new WaitForSeconds(2f);
					<>1__state = 2;
					return true;
					IL_028f:
					if (<elapsed>5__2 < 12f)
					{
						<>2__current = (object)new WaitForSeconds(0.3f);
						<>1__state = 1;
						return true;
					}
					goto IL_02a5;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		private static readonly FieldInfo _tumble = AccessTools.Field(typeof(PlayerAvatar), "tumble");

		private static readonly FieldInfo _physGrabber = AccessTools.Field(typeof(PlayerAvatar), "physGrabber");

		private static readonly FieldInfo _playerHealth = AccessTools.Field(typeof(PlayerAvatar), "playerHealth");

		private readonly HashSet<Player> _pendingSync = new HashSet<Player>();

		public override void OnJoinedRoom()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0041: Expected O, but got Unknown
			SharedUpgrades__.LogVerbose($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})");
			try
			{
				if (PhotonNetwork.IsMasterClient)
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"su__v1", (object)"1.3.1");
					Hashtable val2 = val;
					PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
					SharedUpgrades__.LogVerbose("Set room property: su__v1=1.3.1");
				}
			}
			catch (Exception ex)
			{
				SharedUpgrades__.Logger.LogError((object)("Couldn't set room properties: " + ex.Message));
			}
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			SharedUpgrades__.LogVerbose($"OnPlayerEnteredRoom: {newPlayer.NickName} (isMaster={SemiFunc.IsMasterClientOrSingleplayer()}, activeRun={IsActiveRun()}, lateJoin={ConfigService.IsLateJoinSyncEnabled()})");
			if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer())
			{
				if (!IsActiveRun())
				{
					_pendingSync.Add(newPlayer);
					SharedUpgrades__.LogAlways($"Deferred sync: {newPlayer.NickName} joined outside of a level, queued. ({_pendingSync.Count} pending)");
				}
				else
				{
					SharedUpgrades__.LogVerbose("Starting immediate sync for " + newPlayer.NickName + ".");
					((MonoBehaviour)this).StartCoroutine(WaitAndSync(newPlayer));
				}
			}
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			bool flag = _pendingSync.Remove(otherPlayer);
			SharedUpgrades__.LogVerbose($"OnPlayerLeftRoom: {otherPlayer.NickName} (was pending: {flag}, pending count: {_pendingSync.Count})");
		}

		private static bool IsActiveRun()
		{
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				return false;
			}
			Level levelCurrent = RunManager.instance.levelCurrent;
			return (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelMainMenu && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelLobbyMenu && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelRecording && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelSplashScreen;
		}

		public override void OnEnable()
		{
			((MonoBehaviourPunCallbacks)this).OnEnable();
			SceneManager.sceneLoaded += OnSceneLoaded;
		}

		public override void OnDisable()
		{
			((MonoBehaviourPunCallbacks)this).OnDisable();
			SceneManager.sceneLoaded -= OnSceneLoaded;
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			SharedUpgrades__.LogVerbose($"OnSceneLoaded: {((Scene)(ref scene)).name} ({_pendingSync.Count} pending, activeRun={IsActiveRun()})");
			if (_pendingSync.Count == 0 || !IsActiveRun() || !ConfigService.IsLateJoinSyncEnabled() || !ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			SharedUpgrades__.LogAlways($"Deferred sync: {((Scene)(ref scene)).name} loaded, processing {_pendingSync.Count} queued player(s).");
			foreach (Player item in _pendingSync)
			{
				SharedUpgrades__.LogVerbose("Starting deferred sync for " + item.NickName + ".");
				((MonoBehaviour)this).StartCoroutine(WaitAndSync(item));
			}
			_pendingSync.Clear();
		}

		[IteratorStateMachine(typeof(<WaitAndSync>d__12))]
		private IEnumerator WaitAndSync(Player joiningPlayer)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WaitAndSync>d__12(0)
			{
				<>4__this = this,
				joiningPlayer = joiningPlayer
			};
		}
	}
	public sealed class RegistryService
	{
		private HashSet<Upgrade> vanillaUpgrades = null;

		private HashSet<Upgrade> moddedUpgrades = null;

		private static readonly RegistryService instance = new RegistryService();

		public IReadOnlyCollection<Upgrade> VanillaUpgrades => vanillaUpgrades;

		public IReadOnlyCollection<Upgrade> ModdedUpgrades => moddedUpgrades;

		public static RegistryService Instance => instance;

		private RegistryService()
		{
			vanillaUpgrades = new HashSet<Upgrade>();
			moddedUpgrades = new HashSet<Upgrade>();
		}

		public void RegisterAll(DiscoveredUpgradesResult result)
		{
			vanillaUpgrades.UnionWith(result.Vanilla.Select(MakeUpgradeFromKey));
			moddedUpgrades.UnionWith(result.Modded.Select(MakeUpgradeFromKey));
			SharedUpgrades__.Logger.LogInfo((object)$"Discovered {vanillaUpgrades.Count} vanilla and {moddedUpgrades.Count} modded upgrade(s).");
			if (result.Vanilla.Count > 0)
			{
				SharedUpgrades__.LogVerbose("Vanilla: " + string.Join(", ", result.Vanilla));
			}
			if (result.Modded.Count > 0)
			{
				SharedUpgrades__.LogVerbose("Modded: " + string.Join(", ", result.Modded));
			}
		}

		public void Clear()
		{
			SharedUpgrades__.LogVerbose($"Registry cleared ({vanillaUpgrades.Count} vanilla, {moddedUpgrades.Count} modded).");
			vanillaUpgrades.Clear();
			moddedUpgrades.Clear();
		}

		public bool IsVanilla(string key)
		{
			string key2 = key;
			return vanillaUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2));
		}

		public bool IsRegistered(string key)
		{
			string key2 = key;
			return IsVanilla(key2) || moddedUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2));
		}

		private Upgrade MakeUpgradeFromKey(string key)
		{
			return new Upgrade(key);
		}
	}
	public static class SnapshotService
	{
		public static Dictionary<string, int> SnapshotPlayerStats(string steamID)
		{
			string steamID2 = steamID;
			if (string.IsNullOrEmpty(steamID2) || StatsManager.instance == null)
			{
				return new Dictionary<string, int>();
			}
			Dictionary<string, int> dictionary = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> kvp) => RegistryService.Instance.IsRegistered(kvp.Key)).ToDictionary((KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Value.GetValueOrDefault(steamID2, 0));
			SharedUpgrades__.LogVerbose($"[Snapshot] Player snapshot for {steamID2} — {dictionary.Count} upgrade(s).");
			return dictionary;
		}

		public static Dictionary<string, int> SnapshotTeamMaxLevels(string? excludeSteamID = null)
		{
			string excludeSteamID2 = excludeSteamID;
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (StatsManager.instance == null)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, Dictionary<string, int>> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> k) => RegistryService.Instance.IsRegistered(k.Key)))
			{
				IEnumerable<int> enumerable;
				if (!string.IsNullOrEmpty(excludeSteamID2))
				{
					enumerable = from p in item.Value
						where p.Key != excludeSteamID2
						select p.Value;
				}
				else
				{
					IEnumerable<int> values = item.Value.Values;
					enumerable = values;
				}
				IEnumerable<int> source = enumerable;
				dictionary[item.Key] = source.DefaultIfEmpty(0).Max();
			}
			SharedUpgrades__.LogVerbose(string.Format("[Snapshot] Team snapshot (exclude={0}) — {1} upgrade(s).", excludeSteamID2 ?? "none", dictionary.Count));
			return dictionary;
		}
	}
	public static class SyncService
	{
		[CompilerGenerated]
		private sealed class <ApplyTeamSnapshot>d__1 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerAvatar player;

			public string steamID;

			public Dictionary<string, int> teamSnapshot;

			private PhotonView <photonView>5__1;

			private string <playerName>5__2;

			private int <chance>5__3;

			private int <sent>5__4;

			private int <skipped>5__5;

			private Dictionary<string, int>.Enumerator <>s__6;

			private KeyValuePair<string, int> <kvp>5__7;

			private int <upgradeLimit>5__8;

			private bool <isVanilla>5__9;

			private int <playerLevel>5__10;

			private Dictionary<string, int> <upgradeDict>5__11;

			private int <value>5__12;

			private int <difference>5__13;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ApplyTeamSnapshot>d__1(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<photonView>5__1 = null;
				<playerName>5__2 = null;
				<>s__6 = default(Dictionary<string, int>.Enumerator);
				<kvp>5__7 = default(KeyValuePair<string, int>);
				<upgradeDict>5__11 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				if (<>1__state != 0)
				{
					return false;
				}
				<>1__state = -1;
				if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
				{
					return false;
				}
				<photonView>5__1 = ((Component)PunManager.instance).GetComponent<PhotonView>();
				if ((Object)(object)<photonView>5__1 == (Object)null)
				{
					SharedUpgrades__.Logger.LogWarning((object)"[LateJoin] PhotonView not found on PunManager, skipping sync.");
					return false;
				}
				<playerName>5__2 = (string)_playerName.GetValue(player);
				<chance>5__3 = ConfigService.SharedUpgradesChancePercentage();
				SharedUpgrades__.LogAlways($"[LateJoin] syncing {<playerName>5__2} — {teamSnapshot.Count} upgrade(s), chance={<chance>5__3}%");
				<sent>5__4 = 0;
				<skipped>5__5 = 0;
				<>s__6 = teamSnapshot.GetEnumerator();
				try
				{
					while (<>s__6.MoveNext())
					{
						<kvp>5__7 = <>s__6.Current;
						<upgradeLimit>5__8 = ConfigService.UpgradeShareLimit(<kvp>5__7.Key);
						<isVanilla>5__9 = RegistryService.Instance.IsVanilla(<kvp>5__7.Key);
						SharedUpgrades__.LogVerbose($"[LateJoin]   {<kvp>5__7.Key} — teamMax={<kvp>5__7.Value}, isVanilla={<isVanilla>5__9}, limit={<upgradeLimit>5__8}");
						if (!<isVanilla>5__9 && !ConfigService.IsModdedUpgradesEnabled())
						{
							SharedUpgrades__.LogVerbose("[LateJoin]   " + <kvp>5__7.Key + " — skipped (modded upgrades disabled).");
							<skipped>5__5++;
							continue;
						}
						if (!ConfigService.IsUpgradeEnabled(<kvp>5__7.Key))
						{
							SharedUpgrades__.LogVerbose("[LateJoin]   " + <kvp>5__7.Key + " — skipped (disabled in config).");
							<skipped>5__5++;
							continue;
						}
						<playerLevel>5__10 = (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(<kvp>5__7.Key, out <upgradeDict>5__11) ? <upgradeDict>5__11.GetValueOrDefault(steamID, 0) : 0);
						if (<upgradeLimit>5__8 > 0 && <upgradeLimit>5__8 <= <playerLevel>5__10)
						{
							SharedUpgrades__.LogInfo($"[LateJoin]   {<kvp>5__7.Key} — {<playerName>5__2} hit share limit ({<upgradeLimit>5__8}), skipping.");
							<skipped>5__5++;
							continue;
						}
						<value>5__12 = <kvp>5__7.Value;
						<difference>5__13 = <value>5__12 - <playerLevel>5__10;
						if (<upgradeLimit>5__8 > 0)
						{
							<difference>5__13 = Math.Min(<difference>5__13, <upgradeLimit>5__8 - <playerLevel>5__10);
						}
						SharedUpgrades__.LogVerbose($"[LateJoin]   {<kvp>5__7.Key} — level={<playerLevel>5__10}, teamMax={<kvp>5__7.Value}, diff={<difference>5__13} (pre-roll)");
						<difference>5__13 = SimulateRealisticLevelling(<difference>5__13);
						<value>5__12 = <playerLevel>5__10 + <difference>5__13;
						if (<difference>5__13 <= 0)
						{
							SharedUpgrades__.LogInfo("[LateJoin]   " + <kvp>5__7.Key + " — rolled 0 after chance simulation, skipping.");
							<skipped>5__5++;
							continue;
						}
						if (<isVanilla>5__9)
						{
							SharedUpgrades__.LogVerbose("[LateJoin]   " + <kvp>5__7.Key + " — sending TesterUpgradeCommandRPC to " + <playerName>5__2);
							<photonView>5__1.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3]
							{
								steamID,
								new Upgrade(<kvp>5__7.Key).CleanName,
								<difference>5__13
							});
						}
						else
						{
							SharedUpgrades__.LogVerbose("[LateJoin]   " + <kvp>5__7.Key + " — sending UpdateStatRPC to " + <playerName>5__2);
							<photonView>5__1.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { <kvp>5__7.Key, steamID, <value>5__12 });
						}
						SharedUpgrades__.LogVerbose($"[LateJoin]   sent {<kvp>5__7.Key} (+{<difference>5__13}) to {<playerName>5__2}.");
						<sent>5__4++;
						<upgradeDict>5__11 = null;
						<kvp>5__7 = default(KeyValuePair<string, int>);
					}
				}
				finally
				{
					((IDisposable)<>s__6).Dispose();
				}
				<>s__6 = default(Dictionary<string, int>.Enumerator);
				SharedUpgrades__.LogAlways($"[LateJoin] done — {<playerName>5__2}: sent={<sent>5__4}, skipped={<skipped>5__5}");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");

		[IteratorStateMachine(typeof(<ApplyTeamSnapshot>d__1))]
		public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary<string, int> teamSnapshot)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ApplyTeamSnapshot>d__1(0)
			{
				player = player,
				steamID = steamID,
				teamSnapshot = teamSnapshot
			};
		}

		private static int SimulateRealisticLevelling(int value)
		{
			int num = ConfigService.SharedUpgradesChancePercentage();
			if (num >= 100 || value <= 0)
			{
				return value;
			}
			int num2 = 0;
			for (int i = 0; i < value; i++)
			{
				if (ConfigService.RollSharedUpgradesChance())
				{
					num2++;
				}
			}
			SharedUpgrades__.LogVerbose($"[LateJoin] roll simulation — input={value}, chance={num}%, result={num2}");
			return num2;
		}
	}
	internal class WatermarkService : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <Poll>d__11 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public WatermarkService <>4__this;

			private float <elapsed>5__1;

			private PlayerAvatar <localPlayer>5__2;

			private bool <isHost>5__3;

			private bool <modPresent>5__4;

			private object <val>5__5;

			private List<PlayerAvatar>.Enumerator <>s__6;

			private PlayerAvatar <p>5__7;

			object? IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object? IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <Poll>d__11(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<localPlayer>5__2 = null;
				<val>5__5 = null;
				<>s__6 = default(List<PlayerAvatar>.Enumerator);
				<p>5__7 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Expected O, but got Unknown
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					<elapsed>5__1 += 1f;
					try
					{
						if (PhotonNetwork.InRoom)
						{
							if (string.IsNullOrEmpty(OwnerID))
							{
								goto IL_0233;
							}
							<localPlayer>5__2 = null;
							<>s__6 = SemiFunc.PlayerGetAll().GetEnumerator();
							try
							{
								while (<>s__6.MoveNext())
								{
									<p>5__7 = <>s__6.Current;
									if ((Object)(object)<p>5__7?.photonView != (Object)null && <p>5__7.photonView.IsMine)
									{
										<localPlayer>5__2 = <p>5__7;
										break;
									}
									<p>5__7 = null;
								}
							}
							finally
							{
								((IDisposable)<>s__6).Dispose();
							}
							<>s__6 = default(List<PlayerAvatar>.Enumerator);
							if (!((Object)(object)<localPlayer>5__2 == (Object)null))
							{
								if ((string)_steamID.GetValue(<localPlayer>5__2) != OwnerID)
								{
									goto IL_0233;
								}
								<isHost>5__3 = PhotonNetwork.IsMasterClient;
								<modPresent>5__4 = ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).ContainsKey((object)"su__v1");
								if (<isHost>5__3 | <modPresent>5__4)
								{
									if (((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"su__v1", out <val>5__5))
									{
										Version = (<val>5__5 as string) ?? "UNKNOWN";
									}
									<>4__this.show = !<isHost>5__3 & <modPresent>5__4;
									goto IL_0233;
								}
								<localPlayer>5__2 = null;
								<val>5__5 = null;
							}
						}
					}
					catch
					{
					}
				}
				else
				{
					<>1__state = -1;
					<>4__this.polling = true;
					<elapsed>5__1 = 0f;
				}
				if (<elapsed>5__1 < 10f)
				{
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 1;
					return true;
				}
				goto IL_0233;
				IL_0233:
				<>4__this.polling = false;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		internal const string RoomKey = "su__v1";

		private static string Version = "UNKNOWN";

		private static readonly string? OwnerID = LoadOwnerID();

		private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		private bool show;

		private bool polling;

		private Object? lastLevel;

		private string? lastRoom;

		private GUIStyle? style;

		private static string? LoadOwnerID()
		{
			try
			{
				string path = Path.Combine(Paths.ConfigPath, "SharedUpgrades++.owner");
				if (!File.Exists(path))
				{
					return null;
				}
				return File.ReadAllText(path).Trim();
			}
			catch
			{
				return null;
			}
		}

		private void Update()
		{
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				return;
			}
			Level levelCurrent = RunManager.instance.levelCurrent;
			Room currentRoom = PhotonNetwork.CurrentRoom;
			string text = ((currentRoom != null) ? currentRoom.Name : null);
			if ((Object)(object)levelCurrent == lastLevel && text == lastRoom)
			{
				return;
			}
			lastLevel = (Object?)(object)levelCurrent;
			lastRoom = text;
			if (text == null)
			{
				show = false;
			}
			if ((Object)(object)levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu)
			{
				show = false;
				if (!polling)
				{
					((MonoBehaviour)this).StartCoroutine(Poll());
				}
			}
		}

		[IteratorStateMachine(typeof(<Poll>d__11))]
		private IEnumerator Poll()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <Poll>d__11(0)
			{
				<>4__this = this
			};
		}

		private void OnGUI()
		{
			//IL_0078: 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)
			//IL_002d: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			if (!show)
			{
				return;
			}
			try
			{
				if (style == null)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label)
					{
						fontSize = 24
					};
					val.normal.textColor = new Color(1f, 1f, 1f, 0.15f);
					style = val;
				}
				GUI.Label(new Rect(6f, (float)(Screen.height - 72), 160f, 56f), "SUP: " + Version, style);
			}
			catch
			{
			}
		}
	}
}
namespace SharedUpgrades__.Patches
{
	[HarmonyPatch(typeof(PunManager), "UpdateStatRPC")]
	internal class ModdedUpgradesPatch
	{
		private static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");

		[HarmonyPostfix]
		public static void Postfix(string dictionaryName, string key, int value)
		{
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			if (!ConfigService.IsSharedUpgradesEnabled() || !ConfigService.IsModdedUpgradesEnabled() || !RegistryService.Instance.IsRegistered(dictionaryName) || RegistryService.Instance.IsVanilla(dictionaryName) || !ConfigService.IsUpgradeEnabled(dictionaryName))
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(key);
			SharedUpgrades__.LogVerbose(string.Format("[ModdedPatch] {0} ({1}) — value={2}, player={3}, distributing={4}", dictionaryName, key, value, val?.playerName ?? "not found", DistributionService.IsDistributing));
			if ((Object)(object)val != (Object)null && ConfigService.IsShareNotificationEnabled())
			{
				SharedUpgrades__.LogVerbose("[ModdedPatch] running effects for " + val.playerName);
				if (val.isLocal)
				{
					SharedUpgrades__.LogVerbose("[ModdedPatch] local player, triggering StatsUI + CameraGlitch.");
					StatsUI.instance.Fetch();
					StatsUI.instance.ShowStats();
					CameraGlitch.Instance.PlayUpgrade();
				}
				else
				{
					SharedUpgrades__.LogVerbose("[ModdedPatch] remote player, shaking camera.");
					GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f);
				}
				if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
				{
					SharedUpgrades__.LogVerbose("[ModdedPatch] applying upgrade material effect to " + val.playerName + ".");
					val.playerHealth.MaterialEffectOverride((Effect)0);
				}
			}
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				if (DistributionService.IsDistributing)
				{
					SharedUpgrades__.LogVerbose("[ModdedPatch] already distributing, skipping " + dictionaryName + ".");
				}
				else if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null)
				{
					SharedUpgrades__.Logger.LogWarning((object)("[ModdedPatch] no PlayerAvatar found for " + key + ", can't distribute " + dictionaryName + "."));
				}
				else
				{
					string text = (string)_playerName.GetValue(val);
					SharedUpgrades__.LogAlways("[ModdedPatch] " + text + " bought " + dictionaryName + ", distributing...");
					UpgradeContext context = new UpgradeContext(key, val.photonView.ViewID, text, new Dictionary<string, int>());
					DistributionService.DistributeUpgrade(context, dictionaryName, 1, value);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")]
	internal class PlayerUpgradeEffectPatch
	{
		[HarmonyPostfix]
		public static void Postfix(string _steamID, string upgradeName, int upgradeNum, PhotonMessageInfo _info)
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			bool flag = upgradeName == "Health";
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID);
			if ((Object)(object)val == (Object)null)
			{
				SharedUpgrades__.Logger.LogError((object)("[Effects] TesterUpgradeCommandRPC fired for " + _steamID + " but no PlayerAvatar found — skipping effects."));
				return;
			}
			SharedUpgrades__.LogVerbose($"[Effects] {val.playerName} got {upgradeName} x{upgradeNum} (local={val.isLocal})");
			if (ConfigService.IsShareNotificationEnabled())
			{
				if (val.isLocal)
				{
					SharedUpgrades__.LogVerbose("[Effects] " + val.playerName + " is local — StatsUI + CameraGlitch.");
					StatsUI.instance.Fetch();
					StatsUI.instance.ShowStats();
					CameraGlitch.Instance.PlayUpgrade();
				}
				else
				{
					SharedUpgrades__.LogVerbose("[Effects] " + val.playerName + " is remote — camera shake.");
					GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f);
				}
				if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
				{
					SharedUpgrades__.LogVerbose("[Effects] applying upgrade material effect to " + val.playerName + ".");
					val.playerHealth.MaterialEffectOverride((Effect)0);
				}
			}
			SharedUpgrades__.LogVerbose("[Effects] " + val.playerName + " effects done.");
			if (flag && SemiFunc.IsMasterClientOrSingleplayer() && ConfigService.IsSharedUpgradeHealEnabled())
			{
				int num = val.playerHealth.maxHealth + 20 * upgradeNum - val.playerHealth.health;
				SharedUpgrades__.LogVerbose($"[Effects] healing {val.playerName} — max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}");
				if (num > 0)
				{
					val.playerHealth.HealOther(num, false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	internal class REPOLibSyncPatch
	{
		private static readonly Type _upgradeType = AccessTools.TypeByName("REPOLib.Modules.Upgrades");

		private static readonly Type _playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade");

		private static readonly FieldInfo? _playerDictionaryField = ((_playerUpgradeType != null) ? AccessTools.Field(_playerUpgradeType, "PlayerDictionary") : null);

		private static readonly FieldInfo? _playerUpgradesField = ((_upgradeType != null) ? AccessTools.Field(_upgradeType, "_playerUpgrades") : null);

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		public static void Postfix()
		{
			if (_playerDictionaryField == null || _playerUpgradesField == null || (Object)(object)StatsManager.instance == (Object)null || !(_playerUpgradesField.GetValue(null) is IDictionary dictionary))
			{
				return;
			}
			SharedUpgrades__.LogVerbose($"Syncing {dictionary.Count} REPOLib upgrade(s) to StatsManager.");
			int num = 0;
			foreach (DictionaryEntry item in dictionary)
			{
				if (item.Value != null)
				{
					string text = $"playerUpgrade{item.Key}";
					if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(text, out var value))
					{
						_playerDictionaryField.SetValue(item.Value, value);
						SharedUpgrades__.LogInfo("Synced PlayerDictionary for " + text + ".");
						num++;
					}
				}
			}
			SharedUpgrades__.LogVerbose($"REPOLib sync done — {num}/{dictionary.Count} upgrade(s).");
		}
	}
	[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
	internal class SharedUpgradesPatch
	{
		private static readonly FieldInfo _itemToggle = AccessTools.Field(typeof(ItemUpgrade), "itemToggle");

		private static readonly FieldInfo _playerTogglePhotonId = AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID");

		private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");

		private static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");

		private static readonly FieldInfo _itemAttributes = AccessTools.Field(typeof(ItemUpgrade), "itemAttributes");

		private static PlayerAvatar? GetUpgradePlayer(ItemUpgrade instance, out int viewID)
		{
			viewID = 0;
			object? value = _itemToggle.GetValue(instance);
			ItemToggle val = (ItemToggle)((value is ItemToggle) ? value : null);
			if (val == null || !val.toggleState)
			{
				return null;
			}
			viewID = (int)_playerTogglePhotonId.GetValue(val);
			return SemiFunc.PlayerAvatarGetFromPhotonID(viewID);
		}

		[HarmonyPrefix]
		public static void Prefix(ItemUpgrade __instance, out UpgradeContext? __state)
		{
			__state = null;
			if (!ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			int viewID;
			PlayerAvatar upgradePlayer = GetUpgradePlayer(__instance, out viewID);
			if (upgradePlayer == null)
			{
				SharedUpgrades__.LogVerbose("[Purchase] upgrade interaction fired but couldn't find a player, skipping.");
				return;
			}
			string text = (string)_steamID.GetValue(upgradePlayer);
			if (!string.IsNullOrEmpty(text))
			{
				string text2 = null;
				object? value = _itemAttributes.GetValue(__instance);
				ItemAttributes val = (ItemAttributes)((value is ItemAttributes) ? value : null);
				if (val != null && (Object)(object)val.item != (Object)null)
				{
					text2 = ((Object)val.item).name;
				}
				SharedUpgrades__.LogVerbose("[Purchase] " + upgradePlayer.playerName + " is buying '" + text2 + "'");
				__state = new UpgradeContext(text, playerName: (string)_playerName.GetValue(upgradePlayer), viewID: viewID, levelsBefore: SnapshotService.SnapshotPlayerStats(text), itemName: text2);
			}
		}

		[HarmonyPostfix]
		public static void Postfix(UpgradeContext? __state)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || __state == null)
			{
				return;
			}
			SharedUpgrades__.LogVerbose("[Purchase] checking what " + __state.PlayerName + " just bought (item='" + __state.ItemName + "')");
			bool flag = false;
			IEnumerable<KeyValuePair<string, Dictionary<string, int>>> enumerable = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> key) => RegistryService.Instance.IsRegistered(key.Key));
			foreach (KeyValuePair<string, Dictionary<string, int>> item in enumerable)
			{
				item.Value.TryGetValue(__state.SteamID, out var value);
				__state.LevelsBefore.TryGetValue(item.Key, out var value2);
				SharedUpgrades__.LogVerbose($"[Purchase]   {item.Key}: {value2} → {value}");
				if (value > value2)
				{
					int num = value - value2;
					flag = true;
					SharedUpgrades__.LogAlways($"[Purchase] {__state.PlayerName} bought {item.Key} (+{num}), distributing...");
					DistributionService.DistributeUpgrade(__state, item.Key, num, value);
				}
			}
			SharedUpgrades__.LogVerbose($"[Purchase] vanilla scan done, distributed={flag}");
			if (!flag && __state.ItemName != null && ConfigService.IsModdedUpgradesEnabled())
			{
				SharedUpgrades__.LogVerbose("[Purchase] no vanilla upgrades changed — checking modded match for '" + __state.ItemName + "'");
				string text = MatchItemNameToModdedUpgrade(__state.ItemName);
				if (text != null)
				{
					__state.LevelsBefore.TryGetValue(text, out var value3);
					int currentValue = value3 + 1;
					SharedUpgrades__.LogInfo("[Purchase] " + __state.PlayerName + " (" + __state.SteamID + ") bought modded " + text + " (+1), distributing...");
					DistributionService.DistributeUpgrade(__state, text, 1, currentValue);
				}
				else
				{
					SharedUpgrades__.LogVerbose("[Purchase] no match for '" + __state.ItemName + "', nothing to distribute.");
				}
			}
		}

		private static string? MatchItemNameToModdedUpgrade(string itemName)
		{
			string text = itemName.Replace(" ", "");
			foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades)
			{
				string value = moddedUpgrade.CleanName.Replace(" ", "");
				if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					return moddedUpgrade.Name;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(StatsManager), "Start")]
	internal class StatsManagerPatch
	{
		private static NetworkCallbackService? _callbackService;

		[HarmonyPostfix]
		public static void Postfix(StatsManager __instance)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			SharedUpgrades__.LogVerbose("StatsManager.Start — discovering upgrades.");
			DiscoveredUpgradesResult discoveredUpgradesResult = DiscoveryService.DiscoveredUpgrades(__instance);
			SharedUpgrades__.LogVerbose($"Found {discoveredUpgradesResult.Vanilla.Count} vanilla and {discoveredUpgradesResult.Modded.Count} modded upgrade(s).");
			RegistryService.Instance.Clear();
			RegistryService.Instance.RegisterAll(discoveredUpgradesResult);
			ConfigService.LoadModsIntoConfig();
			if ((Object)(object)_callbackService == (Object)null)
			{
				GameObject val = new GameObject("NetworkCallbackService");
				_callbackService = val.AddComponent<NetworkCallbackService>();
				val.AddComponent<WatermarkService>();
				Object.DontDestroyOnLoad((Object)(object)val);
				SharedUpgrades__.LogVerbose("Created NetworkCallbackService and WatermarkService.");
			}
			else
			{
				SharedUpgrades__.LogVerbose("NetworkCallbackService already exists, skipping.");
			}
		}
	}
}
namespace SharedUpgrades__.Models
{
	public sealed class DiscoveredUpgradesResult
	{
		public HashSet<string> Vanilla { get; }

		public HashSet<string> Modded { get; }

		public DiscoveredUpgradesResult(HashSet<string> vanilla, HashSet<string> modded)
		{
			Vanilla = vanilla;
			Modded = modded;
			base..ctor();
		}
	}
	public sealed class Upgrade : IEquatable<Upgrade>
	{
		public string Name { get; }

		public string CleanName => Name.StartsWith("playerUpgrade") ? Name.Substring("playerUpgrade".Length) : Name;

		public Upgrade(string Name)
		{
			this.Name = Name;
			base..ctor();
		}

		public bool Equals(Upgrade? other)
		{
			return other != null && other.Name == Name;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as Upgrade);
		}

		public override int GetHashCode()
		{
			return Name.GetHashCode();
		}
	}
	public sealed class UpgradeContext
	{
		public string SteamID { get; }

		public int ViewID { get; }

		public string PlayerName { get; }

		public Dictionary<string, int> LevelsBefore { get; }

		public string? ItemName { get; }

		public UpgradeContext(string steamID, int viewID, string playerName, Dictionary<string, int> levelsBefore, string? itemName = null)
		{
			SteamID = steamID;
			ViewID = viewID;
			PlayerName = playerName;
			LevelsBefore = levelsBefore;
			ItemName = itemName;
			base..ctor();
		}
	}
}
namespace SharedUpgrades__.Configuration
{
	public enum VerbosityLevel
	{
		Off,
		Debug,
		Verbose
	}
	internal static class PluginConfig
	{
		public static ConfigEntry<bool> EnableSharedUpgrades;

		public static ConfigEntry<int> SharedUpgradeChance;

		public static ConfigEntry<bool> EnableLateJoinSync;

		public static ConfigEntry<bool> EnableModdedUpgrades;

		public static ConfigEntry<bool> EnableSharedUpgradeHeal;

		public static ConfigEntry<bool> EnableShareNotification;

		public static ConfigEntry<VerbosityLevel> LoggingLevel;

		public static ConfigFile? ConfigFile;

		public static void Init(ConfigFile config)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			ConfigFile = config;
			EnableSharedUpgrades = config.Bind<bool>("General", "EnableSharedUpgrades", true, "Enable or disable all upgrade sharing");
			SharedUpgradeChance = config.Bind<int>("General", "SharedUpgradesChance", 100, new ConfigDescription("Chance per upgrade level to be shared with each player", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			EnableLateJoinSync = config.Bind<bool>("General", "LateJoinSync", true, "Sync upgrades to players who join mid-run");
			EnableModdedUpgrades = config.Bind<bool>("General", "EnableModdedUpgrades", true, "Sync upgrades added by other mods");
			EnableSharedUpgradeHeal = config.Bind<bool>("Effects", "EnableSharedUpgradeHeal", false, "Heal players to full HP when receiving a shared health upgrade");
			EnableShareNotification = config.Bind<bool>("Effects", "EnableShareNotification", true, "Provide a visual effect when upgrades are shared with you");
			LoggingLevel = config.Bind<VerbosityLevel>("General", "LogLevel", VerbosityLevel.Off, "Off: key events only (sync start/result, purchases). Debug: per-player distribution results and skips. Verbose: full trace of every step.");
		}
	}
}