Decompiled source of Shared v1.0.2

Shared.dll

Decompiled 18 hours 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.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 Shared.Patches;
using Shared.Services;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Shared")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Integrated Sharing Mod for REPO (Upgrades, Health, Damage, Battery).")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+e888876923ee23b6d8201d2e7552f540ec268467")]
[assembly: AssemblyProduct("Shared")]
[assembly: AssemblyTitle("Shared")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Shared
{
	internal static class Configuration
	{
		public static ConfigEntry<string> LoggingLevel { get; private set; }

		public static ConfigEntry<bool> ShowWatermark { get; private set; }

		public static ConfigEntry<bool> EnableUpgradeSharing { get; private set; }

		public static ConfigEntry<int> UpgradeShareChance { get; private set; }

		public static ConfigEntry<bool> WholeTeamChance { get; private set; }

		public static ConfigEntry<bool> EnableModdedUpgradeSharing { get; private set; }

		public static ConfigEntry<bool> EnableLateJoinUpgradeSync { get; private set; }

		public static ConfigEntry<float> UpgradePriceScaling { get; private set; }

		public static ConfigEntry<bool> EnableUpgradeHeal { get; private set; }

		public static ConfigEntry<bool> EnableShareNotification { get; private set; }

		public static ConfigEntry<bool> EnableHealthSharing { get; private set; }

		public static ConfigEntry<bool> EnableDeathLiability { get; private set; }

		public static ConfigEntry<int> DeathDamagePercent { get; private set; }

		public static ConfigEntry<bool> ExcludeSafeZones { get; private set; }

		public static ConfigEntry<bool> EnableBatterySharing { get; private set; }

		public static void Init(ConfigFile config)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Expected O, but got Unknown
			LoggingLevel = config.Bind<string>("General", "LoggingLevel", "Info", new ConfigDescription("Verbosity level of mod logs.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "None", "Info", "Debug", "Verbose" }), Array.Empty<object>()));
			ShowWatermark = config.Bind<bool>("General", "ShowWatermark", true, "Show a subtle watermark UI indicating the Shared mod is loaded.");
			EnableUpgradeSharing = config.Bind<bool>("Upgrades", "EnableUpgradeSharing", true, "Enable sharing player stat upgrades with the team.");
			UpgradeShareChance = config.Bind<int>("Upgrades", "UpgradeShareChance", 100, new ConfigDescription("The percentage chance that purchased upgrades are shared (0-100).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			WholeTeamChance = config.Bind<bool>("Upgrades", "WholeTeamChance", true, "If true, a single roll is performed for the team. If false, each player rolls independently.");
			EnableModdedUpgradeSharing = config.Bind<bool>("Upgrades", "EnableModdedUpgradeSharing", true, "Enable sharing of modded/custom upgrades (including those from REPOLib).");
			EnableLateJoinUpgradeSync = config.Bind<bool>("Upgrades", "EnableLateJoinUpgradeSync", true, "Synchronize existing team upgrades to players who join the lobby late.");
			UpgradePriceScaling = config.Bind<float>("Upgrades", "UpgradePriceScaling", 0.5f, new ConfigDescription("Scale the price of upgrades based on player count: Price = BasePrice * (1 + (PlayerCount - 1) * UpgradePriceScaling). Set to 0 to disable scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			EnableUpgradeHeal = config.Bind<bool>("Upgrades", "EnableUpgradeHeal", true, "Automatically heal players when sharing health capacity upgrades.");
			EnableShareNotification = config.Bind<bool>("Upgrades", "EnableShareNotification", true, "Play visual and audio cues when upgrades are shared.");
			EnableHealthSharing = config.Bind<bool>("Health", "EnableHealthSharing", true, "When a health pack is used to heal a player, heal all other players by the same amount.");
			EnableDeathLiability = config.Bind<bool>("Damage", "EnableDeathLiability", true, "When a player dies, all other players take damage as a penalty.");
			DeathDamagePercent = config.Bind<int>("Damage", "DeathDamagePercent", 10, new ConfigDescription("Percentage of max health damage survivors take on team member death (0-100). 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			ExcludeSafeZones = config.Bind<bool>("Damage", "ExcludeSafeZones", true, "Do not deal liability damage to players inside the truck or extraction point rooms.");
			EnableBatterySharing = config.Bind<bool>("Battery", "EnableBatterySharing", true, "Drain charging station power crystals instead of item battery life when using battery-powered items.");
		}

		public static bool IsVerbose()
		{
			return LoggingLevel.Value == "Verbose";
		}

		public static bool IsDebug()
		{
			if (!(LoggingLevel.Value == "Debug"))
			{
				return LoggingLevel.Value == "Verbose";
			}
			return true;
		}

		public static bool IsInfo()
		{
			return LoggingLevel.Value != "None";
		}
	}
	[BepInPlugin("semimodder-shared", "Shared", "1.0.2")]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger;

		private void Awake()
		{
			Instance = this;
			((Component)this).transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Configuration.Init(((BaseUnityPlugin)this).Config);
			Patch();
			Log.LogInfo((object)"Shared Mod v1.0.2 loaded successfully!");
		}

		private void Patch()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			if (_harmony == null)
			{
				_harmony = new Harmony("semimodder-shared");
			}
			_harmony.PatchAll();
			PatchRepoLib();
		}

		private void PatchRepoLib()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_0079: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_00bb: Expected O, but got Unknown
			if (_harmony == null)
			{
				return;
			}
			MethodInfo setLevelMethod = RepoLibInterop.SetLevelMethod;
			MethodInfo applyUpgradeMethod = RepoLibInterop.ApplyUpgradeMethod;
			if (setLevelMethod == null || applyUpgradeMethod == null)
			{
				Log.LogInfo((object)"REPOLib not detected. Skipping custom modded upgrades integration.");
				return;
			}
			try
			{
				_harmony.Patch((MethodBase)setLevelMethod, new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("SetLevelPrefix")), new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("SetLevelPostfix")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_harmony.Patch((MethodBase)applyUpgradeMethod, new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("ApplyUpgradePrefix")), new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("ApplyUpgradePostfix")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Successfully hooked REPOLib PlayerUpgrade.SetLevel and ApplyUpgrade!");
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to hook REPOLib: " + ex.Message));
			}
		}
	}
	internal static class RepoLibUpgradePatches
	{
		[ThreadStatic]
		private static int? _stashedPrior;

		public static void SetLevelPrefix(object __instance, string steamId)
		{
			_stashedPrior = RepoLibInterop.ReadCurrentLevel(__instance, steamId);
		}

		public static void SetLevelPostfix()
		{
			_stashedPrior = null;
		}

		public static void ApplyUpgradePrefix(object __instance, string steamId, out int __state)
		{
			if (_stashedPrior.HasValue)
			{
				__state = _stashedPrior.Value;
				_stashedPrior = null;
			}
			else
			{
				__state = RepoLibInterop.ReadCurrentLevel(__instance, steamId);
			}
		}

		public static void ApplyUpgradePostfix(object __instance, string steamId, int level, int __state)
		{
			if (!Configuration.EnableUpgradeSharing.Value || !Configuration.EnableModdedUpgradeSharing.Value || !RepoLibInterop.TryReadUpgradeKey(__instance, out string upgradeKey) || !RegistryService.Instance.IsRegistered(upgradeKey) || RegistryService.Instance.IsVanilla(upgradeKey))
			{
				return;
			}
			int num = level - __state;
			if (num <= 0 || SyncService.IsDistributing)
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(steamId);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			if (Configuration.EnableShareNotification.Value)
			{
				UpgradePatches.PlayShareEffect(val);
			}
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				if (Configuration.IsInfo())
				{
					Plugin.Log.LogInfo((object)$"[SharedMod] REPOLib upgrade {upgradeKey} (+{num}) bought by {val.GetPlayerName()}, distributing...");
				}
				SyncService.DistributeUpgrade(upgradeKey, num, steamId, val.photonView.ViewID, val.GetPlayerName());
			}
		}
	}
}
namespace Shared.Services
{
	public class NetworkCallbackService : MonoBehaviourPunCallbacks
	{
		private readonly HashSet<Player> _pendingSync = new HashSet<Player>();

		public static NetworkCallbackService? Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			CatchUpExistingPlayers();
		}

		private void CatchUpExistingPlayers()
		{
			if (!Configuration.EnableLateJoinUpgradeSync.Value || !Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
			foreach (Player val in playerListOthers)
			{
				_pendingSync.Add(val);
				if (Configuration.IsInfo())
				{
					Debug.Log((object)("[SharedMod] Deferred sync: " + val.NickName + " already in room, queued."));
				}
			}
		}

		public override void OnJoinedRoom()
		{
			//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_003d: Expected O, but got Unknown
			//IL_003e: Expected O, but got Unknown
			if (Configuration.IsDebug())
			{
				Debug.Log((object)$"[SharedMod] OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})");
			}
			try
			{
				if (PhotonNetwork.IsMasterClient)
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"shared_mod_ver", (object)"1.1.0");
					Hashtable val2 = val;
					PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
					if (Configuration.IsDebug())
					{
						Debug.Log((object)"[SharedMod] Set room custom property shared_mod_ver = 1.1.0");
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[SharedMod] Failed to set room custom properties: " + ex.Message));
			}
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			if (Configuration.IsInfo())
			{
				Debug.Log((object)$"[SharedMod] OnPlayerEnteredRoom: {newPlayer.NickName} (master={SemiFunc.IsMasterClientOrSingleplayer()}, sync={Configuration.EnableLateJoinUpgradeSync.Value})");
			}
			if (Configuration.EnableLateJoinUpgradeSync.Value && Configuration.EnableUpgradeSharing.Value && SemiFunc.IsMasterClientOrSingleplayer())
			{
				_pendingSync.Add(newPlayer);
			}
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			_pendingSync.Remove(otherPlayer);
		}

		public static bool IsPlayerPendingSync(Player player)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				return Instance._pendingSync.Contains(player);
			}
			return false;
		}

		public IEnumerator LateSyncPlayer(PlayerAvatar avatar, string steamID, Dictionary<string, int> teamSnapshot)
		{
			yield return SyncService.ApplyTeamSnapshot(avatar, steamID, teamSnapshot);
			_pendingSync.Remove(avatar.photonView.Owner);
		}
	}
	public static class ReflectionExtensions
	{
		public static string GetPlayerName(this PlayerAvatar avatar)
		{
			return (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(avatar);
		}

		public static string GetSteamID(this PlayerAvatar avatar)
		{
			return SemiFunc.PlayerGetSteamID(avatar);
		}

		public static bool GetIsLocal(this PlayerAvatar avatar)
		{
			return (bool)AccessTools.Field(typeof(PlayerAvatar), "isLocal").GetValue(avatar);
		}

		public static bool GetDeadSet(this PlayerAvatar avatar)
		{
			return (bool)AccessTools.Field(typeof(PlayerAvatar), "deadSet").GetValue(avatar);
		}

		public static bool GetLevelAnimationCompleted(this PlayerAvatar avatar)
		{
			return (bool)AccessTools.Field(typeof(PlayerAvatar), "levelAnimationCompleted").GetValue(avatar);
		}

		public static int GetHealth(this PlayerHealth ph)
		{
			return (int)AccessTools.Field(typeof(PlayerHealth), "health").GetValue(ph);
		}

		public static int GetMaxHealth(this PlayerHealth ph)
		{
			return (int)AccessTools.Field(typeof(PlayerHealth), "maxHealth").GetValue(ph);
		}

		public static Dictionary<string, Dictionary<string, int>> GetDictionaryOfDictionaries(this StatsManager manager)
		{
			return (Dictionary<string, Dictionary<string, int>>)AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries").GetValue(manager);
		}

		public static Dictionary<string, int> GetRunStats(this StatsManager manager)
		{
			return (Dictionary<string, int>)AccessTools.Field(typeof(StatsManager), "runStats").GetValue(manager);
		}

		public static Dictionary<string, int> GetItemsPurchased(this StatsManager manager)
		{
			return (Dictionary<string, int>)AccessTools.Field(typeof(StatsManager), "itemsPurchased").GetValue(manager);
		}

		public static int GetChargeTotal(this ChargingStation station)
		{
			return (int)AccessTools.Field(typeof(ChargingStation), "chargeTotal").GetValue(station);
		}

		public static void SetChargeTotal(this ChargingStation station, int value)
		{
			AccessTools.Field(typeof(ChargingStation), "chargeTotal").SetValue(station, value);
		}

		public static List<GameObject> GetCrystals(this ChargingStation station)
		{
			return (List<GameObject>)AccessTools.Field(typeof(ChargingStation), "crystals").GetValue(station);
		}

		public static void SetChargeFloat(this ChargingStation station, float value)
		{
			AccessTools.Field(typeof(ChargingStation), "chargeFloat").SetValue(station, value);
		}

		public static void SetChargeInt(this ChargingStation station, int value)
		{
			AccessTools.Field(typeof(ChargingStation), "chargeInt").SetValue(station, value);
		}

		public static int GetBatteryLifeCountBars(this ItemBattery battery)
		{
			return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBars").GetValue(battery);
		}

		public static void SetBatteryLifeCountBars(this ItemBattery battery, int value)
		{
			AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBars").SetValue(battery, value);
		}

		public static int GetBatteryLifeCountBarsPrev(this ItemBattery battery)
		{
			return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBarsPrev").GetValue(battery);
		}

		public static void SetBatteryLifeCountBarsPrev(this ItemBattery battery, int value)
		{
			AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBarsPrev").SetValue(battery, value);
		}

		public static int GetBatteryLifeInt(this ItemBattery battery)
		{
			return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeInt").GetValue(battery);
		}

		public static void SetBatteryLifeInt(this ItemBattery battery, int value)
		{
			AccessTools.Field(typeof(ItemBattery), "batteryLifeInt").SetValue(battery, value);
		}

		public static int GetCurrentBars(this ItemBattery battery)
		{
			return (int)AccessTools.Field(typeof(ItemBattery), "currentBars").GetValue(battery);
		}

		public static void SetCurrentBars(this ItemBattery battery, int value)
		{
			AccessTools.Field(typeof(ItemBattery), "currentBars").SetValue(battery, value);
		}

		public static string GetInstanceName(this ItemAttributes attr)
		{
			return (string)AccessTools.Field(typeof(ItemAttributes), "instanceName").GetValue(attr);
		}

		public static int GetItemType(this ItemAttributes attr)
		{
			return (int)AccessTools.Field(typeof(ItemAttributes), "itemType").GetValue(attr);
		}

		public static int GetItemValue(this ItemAttributes attr)
		{
			return (int)AccessTools.Field(typeof(ItemAttributes), "value").GetValue(attr);
		}

		public static void SetItemValue(this ItemAttributes attr, int val)
		{
			AccessTools.Field(typeof(ItemAttributes), "value").SetValue(attr, val);
		}

		public static PhotonView GetPhotonView(this ItemAttributes attr)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			return (PhotonView)AccessTools.Field(typeof(ItemAttributes), "photonView").GetValue(attr);
		}

		public static ItemToggle GetItemToggle(this ItemUpgrade upgrade)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			return (ItemToggle)AccessTools.Field(typeof(ItemUpgrade), "itemToggle").GetValue(upgrade);
		}

		public static ItemAttributes GetItemAttributes(this ItemUpgrade upgrade)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			return (ItemAttributes)AccessTools.Field(typeof(ItemUpgrade), "itemAttributes").GetValue(upgrade);
		}

		public static int GetPlayerTogglePhotonID(this ItemToggle toggle)
		{
			return (int)AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID").GetValue(toggle);
		}

		public static ItemToggle GetItemToggle(this ItemHealthPack hp)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			return (ItemToggle)AccessTools.Field(typeof(ItemHealthPack), "itemToggle").GetValue(hp);
		}

		public static bool GetUsed(this ItemHealthPack hp)
		{
			return (bool)AccessTools.Field(typeof(ItemHealthPack), "used").GetValue(hp);
		}

		public static bool GetLevelIsShop(this RunManager manager)
		{
			return (bool)AccessTools.Field(typeof(RunManager), "levelIsShop").GetValue(manager);
		}
	}
	public sealed class Upgrade : IEquatable<Upgrade>
	{
		public string Name { get; }

		public string CleanName
		{
			get
			{
				if (!Name.StartsWith("playerUpgrade"))
				{
					return Name;
				}
				return Name.Substring("playerUpgrade".Length);
			}
		}

		public Upgrade(string name)
		{
			Name = name;
		}

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

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

		public override int GetHashCode()
		{
			return Name.GetHashCode();
		}
	}
	public sealed class RegistryService
	{
		private readonly HashSet<Upgrade> _vanillaUpgrades = new HashSet<Upgrade>();

		private readonly HashSet<Upgrade> _moddedUpgrades = new HashSet<Upgrade>();

		private static readonly RegistryService _instance = new RegistryService();

		public static RegistryService Instance => _instance;

		public IReadOnlyCollection<Upgrade> VanillaUpgrades => _vanillaUpgrades;

		public IReadOnlyCollection<Upgrade> ModdedUpgrades => _moddedUpgrades;

		private RegistryService()
		{
		}

		public void DiscoverAndRegister(StatsManager statsManager)
		{
			_vanillaUpgrades.Clear();
			_moddedUpgrades.Clear();
			foreach (string item in statsManager.GetDictionaryOfDictionaries().Keys.Where((string k) => k.StartsWith("playerUpgrade")))
			{
				if (AccessTools.Field(typeof(StatsManager), item) != null)
				{
					_vanillaUpgrades.Add(new Upgrade(item));
				}
				else
				{
					_moddedUpgrades.Add(new Upgrade(item));
				}
			}
			foreach (string moddedKey in RepoLibInterop.GetModdedUpgradeKeys())
			{
				if (!_vanillaUpgrades.Any((Upgrade u) => u.Name == moddedKey))
				{
					_moddedUpgrades.Add(new Upgrade(moddedKey));
				}
			}
			Debug.Log((object)$"[SharedRegistry] Discovered {_vanillaUpgrades.Count} vanilla and {_moddedUpgrades.Count} modded upgrades.");
		}

		public bool IsVanilla(string key)
		{
			return _vanillaUpgrades.Any((Upgrade u) => u.Name == key);
		}

		public bool IsRegistered(string key)
		{
			if (!_vanillaUpgrades.Any((Upgrade u) => u.Name == key))
			{
				return _moddedUpgrades.Any((Upgrade u) => u.Name == key);
			}
			return true;
		}
	}
	internal static class RepoLibInterop
	{
		private const string KeyPrefix = "playerUpgrade";

		private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static bool _resolved;

		private static Type? _playerUpgradeType;

		private static FieldInfo? _upgradeIdField;

		private static FieldInfo? _playerDictionaryField;

		private static MethodInfo? _applyUpgradeMethod;

		private static MethodInfo? _setLevelMethod;

		private static FieldInfo? _playerUpgradesDictField;

		private static PropertyInfo? _playerUpgradesProp;

		public static MethodInfo? ApplyUpgradeMethod
		{
			get
			{
				Resolve();
				return _applyUpgradeMethod;
			}
		}

		public static MethodInfo? SetLevelMethod
		{
			get
			{
				Resolve();
				return _setLevelMethod;
			}
		}

		public static HashSet<string> GetModdedUpgradeKeys()
		{
			HashSet<string> hashSet = new HashSet<string>();
			Resolve();
			if (_playerUpgradesProp == null || _upgradeIdField == null)
			{
				return hashSet;
			}
			try
			{
				if (!(_playerUpgradesProp.GetValue(null) is IEnumerable enumerable))
				{
					return hashSet;
				}
				foreach (object item in enumerable)
				{
					if (item != null && _upgradeIdField.GetValue(item) is string text && !string.IsNullOrEmpty(text))
					{
						hashSet.Add("playerUpgrade" + text);
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[RepoLibInterop] reading PlayerUpgrades failed: " + ex.Message));
			}
			return hashSet;
		}

		public static bool TryReadUpgradeKey(object playerUpgrade, out string upgradeKey)
		{
			upgradeKey = string.Empty;
			Resolve();
			if (_upgradeIdField == null)
			{
				return false;
			}
			if (!(_upgradeIdField.GetValue(playerUpgrade) is string text) || string.IsNullOrEmpty(text))
			{
				return false;
			}
			upgradeKey = "playerUpgrade" + text;
			return true;
		}

		public static int ReadCurrentLevel(object playerUpgrade, string steamId)
		{
			Resolve();
			if (_playerDictionaryField == null)
			{
				return 0;
			}
			if (!(_playerDictionaryField.GetValue(playerUpgrade) is IDictionary dictionary))
			{
				return 0;
			}
			if (!dictionary.Contains(steamId))
			{
				return 0;
			}
			object obj = dictionary[steamId];
			if (obj is int)
			{
				return (int)obj;
			}
			return 0;
		}

		public static bool TrySetLevel(string upgradeKey, string steamId, int level)
		{
			Resolve();
			if (_setLevelMethod == null || _playerUpgradesDictField == null)
			{
				return false;
			}
			if (!upgradeKey.StartsWith("playerUpgrade"))
			{
				return false;
			}
			int length = "playerUpgrade".Length;
			string text = upgradeKey.Substring(length, upgradeKey.Length - length);
			if (!(_playerUpgradesDictField.GetValue(null) is IDictionary dictionary))
			{
				return false;
			}
			if (!dictionary.Contains(text))
			{
				return false;
			}
			object obj = dictionary[text];
			if (obj == null)
			{
				return false;
			}
			try
			{
				_setLevelMethod.Invoke(obj, new object[2] { steamId, level });
				return true;
			}
			catch (Exception ex)
			{
				Exception ex2 = (ex as TargetInvocationException)?.InnerException ?? ex;
				Debug.LogWarning((object)$"[RepoLibInterop] SetLevel({text}, {steamId}, {level}) threw {ex2.GetType().Name}: {ex2.Message}");
				return false;
			}
		}

		private static void Resolve()
		{
			if (_resolved)
			{
				return;
			}
			_resolved = true;
			Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades");
			if (!(type == null))
			{
				_playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade");
				if (!(_playerUpgradeType == null))
				{
					_playerUpgradesProp = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					_playerUpgradesDictField = type.GetField("_playerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					_upgradeIdField = _playerUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					_playerDictionaryField = _playerUpgradeType.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					_applyUpgradeMethod = _playerUpgradeType.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
					{
						typeof(string),
						typeof(int)
					}, null);
					_setLevelMethod = _playerUpgradeType.GetMethod("SetLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
					{
						typeof(string),
						typeof(int)
					}, null);
				}
			}
		}
	}
	public static class SyncService
	{
		private static bool _distributing;

		public static bool IsDistributing => _distributing;

		public static Dictionary<string, int> SnapshotPlayerStats(string steamID)
		{
			if (string.IsNullOrEmpty(steamID) || (Object)(object)StatsManager.instance == (Object)null)
			{
				return new Dictionary<string, int>();
			}
			return (from kvp in StatsManager.instance.GetDictionaryOfDictionaries()
				where RegistryService.Instance.IsRegistered(kvp.Key)
				select kvp).ToDictionary((KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Value.GetValueOrDefault(steamID, 0));
		}

		public static Dictionary<string, int> SnapshotTeamMaxLevels(string? excludeSteamID = null)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if ((Object)(object)StatsManager.instance == (Object)null)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, Dictionary<string, int>> item in from k in StatsManager.instance.GetDictionaryOfDictionaries()
				where RegistryService.Instance.IsRegistered(k.Key)
				select k)
			{
				IEnumerable<int> source = (string.IsNullOrEmpty(excludeSteamID) ? item.Value.Values : (from p in item.Value
					where p.Key != excludeSteamID
					select p.Value));
				dictionary[item.Key] = source.DefaultIfEmpty(0).Max();
			}
			return dictionary;
		}

		public static void DistributeUpgrade(string upgradeKey, int levelDifference, string purchaserSteamID, int purchaserViewID, string purchaserName)
		{
			if (!Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null)
			{
				Debug.LogError((object)"[SharedMod] PunManager PhotonView not found, cannot distribute upgrade.");
				return;
			}
			bool flag = RegistryService.Instance.IsVanilla(upgradeKey);
			if (!flag && !Configuration.EnableModdedUpgradeSharing.Value)
			{
				return;
			}
			int value = Configuration.UpgradeShareChance.Value;
			bool value2 = Configuration.WholeTeamChance.Value;
			if (Configuration.IsVerbose())
			{
				Debug.Log((object)string.Format("[SharedMod] Distributing {0} (+{1}) from {2}. Chance={3}%, Mode={4}", upgradeKey, levelDifference, purchaserName, value, value2 ? "Team" : "Individual"));
			}
			if (value2 && !RollChance(value))
			{
				if (Configuration.IsInfo())
				{
					Debug.Log((object)$"[SharedMod] Upgrade share roll failed for team ({value}%).");
				}
				return;
			}
			_distributing = true;
			try
			{
				foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
				{
					if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == purchaserViewID)
					{
						continue;
					}
					string steamID = item.GetSteamID();
					if (string.IsNullOrEmpty(steamID))
					{
						continue;
					}
					int value3 = 0;
					if (StatsManager.instance.GetDictionaryOfDictionaries().TryGetValue(upgradeKey, out Dictionary<string, int> value4))
					{
						value4.TryGetValue(steamID, out value3);
					}
					if (value2 || RollChance(value))
					{
						int num = value3 + levelDifference;
						if (Configuration.IsInfo())
						{
							Debug.Log((object)$"[SharedMod] Syncing {upgradeKey} (+{levelDifference}) to {item.GetPlayerName()} ({steamID}): {value3} -> {num}");
						}
						if (flag)
						{
							string cleanName = new Upgrade(upgradeKey).CleanName;
							component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, cleanName, levelDifference });
						}
						else if (!RepoLibInterop.TrySetLevel(upgradeKey, steamID, num))
						{
							component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, steamID, num });
						}
					}
				}
			}
			finally
			{
				_distributing = false;
			}
			if (!(upgradeKey == "playerUpgradeHealth") || !Configuration.EnableUpgradeHeal.Value)
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(purchaserSteamID);
			if ((Object)(object)val != (Object)null)
			{
				int maxHealth = val.playerHealth.GetMaxHealth();
				int health = val.playerHealth.GetHealth();
				int num2 = maxHealth + 20 * levelDifference - health;
				if (num2 > 0)
				{
					val.playerHealth.HealOther(num2, false);
				}
			}
		}

		public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary<string, int> teamSnapshot)
		{
			if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
			{
				yield break;
			}
			PhotonView punView = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)punView == (Object)null)
			{
				yield break;
			}
			string playerName = player.GetPlayerName();
			if (Configuration.IsInfo())
			{
				Debug.Log((object)$"[SharedMod] Starting late join sync for {playerName} with {teamSnapshot.Count} upgrades.");
			}
			_distributing = true;
			try
			{
				foreach (KeyValuePair<string, int> item in teamSnapshot)
				{
					bool flag = RegistryService.Instance.IsVanilla(item.Key);
					if (!flag && !Configuration.EnableModdedUpgradeSharing.Value)
					{
						continue;
					}
					int value = 0;
					if (StatsManager.instance.GetDictionaryOfDictionaries().TryGetValue(item.Key, out Dictionary<string, int> value2))
					{
						value2.TryGetValue(steamID, out value);
					}
					int num = item.Value - value;
					if (num <= 0)
					{
						continue;
					}
					if (Configuration.IsDebug())
					{
						Debug.Log((object)$"[SharedMod]   LateJoin Sync {item.Key}: level={value}, teamMax={item.Value}, missing={num}");
					}
					if (flag)
					{
						string cleanName = new Upgrade(item.Key).CleanName;
						punView.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, cleanName, num });
					}
					else
					{
						int num2 = value + num;
						if (!RepoLibInterop.TrySetLevel(item.Key, steamID, num2))
						{
							punView.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { item.Key, steamID, num2 });
						}
					}
					yield return (object)new WaitForSeconds(0.05f);
				}
			}
			finally
			{
				_distributing = false;
			}
			if (Configuration.IsInfo())
			{
				Debug.Log((object)("[SharedMod] Completed late join sync for " + playerName + "."));
			}
		}

		private static bool RollChance(int percentage)
		{
			if (percentage >= 100)
			{
				return true;
			}
			if (percentage <= 0)
			{
				return false;
			}
			return Random.Range(0, 100) < percentage;
		}
	}
	internal class WatermarkService : MonoBehaviour
	{
		private GUIStyle? _style;

		private void OnGUI()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if (!Configuration.ShowWatermark.Value)
			{
				return;
			}
			try
			{
				if (_style == null)
				{
					_style = new GUIStyle(GUI.skin.label)
					{
						fontSize = 18,
						alignment = (TextAnchor)6
					};
					_style.normal.textColor = new Color(1f, 1f, 1f, 0.2f);
				}
				GUI.Label(new Rect(10f, (float)Screen.height - 35f, 250f, 30f), "Shared Mod v1.1.0", _style);
			}
			catch
			{
			}
		}
	}
}
namespace Shared.Patches
{
	[HarmonyPatch]
	public static class BatteryPatches
	{
		public readonly struct BatterySnapshot
		{
			public float BatteryLife { get; }

			public int BatteryLifeInt { get; }

			public int BatteryLifeCountBars { get; }

			public int BatteryLifeCountBarsPrev { get; }

			public int CurrentBars { get; }

			public BatterySnapshot(float batteryLife, int batteryLifeInt, int batteryLifeCountBars, int batteryLifeCountBarsPrev, int currentBars)
			{
				BatteryLife = batteryLife;
				BatteryLifeInt = batteryLifeInt;
				BatteryLifeCountBars = batteryLifeCountBars;
				BatteryLifeCountBarsPrev = batteryLifeCountBarsPrev;
				CurrentBars = currentBars;
			}
		}

