Decompiled source of LateRepo Fix v1.1.4

LateRepoFix.dll

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

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

[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("laterepo.fix.runtime", "LateRepo Fix", "1.1.4")]
public sealed class Plugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> DebugLogging;

	internal static ConfigEntry<bool> AllowActiveRoundJoin;

	internal static ConfigEntry<bool> KeepPhotonRoomOpen;

	internal static ConfigEntry<bool> SyncModuleConnectionsForLateJoiners;

	internal static ConfigEntry<bool> SyncItemBatteryStateForLateJoiners;

	internal static ConfigEntry<bool> RepairInventoryBatteryUiForLateJoiners;

	private void Awake()
	{
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enable extra logs for LateRepo Fix.");
		AllowActiveRoundJoin = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "AllowJoiningDuringActiveRound", true, "Keep the Steam lobby joinable for late joins while the host is already in an active run.");
		KeepPhotonRoomOpen = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "KeepPhotonRoomOpenDuringActiveRound", true, "Keep the Photon room open and visible while active round joining is enabled.");
		SyncModuleConnectionsForLateJoiners = ((BaseUnityPlugin)this).Config.Bind<bool>("Level", "SyncModuleConnectionsForLateJoiners", true, "Repair module connection state for late joiners before client block generation and replay the host state after their avatar is ready.");
		SyncItemBatteryStateForLateJoiners = ((BaseUnityPlugin)this).Config.Bind<bool>("Items", "SyncItemBatteryStateForLateJoiners", true, "Send current ItemBattery charge state only to newly joined clients so vanilla battery UI starts with the correct bar count.");
		RepairInventoryBatteryUiForLateJoiners = ((BaseUnityPlugin)this).Config.Bind<bool>("Items", "RepairInventoryBatteryUiForLateJoiners", true, "Repair vanilla inventory battery visuals when a late-joined client has batteryLife but a stale private battery bar counter.");
		new Harmony("laterepo.fix.runtime").PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched LateRepo LevelGenerator hook guard, Steam/Photon access, enemy late-join state, early client module connection repair, targeted module replay, and item battery late-join UI sync.");
	}

	private void Update()
	{
		LateJoinAccess.Update();
		EnemyLateJoinState.Update();
		ModuleConnectionBuffer.Update();
		LateJoinItemBatteryState.Update();
	}
}
internal static class ModLog
{
	private static bool loggedSuppressedException;

	private static bool loggedPreventedLobbyLock;

	private static bool loggedForcedLobbyOpen;

	private static bool loggedPhotonRoomOpen;

	private static bool loggedBufferedModuleConnections;

	private static bool loggedItemBatterySync;

	public static void Info(string message)
	{
		if (Plugin.Log != null)
		{
			Plugin.Log.LogInfo((object)message);
		}
	}

	public static void Debug(string message)
	{
		if (Plugin.DebugLogging != null && Plugin.DebugLogging.Value && Plugin.Log != null)
		{
			Plugin.Log.LogInfo((object)("[Debug] " + message));
		}
	}

	public static void SuppressedSingleplayerPool()
	{
		if (loggedSuppressedException)
		{
			Debug("Suppressed another outdated RunManager.singleplayerPool access.");
			return;
		}
		Info("Suppressed outdated RunManager.singleplayerPool access during LevelGenerator.Start.");
		loggedSuppressedException = true;
	}

	public static void PreventedLobbyLock()
	{
		if (loggedPreventedLobbyLock)
		{
			Debug("Prevented another Steam lobby lock while active round joining is enabled.");
			return;
		}
		Info("Prevented Steam lobby lock while active round joining is enabled.");
		loggedPreventedLobbyLock = true;
	}

	public static void ForcedLobbyOpen(string reason)
	{
		if (loggedForcedLobbyOpen)
		{
			Debug("Kept Steam lobby joinable after " + reason + ".");
			return;
		}
		Info("Kept Steam lobby joinable after " + reason + ".");
		loggedForcedLobbyOpen = true;
	}

	public static void PhotonRoomOpen(string reason)
	{
		if (loggedPhotonRoomOpen)
		{
			Debug("Kept Photon room open after " + reason + ".");
			return;
		}
		Info("Kept Photon room open and visible for active round joining.");
		loggedPhotonRoomOpen = true;
	}

	public static void BufferedModuleConnection(int viewId)
	{
		if (loggedBufferedModuleConnections)
		{
			Debug("Stored module connection state for PhotonView " + viewId + ".");
			return;
		}
		Info("Stored module connection states for active-round late joiners.");
		loggedBufferedModuleConnections = true;
	}

