Decompiled source of MoreCrew v0.10.9

plugins/CrewSizeMod.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using Steamworks;
using UnityEngine;
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("CrewSizeMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.10.8.0")]
[assembly: AssemblyInformationalVersion("0.10.8")]
[assembly: AssemblyProduct("CrewSizeMod")]
[assembly: AssemblyTitle("CrewSizeMod")]
[assembly: AssemblyVersion("0.10.8.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 CrewSizeMod
{
	[BepInPlugin("com.hunter.crewsizemod", "Crew Size Mod", "0.10.9")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.hunter.crewsizemod";

		public const string Name = "Crew Size Mod";

		public const string Version = "0.10.9";

		internal static ManualLogSource Log;

		internal static ConfigEntry<int> MaxPlayers;

		internal static ConfigEntry<int> TestSlotLimitOverride;

		internal static ConfigEntry<bool> EnableModPresenceCheck;

		internal static ConfigEntry<bool> ExperimentalExpandPlayerUI;

		internal static ConfigEntry<int> ExperimentalExpandPreviewCount;

		internal static ConfigEntry<bool> PlaceOverflowInLobbyRow;

		internal static ConfigEntry<bool> ExperimentalCloneLobbyParkingSpots;

		internal static ConfigEntry<int> ExperimentalLobbyPreviewCount;

		internal static ConfigEntry<string> LobbyManualSpots;

		internal static ConfigEntry<bool> ShowTwitchCredit;

		internal static ConfigEntry<ScreenCorner> TwitchCreditCorner;

		internal static ConfigEntry<float> CreditWidth;

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Expected O, but got Unknown
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			MaxPlayers = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxPlayers", 8, new ConfigDescription("Total players allowed in a session. EVERY player must run this mod with the SAME value; the HOST's value is the one that actually sizes the session. The game's authored ceiling is ~6-8; higher values are accepted but get rougher (netcode/physics strain, UI authored for 4, and the lobby parking spots only extrapolate cleanly to ~8).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 12), Array.Empty<object>()));
			TestSlotLimitOverride = ((BaseUnityPlugin)this).Config.Bind<int>("Testing", "TestSlotLimitOverride", 0, new ConfigDescription("TEST ONLY. If > 0, pretend the lobby has only this many slots, so overflow skipping triggers earlier. Set to 1 to reproduce the overflow path with just 2 machines (player 2 becomes the 'overflow' player). Set to 0 for normal play (uses the real slot count).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
			EnableModPresenceCheck = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableModPresenceCheck", true, "Advertise this machine as modded via Steam lobby member data, and log which lobby members are running vanilla (their voice/UI may glitch at 5+ players, since those fixes run client-side). Purely informational - no gameplay effect. Also the seam for any future enhancements that require everyone to have the mod.");
			ExperimentalExpandPlayerUI = ((BaseUnityPlugin)this).Config.Bind<bool>("Experimental", "ExpandPlayerUI", false, "EXPERIMENTAL (default OFF). When ON, the ready-up proceed icons are EXPANDED to show ALL players by cloning the game's 4 authored icon slots at runtime, instead of capping the display at 4. Purely local/cosmetic per machine (no networking). Leave OFF for the stable behaviour; turn ON only to test the 'show all 8' layout. If the cloned icons look misaligned, that's the authored layout not auto-arranging - report how it looks and it can be positioned manually.");
			ExperimentalExpandPreviewCount = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "ExpandPreviewCount", 0, new ConfigDescription("TEST AID for ExpandPlayerUI. If > 0 (and ExpandPlayerUI is ON), the proceed-icon row is padded to this many icons by duplicating a player, so you can see the N-icon layout SOLO without needing N real players. e.g. set to 8 to preview the 8-player row with just 1-2 machines. 0 = off (only real players show).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
			LobbyManualSpots = ((BaseUnityPlugin)this).Config.Bind<string>("General", "LobbyManualSpots", "", "Manually position EVERY lobby parking spot (the real ones AND the overflow ones), as semicolon-separated 'x,z' world coordinates, one per player index starting at 0. The host log prints a ready-to-paste line ('Lobby spot positions ...') with the current layout - copy it here, tweak the numbers, relaunch, repeat. An empty entry (e.g. two semicolons in a row) leaves that spot at its default. Leave the whole value blank to use the game's real spots + auto-placed overflow. Example: '-10.8,14.9; -10.8,18; -17,12.6; -17,15.9; -4,17; -4,20; 2,19; 2,22'");
			PlaceOverflowInLobbyRow = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PlaceOverflowInLobbyRow", true, "When ON (default), overflow players' lobby bodies are positioned at extrapolated parking spots in line with the 4 real ones, instead of spawning at the prefab's default position. Cosmetic only (these bodies are destroyed before the run). Turn OFF if the extrapolated placement looks wrong in your lobby.");
			ExperimentalCloneLobbyParkingSpots = ((BaseUnityPlugin)this).Config.Bind<bool>("Experimental", "CloneLobbyParkingSpots", false, "EXPERIMENTAL / PATH B (default OFF). When ON, overflow players get a CLONED real LobbyPlayer parking spot (with the assigned animation) instead of a forklift stand-in. Requires EVERY player to have the mod (it registers a Mirror spawn handler so the cloned spot replicates to clients). This is the fragile path - if a clone fails it falls back to the forklift body automatically. Leave OFF unless you're testing it.");
			ExperimentalLobbyPreviewCount = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "LobbyPreviewCount", 0, new ConfigDescription("SOLO TEST AID. If > 0, spawn this many FAKE forklifts at the non-host parking spots (index 1, 2, 3, ...) the moment the lobby loads - so you can lay out the whole lobby ALONE. Set to 7 to see a full 8-player lobby (you at spot 0 + 7 fakes). Each fake sits at its spot's LobbyManualSpots override or the default. Uses forklifts, or cloned spots if CloneLobbyParkingSpots is ON. Fakes are unowned and destroyed at run start. 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
			Harmony val = new Harmony("com.hunter.crewsizemod");
			val.PatchAll();
			Log.LogInfo((object)string.Format("{0} {1} loaded. Target max players = {2}", "Crew Size Mod", "0.10.9", MaxPlayers.Value));
			if (TestSlotLimitOverride.Value > 0)
			{
				Log.LogWarning((object)($"TestSlotLimitOverride = {TestSlotLimitOverride.Value} - TEST MODE is ON " + "(forces the overflow path early). Set it to 0 in the config for normal play."));
			}
			if (EnableModPresenceCheck.Value)
			{
				ModPresence.Init();
			}
			ShowTwitchCredit = ((BaseUnityPlugin)this).Config.Bind<bool>("Credit", "ShowTwitchCredit", true, "Show a small 'MoreCrew by Izaya_here / Follow on Twitch' credit in the corner during the lobby and break room (hidden during the actual shift). Default ON.");
			TwitchCreditCorner = ((BaseUnityPlugin)this).Config.Bind<ScreenCorner>("Credit", "Corner", ScreenCorner.TopLeft, "Which screen corner the credit sits in. Move it if it overlaps the game's UI.");
			CreditWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Credit", "ImageWidth", 320f, new ConfigDescription("On-screen width (pixels) of the credit image (credit.png, shipped next to the mod DLL). Height auto-scales to the image's aspect ratio. Lower it to make the logo more subtle.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(120f, 900f), Array.Empty<object>()));
			Log.LogInfo((object)($"Twitch credit enabled (ShowTwitchCredit={ShowTwitchCredit.Value}, " + $"corner={TwitchCreditCorner.Value}); rendered via uGUI canvas."));
			foreach (MethodBase patchedMethod in val.GetPatchedMethods())
			{
				Log.LogInfo((object)("  [patched] " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name));
			}
			Type type = AccessTools.TypeByName("LobbyManager");
			Log.LogInfo((object)("  [resolve] LobbyManager type = " + ((type != null) ? "FOUND" : "NULL") + ", ServerAddPlayer = " + ((AccessTools.Method(type, "ServerAddPlayer", (Type[])null, (Type[])null) != null) ? "FOUND" : "NULL")));
		}
	}
	[HarmonyPatch(typeof(NetworkServer), "Listen")]
	internal static class Patch_NetworkServerListen
	{
		private static void Prefix(ref int maxConns)
		{
			int value = Plugin.MaxPlayers.Value;
			Plugin.Log.LogInfo((object)$"NetworkServer.Listen maxConns {maxConns} -> {value}");
			maxConns = value;
		}
	}
	[HarmonyPatch(typeof(NetworkManager), "Awake")]
	internal static class Patch_NetworkManagerMaxConnections
	{
		private static void Postfix(NetworkManager __instance)
		{
			__instance.maxConnections = Plugin.MaxPlayers.Value;
			Plugin.Log.LogInfo((object)$"NetworkManager.maxConnections -> {__instance.maxConnections}");
		}
	}
	[HarmonyPatch(typeof(SteamMatchmaking), "CreateLobby")]
	internal static class Patch_CreateLobby
	{
		private static void Prefix(ref int cMaxMembers)
		{
			Plugin.Log.LogInfo((object)$"SteamMatchmaking.CreateLobby cMaxMembers {cMaxMembers} -> {Plugin.MaxPlayers.Value}");
			cMaxMembers = Plugin.MaxPlayers.Value;
		}
	}
	[HarmonyPatch(typeof(SteamMatchmaking), "SetLobbyMemberLimit")]
	internal static class Patch_SetLobbyMemberLimit
	{
		private static void Prefix(ref int cMaxMembers)
		{
			Plugin.Log.LogInfo((object)$"SteamMatchmaking.SetLobbyMemberLimit cMaxMembers {cMaxMembers} -> {Plugin.MaxPlayers.Value}");
			cMaxMembers = Plugin.MaxPlayers.Value;
		}
	}
	internal static class Recon
	{
		public static void DumpType(string typeName)
		{
			Type type = AccessTools.TypeByName(typeName);
			if (type == null)
			{
				Plugin.Log.LogWarning((object)("Type not found: " + typeName));
				return;
			}
			Plugin.Log.LogInfo((object)("--- " + type.FullName + " ---"));
			FieldInfo[] fields = type.GetFields(AccessTools.all);
			foreach (FieldInfo fieldInfo in fields)
			{
				Plugin.Log.LogInfo((object)("  field  " + fieldInfo.FieldType.Name + " " + fieldInfo.Name));
			}
			MethodInfo[] methods = type.GetMethods(AccessTools.all);
			foreach (MethodInfo methodInfo in methods)
			{
				string[] value = Array.ConvertAll(methodInfo.GetParameters(), (ParameterInfo p) => p.ParameterType.Name);
				Plugin.Log.LogInfo((object)("  method " + methodInfo.Name + "(" + string.Join(", ", value) + ")"));
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_InviteButtonAlwaysOn
	{
		private static readonly Dictionary<Type, FieldInfo> _btnFields = new Dictionary<Type, FieldInfo>();

		private static PropertyInfo _interactableProp;

		private static bool _warned;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			string[] array = new string[2] { "LobbyButtonsUI", "GameMenuUI" };
			for (int i = 0; i < array.Length; i++)
			{
				Type type = AccessTools.TypeByName(array[i]);
				MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null) : null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Postfix(object __instance)
		{
			try
			{
				Type type = __instance.GetType();
				if (!_btnFields.TryGetValue(type, out var value))
				{
					value = AccessTools.Field(type, "inviteFriendsButton");
					_btnFields[type] = value;
				}
				object obj = value?.GetValue(__instance);
				if (obj != null)
				{
					if (_interactableProp == null)
					{
						_interactableProp = AccessTools.Property(obj.GetType(), "interactable");
					}
					_interactableProp?.SetValue(obj, true);
				}
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)("invite-button postfix: " + ex.Message + " (logged once)"));
				}
			}
		}
	}
	internal static class OverflowTracker
	{
		public static readonly HashSet<int> Conns = new HashSet<int>();

		public static readonly List<GameObject> Bodies = new List<GameObject>();

		private static List<Vector3?> _manual;

		private static bool _manualParsed;

		private static bool _loggedSpots;

		public static int EffectiveSlots(object lobbyManager)
		{
			int num = (AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as ICollection)?.Count ?? 0;
			if (Plugin.TestSlotLimitOverride.Value <= 0)
			{
				return num;
			}
			return Math.Min(num, Plugin.TestSlotLimitOverride.Value);
		}

		private static List<Vector3?> Manual()
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			if (_manualParsed)
			{
				return _manual;
			}
			_manualParsed = true;
			_manual = new List<Vector3?>();
			string text = Plugin.LobbyManualSpots?.Value ?? "";
			if (string.IsNullOrWhiteSpace(text))
			{
				return _manual;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(',');
				if (array2.Length >= 2 && float.TryParse(array2[0].Trim(), out var result) && float.TryParse(array2[1].Trim(), out var result2))
				{
					_manual.Add((Vector3?)new Vector3(result, 0f, result2));
				}
				else
				{
					_manual.Add(null);
				}
			}
			return _manual;
		}

		public static Vector3? ManualSpot(int index)
		{
			List<Vector3?> list = Manual();
			if (list == null || index < 0 || index >= list.Count)
			{
				return null;
			}
			return list[index];
		}

		public static Vector3 AutoSpot(List<Vector3> positions, int index)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			int count = positions.Count;
			if (index >= 0 && index < count)
			{
				return positions[index];
			}
			int num = index - count;
			int num2 = num % count;
			int num3 = 1 + num / count;
			int num4 = -1;
			for (int i = 0; i < count; i++)
			{
				if (i != num2 && Mathf.Abs(positions[i].x - positions[num2].x) < 3f)
				{
					num4 = i;
					break;
				}
			}
			float num5 = ((num4 < 0) ? ((num2 % 2 == 0) ? (-1f) : 1f) : ((positions[num2].z < positions[num4].z) ? (-1f) : 1f));
			return positions[num2] + new Vector3(0f, 0f, num5 * 2f * (float)num3);
		}

		private static List<Vector3> ReadSpotPositions(object lobbyManager, out IList list)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			list = null;
			list = AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as IList;
			if (list == null || list.Count == 0)
			{
				return null;
			}
			List<Vector3> list2 = new List<Vector3>();
			foreach (object item in list)
			{
				Component val = (Component)((item is Component) ? item : null);
				if (val != null)
				{
					list2.Add(val.transform.position);
				}
			}
			return list2;
		}

		private static void LogSpotsOnce(List<Vector3> positions)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if (_loggedSpots)
			{
				return;
			}
			_loggedSpots = true;
			int num = Math.Max(Plugin.MaxPlayers.Value, positions.Count);
			StringBuilder stringBuilder = new StringBuilder("Lobby spot positions (copy into [General] LobbyManualSpots to customize): ");
			for (int i = 0; i < num; i++)
			{
				Vector3 val = (Vector3)(((??)ManualSpot(i)) ?? AutoSpot(positions, i));
				stringBuilder.Append($"{val.x:0.0},{val.z:0.0}");
				if (i < num - 1)
				{
					stringBuilder.Append("; ");
				}
			}
			Plugin.Log.LogInfo((object)stringBuilder.ToString());
		}

		public static void ApplyManualToRealSpots(object lobbyManager)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ReadSpotPositions(lobbyManager, out var list) == null)
				{
					return;
				}
				for (int i = 0; i < list.Count; i++)
				{
					Vector3? val = ManualSpot(i);
					if (val.HasValue)
					{
						object? obj = list[i];
						Component val2 = (Component)((obj is Component) ? obj : null);
						if (val2 != null)
						{
							val2.transform.position = val.Value;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("apply manual spots: " + ex.Message));
			}
		}

		public static int RealSpotCount(object lobbyManager)
		{
			return (AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) as ICollection)?.Count ?? 0;
		}

		public static bool TryGetLobbyRowSpot(object lobbyManager, int playerIndex, out Vector3 pos, out Quaternion rot)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: 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_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			pos = Vector3.zero;
			rot = Quaternion.identity;
			try
			{
				if (!(AccessTools.Field(lobbyManager.GetType(), "_serverAvailablePlayers")?.GetValue(lobbyManager) is IList list) || list.Count == 0)
				{
					return false;
				}
				List<Vector3> list2 = new List<Vector3>();
				foreach (object item in list)
				{
					Component val = (Component)((item is Component) ? item : null);
					if (val != null)
					{
						list2.Add(val.transform.position);
						rot = val.transform.rotation;
					}
				}
				if (list2.Count == 0)
				{
					return false;
				}
				LogSpotsOnce(list2);
				pos = (Vector3)(((??)ManualSpot(playerIndex)) ?? AutoSpot(list2, playerIndex));
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("lobby-row spot: " + ex.Message));
				return false;
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_LobbyAddOverflow
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("LobbyManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "ServerAddPlayer", (Type[])null, (Type[])null);
		}

		private static bool Prefix(object __instance, NetworkConnectionToClient conn, int playerIndex)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				int num = OverflowTracker.EffectiveSlots(__instance);
				if (num > 0 && playerIndex >= num)
				{
					Vector3 pos = Vector3.zero;
					Quaternion rot = Quaternion.identity;
					bool flag = Plugin.PlaceOverflowInLobbyRow != null && Plugin.PlaceOverflowInLobbyRow.Value && OverflowTracker.TryGetLobbyRowSpot(__instance, playerIndex, out pos, out rot);
					if (Plugin.ExperimentalCloneLobbyParkingSpots != null && Plugin.ExperimentalCloneLobbyParkingSpots.Value)
					{
						if (LobbyCloneSpawner.TrySpawnClonedSpot(conn, playerIndex, flag, pos, rot, out var body))
						{
							OverflowTracker.Conns.Add(conn.connectionId);
							OverflowTracker.Bodies.Add(body);
							Plugin.Log.LogInfo((object)($"[Experimental] Overflow player (index {playerIndex}, connId " + $"{conn.connectionId}) got a CLONED LobbyPlayer spot. Ready gate ignores it."));
							return false;
						}
						Plugin.Log.LogWarning((object)"[Experimental] LobbyPlayer clone failed - falling back to forklift body.");
					}
					NetworkManager singleton = NetworkManager.singleton;
					GameObject val = (((Object)(object)singleton != (Object)null) ? singleton.playerPrefab : null);
					if ((Object)(object)val == (Object)null)
					{
						Plugin.Log.LogWarning((object)"Overflow: NetworkManager.singleton.playerPrefab is null; falling back to skip (player will likely strand).");
						OverflowTracker.Conns.Add(conn.connectionId);
						return false;
					}
					GameObject val2 = (flag ? Object.Instantiate<GameObject>(val, pos, rot) : Object.Instantiate<GameObject>(val));
					NetworkServer.AddPlayerForConnection(conn, val2);
					OverflowTracker.Conns.Add(conn.connectionId);
					OverflowTracker.Bodies.Add(val2);
					Plugin.Log.LogInfo((object)($"Overflow lobby player (index {playerIndex} >= {num} slots, " + $"connId {conn.connectionId}) - gave it a forklift body at " + (flag ? ((object)(Vector3)(ref pos)).ToString() : "default position") + " via AddPlayerForConnection (no lobby slot). Ready gate will ignore it."));
					return false;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"overflow-add prefix: {arg}");
				if (conn != null)
				{
					OverflowTracker.Conns.Add(conn.connectionId);
				}
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class Patch_LobbyDisconnectOverflow
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("LobbyManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "ServerDisconnected", (Type[])null, (Type[])null);
		}

		private static bool Prefix(object __instance, NetworkConnectionToClient conn, int playerIndex)
		{
			try
			{
				int num = OverflowTracker.EffectiveSlots(__instance);
				if ((conn != null && OverflowTracker.Conns.Contains(conn.connectionId)) || (num > 0 && playerIndex >= num))
				{
					if (conn != null)
					{
						OverflowTracker.Conns.Remove(conn.connectionId);
					}
					OverflowTracker.Bodies.RemoveAll((GameObject b) => (Object)(object)b == (Object)null);
					Plugin.Log.LogInfo((object)$"Overflow disconnect (index {playerIndex}) - skipping slot teardown.");
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("overflow-disconnect prefix: " + ex.Message));
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class Patch_CleanupOverflowBodies
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("AggroNetworkManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "SpawnPlayers", (Type[])null, (Type[])null);
		}

		private static void Prefix()
		{
			try
			{
				int num = 0;
				foreach (GameObject body in OverflowTracker.Bodies)
				{
					if ((Object)(object)body == (Object)null)
					{
						continue;
					}
					try
					{
						NetworkIdentity component = body.GetComponent<NetworkIdentity>();
						NetworkConnectionToClient val = (((Object)(object)component != (Object)null) ? component.connectionToClient : null);
						if (val != null)
						{
							NetworkServer.RemovePlayerForConnection(val, (RemovePlayerOptions)2);
						}
						else
						{
							NetworkServer.Destroy(body);
						}
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("overflow body cleanup: " + ex.Message));
						try
						{
							Object.Destroy((Object)(object)body);
						}
						catch
						{
						}
					}
				}
				if (num > 0)
				{
					Plugin.Log.LogInfo((object)($"Run start: destroyed {num} leftover overflow lobby body(ies); " + "SpawnPlayers will give those connections clean run forklifts."));
				}
			}
			finally
			{
				OverflowTracker.Bodies.Clear();
				OverflowTracker.Conns.Clear();
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_VoiceOverflowGuard
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type t = AccessTools.TypeByName("VoiceManager");
			string[] array = new string[4] { "AddPlayer", "RemovePlayer", "OnPlayerJoinedSession", "OnPlayerLeftSession" };
			foreach (string text in array)
			{
				MethodInfo methodInfo = ((t != null) ? AccessTools.Method(t, text, (Type[])null, (Type[])null) : null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
		{
			if (__exception is ArgumentOutOfRangeException || __exception is IndexOutOfRangeException)
			{
				Plugin.Log.LogInfo((object)("Voice overflow in " + __originalMethod.Name + " swallowed (player 5+ has no in-game voice; this is expected with Crew Size Mod)."));
				return null;
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class Patch_ReadyIgnoreSlotless
	{
		private static bool _lastReady;

		private static bool _resolved;

		private static bool _warnedCatch;

		private static PropertyInfo _suppressProp;

		private static FieldInfo _proceedingField;

		private static MethodInfo _containsM;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("PlayersManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "ServerIsReadyToProceed", (Type[])null, (Type[])null);
		}

		private static bool Prefix(object __instance, ref bool __result)
		{
			try
			{
				if (!NetworkServer.active)
				{
					__result = false;
					_lastReady = false;
					return false;
				}
				if (!_resolved)
				{
					_resolved = true;
					Type type = __instance.GetType();
					_suppressProp = AccessTools.Property(type, "serverSuppressProceed");
					_proceedingField = AccessTools.Field(type, "_syncPlayersProceeding");
				}
				if (_suppressProp != null && (bool)_suppressProp.GetValue(__instance))
				{
					__result = false;
					_lastReady = false;
					return false;
				}
				object obj = _proceedingField?.GetValue(__instance);
				if (obj == null)
				{
					return true;
				}
				ICollection<NetworkIdentity> collection = obj as ICollection<NetworkIdentity>;
				if (collection == null && _containsM == null)
				{
					_containsM = AccessTools.Method(obj.GetType(), "Contains", (Type[])null, (Type[])null);
				}
				if (collection == null && _containsM == null)
				{
					return true;
				}
				object[] array = ((collection == null) ? new object[1] : null);
				foreach (KeyValuePair<int, NetworkConnectionToClient> connection in NetworkServer.connections)
				{
					if (OverflowTracker.Conns.Contains(connection.Key))
					{
						continue;
					}
					NetworkIdentity val = ((connection.Value != null) ? ((NetworkConnection)connection.Value).identity : null);
					if (!((Object)(object)val == (Object)null))
					{
						bool flag;
						if (collection != null)
						{
							flag = collection.Contains(val);
						}
						else
						{
							array[0] = val;
							flag = (bool)_containsM.Invoke(obj, array);
						}
						if (!flag)
						{
							__result = false;
							_lastReady = false;
							return false;
						}
					}
				}
				__result = true;
				if (!_lastReady)
				{
					_lastReady = true;
					Plugin.Log.LogInfo((object)"ReadyToProceed: all slotted players ready (slotless overflow ignored).");
				}
				return false;
			}
			catch (Exception ex)
			{
				if (!_warnedCatch)
				{
					_warnedCatch = true;
					Plugin.Log.LogWarning((object)("ready-ignore-slotless prefix: " + ex.Message + "; falling back to original (logged once)."));
				}
				return true;
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_SwallowOverflowBodyNre
	{
		private static readonly HashSet<string> _noticed = new HashSet<string>();

		private static IEnumerable<MethodBase> TargetMethods()
		{
			(string, string[])[] array = new(string, string[])[2]
			{
				("PlayerEffects", new string[3] { "OnUpdateSimulation", "OnUpdateSimulationEarly", "OnUpdateSimulationLate" }),
				("FloaterPopulator", new string[1] { "OnEntityCreated" })
			};
			(string type, string[] methods)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(string type, string[] methods) tuple = array2[i];
				string item = tuple.type;
				string[] item2 = tuple.methods;
				Type t = AccessTools.TypeByName(item);
				if (t == null)
				{
					continue;
				}
				string[] array3 = item2;
				foreach (string text in array3)
				{
					MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
					if (methodInfo != null)
					{
						yield return methodInfo;
					}
				}
			}
		}

		private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
		{
			if (__exception is NullReferenceException)
			{
				string text = __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name;
				if (_noticed.Add(text))
				{
					Plugin.Log.LogInfo((object)("Swallowing " + text + " NullReferenceException on the overflow player's forklift (cosmetic; not a crash). This notice prints once per method."));
				}
				return null;
			}
			return __exception;
		}
	}
	[HarmonyPatch]
	internal static class Patch_QuotaReportPlayerCap
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("QuotaReportUI");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "UpdateUIData", (Type[])null, (Type[])null);
		}

		private static void Prefix(object __instance, IList playerResults, out object __state)
		{
			__state = null;
			try
			{
				if (playerResults == null)
				{
					return;
				}
				int num = (AccessTools.Field(__instance.GetType(), "allPlayerCrashoutCounts")?.GetValue(__instance) as Array)?.Length ?? 4;
				if (num < 1)
				{
					num = 4;
				}
				if (playerResults.Count > num)
				{
					List<object> list = new List<object>();
					for (int num2 = playerResults.Count - 1; num2 >= num; num2--)
					{
						list.Add(playerResults[num2]);
						playerResults.RemoveAt(num2);
					}
					__state = list;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("quota-report cap prefix: " + ex.Message));
			}
		}

		private static void Postfix(IList playerResults, object __state)
		{
			try
			{
				if (__state is List<object> list && playerResults != null)
				{
					for (int num = list.Count - 1; num >= 0; num--)
					{
						playerResults.Add(list[num]);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("quota-report cap postfix: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_ProceedUiPlayerCap
	{
		private static FieldInfo _fProceeds;

		private static FieldInfo _fIcons;

		private static FieldInfo _fParents;

		private static bool _expandNoticed;

		private static bool _warnedCap;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("PlayerProceedUI");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null);
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			MethodInfo method = typeof(Patch_ProceedUiPlayerCap).GetMethod("Cap", BindingFlags.Static | BindingFlags.NonPublic);
			for (int i = 0; i < list.Count; i++)
			{
				MethodInfo methodInfo = list[i].operand as MethodInfo;
				if (methodInfo != null && methodInfo.Name == "GetAllPlayerProceeds")
				{
					list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)method));
					list.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
					Plugin.Log.LogInfo((object)"PlayerProceedUI: injected proceed-icon cap after GetAllPlayerProceeds.");
					return list;
				}
			}
			Plugin.Log.LogWarning((object)"PlayerProceedUI: GetAllPlayerProceeds call not found; proceed-icon cap NOT applied (5+ players may flood this UI). Method left unchanged.");
			return list;
		}

		private static void Cap(object self)
		{
			try
			{
				Type type = self.GetType();
				if (_fProceeds == null)
				{
					_fProceeds = AccessTools.Field(type, "_playerProceeds");
				}
				if (_fIcons == null)
				{
					_fIcons = AccessTools.Field(type, "playerIcons");
				}
				if (_fParents == null)
				{
					_fParents = AccessTools.Field(type, "playerParents");
				}
				IList list = _fProceeds?.GetValue(self) as IList;
				IList list2 = _fIcons?.GetValue(self) as IList;
				if (list == null || list2 == null)
				{
					return;
				}
				if (Plugin.ExperimentalExpandPlayerUI != null && Plugin.ExperimentalExpandPlayerUI.Value)
				{
					int num = Plugin.ExperimentalExpandPreviewCount?.Value ?? 0;
					if (num > list.Count && list.Count > 0)
					{
						object value = list[list.Count - 1];
						while (list.Count < num)
						{
							list.Add(value);
						}
					}
					EnsureExpanded(_fParents?.GetValue(self) as IList, list2, list.Count);
				}
				else
				{
					while (list.Count > list2.Count)
					{
						list.RemoveAt(list.Count - 1);
					}
				}
			}
			catch (Exception ex)
			{
				if (!_warnedCap)
				{
					_warnedCap = true;
					Plugin.Log.LogWarning((object)("proceed-icon adjust: " + ex.Message + " (logged once)"));
				}
			}
		}

		private static void EnsureExpanded(IList parents, IList icons, int needed)
		{
			if (parents == null || icons == null)
			{
				return;
			}
			int count = icons.Count;
			if (count == 0 || parents.Count == 0)
			{
				return;
			}
			int num = Math.Min(needed, 16);
			int index = count - 1;
			object? obj = parents[index];
			GameObject val = (GameObject)((obj is GameObject) ? obj : null);
			object? obj2 = icons[index];
			Component val2 = (Component)((obj2 is Component) ? obj2 : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				return;
			}
			int num2 = 0;
			while (icons.Count < num)
			{
				GameObject val3 = Object.Instantiate<GameObject>(val, val.transform.parent);
				((Object)val3).name = ((Object)val).name + "_crewmod" + icons.Count;
				Transform val4 = MapByPath(val.transform, val2.transform, val3.transform);
				Component val5 = (((Object)(object)val4 != (Object)null) ? ((Component)val4).GetComponent(((object)val2).GetType()) : val3.GetComponentInChildren(((object)val2).GetType(), true));
				if ((Object)(object)val5 == (Object)null)
				{
					Object.Destroy((Object)(object)val3);
					break;
				}
				parents.Add(val3);
				icons.Add(val5);
				num2++;
			}
			if (num2 > 0 && !_expandNoticed)
			{
				_expandNoticed = true;
				Plugin.Log.LogInfo((object)($"[Experimental] Expanded proceed icons from {count} to {icons.Count} " + "(cloned authored slots to show all players). Check the layout in-game."));
			}
		}

		private static Transform MapByPath(Transform root, Transform target, Transform newRoot)
		{
			List<int> list = new List<int>();
			Transform val = target;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				list.Add(val.GetSiblingIndex());
				val = val.parent;
			}
			if ((Object)(object)val != (Object)(object)root)
			{
				return null;
			}
			list.Reverse();
			Transform val2 = newRoot;
			foreach (int item in list)
			{
				if (item < 0 || item >= val2.childCount)
				{
					return null;
				}
				val2 = val2.GetChild(item);
			}
			return val2;
		}
	}
	internal static class ModPresence
	{
		public const string Key = "CrewSizeMod";

		private static Callback<LobbyEnter_t> _enterCb;

		private static Callback<LobbyDataUpdate_t> _dataCb;

		private static CSteamID _lobby;

		private static string _lastSummary = "";

		public static bool AllPeersModded { get; private set; }

		public static int MemberCount { get; private set; }

		public static int ModdedCount { get; private set; }

		public static void Init()
		{
			try
			{
				_enterCb = Callback<LobbyEnter_t>.Create((DispatchDelegate<LobbyEnter_t>)OnLobbyEnter);
				_dataCb = Callback<LobbyDataUpdate_t>.Create((DispatchDelegate<LobbyDataUpdate_t>)OnLobbyDataUpdate);
				Plugin.Log.LogInfo((object)"Mod-presence handshake registered (advertises via Steam lobby member data).");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("presence init failed (non-critical, informational only): " + ex.Message));
			}
		}

		private static void OnLobbyEnter(LobbyEnter_t e)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				_lobby = new CSteamID(e.m_ulSteamIDLobby);
				SteamMatchmaking.SetLobbyMemberData(_lobby, "CrewSizeMod", "0.10.9");
				_lastSummary = "";
				Scan();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("presence enter: " + ex.Message));
			}
		}

		private static void OnLobbyDataUpdate(LobbyDataUpdate_t e)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (((CSteamID)(ref _lobby)).IsValid() && e.m_ulSteamIDLobby == _lobby.m_SteamID)
			{
				Scan();
			}
		}

		private static void Scan()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((CSteamID)(ref _lobby)).IsValid())
				{
					return;
				}
				int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(_lobby);
				int num = 0;
				for (int i = 0; i < numLobbyMembers; i++)
				{
					CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(_lobby, i);
					if (!string.IsNullOrEmpty(SteamMatchmaking.GetLobbyMemberData(_lobby, lobbyMemberByIndex, "CrewSizeMod")))
					{
						num++;
					}
				}
				MemberCount = numLobbyMembers;
				ModdedCount = num;
				AllPeersModded = numLobbyMembers > 0 && num >= numLobbyMembers;
				string text = $"{num}/{numLobbyMembers}";
				if (text == _lastSummary)
				{
					return;
				}
				_lastSummary = text;
				if (numLobbyMembers > 0)
				{
					if (num >= numLobbyMembers)
					{
						Plugin.Log.LogInfo((object)$"Crew Size Mod presence: all {numLobbyMembers} lobby member(s) have the mod - full experience.");
					}
					else
					{
						Plugin.Log.LogWarning((object)($"Crew Size Mod presence: {num}/{numLobbyMembers} lobby member(s) have the mod. " + $"The {numLobbyMembers - num} without it can still play, but may see voice/UI glitches at 5+ players " + "(those fixes run client-side). Ask them to install Crew Size Mod for the clean experience."));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("presence scan: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_ProceedUiDebugLog
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type t = AccessTools.TypeByName("PlayerProceedUI");
			string[] array = new string[2] { "Show", "Hide" };
			foreach (string text in array)
			{
				MethodInfo methodInfo = ((t != null) ? AccessTools.Method(t, text, (Type[])null, (Type[])null) : null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Postfix(object __instance, MethodBase __originalMethod)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.ExperimentalExpandPlayerUI == null || !Plugin.ExperimentalExpandPlayerUI.Value)
			{
				return;
			}
			try
			{
				if (__originalMethod.Name == "Hide")
				{
					Plugin.Log.LogInfo((object)"PlayerProceedUI HIDDEN.");
					return;
				}
				int num = 0;
				if (AccessTools.Field(__instance.GetType(), "playerIcons")?.GetValue(__instance) is ICollection collection)
				{
					num = collection.Count;
				}
				Scene activeScene = SceneManager.GetActiveScene();
				string name = ((Scene)(ref activeScene)).name;
				Plugin.Log.LogInfo((object)($"PlayerProceedUI SHOWN - scene '{name}', {num} authored icon slot(s). " + "(These are the ready-up proceed icons; expand line follows if it grows.)"));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("proceed-ui debug log: " + ex.Message));
			}
		}
	}
	internal static class LobbyCloneSpawner
	{
		public const uint AssetId = 3320164359u;

		private static bool _registered;

		private static GameObject _cachedTemplate;

		public static void RegisterFresh()
		{
			_registered = false;
			_cachedTemplate = null;
			Register();
		}

		public static void Register()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_003f: Expected O, but got Unknown
			if (_registered || Plugin.ExperimentalCloneLobbyParkingSpots == null || !Plugin.ExperimentalCloneLobbyParkingSpots.Value)
			{
				return;
			}
			try
			{
				NetworkClient.RegisterSpawnHandler(3320164359u, new SpawnHandlerDelegate(SpawnHandler), new UnSpawnDelegate(UnspawnHandler));
				_registered = true;
				Plugin.Log.LogInfo((object)$"[Experimental] Registered lobby-spot spawn handler (assetId 0x{3320164359u:X}).");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Experimental] register spawn handler: " + ex.Message));
			}
		}

		private static GameObject SpawnHandler(SpawnMessage msg)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = FindTemplate();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[Experimental] spawn handler: no LobbyPlayer template on this client.");
					return null;
				}
				return Object.Instantiate<GameObject>(val, msg.position, msg.rotation);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Experimental] spawn handler: " + ex.Message));
				return null;
			}
		}

		private static void UnspawnHandler(GameObject obj)
		{
			if ((Object)(object)obj != (Object)null)
			{
				Object.Destroy((Object)(object)obj);
			}
		}

		private static GameObject FindTemplate()
		{
			if ((Object)(object)_cachedTemplate != (Object)null)
			{
				return _cachedTemplate;
			}
			Type type = AccessTools.TypeByName("LobbyPlayer");
			if (type == null)
			{
				return null;
			}
			FieldInfo fieldInfo = AccessTools.Field(type, "lobbyPlayerIndex");
			Object[] array = Object.FindObjectsOfType(type);
			GameObject val = null;
			int num = int.MaxValue;
			Object[] array2 = array;
			foreach (Object obj in array2)
			{
				Component val2 = (Component)(object)((obj is Component) ? obj : null);
				if (val2 == null)
				{
					continue;
				}
				int num2 = 0;
				try
				{
					if (fieldInfo != null)
					{
						num2 = (int)fieldInfo.GetValue(val2);
					}
				}
				catch
				{
				}
				if (num2 < num)
				{
					num = num2;
					val = val2.gameObject;
				}
			}
			_cachedTemplate = val;
			return val;
		}

		private static GameObject MakeStampedClone(bool placed, Vector3 pos, Quaternion rot, int index, out object lp, out Type lpType)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			lp = null;
			lpType = null;
			GameObject val = FindTemplate();
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[Experimental] clone: no LobbyPlayer template.");
				return null;
			}
			GameObject val2 = (placed ? Object.Instantiate<GameObject>(val, pos, rot) : Object.Instantiate<GameObject>(val));
			NetworkIdentity component = val2.GetComponent<NetworkIdentity>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				Plugin.Log.LogWarning((object)"[Experimental] clone has no NetworkIdentity.");
				return null;
			}
			Type typeFromHandle = typeof(NetworkIdentity);
			AccessTools.Field(typeFromHandle, "sceneId")?.SetValue(component, 0uL);
			AccessTools.Field(typeFromHandle, "_assetId")?.SetValue(component, 3320164359u);
			lpType = AccessTools.TypeByName("LobbyPlayer");
			lp = ((lpType != null) ? val2.GetComponent(lpType) : null);
			if (lpType != null)
			{
				AccessTools.Field(lpType, "lobbyPlayerIndex")?.SetValue(lp, index);
			}
			return val2;
		}

		private static void MarkAssigned(object lp, Type lpType)
		{
			if (lp != null && lpType != null)
			{
				AccessTools.Method(lpType, "ServerPlayerAssigned", (Type[])null, (Type[])null)?.Invoke(lp, null);
			}
		}

		public static bool TrySpawnClonedSpot(NetworkConnectionToClient conn, int index, bool placed, Vector3 pos, Quaternion rot, out GameObject body)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			body = null;
			try
			{
				Register();
				object lp;
				Type lpType;
				GameObject val = MakeStampedClone(placed, pos, rot, index, out lp, out lpType);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				NetworkServer.AddPlayerForConnection(conn, val);
				MarkAssigned(lp, lpType);
				body = val;
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[Experimental] clone spawn failed: {arg}");
				return false;
			}
		}

		public static GameObject TrySpawnPreviewClone(bool placed, Vector3 pos, Quaternion rot, int index)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Register();
				object lp;
				Type lpType;
				GameObject val = MakeStampedClone(placed, pos, rot, index, out lp, out lpType);
				if ((Object)(object)val == (Object)null)
				{
					return null;
				}
				if (NetworkServer.active)
				{
					NetworkServer.Spawn(val, (NetworkConnectionToClient)null);
				}
				MarkAssigned(lp, lpType);
				return val;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Experimental] preview clone: " + ex.Message));
				return null;
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_LobbyPreviewSpawner
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("LobbyManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "OnEntityStart", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkServer.active)
			{
				return;
			}
			try
			{
				OverflowTracker.ApplyManualToRealSpots(__instance);
				int num = Plugin.ExperimentalLobbyPreviewCount?.Value ?? 0;
				if (num <= 0)
				{
					return;
				}
				int num2 = 0;
				for (int i = 0; i < num; i++)
				{
					int num3 = 1 + i;
					Vector3 pos;
					Quaternion rot;
					bool flag = OverflowTracker.TryGetLobbyRowSpot(__instance, num3, out pos, out rot);
					GameObject val = null;
					if (Plugin.ExperimentalCloneLobbyParkingSpots != null && Plugin.ExperimentalCloneLobbyParkingSpots.Value)
					{
						val = LobbyCloneSpawner.TrySpawnPreviewClone(flag, pos, rot, num3);
					}
					if ((Object)(object)val == (Object)null)
					{
						GameObject val2 = NetworkManager.singleton?.playerPrefab;
						if ((Object)(object)val2 == (Object)null)
						{
							continue;
						}
						val = (flag ? Object.Instantiate<GameObject>(val2, pos, rot) : Object.Instantiate<GameObject>(val2));
						NetworkIdentity component = val.GetComponent<NetworkIdentity>();
						if ((Object)(object)component != (Object)null && component.netId == 0)
						{
							NetworkServer.Spawn(val, (NetworkConnectionToClient)null);
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						OverflowTracker.Bodies.Add(val);
						num2++;
					}
				}
				Plugin.Log.LogInfo((object)$"[Experimental] Spawned {num2} fake lobby forklift(s) at spots 1..{num} for solo layout testing.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("lobby preview spawn: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_RegisterLobbyCloneHandler
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("AggroNetworkManager");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "OnStartClient", (Type[])null, (Type[])null);
		}

		private static void Postfix()
		{
			LobbyCloneSpawner.RegisterFresh();
		}
	}
	internal static class UiExpand
	{
		public static void EnsureExpanded(object instance, FieldInfo field, int target)
		{
			if (instance == null || field == null)
			{
				return;
			}
			int num = Math.Min(target, 16);
			object value = field.GetValue(instance);
			if (value is Array array)
			{
				int length = array.Length;
				if (length == 0 || length >= num)
				{
					return;
				}
				Type elementType = array.GetType().GetElementType();
				object? value2 = array.GetValue(length - 1);
				Component val = (Component)((value2 is Component) ? value2 : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Array array2 = Array.CreateInstance(elementType, num);
				Array.Copy(array, array2, length);
				for (int i = length; i < num; i++)
				{
					GameObject val2 = Object.Instantiate<GameObject>(val.gameObject, val.gameObject.transform.parent);
					Component component = val2.GetComponent(elementType);
					if ((Object)(object)component == (Object)null)
					{
						Object.Destroy((Object)(object)val2);
						break;
					}
					array2.SetValue(component, i);
				}
				field.SetValue(instance, array2);
			}
			else
			{
				if (!(value is IList list) || list.Count == 0 || list.Count >= num)
				{
					return;
				}
				object? obj = list[list.Count - 1];
				Component val3 = (Component)((obj is Component) ? obj : null);
				if ((Object)(object)val3 == (Object)null)
				{
					return;
				}
				Type type = ((object)val3).GetType();
				while (list.Count < num)
				{
					GameObject val4 = Object.Instantiate<GameObject>(val3.gameObject, val3.gameObject.transform.parent);
					Component component2 = val4.GetComponent(type);
					if ((Object)(object)component2 == (Object)null)
					{
						Object.Destroy((Object)(object)val4);
						break;
					}
					list.Add(component2);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_ShiftProceedAreaExpand
	{
		private static FieldInfo _f;

		private static bool _warned;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("ShiftProceedArea");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "OnUpdatePresentation", (Type[])null, (Type[])null);
		}

		private static void Prefix(object __instance)
		{
			try
			{
				if (_f == null)
				{
					_f = AccessTools.Field(__instance.GetType(), "playerImages");
				}
				UiExpand.EnsureExpanded(__instance, _f, Plugin.MaxPlayers.Value);
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)("shift-proceed-area expand: " + ex.Message + " (logged once)"));
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_ModifierVoteExpand
	{
		private static bool _resolved;

		private static bool _warned;

		private static FieldInfo _fUndecided;

		private static FieldInfo _fBtnA;

		private static FieldInfo _fBtnB;

		private static FieldInfo _fPlayers;

		private static FieldInfo[] _btnFields;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("ModifierChoiceManagerUI");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "RefreshVotes", (Type[])null, (Type[])null);
		}

		private static void Prefix(object __instance)
		{
			try
			{
				int value = Plugin.MaxPlayers.Value;
				if (!_resolved)
				{
					_resolved = true;
					Type type = __instance.GetType();
					_fUndecided = AccessTools.Field(type, "undecidedPlayers");
					_fBtnA = AccessTools.Field(type, "choiceButtonA");
					_fBtnB = AccessTools.Field(type, "choiceButtonB");
					_btnFields = new FieldInfo[2] { _fBtnA, _fBtnB };
				}
				UiExpand.EnsureExpanded(__instance, _fUndecided, value);
				FieldInfo[] btnFields = _btnFields;
				for (int i = 0; i < btnFields.Length; i++)
				{
					object obj = btnFields[i]?.GetValue(__instance);
					if (obj != null)
					{
						if (_fPlayers == null)
						{
							_fPlayers = AccessTools.Field(obj.GetType(), "players");
						}
						UiExpand.EnsureExpanded(obj, _fPlayers, value);
					}
				}
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)("modifier-vote expand: " + ex.Message + " (logged once)"));
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_SwallowUiOverflow
	{
		private static readonly HashSet<string> _noticed = new HashSet<string>();

		private static IEnumerable<MethodBase> TargetMethods()
		{
			(string, string[])[] array = new(string, string[])[2]
			{
				("ShiftProceedArea", new string[1] { "OnUpdatePresentation" }),
				("ModifierChoiceManagerUI", new string[2] { "RefreshVotes", "OnUpdatePresentation" })
			};
			(string type, string[] methods)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(string type, string[] methods) tuple = array2[i];
				string item = tuple.type;
				string[] item2 = tuple.methods;
				Type t = AccessTools.TypeByName(item);
				if (t == null)
				{
					continue;
				}
				string[] array3 = item2;
				foreach (string text in array3)
				{
					MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
					if (methodInfo != null)
					{
						yield return methodInfo;
					}
				}
			}
		}

		private static Exception Finalizer(Exception __exception, MethodBase __originalMethod)
		{
			if (__exception is IndexOutOfRangeException || __exception is ArgumentOutOfRangeException)
			{
				string text = __originalMethod.DeclaringType?.Name + "." + __originalMethod.Name;
				if (_noticed.Add(text))
				{
					Plugin.Log.LogInfo((object)("Swallowing " + text + " index overflow at 5+ players (cosmetic; prevents flood/lag). Prints once."));
				}
				return null;
			}
			return __exception;
		}
	}
	internal enum ScreenCorner
	{
		TopLeft,
		TopRight,
		BottomLeft,
		BottomRight
	}
	internal static class CreditUI
	{
		private const string Text = "MoreCrew  by Izaya_here\nFollow on Twitch:  twitch.tv/izaya_here";

		private static GameObject _go;

		private static RectTransform _rt;

		private static Text _text;

		private static RawImage _raw;

		private static Texture2D _tex;

		private static bool _created;

		private static bool _failed;

		internal static object ShiftMgr;

		private static FieldInfo _phaseField;

		private const int PHASE_SHIFT = 3;

		private static bool _lastInShift;

		private static int _lastFrame = -1;

		private static ScreenCorner _appliedCorner = (ScreenCorner)(-1);

		private static float _appliedWidth = float.NaN;

		internal static void SetShiftManager(object mgr)
		{
			ShiftMgr = mgr;
			if (mgr != null && _phaseField == null)
			{
				_phaseField = AccessTools.Field(mgr.GetType(), "_shiftPhase");
			}
		}

		internal static void ClearShiftManager(object mgr)
		{
			if (mgr == null || ShiftMgr == mgr)
			{
				ShiftMgr = null;
			}
		}

		private static bool InActiveShift()
		{
			try
			{
				object shiftMgr = ShiftMgr;
				if ((Object)((shiftMgr is Object) ? shiftMgr : null) == (Object)null)
				{
					return false;
				}
				if (_phaseField == null)
				{
					return false;
				}
				return Convert.ToInt32(_phaseField.GetValue(ShiftMgr)) == 3;
			}
			catch
			{
				return false;
			}
		}

		public static bool ShouldShow()
		{
			if (Plugin.ShowTwitchCredit == null || !Plugin.ShowTwitchCredit.Value)
			{
				return false;
			}
			bool flag = InActiveShift();
			if (flag != _lastInShift)
			{
				_lastInShift = flag;
				Plugin.Log.LogInfo((object)$"Credit: in-round = {flag} (credit hidden while true).");
			}
			return !flag;
		}

		public static void Tick()
		{
			if (_failed)
			{
				return;
			}
			int frameCount = Time.frameCount;
			if (frameCount == _lastFrame)
			{
				return;
			}
			_lastFrame = frameCount;
			try
			{
				if (!_created)
				{
					Create();
				}
				if ((Object)(object)_go != (Object)null)
				{
					ApplyCorner();
					bool flag = ShouldShow();
					if (_go.activeSelf != flag)
					{
						_go.SetActive(flag);
					}
				}
			}
			catch (Exception ex)
			{
				_failed = true;
				Plugin.Log.LogWarning((object)("credit ui: " + ex.Message));
			}
		}

		private static string PngPath()
		{
			try
			{
				string directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
				return string.IsNullOrEmpty(directoryName) ? null : Path.Combine(directoryName, "credit.png");
			}
			catch
			{
				return null;
			}
		}

		private static Texture2D LoadPng()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			try
			{
				string text = PngPath();
				if (text == null || !File.Exists(text))
				{
					return null;
				}
				byte[] array = File.ReadAllBytes(text);
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConversion.LoadImage(val, array))
				{
					return null;
				}
				((Texture)val).wrapMode = (TextureWrapMode)1;
				((Texture)val).filterMode = (FilterMode)1;
				return val;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("credit png load: " + ex.Message));
				return null;
			}
		}

		private static void Create()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			_created = true;
			_go = new GameObject("CrewSizeMod.CreditCanvas");
			Object.DontDestroyOnLoad((Object)(object)_go);
			Canvas obj = _go.AddComponent<Canvas>();
			obj.renderMode = (RenderMode)0;
			obj.sortingOrder = 30000;
			_go.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
			_tex = LoadPng();
			if ((Object)(object)_tex != (Object)null)
			{
				GameObject val = new GameObject("Image");
				val.transform.SetParent(_go.transform, false);
				_raw = val.AddComponent<RawImage>();
				_raw.texture = (Texture)(object)_tex;
				((Graphic)_raw).raycastTarget = false;
				_rt = ((Graphic)_raw).rectTransform;
				float num = Plugin.CreditWidth?.Value ?? 320f;
				float num2 = num * (float)((Texture)_tex).height / (float)Mathf.Max(1, ((Texture)_tex).width);
				_rt.sizeDelta = new Vector2(num, num2);
				ApplyCorner();
				Plugin.Log.LogInfo((object)$"Credit UI canvas created (uGUI image, credit.png {((Texture)_tex).width}x{((Texture)_tex).height} -> {(int)num}x{(int)num2} px).");
			}
			else
			{
				GameObject val2 = new GameObject("Text");
				val2.transform.SetParent(_go.transform, false);
				_text = val2.AddComponent<Text>();
				_text.font = Font.CreateDynamicFontFromOSFont("Arial", 20);
				_text.fontSize = 20;
				_text.fontStyle = (FontStyle)1;
				((Graphic)_text).color = Color.white;
				_text.text = "MoreCrew  by Izaya_here\nFollow on Twitch:  twitch.tv/izaya_here";
				_text.horizontalOverflow = (HorizontalWrapMode)1;
				_text.verticalOverflow = (VerticalWrapMode)1;
				((Graphic)_text).raycastTarget = false;
				Shadow obj2 = val2.AddComponent<Shadow>();
				obj2.effectColor = new Color(0f, 0f, 0f, 0.85f);
				obj2.effectDistance = new Vector2(1.5f, -1.5f);
				_rt = ((Graphic)_text).rectTransform;
				_rt.sizeDelta = new Vector2(520f, 64f);
				ApplyCorner();
				Plugin.Log.LogInfo((object)"Credit UI canvas created (uGUI text fallback - credit.png not found next to the DLL).");
			}
		}

		private static void ApplyCorner()
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_rt == (Object)null)
			{
				return;
			}
			ScreenCorner screenCorner = Plugin.TwitchCreditCorner?.Value ?? ScreenCorner.TopLeft;
			float num = Plugin.CreditWidth?.Value ?? 320f;
			if (screenCorner != _appliedCorner || num != _appliedWidth)
			{
				_appliedCorner = screenCorner;
				_appliedWidth = num;
				bool flag = screenCorner == ScreenCorner.TopLeft || screenCorner == ScreenCorner.BottomLeft;
				bool flag2 = screenCorner == ScreenCorner.TopLeft || screenCorner == ScreenCorner.TopRight;
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(flag ? 0f : 1f, flag2 ? 1f : 0f);
				_rt.anchorMin = val;
				_rt.anchorMax = val;
				_rt.pivot = val;
				_rt.anchoredPosition = new Vector2(flag ? 18f : (-18f), flag2 ? (-14f) : 14f);
				if ((Object)(object)_raw != (Object)null && (Object)(object)_tex != (Object)null)
				{
					float num2 = num * (float)((Texture)_tex).height / (float)Mathf.Max(1, ((Texture)_tex).width);
					_rt.sizeDelta = new Vector2(num, num2);
				}
				if ((Object)(object)_text != (Object)null)
				{
					_text.alignment = (TextAnchor)(screenCorner switch
					{
						ScreenCorner.BottomRight => 8, 
						ScreenCorner.TopRight => 2, 
						ScreenCorner.TopLeft => 0, 
						_ => 6, 
					});
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_CreditDriver
	{
		private static bool _logged;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			(string, string)[] array = new(string, string)[4]
			{
				("GameManager", "Update"),
				("ShiftManager", "OnUpdatePresentation"),
				("GameMenuUI", "OnUpdatePresentation"),
				("LobbyButtonsUI", "OnUpdatePresentation")
			};
			(string type, string method)[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				(string type, string method) tuple = array2[i];
				string item = tuple.type;
				string item2 = tuple.method;
				Type type = AccessTools.TypeByName(item);
				MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, item2, (Type[])null, (Type[])null) : null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Postfix()
		{
			if (!_logged)
			{
				_logged = true;
				Plugin.Log.LogInfo((object)"Credit driver is firing (uGUI path active).");
			}
			CreditUI.Tick();
		}
	}
	[HarmonyPatch]
	internal static class Patch_ShiftLifecycle
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type t = AccessTools.TypeByName("ShiftManager");
			if (t == null)
			{
				yield break;
			}
			string[] array = new string[2] { "OnEntityStart", "OnEntityDestroyed" };
			foreach (string text in array)
			{
				MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Postfix(object __instance, MethodBase __originalMethod)
		{
			if (__originalMethod.Name == "OnEntityStart")
			{
				CreditUI.SetShiftManager(__instance);
			}
			else
			{
				CreditUI.ClearShiftManager(__instance);
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_FinalReportPlayerCap
	{
		private static readonly string[] PerPlayerLists = new string[12]
		{
			"playerIconImages", "playerNameTexts", "playerHighlightImages", "playerIconObjects", "crashoutCountObjects", "nitroCountObjects", "driftCountObjects", "upgradeCountObjects", "crashoutCountTexts", "nitroCountTexts",
			"driftCountTexts", "upgradeCountTexts"
		};

		private static bool _noticed;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("ReportUI");
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "StartSequenceCo", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, object __result)
		{
			try
			{
				if (__instance == null || __result == null)
				{
					return;
				}
				FieldInfo fieldInfo = AccessTools.Field(__result.GetType(), "playerResults");
				if (fieldInfo == null || !(fieldInfo.GetValue(__result) is Array array))
				{
					return;
				}
				int num = RowCapacity(__instance);
				if (num >= 1 && array.Length > num)
				{
					Array array2 = Array.CreateInstance(array.GetType().GetElementType(), num);
					Array.Copy(array, array2, num);
					fieldInfo.SetValue(__result, array2);
					if (!_noticed)
					{
						_noticed = true;
						Plugin.Log.LogWarning((object)($"Final report (ReportUI): capped {array.Length} players -> {num} UI rows to " + "avoid the end-of-run freeze. Players 5+ get no row on the grade screen (cosmetic)."));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("final-report cap postfix: " + ex.Message));
			}
		}

		private static int RowCapacity(object reportUi)
		{
			int num = int.MaxValue;
			Type type = reportUi.GetType();
			string[] perPlayerLists = PerPlayerLists;
			foreach (string text in perPlayerLists)
			{
				if (AccessTools.Field(type, text)?.GetValue(reportUi) is IList list && list.Count > 0 && list.Count < num)
				{
					num = list.Count;
				}
			}
			if (num != int.MaxValue)
			{
				return num;
			}
			return 4;
		}
	}
}