Decompiled source of AtlyssCasino v1.10.1

AtlyssCasino.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
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 Mirror;
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.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AtlyssCasino")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.10.0.0")]
[assembly: AssemblyInformationalVersion("1.10.0")]
[assembly: AssemblyProduct("AtlyssCasino")]
[assembly: AssemblyTitle("AtlyssCasino")]
[assembly: AssemblyVersion("1.10.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AtlyssCasino
{
	public static class BlackjackSceneWatcher
	{
		private const string CASINO_SCENE_NAME = "AtlyssCasino";

		private const float EXIT_CONFIRM_DELAY_SECONDS = 0.25f;

		private static Coroutine? _cleanupCoroutine;

		private static bool _casinoSceneSeen;

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

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (((Scene)(ref scene)).name == "AtlyssCasino")
			{
				_casinoSceneSeen = true;
				Plugin.Log.LogInfo("[SceneWatcher] Casino scene loaded.");
			}
			else if (_casinoSceneSeen)
			{
				ScheduleScopedCleanup("scene-loaded:" + ((Scene)(ref scene)).name);
			}
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			if (!(((Scene)(ref scene)).name != "AtlyssCasino"))
			{
				if (!CasinoRuntimeActivity.IsLocalPlayerInCasino)
				{
					CleanupOnConfirmedLeave("casino-unloaded");
				}
				_casinoSceneSeen = CasinoRuntimeActivity.CasinoSceneLoaded;
			}
		}

		private static void OnActiveSceneChanged(Scene oldScene, Scene newScene)
		{
			if (!(((Scene)(ref oldScene)).name != "AtlyssCasino") && !(((Scene)(ref newScene)).name == "AtlyssCasino"))
			{
				ScheduleScopedCleanup("active-scene:" + ((Scene)(ref oldScene)).name + "->" + ((Scene)(ref newScene)).name);
			}
		}

		private static void ScheduleScopedCleanup(string reason)
		{
			if (_cleanupCoroutine != null || (Object)(object)Plugin.Instance == (Object)null)
			{
				return;
			}
			try
			{
				_cleanupCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(ScopedCleanupAfterPresenceSettles(reason));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[SceneWatcher] Failed to schedule scoped cleanup: " + ex.Message);
			}
		}

		private static IEnumerator ScopedCleanupAfterPresenceSettles(string reason)
		{
			yield return null;
			yield return (object)new WaitForSecondsRealtime(0.25f);
			if (CasinoRuntimeActivity.IsLocalPlayerInCasino)
			{
				Plugin.Log.LogDebug("[SceneWatcher] Cleanup skipped (" + reason + "); player is still in casino.");
				_cleanupCoroutine = null;
			}
			else
			{
				CleanupOnConfirmedLeave(reason);
				_cleanupCoroutine = null;
			}
		}

		private static void CleanupOnConfirmedLeave(string reason)
		{
			try
			{
				bool num = ReleaseAnySeatedTable();
				bool flag = ReleaseAnyRouletteTable();
				Plugin.HasSetBlackjackBet = false;
				Plugin.HasSetRouletteBet = false;
				if (num || flag)
				{
					Plugin.Log.LogInfo("[SceneWatcher] Released local casino table state (" + reason + ").");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[SceneWatcher] Scoped cleanup failed: " + ex.Message);
			}
		}

		private static bool ReleaseAnySeatedTable()
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return false;
			}
			bool result = false;
			BlackjackTable[] array = Object.FindObjectsOfType<BlackjackTable>();
			foreach (BlackjackTable blackjackTable in array)
			{
				int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer);
				if (seatForPlayer >= 0)
				{
					if (BJNetcode.AmHostFresh())
					{
						blackjackTable.ReleaseSeat(seatForPlayer);
						BJNetcode.BroadcastSeatReleased(((Object)blackjackTable).name, seatForPlayer, blackjackTable.HostSeatIndex);
					}
					else
					{
						BJNetcode.SendReleaseSeatRequest(((Object)blackjackTable).name);
						blackjackTable.ApplySeatReleased(seatForPlayer, blackjackTable.HostSeatIndex);
					}
					result = true;
				}
			}
			return result;
		}

		private static bool ReleaseAnyRouletteTable()
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				return false;
			}
			bool result = false;
			ulong localSteam = BJNetcode.GetLocalSteam64();
			RouletteTable[] array = Object.FindObjectsOfType<RouletteTable>();
			foreach (RouletteTable rouletteTable in array)
			{
				if (rouletteTable.IsPlayerAtTable(mainPlayer))
				{
					if (BJNetcode.AmHostFresh())
					{
						rouletteTable.Leave(mainPlayer);
						RNNetcode.BroadcastPlayerLeft(((Object)rouletteTable).name, localSteam, rouletteTable.HostSteam64, rouletteTable.PlayerCount);
					}
					else
					{
						RNNetcode.SendLeaveTableRequest(((Object)rouletteTable).name);
						rouletteTable.ApplyPlayerLeftBySteam64(localSteam, 0uL);
					}
					result = true;
				}
			}
			return result;
		}
	}
	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 const float TRACK_END_TOLERANCE_SEC = 0.15f;

		private const float AUTHORITY_REFRESH_INTERVAL_SEC = 2f;

		private const float OUTSIDE_UPDATE_INTERVAL_SEC = 0.5f;

		private static readonly Dictionary<string, CasinoJukebox> _registry = new Dictionary<string, CasinoJukebox>(StringComparer.Ordinal);

		private AudioSource? _source;

		private AudioSource? _localExtraSource;

		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 bool _trackHasAudioProgress;

		private bool _playingLocalExtras;

		private bool _waitingForServerBoundary;

		private bool _hasLatestServerState;

		private bool _wasWorldAudioAllowed;

		private bool _hasClockAuthority;

		private float _nextOutsideUpdateTime;

		private int _playlistStep;

		private int _appliedPlaylistStep = int.MinValue;

		private int _localExtraIndex;

		private int _latestServerTrackIndex = -1;

		private float _trackStartedAt;

		private float _localExtraStartedAt;

		private float _lastObservedSourceTime;

		private float _nextInputTime;

		private float _nextRestartAttemptTime;

		private float _appliedVolume = -1f;

		private float _appliedExtraVolume = -1f;

		private float _nextAuthorityRefreshTime;

		private JukeboxWorldState _latestServerState;

		private string _objectName = "CasinoJukebox";

		internal string ObjectName => _objectName;

		internal int PlaylistStep => _playlistStep;

		internal bool IsPlaying => _isPlaying;

		internal bool IsLocallyAudible
		{
			get
			{
				if (IsWorldAudioAllowedLocally() && _isPlaying)
				{
					if (!((Object)(object)_source != (Object)null) || !_source.isPlaying)
					{
						if ((Object)(object)_localExtraSource != (Object)null)
						{
							return _localExtraSource.isPlaying;
						}
						return false;
					}
					return true;
				}
				return false;
			}
		}

		internal float ElapsedSeconds
		{
			get
			{
				if (!_isPlaying)
				{
					return 0f;
				}
				return Mathf.Max(0f, Time.time - _trackStartedAt);
			}
		}

		public void Setup()
		{
			_objectName = ((Object)((Component)this).gameObject).name;
			_registry[_objectName] = this;
			if (!_setup)
			{
				_collider = ((Component)this).GetComponent<BoxCollider>() ?? ((Component)this).GetComponentInChildren<BoxCollider>(true);
				if (!_subscribed)
				{
					CasinoJukeboxManager.OnPlaylistChanged += OnPlaylistChanged;
					_subscribed = true;
				}
				_setup = true;
				_wasWorldAudioAllowed = IsWorldAudioAllowedLocally();
				_hasClockAuthority = HasClockAuthority();
				_nextAuthorityRefreshTime = Time.unscaledTime + 2f;
				if (_hasClockAuthority)
				{
					CasinoJukeboxManager.EnsureServerTracksLoaded();
				}
				else if (_wasWorldAudioAllowed)
				{
					CasinoJukeboxManager.EnsureClientTracksLoaded();
				}
				if (_wasWorldAudioAllowed)
				{
					EnsureWorldAudioSource();
				}
				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()
		{
			foreach (CasinoJukebox value in _registry.Values)
			{
				if (!((Object)(object)value == (Object)null))
				{
					value.PrepareForLocalEntry();
					value.RequestAutoStart();
				}
			}
		}

		private void PrepareForLocalEntry()
		{
			if (_setup && !Plugin.IsHeadlessServer)
			{
				_wasWorldAudioAllowed = false;
				_appliedPlaying = false;
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				if (!BJNetcode.AmHost())
				{
					JukeboxNetcode.SendStateRequest(_objectName);
				}
			}
		}

		internal static CasinoJukebox? FindByName(string objectName)
		{
			if (_registry.TryGetValue(objectName, out CasinoJukebox value))
			{
				if ((Object)(object)value != (Object)null)
				{
					return value;
				}
				_registry.Remove(objectName);
			}
			return null;
		}

		internal static bool IsAnyWorldJukeboxPlaying()
		{
			foreach (CasinoJukebox value in _registry.Values)
			{
				if ((Object)(object)value != (Object)null && value.IsLocallyAudible)
				{
					return true;
				}
			}
			return false;
		}

		internal static void ClearRegistry()
		{
			_registry.Clear();
		}

		internal JukeboxWorldState BuildState()
		{
			return new JukeboxWorldState
			{
				ObjectName = _objectName,
				PlaylistStep = _playlistStep,
				Playing = _isPlaying,
				ElapsedSeconds = ElapsedSeconds
			};
		}

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

		internal void ApplyRemoteState(int playlistStep, bool playing, float elapsedSeconds)
		{
			int previousServerStep = (_hasLatestServerState ? _latestServerState.PlaylistStep : int.MinValue);
			int latestServerTrackIndex = _latestServerTrackIndex;
			CacheServerState(playlistStep, playing, elapsedSeconds);
			if (!ShouldHoldForLocalExtras(previousServerStep, playlistStep, latestServerTrackIndex, _latestServerTrackIndex, playing))
			{
				ApplyState(playlistStep, playing, elapsedSeconds);
			}
		}

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

		private void TryAutoStart()
		{
			if (_setup && _autoStartRequested && !_isPlaying && Plugin.JukeboxEnabled && HasClockAuthority())
			{
				CasinoJukeboxManager.EnsureServerTracksLoaded();
				if (CasinoJukeboxManager.HasServerTracks)
				{
					ApplyState(_playlistStep, playing: true, 0f);
					JukeboxNetcode.BroadcastState(BuildState());
				}
			}
		}

		private void ApplyState(int playlistStep, bool playing, float elapsedSeconds)
		{
			_playlistStep = playlistStep;
			_isPlaying = playing;
			elapsedSeconds = Mathf.Max(0f, elapsedSeconds);
			_trackStartedAt = Time.time - elapsedSeconds;
			if (!playing || !Plugin.JukeboxEnabled)
			{
				_appliedClip = null;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = false;
				_playingLocalExtras = false;
				_waitingForServerBoundary = false;
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				return;
			}
			bool flag = IsWorldAudioAllowedLocally();
			bool flag2 = HasClockAuthority();
			if (!flag && !flag2)
			{
				_appliedClip = null;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = false;
				_playingLocalExtras = false;
				_waitingForServerBoundary = false;
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				return;
			}
			if (flag2)
			{
				CasinoJukeboxManager.EnsureServerTracksLoaded();
			}
			else
			{
				CasinoJukeboxManager.EnsureClientTracksLoaded();
			}
			AudioClip serverClip = CasinoJukeboxManager.GetServerClip(playlistStep);
			if ((Object)(object)serverClip == (Object)null)
			{
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				Plugin.Log.LogWarning($"[Jukebox] No bundled server clip available for playlist step {playlistStep}.");
				return;
			}
			if (serverClip.length > 0.1f)
			{
				elapsedSeconds = Mathf.Min(elapsedSeconds, serverClip.length - 0.05f);
			}
			_trackStartedAt = Time.time - elapsedSeconds;
			ResetPlaybackObservation(elapsedSeconds);
			if (!flag)
			{
				_appliedClip = serverClip;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = false;
				_playingLocalExtras = false;
				_waitingForServerBoundary = false;
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				return;
			}
			EnsureWorldAudioSource();
			if ((Object)(object)_source == (Object)null)
			{
				return;
			}
			bool flag3 = (Object)(object)_appliedClip != (Object)(object)serverClip || _appliedPlaylistStep != playlistStep || _appliedPlaying != playing;
			bool flag4 = (Object)(object)_source.clip == (Object)(object)serverClip && _source.isPlaying && flag;
			if (!flag3 && flag4)
			{
				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 (!flag3 && flag && !_source.isPlaying && Time.time < _nextRestartAttemptTime)
				{
					return;
				}
				_appliedClip = serverClip;
				_appliedPlaylistStep = playlistStep;
				_appliedPlaying = playing;
				_playingLocalExtras = false;
				_waitingForServerBoundary = false;
				StopLocalExtraAudio();
				if ((Object)(object)_source.clip != (Object)(object)serverClip)
				{
					_source.clip = serverClip;
				}
				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.GetServerTrackName(playlistStep) + " " + $"on '{_objectName}' (step {playlistStep}, " + $"volume={CasinoConfig.EffectiveJukeboxVolume:0.000}).");
			}
		}

		private void Update()
		{
			if (!_setup)
			{
				return;
			}
			bool flag = IsWorldAudioAllowedLocally();
			if (!flag && !_wasWorldAudioAllowed && Time.unscaledTime < _nextOutsideUpdateTime)
			{
				return;
			}
			if (!flag)
			{
				_nextOutsideUpdateTime = Time.unscaledTime + 0.5f;
			}
			bool clockAuthorityCached = GetClockAuthorityCached();
			if (!flag)
			{
				if (_wasWorldAudioAllowed)
				{
					_wasWorldAudioAllowed = false;
					_appliedPlaying = false;
					_playingLocalExtras = false;
					_waitingForServerBoundary = false;
					StopLocalExtraAudio();
					StopLocalWorldAudio();
				}
				_playerNearby = false;
				if (Plugin.JukeboxEnabled && clockAuthorityCached)
				{
					if (Plugin.IsHeadlessServer || (Object)(object)Plugin.AssetsBundle != (Object)null || _isPlaying)
					{
						CasinoJukeboxManager.EnsureServerTracksLoaded();
					}
					if (_isPlaying && HasCurrentTrackEndedByClock())
					{
						HostAdvance(1);
					}
				}
				return;
			}
			if (!_wasWorldAudioAllowed)
			{
				_wasWorldAudioAllowed = true;
				CasinoJukeboxManager.EnsureClientTracksLoaded();
				_latestServerTrackIndex = CasinoJukeboxManager.GetServerTrackIndex(_playlistStep);
				if (_isPlaying)
				{
					ApplyState(_playlistStep, playing: true, ElapsedSeconds);
				}
			}
			ApplyVolumeIfChanged();
			if (!Plugin.JukeboxEnabled)
			{
				StopLocalExtraAudio();
				StopLocalWorldAudio();
				return;
			}
			if (_playingLocalExtras)
			{
				UpdateLocalExtraPlayback(flag);
				UpdateLocalInteraction();
				return;
			}
			if (_waitingForServerBoundary)
			{
				StopLocalWorldAudio();
				UpdateLocalInteraction();
				return;
			}
			if ((Object)(object)_source != (Object)null && _isPlaying && _source.isPlaying)
			{
				ObserveAudioProgress();
			}
			if (_isPlaying && (Object)(object)_source != (Object)null && !_source.isPlaying && (Object)(object)CasinoJukeboxManager.GetServerClip(_playlistStep) != (Object)null)
			{
				if (clockAuthorityCached && HasCurrentTrackEnded())
				{
					HostAdvance(1);
					UpdateLocalInteraction();
					return;
				}
				if (Time.time < _nextRestartAttemptTime)
				{
					UpdateLocalInteraction();
					return;
				}
				ApplyState(_playlistStep, playing: true, GetRestartElapsedSeconds());
			}
			if (clockAuthorityCached && _isPlaying && HasCurrentTrackEnded())
			{
				HostAdvance(1);
			}
			UpdateLocalInteraction();
		}

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

		private static bool HasClockAuthority()
		{
			if (!Plugin.IsHeadlessServer)
			{
				return BJNetcode.AmHost();
			}
			return true;
		}

		private bool GetClockAuthorityCached()
		{
			if (Plugin.IsHeadlessServer)
			{
				_hasClockAuthority = true;
				return true;
			}
			if (Time.unscaledTime < _nextAuthorityRefreshTime)
			{
				return _hasClockAuthority;
			}
			_nextAuthorityRefreshTime = Time.unscaledTime + 2f;
			_hasClockAuthority = BJNetcode.AmHost();
			return _hasClockAuthority;
		}

		private void EnsureWorldAudioSource()
		{
			if ((Object)(object)_source == (Object)null)
			{
				_source = ((Component)this).GetComponentInChildren<AudioSource>(true) ?? ((Component)this).gameObject.AddComponent<AudioSource>();
				CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true);
				_appliedVolume = -1f;
			}
			ApplyVolumeIfChanged();
		}

		private void ApplyVolumeIfChanged()
		{
			if (!((Object)(object)_source == (Object)null))
			{
				float effectiveJukeboxVolume = CasinoConfig.EffectiveJukeboxVolume;
				if (!Mathf.Approximately(_appliedVolume, effectiveJukeboxVolume))
				{
					_source.volume = effectiveJukeboxVolume;
					_appliedVolume = effectiveJukeboxVolume;
				}
			}
		}

		private void ApplyExtraVolumeIfChanged()
		{
			if (!((Object)(object)_localExtraSource == (Object)null))
			{
				float effectiveJukeboxVolume = CasinoConfig.EffectiveJukeboxVolume;
				if (!Mathf.Approximately(_appliedExtraVolume, effectiveJukeboxVolume))
				{
					_localExtraSource.volume = effectiveJukeboxVolume;
					_appliedExtraVolume = effectiveJukeboxVolume;
				}
			}
		}

		private bool CanUseLocalExtras()
		{
			if (Plugin.IsHeadlessServer)
			{
				return false;
			}
			if (BJNetcode.AmHost())
			{
				return false;
			}
			if (!Plugin.JukeboxEnabled)
			{
				return false;
			}
			return CasinoJukeboxManager.LocalTrackCount > 0;
		}

		private void CacheServerState(int playlistStep, bool playing, float elapsedSeconds)
		{
			_latestServerState = new JukeboxWorldState
			{
				ObjectName = _objectName,
				PlaylistStep = playlistStep,
				Playing = playing,
				ElapsedSeconds = elapsedSeconds
			};
			_hasLatestServerState = true;
			_latestServerTrackIndex = CasinoJukeboxManager.GetServerTrackIndex(playlistStep);
		}

		private bool ShouldHoldForLocalExtras(int previousServerStep, int currentServerStep, int previousServerIndex, int currentServerIndex, bool playing)
		{
			if (!CanUseLocalExtras())
			{
				return false;
			}
			if (!playing)
			{
				_playingLocalExtras = false;
				_waitingForServerBoundary = false;
				StopLocalExtraAudio();
				return false;
			}
			if (_waitingForServerBoundary)
			{
				if (currentServerStep != previousServerStep && currentServerIndex >= 0)
				{
					_waitingForServerBoundary = false;
					return false;
				}
				StopLocalWorldAudio();
				return true;
			}
			if (_playingLocalExtras)
			{
				return true;
			}
			if (!IsWorldAudioAllowedLocally())
			{
				return false;
			}
			int serverTrackCount = CasinoJukeboxManager.ServerTrackCount;
			if (serverTrackCount <= 0)
			{
				return false;
			}
			if (currentServerStep == previousServerStep)
			{
				return false;
			}
			if (previousServerIndex != serverTrackCount - 1)
			{
				return false;
			}
			if (currentServerIndex != 0)
			{
				return false;
			}
			StartLocalExtras();
			return true;
		}

		private void StartLocalExtras()
		{
			if (CanUseLocalExtras())
			{
				StopLocalWorldAudio();
				CasinoJukeboxPersonalPlayer.StopForWorldJukebox();
				_playingLocalExtras = true;
				_waitingForServerBoundary = false;
				_localExtraIndex = 0;
				PlayLocalExtra(_localExtraIndex);
			}
		}

		private void UpdateLocalExtraPlayback(bool worldAudioAllowed)
		{
			if (!worldAudioAllowed || !CanUseLocalExtras())
			{
				FinishLocalExtras();
				return;
			}
			if ((Object)(object)_localExtraSource == (Object)null || (Object)(object)_localExtraSource.clip == (Object)null || !_localExtraSource.isPlaying)
			{
				AudioClip localClip = CasinoJukeboxManager.GetLocalClip(_localExtraIndex);
				if ((Object)(object)localClip == (Object)null)
				{
					FinishLocalExtras();
					return;
				}
				if ((Object)(object)_localExtraSource == (Object)null || (Object)(object)_localExtraSource.clip != (Object)(object)localClip)
				{
					PlayLocalExtra(_localExtraIndex);
					return;
				}
			}
			if ((Object)(object)_localExtraSource != (Object)null)
			{
				ApplyExtraVolumeIfChanged();
			}
			AudioSource? localExtraSource = _localExtraSource;
			AudioClip val = ((localExtraSource != null) ? localExtraSource.clip : null);
			if ((Object)(object)val == (Object)null || val.length <= 0.1f)
			{
				return;
			}
			bool flag = Time.time - _localExtraStartedAt >= val.length - 0.15f;
			bool flag2 = false;
			try
			{
				flag2 = (Object)(object)_localExtraSource != (Object)null && !_localExtraSource.isPlaying;
			}
			catch
			{
			}
			if (flag || flag2)
			{
				_localExtraIndex++;
				if (_localExtraIndex < CasinoJukeboxManager.LocalTrackCount)
				{
					PlayLocalExtra(_localExtraIndex);
				}
				else
				{
					FinishLocalExtras();
				}
			}
		}

		private void PlayLocalExtra(int localIndex)
		{
			AudioClip localClip = CasinoJukeboxManager.GetLocalClip(localIndex);
			if ((Object)(object)localClip == (Object)null)
			{
				FinishLocalExtras();
				return;
			}
			if ((Object)(object)_localExtraSource == (Object)null)
			{
				_localExtraSource = ((Component)this).gameObject.AddComponent<AudioSource>();
				_appliedExtraVolume = -1f;
			}
			CasinoJukeboxManager.ConfigureAudioSource(_localExtraSource, spatial: true);
			ApplyExtraVolumeIfChanged();
			_localExtraSource.Stop();
			_localExtraSource.clip = localClip;
			_localExtraStartedAt = Time.time;
			_localExtraSource.Play();
			CasinoJukeboxAudioGuard.RegisterJukeboxSource(_localExtraSource);
			Plugin.Log.LogInfo("[Jukebox] Playing local extra " + CasinoJukeboxManager.GetLocalTrackName(localIndex) + " " + $"on '{_objectName}' (local {localIndex + 1}/{CasinoJukeboxManager.LocalTrackCount}).");
		}

		private void FinishLocalExtras()
		{
			StopLocalExtraAudio();
			_playingLocalExtras = false;
			_waitingForServerBoundary = CanUseLocalExtras() && _hasLatestServerState;
		}

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

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

		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;
			}
			if (Plugin.IsHeadlessServer)
			{
				return HasCurrentTrackEndedByClock();
			}
			if (_source.isPlaying)
			{
				float num = 0f;
				try
				{
					num = _source.time;
				}
				catch
				{
				}
				if (num > _lastObservedSourceTime)
				{
					ObserveAudioProgress();
				}
				return num >= length - 0.15f;
			}
			if (!_trackHasAudioProgress)
			{
				return false;
			}
			return _lastObservedSourceTime >= length - 0.15f;
		}

		private bool HasCurrentTrackEndedByClock()
		{
			AudioClip serverClip = CasinoJukeboxManager.GetServerClip(_playlistStep);
			if ((Object)(object)serverClip == (Object)null)
			{
				return false;
			}
			float length = serverClip.length;
			if (length <= 0.1f)
			{
				return false;
			}
			return Time.time - _trackStartedAt >= length - 0.15f;
		}

		private void ResetPlaybackObservation(float elapsedSeconds)
		{
			_lastObservedSourceTime = Mathf.Max(0f, elapsedSeconds);
			_trackHasAudioProgress = elapsedSeconds > 0.15f;
		}

		private void ObserveAudioProgress()
		{
			if (!((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null))
			{
				float num = 0f;
				try
				{
					num = _source.time;
				}
				catch
				{
					return;
				}
				if (!_trackHasAudioProgress || num > _lastObservedSourceTime + 0.02f)
				{
					_trackHasAudioProgress = true;
					_lastObservedSourceTime = num;
				}
			}
		}

		private float GetRestartElapsedSeconds()
		{
			AudioClip serverClip = CasinoJukeboxManager.GetServerClip(_playlistStep);
			if ((Object)(object)serverClip == (Object)null || serverClip.length <= 0.1f)
			{
				return 0f;
			}
			float num = Mathf.Max(0f, serverClip.length - 0.15f);
			if ((Object)(object)_source != (Object)null && (Object)(object)_source.clip == (Object)(object)serverClip)
			{
				try
				{
					float time = _source.time;
					if (time > 0f && time < num)
					{
						return time;
					}
				}
				catch
				{
				}
			}
			if (_trackHasAudioProgress)
			{
				return Mathf.Clamp(_lastObservedSourceTime, 0f, num);
			}
			return Mathf.Clamp(Time.time - _trackStartedAt, 0f, num);
		}

		private void UpdateLocalInteraction()
		{
			if (Plugin.IsHeadlessServer)
			{
				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.AmHostFresh())
				{
					HostAdvance(1);
					return;
				}
				JukeboxNetcode.SendAdvanceRequest(_objectName, 1);
				Plugin.ShowHUDInfo("Jukebox skip requested...");
			}
		}

		private bool IsPlayerInRange(Player player)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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.HasServerTracks)
			{
				Plugin.ShowHUDError("Jukebox has no server songs loaded.");
				return;
			}
			string text = (_isPlaying ? CasinoJukeboxManager.GetServerTrackName(_playlistStep) : "ready");
			Plugin.ShowHUDInfo("Jukebox: " + text + ". Press " + CasinoInput.InteractPrompt + " for next song.");
		}

		private void OnPlaylistChanged()
		{
			if (_playingLocalExtras)
			{
				if (CasinoJukeboxManager.LocalTrackCount == 0)
				{
					FinishLocalExtras();
				}
				else if (_localExtraIndex >= CasinoJukeboxManager.LocalTrackCount)
				{
					FinishLocalExtras();
				}
				else
				{
					PlayLocalExtra(_localExtraIndex);
				}
			}
			else
			{
				TryAutoStart();
			}
		}

		private void OnDestroy()
		{
			StopLocalExtraAudio();
			StopLocalWorldAudio();
			if (_subscribed)
			{
				CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged;
				_subscribed = false;
			}
			if (_registry.TryGetValue(_objectName, out CasinoJukebox value) && value == this)
			{
				_registry.Remove(_objectName);
			}
		}
	}
	internal static class CasinoJukeboxAudioGuard
	{
		private static readonly List<AudioSource> _owners = new List<AudioSource>();

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

		private static bool _sceneHooksInstalled;

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

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

		private static void InstallSceneHooks()
		{
			if (!_sceneHooksInstalled)
			{
				SceneManager.sceneLoaded += OnSceneLoaded;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				_sceneHooksInstalled = true;
			}
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			RemoveDeadOwners();
			if (_owners.Count > 0)
			{
				PauseCompetingSources();
			}
			else
			{
				RestorePausedSources();
			}
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			RemoveDeadOwners();
			RemoveDeadPausedSources();
			if (_owners.Count == 0)
			{
				RestorePausedSources();
			}
		}

		private static void PauseCompetingSources()
		{
			if (_owners.Count == 0)
			{
				return;
			}
			AudioSource[] array = Object.FindObjectsOfType<AudioSource>();
			foreach (AudioSource val in array)
			{
				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 void RemoveDeadPausedSources()
		{
			for (int num = _pausedSources.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)_pausedSources[num] == (Object)null)
				{
					_pausedSources.RemoveAt(num);
				}
			}
		}

		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;
			if (!ContainsAudioWord(value, "music") && !ContainsAudioWord(value, "ambience") && !ContainsAudioWord(value, "ambient"))
			{
				return ContainsAudioWord(value, "bgm");
			}
			return true;
		}

		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>();
				foreach (Component val2 in components)
				{
					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;
			}
		}

		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 _serverTracksLoaded;

		private static bool _clientLoadStarted;

		private static bool _loadingLocalSongs;

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

		internal static int TrackCount => _playlist.Count;

		internal static int ServerTrackCount => _bundledTracks.Count;

		internal static int LocalTrackCount => _localTracks.Count;

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

		internal static bool HasServerTracks => _bundledTracks.Count > 0;

		internal static bool IsLoadingLocalSongs => _loadingLocalSongs;

		internal static event Action? OnPlaylistChanged;

		internal static void Init()
		{
			if (!_initialized)
			{
				_initialized = true;
				Plugin.Log.LogDebug("[Jukebox] Deferred song discovery until casino authority or a local casino entry needs it.");
			}
		}

		internal static void EnsureServerTracksLoaded()
		{
			if (!_initialized)
			{
				Init();
			}
			if (_serverTracksLoaded)
			{
				return;
			}
			if ((Object)(object)Plugin.AssetsBundle == (Object)null)
			{
				bool num;
				if (!Plugin.IsHeadlessServer && !BJNetcode.AmHostFresh())
				{
					if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
					{
						return;
					}
					num = Plugin.EnsurePresentationAssetsLoaded();
				}
				else
				{
					num = Plugin.EnsureAuthorityAssetsLoaded();
				}
				if (!num || (Object)(object)Plugin.AssetsBundle == (Object)null)
				{
					return;
				}
			}
			_serverTracksLoaded = true;
			LoadBundledSongs();
			RebuildPlaylist();
			CasinoJukeboxManager.OnPlaylistChanged?.Invoke();
		}

		internal static void EnsureClientTracksLoaded()
		{
			if (Plugin.IsHeadlessServer || !CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
			{
				return;
			}
			EnsureServerTracksLoaded();
			if (_clientLoadStarted)
			{
				return;
			}
			_clientLoadStarted = 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);
				_clientLoadStarted = false;
				return;
			}
			try
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(LoadLocalSongs());
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError("[Jukebox] Failed to start local song loader: " + ex2.Message);
				_clientLoadStarted = false;
			}
		}

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

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

		internal static AudioClip? GetServerClip(int playlistStep)
		{
			return GetTrackFrom(_bundledTracks, playlistStep)?.Clip;
		}

		internal static string GetServerTrackName(int playlistStep)
		{
			JukeboxTrack trackFrom = GetTrackFrom(_bundledTracks, playlistStep);
			if (trackFrom != null)
			{
				return trackFrom.Name;
			}
			return "No server songs loaded";
		}

		internal static int GetServerTrackIndex(int playlistStep)
		{
			return GetTrackIndex(_bundledTracks.Count, playlistStep);
		}

		internal static AudioClip? GetLocalClip(int localIndex)
		{
			return GetTrackFrom(_localTracks, localIndex)?.Clip;
		}

		internal static string GetLocalTrackName(int localIndex)
		{
			JukeboxTrack trackFrom = GetTrackFrom(_localTracks, localIndex);
			if (trackFrom != null)
			{
				return trackFrom.Name;
			}
			return "No local songs loaded";
		}

		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)
		{
			return GetTrackFrom(_playlist, playlistStep);
		}

		private static JukeboxTrack? GetTrackFrom(List<JukeboxTrack> tracks, int playlistStep)
		{
			int trackIndex = GetTrackIndex(tracks.Count, playlistStep);
			if (trackIndex < 0)
			{
				return null;
			}
			return tracks[trackIndex];
		}

		private static int GetTrackIndex(int count, int playlistStep)
		{
			if (count == 0)
			{
				return -1;
			}
			int num = playlistStep % count;
			if (num < 0)
			{
				num += count;
			}
			return 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);
			}
			array = allAssetNames;
			foreach (string assetName2 in array)
			{
				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));
		}

		private static IEnumerator LoadLocalSongs()
		{
			_loadingLocalSongs = true;
			_localTracks.Clear();
			yield return null;
			if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
			{
				CancelLocalSongLoad();
				yield break;
			}
			string[] paths;
			try
			{
				paths = Directory.GetFiles(LocalSongsDirectory);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError("[Jukebox] Failed to list local songs: " + ex.Message);
				_loadingLocalSongs = false;
				_clientLoadStarted = false;
				yield break;
			}
			Array.Sort(paths, (string a, string b) => string.Compare(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase));
			for (int i = 0; i < paths.Length; i++)
			{
				if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
				{
					CancelLocalSongLoad();
					yield break;
				}
				string text = paths[i];
				if (IsSupportedAudioFile(text))
				{
					yield return LoadLocalSong(text, i);
					if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
					{
						CancelLocalSongLoad();
						yield break;
					}
				}
			}
			_loadingLocalSongs = false;
			RebuildPlaylist();
			Plugin.Log.LogInfo($"[Jukebox] Loaded {_localTracks.Count} local song(s). Total playlist: {_playlist.Count}.");
			CasinoJukeboxManager.OnPlaylistChanged?.Invoke();
		}

		private static IEnumerator LoadLocalSong(string filePath, int sortIndex)
		{
			AudioType audioType = GetAudioType(filePath);
			if ((int)audioType == 0)
			{
				Plugin.Log.LogWarning("[Jukebox] Unsupported local song extension: " + filePath);
				yield break;
			}
			string text;
			try
			{
				text = new Uri(filePath).AbsoluteUri;
			}
			catch
			{
				text = filePath;
			}
			UnityWebRequest loader = UnityWebRequestMultimedia.GetAudioClip(text, audioType);
			DownloadHandler downloadHandler = loader.downloadHandler;
			DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
			if (val != null)
			{
				val.streamAudio = CasinoConfig.StreamLocalJukeboxSongsFromDisk;
			}
			UnityWebRequestAsyncOperation operation = loader.SendWebRequest();
			while (!((AsyncOperation)operation).isDone)
			{
				if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino)
				{
					loader.Abort();
					yield break;
				}
				yield return null;
			}
			if ((int)loader.result != 1)
			{
				Plugin.Log.LogWarning("[Jukebox] Failed to load local song '" + filePath + "': " + loader.error);
				yield break;
			}
			AudioClip content = DownloadHandlerAudioClip.GetContent(loader);
			if ((Object)(object)content == (Object)null || (int)content.loadState != 2)
			{
				Plugin.Log.LogWarning("[Jukebox] Local song did not produce a loaded AudioClip: " + filePath);
				yield break;
			}
			((Object)content).name = Path.GetFileNameWithoutExtension(filePath);
			_localTracks.Add(new JukeboxTrack(((Object)content).name, content, isLocal: true, sortIndex));
		}

		private static void CancelLocalSongLoad()
		{
			for (int i = 0; i < _localTracks.Count; i++)
			{
				AudioClip clip = _localTracks[i].Clip;
				if ((Object)(object)clip != (Object)null)
				{
					Object.Destroy((Object)(object)clip);
				}
			}
			_localTracks.Clear();
			_loadingLocalSongs = false;
			_clientLoadStarted = false;
			RebuildPlaylist();
			Plugin.Log.LogDebug("[Jukebox] Cancelled local song loading after leaving the casino.");
		}

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

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

		private static AudioType GetAudioType(string path)
		{
			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 bool _wasInCasino;

		private int _playlistStep = -1;

		private float _trackStartedAt;

		private float _appliedVolume = -1f;

		internal static void Init()
		{
		}

		internal static void Play()
		{
			if (!Plugin.IsHeadlessServer)
			{
				if (!Plugin.IsLocalPlayerConfirmedInCasino())
				{
					ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true);
				}
				else
				{
					GetOrCreate().PlayInternal();
				}
			}
		}

		internal static void Forward()
		{
			if (!Plugin.IsHeadlessServer)
			{
				if (!Plugin.IsLocalPlayerConfirmedInCasino())
				{
					ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true);
				}
				else
				{
					GetOrCreate().AdvanceInternal(1, showMessage: true);
				}
			}
		}

		internal static void Previous()
		{
			if (!Plugin.IsHeadlessServer)
			{
				if (!Plugin.IsLocalPlayerConfirmedInCasino())
				{
					ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true);
				}
				else
				{
					GetOrCreate().AdvanceInternal(-1, showMessage: true);
				}
			}
		}

		internal static void StopPlayback()
		{
			if (!Plugin.IsHeadlessServer)
			{
				if ((Object)(object)_instance == (Object)null)
				{
					ShowCommandMessage("Stopped personal jukebox.");
				}
				else
				{
					_instance.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()
		{
			//IL_0012: 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_0027: Expected O, but got Unknown
			if ((Object)(object)_instance == (Object)null)
			{
				GameObject val = new GameObject("AtlyssCasino_PersonalJukebox");
				_instance = val.AddComponent<CasinoJukeboxPersonalPlayer>();
				Object.DontDestroyOnLoad((Object)val);
				CasinoJukeboxManager.OnPlaylistChanged += _instance.OnPlaylistChanged;
			}
			return _instance;
		}

		private void Update()
		{
			if (!Plugin.IsLocalPlayerConfirmedInCasino())
			{
				if (_playing)
				{
					StopInternal(showMessage: false);
				}
				_wasInCasino = false;
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			if (!_wasInCasino)
			{
				_wasInCasino = true;
				CasinoJukeboxManager.EnsureClientTracksLoaded();
			}
			ApplyVolumeIfChanged();
			if (!CasinoConfig.JukeboxEnabled)
			{
				StopInternal(showMessage: false);
			}
			else if (_playing && !((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null))
			{
				float length = _source.clip.length;
				if (length > 0.1f && Time.time - _trackStartedAt >= length - 0.05f)
				{
					AdvanceInternal(1, showMessage: false);
				}
			}
		}

		private void PlayInternal()
		{
			if (!Plugin.IsLocalPlayerConfirmedInCasino())
			{
				StopInternal(showMessage: false);
				ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true);
				return;
			}
			CasinoJukeboxManager.EnsureClientTracksLoaded();
			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 (!Plugin.IsLocalPlayerConfirmedInCasino())
			{
				StopInternal(showMessage: false);
				if (showMessage)
				{
					ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true);
				}
				return;
			}
			CasinoJukeboxManager.EnsureClientTracksLoaded();
			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 (!Plugin.IsLocalPlayerConfirmedInCasino())
			{
				StopInternal(showMessage: false);
				return;
			}
			if ((Object)(object)_source == (Object)null)
			{
				_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			}
			CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false);
			_appliedVolume = -1f;
			ApplyVolumeIfChanged();
			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 && Plugin.IsLocalPlayerConfirmedInCasino())
			{
				PlayStep(_playlistStep, showMessage: false);
			}
			else if (_playing)
			{
				StopInternal(showMessage: false);
			}
		}

		private void ApplyVolumeIfChanged()
		{
			if (!((Object)(object)_source == (Object)null))
			{
				float effectivePersonalJukeboxVolume = CasinoConfig.EffectivePersonalJukeboxVolume;
				if (!Mathf.Approximately(_appliedVolume, effectivePersonalJukeboxVolume))
				{
					_source.volume = effectivePersonalJukeboxVolume;
					_appliedVolume = effectivePersonalJukeboxVolume;
				}
			}
		}

		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 BoxCollider Collider { get; private set; }

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

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

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

		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_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: 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_0031: 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_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				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, string mapScopeId, string zoneId)
		{
			Collider = box;
			RoomName = RoomZoneRegistry.SanitizeRoomLabel(roomName);
			ZoneId = zoneId ?? string.Empty;
			MapScopeId = mapScopeId ?? string.Empty;
			SceneName = ((Scene)(ref scene)).name ?? string.Empty;
			SceneHandle = ((Scene)(ref scene)).handle;
			MapInstance = mapInstance;
			((Collider)Collider).isTrigger = true;
			RoomZoneRegistry.Register(this);
		}

		private void OnTriggerEnter(Collider other)
		{
			RoomZoneRegistry.TrackPlayer(other);
		}

		private void OnTriggerStay(Collider other)
		{
			RoomZoneRegistry.TrackPlayer(other);
		}

		private void OnTriggerExit(Collider other)
		{
			RoomZoneRegistry.ForgetTrackedCollider(other);
		}

		private void OnDestroy()
		{
			RoomZoneRegistry.Unregister(this);
		}
	}
	internal static class RoomZoneRegistry
	{
		private enum DeliveryDisplayResult
		{
			Unresolved,
			Displayed,
			Rejected
		}

		private const string Prefix = "RoomZone";

		private const string ExpectedPrivateRoomName = "Office";

		private const int DeliveryResolveAttempts = 12;

		private const float DeliveryResolveDelaySeconds = 0.1f;

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

		private static readonly Dictionary<ulong, Player> KnownPlayers = new Dictionary<ulong, Player>();

		private static readonly Dictionary<int, Collider[]> PlayerColliderCache = new Dictionary<int, Collider[]>();

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

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

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

		private static bool _initialized;

		private static FieldInfo? _playerMapInstanceField;

		private static PropertyInfo? _playerMapInstanceProperty;

		private static FieldInfo? _playerMapNameField;

		private static PropertyInfo? _playerMapNameProperty;

		private static bool _playerReflectionResolved;

		private static bool _loggedPlayerReflectionFailure;

		private static MethodInfo? _vanillaReceiveChatMethod;

		private static MethodInfo? _vanillaTargetNoticeMethod;

		private static bool _loggedVanillaReceiveFailure;

		internal static bool IsRoutingHost
		{
			get
			{
				try
				{
					return NetworkServer.active;
				}
				catch
				{
					return Plugin.IsHeadlessServer;
				}
			}
		}

		internal static void Init()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			if (_initialized)
			{
				return;
			}
			_initialized = true;
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.sceneUnloaded += OnSceneUnloaded;
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(i);
				if (IsCasinoScene(sceneAt))
				{
					ScanScene(sceneAt);
					ScheduleSceneScan(sceneAt);
				}
			}
			Plugin.Log.LogInfo("[RoomZone] Event-driven 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 TrackPlayer(Collider collider)
		{
			if ((Object)(object)collider == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)collider).GetInstanceID();
			if (!TrackedTriggerColliderIds.Add(instanceID) || !TryGetPlayer(collider, out Player player))
			{
				return;
			}
			ulong steam = GetSteam64(player);
			if (steam == 0L)
			{
				TrackedTriggerColliderIds.Remove(instanceID);
				return;
			}
			Player value;
			bool num = !KnownPlayers.TryGetValue(steam, out value) || value != player;
			KnownPlayers[steam] = player;
			if (num && player == Player._mainPlayer)
			{
				RoomZoneChatNetcode.NotifyLocalPlayerReady(player);
			}
		}

		internal static void ForgetTrackedCollider(Collider collider)
		{
			if (!((Object)(object)collider == (Object)null))
			{
				TrackedTriggerColliderIds.Remove(((Object)collider).GetInstanceID());
			}
		}

		internal static bool TryRouteValidatedZoneChat(ChatBehaviour chat, string message, bool sentByServer, ChatChannel channel)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			if ((int)channel != 2)
			{
				return false;
			}
			if (sentByServer)
			{
				return false;
			}
			if ((Object)(object)chat == (Object)null || 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;
			}
			RoomZone roomZone = FindContainingZone(player, refreshColliders: true);
			if ((Object)(object)roomZone == (Object)null)
			{
				return false;
			}
			ulong steam = GetSteam64(player);
			if (steam == 0L)
			{
				NotifyRoutingFailure(player, 0uL, roomZone, "Your message stayed private, but your player identity was unavailable.");
				return true;
			}
			KnownPlayers[steam] = player;
			if (!RoomZoneChatNetcode.IsPrivateOutgoingEnabled(steam))
			{
				return false;
			}
			try
			{
				RoutePrivateMessage(player, steam, roomZone, message);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError($"[RoomZone] Private routing failed closed for {steam}: {arg}");
				NotifyRoutingFailure(player, steam, roomZone, "Your message stayed private, but it could not be delivered.");
			}
			return true;
		}

		internal static bool ShouldFailClosed(ChatBehaviour chat, string message, bool sentByServer, ChatChannel channel)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			if ((int)channel != 2)
			{
				return false;
			}
			if (sentByServer || (Object)(object)chat == (Object)null)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(message) || IsSingleSlashCommand(message))
			{
				return false;
			}
			if (!IsRoutingHost)
			{
				return false;
			}
			Player player = GetPlayer(chat);
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			RoomZone roomZone = FindContainingZone(player, refreshColliders: true);
			if ((Object)(object)roomZone == (Object)null)
			{
				return false;
			}
			ulong steam = GetSteam64(player);
			if (steam != 0L && !RoomZoneChatNetcode.IsPrivateOutgoingEnabled(steam))
			{
				return false;
			}
			NotifyRoutingFailure(player, steam, roomZone, "Your message stayed private, but it could not be delivered.");
			return true;
		}

		private static void RoutePrivateMessage(Player sender, ulong senderSteam64, RoomZone senderZone, string rawMessage)
		{
			List<Player> playerCandidates = GetPlayerCandidates(sender);
			int num = 0;
			int num2 = 0;
			foreach (Player item in playerCandidates)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ulong steam = GetSteam64(item);
				if (steam == 0L)
				{
					continue;
				}
				RoomZone roomZone = FindContainingZone(item, item == sender);
				if (!((Object)(object)roomZone == (Object)null) && string.Equals(roomZone.ZoneId, senderZone.ZoneId, StringComparison.Ordinal))
				{
					num++;
					if (RoomZoneChatNetcode.SendRoomChatDelivery(steam, senderSteam64, rawMessage, senderZone.RoomName, senderZone.MapScopeId, senderZone.ZoneId))
					{
						num2++;
					}
				}
			}
			Plugin.Log.LogDebug($"[RoomZone] Private Zone chat sender={senderSteam64} " + $"zone='{senderZone.ZoneId}' eligible={num} queued={num2}.");
			if (num == 0 || num2 < num)
			{
				NotifyRoutingFailure(sender, senderSteam64, senderZone, (num == 0) ? "Your message stayed private, but no room recipients could be resolved." : "Your message stayed private, but delivery failed for one or more room players.");
			}
		}

		private static void NotifyRoutingFailure(Player sender, ulong senderSteam64, RoomZone zone, string message)
		{
			if ((senderSteam64 == 0L || !RoomZoneChatNetcode.SendPrivateNotice(senderSteam64, message, zone.MapScopeId, zone.ZoneId)) && !TrySendVanillaTargetNotice(sender, message) && sender == Player._mainPlayer)
			{
				DisplayLocalNotice(message);
			}
		}

		private static bool TrySendVanillaTargetNotice(Player sender, string message)
		{
			if (!IsRoutingHost || (Object)(object)sender == (Object)null)
			{
				return false;
			}
			ChatBehaviour chatBehaviour = GetChatBehaviour(sender);
			if ((Object)(object)chatBehaviour == (Object)null)
			{
				return false;
			}
			if ((object)_vanillaTargetNoticeMethod == null)
			{
				_vanillaTargetNoticeMethod = typeof(ChatBehaviour).GetMethod("Target_RecieveMessage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
			}
			if (_vanillaTargetNoticeMethod == null)
			{
				return false;
			}
			try
			{
				_vanillaTargetNoticeMethod.Invoke(chatBehaviour, new object[1] { "[RoomZone] " + SanitizeNotice(message) });
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static void ReceiveRoomChatDelivery(RoomZoneChatDelivery delivery)
		{
			if (delivery != null && delivery.SenderSteam64 != 0L && !string.IsNullOrWhiteSpace(delivery.RawMessage) && !string.IsNullOrWhiteSpace(delivery.ZoneId))
			{
				if ((Object)(object)Plugin.Instance == (Object)null)
				{
					TryDisplayDelivery(delivery, allowUnresolved: false);
				}
				else
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(ResolveAndDisplayDelivery(delivery));
				}
			}
		}

		private static IEnumerator ResolveAndDisplayDelivery(RoomZoneChatDelivery delivery)
		{
			for (int attempt = 0; attempt < 12; attempt++)
			{
				DeliveryDisplayResult deliveryDisplayResult = TryDisplayDelivery(delivery, allowUnresolved: true);
				if (deliveryDisplayResult == DeliveryDisplayResult.Displayed || deliveryDisplayResult == DeliveryDisplayResult.Rejected)
				{
					yield break;
				}
				yield return (object)new WaitForSeconds(0.1f);
			}
			Plugin.Log.LogDebug("[RoomZone] Dropped unresolved private delivery " + $"sender={delivery.SenderSteam64} zone='{delivery.ZoneId}'.");
		}

		private static DeliveryDisplayResult TryDisplayDelivery(RoomZoneChatDelivery delivery, bool allowUnresolved)
		{
			Player mainPlayer = Player._mainPlayer;
			if ((Object)(object)mainPlayer == (Object)null)
			{
				if (!allowUnresolved)
				{
					return DeliveryDisplayResult.Rejected;
				}
				return DeliveryDisplayResult.Unresolved;
			}
			RoomZone roomZone = FindContainingZone(mainPlayer, refreshColliders: true);
			if ((Object)(object)roomZone == (Object)null)
			{
				if (!allowUnresolved || HasZoneId(delivery.ZoneId))
				{
					return DeliveryDisplayResult.Rejected;
				}
				return DeliveryDisplayResult.Unresolved;
			}
			if (!string.Equals(roomZone.MapScopeId, delivery.MapScopeId ?? string.Empty, StringComparison.Ordinal) || !string.Equals(roomZone.ZoneId, delivery.ZoneId ?? string.Empty, StringComparison.Ordinal))
			{
				return DeliveryDisplayResult.Rejected;
			}
			Player val = FindPlayerBySteam64(delivery.SenderSteam64);
			if ((Object)(object)val == (Object)null)
			{
				return DeliveryDisplayResult.Unresolved;
			}
			ChatBehaviour chatBehaviour = GetChatBehaviour(val);
			if ((Object)(object)chatBehaviour == (Object)null)
			{
				return DeliveryDisplayResult.Unresolved;
			}
			MethodInfo vanillaReceiveChatMethod = GetVanillaReceiveChatMethod();
			if (vanillaReceiveChatMethod == null)
			{
				return DeliveryDisplayResult.Rejected;
			}
			string text = SanitizeRoomLabel(delivery.RoomName);
			string text2 = "[" + text + "] " + delivery.RawMessage;
			try
			{
				vanillaReceiveChatMethod.Invoke(chatBehaviour, new object[3]
				{
					text2,
					false,
					(object)(ChatChannel)2
				});
				return DeliveryDisplayResult.Displayed;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[RoomZone] Vanilla private receive failed: " + ex.Message);
				return DeliveryDisplayResult.Rejected;
			}
		}

		internal static void ReceivePrivateNotice(RoomZoneChatNotice notice)
		{
			if (notice == null || string.IsNullOrWhiteSpace(notice.Message))
			{
				return;
			}
			Player mainPlayer = Player._mainPlayer;
			if (!((Object)(object)mainPlayer == (Object)null))
			{
				RoomZone roomZone = FindContainingZone(mainPlayer, refreshColliders: true);
				if (!((Object)(object)roomZone == (Object)null) && string.Equals(roomZone.MapScopeId, notice.MapScopeId, StringComparison.Ordinal) && string.Equals(roomZone.ZoneId, notice.ZoneId, StringComparison.Ordinal))
				{
					DisplayLocalNotice(notice.Message);
				}
			}
		}

		private static void DisplayLocalNotice(string message)
		{
			Player mainPlayer = Player._mainPlayer;
			ChatBehaviour val = (((Object)(object)mainPlayer == (Object)null) ? null : GetChatBehaviour(mainPlayer));
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				val.New_ChatMessage("<color=#FFB366>[RoomZone]</color> " + SanitizeNotice(message));
			}
			catch
			{
			}
		}

		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, null, new Type[3]
			{
				typeof(string),
				typeof(bool),
				typeof(ChatChannel)
			}, null);
			if (_vanillaReceiveChatMethod == null && !_loggedVanillaReceiveFailure)
			{
				_loggedVanillaReceiveFailure = true;
				Plugin.Log.LogError("[RoomZone] Could not resolve vanilla UserCode_Rpc_RecieveChatMessage. Private delivery will fail closed.");
			}
			return _vanillaReceiveChatMethod;
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			RefreshScene(scene);
		}

		internal static void RefreshScene(Scene scene)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (IsCasinoScene(scene))
			{
				ScanScene(scene);
				ScheduleSceneScan(scene);
			}
		}

		internal static int CountRegisteredZones(Scene scene)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!IsCasinoScene(scene))
			{
				return 0;
			}
			Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null);
			int num = 0;
			for (int num2 = 0; num2 < Zones.Count; num2++)
			{
				RoomZone roomZone = Zones[num2];
				if ((Object)(object)roomZone != (Object)null && roomZone.SceneHandle == ((Scene)(ref scene)).handle)
				{
					num++;
				}
			}
			return num;
		}

		internal static bool HasExpectedPrivateRoom(Scene scene)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!IsCasinoScene(scene))
			{
				return false;
			}
			Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null);
			for (int num = 0; num < Zones.Count; num++)
			{
				RoomZone roomZone = Zones[num];
				if (!((Object)(object)roomZone == (Object)null) && roomZone.SceneHandle == ((Scene)(ref scene)).handle && !((Object)(object)roomZone.Collider == (Object)null) && ((Collider)roomZone.Collider).enabled && ((Component)roomZone).gameObject.activeInHierarchy && string.Equals(roomZone.RoomName, "Office", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static void ScheduleSceneScan(Scene scene)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || !PendingSceneScans.Add(((Scene)(ref scene)).handle))
			{
				return;
			}
			if ((Object)(object)Plugin.Instance == (Object)null)
			{
				PendingSceneScans.Remove(((Scene)(ref scene)).handle);
				return;
			}
			try
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(CoalescedSceneScan(scene));
			}
			catch (Exception ex)
			{
				PendingSceneScans.Remove(((Scene)(ref scene)).handle);
				Plugin.Log.LogWarning("[RoomZone] Could not start scene discovery; a later refresh can retry: " + ex.Message);
			}
		}

		private static IEnumerator CoalescedSceneScan(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)
			float retryDelay = 0.5f;
			try
			{
				yield return null;
				yield return null;
				while (IsCasinoScene(scene))
				{
					ScanScene(scene);
					if (!HasExpectedPrivateRoom(scene))
					{
						yield return (object)new WaitForSecondsRealtime(retryDelay);
						retryDelay = Mathf.Min(retryDelay * 2f, 30f);
						continue;
					}
					break;
				}
			}
			finally
			{
				PendingSceneScans.Remove(((Scene)(ref scene)).handle);
			}
		}

		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)
			if (string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal))
			{
				Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null || zone.SceneHandle == ((Scene)(ref scene)).handle);
				PendingSceneScans.Remove(((Scene)(ref scene)).handle);
				PlayerColliderCache.Clear();
				TrackedTriggerColliderIds.Clear();
			}
		}

		private static void ScanScene(Scene scene)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			if (!IsCasinoScene(scene))
			{
				return;
			}
			int num = 0;
			GameObject[] rootGameObjects;
			try
			{
				rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning("[RoomZone] Could not enumerate scene roots; discovery will retry: " + ex.Message);
				return;
			}
			GameObject[] array = rootGameObjects;
			foreach (GameObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				BoxCollider[] componentsInChildren;
				try
				{
					componentsInChildren = val.GetComponentsInChildren<BoxCollider>(true);
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning("[RoomZone] Could not scan root '" + ((Object)val).name + "'; discovery will continue: " + ex2.Message);
					continue;
				}
				BoxCollider[] array2 = componentsInChildren;
				foreach (BoxCollider val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					try
					{
						bool flag = StartsWithRoomZone(CleanUnityName(((Object)((Component)val2).gameObject).name));
						if ((((Collider)val2).isTrigger || flag) && IsRoomZoneCandidate(val2))
						{
							Component mapInstance = FindNearestMapInstance(((Component)val2).transform);
							string mapScopeId = BuildMapScopeId(scene, mapInstance);
							string zoneId = BuildZoneId(val2, scene, mapInstance, mapScopeId);
							RoomZone? roomZone = FindRoomZoneComponent(val2);
							RoomZone roomZone2 = roomZone ?? ((Component)val2).gameObject.AddComponent<RoomZone>();
							roomZone2.Setup(val2, ResolveRoomName(val2), scene, mapInstance, mapScopeId, zoneId);
							if ((Object)(object)roomZone == (Object)null)
							{
								num++;
								Plugin.Log.LogInfo("[RoomZone] Hooked room='" + roomZone2.RoomName + "' zone='" + roomZone2.ZoneId + "' scene='" + roomZone2.SceneName + "'.");
							}
						}
					}
					catch (Exception ex3)
					{
						Plugin.Log.LogWarning("[RoomZone] Collider '" + ((Object)val2).name + "' scan failed; other zones and later retries remain active: " + ex3.Message);
					}
				}
			}
			if (num > 0)
			{
				Plugin.Log.LogInfo($"[RoomZone] Hooked {num} collider zone(s) " + "in scene '" + ((Scene)(ref scene)).name + "'.");
			}
		}

		private static bool IsCasinoScene(Scene scene)
		{
			if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded)
			{
				return string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal);
			}
			return false;
		}

		private static RoomZone? FindRoomZoneComponent(BoxCollider box)
		{
			RoomZone[] components = ((Component)box).gameObject.GetComponents<RoomZone>();
			RoomZone[] array = components;
			foreach (RoomZone roomZone in array)
			{
				if ((Object)(object)roomZone != (Object)null && roomZone.Collider == box)
				{
					return roomZone;
				}
			}
			array = components;
			foreach (RoomZone roomZone2 in array)
			{
				if ((Object)(object)roomZone2 != (Object)null && (Object)(object)roomZone2.Collider == (Object)null)
				{
					return roomZone2;
				}
			}
			return null;
		}

		private static Component? FindNearestMapInstance(Transform transform)
		{
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				Component[] components = ((Component)val).GetComponents<Component>();
				foreach (Component val2 in components)
				{
					if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2).GetType().Name, "MapInstance", StringComparison.Ordinal))
					{
						return val2;
					}
				}
				val = val.parent;
			}
			return null;
		}

		private static string BuildMapScopeId(Scene scene, Component? mapInstance)
		{
			string text = NormalizeIdPart(((Scene)(ref scene)).name);
			if ((Object)(object)mapInstance == (Object)null)
			{
				return "scene:" + text;
			}
			try
			{
				NetworkIdentity val = mapInstance.GetComponent<NetworkIdentity>() ?? mapInstance.GetComponentInParent<NetworkIdentity>();
				if ((Object)(object)val != (Object)null && val.netId != 0)
				{
					return $"scene:{text}|mapnet:{val.netId}";
				}
			}
			catch
			{
			}
			return "scene:" + text + "|map:" + BuildPathToSceneRoot(mapInstance.transform);
		}

		private static string BuildZoneId(BoxCollider box, Scene scene, Component? mapInstance, string mapScopeId)
		{
			Transform stopExclusive = (((Object)(object)mapInstance == (Object)null) ? null : mapInstance.transform);
			string arg = BuildRelativePath(((Component)box).transform, stopExclusive);
			BoxCollider[] components = ((Component)box).gameObject.GetComponents<BoxCollider>();
			int num = 0;
			for (int i = 0; i < components.Length; i++)
			{
				if (components[i] == box)
				{
					num = i;
					break;
				}
			}
			return $"{mapScopeId}|zone:{arg}|box:{num}";
		}

		private static string BuildPathToSceneRoot(Transform transform)
		{
			return BuildRelativePath(transform, null);
		}

		private static string BuildRelativePath(Transform transform, Transform? stopExclusive)
		{
			List<string> list = new List<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && val != stopExclusive)
			{
				list.Add($"{NormalizeIdPart(((Object)val).name)}@{val.GetSiblingIndex()}");
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list.ToArray());
		}

		private static string NormalizeIdPart(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "_";
			}
			string text = value.Trim();
			if (text.Length == 0)
			{
				return "_";
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length * 4);
			string text2 = text;
			foreach (char c in text2)
			{
				int num = c;
				stringBuilder.Append(num.ToString("X4"));
			}
			return stringBuilder.ToString();
		}

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

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

		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(' ', '_', '-', ':');
				if (!IsDefaultRoomName(text2))
				{
					return text2;
				}
				return null;
			}
			if (text.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase) && text.Length > "RoomZone".Length)
			{
				string text3 = text.Substring("RoomZone".Length).Trim(' ', '_', '-', ':');
				if (!IsDefaultRoomName(text3))
				{
					return text3;
				}
				return null;
			}
			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);
			if (!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))
			{
				return string.Equals(a, "Trigger", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

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

		internal static string SanitizeRoomLabel(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "Room";
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, 48));
			foreach (char c in value)
			{
				if (stringBuilder.Length >= 48)
				{
					break;
				}
				if (!char.IsControl(c) && c != '<' && c != '>' && c != '[' && c != ']')
				{
					stringBuilder.Append(c);
				}
			}
			string text = stringBuilder.ToString().Trim();
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return "Room";
		}

		private static string SanitizeNotice(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "Private delivery failed.";
			}
			return value.Replace("<", string.Empty).Replace(">", string.Empty).Trim();
		}

		private static RoomZone? FindContainingZone(Player player, bool refreshColliders)
		{
			RoomZone result = null;
			float num = float.MaxValue;
			for (int num2 = Zones.Count - 1; num2 >= 0; num2--)
			{
				RoomZone roomZone = Zones[num2];
				if ((Object)(object)roomZone == (Object)null || (Object)(object)roomZone.Collider == (Object)null)
				{
					Zones.RemoveAt(num2);
				}
				else if (((Collider)roomZone.Collider).enabled && ((Component)roomZone).gameObject.activeInHierarchy && IsPlayerInZoneScope(player, roomZone) && OverlapsZone(player, roomZone, refreshColliders))
				{
					float volume = roomZone.Volume;
					if (volume < num)
					{
						result = roomZone;
						num = volume;
					}
				}
			}
			return result;
		}

		private static bool OverlapsZone(Player player, RoomZone zone, bool refreshColliders)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: 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)
			Collider[] playerColliders = GetPlayerColliders(player, refreshColliders);
			if (ComputeOverlap(zone.Collider, playerColliders))
			{
				return true;
			}
			if (!refreshColliders)
			{
				playerColliders = GetPlayerColliders(player, refresh: true);
				if (ComputeOverlap(zone.Collider, playerColliders))
				{
					return true;
				}
			}
			Vector3 val = ((Component)zone.Collider).transform.InverseTransformPoint(GetPlayerZonePosition(player)) - zone.Collider.center;
			Vector3 val2 = zone.Collider.size * 0.5f;
			if (Mathf.Abs(val.x) <= val2.x && Mathf.Abs(val.y) <= val2.y)
			{
				return Mathf.Abs(val.z) <= val2.z;
			}
			return false;
		}

		private static bool ComputeOverlap(BoxCollider zoneCollider, Collider[] playerColliders)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val2 = default(Vector3);
			float num = default(float);
			foreach (Collider val in playerColliders)
			{
				if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy || (object)val == zoneCollider)
				{
					continue;
				}
				try
				{
					if (Physics.ComputePenetration((Collider)(object)zoneCollider, ((Component)zoneCollider).transform.position, ((Component)zoneCollider).transform.rotation, val, ((Component)val).transform.position, ((Component)val).transform.rotation, ref val2, ref num))
					{
						return true;
					}
				}
				catch
				{
				}
			}
			return false;
		}

		private static Collider[] GetPlayerColliders(Player player, bool refresh)
		{
			int instanceID = ((Object)player).GetInstanceID();
			if (!refresh && PlayerColliderCache.TryGetValue(instanceID, out Collider[] value) && value != null)
			{
				return value;
			}
			Collider[] array;
			try
			{
				array = ((Component)player).GetComponentsInChildren<Collider>(false);
			}
			catch
			{
				array = Array.Empty<Collider>();
			}
			PlayerColliderCache[instanceID] = array;
			return array;
		}

		private static List<Player> GetPlayerCandidates(Player sender)
		{
			List<Player> list = new List<Player>();
			HashSet<int> seen = new HashSet<int>();
			AddCandidate(sender, list, seen);
			foreach (Player value in KnownPlayers.Values)
			{
				AddCandidate(value, list, seen);
			}
			Player[] array = Object.FindObjectsOfType<Player>();
			foreach (Player val in array)
			{
				AddCandidate(val, list, seen);
				ulong steam = GetSteam64(val);
				if (steam != 0L)
				{
					KnownPlayers[steam] = val;
				}
			}
			return list;
		}

		private static void AddCandidate(Player player, List<Player> players, HashSet<int> seen)
		{
			if (!((Object)(object)player == (Object)null) && seen.Add(((Object)player).GetInstanceID()))
			{
				players.Add(player);
			}
		}

		private static bool HasZoneId(string zoneId)
		{
			foreach (RoomZone zone in Zones)
			{
				if ((Object)(object)zone != (Object)null && (Object)(object)zone.Collider != (Object)null && string.Equals(zone.ZoneId, zoneId, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		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 Vector3 GetPlayerZonePosition(Player player)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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[] playerColliders = GetPlayerColliders(player, refresh: false);
				Bounds? val2 = null;
				Collider[] array = playerColliders;
				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 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
			{
				Player componentInParent = ((Component)chat).GetComponentInParent<Player>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					return componentInParent;
				}
			}
			catch
			{
			}
			try
			{
				object? obj3 = typeof(ChatBehaviour).GetField("_player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat);
				return (Player?)((obj3 is Player) ? obj3 : null);
			}
			catch
			{
				return null;
			}
		}

		private static ChatBehaviour? GetChatBehaviour(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			try
			{
				return ((Component)player).GetComponent<ChatBehaviour>() ?? ((Component)player).GetComponentInChildren<ChatBehaviour>();
			}
			catch
			{
				return null;
			}
		}

		private static Player? FindPlayerBySteam64(ulong steam64)
		{
			if (steam64 == 0L)
			{
				return null;
			}
			if (KnownPlayers.TryGetValue(steam64, out Player value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			try
			{
				Player val = BJNetcode.FindPlayerBySteam64(steam64);
				if ((Object)(object)val != (Object)null)
				{
					KnownPlayers[steam64] = val;
				}
				return val;
			}
			catch
			{
				return null;
			}
		}

		private static bool IsPlayerInZoneScope(Player player, RoomZone zone)
		{
			//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)
			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) && !strin