	public static void ItemBatterySync(Player target, int count)
	{
		string text = ((target == null) ? "unknown" : (string.IsNullOrEmpty(target.NickName) ? target.ActorNumber.ToString() : target.NickName));
		if (loggedItemBatterySync)
		{
			Debug("Sent " + count + " item battery state(s) to late joiner " + text + ".");
		}
		else
		{
			Info("Sent item battery charge states to late joiner " + text + ".");
			loggedItemBatterySync = true;
		}
	}
}
internal static class ExceptionFilter
{
	public static bool IsSingleplayerPoolMissing(Exception ex)
	{
		while (ex != null)
		{
			if (ex is MissingFieldException && ex.ToString().IndexOf("RunManager.singleplayerPool", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			ex = ex.InnerException;
		}
		return false;
	}
}
internal static class ModuleConnectionBuffer
{
	private sealed class ModuleConnectionState
	{
		public PhotonView PhotonView;

		public int ViewId;

		public bool Top;

		public bool Bottom;

		public bool Right;

		public bool Left;

		public bool First;
	}

	private sealed class PendingReplay
	{
		public Player Target;

		public float NextTime;

		public int Attempts;
	}

	private const int LocalReplayAttemptsMax = 10;

	private static readonly FieldInfo ModulePhotonViewField = AccessTools.Field(typeof(Module), "photonView");

	private static readonly FieldInfo ModuleConnectingTopField = AccessTools.Field(typeof(Module), "ConnectingTop");

	private static readonly FieldInfo ModuleConnectingBottomField = AccessTools.Field(typeof(Module), "ConnectingBottom");

	private static readonly FieldInfo ModuleConnectingRightField = AccessTools.Field(typeof(Module), "ConnectingRight");

	private static readonly FieldInfo ModuleConnectingLeftField = AccessTools.Field(typeof(Module), "ConnectingLeft");

	private static readonly FieldInfo ModuleSetupDoneField = AccessTools.Field(typeof(Module), "SetupDone");

	private static readonly FieldInfo ModuleFirstField = AccessTools.Field(typeof(Module), "First");

	private static readonly FieldInfo ModuleGridXField = AccessTools.Field(typeof(Module), "GridX");

	private static readonly FieldInfo ModuleGridYField = AccessTools.Field(typeof(Module), "GridY");

	private static readonly FieldInfo ModuleStartRoomField = AccessTools.Field(typeof(Module), "StartRoom");

	private static readonly FieldInfo ModulePropSwitchModuleField = AccessTools.Field(typeof(ModulePropSwitch), "Module");

	private static readonly FieldInfo LevelGeneratorModulesSpawnedField = AccessTools.Field(typeof(LevelGenerator), "ModulesSpawned");

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

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

	private static int localReplayAttempts;

	public static bool TrySendBuffered(Module module, bool top, bool bottom, bool right, bool left, bool first)
	{
		if (Plugin.SyncModuleConnectionsForLateJoiners == null || !Plugin.SyncModuleConnectionsForLateJoiners.Value)
		{
			return false;
		}
		if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || !PhotonNetwork.IsMasterClient)
		{
			return false;
		}
		PhotonView photonView = GetPhotonView(module);
		if ((Object)(object)photonView == (Object)null || photonView.ViewID == 0)
		{
			return false;
		}
		try
		{
			States[photonView.ViewID] = new ModuleConnectionState
			{
				PhotonView = photonView,
				ViewId = photonView.ViewID,
				Top = top,
				Bottom = bottom,
				Right = right,
				Left = left,
				First = first
			};
			ModLog.BufferedModuleConnection(photonView.ViewID);
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not store module connection state: " + ex.GetType().Name);
		}
		return false;
	}

	public static void Queue(PlayerAvatar playerAvatar)
	{
		if (Plugin.SyncModuleConnectionsForLateJoiners != null && Plugin.SyncModuleConnectionsForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && !((Object)(object)playerAvatar == (Object)null) && !((Object)(object)playerAvatar.photonView == (Object)null))
		{
			Queue(playerAvatar.photonView.Owner, 3f);
		}
	}

	public static void ApplyLocalClientConnections(LevelGenerator levelGenerator)
	{
		if (Plugin.SyncModuleConnectionsForLateJoiners == null || !Plugin.SyncModuleConnectionsForLateJoiners.Value || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient || (Object)(object)levelGenerator == (Object)null)
		{
			return;
		}
		Module[] array = FindModules();
		if (array == null || array.Length == 0)
		{
			return;
		}
		Dictionary<string, Module> dictionary = new Dictionary<string, Module>();
		Module[] array2 = array;
		foreach (Module val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !IsStartRoom(val))
			{
				int @int = GetInt(ModuleGridXField, val);
				int int2 = GetInt(ModuleGridYField, val);
				string gridKey = GetGridKey(@int, int2);
				if (!dictionary.ContainsKey(gridKey))
				{
					dictionary.Add(gridKey, val);
				}
			}
		}
		int num = 0;
		array2 = array;
		foreach (Module val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !IsStartRoom(val))
			{
				int @int = GetInt(ModuleGridXField, val);
				int int2 = GetInt(ModuleGridYField, val);
				bool flag = IsFirstModule(val);
				bool top = dictionary.ContainsKey(GetGridKey(@int, int2 + 1));
				bool bottom = dictionary.ContainsKey(GetGridKey(@int, int2 - 1)) || flag;
				bool right = dictionary.ContainsKey(GetGridKey(@int + 1, int2));
				bool left = dictionary.ContainsKey(GetGridKey(@int - 1, int2));
				if (ApplyState(val, top, bottom, right, left, flag, countModuleReady: true))
				{
					num++;
				}
			}
		}
		if (num > 0)
		{
			localReplayAttempts = 0;
			ModLog.Debug("Applied " + num + " local module connection state(s) before client block generation.");
		}
		else if (localReplayAttempts < 10)
		{
			localReplayAttempts++;
			ModLog.Debug("Local module connection prepass found no unapplied modules.");
		}
	}

	public static bool ApplyIncomingRpc(Module module, bool top, bool bottom, bool right, bool left, bool first)
	{
		if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient)
		{
			return true;
		}
		bool @bool = GetBool(ModuleSetupDoneField, module);
		if (@bool && GetBool(ModuleConnectingTopField, module) == top && GetBool(ModuleConnectingBottomField, module) == bottom && GetBool(ModuleConnectingRightField, module) == right && GetBool(ModuleConnectingLeftField, module) == left && GetBool(ModuleFirstField, module) == first)
		{
			return false;
		}
		ApplyState(module, top, bottom, right, left, first, !@bool);
		return false;
	}

	private static void Queue(Player target, float delay)
	{
		if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber)
		{
			return;
		}
		for (int num = Pending.Count - 1; num >= 0; num--)
		{
			if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber)
			{
				Pending.RemoveAt(num);
			}
		}
		Pending.Add(new PendingReplay
		{
			Target = target,
			NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay),
			Attempts = 0
		});
	}

	public static void Update()
	{
		if (Pending.Count == 0)
		{
			return;
		}
		for (int num = Pending.Count - 1; num >= 0; num--)
		{
			PendingReplay pendingReplay = Pending[num];
			if (pendingReplay != null && pendingReplay.Target != null && !(Time.unscaledTime < pendingReplay.NextTime))
			{
				int num2 = SendCurrentState(pendingReplay.Target);
				pendingReplay.Attempts++;
				if (pendingReplay.Attempts >= 16)
				{
					Pending.RemoveAt(num);
					ModLog.Debug("Sent " + num2 + " module connection state(s) to late joiner.");
				}
				else
				{
					pendingReplay.NextTime = Time.unscaledTime + 5f;
				}
			}
		}
	}

	private static int SendCurrentState(Player target)
	{
		if (States.Count == 0)
		{
			return 0;
		}
		int num = 0;
		foreach (ModuleConnectionState value in States.Values)
		{
			if (value == null)
			{
				continue;
			}
			PhotonView val = (((Object)(object)value.PhotonView != (Object)null) ? value.PhotonView : PhotonNetwork.GetPhotonView(value.ViewId));
			if (!((Object)(object)val == (Object)null) && val.ViewID != 0)
			{
				try
				{
					val.RPC("ModuleConnectionSetRPC", target, new object[5] { value.Top, value.Bottom, value.Right, value.Left, value.First });
					num++;
				}
				catch (Exception ex)
				{
					ModLog.Debug("Could not replay module connection state for PhotonView " + value.ViewId + ": " + ex.GetType().Name);
				}
			}
		}
		return num;
	}

	private static PhotonView GetPhotonView(Module module)
	{
		PhotonView val = (PhotonView)((ModulePhotonViewField == null) ? null : /*isinst with value type is only supported in some contexts*/);
		return ((Object)(object)val != (Object)null) ? val : ((Component)module).GetComponent<PhotonView>();
	}

	private static bool ApplyState(Module module, bool top, bool bottom, bool right, bool left, bool first, bool countModuleReady)
	{
		if ((Object)(object)module == (Object)null)
		{
			return false;
		}
		bool @bool = GetBool(ModuleSetupDoneField, module);
		SetBool(ModuleConnectingTopField, module, top);
		SetBool(ModuleConnectingBottomField, module, bottom);
		SetBool(ModuleConnectingRightField, module, right);
		SetBool(ModuleConnectingLeftField, module, left);
		SetBool(ModuleFirstField, module, first);
		SetBool(ModuleSetupDoneField, module, value: true);
		try
		{
			ModulePropSwitch[] componentsInChildren = ((Component)module).GetComponentsInChildren<ModulePropSwitch>(true);
			ModulePropSwitch[] array = componentsInChildren;
			foreach (ModulePropSwitch val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if (ModulePropSwitchModuleField != null)
					{
						ModulePropSwitchModuleField.SetValue(val, module);
					}
					val.Setup();
				}
			}
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not setup module prop switches for " + ((Object)module).name + ": " + ex.GetType().Name);
		}
		if (countModuleReady && !@bool && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGeneratorModulesSpawnedField != null)
		{
			int @int = GetInt(LevelGeneratorModulesSpawnedField, LevelGenerator.Instance);
			LevelGeneratorModulesSpawnedField.SetValue(LevelGenerator.Instance, @int + 1);
		}
		return !@bool;
	}

	private static bool IsFirstModule(Module module)
	{
		return (Object)(object)module != (Object)null && (GetBool(ModuleFirstField, module) || IsStartRoom(module) || (Object)(object)((Component)module).GetComponent<StartRoom>() != (Object)null || (Object)(object)((Component)module).GetComponentInChildren<StartRoom>(true) != (Object)null);
	}

	private static bool IsStartRoom(Module module)
	{
		return (Object)(object)module != (Object)null && GetBool(ModuleStartRoomField, module);
	}

	private static Module[] FindModules()
	{
		try
		{
			return Object.FindObjectsOfType<Module>(true);
		}
		catch
		{
			return Object.FindObjectsOfType<Module>();
		}
	}

	private static string GetGridKey(int x, int y)
	{
		return x + ":" + y;
	}

	private static bool GetBool(FieldInfo field, object instance)
	{
		if (field == null || instance == null)
		{
			return false;
		}
		object value = field.GetValue(instance);
		return value is bool && (bool)value;
	}

	private static void SetBool(FieldInfo field, object instance, bool value)
	{
		if (field != null && instance != null)
		{
			field.SetValue(instance, value);
		}
	}

	private static int GetInt(FieldInfo field, object instance)
	{
		if (field == null || instance == null)
		{
			return 0;
		}
		object value = field.GetValue(instance);
		return (value is int) ? ((int)value) : 0;
	}
}
[HarmonyPatch(typeof(LevelGenerator), "Start")]
internal static class LevelGeneratorStartPatch
{
	private static void Postfix()
	{
		LateJoinAccess.TryUnlock("LevelGenerator.Start");
	}

