Decompiled source of RouletteRevival v1.2.3

Mods/RouletteRevival.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using HarmonyLib;
using Il2Cpp;
using Il2CppFishNet;
using Il2CppFishNet.Connection;
using Il2CppFishNet.Managing;
using Il2CppFishNet.Managing.Object;
using Il2CppFishNet.Managing.Server;
using Il2CppFishNet.Managing.Timing;
using Il2CppFishNet.Object;
using Il2CppFishNet.Object.Synchronizing;
using Il2CppFishNet.Transporting;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppTMPro;
using Il2Cpp_Scripts.Systems.Chat;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using RouletteRevival;
using RouletteRevival.BetDisplay;
using RouletteRevival.ChatBetting;
using RouletteRevival.Config;
using RouletteRevival.Hud;
using RouletteRevival.Input;
using RouletteRevival.Placement;
using RouletteRevival.Spawning;
using RouletteRevival.TableLock;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(RouletteRevivalMod), "Roulette Revival", "1.2.3", "itsRevela", null)]
[assembly: MelonGame("The Sledding Corporation", "Sledding Game")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("RouletteRevival")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.3.0")]
[assembly: AssemblyInformationalVersion("1.2.3+d5f32091bf91e9754e5673a8e9bcea6946c76de5")]
[assembly: AssemblyProduct("RouletteRevival")]
[assembly: AssemblyTitle("RouletteRevival")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RouletteRevival
{
	public sealed class RouletteRevivalMod : MelonMod
	{
		private RouletteSettings _settings;

		private RouletteTableSpawner _spawner;

		private TableRelocator _relocator;

		private RouletteHudBinder _hudBinder;

		private readonly TableLockEnforcer _tableLock = new TableLockEnforcer();

		private string _lastError;

		internal static RouletteSettings Settings { get; private set; }

		public override void OnInitializeMelon()
		{
			_settings = new RouletteSettings();
			Settings = _settings;
			_spawner = new RouletteTableSpawner();
			_relocator = new TableRelocator(_spawner, _settings);
			_hudBinder = new RouletteHudBinder();
			if (!_settings.IsRelocateKeyValid)
			{
				((MelonBase)this).LoggerInstance.Warning($"RelocateKey '{_settings.RelocateKeyText}' is not F1-F12; using {"F8"}.");
			}
			((MelonBase)this).LoggerInstance.Msg($"Loaded. Host a lobby to place the table; host presses F{_settings.RelocateFunctionKey} to move it.");
		}

		public override void OnLateInitializeMelon()
		{
			foreach (MethodBase patchedMethod in ((MelonBase)this).HarmonyInstance.GetPatchedMethods())
			{
				((MelonBase)this).LoggerInstance.Msg("Patched " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name);
			}
		}

		public override void OnUpdate()
		{
			if (_settings == null)
			{
				return;
			}
			Guarded("HUD binding", _hudBinder.Tick);
			if (!_settings.Enabled)
			{
				return;
			}
			Guarded("table spawning", delegate
			{
				_spawner.Tick(_settings.SpawnPose);
			});
			Guarded("table lock", delegate
			{
				if (_settings.BlockTableWithoutMod && RouletteTableSpawner.IsHosting)
				{
					_tableLock.Tick();
				}
			});
			Guarded("chat bet settlement", ChatBetService.Instance.Tick);
			Guarded("relocate hotkey", delegate
			{
				if (HotkeyReader.WasFunctionKeyPressed(_settings.RelocateFunctionKey))
				{
					_relocator.RelocateToLocalPlayer();
				}
			});
		}

		private void Guarded(string step, Action action)
		{
			try
			{
				action();
			}
			catch (Exception value)
			{
				string text = $"{step}: {value}";
				if (text != _lastError)
				{
					((MelonBase)this).LoggerInstance.Error($"Roulette Revival {step} failed: {value}");
					_lastError = text;
				}
			}
		}
	}
	internal static class ModVersion
	{
		public const string Value = "1.2.3";
	}
}
namespace RouletteRevival.TableLock
{
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class IsSpinningPatch
	{
		private static bool _loggedLockSeen;

		private static void Postfix(RouletteSpinner __instance, ref bool __result)
		{
			try
			{
				if (SpinLockRules.IsLocked(__instance.sync_LastSpinNetworkTime.Value))
				{
					if (!_loggedLockSeen)
					{
						_loggedLockSeen = true;
						Melon<RouletteRevivalMod>.Logger.Msg($"Table lock seen: games without the mod read spinning={__result}; this game uses the real state ({RealSpin.IsSpinning}).");
					}
					__result = RealSpin.IsSpinning;
				}
			}
			catch (Exception ex)
			{
				Melon<RouletteRevivalMod>.Logger.Warning("Spin state check failed; using the game's value: " + ex.Message);
			}
		}
	}
	public static class RealSpin
	{
		private static float _endsAt;

		public static bool IsSpinning => SpinLockRules.IsRealSpinActive(_endsAt, Time.unscaledTime);

		public static void Started(float spinTime)
		{
			float num = Time.unscaledTime + spinTime;
			if (num > _endsAt)
			{
				_endsAt = num;
			}
		}

		public static void Reset()
		{
			_endsAt = 0f;
		}
	}
	public static class SpinLockRules
	{
		public const double LockedSpinTime = 1000000000000.0;

		public const double LockThreshold = 100000000000.0;

		public static bool IsLocked(double lastSpinNetworkTime)
		{
			return lastSpinNetworkTime >= 100000000000.0;
		}

		public static bool IsRealSpinActive(float spinEndsAt, float now)
		{
			return now < spinEndsAt;
		}
	}
	[HarmonyPatch(typeof(RouletteTable), "OnSpin")]
	internal static class SpinResultPatch
	{
		private static void Postfix(int result, float spinTime)
		{
			try
			{
				RealSpin.Started(spinTime);
				if (RouletteTableSpawner.IsHosting)
				{
					TableBetDisplay.Clear();
				}
				RouletteSettings settings = RouletteRevivalMod.Settings;
				if (settings != null && settings.ChatBetting && RouletteTableSpawner.IsHosting)
				{
					ChatBetService.Instance.OnSpin(result, spinTime);
				}
			}
			catch (Exception value)
			{
				Melon<RouletteRevivalMod>.Logger.Error($"Spin result handling failed: {value}");
			}
		}
	}
	public sealed class TableLockEnforcer
	{
		private const float SearchIntervalSeconds = 1f;

		private RouletteSpinner _spinner;

		private float _nextSearch;

		private bool _loggedLock;

		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public void Tick()
		{
			RouletteSpinner val = FindSpinner();
			if ((Object)(object)val == (Object)null)
			{
				_loggedLock = false;
				return;
			}
			SyncVar<double> sync_LastSpinNetworkTime = val.sync_LastSpinNetworkTime;
			if (!SpinLockRules.IsLocked(sync_LastSpinNetworkTime.Value))
			{
				if (sync_LastSpinNetworkTime.Value > 0.0)
				{
					RealSpin.Started(val.spinTime);
				}
				sync_LastSpinNetworkTime.Value = 1000000000000.0;
				if (!_loggedLock)
				{
					_loggedLock = true;
					Log.Msg("Table locked for players without the mod (they bet with !bet and spin with !spin).");
				}
			}
		}

		private RouletteSpinner FindSpinner()
		{
			if ((Object)(object)_spinner != (Object)null)
			{
				return _spinner;
			}
			if (Time.unscaledTime < _nextSearch)
			{
				return null;
			}
			_nextSearch = Time.unscaledTime + 1f;
			_spinner = Object.FindObjectOfType<RouletteSpinner>();
			return _spinner;
		}
	}
}
namespace RouletteRevival.SpinDispatch
{
	[HarmonyPatch(typeof(RouletteSpinner), "RpcLogic___Cmd_SpinRoulette___2166136261")]
	internal static class SpinCommandPatch
	{
		private const int Pockets = 37;

		private static bool Prefix(RouletteSpinner __instance)
		{
			bool flag = false;
			try
			{
				RouletteTableNetworked rouletteTableNetworked = __instance.rouletteTableNetworked;
				TimeManager timeManager = InstanceFinder.TimeManager;
				if (!((NetworkBehaviour)__instance).IsServerInitialized || (Object)(object)rouletteTableNetworked == (Object)null || (Object)(object)timeManager == (Object)null)
				{
					return true;
				}
				if (__instance.IsSpinning)
				{
					return false;
				}
				int num = Random.Range(0, 37);
				float spinTime = __instance.spinTime;
				flag = true;
				__instance.sync_LastSpinNetworkTime.Value = timeManager.TicksToTime(timeManager.Tick);
				rouletteTableNetworked.Server_OnSpin(num, SpinTiming.DelayedResultTime(spinTime));
				__instance.Rpc_SpinWheel(spinTime, num, __instance.stopWheelBeforeEndMultiplier);
				RealSpin.Started(SpinTiming.DelayedResultTime(spinTime));
				return false;
			}
			catch (Exception value)
			{
				Melon<RouletteRevivalMod>.Logger.Error($"Spin dispatch failed{(flag ? "" : "; letting the game spin")}: {value}");
				return !flag;
			}
		}
	}
	[HarmonyPatch(typeof(RouletteTable), "OnSpin")]
	internal static class SpinResultTimingPatch
	{
		private static void Prefix(RouletteTable __instance, ref float spinTime)
		{
			try
			{
				RouletteSpinner spinner = __instance.spinner;
				if ((Object)(object)spinner != (Object)null)
				{
					spinTime = SpinTiming.LocalResultTime(spinTime, spinner.spinTime);
				}
			}
			catch (Exception ex)
			{
				Melon<RouletteRevivalMod>.Logger.Warning("Spin result timing check failed; using the received time: " + ex.Message);
			}
		}
	}
	public static class SpinTiming
	{
		public const float ResultDelaySeconds = 0.25f;

		private const float Tolerance = 0.01f;

		public static float DelayedResultTime(float wheelSpinTime)
		{
			return wheelSpinTime + 0.25f;
		}

		public static float LocalResultTime(float received, float wheelSpinTime)
		{
			if (!(Math.Abs(received - DelayedResultTime(wheelSpinTime)) <= 0.01f))
			{
				return received;
			}
			return wheelSpinTime;
		}
	}
}
namespace RouletteRevival.Spawning
{
	public static class GroundSnapper
	{
		private const float CastHeightAbove = 3f;

		private const float CastDistance = 8f;

		public static TablePose Snap(TablePose pose)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			RaycastHit val = default(RaycastHit);
			if (!Physics.Raycast(new Vector3(pose.X, pose.Y + 3f, pose.Z), Vector3.down, ref val, 8f, -5, (QueryTriggerInteraction)1))
			{
				return pose;
			}
			return pose.WithY(((RaycastHit)(ref val)).point.y);
		}
	}
	public static class PrefabNames
	{
		public const string RouletteSetup = "Roulette Setup";

		public static bool IsRouletteSetup(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			return string.Equals((name.EndsWith("(Clone)", StringComparison.Ordinal) ? name.Substring(0, name.Length - "(Clone)".Length) : name).Trim(), "Roulette Setup", StringComparison.Ordinal);
		}
	}
	public static class RoulettePrefabLocator
	{
		public static NetworkObject Find(NetworkManager networkManager)
		{
			PrefabObjects spawnablePrefabs = networkManager.SpawnablePrefabs;
			if ((Object)(object)spawnablePrefabs == (Object)null)
			{
				return null;
			}
			int objectCount = spawnablePrefabs.GetObjectCount();
			for (int i = 0; i < objectCount; i++)
			{
				NetworkObject val = spawnablePrefabs.GetObject(true, i);
				if ((Object)(object)val != (Object)null && PrefabNames.IsRouletteSetup(((Object)((Component)val).gameObject).name))
				{
					return val;
				}
			}
			return null;
		}
	}
	public sealed class RouletteTableSpawner
	{
		private const float SpawnDelaySeconds = 2f;

		private GameObject _spawned;

		private float _readySince = -1f;

		private bool _gaveUpThisSession;

		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public static bool IsHosting
		{
			get
			{
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				if ((Object)(object)networkManager != (Object)null)
				{
					return networkManager.IsServerStarted;
				}
				return false;
			}
		}

		public void Tick(TablePose configuredPose)
		{
			NetworkManager networkManager = InstanceFinder.NetworkManager;
			if ((Object)(object)networkManager == (Object)null || !networkManager.IsServerStarted)
			{
				ResetSession();
			}
			else
			{
				if (_gaveUpThisSession || (Object)(object)_spawned != (Object)null)
				{
					return;
				}
				if ((Object)(object)PlayerControl.LocalPlayerInstance == (Object)null)
				{
					_readySince = -1f;
				}
				else if (_readySince < 0f)
				{
					_readySince = Time.unscaledTime;
				}
				else if (!(Time.unscaledTime - _readySince < 2f))
				{
					if ((Object)(object)Object.FindObjectOfType<RouletteTable>() != (Object)null)
					{
						Log.Msg("A roulette table already exists in this world; not spawning another.");
						_gaveUpThisSession = true;
					}
					else
					{
						_spawned = SpawnAt(networkManager, GroundSnapper.Snap(configuredPose));
						_gaveUpThisSession = (Object)(object)_spawned == (Object)null;
					}
				}
			}
		}

		public bool Relocate(TablePose pose)
		{
			NetworkManager networkManager = InstanceFinder.NetworkManager;
			if ((Object)(object)networkManager == (Object)null || !networkManager.IsServerStarted)
			{
				return false;
			}
			if ((Object)(object)_spawned != (Object)null)
			{
				networkManager.ServerManager.Despawn(_spawned, new Nullable<DespawnType>());
				_spawned = null;
			}
			_spawned = SpawnAt(networkManager, pose);
			return (Object)(object)_spawned != (Object)null;
		}

		private static GameObject SpawnAt(NetworkManager networkManager, TablePose pose)
		{
			//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_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			NetworkObject val = RoulettePrefabLocator.Find(networkManager);
			if ((Object)(object)val == (Object)null)
			{
				Log.Error("Could not find the 'Roulette Setup' prefab in FishNet's spawnable prefabs. A game update may have removed it.");
				return null;
			}
			Quaternion val2 = Quaternion.Euler(0f, pose.YawDegrees, 0f);
			GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, new Vector3(pose.X, pose.Y, pose.Z), val2);
			networkManager.ServerManager.Spawn(val3, (NetworkConnection)null, default(Scene));
			Log.Msg($"Roulette table spawned at {pose}.");
			return val3;
		}

		private void ResetSession()
		{
			_spawned = null;
			_readySince = -1f;
			_gaveUpThisSession = false;
		}
	}
	public sealed class TableRelocator
	{
		private readonly RouletteTableSpawner _spawner;

		private readonly RouletteSettings _settings;

		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public TableRelocator(RouletteTableSpawner spawner, RouletteSettings settings)
		{
			_spawner = spawner ?? throw new ArgumentNullException("spawner");
			_settings = settings ?? throw new ArgumentNullException("settings");
		}

		public void RelocateToLocalPlayer()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			PlayerControl localPlayerInstance = PlayerControl.LocalPlayerInstance;
			if ((Object)(object)localPlayerInstance == (Object)null)
			{
				Log.Msg("Join or host a lobby before moving the roulette table.");
				return;
			}
			if (!RouletteTableSpawner.IsHosting)
			{
				Log.Msg("Only the lobby host can move the roulette table.");
				return;
			}
			Vector3 position = ((Component)localPlayerInstance).transform.position;
			TablePose tablePose = GroundSnapper.Snap(PlacementMath.InFrontOf(position.x, position.y, position.z, ViewYaw(localPlayerInstance)));
			if (!tablePose.IsValid)
			{
				Log.Warning($"Can't place the roulette table at {tablePose}; move somewhere else on the map and try again.");
			}
			else if (_spawner.Relocate(tablePose))
			{
				_settings.SaveCustomPose(tablePose);
				Log.Msg($"Roulette table moved to {tablePose} and saved.");
			}
		}

		private static float ViewYaw(PlayerControl player)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			Camera main = Camera.main;
			Vector3 val = (((Object)(object)main != (Object)null) ? ((Component)main).transform.forward : ((Component)player).transform.forward);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(val.x, 0f, val.z);
			if (!(((Vector3)(ref val2)).sqrMagnitude < 0.0001f))
			{
				Quaternion val3 = Quaternion.LookRotation(val2);
				return ((Quaternion)(ref val3)).eulerAngles.y;
			}
			return ((Component)player).transform.eulerAngles.y;
		}
	}
}
namespace RouletteRevival.Placement
{
	public static class PlacementMath
	{
		public static readonly TablePose DefaultPose = new TablePose(2.32f, 31.39f, 125.85f, 327.56f);

