Decompiled source of SharedHostUpgrades by Schleimi v1.0.3

BepInEx/plugins/SharedHostUpgrades_by_Schleimi/SharedHostUpgrades_by_Schleimi.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace SharedHostUpgrades;

[BepInPlugin("schleimi.sharedhostupgrades", "Shared Host Upgrades by Schleimi", "1.0.3")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Guid = "schleimi.sharedhostupgrades";

	public const string Name = "Shared Host Upgrades by Schleimi";

	public const string Version = "1.0.3";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> PollSeconds;

	internal static ConfigEntry<float> SettleSeconds;

	internal static ConfigEntry<float> RpcIntervalSeconds;

	private void Awake()
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Expected O, but got Unknown
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Expected O, but got Unknown
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Host-only upgrade sharing.");
		PollSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PollSeconds", 1f, new ConfigDescription("How often the host checks for newly used upgrades.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 10f), new object[0]));
		SettleSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SettleSeconds", 0.5f, new ConfigDescription("Quiet time after the last upgrade before synchronization begins.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), new object[0]));
		RpcIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RpcIntervalSeconds", 0.15f, new ConfigDescription("Minimum delay between shared upgrade RPCs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), new object[0]));
		new Harmony("schleimi.sharedhostupgrades").PatchAll();
		Log.LogInfo((object)"Shared Host Upgrades by Schleimi 1.0.3 loaded; host delta synchronizer installed.");
	}
}
[HarmonyPatch(typeof(PlayerHealth), "Start")]
public static class PlayerHealthHostSyncPatch
{
	public static void Postfix(PlayerHealth __instance)
	{
		if (!((Object)(object)__instance == (Object)null) && !(((Object)__instance).name == "PlayerAvatarMenu"))
		{
			HostUpgradeSyncRunner hostUpgradeSyncRunner = ((Component)__instance).gameObject.GetComponent<HostUpgradeSyncRunner>();
			if ((Object)(object)hostUpgradeSyncRunner == (Object)null)
			{
				hostUpgradeSyncRunner = ((Component)__instance).gameObject.AddComponent<HostUpgradeSyncRunner>();
			}
			hostUpgradeSyncRunner.ScheduleMapStart();
		}
	}
}
public sealed class HostUpgradeSyncRunner : MonoBehaviour
{
	private sealed class UpgradeDelta
	{
		internal readonly string Key;

		internal readonly string PlayerId;

		internal readonly int Amount;

		internal UpgradeDelta(string key, string playerId, int amount)
		{
			Key = key;
			PlayerId = playerId;
			Amount = amount;
		}
	}

	private sealed class UpgradeCommand
	{
		internal readonly string Key;

		internal readonly string PlayerId;

		internal UpgradeCommand(string key, string playerId)
		{
			Key = key;
			PlayerId = playerId;
		}
	}

	private static HostUpgradeSyncRunner active;

	private readonly Dictionary<string, int> hostLevels = new Dictionary<string, int>();

	private readonly Dictionary<string, Dictionary<string, int>> observed = new Dictionary<string, Dictionary<string, int>>();

	private float initializeAt = -1f;

	private float nextPoll;

	private bool initialized;

	private bool reconciliationPending;

	private bool reconciliationRunning;

	private float reconcileAt;

	private float nextRpcAt;

	private readonly Queue<UpgradeCommand> rpcQueue = new Queue<UpgradeCommand>();

	private void Awake()
	{
		if ((Object)(object)active == (Object)null)
		{
			active = this;
		}
	}

	private void OnDestroy()
	{
		if ((Object)(object)active == (Object)(object)this)
		{
			active = null;
		}
	}

	internal void ScheduleMapStart()
	{
		if ((Object)(object)active != (Object)(object)this)
		{
			if ((Object)(object)active != (Object)null)
			{
				active.ScheduleMapStart();
			}
		}
		else
		{
			initializeAt = Time.time + 2f;
			initialized = false;
			CancelReconciliation();
		}
	}