	private static Exception Finalizer(Exception __exception)
	{
		if (__exception != null && ExceptionFilter.IsSingleplayerPoolMissing(__exception))
		{
			ModLog.SuppressedSingleplayerPool();
			return null;
		}
		return __exception;
	}
}
[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
internal static class LevelGeneratorGenerateDonePatch
{
	private static void Postfix()
	{
		LateJoinItemBatteryState.QueueAllRemote(2f);
	}
}
[HarmonyPatch(typeof(LevelGenerator), "ItemSetup")]
internal static class LevelGeneratorItemSetupPatch
{
	private static void Postfix()
	{
		LateJoinItemBatteryState.QueueAllRemote(2f);
	}
}
[HarmonyPatch(typeof(LevelGenerator), "GenerateConnectObjects")]
internal static class LevelGeneratorGenerateConnectObjectsPatch
{
	private static void Prefix(LevelGenerator __instance)
	{
		ModuleConnectionBuffer.ApplyLocalClientConnections(__instance);
	}
}
[HarmonyPatch(typeof(LevelGenerator), "GenerateBlockObjects")]
internal static class LevelGeneratorGenerateBlockObjectsPatch
{
	private static void Prefix(LevelGenerator __instance)
	{
		ModuleConnectionBuffer.ApplyLocalClientConnections(__instance);
	}
}
internal static class LateJoinAccess
{
	private static readonly FieldInfo PrivateLobbyField = AccessTools.Field(typeof(SteamManager), "privateLobby");

	private static float nextAccessRefreshTime;

	public static bool ShouldKeepJoinable()
	{
		return Plugin.AllowActiveRoundJoin != null && Plugin.AllowActiveRoundJoin.Value && PhotonNetwork.IsMasterClient;
	}

	public static void Update()
	{
		if (!(Time.unscaledTime < nextAccessRefreshTime))
		{
			nextAccessRefreshTime = Time.unscaledTime + 2f;
			RefreshPhotonRoom("periodic host access refresh");
		}
	}

	public static bool PreventLock(SteamManager manager)
	{
		if (!ShouldKeepJoinable() || (Object)(object)manager == (Object)null)
		{
			return true;
		}
		Unlock(manager, "SteamManager.LockLobby");
		OpenPhotonRoom("SteamManager.LockLobby");
		ModLog.PreventedLobbyLock();
		return false;
	}

	public static void TryUnlock(string reason)
	{
		if (ShouldKeepJoinable() && !((Object)(object)SteamManager.instance == (Object)null))
		{
			Unlock(SteamManager.instance, reason);
			OpenPhotonRoom(reason);
			ModLog.ForcedLobbyOpen(reason);
		}
	}

	private static void RefreshPhotonRoom(string reason)
	{
		if (ShouldKeepJoinable())
		{
			OpenPhotonRoom(reason);
		}
	}

	private static void Unlock(SteamManager manager, string reason)
	{
		try
		{
			bool flag = !IsPrivateLobby(manager);
			manager.UnlockLobby(flag);
			ModLog.Debug("Unlocked Steam lobby from " + reason + " with open=" + flag + ".");
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not unlock Steam lobby from " + reason + ": " + ex.GetType().Name);
		}
	}

	private static bool IsPrivateLobby(SteamManager manager)
	{
		if (PrivateLobbyField == null || (Object)(object)manager == (Object)null)
		{
			return false;
		}
		return (bool)PrivateLobbyField.GetValue(manager);
	}

	private static void OpenPhotonRoom(string reason)
	{
		if (Plugin.KeepPhotonRoomOpen == null || !Plugin.KeepPhotonRoomOpen.Value || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
		{
			return;
		}
		try
		{
			bool flag = false;
			if (!PhotonNetwork.CurrentRoom.IsOpen)
			{
				PhotonNetwork.CurrentRoom.IsOpen = true;
				flag = true;
			}
			if (!PhotonNetwork.CurrentRoom.IsVisible)
			{
				PhotonNetwork.CurrentRoom.IsVisible = true;
				flag = true;
			}
			if (flag)
			{
				ModLog.PhotonRoomOpen(reason);
			}
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not keep Photon room open after " + reason + ": " + ex.GetType().Name);
		}
	}
}
internal static class EnemyLateJoinState
{
	private static readonly Type EnemyOnScreenCoroutineType = AccessTools.TypeByName("EnemyOnScreen+<Logic>d__19");

	private static readonly Type EnemyVisionCoroutineType = AccessTools.TypeByName("EnemyVision+<Vision>d__34");

	private static readonly FieldInfo EnemyOnScreenCoroutineOwner = ((EnemyOnScreenCoroutineType == null) ? null : AccessTools.Field(EnemyOnScreenCoroutineType, "<>4__this"));

	private static readonly FieldInfo EnemyVisionCoroutineOwner = ((EnemyVisionCoroutineType == null) ? null : AccessTools.Field(EnemyVisionCoroutineType, "<>4__this"));

	private static float nextSyncTime;

	private static int lastPlayerSignature;

	public static void Update()
	{
		if (!(Time.unscaledTime < nextSyncTime))
		{
			nextSyncTime = Time.unscaledTime + 1f;
			int playerSignature = GetPlayerSignature();
			if (playerSignature != 0 && playerSignature != lastPlayerSignature)
			{
				lastPlayerSignature = playerSignature;
				SyncAll();
			}
		}
	}

	public static void SyncAll()
	{
		List<int> playerViewIds = GetPlayerViewIds();
		if (playerViewIds.Count != 0)
		{
			EnemyOnScreen[] array = Object.FindObjectsOfType<EnemyOnScreen>();
			foreach (EnemyOnScreen enemyOnScreen in array)
			{
				Sync(enemyOnScreen, playerViewIds);
			}
			EnemyVision[] array2 = Object.FindObjectsOfType<EnemyVision>();
			foreach (EnemyVision enemyVision in array2)
			{
				Sync(enemyVision, playerViewIds);
			}
		}
	}

	public static void Sync(EnemyOnScreen enemyOnScreen)
	{
		if (!((Object)(object)enemyOnScreen == (Object)null))
		{
			Sync(enemyOnScreen, GetPlayerViewIds());
		}
	}

	public static void Sync(EnemyVision enemyVision)
	{
		if (!((Object)(object)enemyVision == (Object)null))
		{
			Sync(enemyVision, GetPlayerViewIds());
		}
	}

	public static void SyncEnemyOnScreenCoroutine(object stateMachine)
	{
		EnemyOnScreen enemyOnScreen = (EnemyOnScreen)((EnemyOnScreenCoroutineOwner == null) ? null : /*isinst with value type is only supported in some contexts*/);
		Sync(enemyOnScreen);
	}

	public static void SyncEnemyVisionCoroutine(object stateMachine)
	{
		EnemyVision enemyVision = (EnemyVision)((EnemyVisionCoroutineOwner == null) ? null : /*isinst with value type is only supported in some contexts*/);
		Sync(enemyVision);
	}

	private static void Sync(EnemyOnScreen enemyOnScreen, List<int> playerIds)
	{
		if ((Object)(object)enemyOnScreen == (Object)null || playerIds.Count == 0)
		{
			return;
		}
		try
		{
			foreach (int playerId in playerIds)
			{
				enemyOnScreen.PlayerAdded(playerId);
			}
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not sync EnemyOnScreen late-join state: " + ex.GetType().Name);
		}
	}

	private static void Sync(EnemyVision enemyVision, List<int> playerIds)
	{
		if ((Object)(object)enemyVision == (Object)null || playerIds.Count == 0)
		{
			return;
		}
		try
		{
			foreach (int playerId in playerIds)
			{
				enemyVision.PlayerAdded(playerId);
			}
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not sync EnemyVision late-join state: " + ex.GetType().Name);
		}
	}

	private static int GetPlayerSignature()
	{
		List<int> playerViewIds = GetPlayerViewIds();
		if (playerViewIds.Count == 0)
		{
			return 0;
		}
		int num = 17;
		foreach (int item in playerViewIds)
		{
			num = num * 31 + item;
		}
		return num;
	}

	private static List<int> GetPlayerViewIds()
	{
		List<int> list = new List<int>();
		if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
		{
			return list;
		}
		foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null) && player.photonView.ViewID != 0)
			{
				list.Add(player.photonView.ViewID);
			}
		}
		return list;
	}
}
internal static class LateJoinItemBatteryState
{
	private sealed class PendingSync
	{
		public Player Target;