		public const float RelocateDistance = 2.5f;

		public const float SideBySideYawOffset = 90f;

		private const float DegToRad = (float)Math.PI / 180f;

		public static TablePose InFrontOf(float x, float y, float z, float playerYawDegrees, float distance = 2.5f)
		{
			if (distance < 0f || float.IsNaN(distance))
			{
				throw new ArgumentOutOfRangeException("distance", distance, "Distance must be zero or positive.");
			}
			float num = playerYawDegrees * ((float)Math.PI / 180f);
			float x2 = x + (float)Math.Sin(num) * distance;
			float z2 = z + (float)Math.Cos(num) * distance;
			return new TablePose(x2, y, z2, NormalizeYaw(playerYawDegrees + 90f));
		}

		public static float NormalizeYaw(float yawDegrees)
		{
			if (float.IsNaN(yawDegrees) || float.IsInfinity(yawDegrees))
			{
				return 0f;
			}
			float num = yawDegrees % 360f;
			if (!(num < 0f))
			{
				return num;
			}
			return num + 360f;
		}

		public static TablePose ResolveConfigured(bool useCustom, TablePose custom)
		{
			if (!useCustom || !custom.IsValid)
			{
				return DefaultPose;
			}
			return custom;
		}
	}
	public readonly struct TablePose : IEquatable<TablePose>
	{
		public const float MaxCoordinate = 10000f;

		public float X { get; }

		public float Y { get; }

		public float Z { get; }

		public float YawDegrees { get; }

		public bool IsValid
		{
			get
			{
				if (IsUsable(X) && IsUsable(Y) && IsUsable(Z) && !float.IsNaN(YawDegrees))
				{
					return !float.IsInfinity(YawDegrees);
				}
				return false;
			}
		}

		public TablePose(float x, float y, float z, float yawDegrees)
		{
			X = x;
			Y = y;
			Z = z;
			YawDegrees = yawDegrees;
		}

		public TablePose WithY(float y)
		{
			return new TablePose(X, y, Z, YawDegrees);
		}

		public bool Equals(TablePose other)
		{
			if (X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z))
			{
				return YawDegrees.Equals(other.YawDegrees);
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is TablePose other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return HashCode.Combine(X, Y, Z, YawDegrees);
		}

		public override string ToString()
		{
			return $"({X:0.0}, {Y:0.0}, {Z:0.0}) yaw {YawDegrees:0}";
		}

		private static bool IsUsable(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Abs(value) <= 10000f;
			}
			return false;
		}
	}
}
namespace RouletteRevival.Input
{
	public static class HotkeyParser
	{
		public const int MinFunctionKey = 1;

