Decompiled source of Relay v1.0.0

Relay.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Character_Stats;
using ExitGames.Client.Photon;
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: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("headclef")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+e1955d83e810698585f2dbd151fbe59e49a48155")]
[assembly: AssemblyProduct("Relay")]
[assembly: AssemblyTitle("Relay")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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 Relay
{
	[BepInPlugin("headclef.Relay", "Relay", "1.0.0")]
	public class Relay : BaseUnityPlugin
	{
		private const string PluginGuid = "headclef.Relay";

		private const string PluginName = "Relay";

		private const string PluginVersion = "1.0.0";

		internal static ConfigEntry<bool> Enabled;

		public const string StatGrabStrength = "playerUpgradeStrength";

		public const string StatTumbleLaunch = "playerUpgradeLaunch";

		public const string StatThrow = "playerUpgradeThrow";

		public const string StatTumbleWings = "playerUpgradeTumbleWings";

		internal static Relay Instance { get; private set; }

		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		public static bool CanDeliver => RelayBridge.CanDeliver;

		private void Awake()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_004f: Expected O, but got Unknown
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			BindConfiguration();
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!");
		}

		private void OnDestroy()
		{
			RelayBridge.Teardown();
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void BindConfiguration()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "Enabled", false, new ConfigDescription("EXPERIMENTAL - OFF BY DEFAULT. Grab Strength, Tumble Launch, Throw and Tumble Wings are simulated by R.E.P.O. on the host's machine, from the host's own copy of your character, so as a co-op client they normally do nothing for you no matter which mod grants them. Turn this on and your client reports those levels to the host, where Relay applies them to the host's copy of you. The host must also run Relay with this turned on - against a host without it, nothing happens and nothing breaks. An earlier always-on version of this bridge broke multiplayer connect (the server list would not load and picking a region hung the game), so it is off unless you opt in. With it off, every mod behaves exactly as it does without Relay installed - your other stats are unaffected either way, they already work as a client.", (AcceptableValueBase)null, Array.Empty<object>()));
		}

		public static void Report(string sourceId, string statName, int levels)
		{
			RelayBridge.Report(sourceId, statName, levels);
		}

		public static void Clear(string sourceId)
		{
			RelayBridge.Clear(sourceId);
		}
	}
	internal static class RelayBridge
	{
		private sealed class AppliedStat
		{
			public Object? component;

			public float baseValue;

			public float lastWritten;
		}

		private const byte EventReport = 117;

		private const byte EventAck = 118;

		private const string Magic = "headclef.Relay";

		private const float Eps = 0.001f;

		private const int HeartbeatTicks = 20;

		private const float AckTimeout = 15f;

		private static readonly string[] StatNames = new string[4] { "playerUpgradeStrength", "playerUpgradeLaunch", "playerUpgradeThrow", "playerUpgradeTumbleWings" };

		private static readonly float[] PerLevel = new float[4] { 0.2f, 1f, 0.3f, 1f };

		private static readonly string[] CsKeys = new string[4] { "Strength", "Launch", "Throw", "Tumble Wings" };

		private static readonly Dictionary<string, int[]> _sources = new Dictionary<string, int[]>();

		private static bool _subscribed;

		private static int _ticksSinceSend;

		private static float _lastAck = -999f;

		private static readonly int[] _lastSent = new int[4] { -1, -1, -1, -1 };

		private static readonly Dictionary<int, int[]> _desired = new Dictionary<int, int[]>();

		private static readonly Dictionary<(int actor, int stat), AppliedStat> _applied = new Dictionary<(int, int), AppliedStat>();

		private static bool? _csPresent;

		internal static bool CanDeliver
		{
			get
			{
				try
				{
					if (!SemiFunc.IsMultiplayer())
					{
						return true;
					}
					if (SemiFunc.IsMasterClientOrSingleplayer())
					{
						return true;
					}
					if (!Relay.Enabled.Value)
					{
						return false;
					}
					return Time.unscaledTime - _lastAck < 15f;
				}
				catch
				{
					return false;
				}
			}
		}

		private static bool CsPresent
		{
			get
			{
				bool valueOrDefault = _csPresent == true;
				if (!_csPresent.HasValue)
				{
					valueOrDefault = Chainloader.PluginInfos.ContainsKey("headclef.CharacterStats");
					_csPresent = valueOrDefault;
				}
				return _csPresent.Value;
			}
		}

		internal static void Report(string sourceId, string statName, int levels)
		{
			if (string.IsNullOrEmpty(sourceId))
			{
				return;
			}
			int num = Array.IndexOf(StatNames, statName);
			if (num >= 0)
			{
				if (!_sources.TryGetValue(sourceId, out int[] value))
				{
					value = new int[StatNames.Length];
					_sources[sourceId] = value;
				}
				value[num] = Mathf.Max(0, levels);
			}
		}

		internal static void Clear(string sourceId)
		{
			if (!string.IsNullOrEmpty(sourceId))
			{
				_sources.Remove(sourceId);
			}
		}

		private static int[] Totals()
		{
			int[] array = new int[StatNames.Length];
			foreach (int[] value in _sources.Values)
			{
				for (int i = 0; i < array.Length; i++)
				{
					array[i] += value[i];
				}
			}
			return array;
		}

		internal static void Tick()
		{
			try
			{
				if (!Relay.Enabled.Value)
				{
					Teardown();
				}
				else if (!SemiFunc.IsMultiplayer())
				{
					Teardown();
				}
				else if (PhotonNetwork.InRoom && PhotonNetwork.IsMessageQueueRunning)
				{
					Subscribe();
					if (PhotonNetwork.IsMasterClient)
					{
						ReceiverTick();
					}
					else
					{
						SenderTick();
					}
				}
			}
			catch (Exception ex)
			{
				Relay.Logger.LogDebug((object)("Relay tick skipped: " + ex.Message));
			}
		}

		internal static void Teardown()
		{
			Unsubscribe();
			_applied.Clear();
			_desired.Clear();
			_lastSent[0] = (_lastSent[1] = (_lastSent[2] = (_lastSent[3] = -1)));
			_ticksSinceSend = 0;
			_lastAck = -999f;
		}

		private static void Subscribe()
		{
			if (_subscribed)
			{
				return;
			}
			try
			{
				if (PhotonNetwork.NetworkingClient != null)
				{
					PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
					_subscribed = true;
				}
			}
			catch
			{
			}
		}

		private static void Unsubscribe()
		{
			if (!_subscribed)
			{
				return;
			}
			try
			{
				if (PhotonNetwork.NetworkingClient != null)
				{
					PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
				}
			}
			catch
			{
			}
			_subscribed = false;
		}

		private static void SenderTick()
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			if (!SemiFunc.RunIsLevel())
			{
				return;
			}
			int[] array = Totals();
			bool flag = false;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] != _lastSent[i])
				{
					flag = true;
				}
			}
			_ticksSinceSend++;
			if (flag || _ticksSinceSend >= 20)
			{
				PhotonNetwork.RaiseEvent((byte)117, (object)new object[5]
				{
					"headclef.Relay",
					array[0],
					array[1],
					array[2],
					array[3]
				}, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)2
				}, SendOptions.SendReliable);
				for (int j = 0; j < array.Length; j++)
				{
					_lastSent[j] = array[j];
				}
				_ticksSinceSend = 0;
				if (flag)
				{
					Relay.Logger.LogDebug((object)$"Relay sent: Strength {array[0]}, Launch {array[1]}, Throw {array[2]}, Wings {array[3]}.");
				}
			}
		}

		private static void OnEvent(EventData photonEvent)
		{
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Expected O, but got Unknown
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!(photonEvent.CustomData is object[] array) || array.Length < 1 || !(array[0] is string text) || text != "headclef.Relay")
				{
					return;
				}
				if (photonEvent.Code == 118)
				{
					if (PhotonNetwork.MasterClient != null && photonEvent.Sender == PhotonNetwork.MasterClient.ActorNumber)
					{
						_lastAck = Time.unscaledTime;
					}
				}
				else
				{
					if (photonEvent.Code != 117 || !PhotonNetwork.IsMasterClient || array.Length < 5)
					{
						return;
					}
					int[] array2 = new int[StatNames.Length];
					for (int i = 0; i < array2.Length; i++)
					{
						if (!(array[i + 1] is int num))
						{
							return;
						}
						array2[i] = Mathf.Max(0, num);
					}
					_desired[photonEvent.Sender] = array2;
					object[] obj = new object[1] { "headclef.Relay" };
					RaiseEventOptions val = new RaiseEventOptions();
					val.TargetActors = new int[1] { photonEvent.Sender };
					PhotonNetwork.RaiseEvent((byte)118, (object)obj, val, SendOptions.SendReliable);
				}
			}
			catch (Exception ex)
			{
				Relay.Logger.LogDebug((object)("Relay event ignored: " + ex.Message));
			}
		}

		private static void ReceiverTick()
		{
			if (!SemiFunc.RunIsLevel() || _desired.Count == 0 || (Object)(object)GameDirector.instance == (Object)null)
			{
				return;
			}
			foreach (KeyValuePair<int, int[]> item in _desired)
			{
				PlayerAvatar val = AvatarFromActor(item.Key);
				if (!((Object)(object)val == (Object)null))
				{
					for (int i = 0; i < StatNames.Length; i++)
					{
						ReconcileStat(item.Key, val, i, item.Value[i]);
						SetOverlay(val, i, item.Value[i]);
					}
				}
			}
		}

		private static PlayerAvatar? AvatarFromActor(int actorNumber)
		{
			foreach (PlayerAvatar item in SemiFunc.PlayerGetList())
			{
				if (!((Object)(object)item == (Object)null) && !item.isLocal && !((Object)(object)item.photonView == (Object)null) && item.photonView.OwnerActorNr == actorNumber)
				{
					return item;
				}
			}
			return null;
		}

		private static void ReconcileStat(int actor, PlayerAvatar avatar, int stat, int desired)
		{
			Object val = StatComponent(avatar, stat);
			if (val == (Object)null)
			{
				return;
			}
			float num = StatGet(avatar, stat);
			(int, int) key = (actor, stat);
			if (!_applied.TryGetValue(key, out AppliedStat value) || value.component != val)
			{
				if (desired == 0)
				{
					return;
				}
				value = new AppliedStat
				{
					component = val,
					baseValue = num,
					lastWritten = num
				};
				_applied[key] = value;
			}
			else if (num > value.lastWritten + 0.001f)
			{
				value.baseValue += num - value.lastWritten;
			}
			else if (num < value.lastWritten - 0.001f)
			{
				value.baseValue = num;
			}
			float num2 = value.baseValue + PerLevel[stat] * (float)desired;
			if (Mathf.Abs(num2 - num) > 0.001f)
			{
				StatSet(avatar, stat, num2);
			}
			value.lastWritten = StatGet(avatar, stat);
		}

		private static Object? StatComponent(PlayerAvatar avatar, int stat)
		{
			switch (stat)
			{
			case 0:
			case 2:
				return (Object?)(object)avatar.physGrabber;
			case 1:
				return (Object?)(object)avatar.tumble;
			case 3:
				return (Object?)(object)avatar;
			default:
				return null;
			}
		}

		private static float StatGet(PlayerAvatar avatar, int stat)
		{
			return stat switch
			{
				0 => avatar.physGrabber.grabStrength, 
				1 => avatar.tumble.tumbleLaunch, 
				2 => avatar.physGrabber.throwStrength, 
				3 => avatar.upgradeTumbleWings, 
				_ => 0f, 
			};
		}

		private static void StatSet(PlayerAvatar avatar, int stat, float value)
		{
			switch (stat)
			{
			case 0:
				avatar.physGrabber.grabStrength = value;
				break;
			case 1:
				avatar.tumble.tumbleLaunch = Mathf.RoundToInt(value);
				break;
			case 2:
				avatar.physGrabber.throwStrength = value;
				break;
			case 3:
				avatar.upgradeTumbleWings = value;
				break;
			}
		}

		private static void SetOverlay(PlayerAvatar avatar, int stat, int levels)
		{
			if (!CsPresent)
			{
				return;
			}
			try
			{
				string text = SemiFunc.PlayerGetSteamID(avatar);
				if (!string.IsNullOrEmpty(text))
				{
					SetOverlayImpl(text, CsKeys[stat], levels);
				}
			}
			catch (Exception ex)
			{
				_csPresent = false;
				Relay.Logger.LogDebug((object)("Relay overlay skipped: " + ex.Message));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void SetOverlayImpl(string steamId, string key, int amount)
		{
			if (amount > 0)
			{
				Character_Stats.SetTemporaryBonus(steamId, key, amount);
			}
			else
			{
				Character_Stats.ClearTemporaryBonus(steamId, key);
			}
		}
	}
}
namespace Relay.Patches
{
	[HarmonyPatch]
	internal static class RelayPatch
	{
		private static Coroutine? _tick;