		public float NextTime;

		public int Attempts;
	}

	private static readonly FieldInfo BatteryPhotonViewField = AccessTools.Field(typeof(ItemBattery), "photonView");

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

	private static readonly FieldInfo BatteryCurrentBarsField = AccessTools.Field(typeof(ItemBattery), "currentBars");

	private static readonly FieldInfo InventorySpotBatteryVisualLogicField = AccessTools.Field(typeof(InventorySpot), "batteryVisualLogic");

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

	public static void Queue(PlayerAvatar playerAvatar)
	{
		if (Plugin.SyncItemBatteryStateForLateJoiners != null && Plugin.SyncItemBatteryStateForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && !((Object)(object)playerAvatar == (Object)null) && !((Object)(object)playerAvatar.photonView == (Object)null))
		{
			Player owner = playerAvatar.photonView.Owner;
			if (owner != null && PhotonNetwork.LocalPlayer != null && owner.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber)
			{
				Queue(owner, 2f);
			}
		}
	}

	public static void QueueAllRemote(float delay)
	{
		if (Plugin.SyncItemBatteryStateForLateJoiners != null && Plugin.SyncItemBatteryStateForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null)
		{
			Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
			foreach (Player target in playerListOthers)
			{
				Queue(target, delay);
			}
		}
	}

