Decompiled source of AtlyssCasino v1.10.1
AtlyssCasino.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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