Decompiled source of AtlyssCasino v1.9.0

AtlyssCasino.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using AtlyssCasino.Blackjack;
using AtlyssCasino.Blackjack.Netcode;
using AtlyssCasino.Jukebox.Netcode;
using AtlyssCasino.RoomZoneChat.Netcode;
using AtlyssCasino.Roulette;
using AtlyssCasino.Roulette.Netcode;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CodeTalker;
using CodeTalker.Networking;
using CodeTalker.Packets;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Nessie.ATLYSS.EasySettings;
using Nessie.ATLYSS.EasySettings.UIElements;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AtlyssCasino")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.9.0.0")]
[assembly: AssemblyInformationalVersion("1.9.0")]
[assembly: AssemblyProduct("AtlyssCasino")]
[assembly: AssemblyTitle("AtlyssCasino")]
[assembly: AssemblyVersion("1.9.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AtlyssCasino
{
	public static class BlackjackSceneWatcher
	{
		[CompilerGenerated]
		private sealed class <DeferredCleanupCoroutine>d__12 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private Exception <ex>5__1;

			private Exception <ex>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DeferredCleanupCoroutine>d__12(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<ex>5__1 = null;
				<ex>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0083: Unknown result type (might be due to invalid IL or missing references)
				//IL_008d: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					try
					{
						CleanupLeakedObjects("deferred-1frame");
					}
					catch (Exception ex)
					{
						<ex>5__1 = ex;
						Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 1 failed: " + <ex>5__1.Message);
					}
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					try
					{
						CleanupLeakedObjects("deferred-1sec");
					}
					catch (Exception ex)
					{
						<ex>5__2 = ex;
						Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 2 failed: " + <ex>5__2.Message);
					}
					_deferredCleanupCoroutine = null;
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const string CASINO_SCENE_NAME = "AtlyssCasino";

		private const float DEFERRED_CLEANUP_DELAY_SEC = 1f;

		private static readonly string[] LEAK_NAME_PREFIXES = new string[3] { "BJCard_", "SlotMachine_", "DebugCard_" };

		private static readonly string[] LEAK_NAME_EXACT = new string[1] { "Pokies Machine" };

		private static readonly string[] LEAK_NAME_STARTSWITH = new string[2] { "Blackjack Table", "Roulette Table" };

		private static bool _wasInCasino = false;

		private static Coroutine? _deferredCleanupCoroutine;

		public static void Init()
		{
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.sceneUnloaded += OnSceneUnloaded;
			SceneManager.activeSceneChanged += OnActiveSceneChanged;
			Plugin.Log.LogInfo("[SceneWatcher] Initialized.");
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (((Scene)(ref scene)).name == "AtlyssCasino")
			{
				_wasInCasino = true;
				Plugin.Log.LogInfo("[SceneWatcher] Entered casino scene.");
			}
			else if (_wasInCasino)
			{
				Plugin.Log.LogInfo("[SceneWatcher] Non-casino scene '" + ((Scene)(ref scene)).name + "' loaded after casino — scheduling cleanup sweeps.");
				ScheduleDeferredCleanup();
				_wasInCasino = false;
			}
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			if (((Scene)(ref scene)).name == "AtlyssCasino" && _wasInCasino)
			{
				Plugin.Log.LogInfo("[SceneWatcher] Casino scene unloaded — running immediate cleanup.");
				CleanupOnLeave();
				ScheduleDeferredCleanup();
				_wasInCasino = false;
			}
		}

		private static void OnActiveSceneChanged(Scene oldScene, Scene newScene)
		{
			if (_wasInCasino && ((Scene)(ref oldScene)).name == "AtlyssCasino" && ((Scene)(ref newScene)).name != "AtlyssCasino")
			{
				Plugin.Log.LogInfo("[SceneWatcher] Active scene changed away from casino (" + ((Scene)(ref oldScene)).name + " -> " + ((Scene)(ref newScene)).name + ") — running cleanup.");
				CleanupOnLeave();
				ScheduleDeferredCleanup();
				_wasInCasino = false;
			}
		}

		private static void ScheduleDeferredCleanup()
		{
			if (_deferredCleanupCoroutine != null)
			{
				return;
			}
			try
			{
				_deferredCleanupCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(DeferredCleanupCoroutine());
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[SceneWatcher] Failed to start deferred cleanup: " + ex.Message);
			}
		}

		[IteratorStateMachine(typeof(<DeferredCleanupCoroutine>d__12))]
		private static IEnumerator DeferredCleanupCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DeferredCleanupCoroutine>d__12(0);
		}

		private static void CleanupOnLeave()
		{
			try
			{
				ReleaseAnySeatedTable();
				ReleaseAnyRouletteTable();
				CleanupLeakedObjects("immediate");
				Plugin.HasSetBlackjackBet = false;
				Plugin.HasSetRouletteBet = false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[SceneWatcher] Cleanup failed: " + ex.Message);
			}
		}

		private static void ReleaseAnySeatedTable()
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				Plugin.Log.LogInfo("[SceneWatcher] No local player — skipping seat release.");
				return;
			}
			BlackjackTable[] array = Object.FindObjectsOfType<BlackjackTable>();
			BlackjackTable[] array2 = array;
			foreach (BlackjackTable blackjackTable in array2)
			{
				int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer);
				if (seatForPlayer >= 0)
				{
					string name = ((Object)blackjackTable).name;
					if (BJNetcode.AmHost())
					{
						blackjackTable.ReleaseSeat(seatForPlayer);
						int hostSeatIndex = blackjackTable.HostSeatIndex;
						BJNetcode.BroadcastSeatReleased(name, seatForPlayer, hostSeatIndex);
						Plugin.Log.LogInfo($"[SceneWatcher] Auto-released host seat {seatForPlayer} at '{name}'.");
					}
					else
					{
						BJNetcode.SendReleaseSeatRequest(name);
						blackjackTable.ApplySeatReleased(seatForPlayer, blackjackTable.HostSeatIndex);
						Plugin.Log.LogInfo($"[SceneWatcher] Auto-released client seat {seatForPlayer} at '{name}' " + "(request sent + local mirror).");
					}
				}
			}
		}

		private static void ReleaseAnyRouletteTable()
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return;
			}
			RouletteTable[] array = Object.FindObjectsOfType<RouletteTable>();
			ulong localSteam = BJNetcode.GetLocalSteam64();
			RouletteTable[] array2 = array;
			foreach (RouletteTable rouletteTable in array2)
			{
				if (rouletteTable.IsPlayerAtTable(mainPlayer))
				{
					string name = ((Object)rouletteTable).name;
					if (BJNetcode.AmHost())
					{
						rouletteTable.Leave(mainPlayer);
						ulong newHostSteam = ComputeNewRouletteHostAfterLeave(rouletteTable, localSteam);
						RNNetcode.BroadcastPlayerLeft(name, localSteam, newHostSteam, rouletteTable.PlayerCount);
						Plugin.Log.LogInfo("[SceneWatcher] Auto-released host roulette seat at '" + name + "'.");
					}
					else
					{
						RNNetcode.SendLeaveTableRequest(name);
						rouletteTable.ApplyPlayerLeftBySteam64(localSteam, 0uL);
						Plugin.Log.LogInfo("[SceneWatcher] Auto-released client roulette seat at '" + name + "' (request sent + local mirror).");
					}
				}
			}
		}

		private static ulong ComputeNewRouletteHostAfterLeave(RouletteTable table, ulong leftSteam64)
		{
			if (table.PlayerCount == 0)
			{
				return 0uL;
			}
			return 0uL;
		}

		private static void CleanupLeakedObjects(string passLabel)
		{
			//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_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Expected O, but got Unknown
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name == "AtlyssCasino")
			{
				Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup skipped: active scene is back in the casino.");
				return;
			}
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			CardVisual[] array = Object.FindObjectsOfType<CardVisual>();
			CardVisual[] array2 = array;
			foreach (CardVisual cardVisual in array2)
			{
				if (!((Object)(object)cardVisual == (Object)null) && !((Object)(object)((Component)cardVisual).gameObject == (Object)null) && !IsInCasinoScene(((Component)cardVisual).gameObject))
				{
					hashSet.Add(((Component)cardVisual).gameObject);
				}
			}
			for (int j = 0; j < SceneManager.sceneCount; j++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(j);
				if (((Scene)(ref sceneAt)).IsValid() && ((Scene)(ref sceneAt)).isLoaded && !(((Scene)(ref sceneAt)).name == "AtlyssCasino"))
				{
					GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects();
					foreach (GameObject val in rootGameObjects)
					{
						CollectLeakedDescendants(val.transform, hashSet);
					}
				}
			}
			try
			{
				GameObject val2 = new GameObject("__casino_ddol_probe");
				Object.DontDestroyOnLoad((Object)(object)val2);
				Scene scene = val2.scene;
				if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded)
				{
					GameObject[] rootGameObjects2 = ((Scene)(ref scene)).GetRootGameObjects();
					foreach (GameObject val3 in rootGameObjects2)
					{
						if (!((Object)(object)val3 == (Object)(object)val2))
						{
							CollectLeakedDescendants(val3.transform, hashSet);
						}
					}
				}
				Object.Destroy((Object)(object)val2);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[SceneWatcher] DDOL scan failed: " + ex.Message);
			}
			int num = 0;
			foreach (GameObject item in hashSet)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				try
				{
					string name = ((Object)item).name;
					Scene scene2 = item.scene;
					object obj;
					if (!((Scene)(ref scene2)).IsValid())
					{
						obj = "<no scene>";
					}
					else
					{
						scene2 = item.scene;
						obj = ((Scene)(ref scene2)).name;
					}
					string text = (string)obj;
					Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " destroying leaked '" + name + "' (scene: " + text + ").");
					Object.Destroy((Object)(object)item);
					num++;
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning("[SceneWatcher] Failed to destroy '" + ((Object)item).name + "': " + ex2.Message);
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup destroyed " + $"{num} leaked casino object(s).");
			}
			else
			{
				Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup found nothing leaked.");
			}
		}

		private static void CollectLeakedDescendants(Transform t, HashSet<GameObject> acc)
		{
			if ((Object)(object)t == (Object)null)
			{
				return;
			}
			string name = ((Object)t).name;
			if (NameMatchesLeakPattern(name))
			{
				acc.Add(((Component)t).gameObject);
				return;
			}
			for (int i = 0; i < t.childCount; i++)
			{
				CollectLeakedDescendants(t.GetChild(i), acc);
			}
		}

		private static bool NameMatchesLeakPattern(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			string[] lEAK_NAME_PREFIXES = LEAK_NAME_PREFIXES;
			foreach (string value in lEAK_NAME_PREFIXES)
			{
				if (name.StartsWith(value))
				{
					return true;
				}
			}
			string[] lEAK_NAME_EXACT = LEAK_NAME_EXACT;
			foreach (string text in lEAK_NAME_EXACT)
			{
				if (name == text)
				{
					return true;
				}
			}
			string[] lEAK_NAME_STARTSWITH = LEAK_NAME_STARTSWITH;
			foreach (string value2 in lEAK_NAME_STARTSWITH)
			{
				if (name.StartsWith(value2))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsInCasinoScene(GameObject go)
		{
			//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_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)
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			Scene scene = go.scene;
			int result;
			if (((Scene)(ref scene)).IsValid())
			{
				scene = go.scene;
				result = ((((Scene)(ref scene)).name == "AtlyssCasino") ? 1 : 0);
			}
			else
			{
				result = 0;
			}
			return (byte)result != 0;
		}
	}
	public sealed class CasinoJukebox : MonoBehaviour
	{
		private const float FALLBACK_RADIUS = 3f;

		private const float INPUT_COOLDOWN = 0.5f;

		private const float RESTART_DEBOUNCE_SEC = 1.25f;

		private const float RESYNC_TIME_THRESHOLD_SEC = 1.5f;

		private AudioSource? _source;

		private BoxCollider? _collider;

		private AudioClip? _appliedClip;

		private bool _setup;

		private bool _subscribed;

		private bool _playerNearby;

		private bool _isPlaying;

		private bool _appliedPlaying;

		private bool _autoStartRequested;

		private int _playlistStep;

		private int _appliedPlaylistStep = int.MinValue;

		private float _trackStartedAt;

		private float _nextInputTime;

		private float _nextRestartAttemptTime;

		private string _objectName = "CasinoJukebox";

		internal string ObjectName => _objectName;

		internal int PlaylistStep => _playlistStep;

		internal bool IsPlaying => _isPlaying;

		internal bool IsLocallyAudible => IsWorldAudioAllowedLocally() && _isPlaying && (Object)(object)_source != (Object)null && _source.isPlaying;

		internal float ElapsedSeconds => _isPlaying ? Mathf.Max(0f, Time.time - _trackStartedAt) : 0f;

		public void Setup()
		{
			_objectName = ((Object)((Component)this).gameObject).name;
			_source = ((Component)this).GetComponentInChildren<AudioSource>(true) ?? ((Component)this).gameObject.AddComponent<AudioSource>();
			CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true);
			_source.volume = CasinoConfig.EffectiveJukeboxVolume;
			_collider = ((Component)this).GetComponent<BoxCollider>() ?? ((Component)this).GetComponentInChildren<BoxCollider>(true);
			if (!_subscribed)
			{
				CasinoJukeboxManager.OnPlaylistChanged += OnPlaylistChanged;
				_subscribed = true;
			}
			_setup = true;
			if (JukeboxNetcode.TryGetCachedState(_objectName, out var state))
			{
				ApplyRemoteState(state.PlaylistStep, state.Playing, state.ElapsedSeconds);
			}
			else if (!Plugin.IsHeadlessServer && !BJNetcode.AmHost())
			{
				JukeboxNetcode.SendStateRequest(_objectName);
			}
		}

		internal static void AutoStartAll()
		{
			CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>();
			CasinoJukebox[] array2 = array;
			foreach (CasinoJukebox casinoJukebox in array2)
			{
				casinoJukebox.RequestAutoStart();
			}
		}

		internal static CasinoJukebox? FindByName(string objectName)
		{
			CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>();
			CasinoJukebox[] array2 = array;
			foreach (CasinoJukebox casinoJukebox in array2)
			{
				if (casinoJukebox.ObjectName == objectName)
				{
					return casinoJukebox;
				}
			}
			return null;
		}

		internal static bool IsAnyWorldJukeboxPlaying()
		{
			CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>();
			CasinoJukebox[] array2 = array;
			foreach (CasinoJukebox casinoJukebox in array2)
			{
				if (casinoJukebox.IsLocallyAudible)
				{
					return true;
				}
			}
			return false;
		}

		internal JukeboxWorldState BuildState()
		{
			JukeboxWorldState result = default(JukeboxWorldState);
			result.ObjectName = _objectName;
			result.PlaylistStep = _playlistStep;
			result.Playing = _isPlaying;
			result.ElapsedSeconds = ElapsedSeconds;
			return result;
		}

		internal void HostAdvance(int delta)
		{
			if ((!BJNetcode.AmHost() && !Plugin.IsHeadlessServer) || !Plugin.JukeboxEnabled)
			{
				return;
			}
			if (!CasinoJukeboxManager.HasTracks)
			{
				Plugin.Log.LogWarning("[Jukebox] Cannot advance: no songs are loaded.");
				return;
			}
			int i = _playlistStep + delta;
			if (i < 0)
			{
				for (int num = Math.Max(1, CasinoJukeboxManager.TrackCount); i < 0; i += num)
				{
				}
			}
			ApplyState(i, playing: true, 0f);
			JukeboxNetcode.BroadcastState(BuildState());
		}

		internal void ApplyRemoteState(int playlistStep, bool playing, float elapsedSeconds)
		{
			ApplyState(playlistStep, playing, elapsedSeconds);
		}

		private void RequestAutoStart()
		{
			_autoStartRequested = true;
			TryAutoStart();
		}

		private void TryAutoStart()
		{
			if (_setup && _autoStartRequested && !_isPlaying && Plugin.JukeboxEnabled && CasinoJukeboxManager.HasTracks && (BJNetcode.AmHost() || Plugin.IsHeadlessServer))
			{
				ApplyState(_playlistStep, playing: true, 0f);
				JukeboxNetcode.BroadcastState(BuildState());
			}
		}

		private void ApplyState(int playlistStep, bool playing, float elapsedSeconds)
		{
			_playlistStep = playlistStep;
			_isPlaying = playing;
			if ((Object)(object)_source == (Object)null)
			{
				_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			}
			CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true);
			_source.volume = CasinoConfig.EffectiveJukeboxVolume;
			if (!playing || !Plugin.JukeboxEnabled)
			{
				_appliedClip = null;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = false;
				StopLocalWorldAudio();
				return;
			}
			AudioClip clip = CasinoJukeboxManager.GetClip(playlistStep);
			if ((Object)(object)clip == (Object)null)
			{
				StopLocalWorldAudio();
				Plugin.Log.LogWarning($"[Jukebox] No clip available for playlist step {playlistStep}.");
				return;
			}
			elapsedSeconds = Mathf.Max(0f, elapsedSeconds);
			if (clip.length > 0.1f)
			{
				elapsedSeconds = Mathf.Min(elapsedSeconds, clip.length - 0.05f);
			}
			bool flag = IsWorldAudioAllowedLocally();
			bool flag2 = (Object)(object)_appliedClip != (Object)(object)clip || _appliedPlaylistStep != playlistStep || _appliedPlaying != playing;
			bool flag3 = (Object)(object)_source.clip == (Object)(object)clip && _source.isPlaying && flag;
			if (!flag2 && flag3)
			{
				float num = 0f;
				try
				{
					num = _source.time;
				}
				catch
				{
				}
				if (Mathf.Abs(num - elapsedSeconds) > 1.5f)
				{
					try
					{
						_source.time = elapsedSeconds;
					}
					catch
					{
					}
					_trackStartedAt = Time.time - elapsedSeconds;
				}
			}
			else
			{
				if (!flag2 && flag && !_source.isPlaying && Time.time < _nextRestartAttemptTime)
				{
					return;
				}
				_appliedClip = clip;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = playing;
				if ((Object)(object)_source.clip != (Object)(object)clip)
				{
					_source.clip = clip;
				}
				_trackStartedAt = Time.time - elapsedSeconds;
				if (!flag)
				{
					StopLocalWorldAudio();
					return;
				}
				CasinoJukeboxPersonalPlayer.StopForWorldJukebox();
				_nextRestartAttemptTime = Time.time + 1.25f;
				_source.Stop();
				if (elapsedSeconds > 0f)
				{
					try
					{
						_source.time = elapsedSeconds;
					}
					catch
					{
					}
				}
				_source.Play();
				CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source);
				Plugin.Log.LogInfo("[Jukebox] Playing " + CasinoJukeboxManager.GetTrackName(playlistStep) + " " + $"on '{_objectName}' (step {playlistStep}, " + $"volume={CasinoConfig.EffectiveJukeboxVolume:0.000}).");
			}
		}

		private void Update()
		{
			if (!_setup)
			{
				return;
			}
			if ((Object)(object)_source != (Object)null)
			{
				_source.volume = CasinoConfig.EffectiveJukeboxVolume;
			}
			if (!Plugin.JukeboxEnabled)
			{
				StopLocalWorldAudio();
				return;
			}
			bool flag = IsWorldAudioAllowedLocally();
			if ((Object)(object)_source != (Object)null && _isPlaying && _source.isPlaying && flag)
			{
				CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source);
			}
			if ((BJNetcode.AmHost() || Plugin.IsHeadlessServer) && _isPlaying && HasCurrentTrackEnded())
			{
				HostAdvance(1);
			}
			if (!flag)
			{
				StopLocalWorldAudio();
				_playerNearby = false;
				return;
			}
			if (_isPlaying && (Object)(object)_source != (Object)null && !_source.isPlaying && (Object)(object)CasinoJukeboxManager.GetClip(_playlistStep) != (Object)null)
			{
				if (Time.time < _nextRestartAttemptTime)
				{
					UpdateLocalInteraction();
					return;
				}
				ApplyState(_playlistStep, playing: true, ElapsedSeconds);
			}
			UpdateLocalInteraction();
		}

		private bool IsWorldAudioAllowedLocally()
		{
			if (Plugin.IsHeadlessServer)
			{
				return false;
			}
			return Plugin.IsLocalPlayerInCasino();
		}

		private void StopLocalWorldAudio()
		{
			if ((Object)(object)_source != (Object)null && _source.isPlaying)
			{
				_source.Stop();
			}
			CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source);
		}

		private bool HasCurrentTrackEnded()
		{
			if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null)
			{
				return false;
			}
			float length = _source.clip.length;
			if (length <= 0.1f)
			{
				return false;
			}
			return Time.time - _trackStartedAt >= length - 0.05f;
		}

		private void UpdateLocalInteraction()
		{
			if (Plugin.IsHeadlessServer)
			{
				return;
			}
			if (!Plugin.IsLocalPlayerInCasino())
			{
				_playerNearby = false;
				return;
			}
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return;
			}
			bool flag = IsPlayerInRange(mainPlayer);
			if (flag && !_playerNearby)
			{
				_playerNearby = true;
				ShowPrompt();
			}
			else if (!flag && _playerNearby)
			{
				_playerNearby = false;
			}
			if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime))
			{
				_nextInputTime = Time.time + 0.5f;
				if (BJNetcode.AmHost())
				{
					HostAdvance(1);
					return;
				}
				JukeboxNetcode.SendAdvanceRequest(_objectName, 1);
				Plugin.ShowHUDInfo("Jukebox skip requested...");
			}
		}

		private bool IsPlayerInRange(Player player)
		{
			//IL_0038: 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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_collider != (Object)null)
			{
				Bounds bounds = ((Collider)_collider).bounds;
				return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position);
			}
			return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 3f;
		}

		private void ShowPrompt()
		{
			if (!CasinoJukeboxManager.HasTracks)
			{
				Plugin.ShowHUDError("Jukebox has no songs loaded.");
				return;
			}
			string text = (_isPlaying ? CasinoJukeboxManager.GetTrackName(_playlistStep) : "ready");
			Plugin.ShowHUDInfo("Jukebox: " + text + ". Press " + CasinoInput.InteractPrompt + " for next song.");
		}

		private void OnPlaylistChanged()
		{
			if (_isPlaying)
			{
				ApplyState(_playlistStep, playing: true, ElapsedSeconds);
			}
			TryAutoStart();
		}

		private void OnDestroy()
		{
			StopLocalWorldAudio();
			if (_subscribed)
			{
				CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged;
				_subscribed = false;
			}
		}
	}
	internal static class CasinoJukeboxAudioGuard
	{
		private const float SCAN_INTERVAL = 0.5f;

		private static readonly List<AudioSource> _owners = new List<AudioSource>();

		private static readonly List<AudioSource> _pausedSources = new List<AudioSource>();

		private static float _nextScanTime;

		internal static void RegisterJukeboxSource(AudioSource? source)
		{
			if (!((Object)(object)source == (Object)null))
			{
				RemoveDeadOwners();
				if (!Contains(_owners, source))
				{
					_owners.Add(source);
				}
				Maintain();
			}
		}

		internal static void UnregisterJukeboxSource(AudioSource? source)
		{
			if ((Object)(object)source == (Object)null)
			{
				return;
			}
			for (int num = _owners.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)_owners[num] == (Object)null || _owners[num] == source)
				{
					_owners.RemoveAt(num);
				}
			}
			if (_owners.Count == 0)
			{
				RestorePausedSources();
			}
		}

		internal static void Maintain()
		{
			RemoveDeadOwners();
			if (_owners.Count == 0)
			{
				RestorePausedSources();
			}
			else
			{
				if (Time.time < _nextScanTime)
				{
					return;
				}
				_nextScanTime = Time.time + 0.5f;
				AudioSource[] array = Object.FindObjectsOfType<AudioSource>();
				AudioSource[] array2 = array;
				foreach (AudioSource val in array2)
				{
					if (ShouldPauseVanillaSource(val))
					{
						try
						{
							val.Pause();
							_pausedSources.Add(val);
						}
						catch (Exception ex)
						{
							Plugin.Log.LogWarning("[Jukebox] Failed to pause vanilla music source '" + ((Object)val).name + "': " + ex.Message);
						}
					}
				}
			}
		}

		private static bool ShouldPauseVanillaSource(AudioSource source)
		{
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			if (!source.isPlaying)
			{
				return false;
			}
			if ((Object)(object)source.clip == (Object)null)
			{
				return false;
			}
			if (Contains(_owners, source))
			{
				return false;
			}
			if (Contains(_pausedSources, source))
			{
				return false;
			}
			if (IsAtlyssJukeboxSource(source))
			{
				return false;
			}
			if (!source.loop)
			{
				return false;
			}
			return LooksLikeVanillaMusicSource(source);
		}

		private static bool LooksLikeVanillaMusicSource(AudioSource source)
		{
			if (HasAudioComponentName(source, "AudioManager") || HasAudioComponentName(source, "Sound_MapAmbience"))
			{
				return true;
			}
			string mixerText = GetMixerText(source);
			if (ContainsAudioWord(mixerText, "music") || ContainsAudioWord(mixerText, "ambience") || ContainsAudioWord(mixerText, "ambient") || ContainsAudioWord(mixerText, "bgm"))
			{
				return true;
			}
			string value = ((Object)source).name + " " + ((Object)((Component)source).gameObject).name + " " + ((Object)source.clip).name;
			return ContainsAudioWord(value, "music") || ContainsAudioWord(value, "ambience") || ContainsAudioWord(value, "ambient") || ContainsAudioWord(value, "bgm");
		}

		private static bool HasAudioComponentName(AudioSource source, string componentName)
		{
			Transform val = ((Component)source).transform;
			while ((Object)(object)val != (Object)null)
			{
				Component[] components = ((Component)val).GetComponents<Component>();
				Component[] array = components;
				foreach (Component val2 in array)
				{
					if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2).GetType().Name, componentName, StringComparison.Ordinal))
					{
						return true;
					}
				}
				val = val.parent;
			}
			return false;
		}

		private static string GetMixerText(AudioSource source)
		{
			try
			{
				if ((Object)(object)source.outputAudioMixerGroup == (Object)null)
				{
					return string.Empty;
				}
				string text = (((Object)(object)source.outputAudioMixerGroup.audioMixer == (Object)null) ? string.Empty : ((Object)source.outputAudioMixerGroup.audioMixer).name);
				return ((Object)source.outputAudioMixerGroup).name + " " + text;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static bool IsAtlyssJukeboxSource(AudioSource source)
		{
			if ((Object)(object)((Component)source).GetComponentInParent<CasinoJukebox>() != (Object)null)
			{
				return true;
			}
			Transform val = ((Component)source).transform;
			while ((Object)(object)val != (Object)null)
			{
				if (((Object)val).name.StartsWith("AtlyssCasino_PersonalJukebox", StringComparison.Ordinal))
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private static bool ContainsAudioWord(string value, string word)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			return value.IndexOf(word, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static bool Contains(List<AudioSource> sources, AudioSource source)
		{
			foreach (AudioSource source2 in sources)
			{
				if (source2 == source)
				{
					return true;
				}
			}
			return false;
		}

		private static void RemoveDeadOwners()
		{
			for (int num = _owners.Count - 1; num >= 0; num--)
			{
				AudioSource val = _owners[num];
				if ((Object)(object)val == (Object)null)
				{
					_owners.RemoveAt(num);
				}
				else if (!val.isPlaying)
				{
					_owners.RemoveAt(num);
				}
			}
		}

		private static void RestorePausedSources()
		{
			for (int num = _pausedSources.Count - 1; num >= 0; num--)
			{
				AudioSource val = _pausedSources[num];
				if (!((Object)(object)val == (Object)null))
				{
					try
					{
						val.UnPause();
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning("[Jukebox] Failed to restore vanilla music source '" + ((Object)val).name + "': " + ex.Message);
					}
				}
			}
			_pausedSources.Clear();
		}
	}
	internal static class CasinoJukeboxManager
	{
		private sealed class JukeboxTrack
		{
			internal readonly string Name;

			internal readonly AudioClip Clip;

			internal readonly bool IsLocal;

			internal readonly int SortIndex;

			internal JukeboxTrack(string name, AudioClip clip, bool isLocal, int sortIndex)
			{
				Name = name;
				Clip = clip;
				IsLocal = isLocal;
				SortIndex = sortIndex;
			}
		}

		[CompilerGenerated]
		private sealed class <LoadLocalSong>d__29 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string filePath;

			public int sortIndex;

			private AudioType <audioType>5__1;

			private string <uri>5__2;

			private UnityWebRequest <loader>5__3;

			private DownloadHandlerAudioClip <handler>5__4;

			private AudioClip <clip>5__5;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadLocalSong>d__29(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<uri>5__2 = null;
				<loader>5__3 = null;
				<handler>5__4 = null;
				<clip>5__5 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0093: 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_002f: 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_003b: Invalid comparison between Unknown and I4
				//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
				//IL_0103: Invalid comparison between Unknown and I4
				//IL_015f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0165: Invalid comparison between Unknown and I4
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					<audioType>5__1 = GetAudioType(filePath);
					if ((int)<audioType>5__1 == 0)
					{
						Plugin.Log.LogWarning("[Jukebox] Unsupported local song extension: " + filePath);
						return false;
					}
					try
					{
						<uri>5__2 = new Uri(filePath).AbsoluteUri;
					}
					catch
					{
						<uri>5__2 = filePath;
					}
					<loader>5__3 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__2, <audioType>5__1);
					ref DownloadHandlerAudioClip reference = ref <handler>5__4;
					DownloadHandler downloadHandler = <loader>5__3.downloadHandler;
					reference = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
					if (<handler>5__4 != null)
					{
						<handler>5__4.streamAudio = CasinoConfig.StreamLocalJukeboxSongsFromDisk;
					}
					<>2__current = <loader>5__3.SendWebRequest();
					<>1__state = 1;
					return true;
				}
				case 1:
					<>1__state = -1;
					if ((int)<loader>5__3.result != 1)
					{
						Plugin.Log.LogWarning("[Jukebox] Failed to load local song '" + filePath + "': " + <loader>5__3.error);
						return false;
					}
					<clip>5__5 = DownloadHandlerAudioClip.GetContent(<loader>5__3);
					if ((Object)(object)<clip>5__5 == (Object)null || (int)<clip>5__5.loadState != 2)
					{
						Plugin.Log.LogWarning("[Jukebox] Local song did not produce a loaded AudioClip: " + filePath);
						return false;
					}
					((Object)<clip>5__5).name = Path.GetFileNameWithoutExtension(filePath);
					_localTracks.Add(new JukeboxTrack(((Object)<clip>5__5).name, <clip>5__5, isLocal: true, sortIndex));
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <LoadLocalSongs>d__28 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private string[] <paths>5__1;

			private Exception <ex>5__2;

			private int <i>5__3;

			private string <path>5__4;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadLocalSongs>d__28(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<paths>5__1 = null;
				<ex>5__2 = null;
				<path>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					<path>5__4 = null;
					goto IL_0109;
				}
				<>1__state = -1;
				_loadingLocalSongs = true;
				_localTracks.Clear();
				try
				{
					<paths>5__1 = Directory.GetFiles(LocalSongsDirectory);
				}
				catch (Exception ex)
				{
					<ex>5__2 = ex;
					Plugin.Log.LogError("[Jukebox] Failed to list local songs: " + <ex>5__2.Message);
					_loadingLocalSongs = false;
					return false;
				}
				Array.Sort(<paths>5__1, (string a, string b) => string.Compare(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase));
				<i>5__3 = 0;
				goto IL_011b;
				IL_011b:
				if (<i>5__3 < <paths>5__1.Length)
				{
					<path>5__4 = <paths>5__1[<i>5__3];
					if (!IsSupportedAudioFile(<path>5__4))
					{
						goto IL_0109;
					}
					<>2__current = LoadLocalSong(<path>5__4, <i>5__3);
					<>1__state = 1;
					return true;
				}
				_loadingLocalSongs = false;
				RebuildPlaylist();
				Plugin.Log.LogInfo($"[Jukebox] Loaded {_localTracks.Count} local song(s). Total playlist: {_playlist.Count}.");
				CasinoJukeboxManager.OnPlaylistChanged?.Invoke();
				return false;
				IL_0109:
				<i>5__3++;
				goto IL_011b;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const string LOCAL_FOLDER_NAME = "ATLYSS Jukebox";

		private const string LOCAL_PARENT_FOLDER = "Custom Songs";

		private static readonly List<JukeboxTrack> _bundledTracks = new List<JukeboxTrack>();

		private static readonly List<JukeboxTrack> _localTracks = new List<JukeboxTrack>();

		private static readonly List<JukeboxTrack> _playlist = new List<JukeboxTrack>();

		private static bool _initialized;

		private static bool _loadingLocalSongs;

		internal static string LocalSongsDirectory => Path.Combine(Paths.BepInExRootPath, "Custom Songs", "ATLYSS Jukebox");

		internal static int TrackCount => _playlist.Count;

		internal static bool HasTracks => _playlist.Count > 0;

		internal static bool IsLoadingLocalSongs => _loadingLocalSongs;

		internal static event Action? OnPlaylistChanged;

		internal static void Init()
		{
			if (_initialized)
			{
				return;
			}
			_initialized = true;
			try
			{
				Directory.CreateDirectory(LocalSongsDirectory);
				Plugin.Log.LogInfo("[Jukebox] Local songs folder: " + LocalSongsDirectory);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[Jukebox] Failed to create local songs folder: " + ex.Message);
			}
			LoadBundledSongs();
			RebuildPlaylist();
			try
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(LoadLocalSongs());
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError("[Jukebox] Failed to start local song loader: " + ex2.Message);
			}
		}

		internal static AudioClip? GetClip(int playlistStep)
		{
			return GetTrack(playlistStep)?.Clip;
		}

		internal static string GetTrackName(int playlistStep)
		{
			JukeboxTrack track = GetTrack(playlistStep);
			return (track == null) ? "No songs loaded" : track.Name;
		}

		internal static void ConfigureAudioSource(AudioSource source, bool spatial)
		{
			if ((Object)(object)source == (Object)null)
			{
				return;
			}
			source.playOnAwake = false;
			source.loop = false;
			source.spatialBlend = (spatial ? 1f : 0f);
			source.dopplerLevel = 0f;
			if (spatial)
			{
				if (source.minDistance <= 0f)
				{
					source.minDistance = 2f;
				}
				if (source.maxDistance < 20f)
				{
					source.maxDistance = 65f;
				}
				source.rolloffMode = (AudioRolloffMode)1;
			}
			source.outputAudioMixerGroup = null;
		}

		private static JukeboxTrack? GetTrack(int playlistStep)
		{
			if (_playlist.Count == 0)
			{
				return null;
			}
			int num = playlistStep % _playlist.Count;
			if (num < 0)
			{
				num += _playlist.Count;
			}
			return _playlist[num];
		}

		private static void LoadBundledSongs()
		{
			_bundledTracks.Clear();
			AssetBundle assetsBundle = Plugin.AssetsBundle;
			if ((Object)(object)assetsBundle == (Object)null)
			{
				Plugin.Log.LogWarning("[Jukebox] Casino asset bundle is not loaded; bundled songs unavailable.");
				return;
			}
			string[] allAssetNames;
			try
			{
				allAssetNames = assetsBundle.GetAllAssetNames();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[Jukebox] Failed to enumerate asset bundle songs: " + ex.Message);
				return;
			}
			HashSet<int> loadedNumbers = new HashSet<int>();
			string[] array = allAssetNames;
			foreach (string assetName in array)
			{
				TryLoadBundledSongAsset(assetsBundle, assetName, requireJukeboxPath: true, loadedNumbers);
			}
			string[] array2 = allAssetNames;
			foreach (string assetName2 in array2)
			{
				TryLoadBundledSongAsset(assetsBundle, assetName2, requireJukeboxPath: false, loadedNumbers);
			}
			if (_bundledTracks.Count == 0)
			{
				TryLoadBundledSongsByClipName(assetsBundle, loadedNumbers);
			}
			_bundledTracks.Sort(delegate(JukeboxTrack a, JukeboxTrack b)
			{
				int num = a.SortIndex.CompareTo(b.SortIndex);
				return (num != 0) ? num : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
			});
			if (_bundledTracks.Count == 0)
			{
				Plugin.Log.LogWarning("[Jukebox] No bundled songs found. Expected AudioClips named Song1, Song2, etc. in atlysscasino_assets.");
				LogBundleSongDiagnostics(allAssetNames);
			}
			else
			{
				Plugin.Log.LogInfo($"[Jukebox] Loaded {_bundledTracks.Count} bundled song(s): {DescribeTracks(_bundledTracks)}.");
			}
		}

		private static bool TryLoadBundledSongAsset(AssetBundle bundle, string assetName, bool requireJukeboxPath, HashSet<int> loadedNumbers)
		{
			if (string.IsNullOrWhiteSpace(assetName))
			{
				return false;
			}
			string text = assetName.Replace('\\', '/').ToLowerInvariant();
			if (requireJukeboxPath && !text.Contains("/jukebox/jukeboxsongs/") && !text.Contains("jukeboxsongs/"))
			{
				return false;
			}
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(assetName);
			if (!TryParseSongNumber(fileNameWithoutExtension, out var number))
			{
				return false;
			}
			if (loadedNumbers.Contains(number))
			{
				return false;
			}
			AudioClip val = null;
			try
			{
				val = bundle.LoadAsset<AudioClip>(assetName);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[Jukebox] Failed loading bundled song '" + assetName + "': " + ex.Message);
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			AddBundledTrack(fileNameWithoutExtension, val, number, loadedNumbers);
			return true;
		}

		private static void TryLoadBundledSongsByClipName(AssetBundle bundle, HashSet<int> loadedNumbers)
		{
			AudioClip[] array;
			try
			{
				array = bundle.LoadAllAssets<AudioClip>();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[Jukebox] Failed to scan AudioClips in asset bundle: " + ex.Message);
				return;
			}
			AudioClip[] array2 = array;
			foreach (AudioClip val in array2)
			{
				if (!((Object)(object)val == (Object)null) && TryParseSongNumber(((Object)val).name, out var number) && !loadedNumbers.Contains(number))
				{
					AddBundledTrack(((Object)val).name, val, number, loadedNumbers);
				}
			}
		}

		private static void AddBundledTrack(string name, AudioClip clip, int songNumber, HashSet<int> loadedNumbers)
		{
			string name2 = (string.IsNullOrWhiteSpace(name) ? $"Song{songNumber}" : name);
			loadedNumbers.Add(songNumber);
			_bundledTracks.Add(new JukeboxTrack(name2, clip, isLocal: false, songNumber));
		}

		private static void LogBundleSongDiagnostics(string[] assetNames)
		{
			if (assetNames == null || assetNames.Length == 0)
			{
				Plugin.Log.LogWarning("[Jukebox] atlysscasino_assets reports 0 asset names.");
				return;
			}
			Plugin.Log.LogWarning($"[Jukebox] atlysscasino_assets reports {assetNames.Length} asset name(s). None loaded as Song<number> AudioClips.");
			List<string> list = new List<string>();
			foreach (string text in assetNames)
			{
				if (!string.IsNullOrWhiteSpace(text))
				{
					string text2 = text.ToLowerInvariant();
					if (text2.Contains("jukebox") || text2.Contains("song") || text2.EndsWith(".ogg") || text2.EndsWith(".mp3") || text2.EndsWith(".wav"))
					{
						list.Add(text);
					}
				}
			}
			if (list.Count > 0)
			{
				Plugin.Log.LogWarning("[Jukebox] Bundle assets mentioning jukebox/song/audio: " + DescribeNames(list));
				return;
			}
			int num = Math.Min(assetNames.Length, 20);
			List<string> list2 = new List<string>(num);
			for (int j = 0; j < num; j++)
			{
				list2.Add(assetNames[j]);
			}
			Plugin.Log.LogWarning("[Jukebox] First bundle asset names: " + DescribeNames(list2));
		}

		[IteratorStateMachine(typeof(<LoadLocalSongs>d__28))]
		private static IEnumerator LoadLocalSongs()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadLocalSongs>d__28(0);
		}

		[IteratorStateMachine(typeof(<LoadLocalSong>d__29))]
		private static IEnumerator LoadLocalSong(string filePath, int sortIndex)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadLocalSong>d__29(0)
			{
				filePath = filePath,
				sortIndex = sortIndex
			};
		}

		private static void RebuildPlaylist()
		{
			_playlist.Clear();
			_playlist.AddRange(_bundledTracks);
			_playlist.AddRange(_localTracks);
		}

		private static bool IsSupportedAudioFile(string path)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			return (int)GetAudioType(path) > 0;
		}

		private static AudioType GetAudioType(string path)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_004c: 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)
			return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch
			{
				".ogg" => 14, 
				".mp3" => 13, 
				".wav" => 20, 
				_ => 0, 
			});
		}

		private static bool TryParseSongNumber(string name, out int number)
		{
			number = 0;
			if (string.IsNullOrWhiteSpace(name))
			{
				return false;
			}
			Match match = Regex.Match(name.Trim(), "^song(\\d+)$", RegexOptions.IgnoreCase);
			if (!match.Success)
			{
				return false;
			}
			return int.TryParse(match.Groups[1].Value, out number);
		}

		private static string DescribeTracks(List<JukeboxTrack> tracks)
		{
			List<string> list = new List<string>();
			foreach (JukeboxTrack track in tracks)
			{
				list.Add(track.Name);
			}
			return DescribeNames(list);
		}

		private static string DescribeNames(IList<string> names)
		{
			if (names.Count == 0)
			{
				return "(none)";
			}
			int num = Math.Min(names.Count, 20);
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < num; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(names[i]);
			}
			if (names.Count > num)
			{
				stringBuilder.Append($", ... +{names.Count - num} more");
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class CasinoJukeboxPersonalPlayer : MonoBehaviour
	{
		private static CasinoJukeboxPersonalPlayer? _instance;

		private AudioSource? _source;

		private bool _playing;

		private int _playlistStep = -1;

		private float _trackStartedAt;

		internal static void Init()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			if (!Plugin.IsHeadlessServer && !((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("AtlyssCasino_PersonalJukebox");
				_instance = val.AddComponent<CasinoJukeboxPersonalPlayer>();
				Object.DontDestroyOnLoad((Object)(object)val);
				CasinoJukeboxManager.OnPlaylistChanged += _instance.OnPlaylistChanged;
			}
		}

		internal static void Play()
		{
			CasinoJukeboxPersonalPlayer orCreate = GetOrCreate();
			orCreate.PlayInternal();
		}

		internal static void Forward()
		{
			CasinoJukeboxPersonalPlayer orCreate = GetOrCreate();
			orCreate.AdvanceInternal(1, showMessage: true);
		}

		internal static void Previous()
		{
			CasinoJukeboxPersonalPlayer orCreate = GetOrCreate();
			orCreate.AdvanceInternal(-1, showMessage: true);
		}

		internal static void StopPlayback()
		{
			CasinoJukeboxPersonalPlayer orCreate = GetOrCreate();
			orCreate.StopInternal(showMessage: true);
		}

		internal static void StopForWorldJukebox()
		{
			if (!((Object)(object)_instance == (Object)null) && _instance._playing)
			{
				_instance.StopInternal(showMessage: false);
			}
		}

		internal static void ShowCommandMessage(string message, bool error = false)
		{
			string text = (error ? "#FF6666" : "#FFD700");
			string text2 = "<color=" + text + ">[Jukebox]</color> " + message;
			try
			{
				ChatBehaviour val = Object.FindObjectOfType<ChatBehaviour>();
				if ((Object)(object)val != (Object)null)
				{
					val.Init_GameLogicMessage(text2);
				}
			}
			catch
			{
			}
			try
			{
				ErrorPromptTextManager.current.Init_ErrorPrompt(Plugin.WrapColor("[Jukebox] " + StripColorTags(message), error ? "#FF3119" : "#FFDC96"));
			}
			catch
			{
			}
		}

		private static CasinoJukeboxPersonalPlayer GetOrCreate()
		{
			if ((Object)(object)_instance == (Object)null)
			{
				Init();
			}
			return _instance;
		}

		private void Awake()
		{
			_source = ((Component)this).gameObject.GetComponent<AudioSource>() ?? ((Component)this).gameObject.AddComponent<AudioSource>();
			CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false);
			_source.volume = CasinoConfig.EffectivePersonalJukeboxVolume;
		}

		private void Update()
		{
			if ((Object)(object)_source != (Object)null)
			{
				_source.volume = CasinoConfig.EffectivePersonalJukeboxVolume;
			}
			if (!CasinoConfig.JukeboxEnabled)
			{
				StopInternal(showMessage: false);
			}
			else if (_playing && !((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null))
			{
				CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source);
				float length = _source.clip.length;
				if (length > 0.1f && Time.time - _trackStartedAt >= length - 0.05f)
				{
					AdvanceInternal(1, showMessage: false);
				}
			}
		}

		private void PlayInternal()
		{
			if (!CasinoConfig.JukeboxEnabled)
			{
				ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true);
				return;
			}
			if (CasinoJukebox.IsAnyWorldJukeboxPlaying())
			{
				StopInternal(showMessage: false);
				ShowCommandMessage("Casino jukebox is already playing here.", error: true);
				return;
			}
			if (!CasinoJukeboxManager.HasTracks)
			{
				string text = (CasinoJukeboxManager.IsLoadingLocalSongs ? " Local songs are still loading." : "");
				ShowCommandMessage("No jukebox songs loaded." + text, error: true);
				return;
			}
			if (_playlistStep < 0)
			{
				_playlistStep = 0;
			}
			PlayStep(_playlistStep, showMessage: true);
		}

		private void AdvanceInternal(int delta, bool showMessage)
		{
			if (!CasinoConfig.JukeboxEnabled)
			{
				if (showMessage)
				{
					ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true);
				}
				return;
			}
			if (!CasinoJukeboxManager.HasTracks)
			{
				if (showMessage)
				{
					ShowCommandMessage("No jukebox songs loaded.", error: true);
				}
				return;
			}
			if (_playlistStep < 0)
			{
				_playlistStep = 0;
			}
			else
			{
				_playlistStep += delta;
			}
			if (_playlistStep < 0)
			{
				_playlistStep = CasinoJukeboxManager.TrackCount - 1;
			}
			PlayStep(_playlistStep, showMessage);
		}

		private void PlayStep(int playlistStep, bool showMessage)
		{
			if ((Object)(object)_source == (Object)null)
			{
				_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			}
			CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false);
			_source.volume = CasinoConfig.EffectivePersonalJukeboxVolume;
			AudioClip clip = CasinoJukeboxManager.GetClip(playlistStep);
			if ((Object)(object)clip == (Object)null)
			{
				StopInternal(showMessage: false);
				if (showMessage)
				{
					ShowCommandMessage("No clip found for that jukebox song.", error: true);
				}
				return;
			}
			_source.Stop();
			_source.clip = clip;
			_source.Play();
			CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source);
			_playing = true;
			_trackStartedAt = Time.time;
			if (showMessage)
			{
				ShowCommandMessage("Playing " + CasinoJukeboxManager.GetTrackName(playlistStep) + ".");
			}
		}

		private void StopInternal(bool showMessage)
		{
			if ((Object)(object)_source != (Object)null)
			{
				_source.Stop();
				CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source);
			}
			_playing = false;
			if (showMessage)
			{
				ShowCommandMessage("Stopped personal jukebox.");
			}
		}

		private void OnPlaylistChanged()
		{
			if (_playing)
			{
				PlayStep(_playlistStep, showMessage: false);
			}
		}

		private void OnDestroy()
		{
			CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source);
			CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged;
			if (_instance == this)
			{
				_instance = null;
			}
		}

		private static string StripColorTags(string message)
		{
			if (string.IsNullOrEmpty(message))
			{
				return string.Empty;
			}
			return message.Replace("<color=#FF6666>", "").Replace("<color=#FFD700>", "").Replace("<color=#FFFFFF>", "")
				.Replace("</color>", "");
		}
	}
	public sealed class RoomZone : MonoBehaviour
	{
		internal readonly HashSet<ulong> Members = new HashSet<ulong>();

		internal BoxCollider Collider { get; private set; } = null;


		internal string RoomName { get; private set; } = "RoomZone";


		internal string SceneName { get; private set; } = string.Empty;


		internal int SceneHandle { get; private set; }

		internal Component? MapInstance { get; private set; }

		internal float Volume
		{
			get
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: 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_0036: 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)
				//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)
				//IL_005b: 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)
				if ((Object)(object)Collider == (Object)null)
				{
					return float.MaxValue;
				}
				Vector3 size = Collider.size;
				Vector3 lossyScale = ((Component)Collider).transform.lossyScale;
				return Mathf.Abs(size.x * lossyScale.x) * Mathf.Abs(size.y * lossyScale.y) * Mathf.Abs(size.z * lossyScale.z);
			}
		}

		internal void Setup(BoxCollider box, string roomName, Scene scene, Component? mapInstance)
		{
			Collider = box;
			RoomName = (string.IsNullOrWhiteSpace(roomName) ? "RoomZone" : roomName.Trim());
			SceneName = ((Scene)(ref scene)).name ?? string.Empty;
			SceneHandle = ((Scene)(ref scene)).handle;
			MapInstance = mapInstance;
			((Collider)Collider).isTrigger = true;
			RoomZoneRegistry.Register(this);
		}

		internal bool Contains(Player player)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_0047: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)Collider == (Object)null)
			{
				return false;
			}
			Vector3 val = ((Component)Collider).transform.InverseTransformPoint(RoomZoneRegistry.GetPlayerZonePosition(player)) - Collider.center;
			Vector3 val2 = Collider.size * 0.5f;
			return Mathf.Abs(val.x) <= val2.x && Mathf.Abs(val.y) <= val2.y && Mathf.Abs(val.z) <= val2.z;
		}

		private void OnTriggerEnter(Collider other)
		{
			if (RoomZoneRegistry.IsRoutingHost && RoomZoneRegistry.TryGetPlayer(other, out Player player))
			{
				ulong steam = RoomZoneRegistry.GetSteam64(player);
				if (steam != 0)
				{
					Members.Add(steam);
				}
			}
		}

		private void OnTriggerExit(Collider other)
		{
			if (RoomZoneRegistry.IsRoutingHost && RoomZoneRegistry.TryGetPlayer(other, out Player player))
			{
				ulong steam = RoomZoneRegistry.GetSteam64(player);
				if (steam != 0)
				{
					Members.Remove(steam);
				}
			}
		}

		private void OnDestroy()
		{
			RoomZoneRegistry.Unregister(this);
		}
	}
	internal static class RoomZoneRegistry
	{
		private sealed class RoomZoneDriver : MonoBehaviour
		{
			private float _nextRefresh;

			private void Update()
			{
				if (!(Time.time < _nextRefresh))
				{
					_nextRefresh = Time.time + 0.5f;
					RefreshMembership();
					ReportLocalPresence();
				}
			}
		}

		[CompilerGenerated]
		private sealed class <DelayedScan>d__40 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Scene scene;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DelayedScan>d__40(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_0071: Unknown result type (might be due to invalid IL or missing references)
				//IL_007b: Expected O, but got Unknown
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					ScanScene(scene);
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 3;
					return true;
				case 3:
					<>1__state = -1;
					ScanScene(scene);
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const string PREFIX = "RoomZone";

		private const float REFRESH_INTERVAL_SEC = 0.5f;

		private const float PRESENCE_REPORT_INTERVAL_SEC = 2f;

		private static readonly List<RoomZone> _zones = new List<RoomZone>();

		private static readonly Dictionary<int, Component?> _mapInstancesByScene = new Dictionary<int, Component>();

		private static readonly Regex _cloneSuffixRegex = new Regex("\\s*\\(\\d+\\)$", RegexOptions.Compiled);

		private static bool _initialized;

		private static RoomZoneDriver? _driver;

		private static FieldInfo? _playerMapInstanceField;

		private static PropertyInfo? _playerMapInstanceProperty;

		private static FieldInfo? _playerMapNameField;

		private static PropertyInfo? _playerMapNameProperty;

		private static FieldInfo? _chatChannelField;

		private static MethodInfo? _vanillaReceiveChatMethod;

		private static bool _reflectionResolved;

		private static bool _loggedReflectionFailure;

		private static readonly Dictionary<ulong, string> _lastZoneBySteam64 = new Dictionary<ulong, string>();

		private static string _lastLocalPresenceKey = string.Empty;

		private static float _nextLocalPresenceReportTime;

		internal static bool IsRoutingHost
		{
			get
			{
				if (Plugin.IsHeadlessServer)
				{
					return true;
				}
				try
				{
					return BJNetcode.AmHost();
				}
				catch
				{
					return false;
				}
			}
		}

		internal static void Init()
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			if (!_initialized)
			{
				_initialized = true;
				SceneManager.sceneLoaded += OnSceneLoaded;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				GameObject val = new GameObject("AtlyssCasino_RoomZoneRegistry");
				Object.DontDestroyOnLoad((Object)(object)val);
				((Object)val).hideFlags = (HideFlags)61;
				_driver = val.AddComponent<RoomZoneDriver>();
				for (int i = 0; i < SceneManager.sceneCount; i++)
				{
					ScanScene(SceneManager.GetSceneAt(i));
				}
				Plugin.Log.LogInfo("[RoomZone] Registry initialized.");
			}
		}

		internal static void Register(RoomZone zone)
		{
			if (!((Object)(object)zone == (Object)null) && !_zones.Contains(zone))
			{
				_zones.Add(zone);
			}
		}

		internal static void Unregister(RoomZone zone)
		{
			if (!((Object)(object)zone == (Object)null))
			{
				_zones.Remove(zone);
			}
		}

		internal static void RefreshMembership()
		{
			if (!IsRoutingHost)
			{
				return;
			}
			for (int i = 0; i < _zones.Count; i++)
			{
				_zones[i].Members.Clear();
			}
			Player[] array = Object.FindObjectsOfType<Player>();
			Player[] array2 = array;
			foreach (Player val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ulong steam = GetSteam64(val);
				if (steam != 0)
				{
					RoomZone roomZone = FindContainingZone(val);
					if ((Object)(object)roomZone != (Object)null)
					{
						roomZone.Members.Add(steam);
					}
					LogMembershipTransition(val, steam, roomZone);
				}
			}
		}

		internal static bool TryRouteZoneChat(ChatBehaviour chat, string message, ChatChannel channel)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			if ((int)channel != 2)
			{
				return false;
			}
			if ((Object)(object)chat == (Object)null)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			if (IsSingleSlashCommand(message))
			{
				return false;
			}
			if (!IsRoutingHost)
			{
				return false;
			}
			Player player = GetPlayer(chat);
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			ulong steam = GetSteam64(player);
			if (steam != 0L && !RoomZoneChatNetcode.IsParticipantEnabled(steam))
			{
				return false;
			}
			if (!HasZonesInSameScene(player))
			{
				return false;
			}
			RefreshMembership();
			RoomZone geometryZone = FindContainingZone(player);
			string text = ResolveEffectiveRoomName(player, steam, geometryZone);
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string roomName = text;
			string formattedMessage = FormatRoomMessage(player, text, message);
			int num = 0;
			List<string> list = new List<string>();
			Player[] array = Object.FindObjectsOfType<Player>();
			Player[] array2 = array;
			foreach (Player val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (!IsSameSceneScope(player, val))
				{
					ulong steam2 = GetSteam64(val);
					if (steam2 != 0)
					{
						list.Add($"{steam2}:other-scope:skipped");
					}
					continue;
				}
				ulong steam3 = GetSteam64(val);
				if (steam3 == 0)
				{
					continue;
				}
				if (!RoomZoneChatNetcode.IsParticipantEnabled(steam3))
				{
					list.Add($"{steam3}:{DescribeZone(FindContainingZone(val))}:opted-out:skipped");
					continue;
				}
				RoomZone geometryZone2 = FindContainingZone(val);
				string text2 = ResolveEffectiveRoomName(val, steam3, geometryZone2);
				if (!RoomNamesEqual(text2, text))
				{
					list.Add($"{steam3}:{DescribeRoomName(text2)}:skipped");
					continue;
				}
				RoomZoneChatNetcode.SendRoomChatTo(steam3, formattedMessage, steam, message, roomName);
				num++;
				list.Add($"{steam3}:{DescribeRoomName(text2)}:sent");
			}
			Plugin.Log.LogDebug($"[RoomZone] Routed zone chat from steam64={steam} " + $"name='{GetDisplayName(player)}' to {num} recipient(s) " + "scope='" + GetScopeLabel(player) + "' room='" + text + "' targets=[" + string.Join(", ", list.ToArray()) + "].");
			return true;
		}

		internal static bool TryRouteLocalRoomZoneChat(ChatBehaviour chat, string message)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			if (!Input.GetKeyDown((KeyCode)13) && !Input.GetKeyDown((KeyCode)271))
			{
				return false;
			}
			if (IsSingleSlashCommand(message))
			{
				return false;
			}
			if (!CasinoConfig.RoomZoneChatEnabled)
			{
				return false;
			}
			if (!IsChatSetToZone(chat))
			{
				return false;
			}
			Player val = Player._mainPlayer ?? GetPlayer(chat);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (!HasZonesInSameScene(val))
			{
				return false;
			}
			RoomZone roomZone = FindContainingZone(val);
			if ((Object)(object)roomZone == (Object)null)
			{
				return false;
			}
			ulong num = GetSteam64(val);
			if (num == 0)
			{
				num = BJNetcode.GetLocalSteam64();
			}
			if (num == 0)
			{
				return false;
			}
			string scopeLabel = GetScopeLabel(val);
			RoomZoneChatNetcode.PublishLocalPresence(roomZone.RoomName, scopeLabel);
			RoomZoneChatNetcode.SendRoomChatRequest(num, message, roomZone.RoomName, scopeLabel);
			CloseLocalChatAfterCustomSend(chat);
			Plugin.Log.LogDebug("[RoomZone] Routed local room chat request " + $"steam64={num} room='{roomZone.RoomName}' scope='{scopeLabel}'.");
			return true;
		}

		internal static void BroadcastRoomChatRequest(ulong senderSteam64, string rawMessage, string roomName, string scope)
		{
			if (!IsRoutingHost || senderSteam64 == 0 || string.IsNullOrWhiteSpace(rawMessage) || string.IsNullOrWhiteSpace(roomName) || !RoomZoneChatNetcode.IsParticipantEnabled(senderSteam64))
			{
				return;
			}
			Player sender = BJNetcode.FindPlayerBySteam64(senderSteam64);
			string text = SanitizeRichText(roomName);
			string formattedMessage = FormatRoomMessage(sender, text, rawMessage);
			int num = 0;
			List<string> list = new List<string>();
			Player[] array = Object.FindObjectsOfType<Player>();
			Player[] array2 = array;
			foreach (Player val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ulong steam = GetSteam64(val);
				if (steam != 0)
				{
					if (!RoomZoneChatNetcode.IsParticipantEnabled(steam))
					{
						list.Add($"{steam}:opted-out:skipped");
						continue;
					}
					RoomZoneChatNetcode.SendRoomChatTo(steam, formattedMessage, senderSteam64, rawMessage, text, scope, requireLocalRoomMatch: true);
					num++;
					list.Add($"{steam}:sent-for-local-room-filter");
				}
			}
			Plugin.Log.LogDebug("[RoomZone] Broadcast room chat request " + $"sender={senderSteam64} room='{text}' scope='{scope}' " + $"to {num} candidate recipient(s) " + "targets=[" + string.Join(", ", list.ToArray()) + "].");
		}

		private static bool IsChatSetToZone(ChatBehaviour chat)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0054: Invalid comparison between Unknown and I4
			try
			{
				if ((object)_chatChannelField == null)
				{
					_chatChannelField = typeof(ChatBehaviour).GetField("_setChatChannel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				if (_chatChannelField == null)
				{
					return true;
				}
				return _chatChannelField.GetValue(chat) is ChatChannel val && (int)val == 2;
			}
			catch
			{
				return true;
			}
		}

		private static void CloseLocalChatAfterCustomSend(ChatBehaviour chat)
		{
			try
			{
				typeof(ChatBehaviour).GetField("_focusedInChat", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(chat, false);
				object obj = typeof(ChatBehaviour).GetField("_chatAssets", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat);
				if (obj != null)
				{
					object obj2 = obj.GetType().GetField("_chatInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj);
					if (obj2 != null)
					{
						PropertyInfo property = obj2.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						if (property != null && property.CanWrite)
						{
							property.SetValue(obj2, string.Empty, null);
						}
						obj2.GetType().GetMethod("DeactivateInputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(obj2, null);
					}
				}
				Cursor.lockState = (CursorLockMode)1;
				Cursor.visible = false;
			}
			catch
			{
			}
		}

		private static string ResolveEffectiveRoomName(Player player, ulong steam64, RoomZone? geometryZone)
		{
			if ((Object)(object)geometryZone != (Object)null)
			{
				return geometryZone.RoomName;
			}
			string reportedRoomName = RoomZoneChatNetcode.GetReportedRoomName(steam64);
			return string.IsNullOrWhiteSpace(reportedRoomName) ? string.Empty : SanitizeRichText(reportedRoomName);
		}

		private static bool RoomNamesEqual(string a, string b)
		{
			return string.Equals((a ?? string.Empty).Trim(), (b ?? string.Empty).Trim(), StringComparison.OrdinalIgnoreCase);
		}

		private static bool IsSingleSlashCommand(string message)
		{
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			string text = message.TrimStart();
			return text.StartsWith("/", StringComparison.Ordinal) && !text.StartsWith("//", StringComparison.Ordinal);
		}

		internal static bool TryGetPlayer(Collider collider, out Player player)
		{
			player = null;
			if ((Object)(object)collider == (Object)null)
			{
				return false;
			}
			player = ((Component)collider).GetComponentInParent<Player>();
			return (Object)(object)player != (Object)null;
		}

		internal static ulong GetSteam64(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return 0uL;
			}
			try
			{
				if (ulong.TryParse(player.Network_steamID, out var result))
				{
					return result;
				}
			}
			catch
			{
			}
			return 0uL;
		}

		internal static bool DisplayLocalRoomChat(string formattedMessage, bool respectLocalPreference = true, ulong senderSteam64 = 0uL, string rawMessage = "", string roomName = "", string scope = "", bool requireLocalRoomMatch = false)
		{
			if (respectLocalPreference && !CasinoConfig.RoomZoneChatEnabled)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(formattedMessage))
			{
				return false;
			}
			if (requireLocalRoomMatch && !IsLocalPlayerInRoom(roomName, scope))
			{
				Plugin.Log.LogDebug("[RoomZone] Skipped private room packet locally; required room='" + DescribeRoomName(roomName) + "' scope='" + scope + "'.");
				return false;
			}
			try
			{
				if (TryDisplayViaVanillaChat(senderSteam64, rawMessage, roomName))
				{
					Plugin.Log.LogDebug("[RoomZone] Displayed private chat via vanilla path " + $"sender={senderSteam64} room='{DescribeRoomName(roomName)}'.");
					return true;
				}
				ChatBehaviour val = Object.FindObjectOfType<ChatBehaviour>();
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				val.New_ChatMessage(formattedMessage);
				Plugin.Log.LogDebug("[RoomZone] Displayed private chat via text fallback " + $"sender={senderSteam64} room='{DescribeRoomName(roomName)}'.");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[RoomZone] Failed to display local room chat: " + ex.Message);
				return false;
			}
		}

		private static bool IsLocalPlayerInRoom(string roomName, string scope)
		{
			if (string.IsNullOrWhiteSpace(roomName))
			{
				return true;
			}
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return false;
			}
			RoomZone roomZone = FindContainingZone(mainPlayer);
			if ((Object)(object)roomZone == (Object)null)
			{
				return false;
			}
			if (!RoomNamesEqual(roomZone.RoomName, roomName))
			{
				return false;
			}
			if (!string.IsNullOrWhiteSpace(scope))
			{
				string scopeLabel = GetScopeLabel(mainPlayer);
				if (!string.Equals(scopeLabel, scope, StringComparison.OrdinalIgnoreCase))
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryDisplayViaVanillaChat(ulong senderSteam64, string rawMessage, string roomName)
		{
			if (senderSteam64 == 0)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(rawMessage))
			{
				return false;
			}
			try
			{
				Player val = BJNetcode.FindPlayerBySteam64(senderSteam64);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				ChatBehaviour val2 = ((Component)val).GetComponent<ChatBehaviour>() ?? ((Component)val).GetComponentInChildren<ChatBehaviour>();
				if ((Object)(object)val2 == (Object)null)
				{
					return false;
				}
				MethodInfo vanillaReceiveChatMethod = GetVanillaReceiveChatMethod();
				if (vanillaReceiveChatMethod == null)
				{
					return false;
				}
				string text = (string.IsNullOrWhiteSpace(roomName) ? rawMessage : ("[" + SanitizeRichText(roomName) + "] " + rawMessage));
				vanillaReceiveChatMethod.Invoke(val2, new object[3]
				{
					text,
					false,
					(object)(ChatChannel)2
				});
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug("[RoomZone] Vanilla private chat display failed: " + ex.Message);
				return false;
			}
		}

		private static MethodInfo? GetVanillaReceiveChatMethod()
		{
			if (_vanillaReceiveChatMethod != null)
			{
				return _vanillaReceiveChatMethod;
			}
			_vanillaReceiveChatMethod = typeof(ChatBehaviour).GetMethod("UserCode_Rpc_RecieveChatMessage__String__Boolean__ChatChannel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (_vanillaReceiveChatMethod == null)
			{
				_vanillaReceiveChatMethod = typeof(ChatBehaviour).GetMethod("Rpc_RecieveChatMessage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			return _vanillaReceiveChatMethod;
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			ScanScene(scene);
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayedScan(scene));
			}
		}

		[IteratorStateMachine(typeof(<DelayedScan>d__40))]
		private static IEnumerator DelayedScan(Scene scene)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DelayedScan>d__40(0)
			{
				scene = scene
			};
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			_zones.RemoveAll((RoomZone z) => (Object)(object)z == (Object)null || z.SceneHandle == ((Scene)(ref scene)).handle);
			_mapInstancesByScene.Remove(((Scene)(ref scene)).handle);
		}

		internal static void ReportLocalPresence()
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return;
			}
			ulong num = GetSteam64(mainPlayer);
			if (num == 0)
			{
				num = BJNetcode.GetLocalSteam64();
			}
			if (num != 0)
			{
				string text = (HasZonesInSameScene(mainPlayer) ? FindContainingZone(mainPlayer) : null)?.RoomName ?? string.Empty;
				string scopeLabel = GetScopeLabel(mainPlayer);
				string text2 = scopeLabel + "::" + text;
				if (!(text2 == _lastLocalPresenceKey) || !(Time.time < _nextLocalPresenceReportTime))
				{
					_lastLocalPresenceKey = text2;
					_nextLocalPresenceReportTime = Time.time + 2f;
					RoomZoneChatNetcode.PublishLocalPresence(text, scopeLabel);
				}
			}
		}

		internal static Vector3 GetPlayerZonePosition(Player player)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return Vector3.zero;
			}
			Bounds val;
			try
			{
				CharacterController componentInChildren = ((Component)player).GetComponentInChildren<CharacterController>();
				if ((Object)(object)componentInChildren != (Object)null && ((Collider)componentInChildren).enabled)
				{
					val = ((Collider)componentInChildren).bounds;
					return ((Bounds)(ref val)).center;
				}
			}
			catch
			{
			}
			try
			{
				Collider[] componentsInChildren = ((Component)player).GetComponentsInChildren<Collider>(false);
				Bounds? val2 = null;
				Collider[] array = componentsInChildren;
				foreach (Collider val3 in array)
				{
					if (!((Object)(object)val3 == (Object)null) && val3.enabled && !val3.isTrigger)
					{
						if (val2.HasValue)
						{
							Bounds value = val2.Value;
							((Bounds)(ref value)).Encapsulate(val3.bounds);
							val2 = value;
						}
						else
						{
							val2 = val3.bounds;
						}
					}
				}
				if (val2.HasValue)
				{
					val = val2.Value;
					return ((Bounds)(ref val)).center;
				}
			}
			catch
			{
			}
			return ((Component)player).transform.position;
		}

		private static void ScanScene(Scene scene)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded)
			{
				return;
			}
			Component sceneMapInstance = GetSceneMapInstance(scene);
			int num = 0;
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			GameObject[] array = rootGameObjects;
			foreach (GameObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				BoxCollider[] componentsInChildren = val.GetComponentsInChildren<BoxCollider>(true);
				BoxCollider[] array2 = componentsInChildren;
				foreach (BoxCollider val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null || !((Collider)val2).enabled || !((Component)val2).gameObject.activeInHierarchy)
					{
						continue;
					}
					bool flag = StartsWithRoomZone(CleanUnityName(((Object)((Component)val2).gameObject).name));
					if ((((Collider)val2).isTrigger || flag) && IsRoomZoneCandidate(val2))
					{
						RoomZone component = ((Component)val2).GetComponent<RoomZone>();
						RoomZone roomZone = component ?? ((Component)val2).gameObject.AddComponent<RoomZone>();
						roomZone.Setup(val2, ResolveRoomName(val2), scene, sceneMapInstance);
						if ((Object)(object)component == (Object)null)
						{
							num++;
							LogZoneHooked(roomZone);
						}
					}
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo($"[RoomZone] Hooked {num} room zone(s) in scene '{((Scene)(ref scene)).name}'.");
				RefreshMembership();
			}
		}

		private static bool IsRoomZoneCandidate(BoxCollider box)
		{
			string cleanName = CleanUnityName(((Object)((Component)box).gameObject).name);
			if (StartsWithRoomZone(cleanName))
			{
				return true;
			}
			Transform parent = ((Component)box).transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				string cleanName2 = CleanUnityName(((Object)parent).name);
				if (StartsWithRoomZone(cleanName2))
				{
					return true;
				}
				parent = parent.parent;
			}
			return false;
		}

		private static string ResolveRoomName(BoxCollider box)
		{
			string text = ExtractUsableRoomName(((Object)((Component)box).gameObject).name);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			Transform parent = ((Component)box).transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				string text2 = ExtractUsableRoomName(((Object)parent).name);
				if (!string.IsNullOrWhiteSpace(text2))
				{
					return text2;
				}
				parent = parent.parent;
			}
			return "RoomZone";
		}

		private static string? ExtractUsableRoomName(string rawName)
		{
			string text = CleanUnityName(rawName);
			if (IsDefaultRoomName(text))
			{
				return null;
			}
			if (text.StartsWith("RoomZone_", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = text.Substring("RoomZone".Length + 1).Trim(' ', '_', '-', ':');
				return IsDefaultRoomName(text2) ? null : text2;
			}
			if (text.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase) && text.Length > "RoomZone".Length)
			{
				string text3 = text.Substring("RoomZone".Length).Trim(' ', '_', '-', ':');
				return IsDefaultRoomName(text3) ? null : text3;
			}
			return text;
		}

		private static bool StartsWithRoomZone(string cleanName)
		{
			return cleanName.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase);
		}

		private static bool IsDefaultRoomName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return true;
			}
			string a = CleanUnityName(value);
			return string.Equals(a, "RoomZone", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "RoomZone_", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "GameObject", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Cube", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Box", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "BoxCollider", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Collider", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Trigger", StringComparison.OrdinalIgnoreCase);
		}

		private static string CleanUnityName(string rawName)
		{
			string input = rawName ?? string.Empty;
			input = _cloneSuffixRegex.Replace(input, string.Empty);
			return input.Trim();
		}

		private static RoomZone? FindContainingZone(Player player)
		{
			RoomZone result = null;
			float num = float.MaxValue;
			for (int i = 0; i < _zones.Count; i++)
			{
				RoomZone roomZone = _zones[i];
				if (!((Object)(object)roomZone == (Object)null) && !((Object)(object)roomZone.Collider == (Object)null) && IsPlayerInZoneScene(player, roomZone) && roomZone.Contains(player))
				{
					float volume = roomZone.Volume;
					if (volume < num)
					{
						result = roomZone;
						num = volume;
					}
				}
			}
			return result;
		}

		private static void LogMembershipTransition(Player player, ulong steam64, RoomZone? zone)
		{
			//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_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: 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_00a6: Unknown result type (might be due to invalid IL or missing references)
			string text = (((Object)(object)zone == (Object)null) ? (GetScopeLabel(player) + "::main") : $"{zone.SceneHandle}:{zone.RoomName}");
			if (!_lastZoneBySteam64.TryGetValue(steam64, out string value) || !string.Equals(value, text, StringComparison.Ordinal))
			{
				_lastZoneBySteam64[steam64] = text;
				Vector3 playerZonePosition = GetPlayerZonePosition(player);
				if ((Object)(object)zone == (Object)null)
				{
					Plugin.Log.LogDebug($"[RoomZone] Player {steam64} transitioned to main " + "at " + FormatVector(playerZonePosition) + " scope='" + GetScopeLabel(player) + "'.");
					return;
				}
				CasinoLog log = Plugin.Log;
				string[] obj = new string[12]
				{
					$"[RoomZone] Player {steam64} transitioned to room ",
					"'",
					zone.RoomName,
					"' at ",
					FormatVector(playerZonePosition),
					" zoneCenter=",
					null,
					null,
					null,
					null,
					null,
					null
				};
				Bounds bounds = ((Collider)zone.Collider).bounds;
				obj[6] = FormatVector(((Bounds)(ref bounds)).center);
				obj[7] = " zoneSize=";
				obj[8] = FormatVector(zone.Collider.size);
				obj[9] = " lossyScale=";
				obj[10] = FormatVector(((Component)zone.Collider).transform.lossyScale);
				obj[11] = ".";
				log.LogDebug(string.Concat(obj));
			}
		}

		private static void LogZoneHooked(RoomZone zone)
		{
			//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_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)zone == (Object)null) && !((Object)(object)zone.Collider == (Object)null))
			{
				CasinoLog log = Plugin.Log;
				string[] obj = new string[15]
				{
					"[RoomZone] Hooked '",
					((Object)((Component)zone).gameObject).name,
					"' room='",
					zone.RoomName,
					"' scene='",
					zone.SceneName,
					"' ",
					$"trigger={((Collider)zone.Collider).isTrigger} ",
					"center=",
					null,
					null,
					null,
					null,
					null,
					null
				};
				Bounds bounds = ((Collider)zone.Collider).bounds;
				obj[9] = FormatVector(((Bounds)(ref bounds)).center);
				obj[10] = " size=";
				obj[11] = FormatVector(zone.Collider.size);
				obj[12] = " lossyScale=";
				obj[13] = FormatVector(((Component)zone.Collider).transform.lossyScale);
				obj[14] = ".";
				log.LogInfo(string.Concat(obj));
			}
		}

		private static bool HasZonesInSameScene(Player player)
		{
			for (int i = 0; i < _zones.Count; i++)
			{
				RoomZone roomZone = _zones[i];
				if ((Object)(object)roomZone != (Object)null && IsPlayerInZoneScene(player, roomZone))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsSameSceneScope(Player a, Player b)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)a == (Object)null || (Object)(object)b == (Object)null)
			{
				return false;
			}
			Component playerMapInstance = GetPlayerMapInstance(a);
			Component playerMapInstance2 = GetPlayerMapInstance(b);
			if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)playerMapInstance2 != (Object)null)
			{
				return playerMapInstance == playerMapInstance2;
			}
			string playerMapName = GetPlayerMapName(a);
			string playerMapName2 = GetPlayerMapName(b);
			if (!string.IsNullOrWhiteSpace(playerMapName) && !string.IsNullOrWhiteSpace(playerMapName2))
			{
				return string.Equals(playerMapName, playerMapName2, StringComparison.OrdinalIgnoreCase);
			}
			Scene scene = ((Component)a).gameObject.scene;
			int handle = ((Scene)(ref scene)).handle;
			scene = ((Component)b).gameObject.scene;
			return handle == ((Scene)(ref scene)).handle;
		}

		private static bool IsPlayerInZoneScene(Player player, RoomZone zone)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			Component playerMapInstance = GetPlayerMapInstance(player);
			if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)zone.MapInstance != (Object)null)
			{
				return playerMapInstance == zone.MapInstance;
			}
			string playerMapName = GetPlayerMapName(player);
			if (!string.IsNullOrWhiteSpace(playerMapName) && !string.IsNullOrWhiteSpace(zone.SceneName))
			{
				return string.Equals(playerMapName, zone.SceneName, StringComparison.OrdinalIgnoreCase);
			}
			Scene scene = ((Component)player).gameObject.scene;
			return ((Scene)(ref scene)).handle == zone.SceneHandle;
		}

		private static Component? GetSceneMapInstance(Scene scene)
		{
			if (_mapInstancesByScene.TryGetValue(((Scene)(ref scene)).handle, out Component value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			Component val = null;
			try
			{
				GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
				GameObject[] array = rootGameObjects;
				foreach (GameObject val2 in array)
				{
					MonoBehaviour[] componentsInChildren = val2.GetComponentsInChildren<MonoBehaviour>(true);
					MonoBehaviour[] array2 = componentsInChildren;
					foreach (MonoBehaviour val3 in array2)
					{
						if (!((Object)(object)val3 == (Object)null) && !(((object)val3).GetType().Name != "MapInstance"))
						{
							val = (Component)(object)val3;
							break;
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[RoomZone] MapInstance scan threw in scene '" + ((Scene)(ref scene)).name + "': " + ex.Message);
			}
			_mapInstancesByScene[((Scene)(ref scene)).handle] = val;
			return val;
		}

		private static Player? GetPlayer(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return null;
			}
			try
			{
				Player component = ((Component)chat).GetComponent<Player>();
				if ((Object)(object)component != (Object)null)
				{
					return component;
				}
			}
			catch
			{
			}
			try
			{
				object? obj2 = typeof(ChatBehaviour).GetField("_player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat);
				return (Player?)((obj2 is Player) ? obj2 : null);
			}
			catch
			{
				return null;
			}
		}

		private static Component? GetPlayerMapInstance(Player player)
		{
			ResolvePlayerReflection();
			try
			{
				object obj = _playerMapInstanceProperty?.GetValue(player);
				Component val = (Component)((obj is Component) ? obj : null);
				if (val != null)
				{
					return val;
				}
			}
			catch
			{
			}
			try
			{
				object obj3 = _playerMapInstanceField?.GetValue(player);
				Component val2 = (Component)((obj3 is Component) ? obj3 : null);
				if (val2 != null)
				{
					return val2;
				}
			}
			catch
			{
			}
			return null;
		}

		private static string GetPlayerMapName(Player player)
		{
			ResolvePlayerReflection();
			try
			{
				object obj = _playerMapNameProperty?.GetValue(player);
				if (obj is string result)
				{
					return result;
				}
			}
			catch
			{
			}
			try
			{
				object obj3 = _playerMapNameField?.GetValue(player);
				if (obj3 is string result2)
				{
					return result2;
				}
			}
			catch
			{
			}
			return string.Empty;
		}

		private static void ResolvePlayerReflection()
		{
			if (!_reflectionResolved)
			{
				_reflectionResolved = true;
				Type typeFromHandle = typeof(Player);
				_playerMapInstanceProperty = typeFromHandle.GetProperty("Network_playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_playerMapInstanceField = typeFromHandle.GetField("_playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeFromHandle.GetField("playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_playerMapNameProperty = typeFromHandle.GetProperty("Network_mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				_playerMapNameField = typeFromHandle.GetField("_mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeFromHandle.GetField("mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!_loggedReflectionFailure && _playerMapInstanceProperty == null && _playerMapInstanceField == null && Plugin.Log != null)
				{
					Plugin.Log.LogWarning("[RoomZone] Player MapInstance reflection failed. Room-zone chat will fall back to map names / GameObject scenes.");
					_loggedReflectionFailure = true;
				}
			}
		}

		private static string FormatRoomMessage(Player? sender, string roomName, string message)
		{
			string text = (string.IsNullOrWhiteSpace(roomName) ? "Zone" : SanitizeRichText(roomName));
			string text2 = SanitizeRichText(((Object)(object)sender == (Object)null) ? "Player" : GetDisplayName(sender));
			return "<color=#9FD3FF>[" + text + "]</color> <color=#FFFFFF>" + text2 + "</color>: " + message;
		}

		private static string GetDisplayName(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return "Player";
			}
			try
			{
				if (!string.IsNullOrWhiteSpace(player.Network_nickname))
				{
					return player.Network_nickname;
				}
			}
			catch
			{
			}
			try
			{
				if (!string.IsNullOrWhiteSpace(player.Network_globalNickname))
				{
					return player.Network_globalNickname;
				}
			}
			catch
			{
			}
			string text = CleanUnityName(((Object)((Component)player).gameObject).name);
			return string.IsNullOrWhiteSpace(text) ? "Player" : text;
		}

		private static string GetScopeLabel(Player player)
		{
			//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)
			Component playerMapInstance = GetPlayerMapInstance(player);
			if ((Object)(object)playerMapInstance != (Object)null)
			{
				return ((Object)playerMapInstance.gameObject).name;
			}
			string playerMapName = GetPlayerMapName(player);
			if (!string.IsNullOrWhiteSpace(playerMapName))
			{
				return playerMapName;
			}
			Scene scene = ((Component)player).gameObject.scene;
			return ((Scene)(ref scene)).name;
		}

		private static string SanitizeRichText(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			return value.Replace("<", string.Empty).Replace(">", string.Empty);
		}

		private static string DescribeZone(RoomZone? zone)
		{
			return ((Object)(object)zone == (Object)null) ? "main" : SanitizeRichText(zone.RoomName);
		}

		private static string DescribeRoomName(string roomName)
		{
			return string.IsNullOrWhiteSpace(roomName) ? "main" : SanitizeRichText(roomName);
		}

		private static string FormatVector(Vector3 value)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			return $"({value.x:F2}, {value.y:F2}, {value.z:F2})";
		}
	}
	[HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")]
	internal static class RoomZoneLocalChatPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		public static bool Prefix(ChatBehaviour __instance, string _message)
		{
			try
			{
				return !RoomZoneRegistry.TryRouteLocalRoomZoneChat(__instance, _message);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError($"[RoomZone] Local chat routing failed; falling back to vanilla chat. {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(ChatBehaviour), "UserCode_Cmd_SendChatMessage__String__ChatChannel")]
	internal static class RoomZoneChatPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		public static bool Prefix(ChatBehaviour __instance, string _message, ChatChannel _chatChannel)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return !RoomZoneRegistry.TryRouteZoneChat(__instance, _message, _chatChannel);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError($"[RoomZone] Chat routing failed; falling back to vanilla chat. {arg}");
				return true;
			}
		}
	}
	internal static class CasinoConfig
	{
		internal const int DefaultEntryFeeCrowns = 100;

		internal const int DefaultWalkAwayPenaltyCrowns = 10;

		internal const int DefaultBlackjackTurnTimeoutSeconds = 60;

		internal const int DefaultRouletteAfkTimeoutSeconds = 60;

		internal const float DefaultSlotsPayoutMultiplier = 1f;

		internal const float DefaultBlackjackPayoutMultiplier = 1f;

		internal const float DefaultRoulettePayoutMultiplier = 1f;

		internal const string DefaultAllowedBetAmountsCsv = "10,50,100,500";

		internal const float JukeboxOutputScale = 0.025f;

		internal const float JukeboxMaxEffectiveVolume = 0.25f;

		internal static ConfigEntry<int>? EntryFeeCrownsEntry;

		internal static ConfigEntry<int>? WalkAwayPenaltyCrownsEntry;

		internal static ConfigEntry<int>? BlackjackTurnTimeoutSecondsEntry;

		internal static ConfigEntry<int>? RouletteAfkTimeoutSecondsEntry;

		internal static ConfigEntry<float>? SlotsPayoutMultiplierEntry;

		internal static ConfigEntry<float>? BlackjackPayoutMultiplierEntry;

		internal static ConfigEntry<float>? RoulettePayoutMul