	private void Update()
	{
		if ((Object)(object)active != (Object)(object)this || !Plugin.Enabled.Value || !SemiFunc.IsMasterClientOrSingleplayer())
		{
			return;
		}
		if (initializeAt >= 0f && Time.time >= initializeAt)
		{
			if (InitializeFromHost())
			{
				initializeAt = -1f;
				nextPoll = Time.time + Plugin.PollSeconds.Value;
			}
			else
			{
				initializeAt = Time.time + 1f;
			}
		}
		else
		{
			if (!initialized)
			{
				return;
			}
			if (reconciliationRunning)
			{
				ProcessReconciliation();
			}
			else if (reconciliationPending)
			{
				if (Time.time >= reconcileAt)
				{
					BeginReconciliation();
				}
			}
			else if (!(Time.time < nextPoll))
			{
				nextPoll = Time.time + Plugin.PollSeconds.Value;
				DetectNewUpgradeSteps();
			}
		}
	}

	private bool InitializeFromHost()
	{
		if (!PlayersReady())
		{
			return false;
		}
		string text = HostSteamId();
		if (string.IsNullOrEmpty(text))
		{
			return false;
		}
		hostLevels.Clear();
		foreach (KeyValuePair<string, Dictionary<string, int>> item in UpgradeDictionaries())
		{
			item.Value.TryGetValue(text, out var value);
			hostLevels[item.Key] = value;
		}
		initialized = true;
		CancelReconciliation();
		CaptureObserved();
		reconciliationPending = true;
		reconcileAt = Time.time + Plugin.SettleSeconds.Value;
		Plugin.Log.LogInfo((object)"Host upgrade values initialized after map start.");
		return true;
	}

	private void DetectNewUpgradeSteps()
	{
		if (!PlayersReady())
		{
			return;
		}
		List<UpgradeDelta> list = new List<UpgradeDelta>();
		foreach (KeyValuePair<string, Dictionary<string, int>> item in UpgradeDictionaries())
		{
			foreach (KeyValuePair<string, int> item2 in item.Value)
			{
				int num = ObservedLevel(item.Key, item2.Key);
				if (item2.Value > num)
				{
					list.Add(new UpgradeDelta(item.Key, item2.Key, item2.Value - num));
				}
			}
		}
		if (list.Count == 0)
		{
			return;
		}
		string text = HostSteamId();
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		foreach (UpgradeDelta item3 in list)
		{
			hostLevels.TryGetValue(item3.Key, out var value);
			if (item3.PlayerId == text)
			{
				hostLevels[item3.Key] = value + item3.Amount;
			}
			else
			{
				Apply(item3.Key, text, item3.Amount);
				hostLevels[item3.Key] = value + item3.Amount;
			}
			Plugin.Log.LogInfo((object)("Host received " + item3.Key + " +" + item3.Amount + " from " + item3.PlayerId + "."));
		}
		CaptureObserved();
		reconciliationPending = true;
		reconcileAt = Time.time + Plugin.SettleSeconds.Value;
		Plugin.Log.LogInfo((object)("Shared upgrade synchronization queued: " + list.Count + " new upgrade step(s)."));
	}

	private void BeginReconciliation()
	{
		if (!PlayersReady())
		{
			reconciliationPending = true;
			reconcileAt = Time.time + 1f;
			return;
		}
		reconciliationPending = false;
		rpcQueue.Clear();
		foreach (KeyValuePair<string, Dictionary<string, int>> item in UpgradeDictionaries())
		{
			if (!hostLevels.TryGetValue(item.Key, out var value))
			{
				continue;
			}
			List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
			if (list == null)
			{
				continue;
			}
			for (int i = 0; i < list.Count; i++)
			{
				string text = Field(list[i], "steamID") as string;
				if (!string.IsNullOrEmpty(text))
				{
					item.Value.TryGetValue(text, out var value2);
					int num = Math.Max(0, value - value2);
					for (int j = 0; j < num; j++)
					{
						rpcQueue.Enqueue(new UpgradeCommand(item.Key, text));
					}
				}
			}
		}
		if (rpcQueue.Count == 0)
		{
			Plugin.Log.LogInfo((object)"Shared upgrade synchronization: all players already match the host.");
			return;
		}
		reconciliationRunning = true;
		nextRpcAt = Time.time;
		Plugin.Log.LogInfo((object)("Shared upgrade synchronization started: " + rpcQueue.Count + " queued RPC(s)."));
	}