		public const int MaxFunctionKey = 12;

		public static bool TryParseFunctionKey(string text, out int number)
		{
			number = 0;
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = text.Trim();
			if (text2.Length < 2 || char.ToUpperInvariant(text2[0]) != 'F')
			{
				return false;
			}
			if (!int.TryParse(text2.AsSpan(1), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			if (result < 1 || result > 12)
			{
				return false;
			}
			number = result;
			return true;
		}
	}
	public static class HotkeyReader
	{
		private static bool _legacyUnavailable;

		public static bool WasFunctionKeyPressed(int number)
		{
			Keyboard current = Keyboard.current;
			if (current != null)
			{
				KeyControl val = FunctionKeyControl(current, number);
				if (val != null)
				{
					return ((ButtonControl)val).wasPressedThisFrame;
				}
				return false;
			}
			return WasPressedLegacy(number);
		}

		private static KeyControl FunctionKeyControl(Keyboard keyboard, int number)
		{
			return (KeyControl)(number switch
			{
				1 => keyboard.f1Key, 
				2 => keyboard.f2Key, 
				3 => keyboard.f3Key, 
				4 => keyboard.f4Key, 
				5 => keyboard.f5Key, 
				6 => keyboard.f6Key, 
				7 => keyboard.f7Key, 
				8 => keyboard.f8Key, 
				9 => keyboard.f9Key, 
				10 => keyboard.f10Key, 
				11 => keyboard.f11Key, 
				12 => keyboard.f12Key, 
				_ => null, 
			});
		}

		private static bool WasPressedLegacy(int number)
		{
			if (_legacyUnavailable || number < 1 || number > 12)
			{
				return false;
			}
			try
			{
				return Input.GetKeyDown((KeyCode)(282 + (number - 1)));
			}
			catch (Exception ex)
			{
				_legacyUnavailable = true;
				Melon<RouletteRevivalMod>.Logger.Warning("No keyboard available for the relocate hotkey: " + ex.Message);
				return false;
			}
		}
	}
}
namespace RouletteRevival.Hud
{
	public static class ColumnButtonFix
	{
		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public static void Apply(RouletteTable table)
		{
			try
			{
				MatchColumnsToRows(table);
			}
			catch (Exception ex)
			{
				Log.Warning("Couldn't check the betting menu's '2 to 1' buttons: " + ex.Message);
			}
		}

		private static void MatchColumnsToRows(RouletteTable table)
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Invalid comparison between Unknown and I4
			List<List<RouletteButtonUI>> groups = ButtonsByParent(table);
			List<RouletteButtonUI> columnButtons = FindGroup(groups, IsColumnGroup);
			List<RouletteButtonUI> list = FindGroup(groups, (List<RouletteButtonUI> g) => columnButtons != null && g.Count == columnButtons.Count && IsNumberGroup(g));
			if (columnButtons == null || list == null)
			{
				Log.Warning("Couldn't find the betting menu's '2 to 1' buttons; column bets may land on the wrong row.");
				return;
			}
			int num = 0;
			for (int num2 = 0; num2 < columnButtons.Count; num2++)
			{
				BetKind? betKind = ColumnRows.ColumnOf(list[num2].betNumber);
				if (betKind.HasValue && (int)columnButtons[num2].betType != (int)betKind.Value)
				{
					columnButtons[num2].betType = (RouletteBetType)betKind.Value;
					num++;
				}
			}
			if (num > 0)
			{
				Log.Msg($"Matched {num} '2 to 1' button(s) to the number rows beside them.");
			}
		}

		private static List<List<RouletteButtonUI>> ButtonsByParent(RouletteTable table)
		{
			List<List<RouletteButtonUI>> list = new List<List<RouletteButtonUI>>();
			List<IntPtr> list2 = new List<IntPtr>();
			foreach (RouletteButtonUI componentsInChild in ((Component)table).GetComponentsInChildren<RouletteButtonUI>(true))
			{
				Transform parent = ((Component)componentsInChild).transform.parent;
				IntPtr item = (((Object)(object)parent != (Object)null) ? ((Il2CppObjectBase)parent).Pointer : IntPtr.Zero);
				int num = list2.IndexOf(item);
				if (num < 0)
				{
					list2.Add(item);
					list.Add(new List<RouletteButtonUI>());
					num = list.Count - 1;
				}
				list[num].Add(componentsInChild);
			}
			return list;
		}

		private static List<RouletteButtonUI> FindGroup(List<List<RouletteButtonUI>> groups, Func<List<RouletteButtonUI>, bool> match)
		{
			foreach (List<RouletteButtonUI> group in groups)
			{
				if (match(group))
				{
					return group;
				}
			}
			return null;
		}

		private static bool IsColumnGroup(List<RouletteButtonUI> group)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected I4, but got Unknown
			foreach (RouletteButtonUI item in group)
			{
				if (!ColumnRows.IsColumn((BetKind)item.betType))
				{
					return false;
				}
			}
			return group.Count > 0;
		}