		private const string PowerCrystalItemName = "Item Power Crystal";

		private static readonly Dictionary<int, BatterySnapshot> SavedBatteryState = new Dictionary<int, BatterySnapshot>();

		private static readonly Dictionary<int, float> BatteryDebt = new Dictionary<int, float>();

		private static int? LastSyncedChargeTotal;

		private static int? LastSyncedCrystalCount;

		private static readonly MethodInfo? BatteryUpdateBarsMethod = typeof(ItemBattery).GetMethod("BatteryUpdateBars", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo? CooldownField = typeof(ItemMelee).GetField("durabilityLossCooldown", BindingFlags.Instance | BindingFlags.NonPublic);

		private static ItemBattery? FindBattery(Component component)
		{
			if (!((Object)(object)component == (Object)null))
			{
				return component.GetComponent<ItemBattery>();
			}
			return null;
		}

		private static BatterySnapshot Capture(ItemBattery itemBattery)
		{
			return new BatterySnapshot(itemBattery.batteryLife, itemBattery.GetBatteryLifeInt(), itemBattery.GetBatteryLifeCountBars(), itemBattery.GetBatteryLifeCountBarsPrev(), itemBattery.GetCurrentBars());
		}

		private static void Restore(ItemBattery itemBattery, BatterySnapshot state)
		{
			itemBattery.batteryLife = state.BatteryLife;
			itemBattery.SetBatteryLifeInt(state.BatteryLifeInt);
			itemBattery.SetCurrentBars(state.CurrentBars);
			itemBattery.SetBatteryLifeCountBars(state.BatteryLifeCountBars);
			itemBattery.SetBatteryLifeCountBarsPrev(state.BatteryLifeCountBarsPrev);
			ItemAttributes component = ((Component)itemBattery).GetComponent<ItemAttributes>();
			if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.GetInstanceName()))
			{
				SemiFunc.StatSetBattery(component.GetInstanceName(), Mathf.RoundToInt(state.BatteryLife));
			}
			BatteryUpdateBarsMethod?.Invoke(itemBattery, new object[1] { state.BatteryLifeInt });
		}

