Decompiled source of BallSwapItem v1.0.1

plugins/com.github.BETCGaming.BallSwapItem.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FMOD;
using FMOD.Studio;
using FMODUnity;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("GameAssembly")]
[assembly: IgnoresAccessChecksTo("SharedAssembly")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.github.BETCGaming.BallSwapItem")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+e2919cd6113bc7dbe8ada3ebbffb6ea78be3d52a")]
[assembly: AssemblyProduct("com.github.BETCGaming.BallSwapItem")]
[assembly: AssemblyTitle("BallSwapItem")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace BallSwapItem
{
	internal static class MirrorSerializers
	{
		public static void Register()
		{
			Writer<SwitcherooRequestMessage>.write = delegate(NetworkWriter writer, SwitcherooRequestMessage message)
			{
				writer.Write<uint>(message.Token);
			};
			Reader<SwitcherooRequestMessage>.read = (NetworkReader reader) => new SwitcherooRequestMessage
			{
				Token = reader.Read<uint>()
			};
			Writer<SwitcherooHelloMessage>.write = delegate
			{
			};
			Reader<SwitcherooHelloMessage>.read = (NetworkReader _) => default(SwitcherooHelloMessage);
			Writer<SwitcherooArmedMessage>.write = delegate(NetworkWriter writer, SwitcherooArmedMessage message)
			{
				writer.Write<float>(message.WindUpSeconds);
				writer.Write<string>(message.UserName);
			};
			Reader<SwitcherooArmedMessage>.read = (NetworkReader reader) => new SwitcherooArmedMessage
			{
				WindUpSeconds = reader.Read<float>(),
				UserName = reader.Read<string>()
			};
			Writer<SwitcherooResultMessage>.write = delegate(NetworkWriter writer, SwitcherooResultMessage message)
			{
				writer.Write<int>(message.SwappedCount);
			};
			Reader<SwitcherooResultMessage>.read = (NetworkReader reader) => new SwitcherooResultMessage
			{
				SwappedCount = reader.Read<int>()
			};
			Writer<SwitcherooUseReplyMessage>.write = delegate(NetworkWriter writer, SwitcherooUseReplyMessage message)
			{
				writer.Write<bool>(message.Accepted);
				writer.Write<byte>(message.Reason);
				writer.Write<uint>(message.Token);
			};
			Reader<SwitcherooUseReplyMessage>.read = (NetworkReader reader) => new SwitcherooUseReplyMessage
			{
				Accepted = reader.Read<bool>(),
				Reason = reader.Read<byte>(),
				Token = reader.Read<uint>()
			};
			Writer<SwitcherooLockMessage>.write = delegate(NetworkWriter writer, SwitcherooLockMessage message)
			{
				writer.Write<bool>(message.Locked);
			};
			Reader<SwitcherooLockMessage>.read = (NetworkReader reader) => new SwitcherooLockMessage
			{
				Locked = reader.Read<bool>()
			};
			Plugin.Log.LogInfo((object)"Registered Switcheroo message serializers.");
		}
	}
	internal static class ModGate
	{
		private const float GraceSeconds = 10f;

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

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

		public static void Reset()
		{
			FirstSeen.Clear();
			Modded.Clear();
		}

		public static void MarkModded(NetworkConnectionToClient conn)
		{
			if (conn != null && Modded.Add(conn.connectionId))
			{
				Plugin.Log.LogInfo((object)$"Connection {conn.connectionId} has BallSwapItem installed.");
			}
		}

		public static void Tick()
		{
			if (!NetworkServer.active || !Plugin.BlockUnmodded.Value)
			{
				return;
			}
			float time = Time.time;
			List<NetworkConnectionToClient> list = null;
			foreach (NetworkConnectionToClient value2 in NetworkServer.connections.Values)
			{
				if (value2 != null && !Modded.Contains(value2.connectionId) && (object)value2 != NetworkServer.localConnection && ((NetworkConnection)value2).isAuthenticated)
				{
					if (!FirstSeen.TryGetValue(value2.connectionId, out var value))
					{
						FirstSeen[value2.connectionId] = time;
					}
					else if (time - value >= 10f)
					{
						(list ?? (list = new List<NetworkConnectionToClient>())).Add(value2);
					}
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (NetworkConnectionToClient item in list)
			{
				Plugin.Log.LogWarning((object)($"Disconnecting connection {item.connectionId}: BallSwapItem is not installed. " + "Set BlockUnmodded to false to allow unmodded players (the Switcheroo will then be withheld)."));
				FirstSeen.Remove(item.connectionId);
				((NetworkConnection)item).Disconnect();
			}
		}
	}
	internal sealed class ModRunner : MonoBehaviour
	{
		internal static ModRunner? Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		private void Start()
		{
			SwitcherooAudio.Load();
		}

		private void Update()
		{
			SwitcherooNetwork.EnsureHandlers();
			SwitcherooNetwork.TrackHeldItems();
			SwitcherooUi.Tick();
			SwitcherooLocalization.EnsureApplied();
			SwitcherooThrownItem.EnsureRegistered();
			ModGate.Tick();
			if (Plugin.DebugHotkeys.Value && Keyboard.current != null)
			{
				if (((ButtonControl)Keyboard.current[(Key)102]).wasPressedThisFrame)
				{
					GiveSwitcheroo();
				}
				if (((ButtonControl)Keyboard.current[(Key)103]).wasPressedThisFrame)
				{
					ForceSwap();
				}
			}
		}

		private static void GiveSwitcheroo()
		{
			if (!NetworkServer.active)
			{
				Plugin.Log.LogWarning((object)"F9 ignored: only the host can hand out items directly.");
				return;
			}
			PlayerInventory localPlayerInventory = GameManager.LocalPlayerInventory;
			if ((Object)(object)localPlayerInventory == (Object)null)
			{
				Plugin.Log.LogWarning((object)"F9 ignored: no local player inventory yet.");
				return;
			}
			bool flag = localPlayerInventory.ServerTryAddItem((ItemType)200, 1);
			Plugin.Log.LogInfo((object)(flag ? "Gave the local player a Switcheroo." : "Could not give a Switcheroo (inventory full?)."));
		}

		private static void ForceSwap()
		{
			if (!NetworkServer.active)
			{
				Plugin.Log.LogWarning((object)"F10 ignored: only the host can run a swap.");
				return;
			}
			string failureReason;
			int count = SwapService.TrySwap(out failureReason).Count;
			Plugin.Log.LogInfo((object)((count > 0) ? $"Swapped {count} balls." : ("Swap did not run: " + failureReason + ".")));
		}
	}
	[BepInPlugin("com.github.BETCGaming.BallSwapItem", "BallSwapItem", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Id = "com.github.BETCGaming.BallSwapItem";

		internal static ManualLogSource Log { get; private set; }

		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<float> WindUpSeconds { get; private set; }

		internal static ConfigEntry<bool> BlockUnmodded { get; private set; }

		internal static ConfigEntry<float> SoundVolume { get; private set; }

		internal static ConfigEntry<float> DenialVolume { get; private set; }

		internal static ConfigEntry<bool> DebugHotkeys { get; private set; }

		internal static ConfigEntry<bool> VerboseLogging { get; private set; }

		public static string Name => "BallSwapItem";

		public static string Version => "1.0.1";

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Expected O, but got Unknown
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the Switcheroo item. When off, the item is not registered and never spawns.");
			WindUpSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "WindUpSeconds", 3f, new ConfigDescription("Seconds between using the Switcheroo and the swap landing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			BlockUnmodded = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "BlockUnmodded", true, "Disconnect players who do not have this mod installed. They cannot handle the Switcheroo if the host hands them one. Host setting; ignored on clients.");
			SoundVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SoundVolume", 0.8f, new ConfigDescription("Volume of the sound played once a swap resolves. This plays outside the game's FMOD mix, so the in-game volume sliders do not affect it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DenialVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "DenialVolume", 0.25f, new ConfigDescription("Volume of the sound played when a Switcheroo use is refused because the swap has already been used this round.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DebugHotkeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugHotkeys", false, "F9 gives the local player a Switcheroo, F10 forces a swap. Host only, for testing.");
			MirrorSerializers.Register();
			SwitcherooLocalization.Initialize();
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Log every step of using the item and running a swap. For diagnosing a stuck or repeating use.");
			new Harmony("com.github.BETCGaming.BallSwapItem").PatchAll();
			((Component)this).gameObject.AddComponent<ModRunner>();
			Log.LogInfo((object)("Plugin " + Name + " v" + Version + " is loaded!"));
		}
	}
	internal static class SwapService
	{
		private static readonly Random Rng = new Random();

		private static readonly Action<GolfBall> UpdateNameTag = AccessTools.MethodDelegate<Action<GolfBall>>(AccessTools.Method(typeof(GolfBall), "UpdateNameTag", (Type[])null, (Type[])null), (object)null, true);

		public static List<PlayerGolfer> TrySwap(out string failureReason)
		{
			failureReason = string.Empty;
			if (!NetworkServer.active)
			{
				failureReason = "swap attempted off the server";
				return new List<PlayerGolfer>();
			}
			List<PlayerGolfer> list = CollectEligiblePlayers();
			if (list.Count < 2)
			{
				failureReason = $"only {list.Count} eligible ball(s) in play";
				return new List<PlayerGolfer>();
			}
			GolfBall[] array = (GolfBall[])(object)new GolfBall[list.Count];
			for (int i = 0; i < list.Count; i++)
			{
				array[i] = list[i].NetworkownBall;
			}
			Derange(array);
			for (int j = 0; j < list.Count; j++)
			{
				list[j].NetworkownBall = array[j];
				array[j].Networkowner = list[j];
				UpdateNameTag(array[j]);
			}
			return list;
		}

		public static int CountEligible()
		{
			return CollectEligiblePlayers().Count;
		}

		private static List<PlayerGolfer> CollectEligiblePlayers()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			List<PlayerGolfer> list = new List<PlayerGolfer>();
			foreach (PlayerGolfer matchParticipant in CourseManager.MatchParticipants)
			{
				if (!((Object)(object)matchParticipant == (Object)null) && !((Object)(object)matchParticipant.NetworkownBall == (Object)null) && (int)matchParticipant.MatchResolution == 0)
				{
					GolfBall networkownBall = matchParticipant.NetworkownBall;
					if (!networkownBall.isInHole && (int)networkownBall.OutOfBoundsReturnState == 0 && HasTeedOff(matchParticipant))
					{
						list.Add(matchParticipant);
					}
				}
			}
			return list;
		}

		private static bool HasTeedOff(PlayerGolfer golfer)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			PlayerInfo playerInfo = golfer.PlayerInfo;
			if ((Object)(object)playerInfo == (Object)null)
			{
				return false;
			}
			PlayerState val = default(PlayerState);
			if (CourseManager.TryGetPlayerState(playerInfo.PlayerId.Guid, ref val))
			{
				return val.matchStrokes > 0;
			}
			return false;
		}

		private static void Derange<T>(T[] items)
		{
			T[] array = (T[])items.Clone();
			for (int i = 0; i < 100; i++)
			{
				for (int num = items.Length - 1; num > 0; num--)
				{
					int num2 = Rng.Next(num + 1);
					int num3 = num;
					int num4 = num2;
					T val = items[num2];
					T val2 = items[num];
					items[num3] = val;
					items[num4] = val2;
				}
				bool flag = false;
				for (int j = 0; j < items.Length; j++)
				{
					if ((object)items[j] == (object)array[j])
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return;
				}
			}
			Array.Copy(array, items, array.Length);
			T val3 = items[^1];
			Array.Copy(array, 0, items, 1, array.Length - 1);
			items[0] = val3;
		}
	}
	internal static class Switcheroo
	{
		public const ItemType Type = (ItemType)200;

		public const string DisplayName = "Switcheroo";

		public const Rule OncePerHoleRule = (Rule)200;

		internal static readonly Color DeviceColor = new Color(1f, 0.4117647f, 0.7058824f, 1f);

		private const uint NetworkAssetId = 3047489537u;

		private static ItemData? itemData;

		private static bool failedOnce;

		private static GameObject? prefabHolder;

		private static GameObject PrefabHolder
		{
			get
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Expected O, but got Unknown
				if ((Object)(object)prefabHolder == (Object)null)
				{
					prefabHolder = new GameObject("BallSwapItemPrefabs");
					prefabHolder.SetActive(false);
					Object.DontDestroyOnLoad((Object)(object)prefabHolder);
				}
				return prefabHolder;
			}
		}

		public static void Register(ItemCollection collection)
		{
			if (!Plugin.Enabled.Value || failedOnce)
			{
				return;
			}
			try
			{
				if (itemData == null)
				{
					itemData = BuildItemData(collection);
				}
				if (itemData != null)
				{
					if (Array.IndexOf(collection.items, itemData) < 0)
					{
						ItemData[] array = collection.items;
						Array.Resize(ref array, array.Length + 1);
						array[^1] = itemData;
						collection.items = array;
					}
					collection.allItemData[(ItemType)200] = itemData;
				}
			}
			catch (Exception arg)
			{
				failedOnce = true;
				Plugin.Log.LogError((object)$"Failed to register the Switcheroo item, it will not appear: {arg}");
			}
		}

		private static ItemData? BuildItemData(ItemCollection collection)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			ItemData val = default(ItemData);
			if (!collection.TryGetItemData((ItemType)10, ref val))
			{
				Plugin.Log.LogError((object)"Orbital Laser item data not found; cannot build the Switcheroo from it.");
				return null;
			}
			if ((Object)(object)val.Prefab == (Object)null)
			{
				return null;
			}
			ItemData val2 = new ItemData();
			FieldInfo[] fields = typeof(ItemData).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				fieldInfo.SetValue(val2, fieldInfo.GetValue(val));
			}
			val2.Type = (ItemType)200;
			val2.Icon = BuildIcon();
			val2.Prefab = BuildPrefab(val.Prefab);
			val2.MaxUses = 1;
			val2.NonAimUse = (ItemNonAimingUse)2;
			val2.CanUsageAffectBalls = false;
			val2.CanUsageAffectTeammateBalls = false;
			val2.IsExplosive = false;
			val2.CanBreakBreakableIce = false;
			val2.CanHitProjectiles = false;
			val2.HitInfoFeedMessageIcon = (InfoFeedIconType)25;
			val2.name = null;
			val2.Initialize();
			Plugin.Log.LogInfo((object)$"Built Switcheroo item data as ItemType {200}.");
			return val2;
		}

		private static GameObject BuildPrefab(GameObject donorPrefab)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			bool activeSelf = donorPrefab.activeSelf;
			donorPrefab.SetActive(false);
			GameObject val;
			try
			{
				val = Object.Instantiate<GameObject>(donorPrefab);
			}
			finally
			{
				donorPrefab.SetActive(activeSelf);
			}
			((Object)val).name = "SwitcherooItem";
			PhysicalItem val2 = default(PhysicalItem);
			if (val.TryGetComponent<PhysicalItem>(ref val2))
			{
				val2.itemType = (ItemType)200;
			}
			else
			{
				Plugin.Log.LogWarning((object)"Switcheroo pickup has no PhysicalItem component.");
			}
			val.transform.SetParent(PrefabHolder.transform, false);
			val.SetActive(true);
			Recolour(val);
			RegisterNetworkPrefab(val);
			return val;
		}

		private static void RegisterNetworkPrefab(GameObject prefab)
		{
			NetworkIdentity val = default(NetworkIdentity);
			if (!prefab.TryGetComponent<NetworkIdentity>(ref val))
			{
				Plugin.Log.LogWarning((object)"Switcheroo pickup has no NetworkIdentity; it will not spawn when dropped.");
				return;
			}
			FieldInfo fieldInfo = AccessTools.Field(typeof(NetworkIdentity), "_assetId");
			if ((object)fieldInfo == null)
			{
				Plugin.Log.LogWarning((object)"Mirror's assetId field was not found; dropped Switcheroos may appear as Orbital Lasers.");
				return;
			}
			fieldInfo.SetValue(val, 0u);
			NetworkClient.RegisterPrefab(prefab, 3047489537u);
			Plugin.Log.LogInfo((object)$"Registered the Switcheroo pickup as network asset {val.assetId}.");
		}

		public static void EnsureNetworkPrefabRegistered()
		{
			ItemData? obj = itemData;
			if (!((Object)(object)((obj != null) ? obj.Prefab : null) == (Object)null))
			{
				NetworkClient.RegisterPrefab(itemData.Prefab, 3047489537u);
			}
		}

		internal static void Recolour(GameObject target)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			Renderer[] componentsInChildren = target.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				Material[] materials = val.materials;
				foreach (Material val2 in materials)
				{
					if (val2.HasProperty("_BaseColor"))
					{
						val2.SetColor("_BaseColor", DeviceColor);
					}
					if (val2.HasProperty("_Color"))
					{
						val2.SetColor("_Color", DeviceColor);
					}
				}
			}
		}

		private static Sprite? BuildIcon()
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			using Stream stream = typeof(Switcheroo).Assembly.GetManifestResourceStream("BallSwapItem.Assets.switcheroo-icon.png");
			if (stream == null)
			{
				Plugin.Log.LogWarning((object)"Embedded Switcheroo icon missing; the item will show no icon.");
				return null;
			}
			byte[] array = new byte[stream.Length];
			stream.Read(array, 0, array.Length);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			if (!ImageConversion.LoadImage(val, array))
			{
				Plugin.Log.LogWarning((object)"Switcheroo icon failed to decode; the item will show no icon.");
				return null;
			}
			((Object)val).name = "SwitcherooIcon";
			Object.DontDestroyOnLoad((Object)(object)val);
			Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
			((Object)val2).name = "SwitcherooIcon";
			Object.DontDestroyOnLoad((Object)(object)val2);
			return val2;
		}
	}
	[HarmonyPatch(typeof(ItemCollection), "Initialize")]
	internal static class ItemCollectionInitializePatch
	{
		private static void Postfix(ItemCollection __instance)
		{
			Switcheroo.Register(__instance);
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class ItemDataNamePatch
	{
		private static bool Prefix(ItemData __instance, ref string __result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			if ((int)__instance.Type != 200)
			{
				return true;
			}
			__result = "Switcheroo";
			return false;
		}
	}
	internal static class SwitcherooAudio
	{
		private const string ResourceName = "BallSwapItem.Assets.bomboclat.mp3";

		private static Sound sound;

		private static bool loaded;

		private static bool failed;

		public static void Load()
		{
			EnsureSound();
		}

		public static void PlayDenial()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0031: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				EventInstance val = RuntimeManager.CreateInstance(GameManager.AudioSettings.CosmeticsButtonSelectDisabled);
				((EventInstance)(ref val)).setVolume(Mathf.Clamp01(Plugin.DenialVolume.Value));
				((EventInstance)(ref val)).start();
				((EventInstance)(ref val)).release();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not play the denial sound: " + ex.Message));
			}
		}

		public static void PlayAnticipation()
		{
			//IL_0005: 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_0012: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				RuntimeManager.PlayOneShot(GameManager.AudioSettings.OrbitalLaserAnticipationEvent, default(Vector3));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not play the wind-up cue: " + ex.Message));
			}
		}

		public static void PlayPayoff()
		{
			//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)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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)
			if (EnsureSound())
			{
				System coreSystem = RuntimeManager.CoreSystem;
				Channel val2 = default(Channel);
				RESULT val = ((System)(ref coreSystem)).playSound(sound, default(ChannelGroup), false, ref val2);
				if ((int)val != 0)
				{
					Plugin.Log.LogWarning((object)$"Could not play the swap sound: {val}");
				}
				else
				{
					((Channel)(ref val2)).setVolume(Mathf.Clamp01(Plugin.SoundVolume.Value));
				}
			}
		}

		private static bool EnsureSound()
		{
			//IL_0039: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			if (loaded)
			{
				return true;
			}
			if (failed)
			{
				return false;
			}
			try
			{
				if (!RuntimeManager.IsInitialized)
				{
					return false;
				}
				byte[] array = ReadResource();
				if (array == null)
				{
					failed = true;
					return false;
				}
				CREATESOUNDEXINFO val = new CREATESOUNDEXINFO
				{
					cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO)),
					length = (uint)array.Length
				};
				System coreSystem = RuntimeManager.CoreSystem;
				RESULT val2 = ((System)(ref coreSystem)).createSound(array, (MODE)2305, ref val, ref sound);
				if ((int)val2 != 0)
				{
					failed = true;
					Plugin.Log.LogWarning((object)$"Could not decode the swap sound, it will not play: {val2}");
					return false;
				}
				loaded = true;
				Plugin.Log.LogInfo((object)"Swap sound ready.");
				return true;
			}
			catch (Exception ex)
			{
				failed = true;
				Plugin.Log.LogWarning((object)("Could not prepare the swap sound, it will not play: " + ex.Message));
				return false;
			}
		}

		private static byte[]? ReadResource()
		{
			using Stream stream = typeof(SwitcherooAudio).Assembly.GetManifestResourceStream("BallSwapItem.Assets.bomboclat.mp3");
			if (stream == null)
			{
				Plugin.Log.LogWarning((object)"Swap sound missing from the plugin; it will not play.");
				return null;
			}
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			return memoryStream.ToArray();
		}
	}
	internal static class SwitcherooEquipment
	{
		public const EquipmentType Type = (EquipmentType)200;

		private static EquipmentSettings? settings;

		private static bool failedOnce;

		public static void EnsureRegistered()
		{
			if (SingletonBehaviour<EquipmentManager>.HasInstance)
			{
				EquipmentCollection equipmentCollection = SingletonBehaviour<EquipmentManager>.Instance.equipmentCollection;
				if ((Object)(object)equipmentCollection != (Object)null)
				{
					Register(equipmentCollection);
				}
			}
		}

		public static void Register(EquipmentCollection collection)
		{
			if (!Plugin.Enabled.Value || failedOnce)
			{
				return;
			}
			try
			{
				if (settings == null)
				{
					settings = Build(collection);
				}
				if (settings != null)
				{
					if (Array.IndexOf(collection.equipment, settings) < 0)
					{
						EquipmentSettings[] array = collection.equipment;
						Array.Resize(ref array, array.Length + 1);
						array[^1] = settings;
						collection.equipment = array;
					}
					collection.equipmentDictionary[(EquipmentType)200] = settings;
				}
			}
			catch (Exception arg)
			{
				failedOnce = true;
				Plugin.Log.LogError((object)$"Could not register the Switcheroo's held model: {arg}");
			}
		}

		private static EquipmentSettings? Build(EquipmentCollection collection)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			EquipmentSettings val = default(EquipmentSettings);
			if (!collection.TryGetEquipmentSettings((EquipmentType)12, ref val) || (Object)(object)val.Prefab == (Object)null)
			{
				return null;
			}
			GameObject gameObject = ((Component)val.Prefab).gameObject;
			bool activeSelf = gameObject.activeSelf;
			gameObject.SetActive(false);
			GameObject val2;
			try
			{
				val2 = Object.Instantiate<GameObject>(gameObject);
			}
			finally
			{
				gameObject.SetActive(activeSelf);
			}
			((Object)val2).name = "SwitcherooModel";
			Switcheroo.Recolour(val2);
			GameObject val3 = new GameObject("SwitcherooEquipment");
			val3.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val3);
			val2.transform.SetParent(val3.transform, false);
			val2.SetActive(true);
			val2.transform.localPosition = Vector3.zero;
			val2.transform.localRotation = Quaternion.identity;
			Equipment val4 = val3.AddComponent<Equipment>();
			val4.Type = (EquipmentType)200;
			Equipment val5 = default(Equipment);
			if (val2.TryGetComponent<Equipment>(ref val5))
			{
				val5.Type = (EquipmentType)200;
			}
			EquipmentSettings val6 = new EquipmentSettings();
			val6.Type = (EquipmentType)200;
			val6.Prefab = val4;
			Plugin.Log.LogInfo((object)$"Built the Switcheroo's held model as EquipmentType {200}.");
			return val6;
		}
	}
	[HarmonyPatch(typeof(EquipmentCollection), "Initialize")]
	internal static class EquipmentCollectionInitializePatch
	{
		private static void Postfix(EquipmentCollection __instance)
		{
			SwitcherooEquipment.Register(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerInventory), "LocalPlayerUpdateEquipmentSwitchers")]
	internal static class EquipmentSwitcherPatch
	{
		private static readonly MethodInfo? CanHoldEquipment = FindCanHoldEquipment();

		private static bool Prefix(PlayerInventory __instance)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if ((int)__instance.GetEffectivelyEquippedItem(false) != 200)
			{
				return true;
			}
			SwitcherooEquipment.EnsureRegistered();
			if ((object)CanHoldEquipment == null)
			{
				return true;
			}
			bool flag = (bool)CanHoldEquipment.Invoke(__instance, null);
			bool flag2 = !((Enum)__instance.thrownItem).HasFlag((Enum)(object)(ThrownItemHand)2);
			__instance.PlayerInfo.RightHandEquipmentSwitcher.SetEquipment((EquipmentType)((flag && flag2) ? 200 : 0));
			__instance.PlayerInfo.LeftHandEquipmentSwitcher.SetEquipment((EquipmentType)0);
			return false;
		}

		private static MethodInfo? FindCanHoldEquipment()
		{
			MethodInfo methodInfo = AccessTools.GetDeclaredMethods(typeof(PlayerInventory)).FirstOrDefault((MethodInfo m) => m.Name.StartsWith("<LocalPlayerUpdateEquipmentSwitchers>g__CanHoldEquipment", StringComparison.Ordinal) && m.ReturnType == typeof(bool) && m.GetParameters().Length == 0);
			if ((object)methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"Could not find PlayerInventory's CanHoldEquipment; the Switcheroo will not show in hand.");
			}
			return methodInfo;
		}
	}
	internal static class SwitcherooLocalization
	{
		private static bool applied;

		public static void Initialize()
		{
			LocalizationManager.LanguageChanged += delegate
			{
				applied = false;
			};
		}

		public static void EnsureApplied()
		{
			//IL_0016: 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)
			if (applied)
			{
				return;
			}
			try
			{
				StringTable val = ((LocalizedDatabase<StringTable, StringTableEntry>)(object)LocalizationSettings.StringDatabase)?.GetTable(TableReference.op_Implicit(((object)(StringTable)1/*cast due to .constrained prefix*/).ToString()), (Locale)null);
				if (!((Object)(object)val == (Object)null))
				{
					string text = $"ITEM_{200}";
					((DetailedLocalizationTable<StringTableEntry>)(object)val).AddEntry(text, "Switcheroo");
					applied = true;
					Plugin.Log.LogInfo((object)("Registered the name 'Switcheroo' as " + text + "."));
				}
			}
			catch (Exception ex)
			{
				applied = true;
				Plugin.Log.LogWarning((object)("Could not name the Switcheroo, its name may show as a key: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAnimatorIo), "SetEquippedItem")]
	internal static class AnimatorEquippedItemPatch
	{
		private static void Prefix(ref ItemType equippedItem)
		{
			if ((int)equippedItem == 200)
			{
				equippedItem = (ItemType)10;
			}
		}
	}
	internal enum SwapRequestState
	{
		None,
		Waiting,
		Accepted,
		Denied
	}
	internal struct SwitcherooRequestMessage : NetworkMessage
	{
		public uint Token;
	}
	[StructLayout(LayoutKind.Sequential, Size = 1)]
	internal struct SwitcherooHelloMessage : NetworkMessage
	{
	}
	internal struct SwitcherooArmedMessage : NetworkMessage
	{
		public float WindUpSeconds;

		public string UserName;
	}
	internal enum SwitcherooDenialReason : byte
	{
		None,
		SwapInProgress,
		AlreadyUsedThisRound,
		NotHolding,
		TooSoon,
		NotEnoughBalls
	}
	internal struct SwitcherooUseReplyMessage : NetworkMessage
	{
		public bool Accepted;

		public byte Reason;

		public uint Token;
	}
	internal struct SwitcherooResultMessage : NetworkMessage
	{
		public int SwappedCount;
	}
	internal struct SwitcherooLockMessage : NetworkMessage
	{
		public bool Locked;
	}
	internal static class SwitcherooNetwork
	{
		private const double MinSecondsBetweenRequests = 1.0;

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

		private const float BusyGraceSeconds = 1.5f;

		public const float ReplyTimeoutSeconds = 2f;

		private static double clientBusyUntil;

		private static uint pendingToken;

		private static bool serverLocked;

		private static bool holeHookInstalled;

		private static bool serverHandlerRegistered;

		private static bool clientHandlerRegistered;

		private static bool helloSent;

		private static bool swapPending;

		private const float HeldMemorySeconds = 5f;

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

		public static bool LockedThisRound { get; private set; }

		public static bool SwapInProgress => Time.timeAsDouble < clientBusyUntil;

		public static SwapRequestState RequestState { get; private set; } = SwapRequestState.None;

		public static string DenialText { get; private set; } = "SWAP UNAVAILABLE";

		public static void EnsureHandlers()
		{
			if (NetworkServer.active)
			{
				if (!serverHandlerRegistered)
				{
					NetworkServer.RegisterHandler<SwitcherooRequestMessage>((Action<NetworkConnectionToClient, SwitcherooRequestMessage>)OnServerRequest, true);
					NetworkServer.RegisterHandler<SwitcherooHelloMessage>((Action<NetworkConnectionToClient, SwitcherooHelloMessage>)OnServerHello, false);
					serverHandlerRegistered = true;
					Plugin.Log.LogInfo((object)"Registered Switcheroo server handlers.");
				}
			}
			else if (serverHandlerRegistered)
			{
				serverHandlerRegistered = false;
				swapPending = false;
				serverLocked = false;
				LastRequestPerConnection.Clear();
				LastHeldSwitcheroo.Clear();
				ModGate.Reset();
			}
			if (NetworkClient.active)
			{
				if (!clientHandlerRegistered)
				{
					NetworkClient.RegisterHandler<SwitcherooArmedMessage>((Action<SwitcherooArmedMessage>)OnClientArmed, true);
					NetworkClient.RegisterHandler<SwitcherooResultMessage>((Action<SwitcherooResultMessage>)OnClientResult, true);
					NetworkClient.RegisterHandler<SwitcherooLockMessage>((Action<SwitcherooLockMessage>)OnClientLock, true);
					NetworkClient.RegisterHandler<SwitcherooUseReplyMessage>((Action<SwitcherooUseReplyMessage>)OnClientUseReply, true);
					clientHandlerRegistered = true;
					Switcheroo.EnsureNetworkPrefabRegistered();
					Plugin.Log.LogInfo((object)"Registered Switcheroo client handlers.");
				}
				if (!helloSent && NetworkClient.isConnected)
				{
					NetworkConnectionToServer connection = NetworkClient.connection;
					if (connection != null && ((NetworkConnection)connection).isAuthenticated)
					{
						NetworkClient.Send<SwitcherooHelloMessage>(default(SwitcherooHelloMessage), 0);
						helloSent = true;
					}
				}
			}
			else
			{
				clientHandlerRegistered = false;
				helloSent = false;
				LockedThisRound = false;
				clientBusyUntil = 0.0;
				RequestState = SwapRequestState.None;
			}
			if (!holeHookInstalled)
			{
				CourseManager.CurrentHoleGlobalIndexChanged += OnHoleChanged;
				holeHookInstalled = true;
			}
		}

		private static void OnHoleChanged()
		{
			LockedThisRound = false;
			if (NetworkServer.active)
			{
				serverLocked = false;
				NetworkServer.SendToAll<SwitcherooLockMessage>(new SwitcherooLockMessage
				{
					Locked = false
				}, 0, false);
			}
		}

		public static void RequestSwap()
		{
			if (!NetworkClient.active)
			{
				Plugin.Log.LogWarning((object)"Switcheroo used while not connected; ignoring.");
				RequestState = SwapRequestState.Denied;
				DenialText = "NOT CONNECTED";
			}
			else
			{
				RequestState = SwapRequestState.Waiting;
				pendingToken++;
				NetworkClient.Send<SwitcherooRequestMessage>(new SwitcherooRequestMessage
				{
					Token = pendingToken
				}, 0);
			}
		}

		public static void TimeOutRequest()
		{
			RequestState = SwapRequestState.Denied;
			DenialText = "NO RESPONSE";
		}

		private static void OnClientUseReply(SwitcherooUseReplyMessage message)
		{
			if (message.Token == pendingToken && RequestState == SwapRequestState.Waiting)
			{
				if (message.Accepted)
				{
					RequestState = SwapRequestState.Accepted;
					return;
				}
				SwitcherooDenialReason reason = (SwitcherooDenialReason)message.Reason;
				RequestState = SwapRequestState.Denied;
				DenialText = reason switch
				{
					SwitcherooDenialReason.SwapInProgress => "SWAP IN PROGRESS", 
					SwitcherooDenialReason.AlreadyUsedThisRound => "ONCE PER ROUND", 
					SwitcherooDenialReason.NotEnoughBalls => "NO BALLS TO SWAP", 
					SwitcherooDenialReason.NotHolding => "NO SWITCHEROO", 
					SwitcherooDenialReason.TooSoon => "TOO SOON", 
					_ => "SWAP UNAVAILABLE", 
				};
				Trace.Log($"client: use denied by the host ({reason})");
			}
		}

		private static void OnServerHello(NetworkConnectionToClient conn, SwitcherooHelloMessage message)
		{
			ModGate.MarkModded(conn);
			((NetworkConnection)conn).Send<SwitcherooLockMessage>(new SwitcherooLockMessage
			{
				Locked = serverLocked
			}, 0);
		}

		private static void OnServerRequest(NetworkConnectionToClient conn, SwitcherooRequestMessage message)
		{
			if (conn == null)
			{
				return;
			}
			double timeAsDouble = Time.timeAsDouble;
			if (LastRequestPerConnection.TryGetValue(conn.connectionId, out var value) && timeAsDouble - value < 1.0)
			{
				Plugin.Log.LogWarning((object)$"Ignoring rapid Switcheroo request from connection {conn.connectionId}.");
				Deny(conn, message.Token, SwitcherooDenialReason.TooSoon);
				return;
			}
			LastRequestPerConnection[conn.connectionId] = timeAsDouble;
			if (swapPending)
			{
				Plugin.Log.LogInfo((object)"Switcheroo request ignored: a swap is already counting down.");
				Deny(conn, message.Token, SwitcherooDenialReason.SwapInProgress);
				return;
			}
			if (serverLocked)
			{
				Plugin.Log.LogInfo((object)"Switcheroo request ignored: already used this round.");
				Deny(conn, message.Token, SwitcherooDenialReason.AlreadyUsedThisRound);
				return;
			}
			if (!SenderHoldsSwitcheroo(conn))
			{
				Plugin.Log.LogWarning((object)$"Ignoring Switcheroo request from connection {conn.connectionId}: no Switcheroo in their inventory.");
				Deny(conn, message.Token, SwitcherooDenialReason.NotHolding);
				return;
			}
			int num = SwapService.CountEligible();
			if (num < 2)
			{
				Plugin.Log.LogInfo((object)$"Switcheroo request refused: only {num} eligible ball(s) in play.");
				Deny(conn, message.Token, SwitcherooDenialReason.NotEnoughBalls);
				return;
			}
			PlayerInfo initiator = (((Object)(object)((NetworkConnection)conn).identity == (Object)null) ? null : ((Component)((NetworkConnection)conn).identity).GetComponent<PlayerInfo>());
			Trace.Log($"server: request accepted from connection {conn.connectionId}");
			((NetworkConnection)conn).Send<SwitcherooUseReplyMessage>(new SwitcherooUseReplyMessage
			{
				Accepted = true,
				Reason = 0,
				Token = message.Token
			}, 0);
			ModRunner? instance = ModRunner.Instance;
			if (instance != null)
			{
				((MonoBehaviour)instance).StartCoroutine(RunSwap(initiator));
			}
		}

		private static void Deny(NetworkConnectionToClient conn, uint token, SwitcherooDenialReason reason)
		{
			((NetworkConnection)conn).Send<SwitcherooUseReplyMessage>(new SwitcherooUseReplyMessage
			{
				Accepted = false,
				Reason = (byte)reason,
				Token = token
			}, 0);
		}

		private static IEnumerator RunSwap(PlayerInfo? initiator)
		{
			swapPending = true;
			float num = Mathf.Max(0f, Plugin.WindUpSeconds.Value);
			NetworkServer.SendToAll<SwitcherooArmedMessage>(new SwitcherooArmedMessage
			{
				WindUpSeconds = num,
				UserName = NameOf(initiator)
			}, 0, false);
			Trace.Log($"server: armed, waiting {num:0.0}s");
			yield return (object)new WaitForSeconds(num);
			Trace.Log("server: wind-up elapsed");
			if (!NetworkServer.active)
			{
				Plugin.Log.LogWarning((object)"Swap abandoned: no longer the server when the wind-up elapsed.");
				swapPending = false;
				yield break;
			}
			string failureReason;
			List<PlayerGolfer> list = SwapService.TrySwap(out failureReason);
			if (list.Count == 0)
			{
				Plugin.Log.LogInfo((object)("Switcheroo used but no swap ran: " + failureReason + "."));
			}
			else
			{
				Plugin.Log.LogInfo((object)$"Switcheroo swapped {list.Count} balls.");
				NetworkServer.SendToAll<SwitcherooResultMessage>(new SwitcherooResultMessage
				{
					SwappedCount = list.Count
				}, 0, false);
				AnnounceInFeed(initiator, list);
				if (MatchSetupRules.GetValueAsBool((Rule)200))
				{
					serverLocked = true;
					NetworkServer.SendToAll<SwitcherooLockMessage>(new SwitcherooLockMessage
					{
						Locked = true
					}, 0, false);
				}
			}
			swapPending = false;
		}

		public static void TrackHeldItems()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			float time = Time.time;
			foreach (NetworkConnectionToClient value in NetworkServer.connections.Values)
			{
				if (value != null && HoldsSwitcheroo(value))
				{
					LastHeldSwitcheroo[value.connectionId] = time;
				}
			}
		}

		private static bool SenderHoldsSwitcheroo(NetworkConnectionToClient conn)
		{
			if (HoldsSwitcheroo(conn))
			{
				return true;
			}
			if (LastHeldSwitcheroo.TryGetValue(conn.connectionId, out var value))
			{
				return Time.time - value <= 5f;
			}
			return false;
		}

		private static bool HoldsSwitcheroo(NetworkConnectionToClient conn)
		{
			//IL_0039: 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)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)((NetworkConnection)conn).identity == (Object)null)
			{
				return false;
			}
			PlayerInfo component = ((Component)((NetworkConnection)conn).identity).GetComponent<PlayerInfo>();
			PlayerInventory val = ((component != null) ? component.Inventory : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Enumerator<InventorySlot> enumerator = val.slots.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					InventorySlot current = enumerator.Current;
					if ((int)current.itemType == 200 && current.remainingUses > 0)
					{
						return true;
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			return false;
		}

		private static string NameOf(PlayerInfo? player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return string.Empty;
			}
			PlayerId playerId = player.PlayerId;
			string text;
			if (!((Object)(object)playerId == (Object)null))
			{
				text = playerId.PlayerNameNoRichText;
				if (text == null)
				{
					return string.Empty;
				}
			}
			else
			{
				text = string.Empty;
			}
			return text;
		}

		private static void AnnounceInFeed(PlayerInfo? initiator, List<PlayerGolfer> swapped)
		{
			if ((Object)(object)initiator == (Object)null)
			{
				return;
			}
			try
			{
				foreach (PlayerGolfer item in swapped)
				{
					PlayerInfo playerInfo = item.PlayerInfo;
					if ((Object)(object)playerInfo != (Object)null && (Object)(object)playerInfo != (Object)(object)initiator)
					{
						InfoFeed.ShowItemHitMessage(initiator, playerInfo, (ItemType)200);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not post the swap to the info feed: " + ex.Message));
			}
		}

		private static void OnClientLock(SwitcherooLockMessage message)
		{
			LockedThisRound = message.Locked;
		}

		private static void OnClientArmed(SwitcherooArmedMessage message)
		{
			clientBusyUntil = Time.timeAsDouble + (double)message.WindUpSeconds + 1.5;
			SwitcherooUi.BeginCountdown(message.WindUpSeconds, message.UserName);
			SwitcherooAudio.PlayAnticipation();
		}

		private static void OnClientResult(SwitcherooResultMessage message)
		{
			clientBusyUntil = 0.0;
			Plugin.Log.LogInfo((object)$"Switcheroo swapped {message.SwappedCount} balls.");
			SwitcherooAudio.PlayPayoff();
		}
	}
	internal static class SwitcherooPool
	{
		private static ItemSpawnerSettings? managed;

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

		public static bool FallbackActive { get; private set; }

		public static void Remember(ItemSpawnerSettings settings)
		{
			managed = settings;
		}

		public static bool IsManaged(ItemSpawnerSettings settings)
		{
			return (Object)(object)managed == (Object)(object)settings;
		}

		public static bool IsManagedPool(ItemPool pool)
		{
			return ManagedPools.Contains(((Object)pool).GetInstanceID());
		}

		public static void EnsureInjected()
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Enabled.Value || (Object)(object)managed == (Object)null || FallbackActive)
			{
				return;
			}
			try
			{
				ManagedPools.Clear();
				Inject(managed.AheadOfBallItemPoolDefaults);
				Inject(managed.AheadOfBallItemPool);
				foreach (ItemPoolData itemPoolsDefault in managed.ItemPoolsDefaults)
				{
					Inject(itemPoolsDefault.pool);
				}
				foreach (ItemPoolData itemPool in managed.ItemPools)
				{
					Inject(itemPool.pool);
				}
			}
			catch (Exception arg)
			{
				UseFallback($"the Switcheroo could not be added to the item pools: {arg}");
			}
		}

		private static void Inject(ItemPool? pool)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)pool == (Object)null)
			{
				return;
			}
			ManagedPools.Add(((Object)pool).GetInstanceID());
			if (!pool.ContainsItemType((ItemType)200))
			{
				float num = RankWeight(pool, warnIfMissing: true);
				if (num <= 0f)
				{
					Plugin.Log.LogWarning((object)("Pool '" + ((Object)pool).name + "' has no weights to rank against; leaving it alone."));
					return;
				}
				ItemSpawnChance[] array = pool.spawnChances;
				Array.Resize(ref array, array.Length + 1);
				array[^1] = new ItemSpawnChance
				{
					item = (ItemType)200,
					spawnChanceWeight = num
				};
				pool.spawnChances = array;
				pool.UpdateTotalWeight();
				Plugin.Log.LogInfo((object)$"Added the Switcheroo to pool '{((Object)pool).name}' at weight {num:0.###}.");
			}
		}

		internal static float RankWeight(ItemPool pool, bool warnIfMissing)
		{
			//IL_0059: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Invalid comparison between Unknown and I4
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			float spawnChanceWeight = pool.GetSpawnChanceWeight((ItemType)10);
			float spawnChanceWeight2 = pool.GetSpawnChanceWeight((ItemType)15);
			if (spawnChanceWeight > 0f && spawnChanceWeight2 > 0f)
			{
				return (spawnChanceWeight + spawnChanceWeight2) / 2f;
			}
			if (spawnChanceWeight > 0f || spawnChanceWeight2 > 0f)
			{
				return Mathf.Max(spawnChanceWeight, spawnChanceWeight2);
			}
			float num = 0f;
			ItemSpawnChance[] spawnChances = pool.SpawnChances;
			foreach (ItemSpawnChance val in spawnChances)
			{
				if ((int)val.item != 200 && !(val.spawnChanceWeight <= 0f) && (num == 0f || val.spawnChanceWeight < num))
				{
					num = val.spawnChanceWeight;
				}
			}
			if (warnIfMissing)
			{
				Plugin.Log.LogInfo((object)("Pool '" + ((Object)pool).name + "' has neither the Orbital Laser nor the Thunderstorm; " + $"ranking the Switcheroo against its rarest item at weight {num:0.###}."));
			}
			return num;
		}

		public static void UseFallback(string reason)
		{
			if (!FallbackActive)
			{
				FallbackActive = true;
				Plugin.Log.LogError((object)("Switcheroo item probabilities are unavailable: " + reason + " Falling back to picking the item as items are drawn, so it still spawns, but it has no slider in the match setup."));
				Withdraw();
			}
		}

		private static void Withdraw()
		{
			//IL_0041: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)managed == (Object)null)
			{
				return;
			}
			try
			{
				RemoveFrom(managed.AheadOfBallItemPoolDefaults);
				RemoveFrom(managed.AheadOfBallItemPool);
				foreach (ItemPoolData itemPoolsDefault in managed.ItemPoolsDefaults)
				{
					RemoveFrom(itemPoolsDefault.pool);
				}
				foreach (ItemPoolData itemPool in managed.ItemPools)
				{
					RemoveFrom(itemPool.pool);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not take the Switcheroo back out of the item pools: {arg}");
			}
		}

		private static void RemoveFrom(ItemPool? pool)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)pool == (Object)null || !pool.ContainsItemType((ItemType)200))
			{
				return;
			}
			List<ItemSpawnChance> list = new List<ItemSpawnChance>(pool.spawnChances.Length);
			ItemSpawnChance[] spawnChances = pool.spawnChances;
			foreach (ItemSpawnChance val in spawnChances)
			{
				if ((int)val.item != 200)
				{
					list.Add(val);
				}
			}
			pool.spawnChances = list.ToArray();
			pool.UpdateTotalWeight();
		}
	}
	[HarmonyPatch(typeof(ItemSpawnerSettings), "ResetRuntimeData")]
	internal static class ItemSpawnerRuntimePoolPatch
	{
		private static void Postfix(ItemSpawnerSettings __instance)
		{
			if (SwitcherooPool.IsManaged(__instance))
			{
				SwitcherooPool.EnsureInjected();
			}
		}
	}
	[HarmonyPatch(typeof(ItemPool), "GetWeightedRandomItem")]
	internal static class ItemPoolDrawPatch
	{
		private static bool Prefix(ItemPool __instance, ref ItemType __result)
		{
			if (!Plugin.Enabled.Value || !SwitcherooPool.FallbackActive)
			{
				return true;
			}
			if (!SwitcherooPool.IsManagedPool(__instance))
			{
				return true;
			}
			float num = SwitcherooPool.RankWeight(__instance, warnIfMissing: false);
			float totalSpawnChanceWeight = __instance.TotalSpawnChanceWeight;
			if (num <= 0f || totalSpawnChanceWeight <= 0f)
			{
				return true;
			}
			if (Random.value >= num / (totalSpawnChanceWeight + num))
			{
				return true;
			}
			__result = (ItemType)200;
			return false;
		}
	}
	[HarmonyPatch(typeof(MatchSetupRules), "Initialize")]
	internal static class MatchSetupRuleRowPatch
	{
		private const string LabelKey = "RULE_SWITCHEROO_ONCE_PER_HOLE";

		private const string LabelText = "One Switcheroo per hole";

		private static DropdownOption? row;

		private static int ownerId;

		private static TableReference labelTable;

		private static bool labelTableKnown;

		private static bool languageHookInstalled;

		private static void Postfix(MatchSetupRules __instance)
		{
			if (!Plugin.Enabled.Value)
			{
				return;
			}
			try
			{
				Build(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)($"Could not add the once-per-hole row to the match setup: {arg} The rule stays off " + "and the Switcheroo can be used as often as it is found."));
			}
		}

		private static void Build(MatchSetupRules rules)
		{
			MatchSetupRules.CategoryPerRule[(Rule)200] = (RuleCategory)3;
			if ((!((Object)(object)row == (Object)null) && ownerId == ((Object)rules).GetInstanceID()) || Clone(rules))
			{
				AccessTools.Method(typeof(MatchSetupRules), "InitDropdownOnOff", (Type[])null, (Type[])null).Invoke(rules, new object[3]
				{
					row,
					(object)(Rule)200,
					null
				});
			}
		}

		private static bool Clone(MatchSetupRules rules)
		{
			DropdownOption hitOtherPlayersBalls = rules.hitOtherPlayersBalls;
			if ((Object)(object)hitOtherPlayersBalls == (Object)null)
			{
				Plugin.Log.LogWarning((object)"No battle row to copy; the once-per-hole rule has no control.");
				return false;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)hitOtherPlayersBalls).gameObject, ((Component)hitOtherPlayersBalls).transform.parent);
			((Object)val).name = "SwitcherooOncePerHole";
			val.transform.SetSiblingIndex(((Component)hitOtherPlayersBalls).transform.GetSiblingIndex() + 1);
			DropdownOption component = val.GetComponent<DropdownOption>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				Plugin.Log.LogWarning((object)"The copied battle row has no DropdownOption; leaving the rule out of the UI.");
				return false;
			}
			Label(val);
			row = component;
			ownerId = ((Object)rules).GetInstanceID();
			Plugin.Log.LogInfo((object)"Added the once-per-hole rule to the match setup's Battle section.");
			return true;
		}

		private static void Label(GameObject clone)
		{
			//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_0049: Unknown result type (might be due to invalid IL or missing references)
			LocalizeStringEvent componentInChildren = clone.GetComponentInChildren<LocalizeStringEvent>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Plugin.Log.LogWarning((object)"The copied row has no localized label; it will read as the row it was copied from.");
				return;
			}
			labelTable = ((LocalizedReference)componentInChildren.StringReference).TableReference;
			labelTableKnown = true;
			if (AddEntry())
			{
				((LocalizedReference)componentInChildren.StringReference).TableEntryReference = TableEntryReference.op_Implicit("RULE_SWITCHEROO_ONCE_PER_HOLE");
				componentInChildren.RefreshString();
				InstallLanguageHook();
				return;
			}
			TMP_Text component = ((Component)componentInChildren).GetComponent<TMP_Text>();
			Object.Destroy((Object)(object)componentInChildren);
			if ((Object)(object)component != (Object)null)
			{
				component.text = "One Switcheroo per hole";
				Plugin.Log.LogWarning((object)"Localization was not ready; the once-per-hole row is labelled in English only.");
			}
		}

		private static bool AddEntry()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (!labelTableKnown)
			{
				return false;
			}
			try
			{
				StringTable val = ((LocalizedDatabase<StringTable, StringTableEntry>)(object)LocalizationSettings.StringDatabase)?.GetTable(labelTable, (Locale)null);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				((DetailedLocalizationTable<StringTableEntry>)(object)val).AddEntry("RULE_SWITCHEROO_ONCE_PER_HOLE", "One Switcheroo per hole");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not name the once-per-hole rule: " + ex.Message));
				return false;
			}
		}

		private static void InstallLanguageHook()
		{
			if (!languageHookInstalled)
			{
				LocalizationManager.LanguageChanged += delegate
				{
					AddEntry();
				};
				languageHookInstalled = true;
			}
		}
	}
	[HarmonyPatch(typeof(MatchSetupRules), "Initialize")]
	internal static class MatchSetupRulesPatch
	{
		private static void Prefix(MatchSetupRules __instance)
		{
			if (Plugin.Enabled.Value && !SwitcherooPool.FallbackActive)
			{
				try
				{
					Extend(__instance);
				}
				catch (Exception arg)
				{
					SwitcherooPool.UseFallback($"the match setup screen could not be extended: {arg}");
					return;
				}
				SwitcherooPool.Remember(__instance.itemSpawnerSettings);
				SwitcherooPool.EnsureInjected();
			}
		}

		private static void Extend(MatchSetupRules rules)
		{
			ItemType[] array = rules.itemOrder;
			if (Array.IndexOf(array, (ItemType)200) < 0)
			{
				ItemType[] array2 = (ItemType[])(object)new ItemType[array.Length + 1];
				Array.Copy(array, array2, array.Length);
				array2[^1] = (ItemType)200;
				array = array2;
			}
			int[] array3 = new int[Math.Max(200, array.Length)];
			for (int i = 0; i < array3.Length; i++)
			{
				array3[i] = Array.IndexOf(array, (ItemType)(i + 1));
			}
			rules.itemOrder = array;
			rules.itemOrderLookup = array3;
		}
	}
	internal static class SwitcherooThrownItem
	{
		public const ThrownUsedItemType Type = (ThrownUsedItemType)200;

		private static bool registered;

		private static bool failedOnce;

		public static void EnsureRegistered()
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			if (registered || failedOnce || !Plugin.Enabled.Value || !SingletonBehaviour<ThrownUsedItemManager>.HasInstance)
			{
				return;
			}
			try
			{
				ThrownUsedItem value;
				if (ThrownUsedItemManager.prefabPerType.ContainsKey((ThrownUsedItemType)200))
				{
					registered = true;
				}
				else if (ThrownUsedItemManager.prefabPerType.TryGetValue((ThrownUsedItemType)6, out value) && !((Object)(object)value == (Object)null))
				{
					GameObject gameObject = ((Component)value).gameObject;
					bool activeSelf = gameObject.activeSelf;
					gameObject.SetActive(false);
					GameObject val;
					try
					{
						val = Object.Instantiate<GameObject>(gameObject);
					}
					finally
					{
						gameObject.SetActive(activeSelf);
					}
					((Object)val).name = "SwitcherooThrownItem";
					Object.DontDestroyOnLoad((Object)(object)val);
					Switcheroo.Recolour(val);
					ThrownUsedItem component = val.GetComponent<ThrownUsedItem>();
					component.type = (ThrownUsedItemType)200;
					ThrownUsedItemManager.prefabPerType[(ThrownUsedItemType)200] = component;
					registered = true;
					Plugin.Log.LogInfo((object)$"Registered the discarded Switcheroo as ThrownUsedItemType {200}.");
				}
			}
			catch (Exception arg)
			{
				failedOnce = true;
				Plugin.Log.LogError((object)$"Could not register the discarded Switcheroo: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerInventory), "ThrowUsedItemInternal")]
	internal static class ThrowUsedItemPatch
	{
		private static bool Prefix(PlayerInventory __instance, ThrownUsedItemType thrownItemType, bool forcePlayerPosition, Vector3 forcedPlayerPosition)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if ((int)thrownItemType != 200)
			{
				return true;
			}
			try
			{
				SwitcherooThrownItem.EnsureRegistered();
				Throw(__instance, forcePlayerPosition, forcedPlayerPosition);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not throw the spent Switcheroo: " + ex.Message));
			}
			return false;
		}

		private static void Throw(PlayerInventory inventory, bool forcePlayerPosition, Vector3 forcedPlayerPosition)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: 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_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)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//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_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: 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)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			ThrownUsedItem unusedThrownItem = ThrownUsedItemManager.GetUnusedThrownItem((ThrownUsedItemType)200);
			if (!((Object)(object)unusedThrownItem == (Object)null))
			{
				PlayerInfo playerInfo = inventory.PlayerInfo;
				Quaternion orbitalLaserThrowDirectionLocalRotation = GameManager.ItemSettings.OrbitalLaserThrowDirectionLocalRotation;
				Vector3 orbitalLaserThrowLocalAngularVelocity = GameManager.ItemSettings.OrbitalLaserThrowLocalAngularVelocity;
				Vector3 val = default(Vector3);
				Quaternion val2 = default(Quaternion);
				((Component)playerInfo.RightHandEquipmentSwitcher).transform.GetPositionAndRotation(ref val, ref val2);
				if (forcePlayerPosition)
				{
					val += forcedPlayerPosition - ((Component)inventory).transform.position;
				}
				Vector3 val3 = ((Component)inventory).transform.TransformDirection(orbitalLaserThrowDirectionLocalRotation * Vector3.forward);
				Vector3 val4 = ((Component)inventory).transform.TransformDirection(orbitalLaserThrowDirectionLocalRotation * orbitalLaserThrowLocalAngularVelocity);
				Vector3 val5 = playerInfo.Rigidbody.linearVelocity + val3 * GameManager.ItemSettings.OrbitalLaserThrowSpeed;
				Vector3 val6 = playerInfo.Rigidbody.angularVelocity + val4;
				unusedThrownItem.Initialize(val, val2, val5, val6, playerInfo.GetEffectiveTeam());
				PhysicsManager.TemporarilyIgnoreCollisionsBetween(playerInfo.AsEntity, unusedThrownItem.AsEntity, 0.5f, false);
			}
		}
	}
	internal static class SwitcherooUi
	{
		private static readonly Color32 GradientBottom = new Color32(byte.MaxValue, (byte)252, (byte)212, byte.MaxValue);

		private const float DenialSeconds = 1.6f;

		private const float DenialFlashHz = 4f;

		private const float DenialAloneY = -180f;

		private const float DenialBelowCountdownY = -360f;

		private static TextMeshProUGUI? label;

		private static TextMeshProUGUI? userLabel;

		private static TextMeshProUGUI? denialLabel;

		private static CanvasGroup? group;

		private static float countdownEndTime;

		private static float denialEndTime;

		private static string denialText = string.Empty;

		private static string countdownUser = string.Empty;

		public static void BeginCountdown(float seconds, string userName)
		{
			countdownEndTime = Time.time + seconds;
			countdownUser = userName ?? string.Empty;
		}

		public static void ShowDenial(string message)
		{
			denialText = message;
			denialEndTime = Time.time + 1.6f;
		}

		public static void Tick()
		{
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			float num = countdownEndTime - Time.time;
			float num2 = denialEndTime - Time.time;
			if (num <= 0f && num2 <= 0f)
			{
				if ((Object)(object)group != (Object)null && group.alpha != 0f)
				{
					group.alpha = 0f;
				}
			}
			else
			{
				if (!EnsureLabel())
				{
					return;
				}
				group.alpha = 1f;
				if (num > 0f)
				{
					((Behaviour)label).enabled = true;
					((TMP_Text)label).text = $"SWITCHEROO IN {Mathf.CeilToInt(num)}";
					((Behaviour)userLabel).enabled = countdownUser.Length > 0;
					if (((Behaviour)userLabel).enabled)
					{
						((TMP_Text)userLabel).text = "USED BY " + countdownUser.ToUpperInvariant();
					}
				}
				else
				{
					((Behaviour)label).enabled = false;
					((Behaviour)userLabel).enabled = false;
				}
				if (num2 > 0f)
				{
					((Behaviour)denialLabel).enabled = true;
					((TMP_Text)denialLabel).text = denialText;
					((TMP_Text)denialLabel).rectTransform.anchoredPosition = new Vector2(0f, (num > 0f) ? (-360f) : (-180f));
					bool flag = Mathf.Repeat(num2 * 4f, 1f) > 0.5f;
					((TMP_Text)denialLabel).alpha = (flag ? 1f : 0.15f);
				}
				else
				{
					((Behaviour)denialLabel).enabled = false;
				}
			}
		}

		private static bool EnsureLabel()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)label != (Object)null)
			{
				return true;
			}
			TMP_FontAsset val = FindGameFont();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			GameObject val2 = new GameObject("BallSwapItemCountdown");
			Object.DontDestroyOnLoad((Object)(object)val2);
			Canvas val3 = val2.AddComponent<Canvas>();
			val3.renderMode = (RenderMode)0;
			val3.sortingOrder = 500;
			CanvasScaler val4 = val2.AddComponent<CanvasScaler>();
			val4.uiScaleMode = (ScaleMode)1;
			val4.referenceResolution = new Vector2(1920f, 1080f);
			val4.screenMatchMode = (ScreenMatchMode)0;
			val4.matchWidthOrHeight = 1f;
			group = val2.AddComponent<CanvasGroup>();
			group.alpha = 0f;
			group.interactable = false;
			group.blocksRaycasts = false;
			GameObject val5 = new GameObject("Label");
			val5.transform.SetParent(val2.transform, false);
			label = val5.AddComponent<TextMeshProUGUI>();
			((TMP_Text)label).font = val;
			((TMP_Text)label).fontSize = 76f;
			((TMP_Text)label).alignment = (TextAlignmentOptions)514;
			((TMP_Text)label).textWrappingMode = (TextWrappingModes)0;
			((Graphic)label).raycastTarget = false;
			((TMP_Text)label).enableVertexGradient = true;
			((TMP_Text)label).colorGradient = new VertexGradient(Color.white, Color.white, Color32.op_Implicit(GradientBottom), Color32.op_Implicit(GradientBottom));
			((TMP_Text)label).outlineWidth = 0.2f;
			((TMP_Text)label).outlineColor = new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue);
			RectTransform rectTransform = ((TMP_Text)label).rectTransform;
			rectTransform.anchorMin = new Vector2(0.5f, 1f);
			rectTransform.anchorMax = new Vector2(0.5f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.anchoredPosition = new Vector2(0f, -180f);
			rectTransform.sizeDelta = new Vector2(1400f, 120f);
			userLabel = BuildSecondaryLabel(val2, val, "User", 40f, GradientBottom, -292f, 60f);
			denialLabel = BuildSecondaryLabel(val2, val, "Denial", 58f, new Color32(byte.MaxValue, (byte)59, (byte)48, byte.MaxValue), -180f, 120f);
			Plugin.Log.LogInfo((object)("Countdown label built using the game font '" + ((Object)val).name + "'."));
			return true;
		}

		private static TextMeshProUGUI BuildSecondaryLabel(GameObject root, TMP_FontAsset font, string name, float fontSize, Color32 color, float y, float height)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: 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)
			GameObject val = new GameObject(name);
			val.transform.SetParent(root.transform, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).font = font;
			((TMP_Text)val2).fontSize = fontSize;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)514;
			((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
			((Graphic)val2).raycastTarget = false;
			((Graphic)val2).color = Color32.op_Implicit(color);
			((TMP_Text)val2).outlineWidth = 0.2f;
			((TMP_Text)val2).outlineColor = new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue);
			((Behaviour)val2).enabled = false;
			RectTransform rectTransform = ((TMP_Text)val2).rectTransform;
			rectTransform.anchorMin = new Vector2(0.5f, 1f);
			rectTransform.anchorMax = new Vector2(0.5f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.anchoredPosition = new Vector2(0f, y);
			rectTransform.sizeDelta = new Vector2(1400f, height);
			return val2;
		}

		private static TMP_FontAsset? FindGameFont()
		{
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			if (array.Length == 0)
			{
				return null;
			}
			return (from text in array
				select text.font into font
				where (Object)(object)font != (Object)null
				group font by font into @group
				orderby @group.Count() descending
				select @group.Key).FirstOrDefault();
		}
	}
	[HarmonyPatch(typeof(PlayerInventory), "TryUseItem")]
	internal static class SwitcherooUsePatch
	{
		private static bool Prefix(PlayerInventory __instance, bool isAirhornReaction, ref bool shouldEatInput, ref bool __result)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			shouldEatInput = true;
			if (!((NetworkBehaviour)__instance).isLocalPlayer)
			{
				return true;
			}
			if ((int)__instance.GetEffectivelyEquippedItem(false) != 200)
			{
				return true;
			}
			if (SwitcherooNetwork.LockedThisRound)
			{
				Refuse("ONCE PER ROUND");
				__result = false;
				return false;
			}
			if (SwitcherooNetwork.SwapInProgress)
			{
				Trace.Log("use refused: a swap is already counting down");
				Refuse("SWAP IN PROGRESS");
				__result = false;
				return false;
			}
			int num = SwapService.CountEligible();
			if (num < 2)
			{
				Trace.Log($"use refused: only {num} eligible ball(s)");
				Refuse("NO BALLS TO SWAP");
				__result = false;
				return false;
			}
			InventorySlot val = default(InventorySlot);
			ItemData val2 = default(ItemData);
			bool flag = default(bool);
			if (!__instance.CanUseEquippedItem(false, isAirhornReaction, ref val, ref val2, ref shouldEatInput, ref flag))
			{
				__result = false;
				return false;
			}
			Trace.Log($"use accepted, currentItemUse={__instance.CurrentItemUse}, slot={__instance.EquippedItemIndex}");
			__instance.ItemUseTimestamp = Time.timeAsDouble;
			__instance.CancelItemUse();
			__instance.itemUseRoutine = ((MonoBehaviour)__instance).StartCoroutine(SwitcherooRoutine(__instance));
			__instance.PlayerInfo.CancelEmote(false);
			__instance.CancelItemFlourish();
			__result = true;
			return false;
		}

		private static void Refuse(string message)
		{
			SwitcherooUi.ShowDenial(message);
			SwitcherooAudio.PlayDenial();
		}

		private static IEnumerator SwitcherooRoutine(PlayerInventory inventory)
		{
			Trace.Log("routine: started");
			inventory.SetCurrentItemUse((ItemUseType)1);
			yield return (object)new WaitForSeconds(GameManager.ItemSettings.OrbitalLaserActivationTime);
			Trace.Log("routine: activation wait done, sending request");
			SwitcherooNetwork.RequestSwap();
			float waited = 0f;
			while (SwitcherooNetwork.RequestState == SwapRequestState.Waiting)
			{
				if (waited >= 2f)
				{
					SwitcherooNetwork.TimeOutRequest();
					break;
				}
				yield return null;
				waited += Time.deltaTime;
			}
			if (SwitcherooNetwork.RequestState != SwapRequestState.Accepted)
			{
				Trace.Log("routine: use denied (" + SwitcherooNetwork.DenialText + "), keeping the item");
				Refuse(SwitcherooNetwork.DenialText);
				inventory.SetCurrentItemUse((ItemUseType)0);
				yield break;
			}
			Trace.Log("routine: host accepted, spending the item");
			int index = inventory.EquippedItemIndex;
			inventory.DecrementUseFromSlotAt(index);
			bool thrown = false;
			for (float timeSince = BMath.GetTimeSince(inventory.ItemUseTimestamp); timeSince < GameManager.ItemSettings.OrbitalLaserActivationTotalDuration; timeSince = BMath.GetTimeSince(inventory.ItemUseTimestamp))
			{
				if (!thrown && timeSince >= GameManager.ItemSettings.OrbitalLaserThrowTime)
				{
					try
					{
						SwitcherooThrownItem.EnsureRegistered();
						inventory.ThrowUsedItemForAllClients((ThrownUsedItemType)200, false, default(Vector3), false);
						inventory.LocalPlayerMarkThrownItem((ThrownItemHand)2);
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Could not throw the spent Switcheroo: {arg}");
					}
					thrown = true;
					Trace.Log($"routine: threw spent device at {timeSince:0.00}s");
				}
				yield return null;
			}
			Trace.Log("routine: finished, clearing use state");
			inventory.SetCurrentItemUse((ItemUseType)0);
			inventory.RemoveIfOutOfUses(index, true);
		}
	}
	internal static class Trace
	{
		public static void Log(string message)
		{
			if (Plugin.VerboseLogging.Value)
			{
				Plugin.Log.LogInfo((object)("[trace] " + message));
			}
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ConstantExpectedAttribute : Attribute
	{
		public object? Min { get; set; }

		public object? Max { get; set; }
	}
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ExperimentalAttribute : Attribute
	{
		public string DiagnosticId { get; }

		public string? UrlFormat { get; set; }

		public ExperimentalAttribute(string diagnosticId)
		{
			DiagnosticId = diagnosticId;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SetsRequiredMembersAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class StringSyntaxAttribute : Attribute
	{
		public const string CompositeFormat = "CompositeFormat";

		public const string DateOnlyFormat = "DateOnlyFormat";

		public const string DateTimeFormat = "DateTimeFormat";

		public const string EnumFormat = "EnumFormat";

		public const string GuidFormat = "GuidFormat";

		public const string Json = "Json";

		public const string NumericFormat = "NumericFormat";

		public const string Regex = "Regex";

		public const string TimeOnlyFormat = "TimeOnlyFormat";

		public const string TimeSpanFormat = "TimeSpanFormat";

		public const string Uri = "Uri";

		public const string Xml = "Xml";

		public string Syntax { get; }

		public object?[] Arguments { get; }

		public StringSyntaxAttribute(string syntax)
		{
			Syntax = syntax;
			Arguments = new object[0];
		}

		public StringSyntaxAttribute(string syntax, params object?[] arguments)
		{
			Syntax = syntax;
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class UnscopedRefAttribute : Attribute
	{
	}
}
namespace System.Runtime.Versioning
{
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresPreviewFeaturesAttribute : Attribute
	{
		public string? Message { get; }

		public string? Url { get; set; }

		public RequiresPreviewFeaturesAttribute()
		{
		}

		public RequiresPreviewFeaturesAttribute(string? message)
		{
			Message = message;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CallerArgumentExpressionAttribute : Attribute
	{
		public string ParameterName { get; }

		public CallerArgumentExpressionAttribute(string parameterName)
		{
			ParameterName = parameterName;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CollectionBuilderAttribute : Attribute
	{
		public Type BuilderType { get; }

		public string MethodName { get; }

		public CollectionBuilderAttribute(Type builderType, string methodName)
		{
			BuilderType = builderType;
			MethodName = methodName;
		}
	}
	[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class CompilerFeatureRequiredAttribute : Attribute
	{
		public const string RefStructs = "RefStructs";

		public const string RequiredMembers = "RequiredMembers";

		public string FeatureName { get; }

		public bool IsOptional { get; set; }

		public CompilerFeatureRequiredAttribute(string featureName)
		{
			FeatureName = featureName;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
	{
		public string[] Arguments { get; }

		public InterpolatedStringHandlerArgumentAttribute(string argument)
		{
			Arguments = new string[1] { argument };
		}

		public InterpolatedStringHandlerArgumentAttribute(params string[] arguments)
		{
			Arguments = arguments;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class InterpolatedStringHandlerAttribute : Attribute
	{
	}
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal static class IsExternalInit
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ModuleInitializerAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class OverloadResolutionPriorityAttribute : Attribute
	{
		public int Priority { get; }

		public OverloadResolutionPriorityAttribute(int priority)
		{
			Priority = priority;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class ParamCollectionAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiredMemberAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	[ExcludeFromCodeCoverage]
	internal sealed class RequiresLocationAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)]
	[ExcludeFromCodeCoverage]
	internal sealed class SkipLocalsInitAttribute : Attribute
	{
	}
}