Decompiled source of LateJoin v1.0.0

BepInEx/plugins/RepoLateJoin.dll

Decompiled 3 hours 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.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using RepoLateJoin.Client;
using RepoLateJoin.Core;
using RepoLateJoin.Join;
using RepoLateJoin.Lobby;
using RepoLateJoin.Menu;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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("RepoLateJoin")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Unified late-join mod for R.E.P.O.")]
[assembly: AssemblyFileVersion("1.5.3.0")]
[assembly: AssemblyInformationalVersion("1.5.3")]
[assembly: AssemblyProduct("RepoLateJoin")]
[assembly: AssemblyTitle("RepoLateJoin")]
[assembly: AssemblyVersion("1.5.3.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 RepoLateJoin
{
	[BepInPlugin("repolatejoin.unified", "RepoLateJoin", "1.5.3")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class Runner : MonoBehaviour
		{
			private void Update()
			{
				if (PunGate.Ready)
				{
					LobbyGate.Tick();
					LoadingWatchdog.Tick();
					ModuleConnectionSync.TickClientRepair();
				}
			}

			private void OnGUI()
			{
				StatusOverlay.Draw();
			}
		}

		internal const string Guid = "repolatejoin.unified";

		private static readonly Dictionary<string, string> Superseded = new Dictionary<string, string>
		{
			{ "ozoka.latejoin", "LateJoin (Ozoka Gaming)" },
			{ "chaos.holz.laterepo", "LateRepo (Chaosholz)" },
			{ "laterepo.fix.runtime", "LateRepo Fix (AST Studios)" },
			{ "jp.laterepo.introfix", "LateRepo Intro Fix (CapackMods)" },
			{ "knightmike.latejoin", "Late Join (knightmike)" },
			{ "zichen.latejoinnow", "LateJoinNow (Zichen)" }
		};

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0021: 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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			Log.Attach(((BaseUnityPlugin)this).Logger);
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			WarnAboutSupersededMods();
			GameObject val = new GameObject("RepoLateJoin")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			JoinQueue.Attach((MonoBehaviour)(object)val.AddComponent<Runner>());
			_harmony = new Harmony("repolatejoin.unified");
			int num = ApplyPatches();
			ClientGuards.Install(_harmony);
			Log.Info("RepoLateJoin ready: " + num + " patch group(s) active. Late join is " + (ModConfig.On ? "on" : "off") + ".");
		}

		private int ApplyPatches()
		{
			Type[] array;
			try
			{
				array = (from t in typeof(Plugin).Assembly.GetTypes()
					where t.Namespace == "RepoLateJoin.Patches" && t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0
					select t).ToArray();
			}
			catch (Exception ex)
			{
				Log.Error("Could not enumerate patch classes: " + ex);
				return 0;
			}
			int num = 0;
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				try
				{
					_harmony.CreateClassProcessor(type).Patch();
					num++;
					Log.Verbose("Applied patch group " + type.Name + ".");
				}
				catch (Exception ex2)
				{
					Log.Warn("Patch group " + type.Name + " could not be applied (" + ex2.GetType().Name + ": " + ex2.Message + "). That feature is disabled; everything else still works.");
				}
			}
			return num;
		}

		private void WarnAboutSupersededMods()
		{
			List<string> list = new List<string>();
			try
			{
				foreach (KeyValuePair<string, string> item in Superseded)
				{
					if (Chainloader.PluginInfos.ContainsKey(item.Key))
					{
						list.Add(item.Value);
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not inspect the loaded plugin list: " + ex.GetType().Name);
				return;
			}
			if (list.Count != 0)
			{
				Log.Warn("RepoLateJoin already contains the features of: " + string.Join(", ", list) + ". Remove " + ((list.Count == 1) ? "it" : "them") + " - running both leaves two mods fighting over the same lobby state.");
			}
		}
	}
}
namespace RepoLateJoin.Patches
{
	[HarmonyPatch(typeof(PlayerDeathHead), "UpdateParticleColors")]
	internal static class DeathHeadColorPatch
	{
		private static Exception Finalizer(Exception __exception)
		{
			if (__exception == null)
			{
				return null;
			}
			if (!ModConfig.On)
			{
				return __exception;
			}
			Log.Once("deathheadcolors", "Suppressed a death head colour update that ran before the head finished arriving over the network (" + __exception.GetType().Name + "). Particle colours stay default until the next cosmetics change.");
			return null;
		}
	}
	[HarmonyPatch]
	internal static class EnemyOnScreenLogicPatch
	{
		private static FieldInfo _thisField;

		private static MethodBase TargetMethod()
		{
			Type type = EnemyAwareness.FindIteratorType(typeof(EnemyOnScreen), "Logic");
			if (type == null)
			{
				return null;
			}
			_thisField = Reflect.Field(type, "<>4__this");
			return Reflect.Method(type, "MoveNext");
		}

		private static void Prefix(object __instance)
		{
			EnemyAwareness.EnsureFor(EnemyAwareness.OwnerOf<EnemyOnScreen>(__instance, _thisField));
		}
	}
	[HarmonyPatch]
	internal static class EnemyVisionLogicPatch
	{
		private static FieldInfo _thisField;

		private static MethodBase TargetMethod()
		{
			Type type = EnemyAwareness.FindIteratorType(typeof(EnemyVision), "Vision");
			if (type == null)
			{
				return null;
			}
			_thisField = Reflect.Field(type, "<>4__this");
			return Reflect.Method(type, "MoveNext");
		}

		private static void Prefix(object __instance)
		{
			EnemyAwareness.EnsureFor(EnemyAwareness.OwnerOf<EnemyVision>(__instance, _thisField));
		}
	}
	[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
	internal static class RunManagerChangeLevelPatch
	{
		private static readonly FieldInfo RestartingField = Reflect.Field(typeof(RunManager), "restarting");

		private static void Prefix()
		{
			if (!PunGate.Ready)
			{
				return;
			}
			try
			{
				if ((!SemiFunc.MenuLevel() && !SemiFunc.IsMasterClientOrSingleplayer()) || ((Object)(object)RunManager.instance != (Object)null && Reflect.Get(RestartingField, RunManager.instance, fallback: false)))
				{
					return;
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not evaluate the change-level guard: " + ex.GetType().Name);
				return;
			}
			LobbyGate.BeginTransition();
			PhotonCacheCleaner.ClearSceneCache();
			PhotonCacheCleaner.ClearStaleLevelUpdateRpc();
			ModuleConnectionSync.Clear();
			LevelStateSync.Clear();
			JoinQueue.Reset();
			ClientGuards.Disarm();
			LoadingWatchdog.Reset();
		}
	}
	[HarmonyPatch(typeof(LevelGenerator), "Start")]
	internal static class LevelGeneratorStartPatch
	{
		private static void Prefix(LevelGenerator __instance)
		{
			PunGate.Mark();
			if (ModConfig.On)
			{
				PhotonCacheCleaner.ClearStaleGenerationRpcs(__instance);
			}
		}

		private static Exception Finalizer(Exception __exception)
		{
			if (__exception == null)
			{
				return null;
			}
			if (!IsSingleplayerPoolMiss(__exception))
			{
				return __exception;
			}
			Log.Once("sppool", "Suppressed an outdated RunManager.singleplayerPool access during level generation.");
			return null;
		}

		private static bool IsSingleplayerPoolMiss(Exception e)
		{
			while (e != null)
			{
				if (e is MissingFieldException && e.ToString().IndexOf("singleplayerPool", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
				e = e.InnerException;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(LoadingUI), "LevelAnimationStart")]
	internal static class LoadingUiAnimationStartPatch
	{
		private static void Prefix()
		{
			LoadingWatchdog.EnsureLevelReference();
		}
	}
	[HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")]
	internal static class MenuPageLobbyButtonStartPatch
	{
		private static void Postfix()
		{
			if (ModConfig.On)
			{
				LobbyGate.ResetAll();
				Log.Verbose("Lobby start pressed; join gating state reset.");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "Spawn")]
	internal static class PlayerAvatarSpawnPatch
	{
		private static readonly FieldInfo SpawnedField = Reflect.Field(typeof(PlayerAvatar), "spawned");

		private static bool Prefix(PlayerAvatar __instance)
		{
			if (!ModConfig.On || (Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if (!Reflect.Get(SpawnedField, __instance, fallback: false))
			{
				return true;
			}
			Log.Once("dupespawn", "Blocked a re-spawn of an already-spawned player so late joins do not teleport the party.");
			return false;
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "Start")]
	internal static class PlayerAvatarStartPatch
	{
		private static void Postfix(PlayerAvatar __instance)
		{
			if (ModConfig.On && !((Object)(object)__instance == (Object)null))
			{
				EnemyAwareness.RegisterAllPlayers();
				if (PunGate.Ready && !((Object)(object)__instance.photonView == (Object)null) && __instance.photonView.IsMine && !PhotonNetwork.IsMasterClient)
				{
					ClientGuards.EvaluateLocalState(__instance);
					JoinQueue.ReleaseOwnLoadingScreen(__instance);
				}
			}
		}
	}
	[HarmonyPatch(typeof(SteamManager), "LockLobby")]
	internal static class SteamManagerLockLobbyPatch
	{
		private static bool Prefix()
		{
			if (!PunGate.Ready)
			{
				return true;
			}
			if (LobbyGate.SelfLocking)
			{
				return true;
			}
			if (!LobbyGate.JoinAllowedNow())
			{
				return true;
			}
			Log.Once("keepopen", "Kept the lobby unlocked because late joining is currently allowed.");
			LobbyGate.Apply("lock suppressed", force: true);
			return false;
		}
	}
	[HarmonyPatch(typeof(NetworkManager), "OnEnable")]
	internal static class NetworkManagerOnEnablePatch
	{
		private static void Postfix()
		{
			PunGate.Mark();
			if (ModConfig.On)
			{
				ModuleConnectionSync.StartListening();
			}
		}
	}
	[HarmonyPatch(typeof(NetworkManager), "OnDisable")]
	internal static class NetworkManagerOnDisablePatch
	{
		private static void Postfix()
		{
			ModuleConnectionSync.StopListening();
		}
	}
	[HarmonyPatch(typeof(NetworkManager), "OnPlayerEnteredRoom")]
	internal static class NetworkManagerPlayerEnteredPatch
	{
		private static void Postfix(Player newPlayer)
		{
			if (PunGate.Ready)
			{
				JoinQueue.OnPlayerEntered(newPlayer);
			}
		}
	}
	[HarmonyPatch(typeof(NetworkManager), "OnPlayerLeftRoom")]
	internal static class NetworkManagerPlayerLeftPatch
	{
		private static void Postfix(Player otherPlayer)
		{
			if (PunGate.Ready)
			{
				JoinQueue.OnPlayerLeft(otherPlayer);
			}
		}
	}
	[HarmonyPatch(typeof(Module), "ModuleConnectionSet")]
	internal static class ModuleConnectionSetPatch
	{
		private static void Postfix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first)
		{
			ModuleConnectionSync.Remember(__instance, _top, _bottom, _right, _left, _first);
		}
	}
	[HarmonyPatch(typeof(EnemyOnScreen), "OnEnable")]
	internal static class EnemyOnScreenEnablePatch
	{
		private static void Prefix(EnemyOnScreen __instance)
		{
			EnemyAwareness.EnsureFor(__instance);
		}
	}
	[HarmonyPatch(typeof(EnemyVision), "OnEnable")]
	internal static class EnemyVisionEnablePatch
	{
		private static void Prefix(EnemyVision __instance)
		{
			EnemyAwareness.EnsureFor(__instance);
		}
	}
	[HarmonyPatch]
	internal static class GrabObjectDestroyPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[2] { "PhysGrabObject", "StaticGrabObject" };
			for (int i = 0; i < array.Length; i++)
			{
				MethodInfo methodInfo = Reflect.Method(Reflect.TypeByName(array[i]), "DestroyPhysGrabObjectRPC");
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Prefix(object __instance)
		{
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			Component val = (Component)((__instance is Component) ? __instance : null);
			if (val != null && !((Object)(object)val == (Object)null))
			{
				PhotonView component = val.GetComponent<PhotonView>();
				LevelStateSync.RememberDestroyed(component);
				if ((Object)(object)component != (Object)null && component.ViewID == component.InstantiationId && PhotonCacheCleaner.ClearOne(component))
				{
					Log.Once("destroycache", "Clearing the Photon cache entry of destroyed objects so late joiners do not receive loot that was already hauled in.");
				}
			}
		}
	}
	[HarmonyPatch(typeof(MenuPageEsc), "Start")]
	internal static class MenuPageEscStartPatch
	{
		private static void Postfix(MenuPageEsc __instance)
		{
			InviteButton.TryAdd(__instance);
		}
	}
	[HarmonyPatch(typeof(GumballValuable), "UpdateHypnosisLinesList")]
	internal static class GumballValuableHypnosisPatch
	{
		private static readonly FieldInfo LinesField = Reflect.Field(typeof(GumballValuable), "hypnosisLines");

		private static readonly FieldInfo InSightField = Reflect.Field(typeof(GumballValuable), "playerInSight");

		private static readonly FieldInfo SeenField = Reflect.Field(typeof(GumballValuable), "previouslySeenList");

		private static void Prefix(GumballValuable __instance)
		{
			if (!ModConfig.On || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			try
			{
				List<PlayerAvatar> list = SemiFunc.PlayerGetList();
				if (list != null)
				{
					GrowFlags(InSightField, __instance, list.Count);
					GrowFlags(SeenField, __instance, list.Count);
					GrowLines(__instance, list);
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not grow the gumball hypnosis lists: " + ex.GetType().Name);
			}
		}

		private static void GrowFlags(FieldInfo field, GumballValuable instance, int target)
		{
			if (field?.GetValue(instance) is List<bool> list)
			{
				while (list.Count < target)
				{
					list.Add(item: false);
				}
			}
		}

		private static void GrowLines(GumballValuable instance, List<PlayerAvatar> players)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (!(LinesField?.GetValue(instance) is List<GameObject> list) || list.Count >= players.Count)
			{
				return;
			}
			GameObject hypnosisLine = instance.hypnosisLine;
			Transform hypnosisLinesParenTransform = instance.HypnosisLinesParenTransform;
			if ((Object)(object)hypnosisLine == (Object)null)
			{
				return;
			}
			int num = 0;
			for (int i = list.Count; i < players.Count; i++)
			{
				PlayerAvatar val = players[i];
				if ((Object)(object)val == (Object)null)
				{
					list.Add(null);
					continue;
				}
				GameObject val2 = Object.Instantiate<GameObject>(hypnosisLine, ((Component)instance).transform.position, Quaternion.identity);
				Transform val3 = null;
				try
				{
					PlayerLocalCamera localCamera = val.localCamera;
					val3 = ((localCamera != null) ? localCamera.GetOverrideTransform() : null);
				}
				catch
				{
				}
				if ((Object)(object)val3 != (Object)null)
				{
					val2.transform.SetParent(val3);
					val2.transform.localPosition = Vector3.zero;
					val2.transform.localRotation = Quaternion.identity;
				}
				EyeLines component = val2.GetComponent<EyeLines>();
				if ((Object)(object)component != (Object)null)
				{
					component.InitializeLine(val);
				}
				if ((Object)(object)hypnosisLinesParenTransform != (Object)null)
				{
					val2.transform.SetParent(hypnosisLinesParenTransform);
				}
				val2.SetActive(false);
				list.Add(val2);
				num++;
			}
			if (num > 0)
			{
				Log.Once("gumball", "Grew a gumball valuable's hypnosis line list by " + num + " so it stops throwing once somebody joins mid-level.");
			}
		}
	}
	[HarmonyPatch(typeof(RunManager), "UpdateLevel")]
	internal static class InventorySlotPatch
	{
		private static void Postfix()
		{
			if (!ModConfig.On)
			{
				return;
			}
			try
			{
				if ((Object)(object)Inventory.instance == (Object)null || SemiFunc.MenuLevel() || SemiFunc.RunIsArena())
				{
					return;
				}
				int num = 0;
				InventorySpot[] array = Object.FindObjectsOfType<InventorySpot>(true);
				foreach (InventorySpot val in array)
				{
					if (!((Object)(object)Inventory.instance.GetSpotByIndex(val.inventorySpotIndex) != (Object)null))
					{
						Inventory.instance.InventorySpotAddAtIndex(val, val.inventorySpotIndex);
						num++;
					}
				}
				if (num > 0)
				{
					Log.Info("Registered " + num + " inventory slot(s) the join skipped.");
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not register inventory slots: " + ex.GetType().Name);
			}
		}
	}
	[HarmonyPatch(typeof(LevelPoint), "Start")]
	internal static class LevelPointLinkPatch
	{
		private static void Postfix(LevelPoint __instance)
		{
			if (ModConfig.On && __instance.ModuleConnect && !((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated)
			{
				((MonoBehaviour)__instance).StartCoroutine(Relink(__instance));
			}
		}

		private static IEnumerator Relink(LevelPoint point)
		{
			yield return null;
			if ((Object)(object)point == (Object)null || (Object)(object)LevelGenerator.Instance == (Object)null)
			{
				yield break;
			}
			Transform transform = ((Component)point).transform;
			foreach (LevelPoint levelPathPoint in LevelGenerator.Instance.LevelPathPoints)
			{
				if ((Object)(object)levelPathPoint == (Object)null || (Object)(object)levelPathPoint == (Object)(object)point || !levelPathPoint.ModuleConnect || point.ConnectedPoints.Contains(levelPathPoint))
				{
					continue;
				}
				Transform transform2 = ((Component)levelPathPoint).transform;
				if (Vector3.Distance(transform.position, transform2.position) < 15f && Vector3.Dot(transform2.forward, transform.forward) <= -0.8f)
				{
					Vector3 forward = transform2.forward;
					Vector3 val = transform.position - transform2.position;
					if (Vector3.Dot(forward, ((Vector3)(ref val)).normalized) > 0.8f)
					{
						point.ConnectedPoints.Add(levelPathPoint);
					}
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class LightManagerActivationPatch
	{
		private static readonly HashSet<string> Reported = new HashSet<string>();

		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = Reflect.TypeByName("LightManager");
			string[] array = new string[2] { "HandleLightActivation", "HandleEmissionActivation" };
			foreach (string name in array)
			{
				MethodInfo methodInfo = Reflect.Method(type, name);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
		{
			if (__exception == null)
			{
				return null;
			}
			if (!ModConfig.On)
			{
				return __exception;
			}
			string text = __originalMethod?.Name ?? "?";
			if (Reported.Add(text))
			{
				Log.Info("Suppressed a failure in LightManager." + text + " (" + __exception.GetType().Name + "). Without this one prop light aborts the whole lighting pass and the rest of the level's decorations stay dark.");
			}
			else
			{
				Log.Verbose("Suppressed another LightManager." + text + " failure.");
			}
			return null;
		}
	}
}
namespace RepoLateJoin.Menu
{
	internal static class InviteButton
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__OpenInviteOverlay;
		}

		private const string CloneName = "RepoLateJoin_InviteButton";

		private static readonly FieldInfo OriginalPositionField = Reflect.Field(typeof(MenuButton), "originalButtonPosition");

		internal static void TryAdd(MenuPageEsc page)
		{
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Expected O, but got Unknown
			if (!PunGate.Ready || !ModConfig.On || (Object)(object)page == (Object)null)
			{
				return;
			}
			if (!PhotonNetwork.InRoom || !LobbyGate.SceneAllowsJoin())
			{
				Log.Verbose("Skipping the invite button: this scene does not accept joins.");
				return;
			}
			try
			{
				MenuButton val = FindQuitButton(page);
				if ((Object)(object)val == (Object)null)
				{
					Log.Once("noanchor", "Could not find the quit button to anchor the invite button to.");
					return;
				}
				Transform parent = ((Component)val).transform.parent;
				if ((Object)(object)parent == (Object)null || (Object)(object)parent.Find("RepoLateJoin_InviteButton") != (Object)null)
				{
					return;
				}
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
				((Object)val2).name = "RepoLateJoin_InviteButton";
				MenuButtonPopUp component = val2.GetComponent<MenuButtonPopUp>();
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
				Relabel(val2, "INVITE");
				Button component2 = val2.GetComponent<Button>();
				if ((Object)(object)component2 != (Object)null)
				{
					((Behaviour)component2).enabled = true;
					((Selectable)component2).interactable = true;
					((UnityEventBase)component2.onClick).RemoveAllListeners();
					ButtonClickedEvent onClick = component2.onClick;
					object obj = <>O.<0>__OpenInviteOverlay;
					if (obj == null)
					{
						UnityAction val3 = OpenInviteOverlay;
						<>O.<0>__OpenInviteOverlay = val3;
						obj = (object)val3;
					}
					((UnityEvent)onClick).AddListener((UnityAction)obj);
				}
				val2.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex());
				((MonoBehaviour)page).StartCoroutine(PlaceNextFrame(val2, ((Component)val).gameObject));
				Log.Once("invite", "Added an INVITE button to the escape menu.");
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not add the invite button: " + ex.GetType().Name);
			}
		}

		private static void OpenInviteOverlay()
		{
			try
			{
				SteamManager instance = SteamManager.instance;
				if (instance != null)
				{
					instance.OpenSteamOverlayToInvite();
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not open the Steam invite overlay: " + ex.GetType().Name);
			}
		}

		private static IEnumerator PlaceNextFrame(GameObject clone, GameObject anchor)
		{
			yield return null;
			if ((Object)(object)clone == (Object)null || (Object)(object)anchor == (Object)null)
			{
				yield break;
			}
			RectTransform component = clone.GetComponent<RectTransform>();
			RectTransform component2 = anchor.GetComponent<RectTransform>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null)
			{
				yield break;
			}
			component.anchorMin = component2.anchorMin;
			component.anchorMax = component2.anchorMax;
			component.pivot = component2.pivot;
			component.sizeDelta = component2.sizeDelta;
			((Transform)component).localScale = ((Transform)component2).localScale;
			((Transform)component).localPosition = FreeSlotBelow(clone, component2);
			Relabel(clone, "INVITE");
			MenuButton component3 = clone.GetComponent<MenuButton>();
			if (!((Object)(object)component3 != (Object)null))
			{
				yield break;
			}
			Reflect.Set(OriginalPositionField, component3, Vector2.op_Implicit(((Transform)component).localPosition));
			try
			{
				component3.ResizeToText();
			}
			catch (Exception ex)
			{
				Log.Verbose("Invite button resize failed: " + ex.GetType().Name);
			}
		}

		private static Vector3 FreeSlotBelow(GameObject clone, RectTransform anchor)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: 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)
			List<float> list = new List<float>();
			foreach (Transform item in ((Transform)anchor).parent)
			{
				Transform val = item;
				if ((Object)(object)((Component)val).gameObject != (Object)(object)clone && ((Component)val).gameObject.activeSelf && (Object)(object)((Component)val).GetComponent<MenuButton>() != (Object)null)
				{
					list.Add(val.localPosition.y);
				}
			}
			list.Sort();
			float num = float.MaxValue;
			for (int i = 1; i < list.Count; i++)
			{
				float num2 = list[i] - list[i - 1];
				if (num2 > 1f && num2 < num)
				{
					num = num2;
				}
			}
			if (num == float.MaxValue)
			{
				num = 32f;
			}
			float num3 = ((list.Count > 0) ? list[0] : ((Transform)anchor).localPosition.y);
			return new Vector3(((Transform)anchor).localPosition.x, num3 - num, ((Transform)anchor).localPosition.z);
		}

		private static void Relabel(GameObject buttonObject, string label)
		{
			if ((Object)(object)buttonObject == (Object)null)
			{
				return;
			}
			MenuButton component = buttonObject.GetComponent<MenuButton>();
			if ((Object)(object)component != (Object)null)
			{
				component.buttonTextString = label;
			}
			TextMeshProUGUI[] componentsInChildren = buttonObject.GetComponentsInChildren<TextMeshProUGUI>(true);
			foreach (TextMeshProUGUI val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					((TMP_Text)val).SetText(label, true);
					((TMP_Text)val).ForceMeshUpdate(false, false);
				}
			}
		}

		private static MenuButton FindQuitButton(MenuPageEsc page)
		{
			MenuButton[] componentsInChildren = ((Component)page).GetComponentsInChildren<MenuButton>(true);
			MenuButton val = null;
			MenuButton[] array = componentsInChildren;
			foreach (MenuButton val2 in array)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					string text = (val2.buttonTextString ?? string.Empty).ToUpperInvariant().Replace(" ", string.Empty);
					if (text == "QUITGAME")
					{
						return val2;
					}
					if ((Object)(object)val == (Object)null && text.Contains("QUIT"))
					{
						val = val2;
					}
				}
			}
			return val;
		}
	}
	internal static class StatusOverlay
	{
		private static GUIStyle _style;

		private static GUIStyle _shadow;

		internal static void Draw()
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.ShowStatus == null || !ModConfig.ShowStatus.Value || !PunGate.PhotonInitialised || !PhotonNetwork.InRoom)
			{
				return;
			}
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (currentRoom == null)
			{
				return;
			}
			string text = currentRoom.Name;
			try
			{
				if (((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).TryGetValue((object)"server_name", out object value) && value is string text2 && !string.IsNullOrEmpty(text2))
				{
					text = text2;
				}
			}
			catch (Exception)
			{
			}
			bool flag = currentRoom.IsOpen && ModConfig.On;
			string text3 = "Room: " + text + "\nLate join: " + (flag ? "OPEN" : "CLOSED");
			if (_style == null)
			{
				_style = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1
				};
				_shadow = new GUIStyle(_style);
				_shadow.normal.textColor = Color.black;
			}
			_style.normal.textColor = (flag ? new Color(0.5f, 1f, 0.5f) : new Color(1f, 0.5f, 0.5f));
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(12f, 10f, 600f, 50f);
			GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text3, _shadow);
			GUI.Label(val, text3, _style);
		}
	}
}
namespace RepoLateJoin.Lobby
{
	internal static class LobbyGate
	{
		private static readonly FieldInfo PrivateLobbyField = Reflect.Field(typeof(SteamManager), "privateLobby");

		private static readonly FieldInfo LobbyTypeField = Reflect.Field(typeof(GameManager), "lobbyType");

		private const float TransitionCooldown = 4f;

		private static float _cooldownUntil;

		private static float _nextTick;

		private static bool? _appliedState;

		internal static bool SelfLocking;

		internal static bool InTransition => Time.unscaledTime < _cooldownUntil;

		internal static bool SceneAllowsJoin()
		{
			try
			{
				if (SemiFunc.RunIsLobbyMenu())
				{
					return true;
				}
				if (SemiFunc.RunIsLobby())
				{
					return ModConfig.JoinInLobby.Value;
				}
				if (SemiFunc.RunIsShop())
				{
					return ModConfig.JoinInShop.Value;
				}
				if (SemiFunc.RunIsLevel())
				{
					return ModConfig.JoinInLevel.Value;
				}
				return false;
			}
			catch (Exception ex)
			{
				Log.Verbose("Scene gate could not be evaluated: " + ex.GetType().Name);
				return false;
			}
		}

		internal static bool ReadyForJoiners()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			try
			{
				if ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated)
				{
					return false;
				}
				if ((Object)(object)GameDirector.instance == (Object)null)
				{
					return false;
				}
				gameState currentState = GameDirector.instance.currentState;
				return (int)currentState == 2 || (int)currentState == 7;
			}
			catch (Exception ex)
			{
				Log.Verbose("Ready gate could not be evaluated: " + ex.GetType().Name);
				return false;
			}
		}

		internal static bool JoinAllowedNow()
		{
			if (PunGate.Ready && PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom && !InTransition && SceneAllowsJoin())
			{
				return ReadyForJoiners();
			}
			return false;
		}

		internal static void BeginTransition()
		{
			_appliedState = null;
			_cooldownUntil = Time.unscaledTime + 4f;
			Apply("level transition started", force: true);
		}

		internal static void Tick()
		{
			if (PunGate.Ready && !(Time.unscaledTime < _nextTick))
			{
				_nextTick = Time.unscaledTime + 1f;
				Apply("periodic refresh");
			}
		}

		internal static void Apply(string reason, bool force = false)
		{
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom)
			{
				return;
			}
			bool flag = JoinAllowedNow();
			if (force || !_appliedState.HasValue || _appliedState.Value != flag)
			{
				_appliedState = flag;
				if (flag)
				{
					OpenRoom(reason);
				}
				else
				{
					CloseRoom(reason);
				}
			}
		}

		private static void OpenRoom(string reason)
		{
			bool flag = !IsPrivateLobby();
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					currentRoom.IsOpen = true;
					currentRoom.IsVisible = flag;
				}
				if ((Object)(object)SteamManager.instance != (Object)null)
				{
					SteamManager.instance.UnlockLobby(flag);
				}
				if (flag && IsMatchmaking() && (Object)(object)GameManager.instance != (Object)null)
				{
					GameManager.instance.SetConnectRandom(true);
				}
				Log.Once("open:" + reason, "Lobby open for late joins (" + reason + "), advertised=" + flag + ".");
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not open the lobby after " + reason + ": " + ex.GetType().Name);
			}
		}

		private static void CloseRoom(string reason)
		{
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					currentRoom.IsOpen = false;
					currentRoom.IsVisible = false;
				}
				if (IsMatchmaking() && (Object)(object)GameManager.instance != (Object)null)
				{
					GameManager.instance.SetConnectRandom(false);
				}
				if ((Object)(object)SteamManager.instance != (Object)null)
				{
					SelfLocking = true;
					try
					{
						SteamManager.instance.LockLobby();
					}
					finally
					{
						SelfLocking = false;
					}
				}
				Log.Once("close:" + reason, "Lobby closed to new joins (" + reason + ").");
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not close the lobby after " + reason + ": " + ex.GetType().Name);
			}
		}

		internal static bool IsPrivateLobby()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			LobbyTypes? val = LobbyType();
			if (val.HasValue)
			{
				return (int)val.Value == 0;
			}
			if ((Object)(object)SteamManager.instance != (Object)null && PrivateLobbyField != null)
			{
				return Reflect.Get(PrivateLobbyField, SteamManager.instance, fallback: true);
			}
			return true;
		}

		internal static bool IsMatchmaking()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			LobbyTypes? val = LobbyType();
			if (val.HasValue)
			{
				return (int)val.Value == 2;
			}
			return false;
		}

		private static LobbyTypes? LobbyType()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)GameManager.instance == (Object)null || LobbyTypeField == null)
			{
				return null;
			}
			try
			{
				object value = LobbyTypeField.GetValue(GameManager.instance);
				if (value is LobbyTypes)
				{
					return (LobbyTypes)value;
				}
			}
			catch
			{
			}
			return null;
		}

		internal static void ResetAll()
		{
			_cooldownUntil = 0f;
			_appliedState = null;
		}
	}
	internal static class PhotonCacheCleaner
	{
		private static readonly FieldInfo RemoveFilterField = Reflect.Field(typeof(PhotonNetwork), "removeFilter");

		private static readonly FieldInfo KeyByteSevenField = Reflect.Field(typeof(PhotonNetwork), "keyByteSeven");

		private static readonly FieldInfo ServerCleanOptionsField = Reflect.Field(typeof(PhotonNetwork), "ServerCleanOptions");

		private static readonly MethodInfo RaiseEventInternalMethod = Reflect.Method(typeof(PhotonNetwork), "RaiseEventInternal");

		internal static bool Available
		{
			get
			{
				if (RemoveFilterField != null && KeyByteSevenField != null && ServerCleanOptionsField != null)
				{
					return RaiseEventInternalMethod != null;
				}
				return false;
			}
		}

		internal static void ClearSceneCache()
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			if (!Available)
			{
				Log.Once("nocache", "Photon cache clearing is unavailable on this Photon build; skipping.");
				return;
			}
			PhotonView[] array;
			try
			{
				array = Object.FindObjectsOfType<PhotonView>(true);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not enumerate PhotonViews: " + ex.GetType().Name);
				return;
			}
			int num = 0;
			int num2 = 0;
			PhotonView[] array2 = array;
			foreach (PhotonView val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (((Scene)(ref scene)).buildIndex == -1)
				{
					num2++;
					continue;
				}
				if (ClearOne(val))
				{
					num++;
				}
				ClearBufferedRpcs(val);
			}
			if (num > 0)
			{
				Log.Once("cachecleared", "Cleared " + num + " buffered instantiation(s) from the finished level, left " + num2 + " persistent object(s) alone.");
			}
			else
			{
				Log.Verbose("No buffered Photon instantiations needed clearing.");
			}
		}

		internal static bool ClearOne(PhotonView view)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)view == (Object)null || !Available)
			{
				return false;
			}
			if (!view.isRuntimeInstantiated || view.InstantiationId < 1)
			{
				return false;
			}
			try
			{
				object? value = RemoveFilterField.GetValue(null);
				Hashtable val = (Hashtable)((value is Hashtable) ? value : null);
				object value2 = KeyByteSevenField.GetValue(null);
				object? value3 = ServerCleanOptionsField.GetValue(null);
				RaiseEventOptions val2 = (RaiseEventOptions)((value3 is RaiseEventOptions) ? value3 : null);
				if (val == null || value2 == null || val2 == null)
				{
					return false;
				}
				val[value2] = view.InstantiationId;
				val2.CachingOption = (EventCaching)6;
				return RaiseInternal(202, val, val2);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not clear Photon cache for view " + view.ViewID + ": " + ex.GetType().Name);
				return false;
			}
		}

		internal static bool RaiseInternal(byte code, object data, RaiseEventOptions options)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (RaiseEventInternalMethod == null)
			{
				return false;
			}
			try
			{
				RaiseEventInternalMethod.Invoke(null, new object[4]
				{
					code,
					data,
					options,
					SendOptions.SendReliable
				});
				return true;
			}
			catch (Exception ex)
			{
				Log.Verbose("Photon internal event " + code + " failed: " + ex.GetType().Name);
				return false;
			}
		}

		private static void ClearBufferedRpcs(PhotonView view)
		{
			if ((Object)(object)view == (Object)null || view.ViewID == 0)
			{
				return;
			}
			try
			{
				PhotonNetwork.RemoveBufferedRPCs(view.ViewID, (string)null, (int[])null);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not clear buffered RPCs for view " + view.ViewID + ": " + ex.GetType().Name);
			}
		}

		internal static void ClearStaleLevelUpdateRpc()
		{
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || (Object)(object)RunManager.instance == (Object)null)
			{
				return;
			}
			try
			{
				object obj = Reflect.Field(typeof(RunManager), "runManagerPUN")?.GetValue(RunManager.instance);
				if (obj != null)
				{
					object? obj2 = Reflect.Field(obj.GetType(), "photonView")?.GetValue(obj);
					PhotonView val = (PhotonView)((obj2 is PhotonView) ? obj2 : null);
					if (val != null && !((Object)(object)val == (Object)null))
					{
						PhotonNetwork.RemoveBufferedRPCs(val.ViewID, "UpdateLevelRPC", (int[])null);
						Log.Verbose("Removed the previous level's buffered UpdateLevelRPC.");
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not remove the stale level update RPC: " + ex.GetType().Name);
			}
		}

		internal static void ClearStaleGenerationRpcs(LevelGenerator generator)
		{
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || (Object)(object)generator == (Object)null)
			{
				return;
			}
			if (generator.Generated)
			{
				Log.Verbose("Generation already reported done; leaving its buffered RPCs alone.");
				return;
			}
			try
			{
				PhotonView photonView = generator.PhotonView;
				if (!((Object)(object)photonView == (Object)null) && photonView.ViewID != 0)
				{
					PhotonNetwork.RemoveBufferedRPCs(photonView.ViewID, (string)null, (int[])null);
					Log.Verbose("Removed the previous level's buffered generation RPCs.");
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not remove stale generation RPCs: " + ex.GetType().Name);
			}
		}
	}
}
namespace RepoLateJoin.Join
{
	internal static class EnemyAwareness
	{
		private static readonly List<int> Scratch = new List<int>();