	private void ProcessReconciliation()
	{
		if (!PlayersReady())
		{
			CancelReconciliation();
			reconciliationPending = true;
			reconcileAt = Time.time + 1f;
			Plugin.Log.LogInfo((object)"Shared upgrade synchronization paused until player avatars are ready.");
		}
		else if (!(Time.time < nextRpcAt))
		{
			if (rpcQueue.Count == 0)
			{
				reconciliationRunning = false;
				Plugin.Log.LogInfo((object)"Shared upgrade synchronization completed.");
				return;
			}
			UpgradeCommand upgradeCommand = rpcQueue.Dequeue();
			Apply(upgradeCommand.Key, upgradeCommand.PlayerId, 1);
			CaptureObservedLevel(upgradeCommand.Key, upgradeCommand.PlayerId);
			nextRpcAt = Time.time + Plugin.RpcIntervalSeconds.Value;
		}
	}

	private void CancelReconciliation()
	{
		reconciliationPending = false;
		reconciliationRunning = false;
		rpcQueue.Clear();
	}

	private void CaptureObserved()
	{
		observed.Clear();
		foreach (KeyValuePair<string, Dictionary<string, int>> item in UpgradeDictionaries())
		{
			observed[item.Key] = new Dictionary<string, int>(item.Value);
		}
	}

	private void CaptureObservedLevel(string key, string playerId)
	{
		if (!observed.TryGetValue(key, out var value))
		{
			value = new Dictionary<string, int>();
			observed[key] = value;
		}
		foreach (KeyValuePair<string, Dictionary<string, int>> item in UpgradeDictionaries())
		{
			if (item.Key != key)
			{
				continue;
			}
			item.Value.TryGetValue(playerId, out var value2);
			value[playerId] = value2;
			break;
		}
	}

	private int ObservedLevel(string key, string playerId)
	{
		Dictionary<string, int> value;
		int value2;
		return (observed.TryGetValue(key, out value) && value.TryGetValue(playerId, out value2)) ? value2 : 0;
	}

	private static void Apply(string key, string steamId, int levels)
	{
		if (levels > 0 && !((Object)(object)PunManager.instance == (Object)null))
		{
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if (!((Object)(object)component == (Object)null))
			{
				component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3]
				{
					steamId,
					key.Substring("playerUpgrade".Length),
					levels
				});
			}
		}
	}

	private static string HostSteamId()
	{
		return Field(SemiFunc.PlayerAvatarLocal(), "steamID") as string;
	}

	private static bool PlayersReady()
	{
		List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
		if (list == null || list.Count == 0)
		{
			return false;
		}
		for (int i = 0; i < list.Count; i++)
		{
			PlayerAvatar val = list[i];
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (Field(val, "photonView") == null)
			{
				return false;
			}
			if (Field(val, "playerHealth") == null)
			{
				return false;
			}
			if (Field(val, "physGrabber") == null)
			{
				return false;
			}
			if (Field(val, "tumble") == null)
			{
				return false;
			}
		}
		return true;
	}

	private static IEnumerable<KeyValuePair<string, Dictionary<string, int>>> UpgradeDictionaries()
	{
		object obj = Field(StatsManager.instance, "dictionaryOfDictionaries");
		SortedDictionary<string, Dictionary<string, int>> sortedDictionary = obj as SortedDictionary<string, Dictionary<string, int>>;
		SortedDictionary<string, Dictionary<string, int>> sortedDictionary2 = new SortedDictionary<string, Dictionary<string, int>>();
		if (sortedDictionary == null)
		{
			return sortedDictionary2;
		}
		foreach (KeyValuePair<string, Dictionary<string, int>> item in sortedDictionary)
		{
			if (!(item.Key == "playerUpgradeTumbleLaunch") && item.Key.StartsWith("playerUpgrade", StringComparison.Ordinal) && item.Value != null)
			{
				sortedDictionary2.Add(item.Key, item.Value);
			}
		}
		return sortedDictionary2;
	}

	private static object Field(object target, string name)
	{
		if (target == null)
		{
			return null;
		}
		FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		return (field == null) ? null : field.GetValue(target);
	}
}