		private static bool IsNumberGroup(List<RouletteButtonUI> group)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			foreach (RouletteButtonUI item in group)
			{
				if (!ColumnRows.ColumnOf(item.betNumber).HasValue || (int)item.betType != 1)
				{
					return false;
				}
			}
			return group.Count > 0;
		}
	}
	public static class ColumnRows
	{
		public static BetKind? ColumnOf(int number)
		{
			if (number < 1 || number > 36)
			{
				return null;
			}
			return (number % 3) switch
			{
				1 => BetKind.Column1, 
				2 => BetKind.Column2, 
				_ => BetKind.Column3, 
			};
		}

		public static bool IsColumn(BetKind kind)
		{
			if (kind != BetKind.Column1 && kind != BetKind.Column2)
			{
				return kind == BetKind.Column3;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(UiReferenceController), "EnsureTopMenuHasValidSelection")]
	internal static class GamepadSelectionPatch
	{
		private static bool Prefix(UiReferenceController __instance)
		{
			GameObject activePanel = RouletteHudBinder.ActivePanel;
			if ((Object)(object)activePanel == (Object)null)
			{
				return true;
			}
			try
			{
				UiToggleableMenu currentOpenMenu = __instance.GetCurrentOpenMenu();
				if (currentOpenMenu == null || (Object)(object)currentOpenMenu.panel == (Object)null || ((Il2CppObjectBase)currentOpenMenu.panel).Pointer != ((Il2CppObjectBase)activePanel).Pointer)
				{
					return true;
				}
				EventSystem eventSystem = __instance.eventSystem;
				GameObject val = (((Object)(object)eventSystem != (Object)null) ? eventSystem.currentSelectedGameObject : null);
				if ((Object)(object)val == (Object)null || !val.activeInHierarchy)
				{
					return true;
				}
				return !val.transform.IsChildOf(activePanel.transform.parent);
			}
			catch (Exception ex)
			{
				Melon<RouletteRevivalMod>.Logger.Warning("Gamepad selection check failed; using the game's default: " + ex.Message);
				return true;
			}
		}
	}
	public static class HudPaths
	{
		public const string Canvas = "(Canvas) Roulette Betting (on)";

		public const string Panel = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)";

		public const string OddsText = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/current selected odds";

		public const string MaxWinText = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/potential earnings/slkjflksdjf (1)";

		public const string ClearBetsButton = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/(button) clear bets";

		public const string TotalBetText = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/Text (TMP) (1)";

		public const string BetAmountText = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/Text (TMP)";

		public const string DecreaseBetButton = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) decrease bet";

		public const string IncreaseBetButton = "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) increase bet";

		public static IReadOnlyList<string> Required { get; } = new string[9] { "(Canvas) Roulette Betting (on)", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/current selected odds", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/potential earnings/slkjflksdjf (1)", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/(button) clear bets", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/Text (TMP) (1)", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/Text (TMP)", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) decrease bet", "(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) increase bet" };

		public static IReadOnlyList<string> FindMissing(Func<string, bool> exists)
		{
			if (exists == null)
			{
				throw new ArgumentNullException("exists");
			}
			List<string> list = new List<string>();
			foreach (string item in Required)
			{
				if (!exists(item))
				{
					list.Add(item);
				}
			}
			return list;
		}
	}
	public static class InteractionMenuWriter
	{
		public static void SetRouletteMenu(UiReferenceController ui, GameObject panel, GameObject firstSelectable)
		{
			//IL_0007: 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_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0021: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Invalid comparison between Unknown and I4
			List<InteractionMenuPair> interactionMenus = ui.interactionMenus;
			InteractionMenuPair val = new InteractionMenuPair
			{
				menuType = (MenuType)8,
				menu = new UiToggleableMenu(panel, firstSelectable)
			};
			for (int i = 0; i < interactionMenus.Count; i++)
			{
				if ((int)interactionMenus[i].menuType == 8)
				{
					interactionMenus[i] = val;
					return;
				}
			}
			interactionMenus.Add(val);
		}
	}
	public sealed class RouletteHudBinder
	{
		private const float ScanIntervalSeconds = 0.25f;

		private UiReferenceController _boundUi;

		private RouletteTable _boundTable;

		private RouletteTable _rejectedTable;

		private bool _isBound;

		private float _nextScanTime;

		internal static GameObject ActivePanel { get; private set; }

		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public void Tick()
		{
			UiReferenceController instance = UiReferenceController.Instance;
			if ((Object)(object)instance == (Object)null || ((Object)(object)_boundUi != (Object)null && ((Il2CppObjectBase)instance).Pointer != ((Il2CppObjectBase)_boundUi).Pointer))
			{
				ForgetBinding();
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
			}
			if (_isBound)
			{
				if ((Object)(object)_boundTable != (Object)null)
				{
					return;
				}
				Unbind(instance);
			}
			if (!(Time.unscaledTime < _nextScanTime))
			{
				_nextScanTime = Time.unscaledTime + 0.25f;
				RouletteTable val = Object.FindObjectOfType<RouletteTable>();
				if (!((Object)(object)val == (Object)null) && (!((Object)(object)_rejectedTable != (Object)null) || !(((Il2CppObjectBase)val).Pointer == ((Il2CppObjectBase)_rejectedTable).Pointer)))
				{
					Bind(instance, val);
				}
			}
		}

		private void Bind(UiReferenceController ui, RouletteTable table)
		{
			if (!RouletteHudParts.TryFind(table, out var parts, out var missing))
			{
				Log.Error("Roulette table found but its betting HUD is incomplete (missing: " + string.Join(", ", missing) + "). Leaving it unwired.");
				_rejectedTable = table;
				return;
			}
			PruneDestroyedButtons(ui);
			ColumnButtonFix.Apply(table);
			ui.rouletteBetAmountText = parts.BetAmountText;
			ui.rouletteCurrentTotalBetText = parts.TotalBetText;
			ui.rouletteMaxWinText = parts.MaxWinText;
			ui.rouletteCurrentOddsText = parts.OddsText;
			ui.rouletteIncreaseBetButton = parts.IncreaseButton;
			ui.rouletteDecreaseBetButton = parts.DecreaseButton;
			ui.rouletteClearBetsButton = parts.ClearButton;
			InteractionMenuWriter.SetRouletteMenu(ui, parts.Panel, parts.FirstSelectable);
			ui.Roulette_Setup();
			_boundUi = ui;
			_boundTable = table;
			_isBound = true;
			ActivePanel = parts.Panel;
			Log.Msg("Roulette betting HUD wired up.");
		}

		private void Unbind(UiReferenceController ui)
		{
			PruneDestroyedButtons(ui);
			ui.rouletteBetAmountText = null;
			ui.rouletteCurrentTotalBetText = null;
			ui.rouletteMaxWinText = null;
			ui.rouletteCurrentOddsText = null;
			ui.rouletteIncreaseBetButton = null;
			ui.rouletteDecreaseBetButton = null;
			ui.rouletteClearBetsButton = null;
			InteractionMenuWriter.SetRouletteMenu(ui, null, null);
			ForgetBinding();
		}

		private void ForgetBinding()
		{
			_boundUi = null;
			_boundTable = null;
			_rejectedTable = null;
			_isBound = false;
			ActivePanel = null;
		}

		private static void PruneDestroyedButtons(UiReferenceController ui)
		{
			List<RouletteButtonUI> rouletteButtons = ui.rouletteButtons;
			if (rouletteButtons == null)
			{
				return;
			}
			for (int num = rouletteButtons.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)rouletteButtons[num] == (Object)null)
				{
					rouletteButtons.RemoveAt(num);
				}
			}
		}
	}
	public sealed class RouletteHudParts
	{
		public GameObject Panel { get; }

		public GameObject FirstSelectable { get; }

		public TMP_Text BetAmountText { get; }

		public TMP_Text TotalBetText { get; }

		public TMP_Text MaxWinText { get; }

		public TMP_Text OddsText { get; }

		public Button IncreaseButton { get; }

		public Button DecreaseButton { get; }

		public Button ClearButton { get; }

		private bool HasAllComponents
		{
			get
			{
				if ((Object)(object)BetAmountText != (Object)null && (Object)(object)TotalBetText != (Object)null && (Object)(object)MaxWinText != (Object)null && (Object)(object)OddsText != (Object)null && (Object)(object)IncreaseButton != (Object)null && (Object)(object)DecreaseButton != (Object)null)
				{
					return (Object)(object)ClearButton != (Object)null;
				}
				return false;
			}
		}

		private RouletteHudParts(GameObject panel, GameObject firstSelectable, TMP_Text betAmount, TMP_Text totalBet, TMP_Text maxWin, TMP_Text odds, Button increase, Button decrease, Button clear)
		{
			Panel = panel;
			FirstSelectable = firstSelectable;
			BetAmountText = betAmount;
			TotalBetText = totalBet;
			MaxWinText = maxWin;
			OddsText = odds;
			IncreaseButton = increase;
			DecreaseButton = decrease;
			ClearButton = clear;
		}

		public static bool TryFind(RouletteTable table, out RouletteHudParts parts, out IReadOnlyList<string> missing)
		{
			Transform root = ((Component)table).transform;
			missing = HudPaths.FindMissing((string path) => (Object)(object)root.Find(path) != (Object)null);
			parts = null;
			if (missing.Count > 0)
			{
				return false;
			}
			RouletteButtonUI componentInChildren = ((Component)root.Find("(Canvas) Roulette Betting (on)")).GetComponentInChildren<RouletteButtonUI>(true);
			RouletteHudParts rouletteHudParts = new RouletteHudParts(((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)")).gameObject, ((Object)(object)componentInChildren != (Object)null) ? ((Component)componentInChildren).gameObject : null, ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/Text (TMP)")).GetComponent<TMP_Text>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/Text (TMP) (1)")).GetComponent<TMP_Text>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/potential earnings/slkjflksdjf (1)")).GetComponent<TMP_Text>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/current selected odds")).GetComponent<TMP_Text>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) increase bet")).GetComponent<Button>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/change betting amount/layout gruop/(button) decrease bet")).GetComponent<Button>(), ((Component)root.Find("(Canvas) Roulette Betting (on)/Roulette table menu interactions (off)/(button) clear bets")).GetComponent<Button>());
			if (!rouletteHudParts.HasAllComponents)
			{
				missing = new string[1] { "a text or button component on the HUD objects" };
				return false;
			}
			parts = rouletteHudParts;
			return true;
		}
	}
	[HarmonyPatch(typeof(AnimationSelectableUi), "Deselect")]
	internal static class TileDeselectPatch
	{
		private static void Postfix(AnimationSelectableUi __instance)
		{
			try
			{
				UiReferenceController instance = UiReferenceController.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				RouletteButtonUI currentSelectedRouletteButton = instance.currentSelectedRouletteButton;
				if (!((Object)(object)currentSelectedRouletteButton == (Object)null))
				{
					AnimationSelectableUi selectableUi = currentSelectedRouletteButton.selectableUi;
					if ((Object)(object)selectableUi != (Object)null && ((Il2CppObjectBase)selectableUi).Pointer == ((Il2CppObjectBase)__instance).Pointer)
					{
						instance.currentSelectedRouletteButton = null;
					}
				}
			}
			catch (Exception ex)
			{
				Melon<RouletteRevivalMod>.Logger.Warning("Could not clear the deselected roulette tile: " + ex.Message);
			}
		}
	}
}
namespace RouletteRevival.Config
{
	public sealed class RouletteSettings
	{
		public const string CategoryId = "RouletteRevival";

		public const string DefaultRelocateKey = "F8";

		public const long MaxChatBetPerSpin = 10000000L;

		private readonly MelonPreferences_Category _category;

		private readonly MelonPreferences_Entry<bool> _enabled;

		private readonly MelonPreferences_Entry<bool> _useCustomPosition;

		private readonly MelonPreferences_Entry<float> _x;

		private readonly MelonPreferences_Entry<float> _y;

		private readonly MelonPreferences_Entry<float> _z;

		private readonly MelonPreferences_Entry<float> _yaw;

		private readonly MelonPreferences_Entry<string> _relocateKey;

		private readonly MelonPreferences_Entry<bool> _chatBetting;

		private readonly MelonPreferences_Entry<long> _chatBetMaxPerSpin;

		private readonly MelonPreferences_Entry<bool> _blockTableWithoutMod;

		public bool Enabled => _enabled.Value;

		public bool ChatBetting => _chatBetting.Value;

		public bool BlockTableWithoutMod => _blockTableWithoutMod.Value;

		public long ChatBetMaxPerSpin => Math.Clamp(_chatBetMaxPerSpin.Value, 0L, 10000000L);

		public TablePose SpawnPose => PlacementMath.ResolveConfigured(_useCustomPosition.Value, new TablePose(_x.Value, _y.Value, _z.Value, _yaw.Value));

		public int RelocateFunctionKey
		{
			get
			{
				if (!HotkeyParser.TryParseFunctionKey(_relocateKey.Value, out var number))
				{
					return int.Parse("F8".Substring(1));
				}
				return number;
			}
		}

		public bool IsRelocateKeyValid
		{
			get
			{
				int number;
				return HotkeyParser.TryParseFunctionKey(_relocateKey.Value, out number);
			}
		}

		public string RelocateKeyText => _relocateKey.Value;

