Decompiled source of TogetherSharedUpgrades v1.0.0

plugins/TogetherSharedUpgrades/TogetherSharedUpgrades.dll

Decompiled an hour 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
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("TogetherSharedUpgrades")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Host-only sharing of vanilla R.E.P.O. player upgrades, with optional catch-up for joining players.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TogetherSharedUpgrades")]
[assembly: AssemblyTitle("TogetherSharedUpgrades")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 TogetherSharedUpgrades
{
	[BepInPlugin("se.repotogether.sharedupgrades", "TogetherSharedUpgrades", "1.0.0")]
	[BepInProcess("REPO.exe")]
	[BepInIncompatibility("se.repotogether15")]
	[BepInIncompatibility("Vippy.SharedUpgradesPlus")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "se.repotogether.sharedupgrades";

		private Harmony harmony;

		public static ManualLogSource Log { get; private set; }

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

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

		private void Awake()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ShareUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrades", "Enabled", true, "Each consumed vanilla player upgrade gives the same increase to every connected teammate. Host only.");
			CatchUpPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("Upgrades", "CatchUpPlayers", true, "Once per session/load, catch ready players up to the highest current-save level for each vanilla upgrade.");
			try
			{
				UpgradeSharing.ValidateGameApi();
				harmony = new Harmony("se.repotogether.sharedupgrades");
				harmony.PatchAll(typeof(Plugin).Assembly);
				Log.LogInfo((object)$"TogetherSharedUpgrades ready: enabled={ShareUpgrades.Value}; catch-up={CatchUpPlayers.Value}. Host the lobby to apply.");
			}
			catch (Exception ex)
			{
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				Log.LogError((object)("TogetherSharedUpgrades could not start; patches removed. Game API may have changed. " + ex));
				((Behaviour)this).enabled = false;
			}
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			UpgradeSharing.ResetSession();
		}
	}
	public static class UpgradeMath
	{
		public static int AddedLevels(int before, int after)
		{
			return (int)Math.Max(0L, Math.Min(2147483647L, (long)after - (long)before));
		}

		public static int MissingLevels(int current, IEnumerable<int> teamLevels)
		{
			int num = Math.Max(0, current);
			foreach (int teamLevel in teamLevels)
			{
				num = Math.Max(num, teamLevel);
			}
			return AddedLevels(Math.Max(0, current), num);
		}

		public static int SafeAddition(int current, int requested)
		{
			return (int)Math.Max(0L, Math.Min(Math.Max(0, requested), 2147483647L - (long)Math.Max(0, current)));
		}
	}
	public static class UpgradeSharing
	{
		internal sealed class PurchaseSnapshot
		{
			internal string Buyer;

			internal int Generation;

			internal StatsManager Stats;

			internal Dictionary<string, int> Before;
		}

		private const string KeyPrefix = "playerUpgrade";

		private static readonly string[] Suffixes = new string[13]
		{
			"CrouchRest", "DeathHeadBattery", "ExtraJump", "Health", "Launch", "MapPlayerCount", "Range", "Speed", "Stamina", "Strength",
			"Throw", "TumbleClimb", "TumbleWings"
		};

		private static readonly FieldInfo DictionariesField = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

		private static readonly FieldInfo UpgradeDoneField = AccessTools.Field(typeof(ItemUpgrade), "upgradeDone");

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

		private static readonly FieldInfo SpawnedField = AccessTools.Field(typeof(PlayerAvatar), "spawned");

		private static readonly HashSet<int> CaughtUpActors = new HashSet<int>();

		private static readonly Dictionary<int, PlayerTumble> Pending = new Dictionary<int, PlayerTumble>();

		private static Room sessionRoom;

		private static int generation;

		private static int sharingDepth;

		internal static void ValidateGameApi()
		{
			if (DictionariesField == null || !typeof(IDictionary<string, Dictionary<string, int>>).IsAssignableFrom(DictionariesField.FieldType) || UpgradeDoneField == null || UpgradeDoneField.FieldType != typeof(bool) || BuyerViewField == null || BuyerViewField.FieldType != typeof(int) || SpawnedField == null || SpawnedField.FieldType != typeof(bool))
			{
				throw new NotSupportedException("This game version has changed the upgrade API. Update TogetherSharedUpgrades.");
			}
		}

		public static void ResetSession()
		{
			generation++;
			CaughtUpActors.Clear();
			Pending.Clear();
			sessionRoom = PhotonNetwork.CurrentRoom;
		}

		private static void EnsureSession()
		{
			if (sessionRoom != PhotonNetwork.CurrentRoom)
			{
				ResetSession();
			}
		}

		private static bool Enabled()
		{
			if (Plugin.ShareUpgrades.Value)
			{
				return SemiFunc.IsMasterClientOrSingleplayer();
			}
			return false;
		}

		private static IDictionary<string, Dictionary<string, int>> GetStats()
		{
			if (!((Object)(object)StatsManager.instance == (Object)null))
			{
				return DictionariesField.GetValue(StatsManager.instance) as IDictionary<string, Dictionary<string, int>>;
			}
			return null;
		}

		private static int Level(IDictionary<string, Dictionary<string, int>> stats, string suffix, string player)
		{
			if (!stats.TryGetValue("playerUpgrade" + suffix, out var value) || !value.TryGetValue(player, out var value2))
			{
				return 0;
			}
			return value2;
		}

		internal static PurchaseSnapshot BeforePurchase(ItemUpgrade item)
		{
			if (!Enabled() || sharingDepth != 0 || (Object)(object)item == (Object)null || !item.isPlayerUpgrade)
			{
				return null;
			}
			EnsureSession();
			if ((bool)UpgradeDoneField.GetValue(item))
			{
				return null;
			}
			ItemToggle component = ((Component)item).GetComponent<ItemToggle>();
			if ((Object)(object)component == (Object)null || !component.toggleState)
			{
				return null;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID((int)BuyerViewField.GetValue(component));
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			string text = SemiFunc.PlayerGetSteamID(val);
			IDictionary<string, Dictionary<string, int>> stats = GetStats();
			if (string.IsNullOrEmpty(text) || stats == null)
			{
				return null;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			string[] suffixes = Suffixes;
			foreach (string text2 in suffixes)
			{
				if (stats.ContainsKey("playerUpgrade" + text2))
				{
					dictionary.Add(text2, Level(stats, text2, text));
				}
			}
			return new PurchaseSnapshot
			{
				Buyer = text,
				Generation = generation,
				Stats = StatsManager.instance,
				Before = dictionary
			};
		}

		internal static void AfterPurchase(ItemUpgrade item, PurchaseSnapshot snapshot)
		{
			if (snapshot == null || !Enabled() || sharingDepth != 0 || snapshot.Generation != generation || (Object)(object)snapshot.Stats != (Object)(object)StatsManager.instance || !(bool)UpgradeDoneField.GetValue(item))
			{
				return;
			}
			IDictionary<string, Dictionary<string, int>> stats = GetStats();
			if (stats == null || (Object)(object)PunManager.instance == (Object)null)
			{
				return;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, int> item2 in snapshot.Before)
			{
				int num = UpgradeMath.AddedLevels(item2.Value, Level(stats, item2.Key, snapshot.Buyer));
				if (num > 0)
				{
					dictionary.Add(item2.Key, num);
				}
			}
			if (dictionary.Count == 0)
			{
				return;
			}
			sharingDepth++;
			try
			{
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal) { snapshot.Buyer };
				PlayerAvatar[] array = SemiFunc.PlayerGetAll().ToArray();
				foreach (PlayerAvatar val in array)
				{
					if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null)
					{
						continue;
					}
					string text = SemiFunc.PlayerGetSteamID(val);
					if (string.IsNullOrEmpty(text) || !hashSet.Add(text))
					{
						continue;
					}
					foreach (KeyValuePair<string, int> item3 in dictionary)
					{
						SendUpgrade(val, text, item3.Key, item3.Value, stats);
					}
				}
				Plugin.Log.LogInfo((object)("Shared consumed upgrade: " + string.Join(", ", dictionary.Select((KeyValuePair<string, int> pair) => pair.Key + " +" + pair.Value).ToArray())));
			}
			finally
			{
				sharingDepth--;
			}
		}

		private static void SendUpgrade(PlayerAvatar player, string id, string suffix, int requested, IDictionary<string, Dictionary<string, int>> stats)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			int num = UpgradeMath.SafeAddition(Level(stats, suffix, id), requested);
			if (num == 0 || !stats.ContainsKey("playerUpgrade" + suffix))
			{
				return;
			}
			if (SemiFunc.IsMultiplayer())
			{
				PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
				if ((Object)(object)component == (Object)null)
				{
					throw new InvalidOperationException("PunManager has no PhotonView.");
				}
				if (player.photonView.Owner != null && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.GetPlayer(player.photonView.OwnerActorNr, false) != null)
				{
					component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { id, suffix, num });
				}
			}
			else
			{
				PunManager.instance.TesterUpgradeCommandRPC(id, suffix, num, default(PhotonMessageInfo));
			}
		}

		internal static void PlayerReady(PlayerTumble tumble)
		{
			if (!Enabled() || !Plugin.CatchUpPlayers.Value || !SemiFunc.IsMultiplayer() || (Object)(object)tumble == (Object)null || (Object)(object)tumble.playerAvatar == (Object)null)
			{
				return;
			}
			EnsureSession();
			PlayerAvatar playerAvatar = tumble.playerAvatar;
			if (!((Object)(object)playerAvatar.photonView == (Object)null) && playerAvatar.photonView.Owner != null)
			{
				int ownerActorNr = playerAvatar.photonView.OwnerActorNr;
				if (!CaughtUpActors.Contains(ownerActorNr) && (!Pending.TryGetValue(ownerActorNr, out var value) || !((Object)(object)value != (Object)null)))
				{
					Pending[ownerActorNr] = tumble;
					((MonoBehaviour)tumble).StartCoroutine(CatchUpWhenSpawned(tumble, ownerActorNr, generation, sessionRoom));
				}
			}
		}

		private static IEnumerator CatchUpWhenSpawned(PlayerTumble tumble, int actor, int queuedGeneration, Room queuedRoom)
		{
			try
			{
				float deadline = Time.realtimeSinceStartup + 90f;
				PlayerAvatar player = tumble.playerAvatar;
				while ((Object)(object)player != (Object)null && !(bool)SpawnedField.GetValue(player))
				{
					if (!IsCurrent(queuedGeneration, queuedRoom) || Time.realtimeSinceStartup > deadline)
					{
						yield break;
					}
					yield return null;
				}
				yield return (object)new WaitForSecondsRealtime(1f);
				if (!((Object)(object)player == (Object)null) && IsCurrent(queuedGeneration, queuedRoom) && Enabled() && Plugin.CatchUpPlayers.Value && !((Object)(object)PunManager.instance == (Object)null))
				{
					string text = SemiFunc.PlayerGetSteamID(player);
					IDictionary<string, Dictionary<string, int>> stats = GetStats();
					if (!string.IsNullOrEmpty(text) && stats != null && queuedRoom.GetPlayer(actor, false) != null && CaughtUpActors.Add(actor))
					{
						CatchUpPlayer(player, text, stats);
					}
				}
			}
			finally
			{
				if (queuedGeneration == generation && Pending.TryGetValue(actor, out var value) && value == tumble)
				{
					Pending.Remove(actor);
				}
			}
		}

		private static bool IsCurrent(int queuedGeneration, Room queuedRoom)
		{
			if (queuedGeneration == generation && queuedRoom != null)
			{
				return queuedRoom == PhotonNetwork.CurrentRoom;
			}
			return false;
		}

		private static void CatchUpPlayer(PlayerAvatar player, string id, IDictionary<string, Dictionary<string, int>> stats)
		{
			sharingDepth++;
			try
			{
				int num = 0;
				string[] suffixes = Suffixes;
				foreach (string text in suffixes)
				{
					if (stats.TryGetValue("playerUpgrade" + text, out var value))
					{
						int num2 = UpgradeMath.MissingLevels(Level(stats, text, id), from pair in value
							where pair.Key != id
							select pair.Value);
						if (num2 > 0)
						{
							SendUpgrade(player, id, text, num2, stats);
							num++;
						}
					}
				}
				if (num > 0)
				{
					Plugin.Log.LogInfo((object)("Caught up joining player with " + num + " upgrade types."));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not finish upgrade catch-up: " + ex));
			}
			finally
			{
				sharingDepth--;
			}
		}
	}
	[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
	internal static class ShareConsumedUpgradePatch
	{
		private static void Prefix(ItemUpgrade __instance, out UpgradeSharing.PurchaseSnapshot __state)
		{
			__state = null;
			try
			{
				__state = UpgradeSharing.BeforePurchase(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Upgrade snapshot failed: " + ex));
			}
		}

		private static void Postfix(ItemUpgrade __instance, UpgradeSharing.PurchaseSnapshot __state)
		{
			try
			{
				UpgradeSharing.AfterPurchase(__instance, __state);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Upgrade sharing failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerTumble), "SetupDone")]
	internal static class CatchUpReadyPlayerPatch
	{
		private static void Postfix(PlayerTumble __instance)
		{
			try
			{
				UpgradeSharing.PlayerReady(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not schedule upgrade catch-up: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "ResetAllStats")]
	internal static class ResetSharedUpgradesPatch
	{
		private static void Prefix()
		{
			UpgradeSharing.ResetSession();
		}
	}
	[HarmonyPatch(typeof(StatsManager), "LoadGame")]
	internal static class LoadSharedUpgradesPatch
	{
		private static void Prefix()
		{
			UpgradeSharing.ResetSession();
		}
	}
}