		private static bool Wanted => ModConfig.On;

		private static List<int> PlayerViewIds()
		{
			Scratch.Clear();
			if (GameDirector.instance?.PlayerList == null)
			{
				return Scratch;
			}
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if (!((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null) && player.photonView.ViewID != 0)
				{
					Scratch.Add(player.photonView.ViewID);
				}
			}
			return Scratch;
		}

		internal static void EnsureFor(EnemyOnScreen onScreen)
		{
			if (!Wanted || (Object)(object)onScreen == (Object)null)
			{
				return;
			}
			try
			{
				List<int> list = PlayerViewIds();
				for (int i = 0; i < list.Count; i++)
				{
					onScreen.PlayerAdded(list[i]);
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("EnemyOnScreen registration failed: " + ex.GetType().Name);
			}
		}

		internal static void EnsureFor(EnemyVision vision)
		{
			if (!Wanted || (Object)(object)vision == (Object)null)
			{
				return;
			}
			try
			{
				List<int> list = PlayerViewIds();
				for (int i = 0; i < list.Count; i++)
				{
					vision.PlayerAdded(list[i]);
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("EnemyVision registration failed: " + ex.GetType().Name);
			}
		}

		internal static void RegisterAllPlayers()
		{
			if (!Wanted)
			{
				return;
			}
			List<int> list = PlayerViewIds();
			if (list.Count == 0)
			{
				return;
			}
			int num = 0;
			try
			{
				EnemyOnScreen[] array = Object.FindObjectsOfType<EnemyOnScreen>(true);
				foreach (EnemyOnScreen val in array)
				{
					if (!((Object)(object)val == (Object)null))
					{
						for (int j = 0; j < list.Count; j++)
						{
							val.PlayerAdded(list[j]);
						}
						num++;
					}
				}
				EnemyVision[] array2 = Object.FindObjectsOfType<EnemyVision>(true);
				foreach (EnemyVision val2 in array2)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						for (int k = 0; k < list.Count; k++)
						{
							val2.PlayerAdded(list[k]);
						}
						num++;
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not sweep enemies for player registration: " + ex.GetType().Name);
				return;
			}
			if (num > 0)
			{
				Log.Once("enemyaware", "Registered " + list.Count + " player(s) with " + num + " enemy tracker(s) so enemies can see late joiners.");
			}
			else
			{
				Log.Verbose("No enemy trackers present yet; nothing to register.");
			}
		}

		internal static Type FindIteratorType(Type owner, string methodName)
		{
			if (owner == null)
			{
				return null;
			}
			string value = "<" + methodName + ">d__";
			try
			{
				Type[] nestedTypes = owner.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
				foreach (Type type in nestedTypes)
				{
					if (type.Name.StartsWith(value, StringComparison.Ordinal))
					{
						return type;
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not enumerate nested types of " + owner.Name + ": " + ex.GetType().Name);
			}
			Log.Once("noiter:" + owner.Name + "." + methodName, "Could not find the " + owner.Name + "." + methodName + " iterator on this game version; enemies may not track late joiners.");
			return null;
		}

		internal static T OwnerOf<T>(object stateMachine, FieldInfo thisField) where T : class
		{
			if (stateMachine == null || thisField == null)
			{
				return null;
			}
			try
			{
				return thisField.GetValue(stateMachine) as T;
			}
			catch
			{
				return null;
			}
		}
	}
	internal static class ExtractionSync
	{
		private static readonly FieldInfo RoundHaulGoalField = Reflect.Field(typeof(RoundDirector), "haulGoal");

		private static readonly FieldInfo PointActiveField = Reflect.Field(typeof(RoundDirector), "extractionPointActive");

		private static readonly FieldInfo PointCurrentField = Reflect.Field(typeof(RoundDirector), "extractionPointCurrent");

		private static readonly FieldInfo PointListField = Reflect.Field(typeof(RoundDirector), "extractionPointList");

		private static readonly FieldInfo PointStateField = Reflect.Field(typeof(ExtractionPoint), "currentState");

		private static readonly FieldInfo PointViewField = Reflect.Field(typeof(ExtractionPoint), "photonView");

		private static readonly FieldInfo PointHaulFetchedField = Reflect.Field(typeof(ExtractionPoint), "haulGoalFetched");

		internal static int SendTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			if ((Object)(object)RoundDirector.instance == (Object)null)
			{
				return 0;
			}
			int num = 0 + SendHaulGoal(target) + SendActivation(target) + SendPointStates(target);
			if (num > 0)
			{
				Log.Once("extractsync", "Sent extraction point state to late joiners.");
			}
			return num;
		}

		private static int SendHaulGoal(Player target)
		{
			int num = Reflect.Get(RoundHaulGoalField, RoundDirector.instance, 0);
			if (num <= 0)
			{
				return 0;
			}
			PhotonView val = ViewOf(RoundDirector.instance);
			if ((Object)(object)val == (Object)null)
			{
				return 0;
			}
			try
			{
				val.RPC("StartRoundRPC", target, new object[1] { num });
				return 1;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not send the haul goal: " + ex.GetType().Name);
				return 0;
			}
		}

		private static int SendActivation(Player target)
		{
			PhotonView val = ViewOf(RoundDirector.instance);
			if ((Object)(object)val == (Object)null)
			{
				return 0;
			}
			bool flag = Reflect.Get(PointActiveField, RoundDirector.instance, fallback: false);
			object? obj = PointCurrentField?.GetValue(RoundDirector.instance);
			ExtractionPoint val2 = (ExtractionPoint)((obj is ExtractionPoint) ? obj : null);
			try
			{
				if (flag && (Object)(object)val2 != (Object)null)
				{
					PhotonView val3 = ViewOf(val2);
					if ((Object)(object)val3 == (Object)null || val3.ViewID == 0)
					{
						return 0;
					}
					val.RPC("ExtractionPointActivateRPC", target, new object[1] { val3.ViewID });
					Log.Verbose("Told the joiner extraction point " + val3.ViewID + " is active.");
					return 1;
				}
				val.RPC("ExtractionPointsUnlockRPC", target, Array.Empty<object>());
				return 1;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not send the extraction activation state: " + ex.GetType().Name);
				return 0;
			}
		}

		private unsafe static int SendPointStates(Player target)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Invalid comparison between Unknown and I4
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (ExtractionPoint item in Points())
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				PhotonView val = ViewOf(item);
				if ((Object)(object)val == (Object)null || val.ViewID == 0)
				{
					continue;
				}
				try
				{
					if (item.haulGoal > 0 && Reflect.Get(PointHaulFetchedField, item, fallback: false))
					{
						val.RPC("HaulGoalSetRPC", target, new object[1] { item.haulGoal });
						num++;
					}
					if (PointStateField?.GetValue(item) is State val2 && (int)val2 != 1 && (int)val2 != 0)
					{
						val.RPC("StateSetRPC", target, new object[1] { val2 });
						num++;
						Log.Verbose("Sent extraction point " + val.ViewID + " state " + ((object)(*(State*)(&val2))/*cast due to .constrained prefix*/).ToString() + ".");
					}
				}
				catch (Exception ex)
				{
					Log.Verbose("Could not send state for extraction point " + val.ViewID + ": " + ex.GetType().Name);
				}
			}
			return num;
		}

		private static IEnumerable<ExtractionPoint> Points()
		{
			if (PointListField?.GetValue(RoundDirector.instance) is List<GameObject> list)
			{
				foreach (GameObject item in list)
				{
					if (!((Object)(object)item == (Object)null))
					{
						ExtractionPoint component = item.GetComponent<ExtractionPoint>();
						if ((Object)(object)component != (Object)null)
						{
							yield return component;
						}
					}
				}
				yield break;
			}
			ExtractionPoint[] array;
			try
			{
				array = Object.FindObjectsOfType<ExtractionPoint>(true);
			}
			catch
			{
				yield break;
			}
			ExtractionPoint[] array2 = array;
			foreach (ExtractionPoint val in array2)
			{
				if ((Object)(object)val != (Object)null)
				{
					yield return val;
				}
			}
		}

		private static PhotonView ViewOf(RoundDirector director)
		{
			if ((Object)(object)director == (Object)null)
			{
				return null;
			}
			return ((Component)director).GetComponent<PhotonView>();
		}

		private static PhotonView ViewOf(ExtractionPoint point)
		{
			if ((Object)(object)point == (Object)null)
			{
				return null;
			}
			object? obj = PointViewField?.GetValue(point);
			PhotonView val = (PhotonView)((obj is PhotonView) ? obj : null);
			if (val != null && (Object)(object)val != (Object)null)
			{
				return val;
			}
			return ((Component)point).GetComponent<PhotonView>();
		}
	}
	internal static class JoinQueue
	{
		private const float AvatarWaitTimeout = 30f;

