Decompiled source of LatejoinMapSyncFix v1.1.0

MapSyncFix.dll

Decompiled 9 hours 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
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(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.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 MapSyncFix
{
	internal static class EnemySync
	{
		[HarmonyPatch(typeof(PlayerAvatar), "Start")]
		internal static class RemoteAvatarPatch
		{
			private static void Postfix(PlayerAvatar __instance)
			{
				try
				{
					if (Enabled && PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.photonView == (Object)null) && !__instance.photonView.IsMine && __instance.photonView.Owner != null && !((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated)
					{
						GameDirector instance = GameDirector.instance;
						if ((Object)(object)instance != (Object)null)
						{
							((MonoBehaviour)instance).StartCoroutine(HostSend(__instance.photonView.Owner.ActorNumber));
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join enemy sync (host)", e);
				}
			}
		}

		internal static bool Enabled;

		private static FieldRef<EnemyParent, bool> _spawned;

		private static FieldRef<EnemyParent, bool> SpawnedRef => _spawned ?? (_spawned = AccessTools.FieldRefAccess<EnemyParent, bool>("Spawned"));

		private static IEnumerator HostSend(int actor)
		{
			float[] array = new float[2] { 4f, 6f };
			foreach (float delay in array)
			{
				yield return (object)new WaitForSeconds(delay);
				if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || !SemiFunc.RunIsLevel())
				{
					break;
				}
				Player player = PhotonNetwork.CurrentRoom.GetPlayer(actor, false);
				if (player == null || (Object)(object)EnemyDirector.instance == (Object)null)
				{
					break;
				}
				int num = 0;
				int num2 = 0;
				foreach (EnemyParent item in EnemyDirector.instance.enemiesSpawned)
				{
					try
					{
						if (!((Object)(object)item == (Object)null) && !((Object)(object)((MonoBehaviourPun)item).photonView == (Object)null))
						{
							num2++;
							if (!SpawnedRef.Invoke(item))
							{
								((MonoBehaviourPun)item).photonView.RPC("DespawnRPC", player, Array.Empty<object>());
								num++;
							}
						}
					}
					catch (Exception e)
					{
						Suppressed.Report("Late-join enemy sync (send)", e);
					}
				}
				if (delay == 4f)
				{
					Plugin.Log.LogInfo((object)$"[Late-join enemy sync] {num2} enemies in the level; told a player who joined mid-level that {num} of them are resting (otherwise they'd see them frozen in place).");
				}
			}
		}
	}
	internal static class LevelRepair
	{
		[HarmonyPatch(typeof(Module), "ModuleConnectionSetRPC")]
		internal static class SetupOncePatch
		{
			private static bool Prefix(Module __instance)
			{
				try
				{
					return (Object)(object)__instance == (Object)null || !SetupDone.Invoke(__instance);
				}
				catch
				{
					return true;
				}
			}
		}

		private const float Spacing = 15f;

		private const float Tolerance = 2f;

		private static readonly FieldRef<Module, bool> Top = AccessTools.FieldRefAccess<Module, bool>("ConnectingTop");

		private static readonly FieldRef<Module, bool> Right = AccessTools.FieldRefAccess<Module, bool>("ConnectingRight");

		private static readonly FieldRef<Module, bool> Bottom = AccessTools.FieldRefAccess<Module, bool>("ConnectingBottom");

		private static readonly FieldRef<Module, bool> Left = AccessTools.FieldRefAccess<Module, bool>("ConnectingLeft");

		private static readonly FieldRef<Module, bool> First = AccessTools.FieldRefAccess<Module, bool>("First");

		private static readonly FieldRef<Module, bool> SetupDone = AccessTools.FieldRefAccess<Module, bool>("SetupDone");

		private static readonly FieldRef<ModulePropSwitch, Module> SwitchModule = AccessTools.FieldRefAccess<ModulePropSwitch, Module>("Module");

		internal static IEnumerator Routine()
		{
			yield return (object)new WaitForSeconds(6f);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			try
			{
				List<Module> list = new List<Module>();
				List<Vector3> list2 = new List<Vector3>();
				Module[] array = Object.FindObjectsOfType<Module>(true);
				foreach (Module val in array)
				{
					if (!((Object)(object)val == (Object)null))
					{
						if ((Object)(object)((Component)val).GetComponent<StartRoom>() != (Object)null)
						{
							list2.Add(((Component)val).transform.position);
						}
						else
						{
							list.Add(val);
						}
					}
				}
				foreach (Module item in list)
				{
					num++;
					if (SetupDone.Invoke(item))
					{
						num2++;
						continue;
					}
					Vector3 position = ((Component)item).transform.position;
					bool flag = Mathf.Abs(position.x) < 2f && Mathf.Abs(position.z - 7.5f) < 2f;
					foreach (Vector3 item2 in list2)
					{
						if (Mathf.Abs(item2.x - position.x) < 2f && item2.z < position.z && position.z - item2.z < 23f)
						{
							flag = true;
						}
					}
					Top.Invoke(item) = HasNeighbour(list, item, position + new Vector3(0f, 0f, 15f));
					Bottom.Invoke(item) = HasNeighbour(list, item, position - new Vector3(0f, 0f, 15f)) || flag;
					Right.Invoke(item) = HasNeighbour(list, item, position + new Vector3(15f, 0f, 0f));
					Left.Invoke(item) = HasNeighbour(list, item, position - new Vector3(15f, 0f, 0f));
					First.Invoke(item) = flag;
					SetupDone.Invoke(item) = true;
					ModulePropSwitch[] componentsInChildren = ((Component)item).GetComponentsInChildren<ModulePropSwitch>(true);
					foreach (ModulePropSwitch val2 in componentsInChildren)
					{
						SwitchModule.Invoke(val2) = item;
						val2.Setup();
						num4++;
					}
					num3++;
				}
			}
			catch (Exception e)
			{
				Suppressed.Report("Late-join level repair", e);
			}
			Plugin.Log.LogInfo((object)((num3 > 0) ? $"[Late-join level repair] Set up {num3} of {num} level pieces the host's setup never reached ({num4} wall/floor switches); {num2} were already set up." : $"[Late-join level repair] All {num} level pieces were already set up; nothing to repair."));
		}

		private static bool HasNeighbour(List<Module> all, Module self, Vector3 at)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			foreach (Module item in all)
			{
				if (!((Object)(object)item == (Object)(object)self))
				{
					Vector3 position = ((Component)item).transform.position;
					if (Mathf.Abs(position.x - at.x) < 2f && Mathf.Abs(position.z - at.z) < 2f)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	internal static class MapSync
	{
		[HarmonyPatch(typeof(NetworkConnect), "OnJoinedRoom")]
		internal static class JoinedRoomPatch
		{
			private static void Postfix()
			{
				_joinedRoomAt = Time.realtimeSinceStartup;
			}
		}

		[HarmonyPatch(typeof(RunManager), "UpdateLevel")]
		internal static class JoinDetectPatch
		{
			private static void Postfix()
			{
				try
				{
					if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient)
					{
						Room currentRoom = PhotonNetwork.CurrentRoom;
						string obj = ((currentRoom != null) ? currentRoom.Name : null);
						bool flag = obj != _lastRoomName;
						_lastRoomName = obj;
						if (flag && SemiFunc.RunIsLevel() && Time.realtimeSinceStartup - _joinedRoomAt < 10f)
						{
							_lateJoinPending = true;
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (join detect)", e);
				}
			}
		}

		[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
		internal static class LevelDonePatch
		{
			private static void Postfix(LevelGenerator __instance)
			{
				try
				{
					if (!PhotonNetwork.InRoom || !SemiFunc.IsMultiplayer() || (Object)(object)__instance == (Object)(object)_lastLevel)
					{
						return;
					}
					_lastLevel = __instance;
					if (!_subscribed)
					{
						PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
						_subscribed = true;
					}
					_receivedThisLevel = false;
					Pending.Clear();
					if (_lateJoinPending && !PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel())
					{
						_lateJoinPending = false;
						Plugin.Log.LogInfo((object)("[Late-join map sync] Joined mid-level" + (MapSyncEnabled ? "; waiting for the host's explored rooms." : ".")));
						if (MapSyncEnabled)
						{
							((MonoBehaviour)__instance).StartCoroutine(JoinerRoutine());
						}
						if (LevelRepairEnabled)
						{
							((MonoBehaviour)__instance).StartCoroutine(LevelRepair.Routine());
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (level done)", e);
				}
			}
		}

		[HarmonyPatch(typeof(PlayerAvatar), "Start")]
		internal static class RemoteAvatarPatch
		{
			private static void Postfix(PlayerAvatar __instance)
			{
				try
				{
					if (MapSyncEnabled && PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.photonView == (Object)null) && !__instance.photonView.IsMine && __instance.photonView.Owner != null && !((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated)
					{
						GameDirector instance = GameDirector.instance;
						if ((Object)(object)instance != (Object)null)
						{
							((MonoBehaviour)instance).StartCoroutine(HostSend(__instance.photonView.Owner.ActorNumber));
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (host)", e);
				}
			}
		}

		internal static bool RevealAllFallback;

		internal static bool MapSyncEnabled = true;

		internal static bool LevelRepairEnabled = true;

		private const byte EventCode = 163;

		private const string Tag = "RW.MapSync";

		private const float MatchDistance = 0.25f;

		private static readonly FieldRef<RoomVolume, bool> Explored = AccessTools.FieldRefAccess<RoomVolume, bool>("Explored");

		private static bool _subscribed;

		private static string _lastRoomName;

		private static LevelGenerator _lastLevel;

		private static bool _lateJoinPending;

		private static float _joinedRoomAt = -1000f;

		private static bool _receivedThisLevel;

		private static readonly List<Vector3> Pending = new List<Vector3>();

		private static IEnumerator HostSend(int actor)
		{
			float[] array = new float[2] { 4f, 8f };
			foreach (float delay in array)
			{
				yield return (object)new WaitForSeconds(delay);
				if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom.GetPlayer(actor, false) == null)
				{
					break;
				}
				try
				{
					List<float> list = new List<float>();
					RoomVolume[] array2 = Object.FindObjectsOfType<RoomVolume>(true);
					foreach (RoomVolume val in array2)
					{
						if ((Object)(object)val != (Object)null && Explored.Invoke(val))
						{
							Vector3 position = ((Component)val).transform.position;
							list.Add(position.x);
							list.Add(position.y);
							list.Add(position.z);
						}
					}
					object[] obj = new object[2]
					{
						"RW.MapSync",
						list.ToArray()
					};
					RaiseEventOptions val2 = new RaiseEventOptions();
					val2.TargetActors = new int[1] { actor };
					PhotonNetwork.RaiseEvent((byte)163, (object)obj, val2, SendOptions.SendReliable);
					if (delay == 4f)
					{
						Plugin.Log.LogInfo((object)$"[Late-join map sync] Sent {list.Count / 3} explored rooms to a player who joined mid-level.");
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (send)", e);
				}
			}
		}

		private static void OnEvent(EventData ev)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ev.Code != 163 || PhotonNetwork.IsMasterClient)
				{
					return;
				}
				Player masterClient = PhotonNetwork.MasterClient;
				if (masterClient != null && ev.Sender == masterClient.ActorNumber && ev.CustomData is object[] array && array.Length == 2 && array[0] is string text && !(text != "RW.MapSync") && array[1] is float[] array2)
				{
					_receivedThisLevel = true;
					for (int i = 0; i + 2 < array2.Length; i += 3)
					{
						Pending.Add(new Vector3(array2[i], array2[i + 1], array2[i + 2]));
					}
				}
			}
			catch (Exception e)
			{
				Suppressed.Report("Late-join map sync (receive)", e);
			}
		}

		private static IEnumerator JoinerRoutine()
		{
			float waited = 0f;
			while (((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) && waited < 60f)
			{
				yield return (object)new WaitForSeconds(0.5f);
				waited += 0.5f;
			}
			yield return (object)new WaitForSeconds(3f);
			int uncovered = 0;
			for (float t = 0f; t < 30f; t += 1f)
			{
				uncovered += ApplyPending();
				if (!_receivedThisLevel && t >= 15f)
				{
					break;
				}
				yield return (object)new WaitForSeconds(1f);
			}
			if (_receivedThisLevel)
			{
				Plugin.Log.LogInfo((object)$"[Late-join map sync] Joined mid-level; the host's map was synced ({uncovered} rooms uncovered).");
				yield break;
			}
			if (!RevealAllFallback)
			{
				Plugin.Log.LogInfo((object)"[Late-join map sync] Joined mid-level, but the host doesn't run Map Sync Fix, so its explored rooms can't be synced (RevealAllRoomsWhenHostHasNoMod is off).");
				yield break;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true);
			foreach (RoomVolume val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					num++;
					if (Explored.Invoke(val))
					{
						num2++;
					}
					else if ((Object)(object)val.MapModule == (Object)null)
					{
						num3++;
					}
					else if (val.SetExplored())
					{
						num4++;
					}
				}
			}
			Plugin.Log.LogInfo((object)$"[Late-join map sync] Joined mid-level and the host doesn't run Map Sync Fix; uncovered the rest of the map (RevealAllRoomsWhenHostHasNoMod): {num4} rooms uncovered now, {num2} already uncovered, {num3} with no map cover (truck/extraction/start), {num} room volumes in total.");
		}

		private static int ApplyPending()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if (Pending.Count == 0)
			{
				return 0;
			}
			int num = 0;
			RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true);
			for (int num2 = Pending.Count - 1; num2 >= 0; num2--)
			{
				bool flag = false;
				bool flag2 = true;
				RoomVolume[] array2 = array;
				foreach (RoomVolume val in array2)
				{
					if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, Pending[num2]) > 0.25f)
					{
						continue;
					}
					flag = true;
					if (!Explored.Invoke(val))
					{
						if ((Object)(object)val.MapModule == (Object)null)
						{
							flag2 = false;
						}
						else if (val.SetExplored())
						{
							num++;
						}
					}
				}
				if (flag && flag2)
				{
					Pending.RemoveAt(num2);
				}
			}
			return num;
		}
	}
	[BepInPlugin("ricketywrecked.repo.mapsyncfix", "Map Sync Fix", "1.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "ricketywrecked.repo.mapsyncfix";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MapSync", true, "Players who join mid-level see a fully covered map, because each machine only uncovers rooms it has watched players walk into. As host this sends them the rooms explored so far; as a joiner it uncovers the rooms the host sends. Both sides need this mod (players without it are unaffected). Restart the game after changing.");
			ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RevealAllRoomsWhenHostHasNoMod", false, "When you join mid-level and the host doesn't run this mod (so it can't send its explored rooms), uncover every room on your map instead. This also shows rooms nobody has explored yet. Restart the game after changing.");
			ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LevelRepair", true, "When you join mid-level, the host's setup of each level piece (which sides connect, and so which floors, walls and pit covers exist) may never reach you, leaving invisible spots you can fall through and phantom walls. This sets those pieces up on your side the same way the host did. Only needs this mod on your side. Restart the game after changing.");
			ConfigEntry<bool> val4 = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnemySync", true, "Host: players who join mid-level saw every enemy that was resting off the map standing frozen where it was created, because the game only announces enemies leaving and returning to the players already there. This tells each mid-level joiner which enemies are resting. Works for joiners without any mods. Restart the game after changing.");
			MapSync.MapSyncEnabled = val.Value;
			EnemySync.Enabled = val4.Value;
			MapSync.RevealAllFallback = val2.Value;
			MapSync.LevelRepairEnabled = val3.Value;
			Harmony val5 = new Harmony("ricketywrecked.repo.mapsyncfix");
			int num = 0;
			int num2 = 0;
			List<Type> list = new List<Type>();
			if (val.Value || val3.Value)
			{
				list.Add(typeof(MapSync.JoinedRoomPatch));
				list.Add(typeof(MapSync.JoinDetectPatch));
				list.Add(typeof(MapSync.LevelDonePatch));
			}
			if (val.Value)
			{
				list.Add(typeof(MapSync.RemoteAvatarPatch));
			}
			if (val3.Value)
			{
				list.Add(typeof(LevelRepair.SetupOncePatch));
			}
			if (val4.Value)
			{
				list.Add(typeof(EnemySync.RemoteAvatarPatch));
			}
			foreach (Type item in list)
			{
				try
				{
					val5.CreateClassProcessor(item).Patch();
					num++;
				}
				catch (Exception ex)
				{
					num2++;
					Log.LogError((object)("Could not apply " + item.Name + ": " + ex.Message));
				}
			}
			Log.LogInfo((object)string.Format("Map Sync Fix loaded: {0} patches applied, {1} failed (map sync {2}, reveal-all fallback {3}, level repair {4}, enemy sync {5}).", num, num2, val.Value ? "on" : "off", val2.Value ? "on" : "off", val3.Value ? "on" : "off", val4.Value ? "on" : "off"));
		}
	}
	internal static class Suppressed
	{
		private static readonly Dictionary<string, int> Counts = new Dictionary<string, int>();

		internal static void Report(string site, Exception e)
		{
			int value;
			lock (Counts)
			{
				Counts.TryGetValue(site, out value);
				value = (Counts[site] = value + 1);
			}
			if (value == 1 || value % 1000 == 0)
			{
				Plugin.Log.LogWarning((object)$"Suppressed exception in {site} ({value} so far): {e.GetType().Name}: {e.Message}");
			}
		}
	}
}