		private static int GetEnergyPerCrystal()
		{
			if ((Object)(object)ChargingStation.instance == (Object)null)
			{
				return 10;
			}
			return Traverse.Create((object)ChargingStation.instance).Field("energyPerCrystal").GetValue<int>();
		}

		private static int GetMaxCrystals()
		{
			if ((Object)(object)ChargingStation.instance == (Object)null)
			{
				return 10;
			}
			return Traverse.Create((object)ChargingStation.instance).Field("maxCrystals").GetValue<int>();
		}

		private static int GetMaxChargeTotal()
		{
			return GetEnergyPerCrystal() * GetMaxCrystals();
		}

		private static int CalculateCrystalCount(int chargeTotal)
		{
			int energyPerCrystal = GetEnergyPerCrystal();
			int maxCrystals = GetMaxCrystals();
			return Mathf.Clamp(Mathf.CeilToInt((float)chargeTotal / (float)energyPerCrystal), 0, maxCrystals);
		}

		private static int GetPurchaseBaselineTotal(int rawTotal)
		{
			int maxChargeTotal = GetMaxChargeTotal();
			int num = Mathf.Clamp(rawTotal, 0, maxChargeTotal);
			if (!LastSyncedChargeTotal.HasValue)
			{
				return num;
			}
			return Mathf.Min(num, LastSyncedChargeTotal.Value);
		}