		private const int AnnounceAttempts = 8;

		private const float AnnounceInterval = 1.5f;

		private static readonly FieldInfo LevelAnimationCompletedField = Reflect.Field(typeof(PlayerAvatar), "levelAnimationCompleted");

		private static readonly FieldInfo IsDisabledField = Reflect.Field(typeof(PlayerAvatar), "isDisabled");

		private static readonly FieldInfo DeathHeadField = Reflect.Field(typeof(PlayerAvatar), "playerDeathHead");

		private static readonly FieldInfo ColoursField = Reflect.Field(typeof(PlayerCosmetics), "colorsEquipped");

		private static MonoBehaviour _runner;

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

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

		internal static int PendingCount => Active.Count;

		internal static void Attach(MonoBehaviour runner)
		{
			_runner = runner;
		}

		internal static void Reset()
		{
			Active.Clear();
			Departed.Clear();
			ModuleConnectionSync.Clear();
		}

		internal static void OnPlayerEntered(Player player)
		{
			if (!PunGate.Ready || player == null || (Object)(object)_runner == (Object)null)
			{
				return;
			}
			int actorNumber = player.ActorNumber;
			Player localPlayer = PhotonNetwork.LocalPlayer;
			if (actorNumber != ((localPlayer != null) ? new int?(localPlayer.ActorNumber) : ((int?)null)))
			{
				Departed.Remove(player.ActorNumber);
				bool flag = false;
				try
				{
					flag = SemiFunc.RunIsLevel() || SemiFunc.RunIsShop() || SemiFunc.RunIsLobby();
				}
				catch
				{
				}
				if (!flag)
				{
					Log.Verbose("Player " + Describe(player) + " joined before the run started; no catch-up needed.");
				}
				else if (Active.Add(player.ActorNumber))
				{
					Log.Info("Late joiner detected: " + Describe(player) + ". Starting catch-up.");
					_runner.StartCoroutine(CatchUp(player));
				}
			}
		}