		public RouletteSettings()
		{
			_category = MelonPreferences.CreateCategory("RouletteRevival", "Roulette Revival");
			_enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Spawn the roulette table when you host a lobby.", false, false, (ValueValidator)null, (string)null);
			_useCustomPosition = _category.CreateEntry<bool>("UseCustomPosition", false, "Use custom position", "Set automatically when the host relocates the table with the hotkey.", false, false, (ValueValidator)null, (string)null);
			TablePose defaultPose = PlacementMath.DefaultPose;
			_x = _category.CreateEntry<float>("PositionX", defaultPose.X, "Position X", (string)null, false, false, (ValueValidator)null, (string)null);
			_y = _category.CreateEntry<float>("PositionY", defaultPose.Y, "Position Y", (string)null, false, false, (ValueValidator)null, (string)null);
			_z = _category.CreateEntry<float>("PositionZ", defaultPose.Z, "Position Z", (string)null, false, false, (ValueValidator)null, (string)null);
			_yaw = _category.CreateEntry<float>("Yaw", defaultPose.YawDegrees, "Yaw (degrees)", (string)null, false, false, (ValueValidator)null, (string)null);
			_relocateKey = _category.CreateEntry<string>("RelocateKey", "F8", "Relocate hotkey", "Host only: moves the table in front of you and remembers the spot. F1-F12.", false, false, (ValueValidator)null, (string)null);
			_chatBetting = _category.CreateEntry<bool>("ChatBetting", true, "Chat betting", "Host only: players without the mod can bet by chat ('!bet red 5000', '!bets', '!clear', '!spin', '!help').", false, false, (ValueValidator)null, (string)null);
			_chatBetMaxPerSpin = _category.CreateEntry<long>("ChatBetMaxPerSpin", 500000L, "Chat bet limit per spin", "Most one player can stake on a single spin through chat (0 to 10,000,000).", false, false, (ValueValidator)null, (string)null);
			_blockTableWithoutMod = _category.CreateEntry<bool>("BlockTableWithoutMod", true, "Block table for players without the mod", "Host only: players without the mod can't open the table (their game's betting menu is broken and traps them in the table camera). They bet with !bet and spin with !spin instead.", false, false, (ValueValidator)null, (string)null);
		}

		public void SaveCustomPose(TablePose pose)
		{
			if (!pose.IsValid)
			{
				Melon<RouletteRevivalMod>.Logger.Warning($"Refusing to save invalid table position {pose}.");
			}
			else
			{
				_x.Value = pose.X;
				_y.Value = pose.Y;
				_z.Value = pose.Z;
				_yaw.Value = pose.YawDegrees;
				_useCustomPosition.Value = true;
				_category.SaveToFile(false);
			}
		}
	}
}
namespace RouletteRevival.ChatBetting
{
	public static class AmountParser
	{
		public const long MaxAmount = 1000000000L;

