Decompiled source of PersistentMultiTome v0.1.2

plugins/PersistentMultiTome.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using MyBox;
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("PersistentMultiTome")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+aeacebcbce45456e93123fbab4e90a51051d05f9")]
[assembly: AssemblyProduct("PersistentMultiTome")]
[assembly: AssemblyTitle("PersistentMultiTome")]
[assembly: AssemblyVersion("0.1.2.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 PersistentMultiTome
{
	internal sealed class ConfigurationManagerAttributes
	{
		public string DispName;

		public int? Order;
	}
	[BepInPlugin("com.darkharasho.stonewards.persistentmultitome", "PersistentMultiTome", "0.1.2")]
	[BepInIncompatibility("rei.stonewards.multitome")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.darkharasho.stonewards.persistentmultitome";

		public const string PluginName = "PersistentMultiTome";

		public const string PluginVersion = "0.1.2";

		public const string MultiTomeGuid = "rei.stonewards.multitome";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> SharePickupUpgrades;

		internal static ConfigEntry<bool> ShareRingUpgrades;

		internal static ConfigEntry<bool> ShareServerWideUpgrades;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SharePickupUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "SharePickupUpgrades", true, new ConfigDescription("Share tomes picked up in the level with every player.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					DispName = "Share tomes",
					Order = 30
				}
			}));
			ShareRingUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareRingUpgrades", false, new ConfigDescription("Share rings chosen on level-up with every player.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					DispName = "Share rings",
					Order = 20
				}
			}));
			ShareServerWideUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareServerWideUpgrades", false, new ConfigDescription("Also share upgrades the base game already applies to the whole team. Each player's copy is shared back once with everyone else, so the effect climbs quickly with more players.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					DispName = "Share upgrades the game already shares",
					Order = 10
				}
			}));
			_harmony = new Harmony("com.darkharasho.stonewards.persistentmultitome");
			_harmony.PatchAll();
			Log.LogInfo((object)"PersistentMultiTome 0.1.2 loaded. Only the host needs it.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal sealed class ShareLedger
	{
		internal readonly struct Entry
		{
			public readonly ulong PickerID;

			public readonly string UpgradeID;

			public readonly int Rarity;

			public Entry(ulong pickerID, string upgradeID, int rarity)
			{
				PickerID = pickerID;
				UpgradeID = upgradeID;
				Rarity = rarity;
			}
		}

		private const string Header = "# PersistentMultiTome shared upgrades";

		private readonly List<Entry> entries = new List<Entry>();

		public IReadOnlyList<Entry> Entries => entries;

		public void Add(Entry entry)
		{
			entries.Add(entry);
		}

		public void Clear()
		{
			entries.Clear();
		}

		public IEnumerable<Entry> BacklogFor(ulong playerID)
		{
			foreach (Entry entry in entries)
			{
				if (entry.PickerID != playerID)
				{
					yield return entry;
				}
			}
		}

		public string Serialize(long saveStamp)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("# PersistentMultiTome shared upgrades").Append('\n');
			stringBuilder.Append("save ").Append(saveStamp.ToString(CultureInfo.InvariantCulture)).Append('\n');
			foreach (Entry entry in entries)
			{
				stringBuilder.Append(entry.PickerID.ToString(CultureInfo.InvariantCulture)).Append('\t').Append(entry.Rarity.ToString(CultureInfo.InvariantCulture))
					.Append('\t')
					.Append(entry.UpgradeID)
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public bool TryLoad(string text, long saveStamp)
		{
			entries.Clear();
			if (text == null)
			{
				return false;
			}
			List<Entry> list = new List<Entry>();
			using (StringReader stringReader = new StringReader(text))
			{
				if (stringReader.ReadLine() != "# PersistentMultiTome shared upgrades")
				{
					return false;
				}
				string text2 = stringReader.ReadLine();
				if (text2 == null || !text2.StartsWith("save ", StringComparison.Ordinal) || !long.TryParse(text2.Substring(5), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result != saveStamp)
				{
					return false;
				}
				string text3;
				while ((text3 = stringReader.ReadLine()) != null)
				{
					if (text3.Length != 0)
					{
						string[] array = text3.Split(new char[1] { '\t' }, 3);
						if (array.Length != 3 || array[2].Length == 0 || !ulong.TryParse(array[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || !int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result3))
						{
							return false;
						}
						list.Add(new Entry(result2, array[2], result3));
					}
				}
			}
			entries.AddRange(list);
			return true;
		}
	}
	internal static class ShareRules
	{
		public static bool ShouldShare(bool isPickup, bool isServerWide, bool sharePickups, bool shareRings, bool shareServerWide)
		{
			if (isServerWide && !shareServerWide)
			{
				return false;
			}
			if (!isPickup)
			{
				return shareRings;
			}
			return sharePickups;
		}
	}
	internal static class TomeSharing
	{
		private static readonly ShareLedger ledger = new ShareLedger();

		private static readonly HashSet<ulong> firstJoins = new HashSet<ulong>();

		private static string LedgerPath => Path.Combine(Paths.ConfigPath, "PersistentMultiTome.shared.txt");

		private static Dictionary<ulong, RunPlayerData> RunPlayers => Singleton<SaveManager>.Instance.saveContainer.runPlayersData;

		public static void OnUpgradePicked(FirstPersonController picker, string upgradeID, Rarity rarity)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected I4, but got Unknown
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkServer.active || (Object)(object)picker == (Object)null || (Object)(object)RogueLikeUpgradeManager.Instance == (Object)null)
			{
				return;
			}
			RogueLikeUpgradeSO upgradeSOByID = RogueLikeUpgradeManager.Instance.GetUpgradeSOByID(upgradeID);
			if ((Object)(object)upgradeSOByID == (Object)null || !ShouldShare(upgradeSOByID))
			{
				return;
			}
			ledger.Add(new ShareLedger.Entry(picker.playerID, upgradeID, (int)rarity));
			HashSet<ulong> hashSet = new HashSet<ulong> { picker.playerID };
			List<FirstPersonController> list = new List<FirstPersonController> { picker };
			foreach (FirstPersonController item in OnlinePlayers())
			{
				if (hashSet.Add(item.playerID))
				{
					list.Add(item);
					Grant(item, upgradeSOByID, rarity);
				}
			}
			int num = 0;
			if (((BaseUpgradeSO)upgradeSOByID).IsServer)
			{
				foreach (FirstPersonController item2 in list)
				{
					if ((Object)(object)item2 == (Object)(object)picker)
					{
						continue;
					}
					foreach (FirstPersonController item3 in list)
					{
						if (!((Object)(object)item3 == (Object)(object)item2))
						{
							Grant(item3, upgradeSOByID, rarity);
							num++;
						}
					}
				}
			}
			List<RunPlayerData> list2 = new List<RunPlayerData>();
			foreach (KeyValuePair<ulong, RunPlayerData> runPlayer in RunPlayers)
			{
				if (!hashSet.Contains(runPlayer.Key))
				{
					list2.Add(runPlayer.Value);
				}
			}
			foreach (RunPlayerData item4 in list2)
			{
				Record(item4, upgradeID, rarity);
			}
			Plugin.Log.LogInfo((object)$"Shared {upgradeSOByID.UpgradeCategory} {upgradeID} ({rarity}) with {hashSet.Count - 1} online and {list2.Count} offline players ({num} shared back).");
		}

		public static void BeforeServerInit(FirstPersonController player)
		{
			if (NetworkServer.active)
			{
				if (RunPlayers.ContainsKey(player.playerID))
				{
					firstJoins.Remove(player.playerID);
				}
				else
				{
					firstJoins.Add(player.playerID);
				}
			}
		}

		public static void AfterPlayerAdded(NetworkConnectionToClient conn)
		{
			if (!NetworkServer.active || (Object)(object)((conn != null) ? ((NetworkConnection)conn).identity : null) == (Object)null)
			{
				return;
			}
			FirstPersonController component = ((Component)((NetworkConnection)conn).identity).GetComponent<FirstPersonController>();
			if ((Object)(object)component == (Object)null || !firstJoins.Remove(component.playerID) || ledger.Entries.Count == 0 || (Object)(object)RogueLikeUpgradeManager.Instance == (Object)null)
			{
				return;
			}
			int num = 0;
			foreach (ShareLedger.Entry item in ledger.BacklogFor(component.playerID))
			{
				RogueLikeUpgradeSO upgradeSOByID = RogueLikeUpgradeManager.Instance.GetUpgradeSOByID(item.UpgradeID);
				if (!((Object)(object)upgradeSOByID == (Object)null))
				{
					Grant(component, upgradeSOByID, (Rarity)item.Rarity);
					num++;
				}
			}
			Plugin.Log.LogInfo((object)$"Gave {num} previously shared upgrades to {component.playerID}, who joined mid-run.");
		}

		public static void OnRunDataCleared()
		{
			ledger.Clear();
			firstJoins.Clear();
		}

		public static void OnSaved(SaveContainer container)
		{
			try
			{
				File.WriteAllText(LedgerPath, ledger.Serialize(container.lastSaveTimeUtc.Ticks));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not save shared upgrades to " + LedgerPath + ": " + ex.Message));
			}
		}

		public static void OnLoaded(SaveContainer container)
		{
			if (container == null || !File.Exists(LedgerPath))
			{
				return;
			}
			try
			{
				if (!ledger.TryLoad(File.ReadAllText(LedgerPath), container.lastSaveTimeUtc.Ticks))
				{
					Plugin.Log.LogWarning((object)"Saved shared upgrades don't match this save; ignoring them.");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not load shared upgrades from " + LedgerPath + ": " + ex.Message));
			}
		}

		private static bool ShouldShare(RogueLikeUpgradeSO upgrade)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			return ShareRules.ShouldShare((int)upgrade.UpgradeCategory == 1, ((BaseUpgradeSO)upgrade).IsServer, Plugin.SharePickupUpgrades.Value, Plugin.ShareRingUpgrades.Value, Plugin.ShareServerWideUpgrades.Value);
		}

		private static void Grant(FirstPersonController player, RogueLikeUpgradeSO upgrade, Rarity rarity)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_0027: Expected O, but got Unknown
			RogueLikeUpgradeManager.Instance.ServerUpgradeAcquired(player, upgrade, rarity);
			if (!RunPlayers.TryGetValue(player.playerID, out var value))
			{
				value = new RunPlayerData();
				RunPlayers[player.playerID] = value;
			}
			Record(value, ((BaseUpgradeSO)upgrade).ID, rarity);
			if (((NetworkBehaviour)player).connectionToClient != null)
			{
				NetworkHelper.Instance.TargetRestoreRogueUpgrade(((NetworkBehaviour)player).connectionToClient, ((BaseUpgradeSO)upgrade).ID);
			}
		}

		private static void Record(RunPlayerData record, string upgradeID, Rarity rarity)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			record.rogueUpgrades.Add(new RogueUpgradeData
			{
				upgradeID = upgradeID,
				rarity = rarity
			});
		}

		private static IEnumerable<FirstPersonController> OnlinePlayers()
		{
			foreach (NetworkConnectionToClient value in NetworkServer.connections.Values)
			{
				if (!((Object)(object)((value != null) ? ((NetworkConnection)value).identity : null) == (Object)null))
				{
					FirstPersonController component = ((Component)((NetworkConnection)value).identity).GetComponent<FirstPersonController>();
					if ((Object)(object)component != (Object)null)
					{
						yield return component;
					}
				}
			}
		}
	}
}
namespace PersistentMultiTome.Patches
{
	[HarmonyPatch(typeof(NetworkHelper), "UserCode_CmdApplyUpgrade__FirstPersonController__String__Rarity")]
	internal static class SharePickedUpgradePatch
	{
		private static void Postfix(FirstPersonController _Player, string _UpgradeID, Rarity _UpgradeRarity)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			TomeSharing.OnUpgradePicked(_Player, _UpgradeID, _UpgradeRarity);
		}
	}
	[HarmonyPatch(typeof(FirstPersonController), "ServerInit")]
	internal static class DetectFirstJoinPatch
	{
		private static void Prefix(FirstPersonController __instance)
		{
			TomeSharing.BeforeServerInit(__instance);
		}
	}
	[HarmonyPatch(typeof(StoneWardsNetworkManager), "OnServerAddPlayer")]
	internal static class GiveBacklogOnJoinPatch
	{
		private static void Postfix(NetworkConnectionToClient conn)
		{
			TomeSharing.AfterPlayerAdded(conn);
		}
	}
	[HarmonyPatch(typeof(StoneWardsNetworkManager), "ClearAllRunData")]
	internal static class ClearOnRunEndPatch
	{
		private static void Postfix()
		{
			TomeSharing.OnRunDataCleared();
		}
	}
	[HarmonyPatch(typeof(SaveManager), "SaveProgression")]
	internal static class SaveLedgerPatch
	{
		private static void Postfix(SaveContainer _saveContainer, bool __result)
		{
			if (__result)
			{
				TomeSharing.OnSaved(_saveContainer);
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "LoadProgressionSave")]
	internal static class LoadLedgerPatch
	{
		private static void Postfix(SaveManager __instance)
		{
			TomeSharing.OnLoaded(__instance.saveContainer);
		}
	}
}