	private static void Queue(Player target, float delay)
	{
		if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber)
		{
			return;
		}
		for (int num = Pending.Count - 1; num >= 0; num--)
		{
			if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber)
			{
				Pending.RemoveAt(num);
			}
		}
		Pending.Add(new PendingSync
		{
			Target = target,
			NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay),
			Attempts = 0
		});
	}

	public static void Update()
	{
		if (Pending.Count == 0)
		{
			return;
		}
		for (int num = Pending.Count - 1; num >= 0; num--)
		{
			PendingSync pendingSync = Pending[num];
			if (pendingSync != null && pendingSync.Target != null && !(Time.unscaledTime < pendingSync.NextTime))
			{
				int count = SendCurrentState(pendingSync.Target);
				pendingSync.Attempts++;
				if (pendingSync.Attempts >= 12)
				{
					Pending.RemoveAt(num);
					ModLog.ItemBatterySync(pendingSync.Target, count);
				}
				else
				{
					pendingSync.NextTime = Time.unscaledTime + 5f;
				}
			}
		}
	}

	private static int SendCurrentState(Player target)
	{
		int num = 0;
		ItemBattery[] array = FindBatteries();
		ItemBattery[] array2 = array;
		foreach (ItemBattery val in array2)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			PhotonView photonView = GetPhotonView(val);
			if (!((Object)(object)photonView == (Object)null) && photonView.ViewID != 0 && CanReceiveBatteryRpc(photonView, val))
			{
				try
				{
					int batteryLevel = GetBatteryLevel(val);
					bool batteryActive = val.batteryActive;
					photonView.RPC("BatteryToggleRPC", target, new object[1] { false });
					photonView.RPC("BatteryFullPercentChangeRPC", target, new object[2] { batteryLevel, false });
					photonView.RPC("BatteryToggleRPC", target, new object[1] { batteryActive });
					num++;
				}
				catch (Exception ex)
				{
					ModLog.Debug("Could not sync ItemBattery state for " + ((Object)val).name + ": " + ex.GetType().Name);
				}
			}
		}
		return num;
	}

	private static bool CanReceiveBatteryRpc(PhotonView photonView, ItemBattery battery)
	{
		if ((Object)(object)photonView == (Object)null || (Object)(object)battery == (Object)null)
		{
			return false;
		}
		ItemBattery component = ((Component)photonView).GetComponent<ItemBattery>();
		return (Object)(object)component != (Object)null && object.ReferenceEquals(component, battery);
	}

	public static void RepairInventorySpot(InventorySpot spot)
	{
		if (!ShouldRepairInventoryBatteryUi() || (Object)(object)spot == (Object)null || !spot.IsOccupied())
		{
			return;
		}
		ItemBattery val = FindBattery((Component)(object)spot.CurrentItem);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		int num = RepairBatteryCounters(val);
		BatteryVisualLogic val2 = (BatteryVisualLogic)((InventorySpotBatteryVisualLogicField == null) ? null : /*isinst with value type is only supported in some contexts*/);
		if ((Object)(object)val2 == (Object)null)
		{
			return;
		}
		try
		{
			if (!((Component)val2).gameObject.activeSelf)
			{
				((Component)val2).gameObject.SetActive(true);
			}
			if ((Object)(object)val2.itemBattery != (Object)(object)val)
			{
				val2.itemBattery = val;
				val2.BatteryBarsSet();
			}
			else if (val2.batteryBars != val.batteryBars)
			{
				val2.BatteryBarsSet();
			}
			val2.BatteryBarsUpdate(num, true);
		}
		catch (Exception ex)
		{
			ModLog.Debug("Could not repair inventory battery UI for " + ((Object)val).name + ": " + ex.GetType().Name);
		}
	}

	public static void RepairInventoryBatteryResult(Inventory inventory, int index, ref int result)
	{
		if (!ShouldRepairInventoryBatteryUi() || (Object)(object)inventory == (Object)null)
		{
			return;
		}
		InventorySpot spotByIndex = inventory.GetSpotByIndex(index);
		if ((Object)(object)spotByIndex == (Object)null || !spotByIndex.IsOccupied())
		{
			return;
		}
		ItemBattery val = FindBattery((Component)(object)spotByIndex.CurrentItem);
		if (!((Object)(object)val == (Object)null))
		{
			int num = RepairBatteryCounters(val);
			if (result < 0 || (result == 0 && val.batteryLife > 0f) || result > Mathf.Max(1, val.batteryBars))
			{
				result = num;
			}
		}
	}

	private static bool ShouldRepairInventoryBatteryUi()
	{
		return Plugin.RepairInventoryBatteryUiForLateJoiners != null && Plugin.RepairInventoryBatteryUiForLateJoiners.Value;
	}

	private static int RepairBatteryCounters(ItemBattery battery)
	{
		int batteryLevel = GetBatteryLevel(battery);
		SetIntField(BatteryLifeIntField, battery, batteryLevel);
		SetIntField(BatteryCurrentBarsField, battery, batteryLevel);
		return batteryLevel;
	}

	private static ItemBattery FindBattery(Component component)
	{
		if ((Object)(object)component == (Object)null)
		{
			return null;
		}
		ItemBattery component2 = component.GetComponent<ItemBattery>();
		if ((Object)(object)component2 != (Object)null)
		{
			return component2;
		}
		component2 = component.GetComponentInChildren<ItemBattery>(true);
		if ((Object)(object)component2 != (Object)null)
		{
			return component2;
		}
		return component.GetComponentInParent<ItemBattery>(true);
	}

	private static ItemBattery[] FindBatteries()
	{
		try
		{
			return Object.FindObjectsOfType<ItemBattery>(true);
		}
		catch
		{
			return Object.FindObjectsOfType<ItemBattery>();
		}
	}

	private static PhotonView GetPhotonView(ItemBattery battery)
	{
		PhotonView val = (PhotonView)((BatteryPhotonViewField == null) ? null : /*isinst with value type is only supported in some contexts*/);
		return ((Object)(object)val != (Object)null) ? val : ((Component)battery).GetComponent<PhotonView>();
	}

	private static int GetBatteryLevel(ItemBattery battery)
	{
		int num = Mathf.Max(1, battery.batteryBars);
		int num2 = 0;
		if (BatteryLifeIntField != null)
		{
			object value = BatteryLifeIntField.GetValue(battery);
			if (value is int)
			{
				num2 = (int)value;
			}
		}
		if (num2 < 0 || num2 > num || (num2 == 0 && battery.batteryLife > 0f))
		{
			num2 = Mathf.RoundToInt(battery.batteryLife / (100f / (float)num));
		}
		return Mathf.Clamp(num2, 0, num);
	}

	private static void SetIntField(FieldInfo field, ItemBattery battery, int value)
	{
		if (field != null && (Object)(object)battery != (Object)null)
		{
			field.SetValue(battery, value);
		}
	}
}
[HarmonyPatch(typeof(SteamManager), "LockLobby")]
internal static class SteamManagerLockLobbyPatch
{
	private static bool Prefix(SteamManager __instance)
	{
		return LateJoinAccess.PreventLock(__instance);
	}
}
[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
internal static class RunManagerChangeLevelPatch
{
	private static void Postfix()
	{
		LateJoinAccess.TryUnlock("RunManager.ChangeLevel");
	}
}
[HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")]
internal static class MenuPageLobbyButtonStartPatch
{
	private static void Postfix()
	{
		LateJoinAccess.TryUnlock("MenuPageLobby.ButtonStart");
	}
}
[HarmonyPatch(typeof(EnemyOnScreen), "OnEnable")]
internal static class EnemyOnScreenOnEnablePatch
{
	private static void Prefix(EnemyOnScreen __instance)
	{
		EnemyLateJoinState.Sync(__instance);
	}
}
[HarmonyPatch(typeof(EnemyVision), "OnEnable")]
internal static class EnemyVisionOnEnablePatch
{
	private static void Prefix(EnemyVision __instance)
	{
		EnemyLateJoinState.Sync(__instance);
	}
}
[HarmonyPatch(typeof(PlayerAvatar), "Start")]
internal static class PlayerAvatarStartPatch
{
	private static void Postfix(PlayerAvatar __instance)
	{
		EnemyLateJoinState.SyncAll();
		ModuleConnectionBuffer.Queue(__instance);
		LateJoinItemBatteryState.Queue(__instance);
	}
}
[HarmonyPatch(typeof(InventorySpot), "StateOccupied")]
internal static class InventorySpotStateOccupiedPatch
{
	private static void Postfix(InventorySpot __instance)
	{
		LateJoinItemBatteryState.RepairInventorySpot(__instance);
	}
}
[HarmonyPatch(typeof(Inventory), "GetBatteryStateFromInventorySpot")]
internal static class InventoryGetBatteryStateFromInventorySpotPatch
{
	private static void Postfix(Inventory __instance, int index, ref int __result)
	{
		LateJoinItemBatteryState.RepairInventoryBatteryResult(__instance, index, ref __result);
	}
}
[HarmonyPatch(typeof(Module), "ModuleConnectionSet")]
internal static class ModuleConnectionSetPatch
{
	private static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first)
	{
		return !ModuleConnectionBuffer.TrySendBuffered(__instance, _top, _bottom, _right, _left, _first);
	}
}
[HarmonyPatch(typeof(Module), "ModuleConnectionSetRPC")]
internal static class ModuleConnectionSetRpcPatch
{
	private static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first)
	{
		return ModuleConnectionBuffer.ApplyIncomingRpc(__instance, _top, _bottom, _right, _left, _first);
	}
}
[HarmonyPatch]
internal static class EnemyOnScreenLogicMoveNextPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("EnemyOnScreen+<Logic>d__19");
		return (type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
	}

	private static void Prefix(object __instance)
	{
		EnemyLateJoinState.SyncEnemyOnScreenCoroutine(__instance);
	}
}
[HarmonyPatch]
internal static class EnemyVisionMoveNextPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("EnemyVision+<Vision>d__34");
		return (type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
	}

	private static void Prefix(object __instance)
	{
		EnemyLateJoinState.SyncEnemyVisionCoroutine(__instance);
	}
}
[HarmonyPatch]
internal static class LateRepoLevelGeneratorHookPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("LateRepo.Patches.LateJoinPatch");
		if (type == null)
		{
			ModLog.Info("LateRepo hook type was not found.");
			return null;
		}
		return AccessTools.Method(type, "LevelGeneratorHook", (Type[])null, (Type[])null);
	}

	private static bool Prefix(Action<LevelGenerator> __0, LevelGenerator __1)
	{
		try
		{
			ClearLobbyBufferedRpcs(__1);
			ModLog.Debug("Calling original LevelGenerator.Start through LateRepo hook.");
			__0(__1);
		}
		catch (Exception ex)
		{
			if (ExceptionFilter.IsSingleplayerPoolMissing(ex))
			{
				ModLog.SuppressedSingleplayerPool();
				return false;
			}
			throw;
		}
		return false;
	}

	private static void ClearLobbyBufferedRpcs(LevelGenerator levelGenerator)
	{
		if (PhotonNetwork.IsMasterClient && SemiFunc.RunIsLobby() && !((Object)(object)levelGenerator == (Object)null) && !((Object)(object)levelGenerator.PhotonView == (Object)null))
		{
			PhotonNetwork.RemoveBufferedRPCs(levelGenerator.PhotonView.ViewID, (string)null, (int[])null);
		}
	}
}