Decompiled source of FixedMoreBopl v0.0.13

BoplMorePlayersLocal8.dll

Decompiled an hour ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
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 UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BoplMorePlayersLocal8")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("8-player local couch multiplayer expansion for Bopl Battle.")]
[assembly: AssemblyFileVersion("0.0.13.0")]
[assembly: AssemblyInformationalVersion("0.0.13+acf943e98315d25a64ef744bf4a13c18070cd98b")]
[assembly: AssemblyProduct("BoplMorePlayersLocal8")]
[assembly: AssemblyTitle("BoplMorePlayersLocal8")]
[assembly: AssemblyVersion("0.0.13.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 BoplMorePlayersLocal8
{
	[BepInPlugin("com.geddesworks.fixedmorebopl", "FixedMoreBopl", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.geddesworks.fixedmorebopl";

		public const string PluginName = "FixedMoreBopl";

		public const string PluginVersion = "0.1.0";

		internal static ConfigEntry<int> TargetLocalPlayers;

		internal static ConfigEntry<bool> EnableDiagnostics;

		internal static ConfigEntry<bool> VerboseDiagnostics;

		internal static ConfigEntry<bool> DisableReplayRecording;

		internal static ConfigEntry<bool> RepositionCharacterSelectBoxes;

		internal static ConfigEntry<bool> CompressMidRoundSelectorSpacing;

		internal static ConfigEntry<bool> IncreaseCameraZoomForCrowdedMatches;

		internal static ConfigEntry<bool> RelaxColorUniquenessWhenFull;

		internal static ConfigEntry<bool> ExpandDrawWinnerUiSlots;

		private Harmony? _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static int TargetPlayerCount => Mathf.Clamp(TargetLocalPlayers.Value, 4, 8);

		internal static int SlotArrayLength => TargetPlayerCount + 1;

		private void Awake()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			ApplyEarlyRuntimeOverrides();
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.sceneUnloaded += OnSceneUnloaded;
			_harmony = new Harmony("com.geddesworks.fixedmorebopl");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			string[] array = (from method in _harmony.GetPatchedMethods().Where(delegate(MethodBase method)
				{
					Patches patchInfo = Harmony.GetPatchInfo(method);
					return patchInfo != null && patchInfo.Owners.Contains("com.geddesworks.fixedmorebopl");
				})
				select method.DeclaringType?.FullName + "." + method.Name into name
				orderby name
				select name).ToArray();
			Log.LogInfo((object)"FixedMoreBopl 0.1.0 loaded.");
			Log.LogInfo((object)$"Target local players: {TargetPlayerCount}. Slot array length: {SlotArrayLength}.");
			Log.LogInfo((object)string.Format("Patched {0} methods:\n  - {1}", array.Length, string.Join("\n  - ", array)));
			RuntimeSnapshot.LogGlobalState("Plugin.Awake", verbose: false);
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			SceneManager.sceneUnloaded -= OnSceneUnloaded;
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			RuntimeSnapshot.Log($"Scene loaded: '{((Scene)(ref scene)).name}' ({mode}).", verbose: false);
			RuntimeSnapshot.LogGlobalState("SceneLoaded:" + ((Scene)(ref scene)).name, verbose: true);
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			RuntimeSnapshot.Log("Scene unloaded: '" + ((Scene)(ref scene)).name + "'.", verbose: false);
		}

		private void BindConfig()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			TargetLocalPlayers = ((BaseUnityPlugin)this).Config.Bind<int>("General", "TargetLocalPlayers", 8, new ConfigDescription("Target number of local couch players.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 8), Array.Empty<object>()));
			EnableDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableDiagnostics", true, "Enable runtime diagnostics for joins, arrays, player counts, and scene transitions.");
			VerboseDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseDiagnostics", false, "Log additional verbose state snapshots.");
			DisableReplayRecording = ((BaseUnityPlugin)this).Config.Bind<bool>("Stability", "DisableReplayRecording", true, "Disable replay recording to avoid 4-player replay packet assumptions during local 8-player testing.");
			RepositionCharacterSelectBoxes = ((BaseUnityPlugin)this).Config.Bind<bool>("Layout", "RepositionCharacterSelectBoxes", true, "Re-layout character select boxes into a compact multi-row arrangement when expanding beyond vanilla slots.");
			CompressMidRoundSelectorSpacing = ((BaseUnityPlugin)this).Config.Bind<bool>("Layout", "CompressMidRoundSelectorSpacing", true, "Reduce between-round selector spacing for 5+ players so circles stay on-screen.");
			IncreaseCameraZoomForCrowdedMatches = ((BaseUnityPlugin)this).Config.Bind<bool>("Match", "IncreaseCameraZoomForCrowdedMatches", true, "Increase max camera zoom and breathing room when player count exceeds 4.");
			RelaxColorUniquenessWhenFull = ((BaseUnityPlugin)this).Config.Bind<bool>("CharacterSelect", "RelaxColorUniquenessWhenFull", true, "Allow duplicate colors once all unique colors are consumed so 5+ players can still ready up.");
			ExpandDrawWinnerUiSlots = ((BaseUnityPlugin)this).Config.Bind<bool>("Match", "ExpandDrawWinnerUiSlots", true, "Clone draw-winner UI slots when needed to avoid out-of-range errors with more than 4 players.");
		}

		private void ApplyEarlyRuntimeOverrides()
		{
			if (!DisableReplayRecording.Value)
			{
				return;
			}
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(Host), "recordReplay");
				if (fieldInfo != null && fieldInfo.IsStatic && fieldInfo.FieldType == typeof(bool))
				{
					fieldInfo.SetValue(null, false);
					Log.LogInfo((object)"Disabled Host.recordReplay for local 8-player stability.");
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to disable replay recording: " + ex.Message));
			}
		}
	}
	internal static class RuntimeSnapshot
	{
		internal static void Log(string message, bool verbose)
		{
			if (Plugin.EnableDiagnostics.Value && (!verbose || Plugin.VerboseDiagnostics.Value))
			{
				Plugin.Log.LogInfo((object)("[Diag] " + message));
			}
		}

		internal static void LogGlobalState(string context, bool verbose)
		{
			if (Plugin.EnableDiagnostics.Value)
			{
				PlayerHandler obj = PlayerHandler.Get();
				List<Player> list = ((obj != null) ? obj.PlayerList() : null);
				int num = list?.Count ?? (-1);
				int num2 = list?.Count((Player p) => p.IsLocalPlayer) ?? (-1);
				int num3 = list?.Count((Player p) => p.IsAlive) ?? (-1);
				bool[] value = Traverse.Create(typeof(CharacterSelectBox)).Field<bool[]>("occupiedRectangles").Value;
				int[] value2 = Traverse.Create(typeof(CharacterSelectBox)).Field<int[]>("deviceIds").Value;
				CharacterSelectHandler val = Object.FindObjectOfType<CharacterSelectHandler>();
				CharacterSelectBox[] array = (((Object)(object)val == (Object)null) ? null : Traverse.Create((object)val).Field<CharacterSelectBox[]>("characterSelectBoxes").Value);
				GameSessionHandler val2 = Object.FindObjectOfType<GameSessionHandler>();
				SlimeController[] array2 = (((Object)(object)val2 == (Object)null) ? null : Traverse.Create((object)val2).Field<SlimeController[]>("slimeControllers").Value);
				Log($"{context}: players={num}, localPlayers={num2}, alivePlayers={num3}, " + "occupiedSlots=" + FormatBoolArray(value) + ", deviceSlots=" + FormatIntArray(value2) + ", " + $"charBoxes={((array != null) ? array.Length : (-1))}, slimeControllers={((array2 != null) ? array2.Length : (-1))}, " + $"targetPlayers={Plugin.TargetPlayerCount}, slotArrayLength={Plugin.SlotArrayLength}", verbose);
			}
		}

		private static string FormatBoolArray(bool[]? values)
		{
			if (values == null)
			{
				return "null";
			}
			return string.Format("len={0} [{1}]", values.Length, string.Join(",", values.Select((bool v) => v ? 1 : 0)));
		}

		private static string FormatIntArray(int[]? values)
		{
			if (values == null)
			{
				return "null";
			}
			return string.Format("len={0} [{1}]", values.Length, string.Join(",", values));
		}
	}
}
namespace BoplMorePlayersLocal8.Patches
{
	[HarmonyPatch(typeof(HandleAbilitySelectUI), "Init")]
	internal static class HandleAbilitySelectUiSpacingPatch
	{
		[HarmonyPrefix]
		private static void Prefix(HandleAbilitySelectUI __instance, ref float __state)
		{
			Traverse<float> val = Traverse.Create((object)__instance).Field<float>("Separation");
			__state = val.Value;
			if (Plugin.CompressMidRoundSelectorSpacing.Value)
			{
				PlayerHandler obj = PlayerHandler.Get();
				int num = ((obj != null) ? obj.NumberOfPlayers() : 0);
				if (num > 4)
				{
					float num2 = 4f / (float)num;
					RuntimeSnapshot.Log(string.Format(arg2: val.Value = Mathf.Max(48f, __state * num2), format: "Compressed mid-round selector separation for {0} players: {1} -> {2}.", arg0: num, arg1: __state), verbose: false);
				}
			}
		}

		[HarmonyPostfix]
		private static void Postfix(HandleAbilitySelectUI __instance, float __state)
		{
			Traverse.Create((object)__instance).Field<float>("Separation").Value = __state;
		}
	}
	[HarmonyPatch(typeof(PlayerAverageCamera), "Start")]
	internal static class PlayerAverageCameraStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerAverageCamera __instance)
		{
			if (Plugin.IncreaseCameraZoomForCrowdedMatches.Value)
			{
				PlayerHandler obj = PlayerHandler.Get();
				int num = ((obj != null) ? obj.NumberOfPlayers() : 0);
				if (num > 4)
				{
					Traverse val = Traverse.Create((object)__instance);
					float value = val.Field<float>("MAX_ZOOM").Value;
					float value2 = val.Field<float>("MIN_ZOOM").Value;
					float value3 = val.Field<float>("extraZoomRoom").Value;
					float num2 = Mathf.Lerp(1.2f, 1.6f, (float)(num - 4) / 4f);
					float num3 = value * num2;
					float num4 = value2 * 1.05f;
					float num5 = value3 + (float)(num - 4) * 0.25f;
					val.Field<float>("MAX_ZOOM").Value = num3;
					val.Field<float>("MIN_ZOOM").Value = num4;
					val.Field<float>("extraZoomRoom").Value = num5;
					RuntimeSnapshot.Log($"Expanded camera zoom for {num} players (MIN_ZOOM {value2}->{num4}, MAX_ZOOM {value}->{num3}, extraZoomRoom {value3}->{num5}).", verbose: false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterStatsList), "OnEnable")]
	internal static class CharacterStatsListDrawSlotsPatch
	{
		[HarmonyPrefix]
		private static void Prefix(CharacterStatsList __instance)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.ExpandDrawWinnerUiSlots.Value)
			{
				PlayerHandler obj = PlayerHandler.Get();
				int num = ((obj != null) ? obj.NumberOfPlayers() : 0);
				if (num > 4)
				{
					EnsureImageArrayCapacity(__instance, "winnersOfDraw", num, Vector2.right, 75f);
				}
			}
		}

		private static void EnsureImageArrayCapacity(CharacterStatsList owner, string fieldName, int required, Vector2 direction, float spacing)
		{
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			Traverse<Image[]> val = Traverse.Create((object)owner).Field<Image[]>(fieldName);
			Image[] value = val.Value;
			if (value == null || value.Length == 0 || value.Length >= required)
			{
				return;
			}
			List<Image> list = value.ToList();
			Image val2 = value[^1];
			RectTransform component = ((Component)val2).GetComponent<RectTransform>();
			for (int i = list.Count; i < required; i++)
			{
				GameObject val3 = Object.Instantiate<GameObject>(((Component)val2).gameObject, ((Component)val2).transform.parent);
				((Object)val3).name = $"{((Object)((Component)val2).gameObject).name}_P{i + 1}";
				Image component2 = val3.GetComponent<Image>();
				if (!((Object)(object)component2 == (Object)null))
				{
					RectTransform component3 = val3.GetComponent<RectTransform>();
					if ((Object)(object)component3 != (Object)null && (Object)(object)component != (Object)null)
					{
						component3.anchoredPosition = component.anchoredPosition + direction * spacing * (float)(i - (value.Length - 1));
					}
					val3.SetActive(false);
					list.Add(component2);
				}
			}
			val.Value = list.ToArray();
			RuntimeSnapshot.Log($"Expanded {fieldName} from {value.Length} to {list.Count}.", verbose: false);
		}
	}
	[HarmonyPatch(typeof(GameSessionHandler), "SpawnPlayers")]
	internal static class GameSessionSpawnDiagnosticsPatch
	{
		[HarmonyPostfix]
		private static void Postfix(GameSessionHandler __instance)
		{
			PlayerHandler obj = PlayerHandler.Get();
			List<Player> list = ((obj != null) ? obj.PlayerList() : null) ?? new List<Player>();
			SlimeController[] value = Traverse.Create((object)__instance).Field<SlimeController[]>("slimeControllers").Value;
			object arg = list.Count;
			object arg2 = ((value != null) ? value.Length : (-1));
			PlayerHandler obj2 = PlayerHandler.Get();
			RuntimeSnapshot.Log($"SpawnPlayers complete: players={arg}, slimes={arg2}, teamsLeft={((obj2 != null) ? obj2.TeamsLeft() : (-1))}.", verbose: false);
			RuntimeSnapshot.LogGlobalState("GameSessionHandler.SpawnPlayers", verbose: true);
		}
	}
	[HarmonyPatch(typeof(CharacterSelectHandler), "Awake")]
	internal static class CharacterSelectHandlerAwakePatch
	{
		[HarmonyPostfix]
		private static void Postfix(CharacterSelectHandler __instance)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			CharacterSelectExpander.Expand(__instance);
			ReadOnlyArray<Gamepad> all = Gamepad.all;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"Connected gamepads: {all.Count}");
			for (int i = 0; i < all.Count; i++)
			{
				stringBuilder.Append($"\n  [{i}] id={((InputDevice)all[i]).deviceId} name='{((InputControl)all[i]).displayName}'");
			}
			RuntimeSnapshot.Log(stringBuilder.ToString(), verbose: false);
			CharSelectClickToJoin[] array = Object.FindObjectsOfType<CharSelectClickToJoin>(true);
			RuntimeSnapshot.Log($"CharSelectClickToJoin instances: {array.Length} " + "[" + string.Join(", ", array.Select((CharSelectClickToJoin c) => $"box{c.csb?.RectangleIndex}(active={((Component)c).gameObject.activeInHierarchy})")) + "]", verbose: false);
			RuntimeSnapshot.LogGlobalState("CharacterSelectHandler.Awake.Postfix", verbose: false);
		}
	}
	[HarmonyPatch(typeof(CharSelectClickToJoin), "CurrentlyActiveIndex")]
	internal static class CharSelectClickToJoinCurrentIndexPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(ref int __result)
		{
			bool[] value = Traverse.Create(typeof(CharacterSelectBox)).Field<bool[]>("occupiedRectangles").Value;
			if (value == null || value.Length == 0)
			{
				return true;
			}
			int num = Mathf.Min(Plugin.TargetPlayerCount, Mathf.Max(0, value.Length - 1));
			for (int i = 0; i < num; i++)
			{
				if (!value[i])
				{
					__result = i;
					return false;
				}
			}
			__result = value.Length - 1;
			return false;
		}
	}
	[HarmonyPatch(typeof(CharSelectClickToJoin), "LateUpdate")]
	internal static class CharSelectClickToJoinExpandedSlotsPatch
	{
		[HarmonyPostfix]
		private static void Postfix(CharSelectClickToJoin __instance)
		{
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Invalid comparison between Unknown and I4
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			if (__instance.csb.RectangleIndex != 0)
			{
				return;
			}
			bool[] occupiedRectangles = CharacterSelectBox.occupiedRectangles;
			if (occupiedRectangles == null)
			{
				return;
			}
			int num = -1;
			int num2 = Mathf.Min(Plugin.TargetPlayerCount, Mathf.Max(0, occupiedRectangles.Length - 1));
			for (int i = 0; i < num2; i++)
			{
				if (!occupiedRectangles[i])
				{
					num = i;
					break;
				}
			}
			if (num < 4)
			{
				return;
			}
			CharacterSelectHandler value = Traverse.Create(typeof(CharacterSelectHandler)).Field<CharacterSelectHandler>("selfRef").Value;
			if ((Object)(object)value == (Object)null)
			{
				return;
			}
			CharacterSelectBox[] value2 = Traverse.Create((object)value).Field<CharacterSelectBox[]>("characterSelectBoxes").Value;
			if (value2 == null || num >= value2.Length)
			{
				return;
			}
			CharacterSelectBox val = value2[num];
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			CharSelectMenu value3 = Traverse.Create((object)val).Field<CharSelectMenu>("menuState").Value;
			if ((int)value3 > 0)
			{
				return;
			}
			if (!CharacterSelectBox.keyboardMouseIsOccupied)
			{
				Keyboard current = Keyboard.current;
				if (current != null && ((ButtonControl)current.anyKey).wasPressedThisFrame && !((ButtonControl)current.escapeKey).wasPressedThisFrame)
				{
					CharacterSelectBox.deviceIds[num] = 0;
					val.UseskeyboardMouse = true;
					CharacterSelectBox.keyboardMouseIsOccupied = true;
					val.OnEnterSelect();
					AudioManager.Get().Play("accept");
					RuntimeSnapshot.Log($"Expanded slot join: keyboard → slot {num}.", verbose: false);
					return;
				}
			}
			int[] deviceIds = CharacterSelectBox.deviceIds;
			Enumerator<Gamepad> enumerator = Gamepad.all.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Gamepad current2 = enumerator.Current;
					bool flag = false;
					for (int j = 0; j < deviceIds.Length; j++)
					{
						if (deviceIds[j] == ((InputDevice)current2).deviceId)
						{
							flag = true;
							break;
						}
					}
					if (flag || (!current2.startButton.wasPressedThisFrame && !current2.aButton.wasPressedThisFrame))
					{
						continue;
					}
					CharacterSelectBox.deviceIds[num] = ((InputDevice)current2).deviceId;
					val.UseskeyboardMouse = false;
					val.currentGamePad = current2;
					val.OnEnterSelect();
					AudioManager.Get().Play("accept");
					RuntimeSnapshot.Log($"Expanded slot join: gamepad {((InputDevice)current2).deviceId} → slot {num}.", verbose: false);
					break;
				}
			}
			finally
			{
				((IDisposable)enumerator).Dispose();
			}
		}
	}
	[HarmonyPatch(typeof(CharacterSelectHandler), "IsColorTaken")]
	internal static class CharacterSelectColorLimitPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(int __0, CharacterSelectBox __1, ref bool __result)
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			CharacterSelectBox __2 = __1;
			if (!Plugin.RelaxColorUniquenessWhenFull.Value || GameLobby.isOnlineGame)
			{
				return true;
			}
			try
			{
				CharacterSelectHandler value = Traverse.Create(typeof(CharacterSelectHandler)).Field<CharacterSelectHandler>("selfRef").Value;
				if ((Object)(object)value == (Object)null)
				{
					return true;
				}
				CharacterSelectBox[] value2 = Traverse.Create((object)value).Field<CharacterSelectBox[]>("characterSelectBoxes").Value;
				PlayerColors value3 = Traverse.Create((object)value).Field<PlayerColors>("playerColors").Value;
				int num = ((value3 != null) ? value3.Length : 0);
				if (value2 == null || value2.Length == 0 || num <= 0)
				{
					return true;
				}
				CharSelectMenu readyState = (CharSelectMenu)2;
				int num2 = value2.Count((CharacterSelectBox box) => (Object)(object)box != (Object)null && (Object)(object)box != (Object)(object)__2 && Traverse.Create((object)box).Field<CharSelectMenu>("menuState").Value == readyState);
				if (num2 < num)
				{
					return true;
				}
				__result = false;
				RuntimeSnapshot.Log($"Color uniqueness relaxed (readyPlayers={num2}, availableColors={num}, requestedColor={__0}).", verbose: false);
				return false;
			}
			catch (Exception ex)
			{
				RuntimeSnapshot.Log("Color uniqueness relaxation failed: " + ex.Message, verbose: false);
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(CharacterSelectBox), "OnEnterSelect")]
	internal static class CharacterSelectJoinDiagnosticsPatch
	{
		[HarmonyPostfix]
		private static void Postfix(CharacterSelectBox __instance)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			RuntimeSnapshot.Log(string.Format("Player joined slot={0}, usesKeyboard={1}, menuState={2}.", __instance.RectangleIndex, __instance.UseskeyboardMouse, Traverse.Create((object)__instance).Field<CharSelectMenu>("menuState").Value), verbose: false);
			RuntimeSnapshot.LogGlobalState("CharacterSelectBox.OnEnterSelect", verbose: true);
		}
	}
	internal static class CharacterSelectExpander
	{
		internal static void Expand(CharacterSelectHandler handler)
		{
			CharacterSelectBox[] array = Traverse.Create((object)handler).Field<CharacterSelectBox[]>("characterSelectBoxes").Value;
			if (array == null || array.Length == 0)
			{
				RuntimeSnapshot.Log("CharacterSelectExpander: characterSelectBoxes missing.", verbose: false);
				return;
			}
			EnsureSlotArrays();
			int targetPlayerCount = Plugin.TargetPlayerCount;
			int num = array.Length;
			if (array.Length < targetPlayerCount)
			{
				array = ExpandBoxes(handler, array, targetPlayerCount);
				Traverse.Create((object)handler).Field<CharacterSelectBox[]>("characterSelectBoxes").Value = array;
				RuntimeSnapshot.Log($"Expanded characterSelectBoxes from {num} to {array.Length}.", verbose: false);
			}
			EnsureAnimateOutDelays(handler, array.Length);
			if (Plugin.RepositionCharacterSelectBoxes.Value)
			{
				RepositionBoxes(handler, array, num);
			}
		}

		private static void EnsureSlotArrays()
		{
			int slotArrayLength = Plugin.SlotArrayLength;
			Traverse<bool[]> val = Traverse.Create(typeof(CharacterSelectBox)).Field<bool[]>("occupiedRectangles");
			bool[] array = val.Value ?? Array.Empty<bool>();
			if (array.Length < slotArrayLength)
			{
				bool[] array2 = new bool[slotArrayLength];
				Array.Copy(array, array2, array.Length);
				val.Value = array2;
				RuntimeSnapshot.Log($"Expanded occupiedRectangles from {array.Length} to {slotArrayLength}.", verbose: false);
			}
			Traverse<int[]> val2 = Traverse.Create(typeof(CharacterSelectBox)).Field<int[]>("deviceIds");
			int[] array3 = val2.Value ?? Array.Empty<int>();
			if (array3.Length < slotArrayLength)
			{
				int[] array4 = new int[slotArrayLength];
				Array.Copy(array3, array4, array3.Length);
				val2.Value = array4;
				RuntimeSnapshot.Log($"Expanded deviceIds from {array3.Length} to {slotArrayLength}.", verbose: false);
			}
		}

		private static CharacterSelectBox[] ExpandBoxes(CharacterSelectHandler handler, CharacterSelectBox[] existing, int targetCount)
		{
			List<CharacterSelectBox> list = existing.ToList();
			CharacterSelectBox val = existing[^1];
			Transform parent = ((Component)val).transform.parent;
			for (int i = existing.Length; i < targetCount; i++)
			{
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
				((Object)val2).name = $"{((Object)((Component)val).gameObject).name}_P{i + 1}";
				CharacterSelectBox component = val2.GetComponent<CharacterSelectBox>();
				if ((Object)(object)component == (Object)null)
				{
					RuntimeSnapshot.Log($"CharacterSelectExpander: clone missing CharacterSelectBox for slot {i}.", verbose: false);
					continue;
				}
				component.RectangleIndex = i;
				component.UseskeyboardMouse = false;
				component.currentGamePad = null;
				Traverse.Create((object)component).Field<int>("selectedIndex").Value = 0;
				Traverse.Create((object)component).Field<CharSelectMenu>("menuState").Value = (CharSelectMenu)0;
				RebindNestedCharacterSelectReferences(component);
				CharSelectClickToJoin componentInChildren = val2.GetComponentInChildren<CharSelectClickToJoin>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					Object.Destroy((Object)(object)componentInChildren);
					RuntimeSnapshot.Log($"Destroyed duplicate CharSelectClickToJoin on clone slot {i}.", verbose: false);
				}
				component.OnEnterJoin();
				list.Add(component);
				RuntimeSnapshot.Log($"Created character select slot {i + 1} from template '{((Object)val).name}'.", verbose: false);
			}
			return list.ToArray();
		}

		private static void RebindNestedCharacterSelectReferences(CharacterSelectBox owner)
		{
			Type typeFromHandle = typeof(CharacterSelectBox);
			Component[] componentsInChildren = ((Component)owner).GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					FieldInfo field = ((object)val).GetType().GetField("csb", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (!(field == null) && !(field.FieldType != typeFromHandle))
					{
						field.SetValue(val, owner);
					}
				}
			}
		}

		private static void EnsureAnimateOutDelays(CharacterSelectHandler handler, int targetLength)
		{
			Traverse<float[]> val = Traverse.Create((object)handler).Field<float[]>("animateOutDelays");
			float[] array = val.Value ?? Array.Empty<float>();
			if (array.Length >= targetLength)
			{
				return;
			}
			float[] array2 = new float[targetLength];
			Array.Copy(array, array2, array.Length);
			float num = 0.05f;
			if (array.Length >= 2)
			{
				num = Mathf.Abs(array[^1] - array[^2]);
				if (num < 0.005f)
				{
					num = 0.05f;
				}
			}
			float num2 = ((array.Length != 0) ? array[^1] : 0f);
			for (int i = array.Length; i < array2.Length; i++)
			{
				array2[i] = num2 + (float)(i - array.Length + 1) * num;
			}
			val.Value = array2;
			RuntimeSnapshot.Log($"Expanded animateOutDelays from {array.Length} to {array2.Length}.", verbose: false);
		}

		private static void RepositionBoxes(CharacterSelectHandler handler, CharacterSelectBox[] boxes, int originalCount)
		{
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			if (boxes.Length <= originalCount || originalCount <= 0)
			{
				return;
			}
			RectTransform[] array = (from box in boxes.Take(originalCount)
				select ((Component)box).GetComponent<RectTransform>() into rect
				where (Object)(object)rect != (Object)null
				select rect).Cast<RectTransform>().ToArray();
			if (array.Length == 0)
			{
				return;
			}
			int num = array.Length;
			int num2 = Mathf.CeilToInt((float)boxes.Length / (float)num);
			float num3 = array.Min((RectTransform rect) => rect.anchoredPosition.x);
			float num4 = array.Max((RectTransform rect) => rect.anchoredPosition.x);
			float num5 = (num3 + num4) * 0.5f;
			float num6 = array.Average((RectTransform rect) => rect.anchoredPosition.y);
			float num7 = boxes.Take(originalCount).Select(MeasureJoinCardWidth).DefaultIfEmpty(260f)
				.Max();
			float num8 = Mathf.Max(num4 - num3, num7 * 3.2f);
			float num9 = num7 * 0.15f;
			float num10 = num8 + num9 * 2f;
			int num11 = boxes.Length;
			float num12 = ((num11 <= 1) ? 0f : (num10 / (float)(num11 - 1)));
			float num13 = num12 * 0.93f;
			float num14 = Mathf.Clamp(num13 / num7, 0.4f, 1f);
			float num15 = num5 - num12 * (float)(num11 - 1) * 0.5f;
			float num16 = num6;
			List<string> list = new List<string>(num11);
			for (int i = 0; i < num11; i++)
			{
				RectTransform component = ((Component)boxes[i]).GetComponent<RectTransform>();
				if (!((Object)(object)component == (Object)null))
				{
					float num17 = num15 + (float)i * num12;
					component.anchoredPosition = new Vector2(num17, num16);
					((Transform)component).localScale = Vector3.one;
					ScaleVisibleChildren(boxes[i], num14);
					list.Add($"slot={i + 1} x={num17:0.0} y={num16:0.0} scale={num14:0.000}");
				}
			}
			RuntimeSnapshot.Log($"Repositioned character select to single row: slots={num11}, baseColumns={num}, rowsBeforeCompression={num2}, centerX={num5:0.0}, centerY={num6:0.0}, rowY={num16:0.0}, availableWidth={num10:0.0}, spacing={num12:0.0}, templateVisualWidth={num7:0.0}, desiredVisualWidth={num13:0.0}, scale={num14:0.000}.", verbose: false);
			RuntimeSnapshot.Log("Row placement details: " + string.Join(" | ", list), verbose: false);
		}

		private static float MeasureJoinCardWidth(CharacterSelectBox box)
		{
			//IL_00a6: 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_0057: 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_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: 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)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			RectTransform value = Traverse.Create((object)box).Field<RectTransform>("joinBorder").Value;
			Rect rect;
			if ((Object)(object)value != (Object)null)
			{
				Transform parent = ((Transform)value).parent;
				if ((Object)(object)parent == (Object)null)
				{
					rect = value.rect;
					return Mathf.Max(220f, ((Rect)(ref rect)).width);
				}
				Bounds val = RectTransformUtility.CalculateRelativeRectTransformBounds(parent, (Transform)(object)value);
				rect = value.rect;
				return Mathf.Max(((Rect)(ref rect)).width, ((Bounds)(ref val)).size.x);
			}
			RectTransform component = ((Component)box).GetComponent<RectTransform>();
			if ((Object)(object)component == (Object)null)
			{
				return 260f;
			}
			rect = component.rect;
			return Mathf.Max(220f, ((Rect)(ref rect)).width * 2f);
		}

		private static void ScaleVisibleChildren(CharacterSelectBox box, float scale)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			if (!(scale <= 0f) && !Mathf.Approximately(scale, 1f))
			{
				Vector3 localScale = Vector3.one * scale;
				List<string> list = new List<string>();
				for (int i = 0; i < ((Component)box).transform.childCount; i++)
				{
					Transform child = ((Component)box).transform.GetChild(i);
					child.localScale = localScale;
					list.Add(((Object)child).name);
				}
				RuntimeSnapshot.Log(string.Format("Box '{0}' scaled all {1} children [{2}] to {3:0.000}.", ((Object)box).name, list.Count, string.Join(",", list), scale), verbose: false);
			}
		}
	}
	[HarmonyPatch(typeof(GameSessionHandler), "LoadAbilitySelectScene")]
	internal static class LoadAbilitySelectDiagnosticsPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			RuntimeSnapshot.Log("GameSessionHandler.LoadAbilitySelectScene invoked.", verbose: false);
			RuntimeSnapshot.LogGlobalState("LoadAbilitySelectScene", verbose: true);
		}
	}
	[HarmonyPatch(typeof(GameSessionHandler), "LoadNextLevelScene")]
	internal static class LoadNextLevelDiagnosticsPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			RuntimeSnapshot.Log("GameSessionHandler.LoadNextLevelScene invoked.", verbose: false);
			RuntimeSnapshot.LogGlobalState("LoadNextLevelScene", verbose: true);
		}
	}
	[HarmonyPatch(typeof(CharacterSelectHandler), "TryStartGame_inner")]
	internal static class CharacterSelectStartDiagnosticsPatch
	{
		[HarmonyPrefix]
		private static void Prefix(CharacterSelectHandler __instance)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			CharacterSelectBox[] value = Traverse.Create((object)__instance).Field<CharacterSelectBox[]>("characterSelectBoxes").Value;
			CharSelectMenu readyState = (CharSelectMenu)2;
			int num = value?.Count((CharacterSelectBox box) => (Object)(object)box != (Object)null && Traverse.Create((object)box).Field<CharSelectMenu>("menuState").Value == readyState) ?? (-1);
			RuntimeSnapshot.Log(string.Format("TryStartGame_inner: boxes={0}, ready={1}, startButton={2}.", (value != null) ? value.Length : (-1), num, Traverse.Create(typeof(CharacterSelectHandler)).Field<bool>("startButtonAvailable").Value), verbose: false);
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			RuntimeSnapshot.LogGlobalState("TryStartGame_inner.Postfix", verbose: true);
		}
	}
	[HarmonyPatch(typeof(CharSelectClickToJoin), "LateUpdate")]
	internal static class CharSelectClickToJoinDiagnosticsPatch
	{
		private static float _lastLogTime;

		[HarmonyPostfix]
		private static void Postfix(CharSelectClickToJoin __instance)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time - _lastLogTime < 5f)
			{
				return;
			}
			_lastLogTime = Time.time;
			CharacterSelectBox csb = __instance.csb;
			CharSelectMenu value = Traverse.Create((object)csb).Field<CharSelectMenu>("menuState").Value;
			int rectangleIndex = csb.RectangleIndex;
			int num = -1;
			bool[] value2 = Traverse.Create(typeof(CharacterSelectBox)).Field<bool[]>("occupiedRectangles").Value;
			if (value2 != null)
			{
				int num2 = Mathf.Min(Plugin.TargetPlayerCount, Mathf.Max(0, value2.Length - 1));
				for (int i = 0; i < num2; i++)
				{
					if (!value2[i])
					{
						num = i;
						break;
					}
				}
				if (num == -1)
				{
					num = value2.Length - 1;
				}
			}
			bool flag = num == rectangleIndex;
			int count = Gamepad.all.Count;
			int num3 = 0;
			int[] value3 = Traverse.Create(typeof(CharacterSelectBox)).Field<int[]>("deviceIds").Value;
			if (value3 != null)
			{
				int[] array = value3;
				for (int j = 0; j < array.Length; j++)
				{
					if (array[j] != 0)
					{
						num3++;
					}
				}
			}
			RuntimeSnapshot.Log($"[ClickToJoin] box={rectangleIndex} menuState={value} activeIndex={num} responds={flag} " + string.Format("gamepads={0} assigned={1} occupied=[{2}]", count, num3, string.Join(",", value2?.Select((bool o) => o ? "1" : "0") ?? Array.Empty<string>())), verbose: false);
		}
	}
}