		private static int GetPurchaseBaselineCrystalCount(int purchasedAfter)
		{
			int num = Mathf.Clamp(purchasedAfter - 1, 0, GetMaxCrystals());
			if (!LastSyncedCrystalCount.HasValue)
			{
				return num;
			}
			return Mathf.Min(num, LastSyncedCrystalCount.Value);
		}

		public static void DrainStation(int cost)
		{
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)ChargingStation.instance != (Object)null && (Object)(object)StatsManager.instance != (Object)null)
			{
				if (Configuration.IsDebug())
				{
					Debug.Log((object)$"[SharedMod] Draining station: cost={cost}, current={ChargingStation.instance.GetChargeTotal()}");
				}
				SyncStationState(Mathf.Max(0, ChargingStation.instance.GetChargeTotal() - cost));
			}
		}

		private static void SyncStationState(int chargeTotal, int? crystalCount = null)
		{
			if (!((Object)(object)StatsManager.instance == (Object)null))
			{
				int maxChargeTotal = GetMaxChargeTotal();
				int num = Mathf.Clamp(chargeTotal, 0, maxChargeTotal);
				int num2 = crystalCount ?? CalculateCrystalCount(num);
				num2 = Mathf.Clamp(num2, 0, GetMaxCrystals());
				bool flag = false;
				if ((Object)(object)ChargingStation.instance != (Object)null)
				{
					ChargingStation.instance.SetChargeTotal(num);
					float value = (float)num / (float)maxChargeTotal;
					ChargingStation.instance.SetChargeFloat(value);
					flag = ChargingStation.instance.GetCrystals().Count > num2;
				}
				if (flag)
				{
					SyncCrystalVisuals(num2);
				}
				if ((Object)(object)ChargingStation.instance != (Object)null)
				{
					ChargingStation.instance.SetChargeInt(num2);
				}
				StatsManager.instance.GetRunStats()["chargingStationChargeTotal"] = num;
				StatsManager.instance.GetRunStats()["chargingStationCharge"] = num2;
				StatsManager.instance.GetItemsPurchased()["Item Power Crystal"] = num2;
				LastSyncedChargeTotal = num;
				LastSyncedCrystalCount = num2;
			}
		}

		private static void SyncCrystalVisuals(int targetCrystalCount)
		{
			if ((Object)(object)ChargingStation.instance == (Object)null || SemiFunc.RunIsShop() || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			int count = ChargingStation.instance.GetCrystals().Count;
			int num = Mathf.Max(0, count - targetCrystalCount);
			if (num <= 0)
			{
				return;
			}
			int value = Mathf.Clamp(targetCrystalCount + num, 0, GetMaxCrystals());
			ChargingStation.instance.SetChargeInt(value);
			StatsManager.instance.GetRunStats()["chargingStationCharge"] = value;
			StatsManager.instance.GetItemsPurchased()["Item Power Crystal"] = value;
			MethodInfo method = typeof(ChargingStation).GetMethod("DestroyCrystal", BindingFlags.Instance | BindingFlags.NonPublic);
			if (method != null)
			{
				for (int i = 0; i < num; i++)
				{
					method.Invoke(ChargingStation.instance, null);
				}
			}
		}

		public static void ResetCachedSync()
		{
			LastSyncedChargeTotal = null;
			LastSyncedCrystalCount = null;
		}

		[HarmonyPatch(typeof(ItemBattery), "RemoveFullBar")]
		[HarmonyPrefix]
		public static bool ItemBattery_RemoveFullBar_Prefix(ItemBattery __instance, int _bars)
		{
			if (!Configuration.EnableBatterySharing.Value)
			{
				return true;
			}
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				return true;
			}
			if (SemiFunc.RunIsShop())
			{
				return true;
			}
			if ((Object)(object)ChargingStation.instance == (Object)null || (Object)(object)StatsManager.instance == (Object)null)
			{
				return true;
			}
			int num = Mathf.Max(1, Mathf.RoundToInt((float)_bars * 20f / (float)__instance.batteryBars));
			if (ChargingStation.instance.GetChargeTotal() >= num)
			{
				DrainStation(num);
				return false;
			}
			return true;
		}

		[HarmonyPatch(typeof(ItemBattery), "Update")]
		[HarmonyPrefix]
		public static void ItemBattery_Update_Prefix(ItemBattery __instance)
		{
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.RunIsShop())
			{
				SavedBatteryState[((Object)__instance).GetInstanceID()] = Capture(__instance);
			}
		}

		[HarmonyPatch(typeof(ItemBattery), "Update")]
		[HarmonyPostfix]
		public static void ItemBattery_Update_Postfix(ItemBattery __instance)
		{
			if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)__instance).GetInstanceID();
			if (!SavedBatteryState.TryGetValue(instanceID, out var value))
			{
				return;
			}
			float num = value.BatteryLife - __instance.batteryLife;
			if (num <= 0f)
			{
				return;
			}
			if (ChargingStation.instance.GetChargeTotal() <= 0)
			{
				BatteryDebt.Remove(instanceID);
				return;
			}
			Restore(__instance, value);
			float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f;
			if (num2 >= 1f)
			{
				int num3 = Mathf.FloorToInt(num2);
				num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal());
				DrainStation(num3);
				num2 -= (float)num3;
			}
			BatteryDebt[instanceID] = num2;
		}

		[HarmonyPatch(typeof(ItemBattery), "FixedUpdate")]
		[HarmonyPrefix]
		public static void ItemBattery_FixedUpdate_Prefix(ItemBattery __instance)
		{
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.RunIsShop())
			{
				SavedBatteryState[((Object)__instance).GetInstanceID()] = Capture(__instance);
			}
		}

		[HarmonyPatch(typeof(ItemBattery), "FixedUpdate")]
		[HarmonyPostfix]
		public static void ItemBattery_FixedUpdate_Postfix(ItemBattery __instance)
		{
			if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)__instance).GetInstanceID();
			if (!SavedBatteryState.TryGetValue(instanceID, out var value))
			{
				return;
			}
			float num = value.BatteryLife - __instance.batteryLife;
			if (num <= 0f)
			{
				return;
			}
			if (ChargingStation.instance.GetChargeTotal() <= 0)
			{
				BatteryDebt.Remove(instanceID);
				return;
			}
			Restore(__instance, value);
			float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f;
			if (num2 >= 1f)
			{
				int num3 = Mathf.FloorToInt(num2);
				num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal());
				DrainStation(num3);
				num2 -= (float)num3;
			}
			BatteryDebt[instanceID] = num2;
		}

		[HarmonyPatch(typeof(ItemBattery), "OnDisable")]
		[HarmonyPostfix]
		public static void ItemBattery_OnDisable_Postfix(ItemBattery __instance)
		{
			int instanceID = ((Object)__instance).GetInstanceID();
			SavedBatteryState.Remove(instanceID);
			BatteryDebt.Remove(instanceID);
		}

		[HarmonyPatch(typeof(StatsManager), "LoadGame")]
		[HarmonyPrefix]
		public static void StatsManager_LoadGame_Cleanup()
		{
			ResetCachedSync();
			SavedBatteryState.Clear();
			BatteryDebt.Clear();
		}

		[HarmonyPatch(typeof(StatsManager), "ResetAllStats")]
		[HarmonyPrefix]
		public static void StatsManager_ResetAllStats_Cleanup()
		{
			ResetCachedSync();
			SavedBatteryState.Clear();
			BatteryDebt.Clear();
		}

		[HarmonyPatch(typeof(ChargingStation), "Start")]
		[HarmonyPrefix]
		public static void ChargingStation_Start_Prefix(ChargingStation __instance)
		{
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)StatsManager.instance != (Object)null)
			{
				int value = Traverse.Create((object)__instance).Field("energyPerCrystal").GetValue<int>();
				int value2 = Traverse.Create((object)__instance).Field("maxCrystals").GetValue<int>();
				int num = value * value2;
				int num2 = StatsManager.instance.GetRunStats()["chargingStationChargeTotal"];
				int num3 = StatsManager.instance.GetItemsPurchased()["Item Power Crystal"];
				int num4 = Mathf.Clamp(num2, 0, num);
				int num5 = Mathf.Clamp(Mathf.CeilToInt((float)num4 / (float)value), 0, value2);
				int value3 = Mathf.Clamp(Mathf.Max(num3, num5), 0, value2);
				SyncStationState(num4, value3);
			}
		}

		[HarmonyPatch(typeof(StatsManager), "ItemPurchase")]
		[HarmonyPostfix]
		public static void StatsManager_ItemPurchase_Postfix(string itemName)
		{
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && itemName == "Item Power Crystal" && (Object)(object)StatsManager.instance != (Object)null)
			{
				int rawTotal = StatsManager.instance.GetRunStats()["chargingStationChargeTotal"];
				int purchasedAfter = Mathf.Max(StatsManager.instance.GetItemsPurchased()["Item Power Crystal"], 0);
				int purchaseBaselineTotal = GetPurchaseBaselineTotal(rawTotal);
				int energyPerCrystal = GetEnergyPerCrystal();
				int purchaseBaselineCrystalCount = GetPurchaseBaselineCrystalCount(purchasedAfter);
				int num = Mathf.Min(GetMaxCrystals(), purchaseBaselineCrystalCount + 1);
				int num2 = Mathf.Min(num * energyPerCrystal, GetMaxChargeTotal());
				SyncStationState(Mathf.Clamp(purchaseBaselineTotal + energyPerCrystal, 0, num2), num);
			}
		}

		[HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")]
		[HarmonyPrefix]
		public static void ItemMelee_SwingHitRPC_Prefix(ItemMelee __instance, ref BatterySnapshot? __state)
		{
			__state = null;
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer())
			{
				ItemBattery val = FindBattery((Component)(object)__instance);
				if ((Object)(object)val != (Object)null)
				{
					__state = Capture(val);
				}
			}
		}

		[HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")]
		[HarmonyPostfix]
		public static void ItemMelee_SwingHitRPC_Postfix(ItemMelee __instance, bool _durabilityLoss, BatterySnapshot? __state)
		{
			if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || !_durabilityLoss || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue)
			{
				return;
			}
			ItemBattery val = FindBattery((Component)(object)__instance);
			if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0 || (float)(CooldownField?.GetValue(__instance) ?? ((object)0f)) <= 0f)
			{
				return;
			}
			float num = __state.Value.BatteryLife - val.batteryLife;
			if (!(num <= 0f))
			{
				Restore(val, __state.Value);
				int instanceID = ((Object)__instance).GetInstanceID();
				float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f;
				if (num2 >= 1f)
				{
					int num3 = Mathf.FloorToInt(num2);
					num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal());
					DrainStation(num3);
					num2 -= (float)num3;
				}
				BatteryDebt[instanceID] = num2;
			}
		}

		[HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")]
		[HarmonyPrefix]
		public static void ItemMelee_EnemyOrPVPSwingHitRPC_Prefix(ItemMelee __instance, ref BatterySnapshot? __state)
		{
			__state = null;
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer())
			{
				ItemBattery val = FindBattery((Component)(object)__instance);
				if ((Object)(object)val != (Object)null)
				{
					__state = Capture(val);
				}
			}
		}

		[HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")]
		[HarmonyPostfix]
		public static void ItemMelee_EnemyOrPVPSwingHitRPC_Postfix(ItemMelee __instance, BatterySnapshot? __state)
		{
			if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue)
			{
				return;
			}
			ItemBattery val = FindBattery((Component)(object)__instance);
			if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0)
			{
				return;
			}
			float num = __state.Value.BatteryLife - val.batteryLife;
			if (!(num <= 0f))
			{
				Restore(val, __state.Value);
				int instanceID = ((Object)__instance).GetInstanceID();
				float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f;
				if (num2 >= 1f)
				{
					int num3 = Mathf.FloorToInt(num2);
					num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal());
					DrainStation(num3);
					num2 -= (float)num3;
				}
				BatteryDebt[instanceID] = num2;
			}
		}

		[HarmonyPatch(typeof(ItemDroneZeroGravity), "Update")]
		[HarmonyPrefix]
		public static void ItemDroneZeroGravity_Update_Prefix(ItemDroneZeroGravity __instance, ref BatterySnapshot? __state)
		{
			__state = null;
			if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer())
			{
				ItemBattery val = FindBattery((Component)(object)__instance);
				if ((Object)(object)val != (Object)null)
				{
					__state = Capture(val);
				}
			}
		}

		[HarmonyPatch(typeof(ItemDroneZeroGravity), "Update")]
		[HarmonyPostfix]
		public static void ItemDroneZeroGravity_Update_Postfix(ItemDroneZeroGravity __instance, BatterySnapshot? __state)
		{
			if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue)
			{
				return;
			}
			ItemBattery val = FindBattery((Component)(object)__instance);
			if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0)
			{
				return;
			}
			float num = __state.Value.BatteryLife - val.batteryLife;
			if (!(num <= 0f))
			{
				Restore(val, __state.Value);
				int instanceID = ((Object)val).GetInstanceID();
				float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f;
				if (num2 >= 1f)
				{
					int num3 = Mathf.FloorToInt(num2);
					num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal());
					DrainStation(num3);
					num2 -= (float)num3;
				}
				BatteryDebt[instanceID] = num2;
			}
		}
	}
	[HarmonyPatch]
	public static class DamagePatches
	{
		private static readonly Dictionary<int, float> MarkedBySolidarityUntil = new Dictionary<int, float>(16);

		private static readonly FieldInfo Field_RoomVolumeCheck_inTruck = AccessTools.Field(typeof(RoomVolumeCheck), "inTruck");

		private static readonly FieldInfo Field_RoomVolumeCheck_inExtractionPoint = AccessTools.Field(typeof(RoomVolumeCheck), "inExtractionPoint");

		[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
		[HarmonyPostfix]
		public static void PlayerAvatar_PlayerDeathRPC_Postfix(PlayerAvatar __instance, int enemyIndex)
		{
			if (!Configuration.EnableDeathLiability.Value || !GameManager.Multiplayer() || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			int num = Mathf.Clamp(Configuration.DeathDamagePercent.Value, 0, 100);
			if (num <= 0)
			{
				return;
			}
			if (ConsumeSolidarityMark(__instance))
			{
				if (Configuration.IsDebug())
				{
					Debug.Log((object)("[SharedMod] " + __instance.GetPlayerName() + " died of solidarity damage. Preventing loop."));
				}
				return;
			}
			MonoBehaviour component = ((Component)__instance).GetComponent<MonoBehaviour>();
			if (component != null)
			{
				component.StartCoroutine(ApplyDamageToSurvivorsNextFrame(num));
			}
		}

		private static IEnumerator ApplyDamageToSurvivorsNextFrame(int percent)
		{
			yield return null;
			if (!PhotonNetwork.IsMasterClient)
			{
				yield break;
			}
			PurgeExpiredMarks();
			PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>();
			if (array == null || array.Length == 0)
			{
				yield break;
			}
			PlayerAvatar[] array2 = array;
			foreach (PlayerAvatar val in array2)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.playerHealth == (Object)null)
				{
					continue;
				}
				bool deadSet = val.GetDeadSet();
				int health = val.playerHealth.GetHealth();
				if (deadSet || health <= 0)
				{
					continue;
				}
				if (Configuration.ExcludeSafeZones.Value && IsInSafeZone(val))
				{
					if (Configuration.IsDebug())
					{
						Debug.Log((object)("[SharedMod] Excluding " + val.GetPlayerName() + " from death liability (in safe zone)."));
					}
					continue;
				}
				int num = val.playerHealth.GetMaxHealth();
				if (num <= 0)
				{
					num = 100;
				}
				int num2 = Mathf.RoundToInt((float)num * ((float)percent / 100f));
				num2 = Mathf.Clamp(num2, 1, num);
				MarkSolidarityTarget(val);
				if (Configuration.IsInfo())
				{
					Debug.Log((object)$"[SharedMod] Applying {num2} death liability damage to {val.GetPlayerName()}");
				}
				try
				{
					val.playerHealth.HurtOther(num2, Vector3.zero, false, -1, false);
				}
				catch (Exception ex)
				{
					Debug.LogError((object)("[SharedMod] Failed to hurt survivor " + val.GetPlayerName() + ": " + ex.Message));
				}
			}
		}

		private static bool IsInSafeZone(PlayerAvatar avatar)
		{
			RoomVolumeCheck roomVolumeCheck = avatar.RoomVolumeCheck;
			if ((Object)(object)roomVolumeCheck == (Object)null)
			{
				return false;
			}
			try
			{
				roomVolumeCheck.CheckSet();
			}
			catch
			{
			}
			bool num = (bool)(Field_RoomVolumeCheck_inTruck?.GetValue(roomVolumeCheck) ?? ((object)false));
			bool flag = (bool)(Field_RoomVolumeCheck_inExtractionPoint?.GetValue(roomVolumeCheck) ?? ((object)false));
			return num || flag;
		}

		private static void MarkSolidarityTarget(PlayerAvatar avatar)
		{
			if ((Object)(object)avatar != (Object)null && (Object)(object)avatar.photonView != (Object)null)
			{
				int viewID = avatar.photonView.ViewID;
				if (viewID > 0)
				{
					MarkedBySolidarityUntil[viewID] = Time.time + 3f;
				}
			}
		}

		private static bool ConsumeSolidarityMark(PlayerAvatar avatar)
		{
			if ((Object)(object)avatar == (Object)null || (Object)(object)avatar.photonView == (Object)null)
			{
				return false;
			}
			int viewID = avatar.photonView.ViewID;
			if (viewID <= 0)
			{
				return false;
			}
			float time = Time.time;
			if (MarkedBySolidarityUntil.TryGetValue(viewID, out var value))
			{
				MarkedBySolidarityUntil.Remove(viewID);
				return value >= time;
			}
			return false;
		}

		private static void PurgeExpiredMarks()
		{
			if (MarkedBySolidarityUntil.Count == 0)
			{
				return;
			}
			float time = Time.time;
			List<int> list = new List<int>();
			foreach (KeyValuePair<int, float> item in MarkedBySolidarityUntil)
			{
				if (item.Value < time)
				{
					list.Add(item.Key);
				}
			}
			foreach (int item2 in list)
			{
				MarkedBySolidarityUntil.Remove(item2);
			}
		}
	}
	[HarmonyPatch]
	public static class HealthPatches
	{
		private static bool _healAllFlag;

		[HarmonyPatch(typeof(ItemHealthPack), "Update")]
		[HarmonyPrefix]
		public static void ItemHealthPack_Update_Prefix(ItemHealthPack __instance)
		{
			if (!Configuration.EnableHealthSharing.Value || SemiFunc.RunIsShop() || RunManager.instance.GetLevelIsShop())
			{
				return;
			}
			ItemToggle itemToggle = __instance.GetItemToggle();
			if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)itemToggle == (Object)null) && itemToggle.toggleState && !__instance.GetUsed())
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(itemToggle.GetPlayerTogglePhotonID());
				if (!((Object)(object)val == (Object)null) && val.playerHealth.GetHealth() < val.playerHealth.GetMaxHealth())
				{
					_healAllFlag = true;
				}
			}
		}

		[HarmonyPatch(typeof(PlayerHealth), "HealOther")]
		[HarmonyPostfix]
		public static void PlayerHealth_HealOther_Postfix(PlayerHealth __instance, int healAmount, bool effect)
		{
			if (!_healAllFlag)
			{
				return;
			}
			_healAllFlag = false;
			if (Configuration.IsInfo())
			{
				Debug.Log((object)$"[SharedMod] Health Pack used. Sharing {healAmount} HP with the entire team.");
			}
			foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
			{
				if ((Object)(object)item != (Object)null && (Object)(object)item.playerHealth != (Object)(object)__instance)
				{
					item.playerHealth.HealOther(healAmount, effect);
				}
			}
		}
	}
	[HarmonyPatch]
	public static class UpgradePatches
	{
		public struct UpgradeContext
		{
			public string SteamID;

			public int ViewID;

			public string PlayerName;

			public Dictionary<string, int> PreUpgradeStats;

			public string? ItemName;
		}

		[HarmonyPatch(typeof(StatsManager), "Start")]
		[HarmonyPostfix]
		public static void StatsManager_Start_Postfix(StatsManager __instance)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (Configuration.IsDebug())
			{
				Debug.Log((object)"[SharedMod] StatsManager.Start: initializing upgrade registry.");
			}
			RegistryService.Instance.DiscoverAndRegister(__instance);
			if ((Object)(object)NetworkCallbackService.Instance == (Object)null)
			{
				GameObject val = new GameObject("SharedMod_NetworkCallbacks");
				val.AddComponent<NetworkCallbackService>();
				val.AddComponent<WatermarkService>();
				Object.DontDestroyOnLoad((Object)val);
			}
		}

		[HarmonyPatch(typeof(StatsManager), "LoadGame")]
		[HarmonyPostfix]
		public static void StatsManager_LoadGame_Postfix(StatsManager __instance)
		{
			if (Configuration.IsDebug())
			{
				Debug.Log((object)"[SharedMod] StatsManager.LoadGame: refreshing upgrade registry.");
			}
			RegistryService.Instance.DiscoverAndRegister(__instance);
		}

		[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
		[HarmonyPostfix]
		[HarmonyAfter(new string[] { "REPOLib" })]
		public static void StatsManager_RunStartStats_Postfix(StatsManager __instance)
		{
			if (Configuration.IsDebug())
			{
				Debug.Log((object)"[SharedMod] StatsManager.RunStartStats: refreshing upgrade registry.");
			}
			RegistryService.Instance.DiscoverAndRegister(__instance);
		}

		[HarmonyPatch(typeof(ItemAttributes), "GetValue")]
		[HarmonyPostfix]
		public static void ItemAttributes_GetValue_Postfix(ItemAttributes __instance)
		{
			if (Configuration.UpgradePriceScaling.Value <= 0f || __instance.GetItemType() != 3)
			{
				return;
			}
			float value = Configuration.UpgradePriceScaling.Value;
			int count = SemiFunc.PlayerGetAll().Count;
			if (count > 1)
			{
				float num = 1f + (float)(count - 1) * value;
				int num2 = (int)Math.Round((float)__instance.GetItemValue() * num);
				if (num2 < 0)
				{
					num2 = 0;
				}
				__instance.SetItemValue(num2);
				PhotonView photonView = __instance.GetPhotonView();
				if (GameManager.Multiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)photonView != (Object)null)
				{
					photonView.RPC("GetValueRPC", (RpcTarget)1, new object[1] { num2 });
				}
			}
		}

		[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
		[HarmonyPrefix]
		public static void ItemUpgrade_PlayerUpgrade_Prefix(ItemUpgrade __instance, out UpgradeContext? __state)
		{
			__state = null;
			if (!Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			ItemToggle itemToggle = __instance.GetItemToggle();
			if ((Object)(object)itemToggle == (Object)null || !itemToggle.toggleState)
			{
				return;
			}
			int playerTogglePhotonID = itemToggle.GetPlayerTogglePhotonID();
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(playerTogglePhotonID);
			if (!((Object)(object)val == (Object)null))
			{
				string steamID = val.GetSteamID();
				if (!string.IsNullOrEmpty(steamID))
				{
					ItemAttributes itemAttributes = __instance.GetItemAttributes();
					string itemName = (((Object)(object)itemAttributes != (Object)null && (Object)(object)itemAttributes.item != (Object)null) ? ((Object)itemAttributes.item).name : null);
					__state = new UpgradeContext
					{
						SteamID = steamID,
						ViewID = playerTogglePhotonID,
						PlayerName = val.GetPlayerName(),
						PreUpgradeStats = SyncService.SnapshotPlayerStats(steamID),
						ItemName = itemName
					};
				}
			}
		}

		[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
		[HarmonyPostfix]
		public static void ItemUpgrade_PlayerUpgrade_Postfix(ItemUpgrade __instance, UpgradeContext? __state)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || !__state.HasValue)
			{
				return;
			}
			string steamID = __state.Value.SteamID;
			int viewID = __state.Value.ViewID;
			string playerName = __state.Value.PlayerName;
			bool flag = false;
			foreach (KeyValuePair<string, Dictionary<string, int>> item in from kvp in StatsManager.instance.GetDictionaryOfDictionaries()
				where RegistryService.Instance.IsRegistered(kvp.Key)
				select kvp)
			{
				int valueOrDefault = item.Value.GetValueOrDefault(steamID, 0);
				int valueOrDefault2 = __state.Value.PreUpgradeStats.GetValueOrDefault(item.Key, 0);
				if (valueOrDefault > valueOrDefault2)
				{
					int num = valueOrDefault - valueOrDefault2;
					flag = true;
					if (Configuration.IsInfo())
					{
						Debug.Log((object)$"[SharedMod] {playerName} bought upgrade {item.Key} (+{num}), distributing...");
					}
					SyncService.DistributeUpgrade(item.Key, num, steamID, viewID, playerName);
				}
			}
			if (flag || __state.Value.ItemName == null || !Configuration.EnableModdedUpgradeSharing.Value)
			{
				return;
			}
			string text = __state.Value.ItemName.Replace(" ", "");
			foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades)
			{
				string value = moddedUpgrade.CleanName.Replace(" ", "");
				if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					if (Configuration.IsInfo())
					{
						Debug.Log((object)("[SharedMod] " + playerName + " bought modded upgrade " + moddedUpgrade.Name + " matched by item name, distributing..."));
					}
					SyncService.DistributeUpgrade(moddedUpgrade.Name, 1, steamID, viewID, playerName);
					break;
				}
			}
		}

		[HarmonyPatch(typeof(PlayerTumble), "SetupDone")]
		[HarmonyPostfix]
		public static void PlayerTumble_SetupDone_Postfix(PlayerTumble __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !string.IsNullOrEmpty(__instance.playerAvatar.GetSteamID()) && NetworkCallbackService.IsPlayerPendingSync(__instance.playerAvatar.photonView.Owner) && Configuration.EnableLateJoinUpgradeSync.Value && Configuration.EnableUpgradeSharing.Value)
			{
				if (Configuration.IsInfo())
				{
					Debug.Log((object)("[SharedMod] PlayerTumble.SetupDone for " + __instance.playerAvatar.GetPlayerName() + " (" + __instance.playerAvatar.GetSteamID() + "), triggering sync."));
				}
				Dictionary<string, int> teamSnapshot = SyncService.SnapshotTeamMaxLevels(__instance.playerAvatar.GetSteamID());
				if ((Object)(object)NetworkCallbackService.Instance != (Object)null)
				{
					((MonoBehaviour)NetworkCallbackService.Instance).StartCoroutine(NetworkCallbackService.Instance.LateSyncPlayer(__instance.playerAvatar, __instance.playerAvatar.GetSteamID(), teamSnapshot));
				}
			}
		}

		[HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")]
		[HarmonyPostfix]
		public static void PunManager_TesterUpgradeCommandRPC_Postfix(string _steamID, string upgradeName, int upgradeNum)
		{
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			if (Configuration.EnableShareNotification.Value)
			{
				PlayShareEffect(val);
			}
			if (upgradeName == "Health" && SemiFunc.IsMasterClientOrSingleplayer() && Configuration.EnableUpgradeHeal.Value)
			{
				int maxHealth = val.playerHealth.GetMaxHealth();
				int health = val.playerHealth.GetHealth();
				int num = maxHealth + 20 * upgradeNum - health;
				if (num > 0)
				{
					val.playerHealth.HealOther(num, false);
				}
			}
		}

		public static void PlayShareEffect(PlayerAvatar player)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (player.GetIsLocal())
				{
					if ((Object)(object)StatsUI.instance != (Object)null)
					{
						StatsUI.instance.Fetch();
						StatsUI.instance.ShowStats();
					}
					if ((Object)(object)CameraGlitch.Instance != (Object)null)
					{
						CameraGlitch.Instance.PlayUpgrade();
					}
				}
				else if ((Object)(object)GameDirector.instance != (Object)null && (Object)(object)GameDirector.instance.CameraImpact != (Object)null)
				{
					GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)player).transform.position, 0.2f);
				}
				if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
				{
					player.playerHealth.MaterialEffectOverride((Effect)0);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[SharedMod] PlayShareEffect failed: " + ex.Message));
			}
		}
	}
}