Decompiled source of Rickys Shared Upgrades v1.1.7

BepInEx/plugins/RickysSharedUpgrades/RickysSharedUpgrades.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
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("RickysSharedUpgrades")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.7.0")]
[assembly: AssemblyInformationalVersion("1.1.7")]
[assembly: AssemblyProduct("RickysSharedUpgrades")]
[assembly: AssemblyTitle("RickysSharedUpgrades")]
[assembly: AssemblyVersion("1.1.7.0")]
[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 RickysSharedUpgrades
{
	[BepInPlugin("com.rickyssharedupgrades.repo", "Ricky's Shared Upgrades", "1.1.7")]
	[BepInProcess("REPO.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class JoiningPlayer
		{
			internal readonly int Actor;

			internal PlayerAvatar Avatar;

			internal bool BaselineSent;

			internal bool Ready;

			internal float ReadyAfter;

			internal JoiningPlayer(int actor, PlayerAvatar avatar)
			{
				Actor = actor;
				Avatar = avatar;
			}
		}

		internal sealed class Consumption
		{
			internal readonly Consumption? Parent;

			internal readonly HashSet<string> Recipients;

			internal readonly List<Gain> Gains = new List<Gain>();

			internal Consumption(Consumption? parent, HashSet<string> recipients)
			{
				Parent = parent;
				Recipients = recipients;
			}
		}

		internal sealed class Gain
		{
			internal readonly UpgradeKind Kind;

			internal readonly string Source;

			internal readonly int Amount;

			internal readonly int Before;

			internal Gain(UpgradeKind kind, string source, int before, int amount)
			{
				Kind = kind;
				Source = source;
				Before = before;
				Amount = amount;
			}
		}

		private sealed class Delivery
		{
			internal readonly UpgradeKind Kind;

			internal readonly string SteamId;

			internal Delivery(UpgradeKind kind, string id)
			{
				Kind = kind;
				SteamId = id;
			}
		}

		internal readonly struct Capture
		{
			internal readonly Consumption? Consumption;

			internal readonly UpgradeKind? Kind;

			internal readonly string SteamId;

			internal readonly int Before;

			internal Capture(Consumption scope, UpgradeKind kind, string id, int before)
			{
				Consumption = scope;
				Kind = kind;
				SteamId = id;
				Before = before;
			}
		}

		public const string PluginGuid = "com.rickyssharedupgrades.repo";

		public const string PluginName = "Ricky's Shared Upgrades";

		public const string PluginVersion = "1.1.7";

		internal static Plugin? Instance;

		private Harmony? harmony;

		private ConfigEntry<bool> sharingEnabled;

		private ConfigEntry<bool> logShares;

		private FieldInfo? consumedField;

		private FieldInfo? tumbleField;

		private FieldInfo? healthReadyField;

		private Consumption? currentConsumption;

		private readonly Queue<Delivery> pending = new Queue<Delivery>();

		private readonly HashSet<(UpgradeKind, string)> scheduled = new HashSet<(UpgradeKind, string)>();

		private readonly UpgradeTargets targets = new UpgradeTargets();

		private readonly Dictionary<string, JoiningPlayer> players = new Dictionary<string, JoiningPlayer>(StringComparer.Ordinal);

		private Room? session;

		private int dispatchDepth;

		private float nextDispatch;

		private float nextErrorLog;

		private float nextPlayerScan;

		private bool HasAuthority
		{
			get
			{
				if (sharingEnabled.Value && Object.op_Implicit((Object)(object)GameManager.instance) && GameManager.Multiplayer())
				{
					return PhotonNetwork.IsMasterClient;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Expected O, but got Unknown
			Instance = this;
			sharingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Share player upgrades and catch up late joiners when hosting.");
			logShares = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LogSharedUpgrades", false, "Log shared upgrade deliveries.");
			try
			{
				consumedField = AccessTools.Field(typeof(ItemUpgrade), "upgradeDone");
				tumbleField = AccessTools.Field(typeof(PlayerAvatar), "tumble");
				healthReadyField = AccessTools.Field(typeof(PlayerHealth), "healthSet");
				if (consumedField?.FieldType != typeof(bool) || tumbleField?.FieldType != typeof(PlayerTumble) || healthReadyField?.FieldType != typeof(bool))
				{
					throw new MissingFieldException("The game changed its upgrade components.");
				}
				MethodInfo method = typeof(PunManager).GetMethod("TesterUpgradeCommandRPC");
				if (method == null || !Attribute.IsDefined(method, typeof(PunRPC)))
				{
					throw new MissingMethodException("The game's native upgrade RPC is unavailable.");
				}
				UpgradeKind[] all = UpgradeKind.All;
				for (int i = 0; i < all.Length; i++)
				{
					all[i].GetMethod();
				}
				harmony = new Harmony("com.rickyssharedupgrades.repo");
				harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded for R.E.P.O. {2}. Host only; {3} upgrade types.", "Ricky's Shared Upgrades", "1.1.7", Application.version, UpgradeKind.All.Length));
			}
			catch (Exception arg)
			{
				Harmony? obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)$"Upgrade sharing disabled: {arg}");
				sharingEnabled.Value = false;
			}
		}

		private void EnsureSession()
		{
			if (session != PhotonNetwork.CurrentRoom)
			{
				session = PhotonNetwork.CurrentRoom;
				ResetProgress();
			}
		}

		internal void ResetProgress()
		{
			pending.Clear();
			scheduled.Clear();
			targets.Clear();
			players.Clear();
			nextPlayerScan = 0f;
			nextDispatch = 0f;
		}

		private bool AvatarReady(PlayerAvatar avatar)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			if (Object.op_Implicit((Object)(object)LevelGenerator.Instance) && LevelGenerator.Instance.Generated && Object.op_Implicit((Object)(object)StatsManager.instance) && StatsManager.instance.statsSynced && Object.op_Implicit((Object)(object)avatar.playerHealth) && Object.op_Implicit((Object)(object)avatar.physGrabber) && Object.op_Implicit((Object)(PlayerTumble)tumbleField.GetValue(avatar)))
			{
				return (bool)healthReadyField.GetValue(avatar.playerHealth);
			}
			return false;
		}

		internal void ScanPlayers()
		{
			if (!HasAuthority || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance) || PhotonNetwork.CurrentRoom == null)
			{
				return;
			}
			EnsureSession();
			Room currentRoom = PhotonNetwork.CurrentRoom;
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, JoiningPlayer> player in players)
			{
				if (!currentRoom.Players.ContainsKey(player.Value.Actor))
				{
					list.Add(player.Key);
				}
			}
			foreach (string item in list)
			{
				players.Remove(item);
			}
			List<JoiningPlayer> list2 = new List<JoiningPlayer>();
			foreach (PlayerAvatar item2 in SemiFunc.PlayerGetList())
			{
				if (!Object.op_Implicit((Object)(object)item2) || !Object.op_Implicit((Object)(object)item2.photonView))
				{
					continue;
				}
				string text = SemiFunc.PlayerGetSteamID(item2);
				int ownerActorNr = item2.photonView.OwnerActorNr;
				if (string.IsNullOrEmpty(text) || ownerActorNr <= 0 || !currentRoom.Players.ContainsKey(ownerActorNr))
				{
					continue;
				}
				if (!players.TryGetValue(text, out JoiningPlayer value) || value.Actor != ownerActorNr)
				{
					value = (players[text] = new JoiningPlayer(ownerActorNr, item2));
					UpgradeKind[] all = UpgradeKind.All;
					foreach (UpgradeKind upgradeKind in all)
					{
						int level = upgradeKind.GetLevel(StatsManager.instance, text);
						int crewLevel = targets.CrewLevel(upgradeKind.RpcName, upgradeKind.GetCrewLevel(StatsManager.instance));
						targets.CatchUp(text, upgradeKind.RpcName, level, crewLevel);
						Schedule(upgradeKind, text);
					}
				}
				if ((Object)(object)value.Avatar != (Object)(object)item2)
				{
					value.Avatar = item2;
					value.Ready = (value.BaselineSent = false);
				}
				if (!AvatarReady(item2))
				{
					value.Ready = (value.BaselineSent = false);
				}
				else if (!value.BaselineSent)
				{
					if (!list2.Contains(value))
					{
						list2.Add(value);
					}
				}
				else if (Time.unscaledTime >= value.ReadyAfter)
				{
					value.Ready = true;
				}
			}
			if (list2.Count == 0 || !Object.op_Implicit((Object)(object)PunManager.instance))
			{
				return;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if (!Object.op_Implicit((Object)(object)component) || component.ViewID == 0)
			{
				return;
			}
			try
			{
				PunManager.instance.SyncAllDictionaries();
				foreach (JoiningPlayer item3 in list2)
				{
					item3.BaselineSent = true;
					item3.ReadyAfter = Time.unscaledTime + 2f;
				}
			}
			catch (Exception exception)
			{
				LogDeliveryError("Unable to synchronize joining players.", exception);
			}
		}

		private void Schedule(UpgradeKind kind, string id)
		{
			if (targets.Missing(id, kind.RpcName, kind.GetLevel(StatsManager.instance, id)) > 0 && scheduled.Add((kind, id)))
			{
				pending.Enqueue(new Delivery(kind, id));
			}
		}

		internal Consumption? BeginConsumption(ItemUpgrade item)
		{
			ItemToggle val = default(ItemToggle);
			if (!HasAuthority || dispatchDepth != 0 || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance) || !Object.op_Implicit((Object)(object)item) || !item.isPlayerUpgrade || (bool)consumedField.GetValue(item) || !((Component)item).TryGetComponent<ItemToggle>(ref val) || !val.toggleState)
			{
				return null;
			}
			EnsureSession();
			ScanPlayers();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (PlayerAvatar item2 in SemiFunc.PlayerGetList())
			{
				if (Object.op_Implicit((Object)(object)item2))
				{
					string text = SemiFunc.PlayerGetSteamID(item2);
					if (!string.IsNullOrEmpty(text))
					{
						hashSet.Add(text);
					}
				}
			}
			foreach (KeyValuePair<string, JoiningPlayer> player in players)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null && currentRoom.Players.ContainsKey(player.Value.Actor))
				{
					hashSet.Add(player.Key);
				}
			}
			return currentConsumption = new Consumption(currentConsumption, hashSet);
		}

		internal void EndConsumption(Consumption? scope)
		{
			if (scope == null)
			{
				return;
			}
			currentConsumption = scope.Parent;
			if (!HasAuthority)
			{
				return;
			}
			foreach (Gain gain in scope.Gains)
			{
				scope.Recipients.Add(gain.Source);
				foreach (string recipient in scope.Recipients)
				{
					int before = ((recipient == gain.Source) ? gain.Before : gain.Kind.GetLevel(StatsManager.instance, recipient));
					targets.Add(recipient, gain.Kind.RpcName, before, gain.Amount);
					Schedule(gain.Kind, recipient);
				}
			}
			if (scope.Gains.Count != 0)
			{
				nextDispatch = Mathf.Max(nextDispatch, Time.unscaledTime + 0.15f);
			}
		}

		internal Capture BeforeUpgrade(MethodBase method, string steamId)
		{
			UpgradeKind upgradeKind = UpgradeKind.FromMethod(method.Name);
			if (!HasAuthority || dispatchDepth != 0 || currentConsumption == null || upgradeKind == null || !Object.op_Implicit((Object)(object)StatsManager.instance) || string.IsNullOrEmpty(steamId))
			{
				return default(Capture);
			}
			return new Capture(currentConsumption, upgradeKind, steamId, upgradeKind.GetLevel(StatsManager.instance, steamId));
		}

		internal void AfterUpgrade(Capture capture)
		{
			if (capture.Consumption != null && capture.Kind != null && Object.op_Implicit((Object)(object)StatsManager.instance))
			{
				long num = (long)capture.Kind.GetLevel(StatsManager.instance, capture.SteamId) - (long)capture.Before;
				if (num > 0 && num <= int.MaxValue)
				{
					capture.Consumption.Gains.Add(new Gain(capture.Kind, capture.SteamId, capture.Before, (int)num));
				}
			}
		}

		private void Update()
		{
			if (!HasAuthority)
			{
				ResetProgress();
				return;
			}
			EnsureSession();
			if (Time.unscaledTime >= nextPlayerScan)
			{
				ScanPlayers();
				nextPlayerScan = Time.unscaledTime + 0.5f;
			}
			if (pending.Count != 0 && !(Time.unscaledTime < nextDispatch))
			{
				ProcessPending();
			}
		}

		internal void ProcessPending()
		{
			if (!HasAuthority)
			{
				ResetProgress();
				return;
			}
			EnsureSession();
			if (!Object.op_Implicit((Object)(object)PunManager.instance) || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance))
			{
				return;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if (!Object.op_Implicit((Object)(object)component) || component.ViewID == 0)
			{
				return;
			}
			int count = pending.Count;
			int num = 0;
			while (count-- > 0 && num < 4)
			{
				Delivery delivery = pending.Dequeue();
				if (!players.TryGetValue(delivery.SteamId, out JoiningPlayer value) || PhotonNetwork.CurrentRoom == null || !PhotonNetwork.CurrentRoom.Players.ContainsKey(value.Actor))
				{
					scheduled.Remove((delivery.Kind, delivery.SteamId));
					continue;
				}
				PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(delivery.SteamId);
				if (!Object.op_Implicit((Object)(object)val) || (Object)(object)value.Avatar != (Object)(object)val || !value.Ready || !AvatarReady(val))
				{
					pending.Enqueue(delivery);
					continue;
				}
				int level = delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId);
				int num2 = targets.Missing(delivery.SteamId, delivery.Kind.RpcName, level);
				if (num2 == 0)
				{
					scheduled.Remove((delivery.Kind, delivery.SteamId));
					continue;
				}
				try
				{
					dispatchDepth++;
					component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3]
					{
						delivery.SteamId,
						delivery.Kind.RpcName,
						num2
					});
					num++;
					if (targets.Missing(delivery.SteamId, delivery.Kind.RpcName, delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId)) > 0)
					{
						pending.Enqueue(delivery);
						if (Time.unscaledTime >= nextErrorLog)
						{
							((BaseUnityPlugin)this).Logger.LogWarning((object)"The game's upgrade receiver is not ready; delivery will be retried.");
							nextErrorLog = Time.unscaledTime + 15f;
						}
					}
					else
					{
						scheduled.Remove((delivery.Kind, delivery.SteamId));
						if (logShares.Value)
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)$"Shared {delivery.Kind.RpcName} +{num2}.");
						}
					}
				}
				catch (Exception exception)
				{
					if (targets.Missing(delivery.SteamId, delivery.Kind.RpcName, delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId)) > 0)
					{
						pending.Enqueue(delivery);
					}
					else
					{
						scheduled.Remove((delivery.Kind, delivery.SteamId));
					}
					LogDeliveryError("Unable to share an upgrade; ready players will be retried.", exception);
				}
				finally
				{
					dispatchDepth--;
				}
			}
			nextDispatch = Time.unscaledTime + 0.05f;
		}

		private void LogDeliveryError(string message, Exception exception)
		{
			if (!(Time.unscaledTime < nextErrorLog))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"{message} {exception}");
				nextErrorLog = Time.unscaledTime + 15f;
			}
		}

		private void OnDestroy()
		{
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			ResetProgress();
			currentConsumption = null;
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "ResetAllStats")]
	internal static class ResetRunPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			Plugin.Instance?.ResetProgress();
		}
	}
	[HarmonyPatch(typeof(StatsManager), "LoadGame")]
	internal static class LoadRunPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			Plugin.Instance?.ResetProgress();
		}
	}
	[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
	internal static class ConsumptionPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(ItemUpgrade __instance, out Plugin.Consumption? __state)
		{
			__state = Plugin.Instance?.BeginConsumption(__instance);
		}

		[HarmonyFinalizer]
		private static void Finalizer(Plugin.Consumption? __state)
		{
			Plugin.Instance?.EndConsumption(__state);
		}
	}
	[HarmonyPatch]
	internal static class AppliedUpgradePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			UpgradeKind[] all = UpgradeKind.All;
			foreach (UpgradeKind upgradeKind in all)
			{
				yield return upgradeKind.GetMethod();
			}
		}

		[HarmonyPrefix]
		private static void Prefix(MethodBase __originalMethod, string __0, out Plugin.Capture __state)
		{
			__state = Plugin.Instance?.BeforeUpgrade(__originalMethod, __0) ?? default(Plugin.Capture);
		}

		[HarmonyPostfix]
		private static void Postfix(Plugin.Capture __state)
		{
			Plugin.Instance?.AfterUpgrade(__state);
		}
	}
	internal sealed class UpgradeKind
	{
		internal readonly string MethodName;

		internal readonly string RpcName;

		private readonly Func<StatsManager, Dictionary<string, int>> levels;

		internal static readonly UpgradeKind[] All = new UpgradeKind[13]
		{
			new UpgradeKind("UpgradePlayerHealth", "Health", (StatsManager s) => s.playerUpgradeHealth),
			new UpgradeKind("UpgradePlayerEnergy", "Stamina", (StatsManager s) => s.playerUpgradeStamina),
			new UpgradeKind("UpgradePlayerExtraJump", "ExtraJump", (StatsManager s) => s.playerUpgradeExtraJump),
			new UpgradeKind("UpgradePlayerTumbleLaunch", "Launch", (StatsManager s) => s.playerUpgradeLaunch),
			new UpgradeKind("UpgradePlayerTumbleClimb", "TumbleClimb", (StatsManager s) => s.playerUpgradeTumbleClimb),
			new UpgradeKind("UpgradeDeathHeadBattery", "DeathHeadBattery", (StatsManager s) => s.playerUpgradeDeathHeadBattery),
			new UpgradeKind("UpgradeMapPlayerCount", "MapPlayerCount", (StatsManager s) => s.playerUpgradeMapPlayerCount),
			new UpgradeKind("UpgradePlayerTumbleWings", "TumbleWings", (StatsManager s) => s.playerUpgradeTumbleWings),
			new UpgradeKind("UpgradePlayerSprintSpeed", "Speed", (StatsManager s) => s.playerUpgradeSpeed),
			new UpgradeKind("UpgradePlayerCrouchRest", "CrouchRest", (StatsManager s) => s.playerUpgradeCrouchRest),
			new UpgradeKind("UpgradePlayerGrabStrength", "Strength", (StatsManager s) => s.playerUpgradeStrength),
			new UpgradeKind("UpgradePlayerThrowStrength", "Throw", (StatsManager s) => s.playerUpgradeThrow),
			new UpgradeKind("UpgradePlayerGrabRange", "Range", (StatsManager s) => s.playerUpgradeRange)
		};

		private UpgradeKind(string method, string rpc, Func<StatsManager, Dictionary<string, int>> levels)
		{
			MethodName = method;
			RpcName = rpc;
			this.levels = levels;
		}

		internal int GetLevel(StatsManager stats, string steamId)
		{
			if (!levels(stats).TryGetValue(steamId, out var value))
			{
				return 0;
			}
			return value;
		}

		internal int GetCrewLevel(StatsManager stats)
		{
			int num = 0;
			foreach (int value in levels(stats).Values)
			{
				num = Math.Max(num, value);
			}
			return num;
		}

		internal MethodInfo GetMethod()
		{
			return typeof(PunManager).GetMethod(MethodName, new Type[2]
			{
				typeof(string),
				typeof(int)
			}) ?? throw new MissingMethodException(typeof(PunManager).FullName, MethodName);
		}

		internal static UpgradeKind? FromMethod(string name)
		{
			UpgradeKind[] all = All;
			foreach (UpgradeKind upgradeKind in all)
			{
				if (upgradeKind.MethodName == name)
				{
					return upgradeKind;
				}
			}
			return null;
		}
	}
	internal sealed class UpgradeTargets
	{
		private readonly Dictionary<string, Dictionary<string, int>> players = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);

		internal int Get(string steamId, string kind, int current)
		{
			if (players.TryGetValue(steamId, out Dictionary<string, int> value) && value.TryGetValue(kind, out var value2))
			{
				return Math.Max(current, value2);
			}
			return Math.Max(0, current);
		}

		internal int CrewLevel(string kind, int savedLevel)
		{
			int num = Math.Max(0, savedLevel);
			foreach (Dictionary<string, int> value2 in players.Values)
			{
				if (value2.TryGetValue(kind, out var value))
				{
					num = Math.Max(num, value);
				}
			}
			return num;
		}

		internal int CatchUp(string steamId, string kind, int current, int crewLevel)
		{
			return Set(steamId, kind, Math.Max(Get(steamId, kind, current), crewLevel));
		}

		internal int Add(string steamId, string kind, int before, int amount)
		{
			return Set(steamId, kind, (int)Math.Min(2147483647L, (long)Get(steamId, kind, before) + (long)Math.Max(0, amount)));
		}

		internal int Missing(string steamId, string kind, int current)
		{
			return (int)Math.Min(2147483647L, Math.Max(0L, (long)Get(steamId, kind, current) - (long)current));
		}

		private int Set(string steamId, string kind, int value)
		{
			if (!players.TryGetValue(steamId, out Dictionary<string, int> value2))
			{
				players.Add(steamId, value2 = new Dictionary<string, int>(StringComparer.Ordinal));
			}
			return value2[kind] = value;
		}

		internal void Clear()
		{
			players.Clear();
		}
	}
}