		internal static void OnPlayerLeft(Player player)
		{
			if (player != null)
			{
				Departed.Add(player.ActorNumber);
				Active.Remove(player.ActorNumber);
			}
		}

		private static IEnumerator CatchUp(Player player)
		{
			int actor = player.ActorNumber;
			string sceneAtStart = CurrentScene();
			_runner.StartCoroutine(AnnounceLoadingFlag(player));
			if (PhotonNetwork.IsMasterClient)
			{
				yield return (object)new WaitForSeconds(1.5f);
				if (Aborted(actor, sceneAtStart))
				{
					Finish(actor, "aborted before module replay");
					yield break;
				}
				ModuleConnectionSync.ReplayTo(player);
			}
			float deadline = Time.unscaledTime + 30f;
			PlayerAvatar avatar = null;
			while (Time.unscaledTime < deadline)
			{
				if (Aborted(actor, sceneAtStart))
				{
					Finish(actor, "aborted while waiting for the avatar");
					yield break;
				}
				avatar = FindAvatar(player);
				if ((Object)(object)avatar != (Object)null)
				{
					break;
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
			if ((Object)(object)avatar == (Object)null)
			{
				Finish(actor, "their avatar never appeared");
				yield break;
			}
			EnemyAwareness.RegisterAllPlayers();
			yield return null;
			if (!PhotonNetwork.IsMasterClient)
			{
				Finish(actor, "client-side catch-up complete");
				yield break;
			}
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before stat sync");
				yield break;
			}
			int num = StatsSync.SendTo(player);
			if (num > 0)
			{
				Log.Verbose("Sent " + num + " stat/total update(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before item name sync");
				yield break;
			}
			int num2 = WorldStateSync.SendItemNamesTo(player);
			if (num2 > 0)
			{
				Log.Verbose("Sent " + num2 + " item identity update(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before battery sync");
				yield break;
			}
			int num3 = WorldStateSync.SendBatteryStateTo(player);
			if (num3 > 0)
			{
				Log.Verbose("Sent " + num3 + " battery state(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before item state sync");
				yield break;
			}
			int num4 = WorldStateSync.SendEquippedItemsTo(player);
			if (num4 > 0)
			{
				Log.Verbose("Sent " + num4 + " equipped-item state(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before value sync");
				yield break;
			}
			int num5 = WorldStateSync.SendValuesTo(player);
			if (num5 > 0)
			{
				Log.Verbose("Sent " + num5 + " value update(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before extraction sync");
				yield break;
			}
			int num6 = ExtractionSync.SendTo(player);
			if (num6 > 0)
			{
				Log.Verbose("Sent " + num6 + " extraction update(s) to " + Describe(player) + ".");
			}
			yield return (object)new WaitForSeconds(0.25f);
			if (Aborted(actor, sceneAtStart))
			{
				Finish(actor, "aborted before level state sync");
				yield break;
			}
			int num7 = LevelStateSync.SendTo(player);
			if (num7 > 0)
			{
				Log.Verbose("Sent " + num7 + " level state update(s) to " + Describe(player) + ".");
			}
			LevelSnapshot.SendTo(player);
			yield return (object)new WaitForSeconds(1f);
			if (!Aborted(actor, sceneAtStart))
			{
				EnemyAwareness.RegisterAllPlayers();
			}
			Finish(actor, "catch-up complete");
		}

		private static IEnumerator AnnounceLoadingFlag(Player player)
		{
			int actor = player.ActorNumber;
			for (int attempt = 0; attempt < 8; attempt++)
			{
				yield return (object)new WaitForSeconds(1.5f);
				if (!ModConfig.On || Departed.Contains(actor))
				{
					break;
				}
				PlayerAvatar val = LocalAvatar();
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.photonView == (Object)null) && Reflect.Get(LevelAnimationCompletedField, val, fallback: false))
				{
					if (attempt == 1)
					{
						ResendOwnVoice(val, player);
					}
					if (attempt == 5)
					{
						ResendOwnColours(val, player);
					}
					try
					{
						val.photonView.RPC("LoadingLevelAnimationCompletedRPC", player, Array.Empty<object>());
						Log.Once("announce", "Announcing our loading-animation state to late joiners so their loading screen releases.");
					}
					catch (Exception ex)
					{
						Log.Verbose("Could not announce the loading flag: " + ex.GetType().Name);
					}
				}
			}
		}

