Decompiled source of AtlyssCasino v1.9.0
AtlyssCasino.dll
Decompiled a week 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.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using AtlyssCasino.Blackjack; using AtlyssCasino.Blackjack.Netcode; using AtlyssCasino.Jukebox.Netcode; using AtlyssCasino.RoomZoneChat.Netcode; using AtlyssCasino.Roulette; using AtlyssCasino.Roulette.Netcode; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CodeTalker; using CodeTalker.Networking; using CodeTalker.Packets; using HarmonyLib; using Microsoft.CodeAnalysis; using Nessie.ATLYSS.EasySettings; using Nessie.ATLYSS.EasySettings.UIElements; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AtlyssCasino")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.9.0.0")] [assembly: AssemblyInformationalVersion("1.9.0")] [assembly: AssemblyProduct("AtlyssCasino")] [assembly: AssemblyTitle("AtlyssCasino")] [assembly: AssemblyVersion("1.9.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AtlyssCasino { public static class BlackjackSceneWatcher { [CompilerGenerated] private sealed class <DeferredCleanupCoroutine>d__12 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private Exception <ex>5__1; private Exception <ex>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DeferredCleanupCoroutine>d__12(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <ex>5__1 = null; <ex>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; try { CleanupLeakedObjects("deferred-1frame"); } catch (Exception ex) { <ex>5__1 = ex; Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 1 failed: " + <ex>5__1.Message); } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; case 2: <>1__state = -1; try { CleanupLeakedObjects("deferred-1sec"); } catch (Exception ex) { <ex>5__2 = ex; Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 2 failed: " + <ex>5__2.Message); } _deferredCleanupCoroutine = null; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string CASINO_SCENE_NAME = "AtlyssCasino"; private const float DEFERRED_CLEANUP_DELAY_SEC = 1f; private static readonly string[] LEAK_NAME_PREFIXES = new string[3] { "BJCard_", "SlotMachine_", "DebugCard_" }; private static readonly string[] LEAK_NAME_EXACT = new string[1] { "Pokies Machine" }; private static readonly string[] LEAK_NAME_STARTSWITH = new string[2] { "Blackjack Table", "Roulette Table" }; private static bool _wasInCasino = false; private static Coroutine? _deferredCleanupCoroutine; public static void Init() { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; Plugin.Log.LogInfo("[SceneWatcher] Initialized."); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "AtlyssCasino") { _wasInCasino = true; Plugin.Log.LogInfo("[SceneWatcher] Entered casino scene."); } else if (_wasInCasino) { Plugin.Log.LogInfo("[SceneWatcher] Non-casino scene '" + ((Scene)(ref scene)).name + "' loaded after casino — scheduling cleanup sweeps."); ScheduleDeferredCleanup(); _wasInCasino = false; } } private static void OnSceneUnloaded(Scene scene) { if (((Scene)(ref scene)).name == "AtlyssCasino" && _wasInCasino) { Plugin.Log.LogInfo("[SceneWatcher] Casino scene unloaded — running immediate cleanup."); CleanupOnLeave(); ScheduleDeferredCleanup(); _wasInCasino = false; } } private static void OnActiveSceneChanged(Scene oldScene, Scene newScene) { if (_wasInCasino && ((Scene)(ref oldScene)).name == "AtlyssCasino" && ((Scene)(ref newScene)).name != "AtlyssCasino") { Plugin.Log.LogInfo("[SceneWatcher] Active scene changed away from casino (" + ((Scene)(ref oldScene)).name + " -> " + ((Scene)(ref newScene)).name + ") — running cleanup."); CleanupOnLeave(); ScheduleDeferredCleanup(); _wasInCasino = false; } } private static void ScheduleDeferredCleanup() { if (_deferredCleanupCoroutine != null) { return; } try { _deferredCleanupCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(DeferredCleanupCoroutine()); } catch (Exception ex) { Plugin.Log.LogError("[SceneWatcher] Failed to start deferred cleanup: " + ex.Message); } } [IteratorStateMachine(typeof(<DeferredCleanupCoroutine>d__12))] private static IEnumerator DeferredCleanupCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DeferredCleanupCoroutine>d__12(0); } private static void CleanupOnLeave() { try { ReleaseAnySeatedTable(); ReleaseAnyRouletteTable(); CleanupLeakedObjects("immediate"); Plugin.HasSetBlackjackBet = false; Plugin.HasSetRouletteBet = false; } catch (Exception ex) { Plugin.Log.LogError("[SceneWatcher] Cleanup failed: " + ex.Message); } } private static void ReleaseAnySeatedTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { Plugin.Log.LogInfo("[SceneWatcher] No local player — skipping seat release."); return; } BlackjackTable[] array = Object.FindObjectsOfType<BlackjackTable>(); BlackjackTable[] array2 = array; foreach (BlackjackTable blackjackTable in array2) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { string name = ((Object)blackjackTable).name; if (BJNetcode.AmHost()) { blackjackTable.ReleaseSeat(seatForPlayer); int hostSeatIndex = blackjackTable.HostSeatIndex; BJNetcode.BroadcastSeatReleased(name, seatForPlayer, hostSeatIndex); Plugin.Log.LogInfo($"[SceneWatcher] Auto-released host seat {seatForPlayer} at '{name}'."); } else { BJNetcode.SendReleaseSeatRequest(name); blackjackTable.ApplySeatReleased(seatForPlayer, blackjackTable.HostSeatIndex); Plugin.Log.LogInfo($"[SceneWatcher] Auto-released client seat {seatForPlayer} at '{name}' " + "(request sent + local mirror)."); } } } } private static void ReleaseAnyRouletteTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable[] array = Object.FindObjectsOfType<RouletteTable>(); ulong localSteam = BJNetcode.GetLocalSteam64(); RouletteTable[] array2 = array; foreach (RouletteTable rouletteTable in array2) { if (rouletteTable.IsPlayerAtTable(mainPlayer)) { string name = ((Object)rouletteTable).name; if (BJNetcode.AmHost()) { rouletteTable.Leave(mainPlayer); ulong newHostSteam = ComputeNewRouletteHostAfterLeave(rouletteTable, localSteam); RNNetcode.BroadcastPlayerLeft(name, localSteam, newHostSteam, rouletteTable.PlayerCount); Plugin.Log.LogInfo("[SceneWatcher] Auto-released host roulette seat at '" + name + "'."); } else { RNNetcode.SendLeaveTableRequest(name); rouletteTable.ApplyPlayerLeftBySteam64(localSteam, 0uL); Plugin.Log.LogInfo("[SceneWatcher] Auto-released client roulette seat at '" + name + "' (request sent + local mirror)."); } } } } private static ulong ComputeNewRouletteHostAfterLeave(RouletteTable table, ulong leftSteam64) { if (table.PlayerCount == 0) { return 0uL; } return 0uL; } private static void CleanupLeakedObjects(string passLabel) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "AtlyssCasino") { Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup skipped: active scene is back in the casino."); return; } HashSet<GameObject> hashSet = new HashSet<GameObject>(); CardVisual[] array = Object.FindObjectsOfType<CardVisual>(); CardVisual[] array2 = array; foreach (CardVisual cardVisual in array2) { if (!((Object)(object)cardVisual == (Object)null) && !((Object)(object)((Component)cardVisual).gameObject == (Object)null) && !IsInCasinoScene(((Component)cardVisual).gameObject)) { hashSet.Add(((Component)cardVisual).gameObject); } } for (int j = 0; j < SceneManager.sceneCount; j++) { Scene sceneAt = SceneManager.GetSceneAt(j); if (((Scene)(ref sceneAt)).IsValid() && ((Scene)(ref sceneAt)).isLoaded && !(((Scene)(ref sceneAt)).name == "AtlyssCasino")) { GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { CollectLeakedDescendants(val.transform, hashSet); } } } try { GameObject val2 = new GameObject("__casino_ddol_probe"); Object.DontDestroyOnLoad((Object)(object)val2); Scene scene = val2.scene; if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { GameObject[] rootGameObjects2 = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val3 in rootGameObjects2) { if (!((Object)(object)val3 == (Object)(object)val2)) { CollectLeakedDescendants(val3.transform, hashSet); } } } Object.Destroy((Object)(object)val2); } catch (Exception ex) { Plugin.Log.LogWarning("[SceneWatcher] DDOL scan failed: " + ex.Message); } int num = 0; foreach (GameObject item in hashSet) { if ((Object)(object)item == (Object)null) { continue; } try { string name = ((Object)item).name; Scene scene2 = item.scene; object obj; if (!((Scene)(ref scene2)).IsValid()) { obj = "<no scene>"; } else { scene2 = item.scene; obj = ((Scene)(ref scene2)).name; } string text = (string)obj; Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " destroying leaked '" + name + "' (scene: " + text + ")."); Object.Destroy((Object)(object)item); num++; } catch (Exception ex2) { Plugin.Log.LogWarning("[SceneWatcher] Failed to destroy '" + ((Object)item).name + "': " + ex2.Message); } } if (num > 0) { Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup destroyed " + $"{num} leaked casino object(s)."); } else { Plugin.Log.LogInfo("[SceneWatcher] " + passLabel + " cleanup found nothing leaked."); } } private static void CollectLeakedDescendants(Transform t, HashSet<GameObject> acc) { if ((Object)(object)t == (Object)null) { return; } string name = ((Object)t).name; if (NameMatchesLeakPattern(name)) { acc.Add(((Component)t).gameObject); return; } for (int i = 0; i < t.childCount; i++) { CollectLeakedDescendants(t.GetChild(i), acc); } } private static bool NameMatchesLeakPattern(string name) { if (string.IsNullOrEmpty(name)) { return false; } string[] lEAK_NAME_PREFIXES = LEAK_NAME_PREFIXES; foreach (string value in lEAK_NAME_PREFIXES) { if (name.StartsWith(value)) { return true; } } string[] lEAK_NAME_EXACT = LEAK_NAME_EXACT; foreach (string text in lEAK_NAME_EXACT) { if (name == text) { return true; } } string[] lEAK_NAME_STARTSWITH = LEAK_NAME_STARTSWITH; foreach (string value2 in lEAK_NAME_STARTSWITH) { if (name.StartsWith(value2)) { return true; } } return false; } private static bool IsInCasinoScene(GameObject go) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { return false; } Scene scene = go.scene; int result; if (((Scene)(ref scene)).IsValid()) { scene = go.scene; result = ((((Scene)(ref scene)).name == "AtlyssCasino") ? 1 : 0); } else { result = 0; } return (byte)result != 0; } } public sealed class CasinoJukebox : MonoBehaviour { private const float FALLBACK_RADIUS = 3f; private const float INPUT_COOLDOWN = 0.5f; private const float RESTART_DEBOUNCE_SEC = 1.25f; private const float RESYNC_TIME_THRESHOLD_SEC = 1.5f; private AudioSource? _source; private BoxCollider? _collider; private AudioClip? _appliedClip; private bool _setup; private bool _subscribed; private bool _playerNearby; private bool _isPlaying; private bool _appliedPlaying; private bool _autoStartRequested; private int _playlistStep; private int _appliedPlaylistStep = int.MinValue; private float _trackStartedAt; private float _nextInputTime; private float _nextRestartAttemptTime; private string _objectName = "CasinoJukebox"; internal string ObjectName => _objectName; internal int PlaylistStep => _playlistStep; internal bool IsPlaying => _isPlaying; internal bool IsLocallyAudible => IsWorldAudioAllowedLocally() && _isPlaying && (Object)(object)_source != (Object)null && _source.isPlaying; internal float ElapsedSeconds => _isPlaying ? Mathf.Max(0f, Time.time - _trackStartedAt) : 0f; public void Setup() { _objectName = ((Object)((Component)this).gameObject).name; _source = ((Component)this).GetComponentInChildren<AudioSource>(true) ?? ((Component)this).gameObject.AddComponent<AudioSource>(); CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true); _source.volume = CasinoConfig.EffectiveJukeboxVolume; _collider = ((Component)this).GetComponent<BoxCollider>() ?? ((Component)this).GetComponentInChildren<BoxCollider>(true); if (!_subscribed) { CasinoJukeboxManager.OnPlaylistChanged += OnPlaylistChanged; _subscribed = true; } _setup = true; if (JukeboxNetcode.TryGetCachedState(_objectName, out var state)) { ApplyRemoteState(state.PlaylistStep, state.Playing, state.ElapsedSeconds); } else if (!Plugin.IsHeadlessServer && !BJNetcode.AmHost()) { JukeboxNetcode.SendStateRequest(_objectName); } } internal static void AutoStartAll() { CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>(); CasinoJukebox[] array2 = array; foreach (CasinoJukebox casinoJukebox in array2) { casinoJukebox.RequestAutoStart(); } } internal static CasinoJukebox? FindByName(string objectName) { CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>(); CasinoJukebox[] array2 = array; foreach (CasinoJukebox casinoJukebox in array2) { if (casinoJukebox.ObjectName == objectName) { return casinoJukebox; } } return null; } internal static bool IsAnyWorldJukeboxPlaying() { CasinoJukebox[] array = Object.FindObjectsOfType<CasinoJukebox>(); CasinoJukebox[] array2 = array; foreach (CasinoJukebox casinoJukebox in array2) { if (casinoJukebox.IsLocallyAudible) { return true; } } return false; } internal JukeboxWorldState BuildState() { JukeboxWorldState result = default(JukeboxWorldState); result.ObjectName = _objectName; result.PlaylistStep = _playlistStep; result.Playing = _isPlaying; result.ElapsedSeconds = ElapsedSeconds; return result; } internal void HostAdvance(int delta) { if ((!BJNetcode.AmHost() && !Plugin.IsHeadlessServer) || !Plugin.JukeboxEnabled) { return; } if (!CasinoJukeboxManager.HasTracks) { Plugin.Log.LogWarning("[Jukebox] Cannot advance: no songs are loaded."); return; } int i = _playlistStep + delta; if (i < 0) { for (int num = Math.Max(1, CasinoJukeboxManager.TrackCount); i < 0; i += num) { } } ApplyState(i, playing: true, 0f); JukeboxNetcode.BroadcastState(BuildState()); } internal void ApplyRemoteState(int playlistStep, bool playing, float elapsedSeconds) { ApplyState(playlistStep, playing, elapsedSeconds); } private void RequestAutoStart() { _autoStartRequested = true; TryAutoStart(); } private void TryAutoStart() { if (_setup && _autoStartRequested && !_isPlaying && Plugin.JukeboxEnabled && CasinoJukeboxManager.HasTracks && (BJNetcode.AmHost() || Plugin.IsHeadlessServer)) { ApplyState(_playlistStep, playing: true, 0f); JukeboxNetcode.BroadcastState(BuildState()); } } private void ApplyState(int playlistStep, bool playing, float elapsedSeconds) { _playlistStep = playlistStep; _isPlaying = playing; if ((Object)(object)_source == (Object)null) { _source = ((Component)this).gameObject.AddComponent<AudioSource>(); } CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true); _source.volume = CasinoConfig.EffectiveJukeboxVolume; if (!playing || !Plugin.JukeboxEnabled) { _appliedClip = null; _appliedPlaylistStep = playlistStep; _appliedPlaying = false; StopLocalWorldAudio(); return; } AudioClip clip = CasinoJukeboxManager.GetClip(playlistStep); if ((Object)(object)clip == (Object)null) { StopLocalWorldAudio(); Plugin.Log.LogWarning($"[Jukebox] No clip available for playlist step {playlistStep}."); return; } elapsedSeconds = Mathf.Max(0f, elapsedSeconds); if (clip.length > 0.1f) { elapsedSeconds = Mathf.Min(elapsedSeconds, clip.length - 0.05f); } bool flag = IsWorldAudioAllowedLocally(); bool flag2 = (Object)(object)_appliedClip != (Object)(object)clip || _appliedPlaylistStep != playlistStep || _appliedPlaying != playing; bool flag3 = (Object)(object)_source.clip == (Object)(object)clip && _source.isPlaying && flag; if (!flag2 && flag3) { float num = 0f; try { num = _source.time; } catch { } if (Mathf.Abs(num - elapsedSeconds) > 1.5f) { try { _source.time = elapsedSeconds; } catch { } _trackStartedAt = Time.time - elapsedSeconds; } } else { if (!flag2 && flag && !_source.isPlaying && Time.time < _nextRestartAttemptTime) { return; } _appliedClip = clip; _appliedPlaylistStep = playlistStep; _appliedPlaying = playing; if ((Object)(object)_source.clip != (Object)(object)clip) { _source.clip = clip; } _trackStartedAt = Time.time - elapsedSeconds; if (!flag) { StopLocalWorldAudio(); return; } CasinoJukeboxPersonalPlayer.StopForWorldJukebox(); _nextRestartAttemptTime = Time.time + 1.25f; _source.Stop(); if (elapsedSeconds > 0f) { try { _source.time = elapsedSeconds; } catch { } } _source.Play(); CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); Plugin.Log.LogInfo("[Jukebox] Playing " + CasinoJukeboxManager.GetTrackName(playlistStep) + " " + $"on '{_objectName}' (step {playlistStep}, " + $"volume={CasinoConfig.EffectiveJukeboxVolume:0.000})."); } } private void Update() { if (!_setup) { return; } if ((Object)(object)_source != (Object)null) { _source.volume = CasinoConfig.EffectiveJukeboxVolume; } if (!Plugin.JukeboxEnabled) { StopLocalWorldAudio(); return; } bool flag = IsWorldAudioAllowedLocally(); if ((Object)(object)_source != (Object)null && _isPlaying && _source.isPlaying && flag) { CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); } if ((BJNetcode.AmHost() || Plugin.IsHeadlessServer) && _isPlaying && HasCurrentTrackEnded()) { HostAdvance(1); } if (!flag) { StopLocalWorldAudio(); _playerNearby = false; return; } if (_isPlaying && (Object)(object)_source != (Object)null && !_source.isPlaying && (Object)(object)CasinoJukeboxManager.GetClip(_playlistStep) != (Object)null) { if (Time.time < _nextRestartAttemptTime) { UpdateLocalInteraction(); return; } ApplyState(_playlistStep, playing: true, ElapsedSeconds); } UpdateLocalInteraction(); } private bool IsWorldAudioAllowedLocally() { if (Plugin.IsHeadlessServer) { return false; } return Plugin.IsLocalPlayerInCasino(); } private void StopLocalWorldAudio() { if ((Object)(object)_source != (Object)null && _source.isPlaying) { _source.Stop(); } CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); } private bool HasCurrentTrackEnded() { if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null) { return false; } float length = _source.clip.length; if (length <= 0.1f) { return false; } return Time.time - _trackStartedAt >= length - 0.05f; } private void UpdateLocalInteraction() { if (Plugin.IsHeadlessServer) { return; } if (!Plugin.IsLocalPlayerInCasino()) { _playerNearby = false; return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; ShowPrompt(); } else if (!flag && _playerNearby) { _playerNearby = false; } if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime)) { _nextInputTime = Time.time + 0.5f; if (BJNetcode.AmHost()) { HostAdvance(1); return; } JukeboxNetcode.SendAdvanceRequest(_objectName, 1); Plugin.ShowHUDInfo("Jukebox skip requested..."); } } private bool IsPlayerInRange(Player player) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider != (Object)null) { Bounds bounds = ((Collider)_collider).bounds; return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position); } return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 3f; } private void ShowPrompt() { if (!CasinoJukeboxManager.HasTracks) { Plugin.ShowHUDError("Jukebox has no songs loaded."); return; } string text = (_isPlaying ? CasinoJukeboxManager.GetTrackName(_playlistStep) : "ready"); Plugin.ShowHUDInfo("Jukebox: " + text + ". Press " + CasinoInput.InteractPrompt + " for next song."); } private void OnPlaylistChanged() { if (_isPlaying) { ApplyState(_playlistStep, playing: true, ElapsedSeconds); } TryAutoStart(); } private void OnDestroy() { StopLocalWorldAudio(); if (_subscribed) { CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged; _subscribed = false; } } } internal static class CasinoJukeboxAudioGuard { private const float SCAN_INTERVAL = 0.5f; private static readonly List<AudioSource> _owners = new List<AudioSource>(); private static readonly List<AudioSource> _pausedSources = new List<AudioSource>(); private static float _nextScanTime; internal static void RegisterJukeboxSource(AudioSource? source) { if (!((Object)(object)source == (Object)null)) { RemoveDeadOwners(); if (!Contains(_owners, source)) { _owners.Add(source); } Maintain(); } } internal static void UnregisterJukeboxSource(AudioSource? source) { if ((Object)(object)source == (Object)null) { return; } for (int num = _owners.Count - 1; num >= 0; num--) { if ((Object)(object)_owners[num] == (Object)null || _owners[num] == source) { _owners.RemoveAt(num); } } if (_owners.Count == 0) { RestorePausedSources(); } } internal static void Maintain() { RemoveDeadOwners(); if (_owners.Count == 0) { RestorePausedSources(); } else { if (Time.time < _nextScanTime) { return; } _nextScanTime = Time.time + 0.5f; AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); AudioSource[] array2 = array; foreach (AudioSource val in array2) { if (ShouldPauseVanillaSource(val)) { try { val.Pause(); _pausedSources.Add(val); } catch (Exception ex) { Plugin.Log.LogWarning("[Jukebox] Failed to pause vanilla music source '" + ((Object)val).name + "': " + ex.Message); } } } } } private static bool ShouldPauseVanillaSource(AudioSource source) { if ((Object)(object)source == (Object)null) { return false; } if (!source.isPlaying) { return false; } if ((Object)(object)source.clip == (Object)null) { return false; } if (Contains(_owners, source)) { return false; } if (Contains(_pausedSources, source)) { return false; } if (IsAtlyssJukeboxSource(source)) { return false; } if (!source.loop) { return false; } return LooksLikeVanillaMusicSource(source); } private static bool LooksLikeVanillaMusicSource(AudioSource source) { if (HasAudioComponentName(source, "AudioManager") || HasAudioComponentName(source, "Sound_MapAmbience")) { return true; } string mixerText = GetMixerText(source); if (ContainsAudioWord(mixerText, "music") || ContainsAudioWord(mixerText, "ambience") || ContainsAudioWord(mixerText, "ambient") || ContainsAudioWord(mixerText, "bgm")) { return true; } string value = ((Object)source).name + " " + ((Object)((Component)source).gameObject).name + " " + ((Object)source.clip).name; return ContainsAudioWord(value, "music") || ContainsAudioWord(value, "ambience") || ContainsAudioWord(value, "ambient") || ContainsAudioWord(value, "bgm"); } private static bool HasAudioComponentName(AudioSource source, string componentName) { Transform val = ((Component)source).transform; while ((Object)(object)val != (Object)null) { Component[] components = ((Component)val).GetComponents<Component>(); Component[] array = components; foreach (Component val2 in array) { if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2).GetType().Name, componentName, StringComparison.Ordinal)) { return true; } } val = val.parent; } return false; } private static string GetMixerText(AudioSource source) { try { if ((Object)(object)source.outputAudioMixerGroup == (Object)null) { return string.Empty; } string text = (((Object)(object)source.outputAudioMixerGroup.audioMixer == (Object)null) ? string.Empty : ((Object)source.outputAudioMixerGroup.audioMixer).name); return ((Object)source.outputAudioMixerGroup).name + " " + text; } catch { return string.Empty; } } private static bool IsAtlyssJukeboxSource(AudioSource source) { if ((Object)(object)((Component)source).GetComponentInParent<CasinoJukebox>() != (Object)null) { return true; } Transform val = ((Component)source).transform; while ((Object)(object)val != (Object)null) { if (((Object)val).name.StartsWith("AtlyssCasino_PersonalJukebox", StringComparison.Ordinal)) { return true; } val = val.parent; } return false; } private static bool ContainsAudioWord(string value, string word) { if (string.IsNullOrWhiteSpace(value)) { return false; } return value.IndexOf(word, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool Contains(List<AudioSource> sources, AudioSource source) { foreach (AudioSource source2 in sources) { if (source2 == source) { return true; } } return false; } private static void RemoveDeadOwners() { for (int num = _owners.Count - 1; num >= 0; num--) { AudioSource val = _owners[num]; if ((Object)(object)val == (Object)null) { _owners.RemoveAt(num); } else if (!val.isPlaying) { _owners.RemoveAt(num); } } } private static void RestorePausedSources() { for (int num = _pausedSources.Count - 1; num >= 0; num--) { AudioSource val = _pausedSources[num]; if (!((Object)(object)val == (Object)null)) { try { val.UnPause(); } catch (Exception ex) { Plugin.Log.LogWarning("[Jukebox] Failed to restore vanilla music source '" + ((Object)val).name + "': " + ex.Message); } } } _pausedSources.Clear(); } } internal static class CasinoJukeboxManager { private sealed class JukeboxTrack { internal readonly string Name; internal readonly AudioClip Clip; internal readonly bool IsLocal; internal readonly int SortIndex; internal JukeboxTrack(string name, AudioClip clip, bool isLocal, int sortIndex) { Name = name; Clip = clip; IsLocal = isLocal; SortIndex = sortIndex; } } [CompilerGenerated] private sealed class <LoadLocalSong>d__29 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string filePath; public int sortIndex; private AudioType <audioType>5__1; private string <uri>5__2; private UnityWebRequest <loader>5__3; private DownloadHandlerAudioClip <handler>5__4; private AudioClip <clip>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadLocalSong>d__29(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <uri>5__2 = null; <loader>5__3 = null; <handler>5__4 = null; <clip>5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Invalid comparison between Unknown and I4 //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Invalid comparison between Unknown and I4 switch (<>1__state) { default: return false; case 0: { <>1__state = -1; <audioType>5__1 = GetAudioType(filePath); if ((int)<audioType>5__1 == 0) { Plugin.Log.LogWarning("[Jukebox] Unsupported local song extension: " + filePath); return false; } try { <uri>5__2 = new Uri(filePath).AbsoluteUri; } catch { <uri>5__2 = filePath; } <loader>5__3 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__2, <audioType>5__1); ref DownloadHandlerAudioClip reference = ref <handler>5__4; DownloadHandler downloadHandler = <loader>5__3.downloadHandler; reference = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (<handler>5__4 != null) { <handler>5__4.streamAudio = CasinoConfig.StreamLocalJukeboxSongsFromDisk; } <>2__current = <loader>5__3.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -1; if ((int)<loader>5__3.result != 1) { Plugin.Log.LogWarning("[Jukebox] Failed to load local song '" + filePath + "': " + <loader>5__3.error); return false; } <clip>5__5 = DownloadHandlerAudioClip.GetContent(<loader>5__3); if ((Object)(object)<clip>5__5 == (Object)null || (int)<clip>5__5.loadState != 2) { Plugin.Log.LogWarning("[Jukebox] Local song did not produce a loaded AudioClip: " + filePath); return false; } ((Object)<clip>5__5).name = Path.GetFileNameWithoutExtension(filePath); _localTracks.Add(new JukeboxTrack(((Object)<clip>5__5).name, <clip>5__5, isLocal: true, sortIndex)); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadLocalSongs>d__28 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private string[] <paths>5__1; private Exception <ex>5__2; private int <i>5__3; private string <path>5__4; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadLocalSongs>d__28(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <paths>5__1 = null; <ex>5__2 = null; <path>5__4 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; <path>5__4 = null; goto IL_0109; } <>1__state = -1; _loadingLocalSongs = true; _localTracks.Clear(); try { <paths>5__1 = Directory.GetFiles(LocalSongsDirectory); } catch (Exception ex) { <ex>5__2 = ex; Plugin.Log.LogError("[Jukebox] Failed to list local songs: " + <ex>5__2.Message); _loadingLocalSongs = false; return false; } Array.Sort(<paths>5__1, (string a, string b) => string.Compare(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)); <i>5__3 = 0; goto IL_011b; IL_011b: if (<i>5__3 < <paths>5__1.Length) { <path>5__4 = <paths>5__1[<i>5__3]; if (!IsSupportedAudioFile(<path>5__4)) { goto IL_0109; } <>2__current = LoadLocalSong(<path>5__4, <i>5__3); <>1__state = 1; return true; } _loadingLocalSongs = false; RebuildPlaylist(); Plugin.Log.LogInfo($"[Jukebox] Loaded {_localTracks.Count} local song(s). Total playlist: {_playlist.Count}."); CasinoJukeboxManager.OnPlaylistChanged?.Invoke(); return false; IL_0109: <i>5__3++; goto IL_011b; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string LOCAL_FOLDER_NAME = "ATLYSS Jukebox"; private const string LOCAL_PARENT_FOLDER = "Custom Songs"; private static readonly List<JukeboxTrack> _bundledTracks = new List<JukeboxTrack>(); private static readonly List<JukeboxTrack> _localTracks = new List<JukeboxTrack>(); private static readonly List<JukeboxTrack> _playlist = new List<JukeboxTrack>(); private static bool _initialized; private static bool _loadingLocalSongs; internal static string LocalSongsDirectory => Path.Combine(Paths.BepInExRootPath, "Custom Songs", "ATLYSS Jukebox"); internal static int TrackCount => _playlist.Count; internal static bool HasTracks => _playlist.Count > 0; internal static bool IsLoadingLocalSongs => _loadingLocalSongs; internal static event Action? OnPlaylistChanged; internal static void Init() { if (_initialized) { return; } _initialized = true; try { Directory.CreateDirectory(LocalSongsDirectory); Plugin.Log.LogInfo("[Jukebox] Local songs folder: " + LocalSongsDirectory); } catch (Exception ex) { Plugin.Log.LogError("[Jukebox] Failed to create local songs folder: " + ex.Message); } LoadBundledSongs(); RebuildPlaylist(); try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(LoadLocalSongs()); } catch (Exception ex2) { Plugin.Log.LogError("[Jukebox] Failed to start local song loader: " + ex2.Message); } } internal static AudioClip? GetClip(int playlistStep) { return GetTrack(playlistStep)?.Clip; } internal static string GetTrackName(int playlistStep) { JukeboxTrack track = GetTrack(playlistStep); return (track == null) ? "No songs loaded" : track.Name; } internal static void ConfigureAudioSource(AudioSource source, bool spatial) { if ((Object)(object)source == (Object)null) { return; } source.playOnAwake = false; source.loop = false; source.spatialBlend = (spatial ? 1f : 0f); source.dopplerLevel = 0f; if (spatial) { if (source.minDistance <= 0f) { source.minDistance = 2f; } if (source.maxDistance < 20f) { source.maxDistance = 65f; } source.rolloffMode = (AudioRolloffMode)1; } source.outputAudioMixerGroup = null; } private static JukeboxTrack? GetTrack(int playlistStep) { if (_playlist.Count == 0) { return null; } int num = playlistStep % _playlist.Count; if (num < 0) { num += _playlist.Count; } return _playlist[num]; } private static void LoadBundledSongs() { _bundledTracks.Clear(); AssetBundle assetsBundle = Plugin.AssetsBundle; if ((Object)(object)assetsBundle == (Object)null) { Plugin.Log.LogWarning("[Jukebox] Casino asset bundle is not loaded; bundled songs unavailable."); return; } string[] allAssetNames; try { allAssetNames = assetsBundle.GetAllAssetNames(); } catch (Exception ex) { Plugin.Log.LogError("[Jukebox] Failed to enumerate asset bundle songs: " + ex.Message); return; } HashSet<int> loadedNumbers = new HashSet<int>(); string[] array = allAssetNames; foreach (string assetName in array) { TryLoadBundledSongAsset(assetsBundle, assetName, requireJukeboxPath: true, loadedNumbers); } string[] array2 = allAssetNames; foreach (string assetName2 in array2) { TryLoadBundledSongAsset(assetsBundle, assetName2, requireJukeboxPath: false, loadedNumbers); } if (_bundledTracks.Count == 0) { TryLoadBundledSongsByClipName(assetsBundle, loadedNumbers); } _bundledTracks.Sort(delegate(JukeboxTrack a, JukeboxTrack b) { int num = a.SortIndex.CompareTo(b.SortIndex); return (num != 0) ? num : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); if (_bundledTracks.Count == 0) { Plugin.Log.LogWarning("[Jukebox] No bundled songs found. Expected AudioClips named Song1, Song2, etc. in atlysscasino_assets."); LogBundleSongDiagnostics(allAssetNames); } else { Plugin.Log.LogInfo($"[Jukebox] Loaded {_bundledTracks.Count} bundled song(s): {DescribeTracks(_bundledTracks)}."); } } private static bool TryLoadBundledSongAsset(AssetBundle bundle, string assetName, bool requireJukeboxPath, HashSet<int> loadedNumbers) { if (string.IsNullOrWhiteSpace(assetName)) { return false; } string text = assetName.Replace('\\', '/').ToLowerInvariant(); if (requireJukeboxPath && !text.Contains("/jukebox/jukeboxsongs/") && !text.Contains("jukeboxsongs/")) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(assetName); if (!TryParseSongNumber(fileNameWithoutExtension, out var number)) { return false; } if (loadedNumbers.Contains(number)) { return false; } AudioClip val = null; try { val = bundle.LoadAsset<AudioClip>(assetName); } catch (Exception ex) { Plugin.Log.LogWarning("[Jukebox] Failed loading bundled song '" + assetName + "': " + ex.Message); } if ((Object)(object)val == (Object)null) { return false; } AddBundledTrack(fileNameWithoutExtension, val, number, loadedNumbers); return true; } private static void TryLoadBundledSongsByClipName(AssetBundle bundle, HashSet<int> loadedNumbers) { AudioClip[] array; try { array = bundle.LoadAllAssets<AudioClip>(); } catch (Exception ex) { Plugin.Log.LogWarning("[Jukebox] Failed to scan AudioClips in asset bundle: " + ex.Message); return; } AudioClip[] array2 = array; foreach (AudioClip val in array2) { if (!((Object)(object)val == (Object)null) && TryParseSongNumber(((Object)val).name, out var number) && !loadedNumbers.Contains(number)) { AddBundledTrack(((Object)val).name, val, number, loadedNumbers); } } } private static void AddBundledTrack(string name, AudioClip clip, int songNumber, HashSet<int> loadedNumbers) { string name2 = (string.IsNullOrWhiteSpace(name) ? $"Song{songNumber}" : name); loadedNumbers.Add(songNumber); _bundledTracks.Add(new JukeboxTrack(name2, clip, isLocal: false, songNumber)); } private static void LogBundleSongDiagnostics(string[] assetNames) { if (assetNames == null || assetNames.Length == 0) { Plugin.Log.LogWarning("[Jukebox] atlysscasino_assets reports 0 asset names."); return; } Plugin.Log.LogWarning($"[Jukebox] atlysscasino_assets reports {assetNames.Length} asset name(s). None loaded as Song<number> AudioClips."); List<string> list = new List<string>(); foreach (string text in assetNames) { if (!string.IsNullOrWhiteSpace(text)) { string text2 = text.ToLowerInvariant(); if (text2.Contains("jukebox") || text2.Contains("song") || text2.EndsWith(".ogg") || text2.EndsWith(".mp3") || text2.EndsWith(".wav")) { list.Add(text); } } } if (list.Count > 0) { Plugin.Log.LogWarning("[Jukebox] Bundle assets mentioning jukebox/song/audio: " + DescribeNames(list)); return; } int num = Math.Min(assetNames.Length, 20); List<string> list2 = new List<string>(num); for (int j = 0; j < num; j++) { list2.Add(assetNames[j]); } Plugin.Log.LogWarning("[Jukebox] First bundle asset names: " + DescribeNames(list2)); } [IteratorStateMachine(typeof(<LoadLocalSongs>d__28))] private static IEnumerator LoadLocalSongs() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadLocalSongs>d__28(0); } [IteratorStateMachine(typeof(<LoadLocalSong>d__29))] private static IEnumerator LoadLocalSong(string filePath, int sortIndex) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadLocalSong>d__29(0) { filePath = filePath, sortIndex = sortIndex }; } private static void RebuildPlaylist() { _playlist.Clear(); _playlist.AddRange(_bundledTracks); _playlist.AddRange(_localTracks); } private static bool IsSupportedAudioFile(string path) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)GetAudioType(path) > 0; } private static AudioType GetAudioType(string path) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch { ".ogg" => 14, ".mp3" => 13, ".wav" => 20, _ => 0, }); } private static bool TryParseSongNumber(string name, out int number) { number = 0; if (string.IsNullOrWhiteSpace(name)) { return false; } Match match = Regex.Match(name.Trim(), "^song(\\d+)$", RegexOptions.IgnoreCase); if (!match.Success) { return false; } return int.TryParse(match.Groups[1].Value, out number); } private static string DescribeTracks(List<JukeboxTrack> tracks) { List<string> list = new List<string>(); foreach (JukeboxTrack track in tracks) { list.Add(track.Name); } return DescribeNames(list); } private static string DescribeNames(IList<string> names) { if (names.Count == 0) { return "(none)"; } int num = Math.Min(names.Count, 20); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < num; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(names[i]); } if (names.Count > num) { stringBuilder.Append($", ... +{names.Count - num} more"); } return stringBuilder.ToString(); } } internal sealed class CasinoJukeboxPersonalPlayer : MonoBehaviour { private static CasinoJukeboxPersonalPlayer? _instance; private AudioSource? _source; private bool _playing; private int _playlistStep = -1; private float _trackStartedAt; internal static void Init() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (!Plugin.IsHeadlessServer && !((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("AtlyssCasino_PersonalJukebox"); _instance = val.AddComponent<CasinoJukeboxPersonalPlayer>(); Object.DontDestroyOnLoad((Object)(object)val); CasinoJukeboxManager.OnPlaylistChanged += _instance.OnPlaylistChanged; } } internal static void Play() { CasinoJukeboxPersonalPlayer orCreate = GetOrCreate(); orCreate.PlayInternal(); } internal static void Forward() { CasinoJukeboxPersonalPlayer orCreate = GetOrCreate(); orCreate.AdvanceInternal(1, showMessage: true); } internal static void Previous() { CasinoJukeboxPersonalPlayer orCreate = GetOrCreate(); orCreate.AdvanceInternal(-1, showMessage: true); } internal static void StopPlayback() { CasinoJukeboxPersonalPlayer orCreate = GetOrCreate(); orCreate.StopInternal(showMessage: true); } internal static void StopForWorldJukebox() { if (!((Object)(object)_instance == (Object)null) && _instance._playing) { _instance.StopInternal(showMessage: false); } } internal static void ShowCommandMessage(string message, bool error = false) { string text = (error ? "#FF6666" : "#FFD700"); string text2 = "<color=" + text + ">[Jukebox]</color> " + message; try { ChatBehaviour val = Object.FindObjectOfType<ChatBehaviour>(); if ((Object)(object)val != (Object)null) { val.Init_GameLogicMessage(text2); } } catch { } try { ErrorPromptTextManager.current.Init_ErrorPrompt(Plugin.WrapColor("[Jukebox] " + StripColorTags(message), error ? "#FF3119" : "#FFDC96")); } catch { } } private static CasinoJukeboxPersonalPlayer GetOrCreate() { if ((Object)(object)_instance == (Object)null) { Init(); } return _instance; } private void Awake() { _source = ((Component)this).gameObject.GetComponent<AudioSource>() ?? ((Component)this).gameObject.AddComponent<AudioSource>(); CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false); _source.volume = CasinoConfig.EffectivePersonalJukeboxVolume; } private void Update() { if ((Object)(object)_source != (Object)null) { _source.volume = CasinoConfig.EffectivePersonalJukeboxVolume; } if (!CasinoConfig.JukeboxEnabled) { StopInternal(showMessage: false); } else if (_playing && !((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null)) { CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); float length = _source.clip.length; if (length > 0.1f && Time.time - _trackStartedAt >= length - 0.05f) { AdvanceInternal(1, showMessage: false); } } } private void PlayInternal() { if (!CasinoConfig.JukeboxEnabled) { ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true); return; } if (CasinoJukebox.IsAnyWorldJukeboxPlaying()) { StopInternal(showMessage: false); ShowCommandMessage("Casino jukebox is already playing here.", error: true); return; } if (!CasinoJukeboxManager.HasTracks) { string text = (CasinoJukeboxManager.IsLoadingLocalSongs ? " Local songs are still loading." : ""); ShowCommandMessage("No jukebox songs loaded." + text, error: true); return; } if (_playlistStep < 0) { _playlistStep = 0; } PlayStep(_playlistStep, showMessage: true); } private void AdvanceInternal(int delta, bool showMessage) { if (!CasinoConfig.JukeboxEnabled) { if (showMessage) { ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true); } return; } if (!CasinoJukeboxManager.HasTracks) { if (showMessage) { ShowCommandMessage("No jukebox songs loaded.", error: true); } return; } if (_playlistStep < 0) { _playlistStep = 0; } else { _playlistStep += delta; } if (_playlistStep < 0) { _playlistStep = CasinoJukeboxManager.TrackCount - 1; } PlayStep(_playlistStep, showMessage); } private void PlayStep(int playlistStep, bool showMessage) { if ((Object)(object)_source == (Object)null) { _source = ((Component)this).gameObject.AddComponent<AudioSource>(); } CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false); _source.volume = CasinoConfig.EffectivePersonalJukeboxVolume; AudioClip clip = CasinoJukeboxManager.GetClip(playlistStep); if ((Object)(object)clip == (Object)null) { StopInternal(showMessage: false); if (showMessage) { ShowCommandMessage("No clip found for that jukebox song.", error: true); } return; } _source.Stop(); _source.clip = clip; _source.Play(); CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); _playing = true; _trackStartedAt = Time.time; if (showMessage) { ShowCommandMessage("Playing " + CasinoJukeboxManager.GetTrackName(playlistStep) + "."); } } private void StopInternal(bool showMessage) { if ((Object)(object)_source != (Object)null) { _source.Stop(); CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); } _playing = false; if (showMessage) { ShowCommandMessage("Stopped personal jukebox."); } } private void OnPlaylistChanged() { if (_playing) { PlayStep(_playlistStep, showMessage: false); } } private void OnDestroy() { CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged; if (_instance == this) { _instance = null; } } private static string StripColorTags(string message) { if (string.IsNullOrEmpty(message)) { return string.Empty; } return message.Replace("<color=#FF6666>", "").Replace("<color=#FFD700>", "").Replace("<color=#FFFFFF>", "") .Replace("</color>", ""); } } public sealed class RoomZone : MonoBehaviour { internal readonly HashSet<ulong> Members = new HashSet<ulong>(); internal BoxCollider Collider { get; private set; } = null; internal string RoomName { get; private set; } = "RoomZone"; internal string SceneName { get; private set; } = string.Empty; internal int SceneHandle { get; private set; } internal Component? MapInstance { get; private set; } internal float Volume { get { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Collider == (Object)null) { return float.MaxValue; } Vector3 size = Collider.size; Vector3 lossyScale = ((Component)Collider).transform.lossyScale; return Mathf.Abs(size.x * lossyScale.x) * Mathf.Abs(size.y * lossyScale.y) * Mathf.Abs(size.z * lossyScale.z); } } internal void Setup(BoxCollider box, string roomName, Scene scene, Component? mapInstance) { Collider = box; RoomName = (string.IsNullOrWhiteSpace(roomName) ? "RoomZone" : roomName.Trim()); SceneName = ((Scene)(ref scene)).name ?? string.Empty; SceneHandle = ((Scene)(ref scene)).handle; MapInstance = mapInstance; ((Collider)Collider).isTrigger = true; RoomZoneRegistry.Register(this); } internal bool Contains(Player player) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)Collider == (Object)null) { return false; } Vector3 val = ((Component)Collider).transform.InverseTransformPoint(RoomZoneRegistry.GetPlayerZonePosition(player)) - Collider.center; Vector3 val2 = Collider.size * 0.5f; return Mathf.Abs(val.x) <= val2.x && Mathf.Abs(val.y) <= val2.y && Mathf.Abs(val.z) <= val2.z; } private void OnTriggerEnter(Collider other) { if (RoomZoneRegistry.IsRoutingHost && RoomZoneRegistry.TryGetPlayer(other, out Player player)) { ulong steam = RoomZoneRegistry.GetSteam64(player); if (steam != 0) { Members.Add(steam); } } } private void OnTriggerExit(Collider other) { if (RoomZoneRegistry.IsRoutingHost && RoomZoneRegistry.TryGetPlayer(other, out Player player)) { ulong steam = RoomZoneRegistry.GetSteam64(player); if (steam != 0) { Members.Remove(steam); } } } private void OnDestroy() { RoomZoneRegistry.Unregister(this); } } internal static class RoomZoneRegistry { private sealed class RoomZoneDriver : MonoBehaviour { private float _nextRefresh; private void Update() { if (!(Time.time < _nextRefresh)) { _nextRefresh = Time.time + 0.5f; RefreshMembership(); ReportLocalPresence(); } } } [CompilerGenerated] private sealed class <DelayedScan>d__40 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Scene scene; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedScan>d__40(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; ScanScene(scene); <>2__current = (object)new WaitForSeconds(1f); <>1__state = 3; return true; case 3: <>1__state = -1; ScanScene(scene); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string PREFIX = "RoomZone"; private const float REFRESH_INTERVAL_SEC = 0.5f; private const float PRESENCE_REPORT_INTERVAL_SEC = 2f; private static readonly List<RoomZone> _zones = new List<RoomZone>(); private static readonly Dictionary<int, Component?> _mapInstancesByScene = new Dictionary<int, Component>(); private static readonly Regex _cloneSuffixRegex = new Regex("\\s*\\(\\d+\\)$", RegexOptions.Compiled); private static bool _initialized; private static RoomZoneDriver? _driver; private static FieldInfo? _playerMapInstanceField; private static PropertyInfo? _playerMapInstanceProperty; private static FieldInfo? _playerMapNameField; private static PropertyInfo? _playerMapNameProperty; private static FieldInfo? _chatChannelField; private static MethodInfo? _vanillaReceiveChatMethod; private static bool _reflectionResolved; private static bool _loggedReflectionFailure; private static readonly Dictionary<ulong, string> _lastZoneBySteam64 = new Dictionary<ulong, string>(); private static string _lastLocalPresenceKey = string.Empty; private static float _nextLocalPresenceReportTime; internal static bool IsRoutingHost { get { if (Plugin.IsHeadlessServer) { return true; } try { return BJNetcode.AmHost(); } catch { return false; } } } internal static void Init() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (!_initialized) { _initialized = true; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; GameObject val = new GameObject("AtlyssCasino_RoomZoneRegistry"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; _driver = val.AddComponent<RoomZoneDriver>(); for (int i = 0; i < SceneManager.sceneCount; i++) { ScanScene(SceneManager.GetSceneAt(i)); } Plugin.Log.LogInfo("[RoomZone] Registry initialized."); } } internal static void Register(RoomZone zone) { if (!((Object)(object)zone == (Object)null) && !_zones.Contains(zone)) { _zones.Add(zone); } } internal static void Unregister(RoomZone zone) { if (!((Object)(object)zone == (Object)null)) { _zones.Remove(zone); } } internal static void RefreshMembership() { if (!IsRoutingHost) { return; } for (int i = 0; i < _zones.Count; i++) { _zones[i].Members.Clear(); } Player[] array = Object.FindObjectsOfType<Player>(); Player[] array2 = array; foreach (Player val in array2) { if ((Object)(object)val == (Object)null) { continue; } ulong steam = GetSteam64(val); if (steam != 0) { RoomZone roomZone = FindContainingZone(val); if ((Object)(object)roomZone != (Object)null) { roomZone.Members.Add(steam); } LogMembershipTransition(val, steam, roomZone); } } } internal static bool TryRouteZoneChat(ChatBehaviour chat, string message, ChatChannel channel) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 if ((int)channel != 2) { return false; } if ((Object)(object)chat == (Object)null) { return false; } if (string.IsNullOrWhiteSpace(message)) { return false; } if (IsSingleSlashCommand(message)) { return false; } if (!IsRoutingHost) { return false; } Player player = GetPlayer(chat); if ((Object)(object)player == (Object)null) { return false; } ulong steam = GetSteam64(player); if (steam != 0L && !RoomZoneChatNetcode.IsParticipantEnabled(steam)) { return false; } if (!HasZonesInSameScene(player)) { return false; } RefreshMembership(); RoomZone geometryZone = FindContainingZone(player); string text = ResolveEffectiveRoomName(player, steam, geometryZone); if (string.IsNullOrWhiteSpace(text)) { return false; } string roomName = text; string formattedMessage = FormatRoomMessage(player, text, message); int num = 0; List<string> list = new List<string>(); Player[] array = Object.FindObjectsOfType<Player>(); Player[] array2 = array; foreach (Player val in array2) { if ((Object)(object)val == (Object)null) { continue; } if (!IsSameSceneScope(player, val)) { ulong steam2 = GetSteam64(val); if (steam2 != 0) { list.Add($"{steam2}:other-scope:skipped"); } continue; } ulong steam3 = GetSteam64(val); if (steam3 == 0) { continue; } if (!RoomZoneChatNetcode.IsParticipantEnabled(steam3)) { list.Add($"{steam3}:{DescribeZone(FindContainingZone(val))}:opted-out:skipped"); continue; } RoomZone geometryZone2 = FindContainingZone(val); string text2 = ResolveEffectiveRoomName(val, steam3, geometryZone2); if (!RoomNamesEqual(text2, text)) { list.Add($"{steam3}:{DescribeRoomName(text2)}:skipped"); continue; } RoomZoneChatNetcode.SendRoomChatTo(steam3, formattedMessage, steam, message, roomName); num++; list.Add($"{steam3}:{DescribeRoomName(text2)}:sent"); } Plugin.Log.LogDebug($"[RoomZone] Routed zone chat from steam64={steam} " + $"name='{GetDisplayName(player)}' to {num} recipient(s) " + "scope='" + GetScopeLabel(player) + "' room='" + text + "' targets=[" + string.Join(", ", list.ToArray()) + "]."); return true; } internal static bool TryRouteLocalRoomZoneChat(ChatBehaviour chat, string message) { if ((Object)(object)chat == (Object)null) { return false; } if (string.IsNullOrWhiteSpace(message)) { return false; } if (!Input.GetKeyDown((KeyCode)13) && !Input.GetKeyDown((KeyCode)271)) { return false; } if (IsSingleSlashCommand(message)) { return false; } if (!CasinoConfig.RoomZoneChatEnabled) { return false; } if (!IsChatSetToZone(chat)) { return false; } Player val = Player._mainPlayer ?? GetPlayer(chat); if ((Object)(object)val == (Object)null) { return false; } if (!HasZonesInSameScene(val)) { return false; } RoomZone roomZone = FindContainingZone(val); if ((Object)(object)roomZone == (Object)null) { return false; } ulong num = GetSteam64(val); if (num == 0) { num = BJNetcode.GetLocalSteam64(); } if (num == 0) { return false; } string scopeLabel = GetScopeLabel(val); RoomZoneChatNetcode.PublishLocalPresence(roomZone.RoomName, scopeLabel); RoomZoneChatNetcode.SendRoomChatRequest(num, message, roomZone.RoomName, scopeLabel); CloseLocalChatAfterCustomSend(chat); Plugin.Log.LogDebug("[RoomZone] Routed local room chat request " + $"steam64={num} room='{roomZone.RoomName}' scope='{scopeLabel}'."); return true; } internal static void BroadcastRoomChatRequest(ulong senderSteam64, string rawMessage, string roomName, string scope) { if (!IsRoutingHost || senderSteam64 == 0 || string.IsNullOrWhiteSpace(rawMessage) || string.IsNullOrWhiteSpace(roomName) || !RoomZoneChatNetcode.IsParticipantEnabled(senderSteam64)) { return; } Player sender = BJNetcode.FindPlayerBySteam64(senderSteam64); string text = SanitizeRichText(roomName); string formattedMessage = FormatRoomMessage(sender, text, rawMessage); int num = 0; List<string> list = new List<string>(); Player[] array = Object.FindObjectsOfType<Player>(); Player[] array2 = array; foreach (Player val in array2) { if ((Object)(object)val == (Object)null) { continue; } ulong steam = GetSteam64(val); if (steam != 0) { if (!RoomZoneChatNetcode.IsParticipantEnabled(steam)) { list.Add($"{steam}:opted-out:skipped"); continue; } RoomZoneChatNetcode.SendRoomChatTo(steam, formattedMessage, senderSteam64, rawMessage, text, scope, requireLocalRoomMatch: true); num++; list.Add($"{steam}:sent-for-local-room-filter"); } } Plugin.Log.LogDebug("[RoomZone] Broadcast room chat request " + $"sender={senderSteam64} room='{text}' scope='{scope}' " + $"to {num} candidate recipient(s) " + "targets=[" + string.Join(", ", list.ToArray()) + "]."); } private static bool IsChatSetToZone(ChatBehaviour chat) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 try { if ((object)_chatChannelField == null) { _chatChannelField = typeof(ChatBehaviour).GetField("_setChatChannel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_chatChannelField == null) { return true; } return _chatChannelField.GetValue(chat) is ChatChannel val && (int)val == 2; } catch { return true; } } private static void CloseLocalChatAfterCustomSend(ChatBehaviour chat) { try { typeof(ChatBehaviour).GetField("_focusedInChat", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(chat, false); object obj = typeof(ChatBehaviour).GetField("_chatAssets", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat); if (obj != null) { object obj2 = obj.GetType().GetField("_chatInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); if (obj2 != null) { PropertyInfo property = obj2.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { property.SetValue(obj2, string.Empty, null); } obj2.GetType().GetMethod("DeactivateInputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(obj2, null); } } Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } catch { } } private static string ResolveEffectiveRoomName(Player player, ulong steam64, RoomZone? geometryZone) { if ((Object)(object)geometryZone != (Object)null) { return geometryZone.RoomName; } string reportedRoomName = RoomZoneChatNetcode.GetReportedRoomName(steam64); return string.IsNullOrWhiteSpace(reportedRoomName) ? string.Empty : SanitizeRichText(reportedRoomName); } private static bool RoomNamesEqual(string a, string b) { return string.Equals((a ?? string.Empty).Trim(), (b ?? string.Empty).Trim(), StringComparison.OrdinalIgnoreCase); } private static bool IsSingleSlashCommand(string message) { if (string.IsNullOrWhiteSpace(message)) { return false; } string text = message.TrimStart(); return text.StartsWith("/", StringComparison.Ordinal) && !text.StartsWith("//", StringComparison.Ordinal); } internal static bool TryGetPlayer(Collider collider, out Player player) { player = null; if ((Object)(object)collider == (Object)null) { return false; } player = ((Component)collider).GetComponentInParent<Player>(); return (Object)(object)player != (Object)null; } internal static ulong GetSteam64(Player player) { if ((Object)(object)player == (Object)null) { return 0uL; } try { if (ulong.TryParse(player.Network_steamID, out var result)) { return result; } } catch { } return 0uL; } internal static bool DisplayLocalRoomChat(string formattedMessage, bool respectLocalPreference = true, ulong senderSteam64 = 0uL, string rawMessage = "", string roomName = "", string scope = "", bool requireLocalRoomMatch = false) { if (respectLocalPreference && !CasinoConfig.RoomZoneChatEnabled) { return false; } if (string.IsNullOrWhiteSpace(formattedMessage)) { return false; } if (requireLocalRoomMatch && !IsLocalPlayerInRoom(roomName, scope)) { Plugin.Log.LogDebug("[RoomZone] Skipped private room packet locally; required room='" + DescribeRoomName(roomName) + "' scope='" + scope + "'."); return false; } try { if (TryDisplayViaVanillaChat(senderSteam64, rawMessage, roomName)) { Plugin.Log.LogDebug("[RoomZone] Displayed private chat via vanilla path " + $"sender={senderSteam64} room='{DescribeRoomName(roomName)}'."); return true; } ChatBehaviour val = Object.FindObjectOfType<ChatBehaviour>(); if ((Object)(object)val == (Object)null) { return false; } val.New_ChatMessage(formattedMessage); Plugin.Log.LogDebug("[RoomZone] Displayed private chat via text fallback " + $"sender={senderSteam64} room='{DescribeRoomName(roomName)}'."); return true; } catch (Exception ex) { Plugin.Log.LogWarning("[RoomZone] Failed to display local room chat: " + ex.Message); return false; } } private static bool IsLocalPlayerInRoom(string roomName, string scope) { if (string.IsNullOrWhiteSpace(roomName)) { return true; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } RoomZone roomZone = FindContainingZone(mainPlayer); if ((Object)(object)roomZone == (Object)null) { return false; } if (!RoomNamesEqual(roomZone.RoomName, roomName)) { return false; } if (!string.IsNullOrWhiteSpace(scope)) { string scopeLabel = GetScopeLabel(mainPlayer); if (!string.Equals(scopeLabel, scope, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } private static bool TryDisplayViaVanillaChat(ulong senderSteam64, string rawMessage, string roomName) { if (senderSteam64 == 0) { return false; } if (string.IsNullOrWhiteSpace(rawMessage)) { return false; } try { Player val = BJNetcode.FindPlayerBySteam64(senderSteam64); if ((Object)(object)val == (Object)null) { return false; } ChatBehaviour val2 = ((Component)val).GetComponent<ChatBehaviour>() ?? ((Component)val).GetComponentInChildren<ChatBehaviour>(); if ((Object)(object)val2 == (Object)null) { return false; } MethodInfo vanillaReceiveChatMethod = GetVanillaReceiveChatMethod(); if (vanillaReceiveChatMethod == null) { return false; } string text = (string.IsNullOrWhiteSpace(roomName) ? rawMessage : ("[" + SanitizeRichText(roomName) + "] " + rawMessage)); vanillaReceiveChatMethod.Invoke(val2, new object[3] { text, false, (object)(ChatChannel)2 }); return true; } catch (Exception ex) { Plugin.Log.LogDebug("[RoomZone] Vanilla private chat display failed: " + ex.Message); return false; } } private static MethodInfo? GetVanillaReceiveChatMethod() { if (_vanillaReceiveChatMethod != null) { return _vanillaReceiveChatMethod; } _vanillaReceiveChatMethod = typeof(ChatBehaviour).GetMethod("UserCode_Rpc_RecieveChatMessage__String__Boolean__ChatChannel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_vanillaReceiveChatMethod == null) { _vanillaReceiveChatMethod = typeof(ChatBehaviour).GetMethod("Rpc_RecieveChatMessage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } return _vanillaReceiveChatMethod; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) ScanScene(scene); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayedScan(scene)); } } [IteratorStateMachine(typeof(<DelayedScan>d__40))] private static IEnumerator DelayedScan(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedScan>d__40(0) { scene = scene }; } private static void OnSceneUnloaded(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _zones.RemoveAll((RoomZone z) => (Object)(object)z == (Object)null || z.SceneHandle == ((Scene)(ref scene)).handle); _mapInstancesByScene.Remove(((Scene)(ref scene)).handle); } internal static void ReportLocalPresence() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } ulong num = GetSteam64(mainPlayer); if (num == 0) { num = BJNetcode.GetLocalSteam64(); } if (num != 0) { string text = (HasZonesInSameScene(mainPlayer) ? FindContainingZone(mainPlayer) : null)?.RoomName ?? string.Empty; string scopeLabel = GetScopeLabel(mainPlayer); string text2 = scopeLabel + "::" + text; if (!(text2 == _lastLocalPresenceKey) || !(Time.time < _nextLocalPresenceReportTime)) { _lastLocalPresenceKey = text2; _nextLocalPresenceReportTime = Time.time + 2f; RoomZoneChatNetcode.PublishLocalPresence(text, scopeLabel); } } } internal static Vector3 GetPlayerZonePosition(Player player) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } Bounds val; try { CharacterController componentInChildren = ((Component)player).GetComponentInChildren<CharacterController>(); if ((Object)(object)componentInChildren != (Object)null && ((Collider)componentInChildren).enabled) { val = ((Collider)componentInChildren).bounds; return ((Bounds)(ref val)).center; } } catch { } try { Collider[] componentsInChildren = ((Component)player).GetComponentsInChildren<Collider>(false); Bounds? val2 = null; Collider[] array = componentsInChildren; foreach (Collider val3 in array) { if (!((Object)(object)val3 == (Object)null) && val3.enabled && !val3.isTrigger) { if (val2.HasValue) { Bounds value = val2.Value; ((Bounds)(ref value)).Encapsulate(val3.bounds); val2 = value; } else { val2 = val3.bounds; } } } if (val2.HasValue) { val = val2.Value; return ((Bounds)(ref val)).center; } } catch { } return ((Component)player).transform.position; } private static void ScanScene(Scene scene) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return; } Component sceneMapInstance = GetSceneMapInstance(scene); int num = 0; GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } BoxCollider[] componentsInChildren = val.GetComponentsInChildren<BoxCollider>(true); BoxCollider[] array2 = componentsInChildren; foreach (BoxCollider val2 in array2) { if ((Object)(object)val2 == (Object)null || !((Collider)val2).enabled || !((Component)val2).gameObject.activeInHierarchy) { continue; } bool flag = StartsWithRoomZone(CleanUnityName(((Object)((Component)val2).gameObject).name)); if ((((Collider)val2).isTrigger || flag) && IsRoomZoneCandidate(val2)) { RoomZone component = ((Component)val2).GetComponent<RoomZone>(); RoomZone roomZone = component ?? ((Component)val2).gameObject.AddComponent<RoomZone>(); roomZone.Setup(val2, ResolveRoomName(val2), scene, sceneMapInstance); if ((Object)(object)component == (Object)null) { num++; LogZoneHooked(roomZone); } } } } if (num > 0) { Plugin.Log.LogInfo($"[RoomZone] Hooked {num} room zone(s) in scene '{((Scene)(ref scene)).name}'."); RefreshMembership(); } } private static bool IsRoomZoneCandidate(BoxCollider box) { string cleanName = CleanUnityName(((Object)((Component)box).gameObject).name); if (StartsWithRoomZone(cleanName)) { return true; } Transform parent = ((Component)box).transform.parent; while ((Object)(object)parent != (Object)null) { string cleanName2 = CleanUnityName(((Object)parent).name); if (StartsWithRoomZone(cleanName2)) { return true; } parent = parent.parent; } return false; } private static string ResolveRoomName(BoxCollider box) { string text = ExtractUsableRoomName(((Object)((Component)box).gameObject).name); if (!string.IsNullOrWhiteSpace(text)) { return text; } Transform parent = ((Component)box).transform.parent; while ((Object)(object)parent != (Object)null) { string text2 = ExtractUsableRoomName(((Object)parent).name); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } parent = parent.parent; } return "RoomZone"; } private static string? ExtractUsableRoomName(string rawName) { string text = CleanUnityName(rawName); if (IsDefaultRoomName(text)) { return null; } if (text.StartsWith("RoomZone_", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("RoomZone".Length + 1).Trim(' ', '_', '-', ':'); return IsDefaultRoomName(text2) ? null : text2; } if (text.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase) && text.Length > "RoomZone".Length) { string text3 = text.Substring("RoomZone".Length).Trim(' ', '_', '-', ':'); return IsDefaultRoomName(text3) ? null : text3; } return text; } private static bool StartsWithRoomZone(string cleanName) { return cleanName.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase); } private static bool IsDefaultRoomName(string value) { if (string.IsNullOrWhiteSpace(value)) { return true; } string a = CleanUnityName(value); return string.Equals(a, "RoomZone", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "RoomZone_", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "GameObject", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Cube", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Box", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "BoxCollider", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Collider", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Trigger", StringComparison.OrdinalIgnoreCase); } private static string CleanUnityName(string rawName) { string input = rawName ?? string.Empty; input = _cloneSuffixRegex.Replace(input, string.Empty); return input.Trim(); } private static RoomZone? FindContainingZone(Player player) { RoomZone result = null; float num = float.MaxValue; for (int i = 0; i < _zones.Count; i++) { RoomZone roomZone = _zones[i]; if (!((Object)(object)roomZone == (Object)null) && !((Object)(object)roomZone.Collider == (Object)null) && IsPlayerInZoneScene(player, roomZone) && roomZone.Contains(player)) { float volume = roomZone.Volume; if (volume < num) { result = roomZone; num = volume; } } } return result; } private static void LogMembershipTransition(Player player, ulong steam64, RoomZone? zone) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) string text = (((Object)(object)zone == (Object)null) ? (GetScopeLabel(player) + "::main") : $"{zone.SceneHandle}:{zone.RoomName}"); if (!_lastZoneBySteam64.TryGetValue(steam64, out string value) || !string.Equals(value, text, StringComparison.Ordinal)) { _lastZoneBySteam64[steam64] = text; Vector3 playerZonePosition = GetPlayerZonePosition(player); if ((Object)(object)zone == (Object)null) { Plugin.Log.LogDebug($"[RoomZone] Player {steam64} transitioned to main " + "at " + FormatVector(playerZonePosition) + " scope='" + GetScopeLabel(player) + "'."); return; } CasinoLog log = Plugin.Log; string[] obj = new string[12] { $"[RoomZone] Player {steam64} transitioned to room ", "'", zone.RoomName, "' at ", FormatVector(playerZonePosition), " zoneCenter=", null, null, null, null, null, null }; Bounds bounds = ((Collider)zone.Collider).bounds; obj[6] = FormatVector(((Bounds)(ref bounds)).center); obj[7] = " zoneSize="; obj[8] = FormatVector(zone.Collider.size); obj[9] = " lossyScale="; obj[10] = FormatVector(((Component)zone.Collider).transform.lossyScale); obj[11] = "."; log.LogDebug(string.Concat(obj)); } } private static void LogZoneHooked(RoomZone zone) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)zone == (Object)null) && !((Object)(object)zone.Collider == (Object)null)) { CasinoLog log = Plugin.Log; string[] obj = new string[15] { "[RoomZone] Hooked '", ((Object)((Component)zone).gameObject).name, "' room='", zone.RoomName, "' scene='", zone.SceneName, "' ", $"trigger={((Collider)zone.Collider).isTrigger} ", "center=", null, null, null, null, null, null }; Bounds bounds = ((Collider)zone.Collider).bounds; obj[9] = FormatVector(((Bounds)(ref bounds)).center); obj[10] = " size="; obj[11] = FormatVector(zone.Collider.size); obj[12] = " lossyScale="; obj[13] = FormatVector(((Component)zone.Collider).transform.lossyScale); obj[14] = "."; log.LogInfo(string.Concat(obj)); } } private static bool HasZonesInSameScene(Player player) { for (int i = 0; i < _zones.Count; i++) { RoomZone roomZone = _zones[i]; if ((Object)(object)roomZone != (Object)null && IsPlayerInZoneScene(player, roomZone)) { return true; } } return false; } private static bool IsSameSceneScope(Player a, Player b) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)a == (Object)null || (Object)(object)b == (Object)null) { return false; } Component playerMapInstance = GetPlayerMapInstance(a); Component playerMapInstance2 = GetPlayerMapInstance(b); if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)playerMapInstance2 != (Object)null) { return playerMapInstance == playerMapInstance2; } string playerMapName = GetPlayerMapName(a); string playerMapName2 = GetPlayerMapName(b); if (!string.IsNullOrWhiteSpace(playerMapName) && !string.IsNullOrWhiteSpace(playerMapName2)) { return string.Equals(playerMapName, playerMapName2, StringComparison.OrdinalIgnoreCase); } Scene scene = ((Component)a).gameObject.scene; int handle = ((Scene)(ref scene)).handle; scene = ((Component)b).gameObject.scene; return handle == ((Scene)(ref scene)).handle; } private static bool IsPlayerInZoneScene(Player player, RoomZone zone) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) Component playerMapInstance = GetPlayerMapInstance(player); if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)zone.MapInstance != (Object)null) { return playerMapInstance == zone.MapInstance; } string playerMapName = GetPlayerMapName(player); if (!string.IsNullOrWhiteSpace(playerMapName) && !string.IsNullOrWhiteSpace(zone.SceneName)) { return string.Equals(playerMapName, zone.SceneName, StringComparison.OrdinalIgnoreCase); } Scene scene = ((Component)player).gameObject.scene; return ((Scene)(ref scene)).handle == zone.SceneHandle; } private static Component? GetSceneMapInstance(Scene scene) { if (_mapInstancesByScene.TryGetValue(((Scene)(ref scene)).handle, out Component value) && (Object)(object)value != (Object)null) { return value; } Component val = null; try { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val2 in array) { MonoBehaviour[] componentsInChildren = val2.GetComponentsInChildren<MonoBehaviour>(true); MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val3 in array2) { if (!((Object)(object)val3 == (Object)null) && !(((object)val3).GetType().Name != "MapInstance")) { val = (Component)(object)val3; break; } } if ((Object)(object)val != (Object)null) { break; } } } catch (Exception ex) { Plugin.Log.LogWarning("[RoomZone] MapInstance scan threw in scene '" + ((Scene)(ref scene)).name + "': " + ex.Message); } _mapInstancesByScene[((Scene)(ref scene)).handle] = val; return val; } private static Player? GetPlayer(ChatBehaviour chat) { if ((Object)(object)chat == (Object)null) { return null; } try { Player component = ((Component)chat).GetComponent<Player>(); if ((Object)(object)component != (Object)null) { return component; } } catch { } try { object? obj2 = typeof(ChatBehaviour).GetField("_player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat); return (Player?)((obj2 is Player) ? obj2 : null); } catch { return null; } } private static Component? GetPlayerMapInstance(Player player) { ResolvePlayerReflection(); try { object obj = _playerMapInstanceProperty?.GetValue(player); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { return val; } } catch { } try { object obj3 = _playerMapInstanceField?.GetValue(player); Component val2 = (Component)((obj3 is Component) ? obj3 : null); if (val2 != null) { return val2; } } catch { } return null; } private static string GetPlayerMapName(Player player) { ResolvePlayerReflection(); try { object obj = _playerMapNameProperty?.GetValue(player); if (obj is string result) { return result; } } catch { } try { object obj3 = _playerMapNameField?.GetValue(player); if (obj3 is string result2) { return result2; } } catch { } return string.Empty; } private static void ResolvePlayerReflection() { if (!_reflectionResolved) { _reflectionResolved = true; Type typeFromHandle = typeof(Player); _playerMapInstanceProperty = typeFromHandle.GetProperty("Network_playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _playerMapInstanceField = typeFromHandle.GetField("_playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeFromHandle.GetField("playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _playerMapNameProperty = typeFromHandle.GetProperty("Network_mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _playerMapNameField = typeFromHandle.GetField("_mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeFromHandle.GetField("mapName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!_loggedReflectionFailure && _playerMapInstanceProperty == null && _playerMapInstanceField == null && Plugin.Log != null) { Plugin.Log.LogWarning("[RoomZone] Player MapInstance reflection failed. Room-zone chat will fall back to map names / GameObject scenes."); _loggedReflectionFailure = true; } } } private static string FormatRoomMessage(Player? sender, string roomName, string message) { string text = (string.IsNullOrWhiteSpace(roomName) ? "Zone" : SanitizeRichText(roomName)); string text2 = SanitizeRichText(((Object)(object)sender == (Object)null) ? "Player" : GetDisplayName(sender)); return "<color=#9FD3FF>[" + text + "]</color> <color=#FFFFFF>" + text2 + "</color>: " + message; } private static string GetDisplayName(Player player) { if ((Object)(object)player == (Object)null) { return "Player"; } try { if (!string.IsNullOrWhiteSpace(player.Network_nickname)) { return player.Network_nickname; } } catch { } try { if (!string.IsNullOrWhiteSpace(player.Network_globalNickname)) { return player.Network_globalNickname; } } catch { } string text = CleanUnityName(((Object)((Component)player).gameObject).name); return string.IsNullOrWhiteSpace(text) ? "Player" : text; } private static string GetScopeLabel(Player player) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) Component playerMapInstance = GetPlayerMapInstance(player); if ((Object)(object)playerMapInstance != (Object)null) { return ((Object)playerMapInstance.gameObject).name; } string playerMapName = GetPlayerMapName(player); if (!string.IsNullOrWhiteSpace(playerMapName)) { return playerMapName; } Scene scene = ((Component)player).gameObject.scene; return ((Scene)(ref scene)).name; } private static string SanitizeRichText(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Replace("<", string.Empty).Replace(">", string.Empty); } private static string DescribeZone(RoomZone? zone) { return ((Object)(object)zone == (Object)null) ? "main" : SanitizeRichText(zone.RoomName); } private static string DescribeRoomName(string roomName) { return string.IsNullOrWhiteSpace(roomName) ? "main" : SanitizeRichText(roomName); } private static string FormatVector(Vector3 value) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:F2}, {value.y:F2}, {value.z:F2})"; } } [HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")] internal static class RoomZoneLocalChatPatch { [HarmonyPrefix] [HarmonyPriority(0)] public static bool Prefix(ChatBehaviour __instance, string _message) { try { return !RoomZoneRegistry.TryRouteLocalRoomZoneChat(__instance, _message); } catch (Exception arg) { Plugin.Log.LogError($"[RoomZone] Local chat routing failed; falling back to vanilla chat. {arg}"); return true; } } } [HarmonyPatch(typeof(ChatBehaviour), "UserCode_Cmd_SendChatMessage__String__ChatChannel")] internal static class RoomZoneChatPatch { [HarmonyPrefix] [HarmonyPriority(0)] public static bool Prefix(ChatBehaviour __instance, string _message, ChatChannel _chatChannel) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) try { return !RoomZoneRegistry.TryRouteZoneChat(__instance, _message, _chatChannel); } catch (Exception arg) { Plugin.Log.LogError($"[RoomZone] Chat routing failed; falling back to vanilla chat. {arg}"); return true; } } } internal static class CasinoConfig { internal const int DefaultEntryFeeCrowns = 100; internal const int DefaultWalkAwayPenaltyCrowns = 10; internal const int DefaultBlackjackTurnTimeoutSeconds = 60; internal const int DefaultRouletteAfkTimeoutSeconds = 60; internal const float DefaultSlotsPayoutMultiplier = 1f; internal const float DefaultBlackjackPayoutMultiplier = 1f; internal const float DefaultRoulettePayoutMultiplier = 1f; internal const string DefaultAllowedBetAmountsCsv = "10,50,100,500"; internal const float JukeboxOutputScale = 0.025f; internal const float JukeboxMaxEffectiveVolume = 0.25f; internal static ConfigEntry<int>? EntryFeeCrownsEntry; internal static ConfigEntry<int>? WalkAwayPenaltyCrownsEntry; internal static ConfigEntry<int>? BlackjackTurnTimeoutSecondsEntry; internal static ConfigEntry<int>? RouletteAfkTimeoutSecondsEntry; internal static ConfigEntry<float>? SlotsPayoutMultiplierEntry; internal static ConfigEntry<float>? BlackjackPayoutMultiplierEntry; internal static ConfigEntry<float>? RoulettePayoutMul