		[HarmonyPatch(typeof(StatsManager), "PlayerAdd")]
		[HarmonyPostfix]
		private static void PlayerAdd_Postfix()
		{
			try
			{
				if (SemiFunc.RunIsLevel() && !((Object)(object)PlayerAvatar.instance == (Object)null))
				{
					StartTick();
				}
			}
			catch (Exception ex)
			{
				Relay.Logger.LogDebug((object)("Relay start skipped: " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(SemiFunc), "OnSceneSwitch")]
		[HarmonyPrefix]
		private static void OnSceneSwitch_Prefix()
		{
			StopTick();
		}

		[HarmonyPatch(typeof(RunManager), "ResetProgress")]
		[HarmonyPostfix]
		private static void ResetProgress_Postfix()
		{
			StopTick();
		}

		private static void StartTick()
		{
			if (_tick == null)
			{
				_tick = ((MonoBehaviour)Relay.Instance).StartCoroutine(TickLoop());
			}
		}

		private static void StopTick()
		{
			RelayBridge.Teardown();
			if (_tick != null)
			{
				((MonoBehaviour)Relay.Instance).StopCoroutine(_tick);
				_tick = null;
			}
		}

		private static IEnumerator TickLoop()
		{
			while (SemiFunc.RunIsLevel())
			{
				RelayBridge.Tick();
				yield return (object)new WaitForSeconds(0.5f);
			}
			RelayBridge.Teardown();
			_tick = null;
		}
	}
}