		private static void ResendOwnVoice(PlayerAvatar local, Player player)
		{
			try
			{
				if (!Reflect.Get(IsDisabledField, local, fallback: false) && !((Object)(object)PlayerVoiceChat.instance == (Object)null))
				{
					PhotonView component = ((Component)PlayerVoiceChat.instance).GetComponent<PhotonView>();
					if ((Object)(object)component != (Object)null)
					{
						local.photonView.RPC("UpdateMyPlayerVoiceChat", player, new object[1] { component.ViewID });
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not resend our voice link: " + ex.GetType().Name);
			}
		}

		private static void ResendOwnColours(PlayerAvatar local, Player player)
		{
			SendColours(local.playerCosmetics, player);
			SendColours(Reflect.Get<PlayerDeathHead>(DeathHeadField, local)?.playerCosmetics, player);
		}

		private static void SendColours(PlayerCosmetics cosmetics, Player player)
		{
			try
			{
				int[] array = (((Object)(object)cosmetics != (Object)null) ? Reflect.Get<int[]>(ColoursField, cosmetics) : null);
				if (array != null && !Array.Exists(array, (int c) => c < 0))
				{
					PhotonView component = ((Component)cosmetics).GetComponent<PhotonView>();
					if (component != null)
					{
						component.RPC("SetupColorsRPC", player, new object[1] { array });
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not resend our colours: " + ex.GetType().Name);
			}
		}

		internal static void ReleaseOwnLoadingScreen(PlayerAvatar local)
		{
			if (!PunGate.Ready || PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom)
			{
				return;
			}
			bool flag;
			try
			{
				flag = SemiFunc.RunIsLevel() || SemiFunc.RunIsShop();
			}
			catch
			{
				return;
			}
			if (!flag)
			{
				return;
			}
			if ((Object)(object)local == (Object)null)
			{
				local = LocalAvatar();
			}
			if ((Object)(object)local == (Object)null || Reflect.Get(LevelAnimationCompletedField, local, fallback: false))
			{
				return;
			}
			try
			{
				local.LoadingLevelAnimationCompleted();
				Log.Once("selfrelease", "Released our own loading screen after joining a run in progress.");
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not release our own loading screen: " + ex.GetType().Name);
			}
		}

		private static void Finish(int actor, string reason)
		{
			Active.Remove(actor);
			Log.Verbose("Catch-up for actor " + actor + " finished: " + reason + ".");
		}

		private static bool Aborted(int actor, string sceneAtStart)
		{
			if (!ModConfig.On)
			{
				return true;
			}
			if (Departed.Contains(actor))
			{
				return true;
			}
			if (!PhotonNetwork.InRoom)
			{
				return true;
			}
			return CurrentScene() != sceneAtStart;
		}

		private static string CurrentScene()
		{
			try
			{
				RunManager instance = RunManager.instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					Level levelCurrent = instance.levelCurrent;
					obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null);
				}
				if (obj == null)
				{
					obj = string.Empty;
				}
				return (string)obj;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static PlayerAvatar LocalAvatar()
		{
			try
			{
				return SemiFunc.PlayerAvatarLocal();
			}
			catch
			{
				return null;
			}
		}

		private static PlayerAvatar FindAvatar(Player player)
		{
			try
			{
				return SemiFunc.PlayerAvatarGetFromPhotonPlayer(player);
			}
			catch
			{
				return null;
			}
		}

		private static string Describe(Player player)
		{
			if (player == null)
			{
				return "unknown";
			}
			if (!string.IsNullOrEmpty(player.NickName))
			{
				return player.NickName;
			}
			return "actor " + player.ActorNumber;
		}
	}
	internal static class LevelSnapshot
	{
		internal const byte Event = 172;

		private static readonly FieldInfo ExploredField = Reflect.Field(typeof(RoomVolume), "Explored");

		internal static bool SendTo(Player target)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			if (!PunGate.Ready)
			{
				return false;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return false;
			}
			try
			{
				List<int> list = new List<int>();
				RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>();
				foreach (RoomVolume val in array)
				{
					if (IsExplored(val))
					{
						AddKey<RoomVolume>(list, val);
					}
				}
				List<int> list2 = new List<int>();
				ValuablePropSwitch[] array2 = Object.FindObjectsOfType<ValuablePropSwitch>(true);
				foreach (ValuablePropSwitch val2 in array2)
				{
					if ((Object)(object)val2.PropParent != (Object)null && AddKey<ValuablePropSwitch>(list2, val2))
					{
						list2.Add(val2.PropParent.activeSelf ? 1 : 0);
					}
				}
				RaiseEventOptions val3 = new RaiseEventOptions();
				val3.TargetActors = new int[1] { target.ActorNumber };
				RaiseEventOptions val4 = val3;
				return PhotonNetwork.RaiseEvent((byte)172, (object)new object[2]
				{
					list.ToArray(),
					list2.ToArray()
				}, val4, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not send the level snapshot: " + ex.GetType().Name);
				return false;
			}
		}

		private static bool IsExplored(RoomVolume room)
		{
			return Reflect.Get(ExploredField, room, fallback: false);
		}

		private static bool AddKey<T>(List<int> into, T item) where T : Component
		{
			PhotonView componentInParent = ((Component)item).GetComponentInParent<PhotonView>(true);
			if ((Object)(object)componentInParent == (Object)null || componentInParent.ViewID < 1)
			{
				return false;
			}
			int num = Array.IndexOf(((Component)componentInParent).GetComponentsInChildren<T>(true), item);
			if (num < 0)
			{
				return false;
			}
			into.Add(componentInParent.ViewID);
			into.Add(num);
			return true;
		}

		internal static void Apply(EventData photonEvent)
		{
			if (!ModConfig.On || PhotonNetwork.IsMasterClient || !(photonEvent.CustomData is object[] array) || array.Length < 2)
			{
				return;
			}
			try
			{
				int num = 0;
				int num2 = 0;
				if (array[0] is int[] array2)
				{
					for (int i = 0; i + 1 < array2.Length; i += 2)
					{
						RoomVolume val = Find<RoomVolume>(array2[i], array2[i + 1]);
						if (val != null && val.SetExplored())
						{
							num++;
						}
					}
				}
				if (array[1] is int[] array3)
				{
					for (int j = 0; j + 2 < array3.Length; j += 3)
					{
						ValuablePropSwitch val2 = Find<ValuablePropSwitch>(array3[j], array3[j + 1]);
						if (val2 != null)
						{
							bool flag = array3[j + 2] == 1;
							val2.PropParent.SetActive(flag);
							if ((Object)(object)val2.ValuableParent != (Object)null)
							{
								val2.ValuableParent.SetActive(!flag);
							}
							num2++;
						}
					}
				}
				Log.Info("Applied the host's level snapshot: " + num + " explored room(s), " + num2 + " prop switch(es).");
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not apply the level snapshot: " + ex.GetType().Name);
			}
		}

		private static T Find<T>(int viewId, int index) where T : Component
		{
			PhotonView val = PhotonView.Find(viewId);
			if ((Object)(object)val == (Object)null)
			{
				return default(T);
			}
			T[] componentsInChildren = ((Component)val).GetComponentsInChildren<T>(true);
			if (index < 0 || index >= componentsInChildren.Length)
			{
				return default(T);
			}
			return componentsInChildren[index];
		}
	}
	internal static class LevelStateSync
	{
		private static readonly FieldInfo EnemySpawnedField = Reflect.Field(typeof(EnemyParent), "Spawned");

		private static readonly FieldInfo DiscoveredField = Reflect.Field(typeof(ValuableObject), "discovered");

		private static readonly FieldInfo HingeBrokenField = Reflect.Field(typeof(PhysGrabHinge), "broken");

		private static readonly FieldInfo HingeDeadField = Reflect.Field(typeof(PhysGrabHinge), "dead");

		private static readonly FieldInfo CosmeticHealthField = Reflect.Field(typeof(CosmeticWorldObject), "healthAmountTarget");

		private static readonly FieldInfo DeadSetField = Reflect.Field(typeof(PlayerAvatar), "deadSet");

		private static readonly FieldInfo SteamIdField = Reflect.Field(typeof(PlayerAvatar), "steamID");

		private static readonly FieldInfo HealthField = Reflect.Field(typeof(PlayerHealth), "health");

		private static readonly FieldInfo MaxHealthField = Reflect.Field(typeof(PlayerHealth), "maxHealth");

		private static readonly FieldInfo StandBrokenField = Reflect.Field(typeof(UpgradeStand), "isBroken");

		private static readonly FieldInfo KeycardStateField = Reflect.Field(typeof(ShopKeycardDoor), "currentState");

		private static readonly FieldInfo ChargingField = Reflect.Field(typeof(ChargingStation), "isCharging");

		private static readonly FieldInfo IsMeleeField = Reflect.Field(typeof(PhysGrabObject), "isMelee");

		private static readonly FieldInfo MineStateField = Reflect.Field(typeof(ItemMine), "state");

		private static readonly FieldInfo TvBrokenField = Reflect.Field(typeof(FlatScreenTV), "broken");

		private static readonly FieldInfo VehicleStageField = Reflect.Field(typeof(ItemVehicle), "currentDestructionStateIndex");

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

		internal static void Clear()
		{
			DeadChildren.Clear();
		}

		internal static void RememberDestroyed(PhotonView view)
		{
			if (!((Object)(object)view == (Object)null) && view.ViewID >= 1 && view.ViewID != view.InstantiationId)
			{
				DeadChildren[view.ViewID] = view.Prefix;
			}
		}

		internal static int SendTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			int num = 0 + SendPropSwitches(target) + SendDeadChildren(target) + SendHinges(target) + SendEnemies(target) + SendDiscovered(target) + SendCosmeticHealth(target) + SendDeathHeads(target) + SendPlayers(target) + SendHaulList(target) + SendMisc(target);
			if (num > 0)
			{
				Log.Once("levelsync", "Sent props, enemies, doors and map state to late joiners.");
			}
			return num;
		}

		private static int SendPropSwitches(Player target)
		{
			if ((Object)(object)ValuableDirector.instance == (Object)null)
			{
				return 0;
			}
			return Rpc((Component)(object)ValuableDirector.instance, "VolumesAndSwitchSetupRPC", target) ? 1 : 0;
		}

		private static int SendDeadChildren(Player target)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			int num = 0;
			foreach (KeyValuePair<int, int> deadChild in DeadChildren)
			{
				Hashtable val = new Hashtable
				{
					[(byte)0] = deadChild.Key,
					[(byte)2] = PhotonNetwork.ServerTimestamp,
					[(byte)3] = "DestroyPhysGrabObjectRPC"
				};
				if (deadChild.Value > 0)
				{
					val[(byte)1] = (short)deadChild.Value;
				}
				RaiseEventOptions val2 = new RaiseEventOptions();
				val2.TargetActors = new int[1] { target.ActorNumber };
				if (PhotonCacheCleaner.RaiseInternal(200, val, val2))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendHinges(Player target)
		{
			int num = 0;
			PhysGrabHinge[] array = Object.FindObjectsOfType<PhysGrabHinge>();
			foreach (PhysGrabHinge val in array)
			{
				if (Reflect.Get(HingeBrokenField, val, fallback: false) && Rpc((Component)(object)val, "HingeBreakRPC", target))
				{
					num++;
				}
				if (Reflect.Get(HingeDeadField, val, fallback: false) && Rpc((Component)(object)val, "DestroyHingeRPC", target))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendEnemies(Player target)
		{
			int num = 0;
			EnemyParent[] array = Object.FindObjectsOfType<EnemyParent>(true);
			foreach (EnemyParent val in array)
			{
				bool flag = Reflect.Get(EnemySpawnedField, val, fallback: false);
				if (Rpc((Component)(object)val, flag ? "SpawnRPC" : "DespawnRPC", target))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendDiscovered(Player target)
		{
			int num = 0;
			ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>();
			foreach (ValuableObject val in array)
			{
				if (Reflect.Get(DiscoveredField, val, fallback: false) && Rpc((Component)(object)val, "DiscoverRPC", target))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendCosmeticHealth(Player target)
		{
			int num = 0;
			CosmeticWorldObject[] array = Object.FindObjectsOfType<CosmeticWorldObject>();
			foreach (CosmeticWorldObject val in array)
			{
				if (Rpc((Component)(object)val, "HealthUpdateRPC", target, Reflect.Get(CosmeticHealthField, val, 1f)))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendDeathHeads(Player target)
		{
			int num = 0;
			PlayerDeathHead[] array = Object.FindObjectsOfType<PlayerDeathHead>(true);
			foreach (PlayerDeathHead val in array)
			{
				string text = (((Object)(object)val.playerAvatar != (Object)null) ? Reflect.Get<string>(SteamIdField, val.playerAvatar) : null);
				if (!string.IsNullOrEmpty(text) && Rpc((Component)(object)val, "SetupRPC", target, text))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendPlayers(Player target)
		{
			int num = 0;
			PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>(true);
			foreach (PlayerAvatar val in array)
			{
				if ((Object)(object)val.photonView == (Object)null || val.photonView.Owner == target)
				{
					continue;
				}
				if (Reflect.Get(DeadSetField, val, fallback: false))
				{
					if (Rpc((Component)(object)val, "PlayerDeathRPC", target, -1))
					{
						num++;
					}
				}
				else if ((Object)(object)val.playerHealth != (Object)null && Rpc((Component)(object)val.playerHealth, "UpdateHealthRPC", target, Reflect.Get(HealthField, val.playerHealth, 100), Reflect.Get(MaxHealthField, val.playerHealth, 100), false, false))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendHaulList(Player target)
		{
			int num = 0;
			if ((Object)(object)RoundDirector.instance == (Object)null)
			{
				return 0;
			}
			foreach (GameObject dollarHaul in RoundDirector.instance.dollarHaulList)
			{
				if ((Object)(object)dollarHaul != (Object)null && Rpc((Component)(object)dollarHaul.transform, "AddToDollarHaulListRPC", target))
				{
					num++;
				}
			}
			return num;
		}

		private static int SendMisc(Player target)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected I4, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Invalid comparison between Unknown and I4
			int num = 0;
			UpgradeStand[] array = Object.FindObjectsOfType<UpgradeStand>();
			foreach (UpgradeStand val in array)
			{
				if (Reflect.Get(StandBrokenField, val, fallback: false) && Rpc((Component)(object)val, "SetBrokenRPC", target))
				{
					num++;
				}
			}
			ShopKeycardDoor[] array2 = Object.FindObjectsOfType<ShopKeycardDoor>();
			foreach (ShopKeycardDoor val2 in array2)
			{
				int num2 = (int)Reflect.Get<State>(KeycardStateField, val2, (State)0);
				if (num2 != 0 && Rpc((Component)(object)val2, "StateSetRPC", target, num2))
				{
					num++;
				}
			}
			ChargingStation[] array3 = Object.FindObjectsOfType<ChargingStation>();
			foreach (ChargingStation val3 in array3)
			{
				if (Reflect.Get(ChargingField, val3, fallback: false) && Rpc((Component)(object)val3, "StartChargeRPC", target))
				{
					num++;
				}
			}
			ItemMelee[] array4 = Object.FindObjectsOfType<ItemMelee>();
			foreach (ItemMelee val4 in array4)
			{
				PhysGrabObject component = ((Component)val4).GetComponent<PhysGrabObject>();
				if ((Object)(object)component != (Object)null && !Reflect.Get(IsMeleeField, component, fallback: true) && Rpc((Component)(object)val4, "MeleeBreakRPC", target))
				{
					num++;
				}
			}
			ItemMine[] array5 = Object.FindObjectsOfType<ItemMine>();
			foreach (ItemMine val5 in array5)
			{
				if ((int)Reflect.Get<States>(MineStateField, val5, (States)0) == 2 && Rpc((Component)(object)val5, "StateSetRPC", target, 2))
				{
					num++;
				}
			}
			FlatScreenTV[] array6 = Object.FindObjectsOfType<FlatScreenTV>();
			foreach (FlatScreenTV val6 in array6)
			{
				if (Rpc((Component)(object)val6, "BrokenOrNotRPC", target, Reflect.Get(TvBrokenField, val6, fallback: true)))
				{
					num++;
				}
			}
			ItemVehicle[] array7 = Object.FindObjectsOfType<ItemVehicle>();
			foreach (ItemVehicle val7 in array7)
			{
				int num3 = Reflect.Get(VehicleStageField, val7, -1);
				if (num3 >= 0 && Rpc((Component)(object)val7, "ChangeDestructionStateRPC", target, num3))
				{
					num++;
				}
			}
			return num;
		}

		private static bool Rpc(Component owner, string method, Player target, params object[] args)
		{
			PhotonView val = (((Object)(object)owner != (Object)null) ? owner.GetComponent<PhotonView>() : null);
			if ((Object)(object)val == (Object)null || val.ViewID < 1)
			{
				return false;
			}
			try
			{
				val.RPC(method, target, args);
				return true;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not replay " + method + " on view " + val.ViewID + ": " + ex.GetType().Name);
				return false;
			}
		}
	}
	internal static class ModuleConnectionSync
	{
		private sealed class ConnectionState
		{
			internal PhotonView View;

			internal bool Top;

			internal bool Bottom;

			internal bool Right;

			internal bool Left;

			internal bool First;
		}

		private const byte RepairRequestEvent = 171;

		private const float FirstRequestDelay = 8f;

		private const float RequestInterval = 10f;

		private const int MaxRequests = 5;

		private static readonly FieldInfo PhotonViewField = Reflect.Field(typeof(Module), "photonView");

		private static readonly FieldInfo SetupDoneField = Reflect.Field(typeof(Module), "SetupDone");

		private static readonly Dictionary<int, ConnectionState> Known = new Dictionary<int, ConnectionState>();

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

		private static float _unsetupSince;

		private static float _nextRequest;

		private static int _requestsSent;

		private static bool _listening;

		private static bool Wanted => PunGate.Ready;

		internal static int KnownCount => Known.Count;

		internal static void Clear()
		{
			if (Known.Count > 0)
			{
				Log.Verbose("Cleared " + Known.Count + " remembered module connection state(s).");
			}
			Known.Clear();
			ServedActors.Clear();
			_unsetupSince = 0f;
			_nextRequest = 0f;
			_requestsSent = 0;
		}

		internal static void StartListening()
		{
			if (_listening)
			{
				return;
			}
			try
			{
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
				_listening = true;
				Log.Verbose("Listening for module repair requests on event " + (byte)171 + ".");
			}
			catch (Exception ex)
			{
				Log.Warn("Could not subscribe to Photon events; module repair requests are unavailable: " + ex.GetType().Name);
			}
		}

		internal static void StopListening()
		{
			if (_listening)
			{
				try
				{
					PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
				}
				catch
				{
				}
				_listening = false;
			}
		}

		private static void OnEvent(EventData photonEvent)
		{
			if (photonEvent.Code == 172)
			{
				LevelSnapshot.Apply(photonEvent);
			}
			else
			{
				if (photonEvent.Code != 171 || !Wanted || !PhotonNetwork.IsMasterClient)
				{
					return;
				}
				try
				{
					if (photonEvent.CustomData is int[] array && array.Length != 0)
					{
						Player value = null;
						if (PhotonNetwork.CurrentRoom != null)
						{
							PhotonNetwork.CurrentRoom.Players?.TryGetValue(photonEvent.Sender, out value);
						}
						if (value != null)
						{
							int num = ReplaySubsetTo(value, array);
							Log.Info("Repaired " + num + " module connection(s) on request from " + Describe(value) + ".");
						}
					}
				}
				catch (Exception ex)
				{
					Log.Verbose("Could not handle a module repair request: " + ex.GetType().Name);
				}
			}
		}

		internal static void Remember(Module module, bool top, bool bottom, bool right, bool left, bool first)
		{
			if (Wanted && !((Object)(object)module == (Object)null) && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient)
			{
				PhotonView val = ViewOf(module);
				if (!((Object)(object)val == (Object)null) && val.ViewID != 0)
				{
					Known[val.ViewID] = new ConnectionState
					{
						View = val,
						Top = top,
						Bottom = bottom,
						Right = right,
						Left = left,
						First = first
					};
					Log.Once("modremember", "Recording module connection states so late joiners can be caught up.");
				}
			}
		}

		internal static int ReplayTo(Player target)
		{
			if (!Wanted || target == null || Known.Count == 0)
			{
				return 0;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			if (!ServedActors.Add(target.ActorNumber))
			{
				Log.Verbose("Already sent module connections to " + Describe(target) + "; not resending.");
				return 0;
			}
			int num = Send(target, Known.Keys);
			if (num > 0)
			{
				Log.Info("Sent " + num + " module connection state(s) to late joiner " + Describe(target) + ".");
			}
			return num;
		}

		private static int ReplaySubsetTo(Player target, int[] viewIds)
		{
			ServedActors.Add(target.ActorNumber);
			return Send(target, viewIds);
		}

		private static int Send(Player target, IEnumerable<int> viewIds)
		{
			int num = 0;
			int num2 = 0;
			foreach (int viewId in viewIds)
			{
				if (!Known.TryGetValue(viewId, out var value) || (Object)(object)value?.View == (Object)null)
				{
					num2++;
					continue;
				}
				try
				{
					value.View.RPC("ModuleConnectionSetRPC", target, new object[5] { value.Top, value.Bottom, value.Right, value.Left, value.First });
					num++;
				}
				catch (Exception ex)
				{
					Log.Verbose("Could not send module connection " + viewId + ": " + ex.GetType().Name);
				}
			}
			if (num2 > 0)
			{
				Log.Verbose(num2 + " requested module view(s) are unknown to us and were skipped.");
			}
			return num;
		}

		internal static void TickClientRepair()
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			if (!Wanted || PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom)
			{
				return;
			}
			bool flag;
			try
			{
				flag = SemiFunc.RunIsLevel() || SemiFunc.RunIsShop();
			}
			catch
			{
				return;
			}
			if (!flag || _requestsSent >= 5)
			{
				_unsetupSince = 0f;
				return;
			}
			List<int> list = CollectUnsetupViewIds();
			if (list.Count == 0)
			{
				if (_unsetupSince != 0f)
				{
					Log.Verbose("All module connection states are present; repair not needed.");
				}
				_unsetupSince = 0f;
			}
			else if (_unsetupSince == 0f)
			{
				_unsetupSince = Time.unscaledTime;
			}
			else if (!(Time.unscaledTime - _unsetupSince < 8f) && !(Time.unscaledTime < _nextRequest))
			{
				_nextRequest = Time.unscaledTime + 10f;
				_requestsSent++;
				try
				{
					RaiseEventOptions val = new RaiseEventOptions
					{
						Receivers = (ReceiverGroup)2
					};
					PhotonNetwork.RaiseEvent((byte)171, (object)list.ToArray(), val, SendOptions.SendReliable);
					Log.Info("Asked the host to repair " + list.Count + " module connection state(s) we never received (attempt " + _requestsSent + " of " + 5 + ").");
				}
				catch (Exception ex)
				{
					Log.Verbose("Could not send a module repair request: " + ex.GetType().Name);
				}
			}
		}

		private static List<int> CollectUnsetupViewIds()
		{
			List<int> list = new List<int>();
			Module[] array;
			try
			{
				array = Object.FindObjectsOfType<Module>();
			}
			catch
			{
				return list;
			}
			Module[] array2 = array;
			foreach (Module val in array2)
			{
				if (!((Object)(object)val == (Object)null) && !Reflect.Get(SetupDoneField, val, fallback: true) && !((Object)(object)((Component)val).GetComponent<StartRoom>() != (Object)null))
				{
					PhotonView val2 = ViewOf(val);
					if (!((Object)(object)val2 == (Object)null) && val2.ViewID != 0)
					{
						list.Add(val2.ViewID);
					}
				}
			}
			return list;
		}

		private static PhotonView ViewOf(Module module)
		{
			object? obj = PhotonViewField?.GetValue(module);
			PhotonView val = (PhotonView)((obj is PhotonView) ? obj : null);
			if (val != null && (Object)(object)val != (Object)null)
			{
				return val;
			}
			return ((Component)module).GetComponent<PhotonView>();
		}

		private static string Describe(Player player)
		{
			if (player == null)
			{
				return "unknown";
			}
			if (!string.IsNullOrEmpty(player.NickName))
			{
				return player.NickName;
			}
			return "actor " + player.ActorNumber;
		}
	}
	internal static class StatsSync
	{
		private static readonly FieldInfo TotalHaulField = Reflect.Field(typeof(RoundDirector), "totalHaul");

		internal static int SendTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			int num = 0 + ResyncStatDictionaries() + SendHaulTotal(target) + SendShoppingCost(target);
			if (num > 0)
			{
				Log.Once("statsync", "Re-sent run statistics and totals for late joiners.");
			}
			return num;
		}

		private static int ResyncStatDictionaries()
		{
			if ((Object)(object)PunManager.instance == (Object)null)
			{
				return 0;
			}
			try
			{
				PunManager.instance.SyncAllDictionaries();
				Log.Verbose("Re-ran the full stat dictionary sync for the new player.");
				return 1;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not re-run the stat dictionary sync: " + ex.GetType().Name);
				return 0;
			}
		}

		private static int SendHaulTotal(Player target)
		{
			if ((Object)(object)PunManager.instance == (Object)null || (Object)(object)RoundDirector.instance == (Object)null)
			{
				return 0;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null || component.ViewID == 0)
			{
				return 0;
			}
			int num = Reflect.Get(TotalHaulField, RoundDirector.instance, 0);
			try
			{
				component.RPC("SyncHaul", target, new object[1] { num });
				Log.Verbose("Sent the current haul total (" + num + ") to the new player.");
				return 1;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not send the haul total: " + ex.GetType().Name);
				return 0;
			}
		}

		private static int SendShoppingCost(Player target)
		{
			if ((Object)(object)PunManager.instance == (Object)null || (Object)(object)ShopManager.instance == (Object)null)
			{
				return 0;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null || component.ViewID == 0)
			{
				return 0;
			}
			try
			{
				component.RPC("UpdateShoppingCostRPC", target, new object[1] { ShopManager.instance.totalCost });
				Log.Verbose("Sent the current cart total (" + ShopManager.instance.totalCost + ").");
				return 1;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not send the cart total: " + ex.GetType().Name);
				return 0;
			}
		}
	}
	internal static class WorldStateSync
	{
		private static readonly FieldInfo ItemValueField = Reflect.Field(typeof(ItemAttributes), "value");

		private static readonly FieldInfo ValuableCurrentField = Reflect.Field(typeof(ValuableObject), "dollarValueCurrent");

		private static readonly FieldInfo ValuableSetField = Reflect.Field(typeof(ValuableObject), "dollarValueSet");

		private static readonly FieldInfo InstanceNameField = Reflect.Field(typeof(ItemAttributes), "instanceName");

		private static readonly FieldInfo ItemStateField = Reflect.Field(typeof(ItemEquippable), "currentState");

		private static readonly FieldInfo ItemSpotIndexField = Reflect.Field(typeof(ItemEquippable), "inventorySpotIndex");

		private static readonly FieldInfo ItemOwnerField = Reflect.Field(typeof(ItemEquippable), "ownerPlayerId");

		private static readonly FieldInfo BatteryLifeIntField = Reflect.Field(typeof(ItemBattery), "batteryLifeInt");

		internal static int SendValuesTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			int num = 0;
			try
			{
				ItemAttributes[] array = Object.FindObjectsOfType<ItemAttributes>(true);
				foreach (ItemAttributes val in array)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					PhotonView component = ((Component)val).GetComponent<PhotonView>();
					if (!((Object)(object)component == (Object)null) && component.ViewID != 0)
					{
						int num2 = Reflect.Get(ItemValueField, val, 0);
						if (num2 > 0)
						{
							component.RPC("GetValueRPC", target, new object[1] { num2 });
							num++;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Log.Verbose("Item value sync failed: " + ex.GetType().Name);
			}
			try
			{
				ValuableObject[] array2 = Object.FindObjectsOfType<ValuableObject>(true);
				foreach (ValuableObject val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null || !Reflect.Get(ValuableSetField, val2, fallback: false))
					{
						continue;
					}
					PhotonView component2 = ((Component)val2).GetComponent<PhotonView>();
					if (!((Object)(object)component2 == (Object)null) && component2.ViewID != 0)
					{
						float num3 = Reflect.Get(ValuableCurrentField, val2, 0f);
						if (!(num3 <= 0f))
						{
							component2.RPC("DollarValueSetRPC", target, new object[1] { num3 });
							num++;
						}
					}
				}
			}
			catch (Exception ex2)
			{
				Log.Verbose("Valuable value sync failed: " + ex2.GetType().Name);
			}
			if (num > 0)
			{
				Log.Once("valuesync", "Sent item and valuable values to late joiners.");
			}
			return num;
		}

		internal static int SendItemNamesTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			if ((Object)(object)PunManager.instance == (Object)null)
			{
				return 0;
			}
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null || component.ViewID == 0)
			{
				return 0;
			}
			ItemAttributes[] array;
			try
			{
				array = Object.FindObjectsOfType<ItemAttributes>(true);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not enumerate items for name sync: " + ex.GetType().Name);
				return 0;
			}
			int num = 0;
			ItemAttributes[] array2 = array;
			foreach (ItemAttributes val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string text = Reflect.Get<string>(InstanceNameField, val);
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				PhotonView component2 = ((Component)val).GetComponent<PhotonView>();
				if (!((Object)(object)component2 == (Object)null) && component2.ViewID != 0)
				{
					try
					{
						component.RPC("SetItemNameRPC", target, new object[2] { text, component2.ViewID });
						num++;
					}
					catch (Exception ex2)
					{
						Log.Verbose("Could not send the item name for " + ((Object)val).name + ": " + ex2.GetType().Name);
					}
				}
			}
			if (num > 0)
			{
				Log.Once("itemnames", "Sent item identities, battery levels and inventory placement to late joiners.");
			}
			return num;
		}

		internal static int SendEquippedItemsTo(Player target)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected I4, but got Unknown
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			ItemEquippable[] array;
			try
			{
				array = Object.FindObjectsOfType<ItemEquippable>(true);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not enumerate equippable items: " + ex.GetType().Name);
				return 0;
			}
			int num = 0;
			ItemEquippable[] array2 = array;
			foreach (ItemEquippable val in array2)
			{
				if ((Object)(object)val == (Object)null || !(ItemStateField?.GetValue(val) is ItemState val2) || (int)val2 == 0)
				{
					continue;
				}
				int num2 = Reflect.Get(ItemOwnerField, val, -1);
				if (num2 == -1)
				{
					continue;
				}
				int num3 = Reflect.Get(ItemSpotIndexField, val, -1);
				PhotonView component = ((Component)val).GetComponent<PhotonView>();
				if (!((Object)(object)component == (Object)null) && component.ViewID != 0)
				{
					try
					{
						component.RPC("RPC_UpdateItemState", target, new object[3]
						{
							(int)val2,
							num3,
							num2
						});
						num++;
					}
					catch (Exception ex2)
					{
						Log.Verbose("Could not send item state for " + ((Object)val).name + ": " + ex2.GetType().Name);
					}
				}
			}
			if (num > 0)
			{
				Log.Once("itemstate", "Sent equipped-item state to late joiners.");
			}
			return num;
		}

		internal static int SendBatteryStateTo(Player target)
		{
			if (!PunGate.Ready)
			{
				return 0;
			}
			if (target == null || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			int num = 0;
			ItemBattery[] array;
			try
			{
				array = Object.FindObjectsOfType<ItemBattery>(true);
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not enumerate batteries: " + ex.GetType().Name);
				return 0;
			}
			ItemBattery[] array2 = array;
			foreach (ItemBattery val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				PhotonView component = ((Component)val).GetComponent<PhotonView>();
				if (!((Object)(object)component == (Object)null) && component.ViewID != 0 && !((Object)(object)((Component)component).GetComponent<ItemBattery>() != (Object)(object)val))
				{
					try
					{
						int num2 = CurrentBarLevel(val);
						bool batteryActive = val.batteryActive;
						component.RPC("BatteryToggleRPC", target, new object[1] { false });
						component.RPC("BatteryFullPercentChangeRPC", target, new object[2] { num2, false });
						component.RPC("BatteryToggleRPC", target, new object[1] { batteryActive });
						num++;
					}
					catch (Exception ex2)
					{
						Log.Verbose("Battery sync failed for " + ((Object)val).name + ": " + ex2.GetType().Name);
					}
				}
			}
			if (num > 0)
			{
				Log.Once("batterysync", "Sent battery charge states to late joiners.");
			}
			return num;
		}

		private static int CurrentBarLevel(ItemBattery battery)
		{
			int num = Mathf.Max(1, battery.batteryBars);
			int num2 = Reflect.Get(BatteryLifeIntField, battery, 0);
			if (num2 < 0 || num2 > num || (num2 == 0 && battery.batteryLife > 0f))
			{
				num2 = Mathf.RoundToInt(battery.batteryLife / (100f / (float)num));
			}
			return Mathf.Clamp(num2, 0, num);
		}
	}
}
namespace RepoLateJoin.Core
{
	internal static class Log
	{
		private static ManualLogSource _source;

		private static readonly HashSet<string> Seen = new HashSet<string>();

		internal static void Attach(ManualLogSource source)
		{
			_source = source;
		}

		internal static void Info(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogInfo((object)message);
			}
		}

		internal static void Warn(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogWarning((object)message);
			}
		}

		internal static void Error(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)message);
			}
		}

		internal static void Verbose(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogDebug((object)message);
			}
		}

		internal static void Once(string key, string message)
		{
			if (Seen.Add(key))
			{
				Info(message);
			}
			else
			{
				Verbose(message);
			}
		}

		internal static void Reset()
		{
			Seen.Clear();
		}
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> JoinInLobby;

		internal static ConfigEntry<bool> JoinInShop;

		internal static ConfigEntry<bool> JoinInLevel;

		internal static ConfigEntry<bool> ShowStatus;

		private const string Section = "Late Join / 중간 참여";

		internal static bool On
		{
			get
			{
				if (Enabled != null)
				{
					return Enabled.Value;
				}
				return false;
			}
		}

		internal static void Bind(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("Late Join / 중간 참여", "Enabled", true, "Late join on/off. / 중간 참여 켜기/끄기.");
			JoinInLobby = cfg.Bind<bool>("Late Join / 중간 참여", "Truck", true, "Allow joining in the truck. / 트럭에서 참여 허용.");
			JoinInShop = cfg.Bind<bool>("Late Join / 중간 참여", "Shop", true, "Allow joining in the shop. / 상점에서 참여 허용.");
			JoinInLevel = cfg.Bind<bool>("Late Join / 중간 참여", "Level", true, "Allow joining during a level. / 레벨 진행 중 참여 허용.");
			ShowStatus = cfg.Bind<bool>("Late Join / 중간 참여", "ShowStatus", false, "Show room name and late join status top-left. / 좌측 상단에 방 이름과 중간 참여 상태 표시.");
		}
	}
	internal static class PunGate
	{
		private static bool _ready;

		internal static bool Ready
		{
			get
			{
				if (_ready)
				{
					return ModConfig.On;
				}
				return false;
			}
		}

		internal static bool PhotonInitialised => _ready;

		internal static void Mark()
		{
			if (!_ready)
			{
				_ready = true;
				Log.Verbose("Photon is initialised by the game; network-dependent work is now allowed.");
			}
		}
	}
	internal static class Reflect
	{
		private static readonly Dictionary<string, FieldInfo> FieldCache = new Dictionary<string, FieldInfo>();

		private static readonly Dictionary<string, MethodInfo> MethodCache = new Dictionary<string, MethodInfo>();

		internal static FieldInfo Field(Type type, string name)
		{
			if (type == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			string text = type.FullName + "::" + name;
			if (FieldCache.TryGetValue(text, out var value))
			{
				return value;
			}
			FieldInfo fieldInfo = null;
			try
			{
				fieldInfo = AccessTools.Field(type, name);
			}
			catch (Exception ex)
			{
				Log.Verbose("Field lookup failed for " + text + ": " + ex.GetType().Name);
			}
			FieldCache[text] = fieldInfo;
			if (fieldInfo == null)
			{
				Log.Once("nofield:" + text, "Field " + text + " is missing on this game version.");
			}
			return fieldInfo;
		}

		internal static MethodInfo Method(Type type, string name, Type[] parameters = null)
		{
			if (type == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			string text = type.FullName + "::" + name + "/" + ((parameters != null) ? parameters.Length : (-1));
			if (MethodCache.TryGetValue(text, out var value))
			{
				return value;
			}
			MethodInfo methodInfo = null;
			try
			{
				methodInfo = AccessTools.Method(type, name, parameters, (Type[])null);
			}
			catch (Exception ex)
			{
				Log.Verbose("Method lookup failed for " + text + ": " + ex.GetType().Name);
			}
			MethodCache[text] = methodInfo;
			if (methodInfo == null)
			{
				Log.Once("nomethod:" + text, "Method " + text + " is missing on this game version.");
			}
			return methodInfo;
		}

		internal static Type TypeByName(string name)
		{
			try
			{
				return AccessTools.TypeByName(name);
			}
			catch
			{
				return null;
			}
		}

		internal static T Get<T>(FieldInfo field, object instance, T fallback = default(T))
		{
			if (field == null)
			{
				return fallback;
			}
			try
			{
				return (field.GetValue(instance) is T val) ? val : fallback;
			}
			catch
			{
				return fallback;
			}
		}

		internal static bool Set(FieldInfo field, object instance, object value)
		{
			if (field == null)
			{
				return false;
			}
			try
			{
				field.SetValue(instance, value);
				return true;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not write " + field.Name + ": " + ex.GetType().Name);
				return false;
			}
		}

		internal static bool Invoke(MethodInfo method, object instance, params object[] args)
		{
			if (method == null)
			{
				return false;
			}
			try
			{
				method.Invoke(instance, args);
				return true;
			}
			catch (Exception ex)
			{
				Log.Verbose("Could not invoke " + method.Name + ": " + ex.GetType().Name);
				return false;
			}
		}
	}
}
namespace RepoLateJoin.Client
{
	internal static class ClientGuards
	{
		private const float SettleSeconds = 20f;

		private static float _settleUntil;

		private static bool _armed;

		private static readonly HashSet<string> Reported = new HashSet<string>();

		private static readonly (string Type, string Method)[] Targets = new(string, string)[11]
		{
			("EnergyUI", "Update"),
			("PlayerAvatarVisuals", "Update"),
			("PlayerExpression", "Update"),
			("PlayerAvatarTalkAnimation", "Update"),
			("PlayerAvatarOverchargeVisuals", "Update"),
			("PlayerAvatarGrabDisableVisuals", "Update"),
			("PlayerAvatarLeftArm", "Update"),
			("PlayerLocalCamera", "GetOverrideTransform"),
			("PhysGrabber", "Update"),
			("ItemEquippable", "RPC_UpdateItemState"),
			("LoadingUI", "LevelAnimationStart")
		};

		internal static bool Settling
		{
			get
			{
				if (ModConfig.On && _armed)
				{
					return Time.unscaledTime < _settleUntil;
				}
				return false;
			}
		}

		internal static void ArmForLateJoin()
		{
			_armed = true;
			_settleUntil = Time.unscaledTime + 20f;
			Log.Once("guardsarmed", "Late-join client guards armed for " + 20f + "s while our client finishes wiring up.");
		}

		internal static void Disarm()
		{
			_armed = false;
			_settleUntil = 0f;
			Reported.Clear();
		}

		internal static void EvaluateLocalState(PlayerAvatar localAvatar)
		{
			if (!PunGate.Ready || PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom || (Object)(object)localAvatar == (Object)null || _armed)
			{
				return;
			}
			try
			{
				if ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated || (!SemiFunc.RunIsLevel() && !SemiFunc.RunIsShop()))
				{
					return;
				}
			}
			catch
			{
				return;
			}
			ArmForLateJoin();
		}

		internal static int Install(Harmony harmony)
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Expected O, but got Unknown
			MethodInfo methodInfo = Reflect.Method(typeof(ClientGuards), "Swallow");
			if (methodInfo == null)
			{
				Log.Warn("Could not resolve the guard finalizer; client guards will not be installed.");
				return 0;
			}
			int num = 0;
			(string, string)[] targets = Targets;
			for (int i = 0; i < targets.Length; i++)
			{
				(string, string) tuple = targets[i];
				string item = tuple.Item1;
				string item2 = tuple.Item2;
				Type type = Reflect.TypeByName(item);
				if (type == null)
				{
					Log.Verbose("Guard target type " + item + " is absent on this game version.");
					continue;
				}
				MethodInfo methodInfo2 = Reflect.Method(type, item2);
				if (methodInfo2 == null || methodInfo2.IsAbstract)
				{
					Log.Verbose("Guard target " + item + "." + item2 + " is absent; skipping.");
					continue;
				}
				try
				{
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null);
					num++;
				}
				catch (Exception ex)
				{
					Log.Verbose("Could not guard " + item + "." + item2 + ": " + ex.GetType().Name);
				}
			}
			Log.Info("Installed " + num + " of " + Targets.Length + " late-join client guards