		public static bool TryParse(string text, out long amount)
		{
			amount = 0L;
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = text.Trim().Replace(",", string.Empty).ToLowerInvariant();
			long num = 1L;
			if (text2.EndsWith("k"))
			{
				num = 1000L;
				text2 = text2.Substring(0, text2.Length - 1);
			}
			else if (text2.EndsWith("m"))
			{
				num = 1000000L;
				text2 = text2.Substring(0, text2.Length - 1);
			}
			if (!decimal.TryParse(text2, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			decimal num2 = result * (decimal)num;
			if (num2 <= 0m || num2 > 1000000000m || num2 != decimal.Truncate(num2))
			{
				return false;
			}
			amount = (long)num2;
			return true;
		}
	}
	public readonly struct PlacedBet : IEquatable<PlacedBet>
	{
		public BetSpot Spot { get; }

		public long Amount { get; }

		public PlacedBet(BetSpot spot, long amount)
		{
			Spot = spot;
			Amount = amount;
		}

		public bool Equals(PlacedBet other)
		{
			if (Spot.Equals(other.Spot))
			{
				return Amount == other.Amount;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is PlacedBet other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return HashCode.Combine(Spot, Amount);
		}
	}
	public enum BetRejection
	{
		None,
		NotPositive,
		NotMultipleOfUnit,
		OverSpinLimit
	}
	public sealed class BetLedger
	{
		private ImmutableDictionary<int, ImmutableList<PlacedBet>> _bets = ImmutableDictionary<int, ImmutableList<PlacedBet>>.Empty;

		public bool IsEmpty => _bets.IsEmpty;

		public IReadOnlyList<PlacedBet> BetsOf(int playerId)
		{
			if (!_bets.TryGetValue(playerId, out var value))
			{
				return ImmutableList<PlacedBet>.Empty;
			}
			return value;
		}

		public long TotalOf(int playerId)
		{
			return BetsOf(playerId).Sum((PlacedBet b) => b.Amount);
		}

		public BetRejection TryAdd(int playerId, BetSpot spot, long amount, long unit, long maxPerSpin)
		{
			if (amount <= 0)
			{
				return BetRejection.NotPositive;
			}
			if (unit <= 0 || amount % unit != 0L)
			{
				return BetRejection.NotMultipleOfUnit;
			}
			if (TotalOf(playerId) + amount > maxPerSpin)
			{
				return BetRejection.OverSpinLimit;
			}
			ImmutableList<PlacedBet> value;
			ImmutableList<PlacedBet> immutableList = (_bets.TryGetValue(playerId, out value) ? value : ImmutableList<PlacedBet>.Empty);
			int num = immutableList.FindIndex((PlacedBet b) => b.Spot.Equals(spot));
			ImmutableList<PlacedBet> value2 = ((num >= 0) ? immutableList.SetItem(num, new PlacedBet(spot, immutableList[num].Amount + amount)) : immutableList.Add(new PlacedBet(spot, amount)));
			_bets = _bets.SetItem(playerId, value2);
			return BetRejection.None;
		}

		public long Clear(int playerId)
		{
			long result = TotalOf(playerId);
			_bets = _bets.Remove(playerId);
			return result;
		}

		public IReadOnlyDictionary<int, IReadOnlyList<PlacedBet>> TakeAll()
		{
			Dictionary<int, IReadOnlyList<PlacedBet>> result = ((IEnumerable<KeyValuePair<int, ImmutableList<PlacedBet>>>)_bets).ToDictionary((Func<KeyValuePair<int, ImmutableList<PlacedBet>>, int>)((KeyValuePair<int, ImmutableList<PlacedBet>> kv) => kv.Key), (Func<KeyValuePair<int, ImmutableList<PlacedBet>>, IReadOnlyList<PlacedBet>>)((KeyValuePair<int, ImmutableList<PlacedBet>> kv) => kv.Value));
			_bets = ImmutableDictionary<int, ImmutableList<PlacedBet>>.Empty;
			return result;
		}

		public void Reset()
		{
			_bets = ImmutableDictionary<int, ImmutableList<PlacedBet>>.Empty;
		}
	}
	public enum BetKind
	{
		Number = 1,
		Red = 2,
		Black = 3,
		Odd = 5,
		Even = 6,
		Dozen1 = 7,
		Dozen2 = 8,
		Dozen3 = 9,
		Column1 = 10,
		Column2 = 11,
		Column3 = 12,
		LowHalf = 13,
		HighHalf = 14
	}
	public readonly struct BetSpot : IEquatable<BetSpot>
	{
		public const int OutsideNumber = -1;

		public BetKind Kind { get; }

		public int Number { get; }

		public BetSpot(BetKind kind, int number)
		{
			Kind = kind;
			Number = number;
		}

		public static BetSpot ForNumber(int number)
		{
			return new BetSpot(BetKind.Number, number);
		}

		public static BetSpot Outside(BetKind kind)
		{
			return new BetSpot(kind, -1);
		}

		public bool Equals(BetSpot other)
		{
			if (Kind == other.Kind)
			{
				return Number == other.Number;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is BetSpot other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return HashCode.Combine(Kind, Number);
		}

		public override string ToString()
		{
			return Kind switch
			{
				BetKind.Number => Number.ToString(CultureInfo.InvariantCulture), 
				BetKind.Red => "red", 
				BetKind.Black => "black", 
				BetKind.Odd => "odd", 
				BetKind.Even => "even", 
				BetKind.LowHalf => "1-18", 
				BetKind.HighHalf => "19-36", 
				BetKind.Dozen1 => "1st 12", 
				BetKind.Dozen2 => "2nd 12", 
				BetKind.Dozen3 => "3rd 12", 
				BetKind.Column1 => "column 1", 
				BetKind.Column2 => "column 2", 
				BetKind.Column3 => "column 3", 
				_ => Kind.ToString(), 
			};
		}
	}
	public static class BetSpotParser
	{
		public const int MaxNumber = 36;

		private static readonly IReadOnlyDictionary<string, BetKind> Names = new Dictionary<string, BetKind>(StringComparer.OrdinalIgnoreCase)
		{
			["red"] = BetKind.Red,
			["black"] = BetKind.Black,
			["odd"] = BetKind.Odd,
			["even"] = BetKind.Even,
			["low"] = BetKind.LowHalf,
			["1-18"] = BetKind.LowHalf,
			["high"] = BetKind.HighHalf,
			["19-36"] = BetKind.HighHalf,
			["dozen1"] = BetKind.Dozen1,
			["1st12"] = BetKind.Dozen1,
			["dozen2"] = BetKind.Dozen2,
			["2nd12"] = BetKind.Dozen2,
			["dozen3"] = BetKind.Dozen3,
			["3rd12"] = BetKind.Dozen3,
			["col1"] = BetKind.Column1,
			["column1"] = BetKind.Column1,
			["col2"] = BetKind.Column2,
			["column2"] = BetKind.Column2,
			["col3"] = BetKind.Column3,
			["column3"] = BetKind.Column3
		};

		public static bool TryParse(string text, out BetSpot spot)
		{
			spot = default(BetSpot);
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = text.Trim();
			if (int.TryParse(text2, NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				if (result > 36 || (text2.Length > 1 && text2[0] == '0'))
				{
					return false;
				}
				spot = BetSpot.ForNumber(result);
				return true;
			}
			if (Names.TryGetValue(text2, out var value))
			{
				spot = BetSpot.Outside(value);
				return true;
			}
			return false;
		}
	}
	public sealed class Bettor
	{
		public int ClientId { get; }

		public int PlayerId { get; }

		public string Name { get; }

		public Bettor(int clientId, int playerId, string name)
		{
			ClientId = clientId;
			PlayerId = playerId;
			Name = name;
		}
	}
	public static class Bettors
	{
		public static Bettor FromConnection(NetworkConnection connection)
		{
			NetworkObject firstObject = connection.FirstObject;
			PlayerControl val = (((Object)(object)firstObject != (Object)null) ? ((Component)firstObject).GetComponent<PlayerControl>() : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			return new Bettor(connection.ClientId, ((NetworkBehaviour)val).ObjectId, UsernameOf(connection, val));
		}

		private static string UsernameOf(NetworkConnection connection, PlayerControl player)
		{
			PlayerReferenceManager instance = PlayerReferenceManager.Instance;
			SyncList<PlayerReference> val = (((Object)(object)instance != (Object)null) ? instance.sync_PlayerReferences : null);
			if (val != null)
			{
				for (int i = 0; i < val.Count; i++)
				{
					PlayerReference val2 = val[i];
					if (val2 != null && val2.ConnectionID == connection.ClientId && !string.IsNullOrWhiteSpace(val2.Username))
					{
						return val2.Username;
					}
				}
			}
			string text = (((Object)(object)player.usernameController != (Object)null) ? player.usernameController.GetUsername() : null);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return $"Player {connection.ClientId}";
		}

		public static NetworkConnection ConnectionOf(int clientId)
		{
			ServerManager serverManager = InstanceFinder.ServerManager;
			NetworkConnection result = default(NetworkConnection);
			if (!((Object)(object)serverManager != (Object)null) || !serverManager.Clients.TryGetValue(clientId, ref result))
			{
				return null;
			}
			return result;
		}
	}
	public sealed class ChatBetService
	{
		private sealed class PendingSpin
		{
			public int Result { get; }

			public float DueTime { get; }

			public IReadOnlyDictionary<int, IReadOnlyList<PlacedBet>> Bets { get; }

			public ImmutableDictionary<int, Bettor> Bettors { get; }

			public PendingSpin(int result, float dueTime, IReadOnlyDictionary<int, IReadOnlyList<PlacedBet>> bets, ImmutableDictionary<int, Bettor> bettors)
			{
				Result = result;
				DueTime = dueTime;
				Bets = bets;
				Bettors = bettors;
			}
		}

		private const float SettleDelayAfterSpin = 0.5f;

		private readonly BetLedger _ledger = new BetLedger();

		private ImmutableDictionary<int, Bettor> _bettors = ImmutableDictionary<int, Bettor>.Empty;

		private IntPtr _tablePointer;

		private PendingSpin _pending;

		public static ChatBetService Instance { get; } = new ChatBetService();

		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public void Handle(NetworkConnection connection, ChatCommand command, long maxPerSpin)
		{
			if (command.Kind == ChatCommandKind.Help)
			{
				foreach (string item in ChatHelp.Page((int)command.Amount, TableBridge.ChargeUnit(), maxPerSpin))
				{
					ChatOutput.ToPlayer(connection, item);
				}
				return;
			}
			Bettor bettor = Bettors.FromConnection(connection);
			RouletteTableNetworked val = TableBridge.FindTable();
			if (bettor == null || (Object)(object)val == (Object)null)
			{
				Log.Warning($"[bet] Ignored {command}: {((bettor == null) ? "sender has no player object" : "no roulette table")}.");
				if ((Object)(object)val == (Object)null)
				{
					ChatOutput.ToPlayer(connection, "There's no roulette table right now.");
				}
				return;
			}
			ResetIfTableChanged(val);
			switch (command.Kind)
			{
			case ChatCommandKind.Bet:
				PlaceBet(connection, val, bettor, command, maxPerSpin);
				break;
			case ChatCommandKind.Clear:
				ClearBets(connection, val, bettor);
				break;
			case ChatCommandKind.ListBets:
				ChatOutput.ToPlayer(connection, "Your bets: " + Describe(_ledger.BetsOf(bettor.PlayerId)));
				break;
			case ChatCommandKind.Spin:
				SpinWheel(connection, val, bettor);
				break;
			}
		}

		public void OnSpin(int result, float spinTime)
		{
			if (!_ledger.IsEmpty)
			{
				_pending = new PendingSpin(result, Time.unscaledTime + spinTime + 0.5f, _ledger.TakeAll(), _bettors);
				Log.Msg($"[bet] Spin locked in {_pending.Bets.Count} chat bettor(s); result {result} settles in {spinTime:0.0}s.");
			}
		}

		public void Tick()
		{
			if (_pending != null && !(Time.unscaledTime < _pending.DueTime))
			{
				PendingSpin pending = _pending;
				_pending = null;
				RouletteTableNetworked val = TableBridge.FindTable();
				if ((Object)(object)val == (Object)null || ((Il2CppObjectBase)val).Pointer != _tablePointer)
				{
					ChatOutput.ToEveryone("The table moved during the spin, so chat bets were voided (nothing was charged).");
				}
				else
				{
					SettleSpin(pending);
				}
			}
		}

		private void PlaceBet(NetworkConnection connection, RouletteTableNetworked table, Bettor bettor, ChatCommand command, long maxPerSpin)
		{
			if (_pending != null || TableBridge.IsSpinning())
			{
				ChatOutput.ToPlayer(connection, "Bets are closed while the wheel spins.");
				return;
			}
			long unit = TableBridge.ChargeUnit();
			BetRejection betRejection = _ledger.TryAdd(bettor.PlayerId, command.Spot, command.Amount, unit, maxPerSpin);
			if (betRejection != BetRejection.None)
			{
				ChatOutput.ToPlayer(connection, RejectionText(betRejection, unit, maxPerSpin));
				return;
			}
			_bettors = _bettors.SetItem(bettor.PlayerId, bettor);
			TableBetDisplay.SetChatBets(table, bettor.PlayerId, _ledger.BetsOf(bettor.PlayerId));
			ChatOutput.ToEveryone($"{bettor.Name} bet {command.Amount:N0} on {command.Spot} (total {_ledger.TotalOf(bettor.PlayerId):N0}).");
		}

		private void SpinWheel(NetworkConnection connection, RouletteTableNetworked table, Bettor bettor)
		{
			if (_pending != null || TableBridge.IsSpinning())
			{
				ChatOutput.ToPlayer(connection, "The wheel is already spinning.");
				return;
			}
			if (table.sync_PlayerBets.Count == 0)
			{
				ChatOutput.ToPlayer(connection, "Place a bet first (!help for how).");
				return;
			}
			RouletteSpinner val = Object.FindObjectOfType<RouletteSpinner>();
			if ((Object)(object)val == (Object)null)
			{
				Log.Warning("[bet] !spin ignored: no roulette wheel.");
				return;
			}
			ChatOutput.ToEveryone(bettor.Name + " spins the wheel!");
			val.RpcLogic___Cmd_SpinRoulette___2166136261();
		}

		private void ClearBets(NetworkConnection connection, RouletteTableNetworked table, Bettor bettor)
		{
			if (_pending != null || TableBridge.IsSpinning())
			{
				ChatOutput.ToPlayer(connection, "Too late, the wheel is spinning.");
				return;
			}
			long num = _ledger.Clear(bettor.PlayerId);
			TableBetDisplay.SetChatBets(table, bettor.PlayerId, Array.Empty<PlacedBet>());
			if (num > 0)
			{
				ChatOutput.ToEveryone($"{bettor.Name} took back {num:N0}.");
			}
			else
			{
				ChatOutput.ToPlayer(connection, "You have no bets.");
			}
		}

		private static void SettleSpin(PendingSpin spin)
		{
			long chargeUnit = TableBridge.ChargeUnit();
			List<string> list = new List<string>();
			foreach (KeyValuePair<int, IReadOnlyList<PlacedBet>> bet in spin.Bets)
			{
				Bettor value;
				Bettor bettor = (spin.Bettors.TryGetValue(bet.Key, out value) ? value : null);
				SettlementResult result = Settlement.Settle(bet.Value, (PlacedBet bet) => TableBridge.ReturnFor(bet, spin.Result), chargeUnit);
				NetworkConnection val = ((bettor != null) ? Bettors.ConnectionOf(bettor.ClientId) : null);
				if (val == (NetworkConnection)null)
				{
					Log.Warning($"[bet] Player {bet.Key} left before settling; net {result.Net:N0} not applied.");
				}
				else
				{
					ApplyResult(val, result);
					list.Add($"{bettor.Name} {((result.Net >= 0) ? "+" : "-")}{Math.Abs(result.Net):N0}");
				}
			}
			string value2 = ((spin.Result == 0) ? "green" : (RouletteTable.IsRed(spin.Result) ? "red" : "black"));
			ChatOutput.ToEveryone($"{spin.Result} {value2}! {string.Join(", ", list)}");
		}

		private static void ApplyResult(NetworkConnection connection, SettlementResult result)
		{
			if (result.PayAmount > 0 && !RemotePoints.TryPayTotal(connection, result.PayAmount, out var error))
			{
				Log.Warning("[bet] Payout failed: " + error + ".");
			}
			if (result.ChargeCount > 0 && !RemotePoints.TryChargeSpinCosts(connection, result.ChargeCount, out var error2))
			{
				Log.Warning("[bet] Charge failed: " + error2 + ".");
			}
		}

		private void ResetIfTableChanged(RouletteTableNetworked table)
		{
			if (!(_tablePointer == ((Il2CppObjectBase)table).Pointer))
			{
				bool num = !_ledger.IsEmpty;
				_tablePointer = ((Il2CppObjectBase)table).Pointer;
				_ledger.Reset();
				_bettors = ImmutableDictionary<int, Bettor>.Empty;
				_pending = null;
				if (num)
				{
					ChatOutput.ToEveryone("The table moved, so chat bets were cleared (nothing was charged).");
				}
			}
		}

		private static string Describe(IReadOnlyList<PlacedBet> bets)
		{
			if (bets.Count != 0)
			{
				return string.Join(", ", bets.Select((PlacedBet b) => $"{b.Amount:N0} on {b.Spot}"));
			}
			return "no bets";
		}

		private static string RejectionText(BetRejection rejection, long unit, long maxPerSpin)
		{
			return rejection switch
			{
				BetRejection.NotMultipleOfUnit => $"bets must be multiples of {unit:N0}.", 
				BetRejection.OverSpinLimit => $"the limit is {maxPerSpin:N0} per spin.", 
				_ => "that bet isn't valid.", 
			};
		}
	}
	public enum ChatCommandKind
	{
		Bet,
		Clear,
		ListBets,
		Spin,
		Help
	}
	public sealed class ChatCommand : IEquatable<ChatCommand>
	{
		public ChatCommandKind Kind { get; }

		public long Amount { get; }

		public BetSpot Spot { get; }

		public ChatCommand(ChatCommandKind kind, long amount = 0L, BetSpot spot = default(BetSpot))
		{
			Kind = kind;
			Amount = amount;
			Spot = spot;
		}

		public bool Equals(ChatCommand other)
		{
			if (other != null && Kind == other.Kind && Amount == other.Amount)
			{
				return Spot.Equals(other.Spot);
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			return Equals(obj as ChatCommand);
		}

		public override int GetHashCode()
		{
			return HashCode.Combine(Kind, Amount, Spot);
		}

		public override string ToString()
		{
			return $"{Kind} {Spot} {Amount}";
		}
	}
	public static class ChatCommandParser
	{
		public const int MaxHelpPage = 99;

		public static bool TryParse(string message, out ChatCommand command)
		{
			command = null;
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			string[] array = message.Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
			switch (array[0].ToLowerInvariant())
			{
			case "!bet":
				return TryParseBet(array, out command);
			case "!clear":
				command = ((array.Length == 1) ? new ChatCommand(ChatCommandKind.Clear, 0L) : null);
				return command != null;
			case "!bets":
				command = ((array.Length == 1) ? new ChatCommand(ChatCommandKind.ListBets, 0L) : null);
				return command != null;
			case "!spin":
				command = ((array.Length == 1) ? new ChatCommand(ChatCommandKind.Spin, 0L) : null);
				return command != null;
			case "!help":
				return TryParseHelp(array, out command);
			default:
				return false;
			}
		}

		private static bool TryParseBet(string[] parts, out ChatCommand command)
		{
			command = null;
			if (parts.Length != 3 || !BetSpotParser.TryParse(parts[1], out var spot) || !AmountParser.TryParse(parts[2], out var amount))
			{
				return false;
			}
			command = new ChatCommand(ChatCommandKind.Bet, amount, spot);
			return true;
		}

		private static bool TryParseHelp(string[] parts, out ChatCommand command)
		{
			command = null;
			int value = 1;
			if (parts.Length > 2 || (parts.Length == 2 && !TryPositive(parts[1], 99, out value)))
			{
				return false;
			}
			command = new ChatCommand(ChatCommandKind.Help, value);
			return true;
		}

		private static bool TryPositive(string text, int max, out int value)
		{
			if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out value) && value > 0)
			{
				return value <= max;
			}
			return false;
		}
	}
	public static class ChatCommandRouter
	{
		public static bool TryHandle(NetworkConnection sender, string message)
		{
			RouletteSettings settings = RouletteRevivalMod.Settings;
			if (settings == null || sender == (NetworkConnection)null || !RouletteTableSpawner.IsHosting)
			{
				return false;
			}
			if (!ChatCommandParser.TryParse(message, out var command))
			{
				return false;
			}
			if (!settings.ChatBetting)
			{
				return false;
			}
			ChatBetService.Instance.Handle(sender, command, settings.ChatBetMaxPerSpin);
			return true;
		}
	}
	public static class ChatHelp
	{
		public const int PageCount = 4;

		public const int MaxLinesPerPage = 4;

		public static IReadOnlyList<string> Page(int page, long chipUnit, long maxPerSpin)
		{
			return Math.Clamp(page, 1, 4) switch
			{
				1 => new string[3]
				{
					$"Help 1/{4}: !bet <spot> <amount>",
					"e.g. !bet 17 10k or !bet red 5k",
					$"Steps of {chipUnit:N0}, max {maxPerSpin:N0}/spin. More: !help 2"
				}, 
				2 => new string[4]
				{
					$"Help 2/{4}: spots: 0-36 (one number)",
					"red, black, odd, even",
					"low (1-18), high (19-36)",
					"dozen1-3 (1-12/13-24/25-36), col1-3. !help 3"
				}, 
				3 => new string[4]
				{
					$"Help 3/{4}: you win, on a {chipUnit:N0} bet:",
					$"a number: +{chipUnit * 35:N0} (35x)",
					$"a dozen or column: +{chipUnit * 2:N0} (2x)",
					$"red black odd even low high: +{chipUnit:N0}. !help 4"
				}, 
				_ => new string[3]
				{
					$"Help 4/{4}: !bets shows your bets",
					"!clear takes your bets back",
					"!spin spins the wheel (needs a bet)"
				}, 
			};
		}
	}
	public static class ChatLineFormat
	{
		public const string SenderName = "Roulette";

		public const string HideRest = "<size=0>";

		public static string Line(string text)
		{
			return "Roulette: " + text;
		}

		public static string InSystemTemplate(string text)
		{
			return Line(text) + "<size=0>";
		}
	}
	public static class ChatOutput
	{
		private static Instance Log => Melon<RouletteRevivalMod>.Logger;

		public static void ToEveryone(string text)
		{
			try
			{
				InstanceFinder.ServerManager.Broadcast<ChatMessage>(Message(text), true, (Channel)0);
			}
			catch (Exception ex)
			{
				Log.Warning("Roulette chat broadcast failed: " + ex.Message);
			}
		}

		public static void ToPlayer(NetworkConnection player, string text)
		{
			if (player.IsLocalClient)
			{
				ChatManager.Instance.SendLocalSystemChatMessage(ChatLineFormat.Line(text));
				return;
			}
			try
			{
				InstanceFinder.ServerManager.Broadcast<ChatMessage>(player, Message(text), true, (Channel)0);
			}
			catch (Exception ex)
			{
				Log.Warning($"Private roulette message to client {player.ClientId} failed: {ex.Message}");
			}
		}

		private static ChatMessage Message(string text)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			ChatMessage val = new ChatMessage(string.Empty, (ChatMessageType)1)
			{
				Username = ChatLineFormat.InSystemTemplate(text),
				UserProductId = string.Empty
			};
			val.SetSystemMessageType((SystemMessageType)0);
			return val;
		}
	}
	[HarmonyPatch(typeof(ChatManagerBase), "SendChatMessage")]
	internal static class HostChatPatch
	{
		private static bool Prefix(string cleanedString)
		{
			try
			{
				return !ChatCommandRouter.TryHandle(((Object)(object)InstanceFinder.ClientManager != (Object)null) ? InstanceFinder.ClientManager.Connection : null, cleanedString);
			}
			catch (Exception value)
			{
				Melon<RouletteRevivalMod>.Logger.Error($"Roulette host chat command failed; sending it as normal chat: {value}");
				return true;
			}
		}
	}
	public static class RemotePoints
	{
		private const int PayoutPlacement = 1;

		private const float NoTicketReward = 0f;

		public static bool TryPay(NetworkConnection player, int points, out string error)
		{
			PlayerHideSeekController val = FindHideSeekController(player);
			if ((Object)(object)val == (Object)null)
			{
				error = "the player's object has no PlayerHideSeekController";
				return false;
			}
			val.Target_AddPoints(player, 1, points);
			error = null;
			return true;
		}

		public static bool TryPayTotal(NetworkConnection player, long points, out string error)
		{
			error = null;
			long num = points;
			while (num > 0)
			{
				int num2 = (int)Math.Min(num, 2147483647L);
				if (!TryPay(player, num2, out error))
				{
					return false;
				}
				num -= num2;
			}
			return true;
		}

		public static bool TryChargeSpinCosts(NetworkConnection player, long times, out string error)
		{
			ArcadeWheelSpinInteractable val = Object.FindObjectOfType<ArcadeWheelSpinInteractable>();
			if ((Object)(object)val == (Object)null)
			{
				error = "no arcade Big Wheel in this scene";
				return false;
			}
			for (long num = 0L; num < times; num++)
			{
				val.Target_SpendPoints(player, 0f);
			}
			error = null;
			return true;
		}

		private static PlayerHideSeekController FindHideSeekController(NetworkConnection player)
		{
			NetworkObject firstObject = player.FirstObject;
			if ((Object)(object)firstObject != (Object)null)
			{
				PlayerHideSeekController componentInChildren = ((Component)firstObject).GetComponentInChildren<PlayerHideSeekController>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					return componentInChildren;
				}
			}
			foreach (PlayerHideSeekController item in Object.FindObjectsOfType<PlayerHideSeekController>())
			{
				if (((NetworkBehaviour)item).Owner != (NetworkConnection)null && ((NetworkBehaviour)item).Owner.ClientId == player.ClientId)
				{
					return item;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(ChatManagerBase), "OnServerReceivedChatBroadcastFromClient")]
	internal static class ServerChatPatch
	{
		private static bool Prefix(NetworkConnection networkConnection, ChatMessage chatMessage)
		{
			try
			{
				return !ChatCommandRouter.TryHandle(networkConnection, (chatMessage != null) ? chatMessage.Message : null);
			}
			catch (Exception value)
			{
				Melon<RouletteRevivalMod>.Logger.Error($"Roulette chat command failed; letting the message through: {value}");
				return true;
			}
		}
	}
	public readonly struct SettlementResult
	{
		public long Staked { get; }

		public long Returned { get; }

		public long ChargeUnit { get; }

		public long Net => Returned - Staked;

		public long PayAmount
		{
			get
			{
				if (Net <= 0)
				{
					return 0L;
				}
				return Net;
			}
		}

		public long ChargeCount
		{
			get
			{
				if (Net >= 0 || ChargeUnit <= 0)
				{
					return 0L;
				}
				return (-Net + ChargeUnit - 1) / ChargeUnit;
			}
		}

		public SettlementResult(long staked, long returned, long chargeUnit)
		{
			Staked = staked;
			Returned = returned;
			ChargeUnit = chargeUnit;
		}
	}
	public static class Settlement
	{
		public static SettlementResult Settle(IReadOnlyList<PlacedBet> bets, Func<PlacedBet, long> returnFor, long chargeUnit)
		{
			if (bets == null)
			{
				throw new ArgumentNullException("bets");
			}
			if (returnFor == null)
			{
				throw new ArgumentNullException("returnFor");
			}
			return new SettlementResult(bets.Sum((PlacedBet b) => b.Amount), bets.Sum(returnFor), chargeUnit);
		}
	}
	public static class TableBridge
	{
		public const long DefaultChargeUnit = 5000L;

		public static RouletteTableNetworked FindTable()
		{
			return Object.FindObjectOfType<RouletteTableNetworked>();
		}

		public static bool IsSpinning()
		{
			RouletteSpinner val = Object.FindObjectOfType<RouletteSpinner>();
			if ((Object)(object)val != (Object)null)
			{
				return val.IsSpinning;
			}
			return false;
		}

		public static long ChargeUnit()
		{
			ArcadeWheelSpinInteractable val = Object.FindObjectOfType<ArcadeWheelSpinInteractable>();
			if (!((Object)(object)val != (Object)null) || val.costOfSpin <= 0)
			{
				return 5000L;
			}
			return val.costOfSpin;
		}

		public static long ReturnFor(PlacedBet bet, int result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			RouletteBet val = ToGameBet(bet);
			if (!RouletteTable.IsWinningBet(val, result))
			{
				return 0L;
			}
			return RouletteTable.CalculatePayout(val);
		}

		private static RouletteBet ToGameBet(PlacedBet bet)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			return new RouletteBet((RouletteBetType)bet.Spot.Kind, bet.Spot.Number, bet.Amount);
		}
	}
}
namespace RouletteRevival.BetDisplay
{
	public static class BetAggregation
	{
		public static IReadOnlyList<PlacedBet> SumBySpot(IEnumerable<IReadOnlyList<PlacedBet>> betLists)
		{
			Dictionary<BetSpot, long> dictionary = new Dictionary<BetSpot, long>();
			if (betLists != null)
			{
				foreach (IReadOnlyList<PlacedBet> betList in betLists)
				{
					if (betList == null)
					{
						continue;
					}
					foreach (PlacedBet item in betList)
					{
						if (item.Amount > 0)
						{
							dictionary[item.Spot] = (dictionary.TryGetValue(item.Spot, out var value) ? value : 0) + item.Amount;
						}
					}
				}
			}
			List<PlacedBet> list = new List<PlacedBet>(dictionary.Count);
			foreach (KeyValuePair<BetSpot, long> item2 in dictionary)
			{
				list.Add(new PlacedBet(item2.Key, item2.Value));
			}
			list.Sort(CompareSpots);
			return list;
		}

		private static int CompareSpots(PlacedBet a, PlacedBet b)
		{
			int num = ((int)a.Spot.Kind).CompareTo((int)b.Spot.Kind);
			if (num == 0)
			{
				return a.Spot.Number.CompareTo(b.Spot.Number);
			}
			return num;
		}
	}
	[HarmonyPatch(typeof(RouletteTableNetworked), "RpcReader___Cmd_SetBetList___2449647308")]
	internal static class BoardBetsPatch
	{
		private static void Postfix(RouletteTableNetworked __instance, NetworkConnection conn)
		{
			try
			{
				if (RouletteTableSpawner.IsHosting && !(conn == (NetworkConnection)null))
				{
					NetworkObject firstObject = conn.FirstObject;
					PlayerControl val = (((Object)(object)firstObject != (Object)null) ? ((Component)firstObject).GetComponent<PlayerControl>() : null);
					TableBetDisplay.AbsorbBoardBets(__instance, ((Object)(object)val != (Object)null) ? new int?(((NetworkBehaviour)val).ObjectId) : ((int?)null));
				}
			}
			catch (Exception ex)
			{
				Melon<RouletteRevivalMod>.Logger.Warning("Combining table chips failed; the game's own display stays: " + ex.Message);
			}
		}
	}
	public static class TableBetDisplay
	{
		public const int CombinedKey = -1;

		private static ImmutableDictionary<int, IReadOnlyList<PlacedBet>> _board = ImmutableDictionary<int, IReadOnlyList<PlacedBet>>.Empty;

		private static ImmutableDictionary<int, IReadOnlyList<PlacedBet>> _chat = ImmutableDictionary<int, IReadOnlyList<PlacedBet>>.Empty;

		private static IntPtr _tablePointer;

		public static void AbsorbBoardBets(RouletteTableNetworked table, int? senderId)
		{
			ForgetIfTableChanged(table);
			SyncDictionary<int, List<RouletteBet>> sync_PlayerBets = table.sync_PlayerBets;
			List<int> list = new List<int>();
			Enumerator<int, List<RouletteBet>> enumerator = sync_PlayerBets.Collection.GetEnumerator();
			while (enumerator.MoveNext())
			{
				KeyValuePair<int, List<RouletteBet>> current = enumerator.Current;
				if (current.Key != -1)
				{
					list.Add(current.Key);
				}
			}
			if (senderId.HasValue && !list.Contains(senderId.Value))
			{
				list.Add(senderId.Value);
			}
			foreach (int item in list)
			{
				IReadOnlyList<PlacedBet> readOnlyList2;
				if (!sync_PlayerBets.ContainsKey(item))
				{
					IReadOnlyList<PlacedBet> readOnlyList = Array.Empty<PlacedBet>();
					readOnlyList2 = readOnlyList;
				}
				else
				{
					readOnlyList2 = FromGame(sync_PlayerBets[item]);
				}
				IReadOnlyList<PlacedBet> readOnlyList3 = readOnlyList2;
				_board = ((readOnlyList3.Count > 0) ? _board.SetItem(item, readOnlyList3) : _board.Remove(item));
				if (sync_PlayerBets.ContainsKey(item))
				{
					sync_PlayerBets.Remove(item);
				}
			}
			Publish(table);
		}

		public static void SetChatBets(RouletteTableNetworked table, int playerId, IReadOnlyList<PlacedBet> bets)
		{
			ForgetIfTableChanged(table);
			_chat = ((bets.Count > 0) ? _chat.SetItem(playerId, bets) : _chat.Remove(playerId));
			Publish(table);
		}

		public static void Clear()
		{
			_board = ImmutableDictionary<int, IReadOnlyList<PlacedBet>>.Empty;
			_chat = ImmutableDictionary<int, IReadOnlyList<PlacedBet>>.Empty;
		}

		private static void Publish(RouletteTableNetworked table)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			IReadOnlyList<PlacedBet> readOnlyList = BetAggregation.SumBySpot(_board.Values.Concat(_chat.Values));
			SyncDictionary<int, List<RouletteBet>> sync_PlayerBets = table.sync_PlayerBets;
			if (readOnlyList.Count == 0)
			{
				if (sync_PlayerBets.ContainsKey(-1))
				{
					sync_PlayerBets.Remove(-1);
				}
				return;
			}
			List<RouletteBet> val = new List<RouletteBet>();
			foreach (PlacedBet item in readOnlyList)
			{
				val.Add(new RouletteBet((RouletteBetType)item.Spot.Kind, item.Spot.Number, item.Amount));
			}
			sync_PlayerBets[-1] = val;
		}

		private static IReadOnlyList<PlacedBet> FromGame(List<RouletteBet> bets)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected I4, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			List<PlacedBet> list = new List<PlacedBet>();
			if (bets == null)
			{
				return list;
			}
			for (int i = 0; i < bets.Count; i++)
			{
				RouletteBet val = bets[i];
				list.Add(new PlacedBet(new BetSpot((BetKind)val.betType, val.numberBetOn), val.betAmount));
			}
			return list;
		}

		private static void ForgetIfTableChanged(RouletteTableNetworked table)
		{
			if (_tablePointer != ((Il2CppObjectBase)table).Pointer)
			{
				_tablePointer = ((Il2CppObjectBase)table).Pointer;
				Clear();
			}
		}
	}
}