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 d__12 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private Exception 5__1; private Exception 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__12(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 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) { 5__1 = ex; Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 1 failed: " + 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) { 5__2 = ex; Plugin.Log.LogError("[SceneWatcher] Deferred cleanup pass 2 failed: " + 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(d__12))] private static IEnumerator DeferredCleanupCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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[] 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(); 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 hashSet = new HashSet(); CardVisual[] array = Object.FindObjectsOfType(); 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 = ""; } 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 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(true) ?? ((Component)this).gameObject.AddComponent(); CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true); _source.volume = CasinoConfig.EffectiveJukeboxVolume; _collider = ((Component)this).GetComponent() ?? ((Component)this).GetComponentInChildren(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[] array2 = array; foreach (CasinoJukebox casinoJukebox in array2) { casinoJukebox.RequestAutoStart(); } } internal static CasinoJukebox? FindByName(string objectName) { CasinoJukebox[] array = Object.FindObjectsOfType(); 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[] 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(); } 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 _owners = new List(); private static readonly List _pausedSources = new List(); 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[] 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[] 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() != (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 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 d__29 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string filePath; public int sortIndex; private AudioType 5__1; private string 5__2; private UnityWebRequest 5__3; private DownloadHandlerAudioClip 5__4; private AudioClip 5__5; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__29(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; 5__4 = null; 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; 5__1 = GetAudioType(filePath); if ((int)5__1 == 0) { Plugin.Log.LogWarning("[Jukebox] Unsupported local song extension: " + filePath); return false; } try { 5__2 = new Uri(filePath).AbsoluteUri; } catch { 5__2 = filePath; } 5__3 = UnityWebRequestMultimedia.GetAudioClip(5__2, 5__1); ref DownloadHandlerAudioClip reference = ref 5__4; DownloadHandler downloadHandler = 5__3.downloadHandler; reference = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (5__4 != null) { 5__4.streamAudio = CasinoConfig.StreamLocalJukeboxSongsFromDisk; } <>2__current = 5__3.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -1; if ((int)5__3.result != 1) { Plugin.Log.LogWarning("[Jukebox] Failed to load local song '" + filePath + "': " + 5__3.error); return false; } 5__5 = DownloadHandlerAudioClip.GetContent(5__3); if ((Object)(object)5__5 == (Object)null || (int)5__5.loadState != 2) { Plugin.Log.LogWarning("[Jukebox] Local song did not produce a loaded AudioClip: " + filePath); return false; } ((Object)5__5).name = Path.GetFileNameWithoutExtension(filePath); _localTracks.Add(new JukeboxTrack(((Object)5__5).name, 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 d__28 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private string[] 5__1; private Exception 5__2; private int 5__3; private string 5__4; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__28(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__2 = null; 5__4 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; 5__4 = null; goto IL_0109; } <>1__state = -1; _loadingLocalSongs = true; _localTracks.Clear(); try { 5__1 = Directory.GetFiles(LocalSongsDirectory); } catch (Exception ex) { 5__2 = ex; Plugin.Log.LogError("[Jukebox] Failed to list local songs: " + 5__2.Message); _loadingLocalSongs = false; return false; } Array.Sort(5__1, (string a, string b) => string.Compare(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)); 5__3 = 0; goto IL_011b; IL_011b: if (5__3 < 5__1.Length) { 5__4 = 5__1[5__3]; if (!IsSupportedAudioFile(5__4)) { goto IL_0109; } <>2__current = LoadLocalSong(5__4, 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: 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 _bundledTracks = new List(); private static readonly List _localTracks = new List(); private static readonly List _playlist = new List(); 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 loadedNumbers = new HashSet(); 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 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(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 loadedNumbers) { AudioClip[] array; try { array = bundle.LoadAllAssets(); } 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 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 AudioClips."); List list = new List(); 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 list2 = new List(num); for (int j = 0; j < num; j++) { list2.Add(assetNames[j]); } Plugin.Log.LogWarning("[Jukebox] First bundle asset names: " + DescribeNames(list2)); } [IteratorStateMachine(typeof(d__28))] private static IEnumerator LoadLocalSongs() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__28(0); } [IteratorStateMachine(typeof(d__29))] private static IEnumerator LoadLocalSong(string filePath, int sortIndex) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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 tracks) { List list = new List(); foreach (JukeboxTrack track in tracks) { list.Add(track.Name); } return DescribeNames(list); } private static string DescribeNames(IList 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(); 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 = "[Jukebox] " + message; try { ChatBehaviour val = Object.FindObjectOfType(); 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() ?? ((Component)this).gameObject.AddComponent(); 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(); } 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("", "").Replace("", "").Replace("", "") .Replace("", ""); } } public sealed class RoomZone : MonoBehaviour { internal readonly HashSet Members = new HashSet(); 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 d__40 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Scene scene; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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 _zones = new List(); private static readonly Dictionary _mapInstancesByScene = new Dictionary(); 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 _lastZoneBySteam64 = new Dictionary(); 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(); 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[] 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 list = new List(); Player[] array = Object.FindObjectsOfType(); 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 list = new List(); Player[] array = Object.FindObjectsOfType(); 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(); 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(); 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() ?? ((Component)val).GetComponentInChildren(); 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(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 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(); 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(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(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 = component ?? ((Component)val2).gameObject.AddComponent(); 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(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(); 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 "[" + text + "] " + text2 + ": " + 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? EntryFeeCrownsEntry; internal static ConfigEntry? WalkAwayPenaltyCrownsEntry; internal static ConfigEntry? BlackjackTurnTimeoutSecondsEntry; internal static ConfigEntry? RouletteAfkTimeoutSecondsEntry; internal static ConfigEntry? SlotsPayoutMultiplierEntry; internal static ConfigEntry? BlackjackPayoutMultiplierEntry; internal static ConfigEntry? RoulettePayoutMultiplierEntry; internal static ConfigEntry? AllowedBetAmountsCsvEntry; internal static ConfigEntry? FloatingTableInfoEnabledEntry; internal static ConfigEntry? HudMessagesEnabledEntry; internal static ConfigEntry? GameFeedMessagesEnabledEntry; internal static ConfigEntry? RoomZoneChatEnabledEntry; internal static ConfigEntry? HideDecorativePropsEntry; internal static ConfigEntry? ReduceIdleSlotVisualsEntry; internal static ConfigEntry? ControllerInteractEnabledEntry; internal static ConfigEntry? ControllerInteractButtonEntry; internal static ConfigEntry? ControllerMacrosEnabledEntry; internal static ConfigEntry? ControllerMacro1ButtonEntry; internal static ConfigEntry? ControllerMacro1CommandEntry; internal static ConfigEntry? ControllerMacro2ButtonEntry; internal static ConfigEntry? ControllerMacro2CommandEntry; internal static ConfigEntry? ControllerMacro3ButtonEntry; internal static ConfigEntry? ControllerMacro3CommandEntry; internal static ConfigEntry? ControllerMacro4ButtonEntry; internal static ConfigEntry? ControllerMacro4CommandEntry; internal static ConfigEntry? JukeboxEnabledEntry; internal static ConfigEntry? JukeboxVolumeEntry; internal static ConfigEntry? PersonalJukeboxVolumeEntry; internal static ConfigEntry? JukeboxVolumeRangeMigratedEntry; internal static ConfigEntry? StreamLocalJukeboxSongsFromDiskEntry; internal static ConfigEntry? SuppressRoutineHeadlessLogsEntry; private static int? _syncedEntryFeeCrowns; private static int? _syncedWalkAwayPenaltyCrowns; private static int? _syncedBlackjackTurnTimeoutSeconds; private static int? _syncedRouletteAfkTimeoutSeconds; private static float? _syncedSlotsPayoutMultiplier; private static float? _syncedBlackjackPayoutMultiplier; private static float? _syncedRoulettePayoutMultiplier; private static string? _syncedAllowedBetAmountsCsv; internal static int EntryFeeCrowns => Clamp(EntryFeeCrownsEntry?.Value ?? 100, 0, 10000); internal static int WalkAwayPenaltyCrowns => Clamp(WalkAwayPenaltyCrownsEntry?.Value ?? 10, 0, 1000); internal static float BlackjackTurnTimeoutSeconds => Clamp(BlackjackTurnTimeoutSecondsEntry?.Value ?? 60, 15, 300); internal static float RouletteAfkTimeoutSeconds => Clamp(RouletteAfkTimeoutSecondsEntry?.Value ?? 60, 15, 300); internal static float SlotsPayoutMultiplier => Clamp(SlotsPayoutMultiplierEntry?.Value ?? 1f, 0f, 10f); internal static float BlackjackPayoutMultiplier => Clamp(BlackjackPayoutMultiplierEntry?.Value ?? 1f, 0f, 10f); internal static float RoulettePayoutMultiplier => Clamp(RoulettePayoutMultiplierEntry?.Value ?? 1f, 0f, 10f); internal static string AllowedBetAmountsCsv => NormalizeBetCsv(AllowedBetAmountsCsvEntry?.Value ?? "10,50,100,500"); internal static int SyncedEntryFeeCrowns => Clamp(_syncedEntryFeeCrowns.GetValueOrDefault(100), 0, 10000); internal static int SyncedWalkAwayPenaltyCrowns => Clamp(_syncedWalkAwayPenaltyCrowns.GetValueOrDefault(10), 0, 1000); internal static float SyncedBlackjackTurnTimeoutSeconds => Clamp(_syncedBlackjackTurnTimeoutSeconds.GetValueOrDefault(60), 15, 300); internal static float SyncedRouletteAfkTimeoutSeconds => Clamp(_syncedRouletteAfkTimeoutSeconds.GetValueOrDefault(60), 15, 300); internal static float SyncedSlotsPayoutMultiplier => Clamp(_syncedSlotsPayoutMultiplier.GetValueOrDefault(1f), 0f, 10f); internal static float SyncedBlackjackPayoutMultiplier => Clamp(_syncedBlackjackPayoutMultiplier.GetValueOrDefault(1f), 0f, 10f); internal static float SyncedRoulettePayoutMultiplier => Clamp(_syncedRoulettePayoutMultiplier.GetValueOrDefault(1f), 0f, 10f); internal static string SyncedAllowedBetAmountsCsv => NormalizeBetCsv(_syncedAllowedBetAmountsCsv ?? "10,50,100,500"); internal static int[] AllowedBetAmounts => ParseAllowedBets(AllowedBetAmountsCsv); internal static int[] SyncedAllowedBetAmounts => ParseAllowedBets(SyncedAllowedBetAmountsCsv); internal static bool FloatingTableInfoEnabled => FloatingTableInfoEnabledEntry?.Value ?? true; internal static bool HudMessagesEnabled => HudMessagesEnabledEntry?.Value ?? true; internal static bool GameFeedMessagesEnabled => GameFeedMessagesEnabledEntry?.Value ?? true; internal static bool RoomZoneChatEnabled => RoomZoneChatEnabledEntry?.Value ?? true; internal static bool HideDecorativeProps => HideDecorativePropsEntry?.Value ?? false; internal static bool ReduceIdleSlotVisuals => ReduceIdleSlotVisualsEntry?.Value ?? false; internal static bool ControllerInteractEnabled => ControllerInteractEnabledEntry?.Value ?? true; internal static GamepadButton ControllerInteractButton { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) ConfigEntry? controllerInteractButtonEntry = ControllerInteractButtonEntry; return SanitizeGamepadButton((GamepadButton)((controllerInteractButtonEntry == null) ? 2 : ((int)controllerInteractButtonEntry.Value)), (GamepadButton)2); } } internal static bool ControllerMacrosEnabled => ControllerMacrosEnabledEntry?.Value ?? false; internal static GamepadButton ControllerMacro1Button { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) ConfigEntry? controllerMacro1ButtonEntry = ControllerMacro1ButtonEntry; return SanitizeGamepadButton((GamepadButton)((controllerMacro1ButtonEntry == null) ? 4 : ((int)controllerMacro1ButtonEntry.Value)), (GamepadButton)4); } } internal static string ControllerMacro1Command => ControllerMacro1CommandEntry?.Value ?? ""; internal static GamepadButton ControllerMacro2Button { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) ConfigEntry? controllerMacro2ButtonEntry = ControllerMacro2ButtonEntry; return SanitizeGamepadButton((GamepadButton)((controllerMacro2ButtonEntry == null) ? 7 : ((int)controllerMacro2ButtonEntry.Value)), (GamepadButton)7); } } internal static string ControllerMacro2Command => ControllerMacro2CommandEntry?.Value ?? ""; internal static GamepadButton ControllerMacro3Button { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) ConfigEntry? controllerMacro3ButtonEntry = ControllerMacro3ButtonEntry; return SanitizeGamepadButton((GamepadButton)((controllerMacro3ButtonEntry == null) ? 5 : ((int)controllerMacro3ButtonEntry.Value)), (GamepadButton)5); } } internal static string ControllerMacro3Command => ControllerMacro3CommandEntry?.Value ?? ""; internal static GamepadButton ControllerMacro4Button { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) ConfigEntry? controllerMacro4ButtonEntry = ControllerMacro4ButtonEntry; return SanitizeGamepadButton((GamepadButton)((controllerMacro4ButtonEntry == null) ? 6 : ((int)controllerMacro4ButtonEntry.Value)), (GamepadButton)6); } } internal static string ControllerMacro4Command => ControllerMacro4CommandEntry?.Value ?? ""; internal static bool JukeboxEnabled => JukeboxEnabledEntry?.Value ?? true; internal static float JukeboxVolume => Clamp(JukeboxVolumeEntry?.Value ?? 5f, 0f, 10f); internal static float PersonalJukeboxVolume => Clamp(PersonalJukeboxVolumeEntry?.Value ?? 5f, 0f, 10f); internal static float EffectiveJukeboxVolume => Clamp(JukeboxVolume * 0.025f, 0f, 0.25f); internal static float EffectivePersonalJukeboxVolume => Clamp(PersonalJukeboxVolume * 0.025f, 0f, 0.25f); internal static bool StreamLocalJukeboxSongsFromDisk => StreamLocalJukeboxSongsFromDiskEntry?.Value ?? false; internal static bool SuppressRoutineHeadlessLogs => SuppressRoutineHeadlessLogsEntry?.Value ?? true; internal static void Bind(ConfigFile config) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Expected O, but got Unknown //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Expected O, but got Unknown EntryFeeCrownsEntry = config.Bind("Host Settings", "Entry Fee Crowns", 100, new ConfigDescription("Crowns charged when a player enters the casino. Change this from the main menu before hosting/loading a casino session.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 10000), Array.Empty())); WalkAwayPenaltyCrownsEntry = config.Bind("Host Settings", "Walk Away Penalty Crowns", 10, new ConfigDescription("Crowns charged when a player walks away from blackjack/roulette without using the leave command.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); BlackjackTurnTimeoutSecondsEntry = config.Bind("Host Settings", "Blackjack Turn Timeout Seconds", 60, new ConfigDescription("Seconds before the lobby host auto-stands an inactive blackjack player.", (AcceptableValueBase)(object)new AcceptableValueRange(15, 300), Array.Empty())); RouletteAfkTimeoutSecondsEntry = config.Bind("Host Settings", "Roulette AFK Timeout Seconds", 60, new ConfigDescription("Seconds before the lobby host removes an inactive roulette player.", (AcceptableValueBase)(object)new AcceptableValueRange(15, 300), Array.Empty())); SlotsPayoutMultiplierEntry = config.Bind("Host Settings", "Slots Payout Multiplier", 1f, new ConfigDescription("Scales slot-machine payouts. 1.0 = normal payouts, 0 = no crown payouts.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); BlackjackPayoutMultiplierEntry = config.Bind("Host Settings", "Blackjack Payout Multiplier", 1f, new ConfigDescription("Scales blackjack win profit. Refunds/pushes still return the original bet.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); RoulettePayoutMultiplierEntry = config.Bind("Host Settings", "Roulette Payout Multiplier", 1f, new ConfigDescription("Scales roulette win profit. Winning bets still refund the original stake.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); AllowedBetAmountsCsvEntry = config.Bind("Host Settings", "Allowed Bet Amounts CSV", "10,50,100,500", "Comma-separated allowed bet amounts. Include 0 to allow free-play/no-crown bets."); FloatingTableInfoEnabledEntry = config.Bind("Client Display", "Floating Table Info Enabled", true, "Shows floating table info text above active blackjack and roulette tables."); HudMessagesEnabledEntry = config.Bind("Client Display", "HUD Messages Enabled", true, "Shows casino center-screen HUD prompts."); GameFeedMessagesEnabledEntry = config.Bind("Client Display", "Game Feed Messages Enabled", true, "Shows casino messages in the bottom-left game feed/chat log."); RoomZoneChatEnabledEntry = config.Bind("Client Chat", "Private Room Zone Chat Enabled", true, "Allows RoomZone triggers to privately route your Zone chat and display private room-zone messages on this client."); HideDecorativePropsEntry = config.Bind("Client Performance", "Hide Decorative Props", false, "Hides non-gameplay casino props such as bar clutter, bottles, and plants. Tables, slots, info signs, entrance objects, walls, floors, and portals stay visible."); ReduceIdleSlotVisualsEntry = config.Bind("Client Performance", "Reduce Idle Slot Visuals", false, "Disables slot screen/reel renderers while a slot machine is idle. The visuals turn back on automatically during local or remote spins."); ControllerInteractEnabledEntry = config.Bind("Client Controls", "Controller Interact Enabled", true, "Allows a controller button to trigger the same casino interactions as the F key."); ControllerInteractButtonEntry = config.Bind("Client Controls", "Controller Interact Button", (GamepadButton)2, "Controller button used for casino interactions. Default is Button_West, matching vanilla ATLYSS X / Square."); ControllerMacrosEnabledEntry = config.Bind("Client Controls", "Controller Command Macros Enabled", false, "Allows configured controller buttons to run local casino slash commands while inside the casino."); ControllerMacro1ButtonEntry = config.Bind("Client Controls", "Controller Macro 1 Button", (GamepadButton)4, "Controller button for macro slot 1."); ControllerMacro1CommandEntry = config.Bind("Client Controls", "Controller Macro 1 Command", "", "Casino slash command for macro slot 1."); ControllerMacro2ButtonEntry = config.Bind("Client Controls", "Controller Macro 2 Button", (GamepadButton)7, "Controller button for macro slot 2."); ControllerMacro2CommandEntry = config.Bind("Client Controls", "Controller Macro 2 Command", "", "Casino slash command for macro slot 2."); ControllerMacro3ButtonEntry = config.Bind("Client Controls", "Controller Macro 3 Button", (GamepadButton)5, "Controller button for macro slot 3."); ControllerMacro3CommandEntry = config.Bind("Client Controls", "Controller Macro 3 Command", "", "Casino slash command for macro slot 3."); ControllerMacro4ButtonEntry = config.Bind("Client Controls", "Controller Macro 4 Button", (GamepadButton)6, "Controller button for macro slot 4."); ControllerMacro4CommandEntry = config.Bind("Client Controls", "Controller Macro 4 Command", "", "Casino slash command for macro slot 4."); JukeboxEnabledEntry = config.Bind("Client Audio", "Jukebox Enabled", true, "Enables casino jukebox audio and personal jukebox command playback on this client."); JukeboxVolumeEntry = config.Bind("Client Audio", "Jukebox Volume", 5f, new ConfigDescription("Normalized volume for the in-casino jukebox on this client. Range is 0-10; output is capped for background playback.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); PersonalJukeboxVolumeEntry = config.Bind("Client Audio", "Personal Jukebox Volume", 5f, new ConfigDescription("Normalized volume for local-only /play jukebox command playback. Range is 0-10; output is capped for background playback.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); JukeboxVolumeRangeMigratedEntry = config.Bind("Client Audio", "Jukebox Volume 0-10 Migration Applied", false, "Internal migration marker. When false, old 0-1 jukebox volume values are converted to the new 0-10 slider range."); MigrateJukeboxVolumeRange(); StreamLocalJukeboxSongsFromDiskEntry = config.Bind("Client Audio", "Stream Local Songs From Disk", false, "Streams local jukebox songs from disk while loading them. Uses less memory, but can be less flexible than fully loaded clips."); SuppressRoutineHeadlessLogsEntry = config.Bind("Server", "Suppress Routine Headless Logs", true, "When true, dedicated/headless servers only print casino error/fatal logs."); } private static void MigrateJukeboxVolumeRange() { if (JukeboxVolumeRangeMigratedEntry != null && !JukeboxVolumeRangeMigratedEntry.Value) { if (JukeboxVolumeEntry != null && JukeboxVolumeEntry.Value >= 0f && JukeboxVolumeEntry.Value <= 1f) { JukeboxVolumeEntry.Value = Clamp(JukeboxVolumeEntry.Value * 10f, 0f, 10f); } if (PersonalJukeboxVolumeEntry != null && PersonalJukeboxVolumeEntry.Value >= 0f && PersonalJukeboxVolumeEntry.Value <= 1f) { PersonalJukeboxVolumeEntry.Value = Clamp(PersonalJukeboxVolumeEntry.Value * 10f, 0f, 10f); } JukeboxVolumeRangeMigratedEntry.Value = true; } } internal static void ApplyHostGameplaySync(int entryFeeCrowns, int walkAwayPenaltyCrowns, int blackjackTurnTimeoutSeconds, int rouletteAfkTimeoutSeconds, float slotsPayoutMultiplier, float blackjackPayoutMultiplier, float roulettePayoutMultiplier, string? allowedBetAmountsCsv) { _syncedEntryFeeCrowns = Clamp(entryFeeCrowns, 0, 10000); _syncedWalkAwayPenaltyCrowns = Clamp(walkAwayPenaltyCrowns, 0, 1000); _syncedBlackjackTurnTimeoutSeconds = Clamp(blackjackTurnTimeoutSeconds, 15, 300); _syncedRouletteAfkTimeoutSeconds = Clamp(rouletteAfkTimeoutSeconds, 15, 300); _syncedSlotsPayoutMultiplier = Clamp(slotsPayoutMultiplier, 0f, 10f); _syncedBlackjackPayoutMultiplier = Clamp(blackjackPayoutMultiplier, 0f, 10f); _syncedRoulettePayoutMultiplier = Clamp(roulettePayoutMultiplier, 0f, 10f); _syncedAllowedBetAmountsCsv = NormalizeBetCsv(allowedBetAmountsCsv ?? "10,50,100,500"); } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } private static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (value > max) { return max; } return value; } private static GamepadButton SanitizeGamepadButton(GamepadButton value, GamepadButton fallback) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if ((int)value == 26) { return fallback; } if ((int)value == 27) { return fallback; } return value; } private static string NormalizeBetCsv(string? csv) { int[] values = ParseAllowedBets(csv); return string.Join(",", values); } private static int[] ParseAllowedBets(string? csv) { SortedSet sortedSet = new SortedSet(); if (!string.IsNullOrWhiteSpace(csv)) { string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && int.TryParse(text, out var result)) { if (result < 0) { result = 0; } if (result > 1000000) { result = 1000000; } sortedSet.Add(result); } } } if (sortedSet.Count == 0) { sortedSet.Add(10); sortedSet.Add(50); sortedSet.Add(100); sortedSet.Add(500); } int[] array2 = new int[sortedSet.Count]; sortedSet.CopyTo(array2); return array2; } } internal static class CasinoEasySettings { [HarmonyPatch(typeof(SettingsManager), "Open_SettingsMenu")] private static class SettingsOpenPatch { private static void Postfix() { RefreshInteractable(); } } [HarmonyPatch(typeof(SettingsManager), "Set_SettingMenuSelectionIndex")] private static class SettingsSelectionPatch { private static void Postfix() { RefreshInteractable(); } } [CompilerGenerated] private static class <>O { public static UnityAction <0>__BuildTab; public static UnityAction <1>__RefreshInteractable; } private static bool _registered; private static bool _built; private static AtlyssHeader? _statusHeader; private static readonly List> _interactableSetters = new List>(); private static readonly List> _localInteractableSetters = new List>(); private static readonly GamepadButton[] _gamepadButtons; private static readonly string[] _gamepadButtonLabels; internal static void Register() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_004d: 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_0058: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown if (!_registered) { _registered = true; UnityEvent onInitialized = Settings.OnInitialized; object obj = <>O.<0>__BuildTab; if (obj == null) { UnityAction val = BuildTab; <>O.<0>__BuildTab = val; obj = (object)val; } onInitialized.AddListener((UnityAction)obj); UnityEvent onApplySettings = Settings.OnApplySettings; object obj2 = <>O.<1>__RefreshInteractable; if (obj2 == null) { UnityAction val2 = RefreshInteractable; <>O.<1>__RefreshInteractable = val2; obj2 = (object)val2; } onApplySettings.AddListener((UnityAction)obj2); UnityEvent onCloseSettings = Settings.OnCloseSettings; object obj3 = <>O.<1>__RefreshInteractable; if (obj3 == null) { UnityAction val3 = RefreshInteractable; <>O.<1>__RefreshInteractable = val3; obj3 = (object)val3; } onCloseSettings.AddListener((UnityAction)obj3); } } private static void BuildTab() { //IL_0445: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Expected O, but got Unknown if (_built) { RefreshInteractable(); return; } _built = true; SettingsTab orAddCustomTab = Settings.GetOrAddCustomTab("Atlyss Casino"); _statusHeader = orAddCustomTab.AddHeader("Casino settings load here."); orAddCustomTab.AddHeader("Host Settings"); Track(orAddCustomTab.AddAdvancedSlider("Entry Fee", CasinoConfig.EntryFeeCrownsEntry)); Track(orAddCustomTab.AddAdvancedSlider("Walk-Away Penalty", CasinoConfig.WalkAwayPenaltyCrownsEntry)); Track(orAddCustomTab.AddAdvancedSlider("BJ Turn Timeout", CasinoConfig.BlackjackTurnTimeoutSecondsEntry)); Track(orAddCustomTab.AddAdvancedSlider("Roulette AFK Timeout", CasinoConfig.RouletteAfkTimeoutSecondsEntry)); Track(orAddCustomTab.AddAdvancedSlider("Slots Payout x", CasinoConfig.SlotsPayoutMultiplierEntry, false)); Track(orAddCustomTab.AddAdvancedSlider("Blackjack Payout x", CasinoConfig.BlackjackPayoutMultiplierEntry, false)); Track(orAddCustomTab.AddAdvancedSlider("Roulette Payout x", CasinoConfig.RoulettePayoutMultiplierEntry, false)); Track(orAddCustomTab.AddTextField("Allowed Bets CSV", CasinoConfig.AllowedBetAmountsCsvEntry, "10,50,100,500")); orAddCustomTab.AddHeader("Client Display"); Track(orAddCustomTab.AddToggle("Floating Table Info", CasinoConfig.FloatingTableInfoEnabledEntry)); Track(orAddCustomTab.AddToggle("HUD Messages", CasinoConfig.HudMessagesEnabledEntry)); Track(orAddCustomTab.AddToggle("Game Feed Messages", CasinoConfig.GameFeedMessagesEnabledEntry)); orAddCustomTab.AddHeader("Client Chat"); TrackLocal(orAddCustomTab.AddToggle("Private Zone Chat", CasinoConfig.RoomZoneChatEnabledEntry)); orAddCustomTab.AddHeader("Client Audio"); TrackLocal(orAddCustomTab.AddToggle("Jukebox Enabled", CasinoConfig.JukeboxEnabledEntry)); TrackLocal(orAddCustomTab.AddAdvancedSlider("Jukebox Volume", CasinoConfig.JukeboxVolumeEntry, false)); TrackLocal(orAddCustomTab.AddAdvancedSlider("Personal Jukebox Vol", CasinoConfig.PersonalJukeboxVolumeEntry, false)); TrackLocal(orAddCustomTab.AddToggle("Stream Local Songs", CasinoConfig.StreamLocalJukeboxSongsFromDiskEntry)); orAddCustomTab.AddHeader("Client Performance"); TrackLocal(orAddCustomTab.AddToggle("Hide Decor Props", CasinoConfig.HideDecorativePropsEntry)); TrackLocal(orAddCustomTab.AddToggle("Idle Slot Screens Off", CasinoConfig.ReduceIdleSlotVisualsEntry)); orAddCustomTab.AddHeader("Client Controls"); TrackLocal(orAddCustomTab.AddToggle("Controller Interact", CasinoConfig.ControllerInteractEnabledEntry)); TrackLocal(AddGamepadButtonDropdown(orAddCustomTab, "Controller Interact Button", CasinoConfig.ControllerInteractButtonEntry, (GamepadButton)2)); orAddCustomTab.AddHeader("Server"); Track(orAddCustomTab.AddToggle("Quiet Headless Logs", CasinoConfig.SuppressRoutineHeadlessLogsEntry)); orAddCustomTab.AddHeader("Controller Command Macros"); AtlyssButton macroToggle = orAddCustomTab.AddButton("Show controller macros"); TrackLocal(macroToggle); List macroElements = new List(); AtlyssToggle val = orAddCustomTab.AddToggle("Enable Controller Macros", CasinoConfig.ControllerMacrosEnabledEntry); TrackLocal(val); macroElements.Add((BaseAtlyssElement)(object)val); AtlyssDropdown val2 = AddGamepadButtonDropdown(orAddCustomTab, "Macro 1 Button", CasinoConfig.ControllerMacro1ButtonEntry, (GamepadButton)4); TrackLocal(val2); macroElements.Add((BaseAtlyssElement)(object)val2); AtlyssTextField val3 = orAddCustomTab.AddTextField("Macro 1 Command", CasinoConfig.ControllerMacro1CommandEntry, "/ready"); TrackLocal(val3); macroElements.Add((BaseAtlyssElement)(object)val3); AtlyssDropdown val4 = AddGamepadButtonDropdown(orAddCustomTab, "Macro 2 Button", CasinoConfig.ControllerMacro2ButtonEntry, (GamepadButton)7); TrackLocal(val4); macroElements.Add((BaseAtlyssElement)(object)val4); AtlyssTextField val5 = orAddCustomTab.AddTextField("Macro 2 Command", CasinoConfig.ControllerMacro2CommandEntry, "/hit"); TrackLocal(val5); macroElements.Add((BaseAtlyssElement)(object)val5); AtlyssDropdown val6 = AddGamepadButtonDropdown(orAddCustomTab, "Macro 3 Button", CasinoConfig.ControllerMacro3ButtonEntry, (GamepadButton)5); TrackLocal(val6); macroElements.Add((BaseAtlyssElement)(object)val6); AtlyssTextField val7 = orAddCustomTab.AddTextField("Macro 3 Command", CasinoConfig.ControllerMacro3CommandEntry, "/stand"); TrackLocal(val7); macroElements.Add((BaseAtlyssElement)(object)val7); AtlyssDropdown val8 = AddGamepadButtonDropdown(orAddCustomTab, "Macro 4 Button", CasinoConfig.ControllerMacro4ButtonEntry, (GamepadButton)6); TrackLocal(val8); macroElements.Add((BaseAtlyssElement)(object)val8); AtlyssTextField val9 = orAddCustomTab.AddTextField("Macro 4 Command", CasinoConfig.ControllerMacro4CommandEntry, "/leave"); TrackLocal(val9); macroElements.Add((BaseAtlyssElement)(object)val9); bool macrosVisible = false; SetElementGroupVisible(macroElements, macrosVisible); macroToggle.OnClicked.AddListener((UnityAction)delegate { macrosVisible = !macrosVisible; SetElementGroupVisible(macroElements, macrosVisible); SetButtonLabel(macroToggle, macrosVisible ? "Hide controller macros" : "Show controller macros"); }); RefreshInteractable(); } internal static void RefreshInteractable() { bool flag = CanEditSettingsNow(); foreach (Action interactableSetter in _interactableSetters) { try { interactableSetter(flag); } catch { } } foreach (Action localInteractableSetter in _localInteractableSetters) { try { localInteractableSetter(obj: true); } catch { } } if (_statusHeader != null) { ((BaseAtlyssLabelElement)_statusHeader).LabelText = (flag ? "Gameplay/server values are editable before loading a session. Client audio, chat, performance, controls, and macros stay local." : "Gameplay/server values are locked while loaded. Client audio, chat, performance, controls, and macros remain editable."); } RoomZoneChatNetcode.PublishLocalPreference(); } private static bool CanEditSettingsNow() { //IL_0026: 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) if (Plugin.IsHeadlessServer) { return false; } if ((Object)(object)Player._mainPlayer != (Object)null) { return false; } Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (((Scene)(ref sceneByName)).IsValid() && ((Scene)(ref sceneByName)).isLoaded) { return false; } return true; } private static void Track(AtlyssAdvancedSlider slider) { AtlyssAdvancedSlider slider2 = slider; _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)slider2.Slider != (Object)null) { ((Selectable)slider2.Slider).interactable = interactable; } if ((Object)(object)slider2.ResetButton != (Object)null) { ((Selectable)slider2.ResetButton).interactable = interactable; } }); } private static void TrackLocal(AtlyssAdvancedSlider slider) { AtlyssAdvancedSlider slider2 = slider; _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)slider2.Slider != (Object)null) { ((Selectable)slider2.Slider).interactable = interactable; } if ((Object)(object)slider2.ResetButton != (Object)null) { ((Selectable)slider2.ResetButton).interactable = interactable; } }); } private static void Track(AtlyssToggle toggle) { AtlyssToggle toggle2 = toggle; _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)toggle2.Toggle != (Object)null) { ((Selectable)toggle2.Toggle).interactable = interactable; } }); } private static void TrackLocal(AtlyssToggle toggle) { AtlyssToggle toggle2 = toggle; _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)toggle2.Toggle != (Object)null) { ((Selectable)toggle2.Toggle).interactable = interactable; } }); } private static void Track(AtlyssTextField textField) { AtlyssTextField textField2 = textField; _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)textField2.InputField != (Object)null) { ((Selectable)textField2.InputField).interactable = interactable; } }); } private static void TrackLocal(AtlyssTextField textField) { AtlyssTextField textField2 = textField; _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)textField2.InputField != (Object)null) { ((Selectable)textField2.InputField).interactable = interactable; } }); } private static void Track(AtlyssDropdown dropdown) { AtlyssDropdown dropdown2 = dropdown; _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)dropdown2.Dropdown != (Object)null) { ((Selectable)dropdown2.Dropdown).interactable = interactable; } }); } private static void TrackLocal(AtlyssDropdown dropdown) { AtlyssDropdown dropdown2 = dropdown; _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)dropdown2.Dropdown != (Object)null) { ((Selectable)dropdown2.Dropdown).interactable = interactable; } }); } private static void Track(AtlyssButton button) { AtlyssButton button2 = button; _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)button2.Button != (Object)null) { ((Selectable)button2.Button).interactable = interactable; } }); } private static void TrackLocal(AtlyssButton button) { AtlyssButton button2 = button; _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)button2.Button != (Object)null) { ((Selectable)button2.Button).interactable = interactable; } }); } private static AtlyssDropdown AddGamepadButtonDropdown(SettingsTab tab, string label, ConfigEntry config, GamepadButton fallback) { //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) //IL_001b: 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) ConfigEntry config2 = config; int gamepadButtonIndex = GetGamepadButtonIndex(config2.Value, fallback); AtlyssDropdown val = tab.AddDropdown(label, _gamepadButtonLabels, gamepadButtonIndex); val.OnValueChanged.AddListener((UnityAction)delegate(int newValue) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (newValue < 0 || newValue >= _gamepadButtons.Length) { newValue = GetGamepadButtonIndex(fallback, fallback); } config2.Value = _gamepadButtons[newValue]; }); return val; } private static int GetGamepadButtonIndex(GamepadButton value, GamepadButton fallback) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between I4 and Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between I4 and Unknown for (int i = 0; i < _gamepadButtons.Length; i++) { if ((int)_gamepadButtons[i] == (int)value) { return i; } } for (int j = 0; j < _gamepadButtons.Length; j++) { if ((int)_gamepadButtons[j] == (int)fallback) { return j; } } return 0; } private static void SetElementGroupVisible(List elements, bool visible) { foreach (BaseAtlyssElement element in elements) { try { if ((Object)(object)element?.Root != (Object)null) { ((Component)element.Root).gameObject.SetActive(visible); } } catch { } } } private static void SetButtonLabel(AtlyssButton button, string label) { try { object obj = typeof(AtlyssButton).GetField("ButtonLabel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(button); obj?.GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.SetValue(obj, label); } catch { } } static CasinoEasySettings() { GamepadButton[] array = new GamepadButton[26]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); _gamepadButtons = (GamepadButton[])(object)array; _gamepadButtonLabels = new string[26] { "A / Cross", "B / Circle", "X / Square", "Y / Triangle", "D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right", "Left Shoulder", "Left Trigger", "Left Stick", "Right Shoulder", "Right Trigger", "Right Stick", "Back", "Start", "Center", "Special", "Left Stick Up", "Left Stick Down", "Left Stick Left", "Left Stick Right", "Right Stick Up", "Right Stick Down", "Right Stick Left", "Right Stick Right" }; } } [BepInPlugin("dev.seth.atlysscasino", "Atlyss Casino", "1.9.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public static CasinoLog Log = null; public static AssetBundle? AssetsBundle = null; private static int _currentBet = 100; public static bool HasSetBet = false; private static int _blackjackBet = 100; public static bool HasSetBlackjackBet = false; private static int _rouletteBet = 100; public static bool HasSetRouletteBet = false; public const ulong CASINO_OWNER_STEAM64 = 76561198284196478uL; public static bool OwnerLuckEnabled = false; private static readonly FieldInfo? ChatMaxOnscreenField = typeof(ChatBehaviour).GetField("maxOnscreenMessages", BindingFlags.Instance | BindingFlags.NonPublic) ?? typeof(ChatBehaviour).GetField("_maxOnscreenMessages", BindingFlags.Instance | BindingFlags.NonPublic); public const string COLOR_INFO = "#FFDC96"; public const string COLOR_ERROR = "#FF3119"; public const string CASINO_SCENE_NAME = "AtlyssCasino"; private static Type? _eventSystemTypeCache; public static Plugin Instance { get; private set; } = null; public static bool IsHeadlessServer => HostCasinoVisibilityWatcher.IsHeadlessServer; public static int CurrentBet { get { return CoerceBetToConfiguredList(_currentBet); } set { _currentBet = value; } } public static int BlackjackBet { get { return CoerceBetToConfiguredList(_blackjackBet); } set { _blackjackBet = value; } } public static int RouletteBet { get { return CoerceBetToConfiguredList(_rouletteBet); } set { _rouletteBet = value; } } private static bool UseLocalGameplayConfig { get { if (IsHeadlessServer) { return true; } if ((Object)(object)Player._mainPlayer == (Object)null) { return true; } try { return BJNetcode.AmHost(); } catch { return false; } } } public static int EntryFeeCrowns => UseLocalGameplayConfig ? CasinoConfig.EntryFeeCrowns : CasinoConfig.SyncedEntryFeeCrowns; public static int WalkAwayPenaltyCrowns => UseLocalGameplayConfig ? CasinoConfig.WalkAwayPenaltyCrowns : CasinoConfig.SyncedWalkAwayPenaltyCrowns; public static float BlackjackTurnTimeoutSeconds => UseLocalGameplayConfig ? CasinoConfig.BlackjackTurnTimeoutSeconds : CasinoConfig.SyncedBlackjackTurnTimeoutSeconds; public static float RouletteAfkTimeoutSeconds => UseLocalGameplayConfig ? CasinoConfig.RouletteAfkTimeoutSeconds : CasinoConfig.SyncedRouletteAfkTimeoutSeconds; public static float SlotsPayoutMultiplier => UseLocalGameplayConfig ? CasinoConfig.SlotsPayoutMultiplier : CasinoConfig.SyncedSlotsPayoutMultiplier; public static float BlackjackPayoutMultiplier => UseLocalGameplayConfig ? CasinoConfig.BlackjackPayoutMultiplier : CasinoConfig.SyncedBlackjackPayoutMultiplier; public static float RoulettePayoutMultiplier => UseLocalGameplayConfig ? CasinoConfig.RoulettePayoutMultiplier : CasinoConfig.SyncedRoulettePayoutMultiplier; public static int[] AllowedBets => UseLocalGameplayConfig ? CasinoConfig.AllowedBetAmounts : CasinoConfig.SyncedAllowedBetAmounts; public static bool FloatingTableInfoEnabled => CasinoConfig.FloatingTableInfoEnabled; public static bool HudMessagesEnabled => CasinoConfig.HudMessagesEnabled; public static bool GameFeedMessagesEnabled => CasinoConfig.GameFeedMessagesEnabled; public static bool HideDecorativeProps => CasinoConfig.HideDecorativeProps; public static bool ReduceIdleSlotVisuals => CasinoConfig.ReduceIdleSlotVisuals; public static bool SuppressRoutineHeadlessLogs => CasinoConfig.SuppressRoutineHeadlessLogs; public static bool JukeboxEnabled => CasinoConfig.JukeboxEnabled; public static float JukeboxVolume => CasinoConfig.JukeboxVolume; public static float PersonalJukeboxVolume => CasinoConfig.PersonalJukeboxVolume; private void Awake() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown Instance = this; Log = new CasinoLog(((BaseUnityPlugin)this).Logger); CasinoConfig.Bind(((BaseUnityPlugin)this).Config); CasinoEasySettings.Register(); LoadAssetBundle(); CasinoJukeboxManager.Init(); CasinoJukeboxPersonalPlayer.Init(); CasinoPatch.Init(); RoomZoneRegistry.Init(); SelfPortraitVisibility.Init(); BlackjackSceneWatcher.Init(); HostCasinoVisibilityWatcher.Init(); CasinoControllerMacros.Init(); BJNetcode.Initialize(); RNNetcode.Initialize(); JukeboxNetcode.Initialize(); RoomZoneChatNetcode.Initialize(); Harmony val = new Harmony("dev.seth.atlysscasino"); val.PatchAll(); Log.LogInfo("Atlyss Casino loaded!"); Log.LogInfo("[Casino] Slots: /slotbet | Blackjack: /blackjackbet /ready /start /hit /stand /leave | Roulette: /tbet /rbet /rclearbets /rstandby /rspin /rleave | Jukebox: /jukebox /next /prev /stop | Help: /casinohelp"); } public static bool IsLocalPlayerInCasino() { return HostCasinoVisibilityWatcher.IsLocalPlayerInsideCasinoBounds(); } public static bool IsTypingInUI() { if (IsChatBehaviourFocused()) { return true; } try { Type type = ResolveEventSystemType(); if (type == null) { return false; } PropertyInfo property = type.GetProperty("current", BindingFlags.Static | BindingFlags.Public); if (property == null) { return false; } object value = property.GetValue(null); if (value == null) { return false; } PropertyInfo property2 = type.GetProperty("currentSelectedGameObject"); if (property2 == null) { return false; } object? value2 = property2.GetValue(value); GameObject val = (GameObject)((value2 is GameObject) ? value2 : null); if ((Object)(object)val == (Object)null) { return false; } Component[] components = val.GetComponents(); foreach (Component val2 in components) { if (!((Object)(object)val2 == (Object)null)) { string name = ((object)val2).GetType().Name; if ((name == "InputField" || name == "TMP_InputField") && IsComponentActuallyFocused(val2)) { return true; } } } } catch { } return false; } private static bool IsChatBehaviourFocused() { try { ChatBehaviour val = Object.FindObjectOfType(); if ((Object)(object)val == (Object)null) { return false; } Type type = ((object)val).GetType(); string[] array = new string[4] { "_focusedInChat", "_inChat", "_isInChat", "_chatFocused" }; bool flag = default(bool); for (int i = 0; i < array.Length; i++) { FieldInfo field = type.GetField(array[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null)) { object value = field.GetValue(val); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } } } } catch { } return false; } private static bool IsComponentActuallyFocused(Component component) { try { PropertyInfo property = ((object)component).GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.GetValue(component) is bool flag && !flag) { return false; } if (!component.gameObject.activeInHierarchy) { return false; } PropertyInfo property2 = ((object)component).GetType().GetProperty("isFocused", BindingFlags.Instance | BindingFlags.Public); if (property2 == null) { return false; } object value = property2.GetValue(component); bool flag2 = default(bool); int num; if (value is bool) { flag2 = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag2 ? 1u : 0u)) != 0; } catch { return false; } } public static bool IsCasinoOwner(ulong steam64) { return steam64 == 76561198284196478L; } public static bool ShouldRigOwnerLuck(ulong steam64) { return OwnerLuckEnabled && IsCasinoOwner(steam64); } public static void ShowGameFeed(string message) { try { ShowGameFeed(Object.FindObjectOfType(), message); } catch { } } public static void ShowGameFeed(ChatBehaviour? chat, string message) { if (!GameFeedMessagesEnabled || !IsLocalPlayerInCasino() || (Object)(object)chat == (Object)null) { return; } try { if (ChatMaxOnscreenField != null) { try { ChatMaxOnscreenField.SetValue(chat, 50); } catch { } } chat.Init_GameLogicMessage(message); } catch { } } private static int CoerceBetToConfiguredList(int value) { int[] allowedBets = AllowedBets; if (allowedBets == null || allowedBets.Length == 0) { return 0; } for (int i = 0; i < allowedBets.Length; i++) { if (allowedBets[i] == value) { return value; } } for (int j = 0; j < allowedBets.Length; j++) { if (allowedBets[j] == 100) { return 100; } } return allowedBets[0]; } private static Type? ResolveEventSystemType() { if (_eventSystemTypeCache != null) { return _eventSystemTypeCache; } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("UnityEngine.EventSystems.EventSystem"); if (type != null) { _eventSystemTypeCache = type; return type; } } } catch { } return null; } public static void ShowHUDInfo(string message) { ShowHUDOnce(message, "#FFDC96"); } public static void ShowHUDError(string message) { ShowHUDOnce(message, "#FF3119"); } private static void ShowHUDOnce(string message, string colorHex) { if (!HudMessagesEnabled || !IsLocalPlayerInCasino()) { return; } try { ErrorPromptTextManager.current.Init_ErrorPrompt(WrapColor(message, colorHex)); } catch { } } public static string WrapColor(string message, string colorHex) { if (string.IsNullOrEmpty(message)) { return message ?? string.Empty; } if (message.StartsWith("" + message + ""; } public static void ShowHUD(string message, float duration = 8f, float refreshInterval = 2.8f) { ShowHUDInfo(message); } public static void ShowHUDErrorLong(string message, float duration = 6f, float refreshInterval = 2.8f) { ShowHUDError(message); } public static void ScopeToCasinoScene(GameObject go) { //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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { return; } Scene scene = go.scene; if (((Scene)(ref scene)).IsValid()) { scene = go.scene; if (((Scene)(ref scene)).name == "AtlyssCasino") { return; } } Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { CasinoLog log = Log; string[] obj = new string[5] { "[Casino] ScopeToCasinoScene called but casino scene isn't loaded — '", ((Object)go).name, "' will stay in scene '", null, null }; scene = go.scene; object obj2; if (!((Scene)(ref scene)).IsValid()) { obj2 = ""; } else { scene = go.scene; obj2 = ((Scene)(ref scene)).name; } obj[3] = (string)obj2; obj[4] = "'."; log.LogWarning(string.Concat(obj)); return; } try { SceneManager.MoveGameObjectToScene(go, sceneByName); } catch (Exception ex) { Log.LogWarning("[Casino] ScopeToCasinoScene failed for '" + ((Object)go).name + "': " + ex.Message); } } public static void ApplyWalkAwayPenalty() { try { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)(object)component == (Object)null) { Log.LogWarning("[Casino] Walk-away penalty: no PlayerInventory on local player."); return; } int heldCurrency = component._heldCurrency; int walkAwayPenaltyCrowns = WalkAwayPenaltyCrowns; int num = Math.Min(walkAwayPenaltyCrowns, heldCurrency); if (num > 0) { component.Network_heldCurrency = heldCurrency - num; } string message = ((walkAwayPenaltyCrowns <= 0) ? "Use commands to leave next time. (No Crowns penalty configured.)" : ((num >= walkAwayPenaltyCrowns) ? ("Use commands to leave next time. " + $"-{walkAwayPenaltyCrowns} Crowns penalty.") : ((num <= 0) ? "Use commands to leave next time. (No Crowns to charge.)" : ("Use commands to leave next time. " + $"-{num} Crowns (couldn't afford full " + $"{walkAwayPenaltyCrowns}).")))); ShowHUDError(message); Log.LogInfo($"[Casino] Walk-away penalty: charged {num} Crowns " + $"(balance was {heldCurrency}, now {heldCurrency - num})."); } catch (Exception ex) { Log.LogError("[Casino] ApplyWalkAwayPenalty failed: " + ex.Message); } } private void LoadAssetBundle() { try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string text = Path.Combine(directoryName, "atlysscasino_assets"); AssetsBundle = AssetBundle.LoadFromFile(text); if ((Object)(object)AssetsBundle == (Object)null) { Log.LogError("[Casino] Failed to load asset bundle at '" + text + "'"); } else { Log.LogInfo("[Casino] Assets bundle loaded."); } } catch (Exception ex) { Log.LogError("[Casino] Bundle load exception: " + ex.Message); } } } public class CasinoLog { private readonly ManualLogSource _inner; public CasinoLog(ManualLogSource inner) { _inner = inner; } public void LogError(object data) { _inner.LogError(data); } public void LogFatal(object data) { _inner.LogFatal(data); } public void LogWarning(object data) { if (!Plugin.IsHeadlessServer || !Plugin.SuppressRoutineHeadlessLogs) { _inner.LogWarning(data); } } public void LogInfo(object data) { if (!Plugin.IsHeadlessServer || !Plugin.SuppressRoutineHeadlessLogs) { _inner.LogInfo(data); } } public void LogDebug(object data) { if (!Plugin.IsHeadlessServer || !Plugin.SuppressRoutineHeadlessLogs) { _inner.LogDebug(data); } } public void LogMessage(object data) { if (!Plugin.IsHeadlessServer || !Plugin.SuppressRoutineHeadlessLogs) { _inner.LogMessage(data); } } } internal class CasinoControllerMacros : MonoBehaviour { private const float MACRO_COOLDOWN = 0.25f; private static CasinoControllerMacros? _instance; private float _nextMacroTime; internal static void Init() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("AtlyssCasino_ControllerMacros"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; _instance = val.AddComponent(); } } private void Update() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (CasinoConfig.ControllerMacrosEnabled && !Plugin.IsHeadlessServer && !HostCasinoVisibilityWatcher.IsServerMode && Plugin.IsLocalPlayerInCasino() && !Plugin.IsTypingInUI() && !(Time.unscaledTime < _nextMacroTime) && !TryRunMacro(CasinoConfig.ControllerMacro1Button, CasinoConfig.ControllerMacro1Command) && !TryRunMacro(CasinoConfig.ControllerMacro2Button, CasinoConfig.ControllerMacro2Command) && !TryRunMacro(CasinoConfig.ControllerMacro3Button, CasinoConfig.ControllerMacro3Command)) { TryRunMacro(CasinoConfig.ControllerMacro4Button, CasinoConfig.ControllerMacro4Command); } } private bool TryRunMacro(GamepadButton button, string command) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(command)) { return false; } if (!CasinoInput.WasControllerButtonPressed(button)) { return false; } _nextMacroTime = Time.unscaledTime + 0.25f; CommandHandler.TryRunLocalCasinoMacro(command); return true; } } internal static class CasinoInput { internal static string InteractPrompt { get { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!CasinoConfig.ControllerInteractEnabled) { return "F"; } string text = ButtonLabel(CasinoConfig.ControllerInteractButton); if (string.IsNullOrEmpty(text)) { return "F"; } return "F / " + text; } } internal static bool WasInteractPressed() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)102)) { return true; } if (!CasinoConfig.ControllerInteractEnabled) { return false; } return WasControllerButtonPressed(CasinoConfig.ControllerInteractButton); } internal static bool WasControllerButtonPressed(GamepadButton button) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((int)button == 26) { return false; } if ((int)button == 27) { return false; } try { return GamepadInput.GetButtonDown(button); } catch { return false; } } internal static string ButtonLabel(GamepadButton button) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected I4, but got Unknown return (int)button switch { 0 => "A", 1 => "B", 2 => "X", 3 => "Y", 4 => "D-Pad Up", 5 => "D-Pad Down", 6 => "D-Pad Left", 7 => "D-Pad Right", 8 => "LB", 9 => "LT", 10 => "LS", 11 => "RB", 12 => "RT", 13 => "RS", 14 => "Back", 15 => "Start", 16 => "Center", 17 => "Special", 18 => "Left Stick Up", 19 => "Left Stick Down", 20 => "Left Stick Left", 21 => "Left Stick Right", 22 => "Right Stick Up", 23 => "Right Stick Down", 24 => "Right Stick Left", 25 => "Right Stick Right", _ => "", }; } } [HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")] public static class CommandHandler { [HarmonyPrefix] [HarmonyPriority(800)] public static bool Prefix(ChatBehaviour __instance, string _message) { try { if (!TryGetCasinoCommand(_message, out string cmd, out string args)) { return true; } switch (cmd) { case "/slotbet": case "/sbet": case "/sb": HandleSlotBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/blackjackbet": case "/bjbet": case "/bjb": case "/bb": HandleBlackjackBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/ready": case "/rdy": case "/rd": case "/br": HandleReady(__instance); SetChatFocusFalse(__instance); return false; case "/start": case "/deal": case "/begin": case "/bd": HandleStart(__instance); SetChatFocusFalse(__instance); return false; case "/hit": case "/h": case "/bh": HandleHit(__instance); SetChatFocusFalse(__instance); return false; case "/stand": case "/stay": case "/st": case "/bst": HandleStand(__instance); SetChatFocusFalse(__instance); return false; case "/leave": case "/lv": case "/l": case "/bl": HandleLeave(__instance); SetChatFocusFalse(__instance); return false; case "/tbet": case "/tb": HandleRouletteBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/rbet": case "/rb": HandleRBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/rclearbets": case "/rclear": case "/rcb": case "/rcl": case "/rc": HandleRClearBets(__instance); SetChatFocusFalse(__instance); return false; case "/rstandby": case "/rready": case "/rsb": case "/rs": case "/rr": HandleRReady(__instance); SetChatFocusFalse(__instance); return false; case "/rspin": case "/rsp": HandleRSpin(__instance); SetChatFocusFalse(__instance); return false; case "/rleave": case "/rl": case "/rlv": HandleRLeave(__instance); SetChatFocusFalse(__instance); return false; case "/play": if (!ArgumentEquals(args, "jukebox")) { return true; } HandlePlayJukebox(); SetChatFocusFalse(__instance); return false; case "/jukebox": case "/juke": case "/jb": HandleJukeboxCommand(__instance, args); SetChatFocusFalse(__instance); return false; case "/jplay": HandlePlayJukebox(); SetChatFocusFalse(__instance); return false; case "/forward": case "/next": case "/skip": case "/fwd": case "/jnext": case "/jn": case "/jf": if (!ArgumentEmptyOrAny(args, "song", "jukebox", "music")) { return true; } HandleForwardSong(); SetChatFocusFalse(__instance); return false; case "/previous": case "/prev": case "/back": case "/rewind": case "/jprev": if (!ArgumentEmptyOrAny(args, "song", "jukebox", "music")) { return true; } HandlePreviousSong(); SetChatFocusFalse(__instance); return false; case "/stop": case "/jstop": case "/js": if (!ArgumentEmptyOrAny(args, "jukebox", "song", "music")) { return true; } HandleStopJukebox(); SetChatFocusFalse(__instance); return false; case "/roomzonechat": case "/roomzone": case "/rzchat": case "/roomchat": case "/privatezonechat": case "/pzchat": HandleRoomZoneChat(__instance, args); SetChatFocusFalse(__instance); return false; case "/casinohelp": case "/chelp": case "/ch": HandleCasinoHelp(__instance); SetChatFocusFalse(__instance); return false; case "/ownerluck": case "/casinocheat": case "/luck": HandleOwnerLuck(__instance, args); SetChatFocusFalse(__instance); return false; default: return true; } } catch (Exception ex) { Plugin.Log.LogError("[Casino] Chat command error: " + ex.Message); return true; } } public static bool TryRunLocalCasinoMacro(string message) { if (string.IsNullOrWhiteSpace(message)) { return false; } ChatBehaviour val = Object.FindObjectOfType(); if ((Object)(object)val == (Object)null) { Plugin.ShowHUDError("Casino macro failed: chat is not ready."); return false; } if (!Prefix(val, message.Trim())) { return true; } SendLocalMessage(val, "[Casino] Macro command not recognized."); return false; } private static void HandleSlotBet(ChatBehaviour chat, string args) { int result; if (string.IsNullOrWhiteSpace(args)) { if (Plugin.HasSetBet) { SendLocalMessage(chat, "[Casino] Current slot bet: " + $"{Plugin.CurrentBet} Crowns. " + "Usage: /slotbet "); return; } string text = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, "[Casino] No slot bet set yet. Usage: /slotbet — allowed: " + text); } else if (!int.TryParse(args.Trim(), out result)) { SendLocalMessage(chat, "[Casino] Invalid amount '" + args + "'. Use a number."); } else if (!IsAllowedBet(result)) { string arg = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, $"[Casino] Bet {result} not allowed. Choose: {arg}"); } else { Plugin.CurrentBet = result; Plugin.HasSetBet = true; Plugin.Log.LogInfo($"[Casino] Slot bet set to {result} via chat."); SendLocalMessage(chat, "[Casino] Slot bet set to " + $"{result} Crowns."); } } private static void HandleBlackjackBet(ChatBehaviour chat, string args) { if (string.IsNullOrWhiteSpace(args)) { if (Plugin.HasSetBlackjackBet) { SendLocalMessage(chat, "[Casino] Current blackjack bet: " + $"{Plugin.BlackjackBet} Crowns."); return; } string text = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, "[Casino] No blackjack bet set. Usage: /blackjackbet — allowed: " + text + ". Required before claiming a seat."); return; } if (IsLocalPlayerSeatedAtAnyBlackjackTable()) { SendLocalMessage(chat, "[Casino] Can't change bet while seated. Use /leave first."); return; } if (!int.TryParse(args.Trim(), out var result)) { SendLocalMessage(chat, "[Casino] Invalid amount '" + args + "'. Use a number."); return; } if (!IsAllowedBet(result)) { string arg = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, $"[Casino] Bet {result} not allowed. Choose: {arg}"); return; } Plugin.BlackjackBet = result; Plugin.HasSetBlackjackBet = true; Plugin.Log.LogInfo($"[Casino] Blackjack bet set to {result} via chat."); SendLocalMessage(chat, "[Casino] Blackjack bet set to " + $"{result} Crowns. " + "Press " + CasinoInput.InteractPrompt + " at a seat trigger to claim your spot."); } private static void HandleReady(ChatBehaviour chat) { var (blackjackTable, num) = FindLocalSeat(); if ((Object)(object)blackjackTable == (Object)null || num < 0) { SendLocalMessage(chat, "[Casino] You must be seated at a blackjack table."); return; } if (blackjackTable.RoundInProgress) { SendLocalMessage(chat, "[Casino] Round in progress — can't change ready state."); return; } bool flag = blackjackTable.IsPlayerReady(num); bool flag2 = !flag; if (flag2) { int bet = blackjackTable.Hands[num].Bet; int localCrownBalance = GetLocalCrownBalance(); if (localCrownBalance < bet) { SendLocalMessage(chat, "[Casino] You can't afford your bet " + $"({localCrownBalance} < {bet}). Use /leave and /blackjackbet to lower it."); return; } } if (BJNetcode.AmHost()) { blackjackTable.SetPlayerReady(num, flag2); BJNetcode.BroadcastReadyChanged(((Object)blackjackTable).name, num, flag2); bool allSeatedPlayersReady = blackjackTable.AllSeatedPlayersReady; SendLocalMessage(chat, $"[Casino] Seat {num + 1}: " + (flag2 ? "READY" : "not ready") + ". " + ReadyCountSummary(blackjackTable) + (allSeatedPlayersReady ? " All players ready — use /start to deal!" : "")); } else { BJNetcode.SendReadyToggleRequest(((Object)blackjackTable).name, flag2); SendLocalMessage(chat, "[Casino] Ready " + (flag2 ? "ON" : "OFF") + " request sent. (" + ReadyCountSummary(blackjackTable) + ")"); } } private static void HandleStart(ChatBehaviour chat) { var (blackjackTable, num) = FindLocalSeat(); if ((Object)(object)blackjackTable == (Object)null || num < 0) { SendLocalMessage(chat, "[Casino] You must be seated to /start."); return; } if (!blackjackTable.IsHost(num)) { Player hostPlayer = blackjackTable.GetHostPlayer(); string text = (((Object)(object)hostPlayer == (Object)null) ? "(none)" : $"seat {blackjackTable.HostSeatIndex + 1}"); SendLocalMessage(chat, "[Casino] Only the host (" + text + ") can /start."); return; } if (blackjackTable.RoundInProgress) { SendLocalMessage(chat, "[Casino] Round already in progress."); return; } if (!blackjackTable.AllSeatedPlayersReady) { SendLocalMessage(chat, "[Casino] Not all seated players are /ready."); return; } if (!BJNetcode.AmHost()) { BJNetcode.SendStartRoundRequest(((Object)blackjackTable).name); SendLocalMessage(chat, "[Casino] /start request sent to lobby host..."); return; } BlackjackTable.StartRoundResult startRoundResult = blackjackTable.StartRound(); if (startRoundResult.KickedSeats != null && startRoundResult.KickedSeats.Count > 0) { foreach (int kickedSeat in startRoundResult.KickedSeats) { SendLocalMessage(chat, $"[Casino] Seat {kickedSeat + 1} auto-left (couldn't afford bet)."); } } if (!startRoundResult.Success) { SendLocalMessage(chat, "[Casino] Could not start: " + startRoundResult.Reason); return; } SendLocalMessage(chat, "[Casino] Round started. " + $"Seat {startRoundResult.FirstTurnSeat + 1}'s turn."); ShowLocalHandPrompt(blackjackTable, finalView: false); if (!blackjackTable.RoundInProgress) { ShowLocalHandPrompt(blackjackTable, finalView: true); } } private static void HandleHit(ChatBehaviour chat) { var (blackjackTable, num) = FindLocalSeat(); if ((Object)(object)blackjackTable == (Object)null || num < 0) { SendLocalMessage(chat, "[Casino] You must be seated to /hit."); return; } if (!BJNetcode.AmHost()) { BJNetcode.SendHitRequest(((Object)blackjackTable).name); SendLocalMessage(chat, "[Casino] /hit request sent..."); return; } BlackjackTable.HitResult hitResult = blackjackTable.PlayerHit(num); if (!hitResult.Success) { SendLocalMessage(chat, "[Casino] " + hitResult.Reason); return; } BlackjackHand blackjackHand = blackjackTable.Hands[num]; SendLocalMessage(chat, "[Casino] Hit: " + blackjackHand.Describe()); ShowLocalHandPrompt(blackjackTable, hitResult.RoundResolved); if (hitResult.AutoStood && !hitResult.RoundResolved) { SendLocalMessage(chat, "[Casino] " + (hitResult.Busted ? "Busted — turn ends." : "21 — turn ends.") + ((hitResult.NextTurnSeat >= 0) ? $" Seat {hitResult.NextTurnSeat + 1}'s turn." : "")); } } private static void HandleStand(ChatBehaviour chat) { var (blackjackTable, num) = FindLocalSeat(); if ((Object)(object)blackjackTable == (Object)null || num < 0) { SendLocalMessage(chat, "[Casino] You must be seated to /stand."); return; } if (!BJNetcode.AmHost()) { BJNetcode.SendStandRequest(((Object)blackjackTable).name); SendLocalMessage(chat, "[Casino] /stand request sent..."); return; } BlackjackTable.StandResult standResult = blackjackTable.PlayerStand(num); if (!standResult.Success) { SendLocalMessage(chat, "[Casino] " + standResult.Reason); return; } SendLocalMessage(chat, $"[Casino] Stood on {blackjackTable.Hands[num].BestValue}."); ShowLocalHandPrompt(blackjackTable, standResult.RoundResolved); if (!standResult.RoundResolved && standResult.NextTurnSeat >= 0) { SendLocalMessage(chat, $"[Casino] Seat {standResult.NextTurnSeat + 1}'s turn."); } } private static void HandleLeave(ChatBehaviour chat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { SendLocalMessage(chat, "[Casino] Player not found."); return; } BlackjackTable[] array = Object.FindObjectsOfType(); BlackjackTable[] array2 = array; foreach (BlackjackTable blackjackTable in array2) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { if (BJNetcode.AmHost()) { blackjackTable.ReleaseSeat(seatForPlayer); int hostSeatIndex = blackjackTable.HostSeatIndex; BJNetcode.BroadcastSeatReleased(((Object)blackjackTable).name, seatForPlayer, hostSeatIndex); Plugin.HasSetBlackjackBet = false; SendLocalMessage(chat, $"[Casino] Left seat {seatForPlayer + 1}. " + "Bet cleared — /blackjackbet to set a new one."); } else { BJNetcode.SendReleaseSeatRequest(((Object)blackjackTable).name); Plugin.HasSetBlackjackBet = false; SendLocalMessage(chat, "[Casino] Leave request sent " + $"for seat {seatForPlayer + 1}. Bet cleared — /blackjackbet to set a new one."); } return; } } SendLocalMessage(chat, "[Casino] You're not seated at any blackjack table."); } private static void HandleRouletteBet(ChatBehaviour chat, string args) { if (string.IsNullOrWhiteSpace(args)) { if (Plugin.HasSetRouletteBet) { SendLocalMessage(chat, "[Casino] Current roulette bet amount: " + $"{Plugin.RouletteBet} Crowns per /rbet. " + "Usage: /tbet "); return; } string text = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, "[Casino] No roulette bet amount set. Usage: /tbet — allowed: " + text + ". Required before joining the table."); return; } if (!int.TryParse(args.Trim(), out var result)) { SendLocalMessage(chat, "[Casino] Invalid amount '" + args + "'. Use a number."); return; } if (!IsAllowedBet(result)) { string arg = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, $"[Casino] Bet {result} not allowed. Choose: {arg}"); return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable != (Object)null) { List betsForPlayer = rouletteTable.GetBetsForPlayer(Player._mainPlayer); if (betsForPlayer.Count > 0) { SendLocalMessage(chat, "[Casino] Clear your bets first with /rclearbets before changing your bet amount."); return; } } Plugin.RouletteBet = result; Plugin.HasSetRouletteBet = true; Plugin.Log.LogInfo($"[Casino] Roulette bet set to {result} via chat."); SendLocalMessage(chat, "[Casino] Roulette bet amount set to " + $"{result} Crowns per /rbet. " + "Walk up to the table and press " + CasinoInput.InteractPrompt + " to join."); } private static void HandleRBet(ChatBehaviour chat, string args) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable == (Object)null) { SendLocalMessage(chat, "[Casino] You are not at a roulette table. Walk up and press " + CasinoInput.InteractPrompt + " to join."); return; } if (!Plugin.HasSetRouletteBet) { SendLocalMessage(chat, "[Casino] Set a bet amount first: /tbet "); return; } if (!RouletteLogic.TryParseBet(args, out BetType betType, out int betTarget, out string errorMsg)) { SendLocalMessage(chat, "[Casino] " + errorMsg); return; } if (BJNetcode.AmHost()) { if (!rouletteTable.TryPlaceBet(mainPlayer, betType, betTarget, Plugin.RouletteBet, out string errorMsg2)) { SendLocalMessage(chat, "[Casino] " + errorMsg2); return; } RNNetcode.BroadcastBetPlaced(((Object)rouletteTable).name, BJNetcode.GetLocalSteam64(), betType, betTarget, Plugin.RouletteBet); } else { RNNetcode.SendPlaceBetRequest(((Object)rouletteTable).name, betType, betTarget, Plugin.RouletteBet); } string text = RouletteLogic.DescribeBet(betType, betTarget); List betsForPlayer = rouletteTable.GetBetsForPlayer(mainPlayer); string text2 = RouletteLogic.FormatBetSummary(betsForPlayer); SendLocalMessage(chat, "[Casino] Bet placed: " + $"{Plugin.RouletteBet} on {text}. " + "All bets: " + text2); Plugin.Log.LogInfo($"[Casino] /rbet {text} {Plugin.RouletteBet} crowns."); } private static void HandleRClearBets(ChatBehaviour chat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable == (Object)null) { SendLocalMessage(chat, "[Casino] You are not at a roulette table."); return; } if (BJNetcode.AmHost()) { if (!rouletteTable.TryClearBets(mainPlayer, out string errorMsg)) { SendLocalMessage(chat, "[Casino] " + errorMsg); return; } RNNetcode.BroadcastBetsCleared(((Object)rouletteTable).name, BJNetcode.GetLocalSteam64()); } else { RNNetcode.SendClearBetsRequest(((Object)rouletteTable).name); } SendLocalMessage(chat, "[Casino] All bets cleared and refunded."); } private static void HandleRReady(ChatBehaviour chat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable == (Object)null) { SendLocalMessage(chat, "[Casino] You are not at a roulette table. Walk up and press " + CasinoInput.InteractPrompt + " to join."); return; } bool flag = rouletteTable.IsPlayerReady(mainPlayer); bool flag2 = !flag; if (flag2 && rouletteTable.GetBetsForPlayer(mainPlayer).Count == 0) { SendLocalMessage(chat, "[Casino] Place at least one bet first. Use /rbet [number]."); return; } if (BJNetcode.AmHost()) { if (!rouletteTable.TrySetReady(mainPlayer, flag2, out string errorMsg)) { SendLocalMessage(chat, "[Casino] " + errorMsg); return; } RNNetcode.BroadcastReadyChanged(((Object)rouletteTable).name, BJNetcode.GetLocalSteam64(), flag2); } else { RNNetcode.SendReadyToggleRequest(((Object)rouletteTable).name, flag2); } int readyCount = rouletteTable.GetReadyCount(); int playerCount = rouletteTable.PlayerCount; bool flag3 = readyCount == playerCount && playerCount > 0; string text = (flag2 ? "READY" : "not ready"); string text2 = $"({readyCount}/{playerCount} players ready)"; string text3 = ((rouletteTable.IsTableHost(mainPlayer) && flag3) ? " All players ready — use /rspin to spin the wheel!" : ((rouletteTable.IsTableHost(mainPlayer) && flag2) ? (" Waiting for other players. " + text2) : (flag2 ? (" Waiting for the host to /rspin. " + text2) : (" " + text2)))); SendLocalMessage(chat, "[Casino] You are " + text + "." + text3); } private static void HandleRSpin(ChatBehaviour chat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable == (Object)null) { SendLocalMessage(chat, "[Casino] You are not at a roulette table."); } else if (BJNetcode.AmHost()) { if (!rouletteTable.TryStartSpin(mainPlayer, out string errorMsg)) { SendLocalMessage(chat, "[Casino] " + errorMsg); } } else { RNNetcode.SendSpinRequest(((Object)rouletteTable).name); } } private static void HandleRLeave(ChatBehaviour chat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } RouletteTable rouletteTable = FindLocalRouletteTable(); if ((Object)(object)rouletteTable == (Object)null) { SendLocalMessage(chat, "[Casino] You are not at a roulette table."); return; } if (rouletteTable.IsSpinning) { SendLocalMessage(chat, "[Casino] The wheel is spinning — wait for the round to finish before leaving."); return; } if (BJNetcode.AmHost()) { ulong localSteam = BJNetcode.GetLocalSteam64(); rouletteTable.Leave(mainPlayer); RNNetcode.BroadcastPlayerLeft(((Object)rouletteTable).name, localSteam, 0uL, rouletteTable.PlayerCount); } else { RNNetcode.SendLeaveTableRequest(((Object)rouletteTable).name); rouletteTable.Leave(mainPlayer); } Plugin.HasSetRouletteBet = false; SendLocalMessage(chat, "[Casino] Left the roulette table. Any unplaced bet amounts have been refunded. Use /tbet to set a new bet amount."); Plugin.Log.LogInfo("[Casino] Local player left roulette table via /rleave."); } private static void HandlePlayJukebox() { CasinoJukeboxPersonalPlayer.Play(); } private static void HandleForwardSong() { CasinoJukeboxPersonalPlayer.Forward(); } private static void HandlePreviousSong() { CasinoJukeboxPersonalPlayer.Previous(); } private static void HandleStopJukebox() { CasinoJukeboxPersonalPlayer.StopPlayback(); } private static void HandleJukeboxCommand(ChatBehaviour chat, string args) { string text = (args ?? string.Empty).Trim().ToLowerInvariant(); if (string.IsNullOrEmpty(text) || text == "play" || text == "start" || text == "on") { HandlePlayJukebox(); return; } switch (text) { default: if (!(text == "fwd")) { switch (text) { default: if (!(text == "rewind")) { if (text == "stop" || text == "off") { HandleStopJukebox(); } else { SendLocalMessage(chat, "[Jukebox] Use /jukebox, /jukebox next, /jukebox prev, or /jukebox stop."); } break; } goto case "previous"; case "previous": case "prev": case "back": HandlePreviousSong(); break; } break; } goto case "forward"; case "forward": case "next": case "skip": HandleForwardSong(); break; } } private static void HandleRoomZoneChat(ChatBehaviour chat, string args) { string text = (args ?? string.Empty).Trim().ToLowerInvariant(); bool value; if (string.IsNullOrEmpty(text) || text == "toggle") { value = !CasinoConfig.RoomZoneChatEnabled; } else { switch (text) { default: if (!(text == "1")) { switch (text) { default: if (!(text == "0")) { if (text == "status") { SendRoomZoneChatStatus(chat); } else { SendLocalMessage(chat, "[Casino] Usage: /roomzonechat [on|off|status]"); } return; } break; case "off": case "disable": case "disabled": case "false": break; } value = false; break; } goto case "on"; case "on": case "enable": case "enabled": case "true": value = true; break; } } if (CasinoConfig.RoomZoneChatEnabledEntry != null) { CasinoConfig.RoomZoneChatEnabledEntry.Value = value; } RoomZoneChatNetcode.PublishLocalPreference(); SendRoomZoneChatStatus(chat); } private static void SendRoomZoneChatStatus(ChatBehaviour chat) { bool roomZoneChatEnabled = CasinoConfig.RoomZoneChatEnabled; string text = (roomZoneChatEnabled ? "ON" : "OFF"); string text2 = (roomZoneChatEnabled ? "RoomZone triggers can privately route your Zone chat." : "Your Zone chat uses normal vanilla Zone routing."); SendLocalMessage(chat, "[Casino] Private zone chat is " + text + ". " + text2); } private static void HandleCasinoHelp(ChatBehaviour chat) { string text = string.Join(", ", Plugin.AllowedBets); SendLocalMessage(chat, "[Casino Commands] (shortcuts in parentheses)"); SendLocalMessage(chat, "-- SLOTS --"); SendLocalMessage(chat, "/slotbet (/sbet, /sb) - Set slot bet (" + text + ")"); SendLocalMessage(chat, "-- BLACKJACK --"); SendLocalMessage(chat, "/blackjackbet (/bjbet, /bjb, /bb) - Set blackjack bet"); SendLocalMessage(chat, "/ready (/rdy, /rd, /br) - Toggle ready"); SendLocalMessage(chat, "/start (/deal, /begin, /bd) - Begin round (host only)"); SendLocalMessage(chat, "/hit (/h, /bh) - Take a card"); SendLocalMessage(chat, "/stand (/stay, /st, /bst) - End your turn"); SendLocalMessage(chat, "/leave (/lv, /l, /bl) - Leave your seat"); SendLocalMessage(chat, "-- ROULETTE --"); SendLocalMessage(chat, "/tbet (/tb) - Set roulette stake per bet"); SendLocalMessage(chat, "/rbet [num] - Place a bet"); SendLocalMessage(chat, "(/rb) Types: number 0-36, red, black, even, odd, low, high, dozen 1-3, column 1-3, d1-d3, c1-c3"); SendLocalMessage(chat, "/rclearbets (/rclear, /rcb, /rcl, /rc) - Clear and refund all your bets"); SendLocalMessage(chat, "/rstandby (/rready, /rsb, /rs, /rr) - Toggle ready at roulette"); SendLocalMessage(chat, "/rspin (/rsp) - Spin the wheel (host only)"); SendLocalMessage(chat, "/rleave (/rl, /rlv) - Leave the roulette table"); SendLocalMessage(chat, "-- JUKEBOX --"); SendLocalMessage(chat, "/play jukebox - Start local personal jukebox playback"); SendLocalMessage(chat, "/jukebox (/jb, /juke, /jplay) - Start local personal jukebox playback"); SendLocalMessage(chat, "/forward song (/forward, /next, /skip, /jnext, /jn) - Next personal jukebox song"); SendLocalMessage(chat, "/previous song (/previous, /prev, /back, /jprev) - Previous personal jukebox song"); SendLocalMessage(chat, "/stop jukebox (/stop, /jstop, /js) - Stop local personal jukebox playback"); SendLocalMessage(chat, "-- CLIENT --"); SendLocalMessage(chat, "/roomzonechat [on|off|status] (/rzchat, /roomchat) - Toggle private Zone chat routing"); SendLocalMessage(chat, "/casinohelp (/chelp, /ch) - This help"); if (Plugin.IsCasinoOwner(BJNetcode.GetLocalSteam64())) { SendLocalMessage(chat, "/ownerluck [on|off|status] (/luck) - Toggle owner-only rigged wins"); } } private static void HandleOwnerLuck(ChatBehaviour chat, string args) { ulong localSteam = BJNetcode.GetLocalSteam64(); if (!Plugin.IsCasinoOwner(localSteam)) { SendLocalMessage(chat, "[Casino] Unknown command."); return; } string text = (args ?? string.Empty).Trim().ToLowerInvariant(); bool flag; if (string.IsNullOrEmpty(text) || text == "toggle") { flag = !Plugin.OwnerLuckEnabled; } else { switch (text) { default: if (!(text == "1")) { switch (text) { default: if (!(text == "0")) { if (text == "status") { string text2 = (Plugin.OwnerLuckEnabled ? "ON" : "OFF"); SendLocalMessage(chat, "[Casino] Owner luck is " + text2 + "."); } else { SendLocalMessage(chat, "[Casino] Usage: /ownerluck [on|off|status]"); } return; } break; case "off": case "disable": case "disabled": case "false": break; } flag = false; break; } goto case "on"; case "on": case "enable": case "enabled": case "true": flag = true; break; } } Plugin.OwnerLuckEnabled = flag; if (BJNetcode.AmHost()) { BJNetcode.BroadcastOwnerLuckChanged(flag, localSteam); } else { BJNetcode.SendOwnerLuckRequest(flag); } string text3 = (Plugin.OwnerLuckEnabled ? "ON" : "OFF"); SendLocalMessage(chat, "[Casino] Owner luck is " + text3 + "."); Plugin.Log.LogInfo(string.Format("[Casino] Owner luck {0} by owner {1}.", Plugin.OwnerLuckEnabled ? "enabled" : "disabled", localSteam)); } private static bool TryGetCasinoCommand(string message, out string cmd, out string args) { cmd = ""; args = ""; if (string.IsNullOrWhiteSpace(message)) { return false; } string text = message.Trim(); Match match = Regex.Match(text, "^(.*)$", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (match.Success) { text = match.Groups[1].Value.Trim(); } if (!text.StartsWith("/") || text.StartsWith("//")) { return false; } int num = text.IndexOfAny(new char[2] { ' ', '\t' }); if (num < 0) { cmd = text.ToLowerInvariant(); return cmd.Length > 1; } cmd = text.Substring(0, num).ToLowerInvariant(); args = text.Substring(num + 1).Trim(); return cmd.Length > 1; } private static bool ArgumentEquals(string args, string expected) { return string.Equals((args ?? string.Empty).Trim(), expected, StringComparison.OrdinalIgnoreCase); } private static bool ArgumentEmptyOrAny(string args, params string[] expected) { string text = (args ?? string.Empty).Trim(); if (string.IsNullOrEmpty(text)) { return true; } foreach (string b in expected) { if (string.Equals(text, b, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool IsAllowedBet(int amount) { int[] allowedBets = Plugin.AllowedBets; foreach (int num in allowedBets) { if (num == amount) { return true; } } return false; } private static (BlackjackTable?, int) FindLocalSeat() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return (null, -1); } BlackjackTable[] array = Object.FindObjectsOfType(); BlackjackTable[] array2 = array; foreach (BlackjackTable blackjackTable in array2) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { return (blackjackTable, seatForPlayer); } } return (null, -1); } private static bool IsLocalPlayerSeatedAtAnyBlackjackTable() { var (blackjackTable, num) = FindLocalSeat(); return (Object)(object)blackjackTable != (Object)null && num >= 0; } private static RouletteTable? FindLocalRouletteTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return null; } RouletteTable[] array = Object.FindObjectsOfType(); RouletteTable[] array2 = array; foreach (RouletteTable rouletteTable in array2) { if (rouletteTable.IsPlayerAtTable(mainPlayer)) { return rouletteTable; } } return null; } private static int GetLocalCrownBalance() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return 0; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)(object)component == (Object)null) { return 0; } return component._heldCurrency; } private static string ReadyCountSummary(BlackjackTable table) { int num = 0; int num2 = 0; for (int i = 0; i < 5; i++) { if (!((Object)(object)table.GetSeatedPlayer(i) == (Object)null)) { num2++; if (table.IsPlayerReady(i)) { num++; } } } return $"({num}/{num2} ready)"; } private static void ShowLocalHandPrompt(BlackjackTable table, bool finalView) { try { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } int seatForPlayer = table.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { string text = (finalView ? table.DescribePlayerHandFinal(seatForPlayer) : table.DescribePlayerHandStatus(seatForPlayer)); if (!string.IsNullOrEmpty(text)) { Plugin.ShowHUDInfo(text); } } } catch (Exception ex) { Plugin.Log.LogError("[Casino] ShowLocalHandPrompt failed: " + ex.Message); } } private static void SendLocalMessage(ChatBehaviour chat, string message) { try { Plugin.ShowGameFeed(chat, message); } catch (Exception ex) { Plugin.Log.LogError("[Casino] Failed to send local chat message: " + ex.Message); } } private static void SetChatFocusFalse(ChatBehaviour chat) { try { string[] array = new string[5] { "Init_ChatFocusFalse", "Set_ChatFocusFalse", "Close_Chat", "Deactivate_Chat", "Exit_Chat" }; Type type = ((object)chat).GetType(); string[] array2 = array; foreach (string text in array2) { MethodInfo method = type.GetMethod(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(method == null)) { method.Invoke(chat, null); Plugin.Log.LogInfo("[CommandHandler] Chat closed via " + text + "()"); return; } } type.GetField("_focusedInChat", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(chat, false); Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; Plugin.Log.LogInfo("[CommandHandler] Chat closed via fallback (cursor locked)."); } catch (Exception ex) { Plugin.Log.LogWarning("[CommandHandler] SetChatFocusFalse failed: " + ex.Message); } } } public class CasinoEntryFee : MonoBehaviour { [CompilerGenerated] private sealed class d__18 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Player player; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__18(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = ((MonoBehaviour)Plugin.Instance).StartCoroutine(CasinoExitTrigger.ForceReturnToSanctum(player, "[EntryFee]")); <>1__state = 2; return true; case 2: <>1__state = -1; 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 d__19 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private int 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__19(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3.5f); <>1__state = 1; return true; case 1: <>1__state = -1; 5__1 = 0; break; case 2: <>1__state = -1; 5__1++; break; } if (5__1 < 3) { try { Plugin.ShowHUDError("Come back when you're a little... mmm... richer!"); } catch { } <>2__current = (object)new WaitForSeconds(3.8f); <>1__state = 2; return true; } Plugin.Log.LogInfo("[EntryFee] Broke message shown."); 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 d__17 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Player player; public PlayerInventory inv; public ulong steam64; public CasinoEntryFee <>4__this; private int 5__1; private int 5__2; private int 5__3; private bool 5__4; private int 5__5; private int 5__6; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__17(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; _paymentPendingPlayers.Add(steam64); if ((Object)(object)player == (Object)null || (Object)(object)inv == (Object)null) { _paymentPendingPlayers.Remove(steam64); _paidPlayers.Remove(steam64); Plugin.Log.LogWarning($"[EntryFee] Payment verification aborted for {steam64}: " + "player or inventory no longer exists."); return false; } 5__1 = ReadCrowns(inv); 5__2 = Plugin.EntryFeeCrowns; 5__3 = 5__1 - 5__2; if (5__1 < 5__2) { _paymentPendingPlayers.Remove(steam64); Plugin.ShowHUDError($"You need at least {5__2} Crowns to enter!"); Plugin.Log.LogInfo($"[EntryFee] Player {steam64} had {5__1} Crowns " + "at verification time — insufficient. Sending back to Sanctum."); _isBooting = true; _pendingBrokeMessage = true; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(BootPlayerToSanctum(player)); <>1__state = 1; return true; } 5__4 = false; 5__5 = 5__1; 5__6 = 1; goto IL_02be; case 1: <>1__state = -1; return false; case 2: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.05f); <>1__state = 3; return true; case 3: <>1__state = -1; 5__5 = ReadCrowns(inv); if (5__5 == 5__3) { 5__4 = true; goto IL_02d3; } Plugin.Log.LogWarning($"[EntryFee] Payment verification attempt {5__6} failed " + $"for {steam64}: started {5__1}, expected " + $"{5__3}, actual {5__5}."); 5__6++; goto IL_02be; case 4: <>1__state = -1; goto IL_03cf; case 5: { <>1__state = -1; return false; } IL_02be: if (5__6 <= 2) { inv.Network_heldCurrency = 5__3; <>2__current = null; <>1__state = 2; return true; } goto IL_02d3; IL_02d3: _paymentPendingPlayers.Remove(steam64); if (5__4) { _paidPlayers.Add(steam64); Plugin.ShowHUDInfo("Welcome to AtlyssCasino! " + $"{5__2} Crown entry fee charged. Good luck!"); Plugin.Log.LogInfo($"[EntryFee] Player {steam64} paid {5__2} Crowns " + $"(verified {5__1} -> {5__3})."); return false; } _paidPlayers.Remove(steam64); if (5__5 != 5__1) { inv.Network_heldCurrency = 5__1; <>2__current = null; <>1__state = 4; return true; } goto IL_03cf; IL_03cf: Plugin.ShowHUDError("Entry fee payment failed. Returning you to Sanctum."); Plugin.Log.LogWarning($"[EntryFee] Player {steam64} was NOT marked paid. " + $"Started {5__1}, expected {5__3}, " + $"actual {5__5}. Sending back to Sanctum."); _isBooting = true; _pendingBrokeMessage = false; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(BootPlayerToSanctum(player)); <>1__state = 5; return true; } } 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 float SMALL_PLAYER_SCALE_THRESHOLD = 0.05f; private const float MESSAGE_DELAY = 3.5f; private const float MESSAGE_REPEAT_INTERVAL = 3.8f; private const int MESSAGE_REPEAT_COUNT = 3; private const string BROKE_MESSAGE = "Come back when you're a little... mmm... richer!"; private static readonly HashSet _paidPlayers = new HashSet(); private static readonly HashSet _paymentPendingPlayers = new HashSet(); private static bool _pendingBrokeMessage = false; private static bool _isBooting = false; private BoxCollider? _collider; private bool _playerWasInside = false; private void Awake() { _collider = ((Component)this).GetComponent(); _paidPlayers.Clear(); _paymentPendingPlayers.Clear(); _pendingBrokeMessage = false; _isBooting = false; SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.sceneLoaded += OnSceneLoaded; Plugin.Log.LogInfo($"[EntryFee] Entry fee trigger active. Fee: {Plugin.EntryFeeCrowns} Crowns."); } private void OnDestroy() { SceneManager.sceneUnloaded -= OnSceneUnloaded; SceneManager.sceneLoaded -= OnSceneLoaded; } private static void OnSceneUnloaded(Scene scene) { if (!(((Scene)(ref scene)).name != "AtlyssCasino")) { _paidPlayers.Clear(); _paymentPendingPlayers.Clear(); _pendingBrokeMessage = false; _isBooting = false; Plugin.Log.LogInfo("[EntryFee] Casino unloaded — paid list cleared."); } } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (_pendingBrokeMessage && !(((Scene)(ref scene)).name == "AtlyssCasino")) { _pendingBrokeMessage = false; ((MonoBehaviour)Plugin.Instance).StartCoroutine(ShowBrokeMessageDelayed()); } } private void Update() { //IL_0039: 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_0048: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider == (Object)null || _isBooting) { return; } Player mainPlayer = Player._mainPlayer; if (!((Object)(object)mainPlayer == (Object)null)) { Bounds bounds = ((Collider)_collider).bounds; bool flag = ((Bounds)(ref bounds)).Contains(((Component)mainPlayer).transform.position); if (flag && !_playerWasInside) { HandlePlayerEntered(mainPlayer); } _playerWasInside = flag; } } private void HandlePlayerEntered(Player player) { ulong steam = GetSteam64(player); if (_paidPlayers.Contains(steam)) { Plugin.Log.LogInfo("[EntryFee] Player already paid — welcome back."); return; } if (_paymentPendingPlayers.Contains(steam)) { Plugin.Log.LogInfo($"[EntryFee] Payment verification already pending for {steam}."); return; } float playerVisibleScale = GetPlayerVisibleScale(player); if (playerVisibleScale > 0f && playerVisibleScale <= 0.05f) { _paidPlayers.Add(steam); Plugin.ShowHUDInfo("Having Fun In There? >:3"); Plugin.Log.LogInfo($"[EntryFee] Player {steam} is small (scale {playerVisibleScale:F3}) " + "— entry fee waived."); return; } PlayerInventory component = ((Component)player).GetComponent(); if (!((Object)(object)component == (Object)null)) { int entryFeeCrowns = Plugin.EntryFeeCrowns; if (component._heldCurrency < entryFeeCrowns) { Plugin.ShowHUDError($"You need at least {entryFeeCrowns} Crowns to enter!"); Plugin.Log.LogInfo($"[EntryFee] Player {steam} has {component._heldCurrency} Crowns " + "— insufficient. Sending back to Sanctum."); _isBooting = true; _pendingBrokeMessage = true; ((MonoBehaviour)this).StartCoroutine(BootPlayerToSanctum(player)); } else { ((MonoBehaviour)this).StartCoroutine(VerifyAndChargeEntryFee(player, component, steam)); } } } [IteratorStateMachine(typeof(d__17))] private IEnumerator VerifyAndChargeEntryFee(Player player, PlayerInventory inv, ulong steam64) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__17(0) { <>4__this = this, player = player, inv = inv, steam64 = steam64 }; } [IteratorStateMachine(typeof(d__18))] private static IEnumerator BootPlayerToSanctum(Player player) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__18(0) { player = player }; } [IteratorStateMachine(typeof(d__19))] private static IEnumerator ShowBrokeMessageDelayed() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__19(0); } private static void TeleportToStartPoint(Player player) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) GameObject[] array = GameObject.FindGameObjectsWithTag("spawnPoint"); GameObject[] array2 = array; foreach (GameObject val in array2) { if (((Object)val).name.Contains("startPoint")) { ((Component)player).transform.position = val.transform.position; break; } } } private static ulong GetSteam64(Player player) { if (ulong.TryParse(player.Network_steamID, out var result)) { return result; } return BJNetcode.GetLocalSteam64(); } private static int ReadCrowns(PlayerInventory inv) { if ((Object)(object)inv == (Object)null) { return 0; } return inv._heldCurrency; } private static float GetPlayerVisibleScale(Player player) { //IL_0307: 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_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 1f; } float minScale = 1f; string minSource = "default"; try { Transform val = ((Component)player).transform; int num = 0; while ((Object)(object)val != (Object)null && num < 5) { Consider(val.localScale.y, "ancestor:" + ((Object)val).name); val = val.parent; num++; } } catch { } try { Type type = ((object)player).GetType(); string[] array = new string[5] { "_raceModelContainer", "_raceModelObject", "_playerRaceModel", "_pVisual", "_visualContainer" }; for (int i = 0; i < array.Length; i++) { FieldInfo field = type.GetField(array[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { continue; } object value2 = field.GetValue(player); Transform val2 = (Transform)((value2 is Transform) ? value2 : null); if ((Object)(object)val2 == (Object)null) { GameObject val3 = (GameObject)((value2 is GameObject) ? value2 : null); if (val3 != null) { val2 = val3.transform; } } if ((Object)(object)val2 == (Object)null) { Component val4 = (Component)((value2 is Component) ? value2 : null); if (val4 != null) { val2 = val4.transform; } } if (!((Object)(object)val2 == (Object)null)) { float value3 = Mathf.Abs(val2.localScale.y); Consider(value3, "field:" + array[i]); } } } catch { } try { Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); foreach (Transform val5 in componentsInChildren) { if (!((Object)(object)val5 == (Object)null) && !((Object)(object)val5 == (Object)(object)((Component)player).transform)) { string text = ((Object)val5).name.ToLowerInvariant(); if ((text.Contains("race") || text.Contains("model") || text.Contains("visual") || text.Contains("body") || text.Contains("scale") || text.Contains("container")) && !((Object)(object)((Component)val5).GetComponent() != (Object)null) && !((Object)(object)((Component)val5).GetComponent() != (Object)null)) { Consider(val5.localScale.y, "child:" + ((Object)val5).name); } } } } catch { } try { SkinnedMeshRenderer[] componentsInChildren2 = ((Component)player).GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val6 in componentsInChildren2) { if (!((Object)(object)val6 == (Object)null)) { float num2 = Mathf.Abs(((Component)val6).transform.lossyScale.y); if (!(num2 <= 0f)) { float value4 = num2 / 0.011f; Consider(value4, "smr-normalized:" + ((Object)val6).name); } } } } catch { } if (minScale < 0.2f) { Plugin.Log.LogInfo($"[EntryFee] Player scale candidate {minScale:F4} " + "from " + minSource + "."); } return minScale; void Consider(float value, string source) { float num3 = Mathf.Abs(value); if (!(num3 <= 0f) && !float.IsNaN(num3) && !float.IsInfinity(num3) && !(num3 >= minScale)) { minScale = num3; minSource = source; } } } } public sealed class CasinoFloatingTextOcclusion : MonoBehaviour { private const float CHECK_INTERVAL_SEC = 0.08f; private const float TARGET_PADDING = 0.25f; private const float TARGET_SURFACE_PADDING = 1.25f; private const float ORIGIN_PADDING = 0.05f; private Transform? _ownerRoot; private Transform? _ignoredPortalRoot; private Renderer[] _renderers = Array.Empty(); private float _nextCheckTime; private bool _lastVisible = true; private string _lastBlockerName = string.Empty; internal static void Ensure(GameObject textObject, Transform ownerRoot) { if (!((Object)(object)textObject == (Object)null)) { CasinoFloatingTextOcclusion casinoFloatingTextOcclusion = textObject.GetComponent() ?? textObject.AddComponent(); casinoFloatingTextOcclusion.Setup(ownerRoot); } } internal void Setup(Transform ownerRoot) { _ownerRoot = ownerRoot; _ignoredPortalRoot = FindIgnoredPortalRoot(ownerRoot); RefreshRenderers(); SetVisible(visible: true, force: true); } private void OnEnable() { RefreshRenderers(); _nextCheckTime = 0f; } private void Update() { if (!(Time.time < _nextCheckTime)) { _nextCheckTime = Time.time + 0.08f; UpdateVisibility(force: false); } } private void RefreshRenderers() { _renderers = ((Component)this).GetComponentsInChildren(true); } private void UpdateVisibility(bool force) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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) Camera val = Camera.main ?? FindAnyCamera(); if ((Object)(object)val == (Object)null) { SetVisible(visible: true, force); return; } Vector3 targetPosition = GetTargetPosition(); string blockerName; bool flag = !IsOccluded(((Component)val).transform.position, targetPosition, out blockerName); if (!flag && blockerName != _lastBlockerName) { _lastBlockerName = blockerName; Plugin.Log.LogDebug("[PortalSigns] Hiding '" + ((Object)this).name + "' because '" + blockerName + "' is between the camera and the sign."); } else if (flag && _lastBlockerName.Length > 0) { _lastBlockerName = string.Empty; } SetVisible(flag, force); } private Camera? FindAnyCamera() { Camera[] allCameras = Camera.allCameras; foreach (Camera val in allCameras) { if ((Object)(object)val != (Object)null && ((Behaviour)val).isActiveAndEnabled) { return val; } } return null; } private Vector3 GetTargetPosition() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0092: 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) if (_renderers.Length == 0) { RefreshRenderers(); } Renderer[] renderers = _renderers; bool flag = false; Bounds val = default(Bounds); foreach (Renderer val2 in renderers) { if (!((Object)(object)val2 == (Object)null)) { if (flag) { ((Bounds)(ref val)).Encapsulate(val2.bounds); continue; } val = val2.bounds; flag = true; } } return flag ? ((Bounds)(ref val)).center : ((Component)this).transform.position; } private bool IsOccluded(Vector3 origin, Vector3 target, out string blockerName) { //IL_0008: 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_000a: 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) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_004c: Unknown result type (might be due to invalid IL or missing references) blockerName = string.Empty; Vector3 val = target - origin; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.25f) { return false; } Vector3 val2 = val / magnitude; float num = Mathf.Max(0f, magnitude - 0.25f); RaycastHit[] array = Physics.RaycastAll(origin, val2, num, -1, (QueryTriggerInteraction)1); if (array == null || array.Length == 0) { return false; } Array.Sort(array, CompareHitDistance); for (int i = 0; i < array.Length; i++) { if (!(((RaycastHit)(ref array[i])).distance <= 0.05f) && !(magnitude - ((RaycastHit)(ref array[i])).distance <= 1.25f)) { Collider collider = ((RaycastHit)(ref array[i])).collider; if (!ShouldIgnore(collider)) { blockerName = (((Object)(object)((Component)collider).gameObject != (Object)null) ? ((Object)((Component)collider).gameObject).name : ((Object)collider).name); return true; } } } return false; } private static int CompareHitDistance(RaycastHit a, RaycastHit b) { return ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance); } private bool ShouldIgnore(Collider collider) { if ((Object)(object)collider == (Object)null) { return true; } if (!collider.enabled) { return true; } if (collider.isTrigger) { return true; } Transform transform = ((Component)collider).transform; string text = ((Object)transform).name ?? string.Empty; if (text.IndexOf("Floor", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Ceiling", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if ((Object)(object)_ownerRoot != (Object)null && ((Object)(object)transform == (Object)(object)_ownerRoot || transform.IsChildOf(_ownerRoot))) { return true; } if ((Object)(object)_ignoredPortalRoot != (Object)null && ((Object)(object)transform == (Object)(object)_ignoredPortalRoot || transform.IsChildOf(_ignoredPortalRoot))) { return true; } try { if ((Object)(object)((Component)collider).GetComponentInParent() != (Object)null) { return true; } } catch { } return false; } private static Transform? FindIgnoredPortalRoot(Transform ownerRoot) { Transform parent = ownerRoot.parent; while ((Object)(object)parent != (Object)null) { string text = ((Object)parent).name ?? string.Empty; if (text.IndexOf("ManualPortal", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("CasinoManualPortal", StringComparison.OrdinalIgnoreCase) >= 0) { return parent; } parent = parent.parent; } return null; } private void SetVisible(bool visible, bool force) { if (!force && _lastVisible == visible) { return; } _lastVisible = visible; if (_renderers.Length == 0) { RefreshRenderers(); } Renderer[] renderers = _renderers; foreach (Renderer val in renderers) { if ((Object)(object)val != (Object)null) { val.enabled = visible; } } } } internal sealed class CasinoManualPortal : MonoBehaviour { private enum PortalKind { Local, ReturnToSanctum } private sealed class TwoWayPair { internal Transform? A; internal Transform? B; internal string AName = string.Empty; internal string BName = string.Empty; } [CompilerGenerated] private sealed class d__17 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Player player; public CasinoManualPortal <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__17(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = CasinoExitTrigger.ForceReturnToSanctum(player, "[ManualPortal/" + ((Object)<>4__this).name + "]"); <>1__state = 1; return true; case 1: <>1__state = -1; <>4__this._isTransporting = false; 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 float FallbackRange = 3f; private const float InputCooldown = 0.5f; private PortalKind _kind; private Transform? _target; private Collider[] _colliders = Array.Empty(); private string _label = "Portal"; private bool _playerNearby; private bool _isTransporting; private float _nextInputTime; private static float _globalNextInputTime; internal static int HookScenePortals(GameObject[] allObjects) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (GameObject val in allObjects) { if ((Object)(object)val == (Object)null) { continue; } if (TryGetTargetId(((Object)val).name, out string id)) { dictionary[id] = val.transform; } else { if (!TryGetTwoWayEndpoint(((Object)val).name, out string id2, out bool isEndpointA)) { continue; } if (!dictionary2.TryGetValue(id2, out var value)) { value = (dictionary2[id2] = new TwoWayPair()); } if (isEndpointA) { if ((Object)(object)value.A != (Object)null) { Plugin.Log.LogWarning("[ManualPortal] Duplicate two-way A endpoint for '" + id2 + "': '" + value.AName + "' and '" + ((Object)val).name + "'."); } value.A = val.transform; value.AName = ((Object)val).name; } else { if ((Object)(object)value.B != (Object)null) { Plugin.Log.LogWarning("[ManualPortal] Duplicate two-way B endpoint for '" + id2 + "': '" + value.BName + "' and '" + ((Object)val).name + "'."); } value.B = val.transform; value.BName = ((Object)val).name; } } } int num = 0; foreach (KeyValuePair item in dictionary2) { string key = item.Key; TwoWayPair value2 = item.Value; if ((Object)(object)value2.A == (Object)null || (Object)(object)value2.B == (Object)null) { Plugin.Log.LogWarning("[ManualPortal] Two-way portal '" + key + "' needs both 'ManualPortalTwoWay_" + key + "_A' and 'ManualPortalTwoWay_" + key + "_B'."); } else { CasinoManualPortal casinoManualPortal = ((Component)value2.A).gameObject.GetComponent() ?? ((Component)value2.A).gameObject.AddComponent(); casinoManualPortal.SetupLocal(value2.B, MakeLabel(key)); num++; CasinoManualPortal casinoManualPortal2 = ((Component)value2.B).gameObject.GetComponent() ?? ((Component)value2.B).gameObject.AddComponent(); casinoManualPortal2.SetupLocal(value2.A, MakeLabel(key)); num++; } } foreach (GameObject val2 in allObjects) { if ((Object)(object)val2 == (Object)null) { continue; } string text = ((Object)val2).name.Trim(); string id3; if (IsReturnToSanctumPortal(text)) { CasinoManualPortal casinoManualPortal3 = val2.GetComponent() ?? val2.AddComponent(); casinoManualPortal3.SetupReturnToSanctum(); num++; } else if (TryGetSourceId(text, out id3)) { if (!dictionary.TryGetValue(id3, out var value3)) { Plugin.Log.LogWarning("[ManualPortal] '" + text + "' has no matching target. Add an object named 'ManualPortalTarget_" + id3 + "' or 'CasinoManualPortalTarget_" + id3 + "'."); } else { CasinoManualPortal casinoManualPortal4 = val2.GetComponent() ?? val2.AddComponent(); casinoManualPortal4.SetupLocal(value3, MakeLabel(id3)); num++; } } } if (num > 0) { Plugin.Log.LogInfo($"[ManualPortal] Hooked {num} manual portal(s)."); } return num; } private void SetupLocal(Transform target, string label) { _kind = PortalKind.Local; _target = target; _label = label; CacheCollider(); } private void SetupReturnToSanctum() { _kind = PortalKind.ReturnToSanctum; _target = null; _label = "Sanctum"; CacheCollider(); } private void CacheCollider() { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) Collider[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); List list = new List(); int num = 0; int num2 = 0; Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((Object)(object)val == (Object)null || !val.enabled) { continue; } try { if (!val.isTrigger) { val.isTrigger = true; num++; } if (!val.isTrigger) { val.enabled = false; num2++; } else { list.Add(val); } } catch (Exception ex) { Plugin.Log.LogWarning("[ManualPortal] '" + ((Object)this).name + "' could not convert collider '" + ((Object)val).name + "' to trigger: " + ex.Message); } } _colliders = list.ToArray(); string text = (((Object)(object)_target == (Object)null) ? "sanctum" : (((Object)_target).name + " at " + FormatVector(_target.position))); Plugin.Log.LogInfo("[ManualPortal] Registered '" + ((Object)this).name + "' -> " + text + "; " + $"triggerCollider(s)={_colliders.Length}, " + $"forcedTrigger={num}, " + $"disabledBlocking={num2}, " + "position=" + FormatVector(((Component)this).transform.position) + "."); } private void Update() { try { if (HostCasinoVisibilityWatcher.IsHeadlessServer || HostCasinoVisibilityWatcher.IsServerMode) { return; } if (!HostCasinoVisibilityWatcher.IsLocalPlayerInsideCasinoBounds()) { _playerNearby = false; _isTransporting = false; return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; Plugin.ShowHUDInfo(BuildPrompt()); } else if (!flag && _playerNearby) { _playerNearby = false; } if (_playerNearby && !_isTransporting && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime) && !(Time.time < _globalNextInputTime)) { _nextInputTime = Time.time + 0.5f; _globalNextInputTime = Time.time + 0.5f; if (_kind == PortalKind.ReturnToSanctum) { _isTransporting = true; CasinoExitTrigger.IsTransporting = true; ((MonoBehaviour)this).StartCoroutine(ReturnToSanctum(mainPlayer)); } else { TeleportLocal(mainPlayer); } } } catch (Exception ex) { Plugin.Log.LogWarning("[ManualPortal] '" + ((Object)this).name + "' update threw: " + ex.Message); } } [IteratorStateMachine(typeof(d__17))] private IEnumerator ReturnToSanctum(Player player) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__17(0) { <>4__this = this, player = player }; } private void TeleportLocal(Player player) { //IL_0043: 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_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_target == (Object)null) { Plugin.Log.LogWarning("[ManualPortal] '" + ((Object)this).name + "' has no target."); return; } ((Component)player).transform.SetPositionAndRotation(_target.position, _target.rotation); Rigidbody component = ((Component)player).GetComponent(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; } Physics.SyncTransforms(); Plugin.ShowHUDInfo(_label); _playerNearby = false; Plugin.Log.LogInfo("[ManualPortal] '" + ((Object)this).name + "' teleported local player to '" + ((Object)_target).name + "' at " + FormatVector(_target.position) + "."); } private bool IsPlayerInRange(Player player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_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_00d0: 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_00d9: 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_0063: 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) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: 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_00f6: 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) Vector3 playerZonePosition = RoomZoneRegistry.GetPlayerZonePosition(player); if (_colliders.Length != 0) { if (TryGetPlayerBounds(player, out var bounds)) { Collider[] colliders = _colliders; foreach (Collider val in colliders) { if (!((Object)(object)val == (Object)null) && val.enabled) { Bounds bounds2 = val.bounds; if (((Bounds)(ref bounds2)).Intersects(bounds) || ((Bounds)(ref bounds2)).Contains(playerZonePosition)) { return true; } } } } Collider[] colliders2 = _colliders; foreach (Collider val2 in colliders2) { if (!((Object)(object)val2 == (Object)null) && val2.enabled) { Bounds bounds3 = val2.bounds; if (((Bounds)(ref bounds3)).Contains(playerZonePosition)) { return true; } Vector3 val3 = val2.ClosestPoint(playerZonePosition); if (Vector3.Distance(val3, playerZonePosition) <= 0.35f) { return true; } } } } return Vector3.Distance(((Component)this).transform.position, playerZonePosition) <= 3f; } private static bool TryGetPlayerBounds(Player player, out Bounds bounds) { //IL_0003: 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) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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_006d: Unknown result type (might be due to invalid IL or missing references) bounds = new Bounds(RoomZoneRegistry.GetPlayerZonePosition(player), Vector3.one); if ((Object)(object)player == (Object)null) { return false; } try { Collider[] componentsInChildren = ((Component)player).GetComponentsInChildren(false); bool flag = false; Collider[] array = componentsInChildren; foreach (Collider val in array) { if (!((Object)(object)val == (Object)null) && val.enabled) { if (flag) { ((Bounds)(ref bounds)).Encapsulate(val.bounds); continue; } bounds = val.bounds; flag = true; } } return flag; } catch { return false; } } private string BuildPrompt() { if (_kind == PortalKind.ReturnToSanctum) { return "Press " + CasinoInput.InteractPrompt + " to return to Sanctum."; } return "Press " + CasinoInput.InteractPrompt + " to enter " + _label + "."; } private static bool IsReturnToSanctumPortal(string objectName) { return string.Equals(objectName, "CasinoExitPortal", StringComparison.OrdinalIgnoreCase) || string.Equals(objectName, "CasinoReturnPortal", StringComparison.OrdinalIgnoreCase) || string.Equals(objectName, "ManualPortal_Sanctum", StringComparison.OrdinalIgnoreCase) || string.Equals(objectName, "ManualPortal_ReturnToSanctum", StringComparison.OrdinalIgnoreCase); } private static bool TryGetSourceId(string objectName, out string id) { return TryStripPrefix(objectName, "ManualPortal_", out id) || TryStripPrefix(objectName, "CasinoManualPortal_", out id); } private static bool TryGetTargetId(string objectName, out string id) { return TryStripPrefix(objectName, "ManualPortalTarget_", out id) || TryStripPrefix(objectName, "CasinoManualPortalTarget_", out id); } private static bool TryGetTwoWayEndpoint(string objectName, out string id, out bool isEndpointA) { id = string.Empty; isEndpointA = false; if (!TryStripPrefix(objectName, "ManualPortalTwoWay_", out string id2) && !TryStripPrefix(objectName, "CasinoManualPortalTwoWay_", out id2)) { return false; } if (id2.EndsWith("_A", StringComparison.OrdinalIgnoreCase)) { id = id2.Substring(0, id2.Length - 2).Trim(); isEndpointA = true; return !string.IsNullOrEmpty(id); } if (id2.EndsWith("_B", StringComparison.OrdinalIgnoreCase)) { id = id2.Substring(0, id2.Length - 2).Trim(); isEndpointA = false; return !string.IsNullOrEmpty(id); } Plugin.Log.LogWarning("[ManualPortal] Two-way portal '" + objectName + "' must end with '_A' or '_B'."); return false; } private static bool TryStripPrefix(string objectName, string prefix, out string id) { id = string.Empty; if (!objectName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return false; } id = objectName.Substring(prefix.Length).Trim(); return !string.IsNullOrEmpty(id); } private static string MakeLabel(string id) { return id.Replace('_', ' ').Replace('-', ' ').Trim(); } 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})"; } } public static class CasinoPatch { [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private int 5__1; private bool 5__2; private GameObject[] 5__3; private int 5__4; private int 5__5; private int 5__6; private int 5__7; private int 5__8; private int 5__9; private bool 5__10; private bool 5__11; private bool 5__12; private Scene 5__13; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Expected O, but got Unknown //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: { <>1__state = -1; _setupRunning = true; _overlayShader = Shader.Find("Unlit/Texture"); if ((Object)(object)_overlayShader == (Object)null) { _overlayShader = Shader.Find("Standard"); } CasinoLog log = Plugin.Log; Shader? overlayShader = _overlayShader; log.LogInfo("[Casino] Overlay shader: " + (((overlayShader != null) ? ((Object)overlayShader).name : null) ?? "NULL")); <>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; 5__1 = 0; 5__2 = false; break; case 3: <>1__state = -1; 5__3 = null; break; } if (5__1 < 15 && !5__2) { 5__1++; Plugin.Log.LogDebug($"[Casino] Setup attempt {5__1}/{15}..."); 5__3 = Object.FindObjectsOfType(); 5__4 = HookSlotMachines(5__3); 5__5 = HookBlackjackTables(5__3); 5__6 = HookRouletteTables(5__3); HookEntryFee(); 5__7 = CasinoManualPortal.HookScenePortals(5__3); 5__8 = CasinoPortalSigns.Ensure(5__3); HookExitTrigger(5__7); 5__9 = HookJukeboxes(5__3); 5__10 = 5__4 >= 1; 5__11 = 5__5 >= 1; 5__12 = 5__6 >= 1; if (!(5__10 & 5__11 & 5__12)) { Plugin.Log.LogDebug($"[Casino] Attempt {5__1} incomplete: " + $"slots={5__4} (need {1}, ok={5__10}), " + $"blackjack={5__5} (need {1}, ok={5__11}), " + $"roulette={5__6} (need {1}, ok={5__12}), " + $"manualPortals={5__7}, " + $"portalSigns={5__8}, " + $"jukeboxes={5__9}. " + $"Will retry in {1f}s..."); <>2__current = (object)new WaitForSeconds(1f); <>1__state = 3; return true; } 5__2 = true; Plugin.Log.LogInfo($"[Casino] Setup SUCCEEDED on attempt {5__1} — " + $"hooked {5__4} slot(s), {5__5} blackjack table(s), " + $"{5__6} roulette table(s)."); 5__13 = SceneManager.GetSceneByName("AtlyssCasino"); CasinoPerformanceOptimizer.ApplyToCasinoScene(5__13); BJNetcode.BroadcastCasinoConfigSync(); CasinoJukebox.AutoStartAll(); } if (!5__2) { Plugin.Log.LogError($"[Casino] Setup FAILED after {15} attempts. " + "Casino interactions will not work this session. This usually means Mirror never replicated the scene contents to this client."); } _setupRunning = false; 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 int MAX_RETRIES = 15; private const float RETRY_INTERVAL_SEC = 1f; private const int MIN_SLOT_MACHINES = 1; private const int MIN_BLACKJACK_TABLES = 1; private const int MIN_ROULETTE_TABLES = 1; private static Shader? _overlayShader; private static bool _setupRunning; public static void Init() { SceneManager.sceneLoaded += OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!(((Scene)(ref scene)).name != "AtlyssCasino")) { Plugin.Log.LogInfo("[Casino] Casino scene loaded — starting setup retry loop."); BJNetcode.SendCasinoConfigRequest(); if (_setupRunning) { Plugin.Log.LogWarning("[Casino] Setup coroutine already running — skipping duplicate."); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SetupWithRetry()); } } } [IteratorStateMachine(typeof(d__9))] private static IEnumerator SetupWithRetry() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0); } private static int HookSlotMachines(GameObject[] allObjects) { int num = 0; foreach (GameObject val in allObjects) { if (((Object)val).name != "SlotBet_Confirm") { continue; } Transform parent = val.transform.parent; GameObject val2 = ((parent != null) ? ((Component)parent).gameObject : null); if ((Object)(object)val2 == (Object)null) { continue; } if (((Object)val2).name.StartsWith("SlotMachine_") && (Object)(object)val2.GetComponent() != (Object)null) { num++; continue; } ((Object)val2).name = $"SlotMachine_{num}"; num++; SlotMachine slotMachine = val2.GetComponent() ?? val2.AddComponent(); slotMachine.MatIdle = MakeMat("idle"); slotMachine.MatIdleBlank = MakeMat("idle_blank"); slotMachine.MatWin = MakeMat("win"); slotMachine.MatJackpot = MakeMat("jackpot"); slotMachine.MatCherry = MakeMat("cherry"); slotMachine.MatLemon = MakeMat("lemon"); slotMachine.MatOrange = MakeMat("orange"); slotMachine.MatStar = MakeMat("star"); slotMachine.MatDiamond = MakeMat("diamond"); slotMachine.MatSeven = MakeMat("seven"); slotMachine.Init(); SlotBetConfirm slotBetConfirm = val.GetComponent() ?? val.AddComponent(); slotBetConfirm.Setup(slotMachine); Transform val3 = val2.transform.Find("SlotBet_Selector"); if ((Object)(object)val3 != (Object)null) { Plugin.Log.LogInfo("[Casino] Removing leftover SlotBet_Selector from old prefab instance."); Object.Destroy((Object)(object)((Component)val3).gameObject); } } Plugin.Log.LogInfo($"[Casino] Hooked {num} slot machine(s)."); return num; } private static int HookBlackjackTables(GameObject[] allObjects) { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; foreach (GameObject val in allObjects) { if (!((Object)val).name.StartsWith("Blackjack Table") || !HasAnyBlackjackWiring(val.transform)) { continue; } num++; bool flag = (Object)(object)val.GetComponent() != (Object)null; BlackjackTable blackjackTable = val.GetComponent() ?? val.AddComponent(); AttachTableInfo(blackjackTable); if (flag) { continue; } int num5 = 0; Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); Transform[] componentsInChildren = val.GetComponentsInChildren(true); Transform[] array = componentsInChildren; foreach (Transform val2 in array) { if ((Object)(object)val2 == (Object)(object)val.transform) { continue; } string text = ((Object)val2).name.Trim(); if (text == "DealerAnchor") { blackjackTable.SetDealerAnchor(val2); num5++; continue; } int num6 = ParseIndexedName(text, "SeatAnchor"); if (num6 >= 0) { if (num6 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num6} " + ">= MAX_PLAYERS; skipping."); continue; } blackjackTable.SetSeatAnchor(num6, val2); num5++; continue; } int num7 = ResolveSeatTriggerIndex(val2); if (num7 >= 0) { if (num7 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num7} " + ">= MAX_PLAYERS; skipping."); continue; } BlackjackSeatTrigger blackjackSeatTrigger = ((Component)val2).gameObject.GetComponent() ?? ((Component)val2).gameObject.AddComponent(); blackjackSeatTrigger.Setup(blackjackTable, num7); dictionary[num7] = blackjackSeatTrigger; continue; } int num8 = ParseIndexedName(text, "Stool"); if (num8 >= 0) { if (num8 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num8} " + ">= MAX_PLAYERS; skipping."); } else { dictionary2[num8] = val2; } } } int count = dictionary.Count; int num9 = 0; foreach (KeyValuePair item in dictionary) { if (dictionary2.TryGetValue(item.Key, out var value)) { item.Value.SetStool(value); num9++; } else { Plugin.Log.LogInfo($"[Casino] No Stool with index {item.Key} on " + "'" + ((Object)val).name + "' — seat color won't change."); } } Plugin.Log.LogInfo("[Casino] Hooked Blackjack table '" + ((Object)val).name + "' — " + $"{count} trigger(s), " + $"{num5} anchor(s), " + $"{num9} stool(s)."); num2 += count; num3 += num5; num4 += num9; } Plugin.Log.LogInfo($"[Casino] Hooked {num} blackjack table(s), " + $"{num2} trigger(s), {num3} anchor(s), " + $"{num4} stool(s)."); return num; } private static bool HasAnyBlackjackWiring(Transform parent) { Transform[] componentsInChildren = ((Component)parent).GetComponentsInChildren(true); Transform[] array = componentsInChildren; foreach (Transform val in array) { if (!((Object)(object)val == (Object)(object)parent)) { string text = ((Object)val).name.Trim(); if (text == "DealerAnchor") { return true; } if (ParseIndexedName(text, "SeatAnchor") >= 0) { return true; } if (ResolveSeatTriggerIndex(val) >= 0) { return true; } } } return false; } private static int HookRouletteTables(GameObject[] allObjects) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (GameObject val in allObjects) { if (!((Object)val).name.StartsWith("Roulette Table") || ((Object)(object)val.transform.parent != (Object)null && ((Object)val.transform.parent).name.StartsWith("Roulette Table"))) { continue; } num++; bool flag = (Object)(object)val.GetComponent() != (Object)null; RouletteTable rouletteTable = val.GetComponent() ?? val.AddComponent(); AttachTableInfo(rouletteTable); if (!flag) { rouletteTable.Init(); Transform val2 = val.transform.Find("RouletteTrigger"); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning("[Casino] Roulette table '" + ((Object)val).name + "' has no 'RouletteTrigger' child — players won't be able to join. Add an empty child GameObject named 'RouletteTrigger' in Unity."); continue; } RouletteTrigger rouletteTrigger = ((Component)val2).gameObject.GetComponent() ?? ((Component)val2).gameObject.AddComponent(); rouletteTrigger.Setup(rouletteTable); Plugin.Log.LogInfo("[Casino] Hooked roulette table '" + ((Object)val).name + "' " + $"with trigger at {val2.position}."); } } Plugin.Log.LogInfo($"[Casino] Hooked {num} roulette table(s)."); return num; } private static void AttachTableInfo(BlackjackTable table) { CasinoTableInfoDisplay casinoTableInfoDisplay = ((Component)table).gameObject.GetComponent() ?? ((Component)table).gameObject.AddComponent(); casinoTableInfoDisplay.Setup(table); } private static void AttachTableInfo(RouletteTable table) { CasinoTableInfoDisplay casinoTableInfoDisplay = ((Component)table).gameObject.GetComponent() ?? ((Component)table).gameObject.AddComponent(); casinoTableInfoDisplay.Setup(table); } private static void HookEntryFee() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("EntryFeeTrigger"); if (!((Object)(object)val == (Object)null) && (Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); Plugin.Log.LogInfo("[Casino] Entry fee trigger hooked at " + $"{val.transform.position}."); } } private static void HookExitTrigger(int manualPortalCount) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { return; } try { GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((object)val2).GetType().Name == "Portal") { Plugin.Log.LogInfo("[Casino] Vanilla Portal found at " + $"{((Component)val2).transform.position} on '{((Object)((Component)val2).gameObject).name}'. " + "F-key handling for clients runs from HCVW (no per-component AddComponent)."); return; } } } } catch (Exception ex) { Plugin.Log.LogWarning("[Casino] Portal scan threw: " + ex.Message); return; } if (manualPortalCount > 0) { Plugin.Log.LogInfo("[Casino] No vanilla Portal component found; using manual F-key portal object(s) instead."); } else { Plugin.Log.LogWarning("[Casino] No vanilla Portal component or manual portal object found in the casino scene. Add a GameObject named 'CasinoExitPortal', 'CasinoReturnPortal', 'ManualPortal_Sanctum', or a paired 'ManualPortal_' / 'ManualPortalTarget_' set, or a two-way 'ManualPortalTwoWay__A' / 'ManualPortalTwoWay__B' set."); } } private static int HookJukeboxes(GameObject[] allObjects) { int num = 0; foreach (GameObject val in allObjects) { if (!((Object)(object)val == (Object)null) && ((Object)val).name.StartsWith("CasinoJukebox", StringComparison.Ordinal)) { CasinoJukebox casinoJukebox = val.GetComponent() ?? val.AddComponent(); casinoJukebox.Setup(); num++; } } if (num == 0) { Plugin.Log.LogWarning("[Jukebox] No CasinoJukebox object found in the casino scene."); } else { Plugin.Log.LogInfo($"[Jukebox] Hooked {num} casino jukebox object(s)."); } return num; } private static int ParseIndexedName(string name, string prefix) { if (name == prefix) { return 0; } string text = prefix + " ("; if (!name.StartsWith(text)) { return -1; } if (!name.EndsWith(")")) { return -1; } int length = text.Length; int num = name.Length - text.Length - ")".Length; if (num <= 0) { return -1; } string s = name.Substring(length, num); if (!int.TryParse(s, out var result)) { return -1; } return result; } private static int ResolveSeatTriggerIndex(Transform trigger) { if ((Object)(object)trigger == (Object)null) { return -1; } string text = ((Object)trigger).name.Trim(); if (!text.StartsWith("SeatTrigger", StringComparison.Ordinal)) { return -1; } int num = FindAncestorIndexedName(trigger.parent, "Stool"); if (num >= 0) { return num; } return ParseIndexedName(text, "SeatTrigger"); } private static int FindAncestorIndexedName(Transform? start, string prefix) { Transform val = start; while ((Object)(object)val != (Object)null) { int num = ParseIndexedName(((Object)val).name.Trim(), prefix); if (num >= 0) { return num; } val = val.parent; } return -1; } private static Material? MakeMat(string textureName) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.AssetsBundle == (Object)null) { Plugin.Log.LogWarning("[Casino] AssetsBundle is null."); return null; } Texture2D val = Plugin.AssetsBundle.LoadAsset(textureName); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning("[Casino] Texture not found: " + textureName); return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; if ((Object)(object)_overlayShader == (Object)null) { Plugin.Log.LogError("[Casino] No shader available for overlay!"); return null; } Material val2 = new Material(_overlayShader) { name = "SlotMat_Runtime_" + textureName, mainTexture = (Texture)(object)val }; val2.mainTextureScale = Vector2.one; val2.mainTextureOffset = Vector2.zero; return val2; } } internal static class CasinoPerformanceOptimizer { internal static void ApplyToCasinoScene(Scene scene) { //IL_0076: 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) if (!Plugin.IsHeadlessServer && ((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded && !(((Scene)(ref scene)).name != "AtlyssCasino")) { if (Plugin.HideDecorativeProps) { int num = HideDecorativeProps(scene); Plugin.Log.LogInfo($"[CasinoPerf] Hid {num} decorative prop root(s)."); } int num2 = ApplySlotVisualMode(scene); if (Plugin.ReduceIdleSlotVisuals) { Plugin.Log.LogInfo("[CasinoPerf] Applied idle slot screen reduction to " + $"{num2} slot machine(s)."); } } } private static int HideDecorativeProps(Scene scene) { List list = new List(); GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { if ((Object)(object)val == (Object)null) { continue; } Transform[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { GameObject gameObject = ((Component)val2).gameObject; if (!((Object)(object)gameObject == (Object)null) && gameObject.activeSelf && IsDecorativeCandidate(val2) && !HasSelectedAncestor(val2, list)) { list.Add(val2); } } } } int num = 0; for (int k = 0; k < list.Count; k++) { Transform val3 = list[k]; if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((Component)val3).gameObject == (Object)null)) { try { ((Component)val3).gameObject.SetActive(false); num++; } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoPerf] Failed to hide decorative prop '" + ((Object)val3).name + "': " + ex.Message); } } } return num; } private static int ApplySlotVisualMode(Scene scene) { int num = 0; GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { if ((Object)(object)val == (Object)null) { continue; } SlotMachine[] componentsInChildren = val.GetComponentsInChildren(true); foreach (SlotMachine slotMachine in componentsInChildren) { if (!((Object)(object)slotMachine == (Object)null)) { slotMachine.ApplyPerformanceSettings(); num++; } } } return num; } private static bool IsDecorativeCandidate(Transform transform) { GameObject gameObject = ((Component)transform).gameObject; if ((Object)(object)gameObject == (Object)null) { return false; } string text = Normalize(((Object)transform).name); if (string.IsNullOrEmpty(text)) { return false; } if (text == "bar" || text.StartsWith("barstool")) { return false; } if (IsSpotLight(gameObject, text)) { return true; } if (ContainsAny(text, "neon", "bottle", "bottles", "beerbottle", "winebottle", "champagnebottle", "liquor", "shelf", "shelves", "bgmoshelf", "hook", "plant", "plants", "pothos", "heartleaf", "philodendron", "anthurium", "potheartleaf", "potheartleafphilodendron", "potanthurium", "zebraplant", "potzebraplant")) { return true; } return false; } private static bool HasSelectedAncestor(Transform transform, List selected) { Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { for (int i = 0; i < selected.Count; i++) { if ((Object)(object)selected[i] == (Object)(object)parent) { return true; } } parent = parent.parent; } return false; } private static bool IsSpotLight(GameObject go, string normalizedName) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 if (normalizedName.StartsWith("spotlight")) { return true; } Light component = go.GetComponent(); return (Object)(object)component != (Object)null && (int)component.type == 0; } private static string Normalize(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Trim().ToLowerInvariant().Replace(" ", string.Empty) .Replace("_", string.Empty) .Replace("-", string.Empty) .Replace("(", string.Empty) .Replace(")", string.Empty); } private static bool ContainsAny(string value, params string[] needles) { for (int i = 0; i < needles.Length; i++) { if (value.Contains(needles[i])) { return true; } } return false; } } internal static class CasinoPortalSigns { private sealed class SignSpec { internal readonly string Key; internal readonly string DisplayName; internal readonly string Text; internal readonly float CharacterSize; internal readonly string[] Aliases; internal readonly string[] ExactNames; internal SignSpec(string key, string displayName, string text, float characterSize, string[] aliases, params string[] exactNames) { Key = key; DisplayName = displayName; Text = text; CharacterSize = characterSize; Aliases = aliases; ExactNames = exactNames; } } private static readonly string[] SignPrefixes = new string[6] { "CasinoTeleportSign", "TeleportSign", "CasinoPortalSign", "PortalSign", "CasinoElevatorSign", "ElevatorSign" }; private static readonly SignSpec[] Signs = new SignSpec[5] { new SignSpec("elevator", "Elevator", "Elevator", 0.12f, new string[5] { "Elevator", "ElevatorFloors", "Floors", "Directory", "ElevatorDirectory" }, "Elevator Floors Sign", "Teleport Sign - Elevator", "CasinoElevatorSign"), new SignSpec("office", "Office", "Office", 0.12f, new string[1] { "Office" }, "Teleport Sign - Office"), new SignSpec("basement", "Basement", "Basement", 0.12f, new string[3] { "Basement", "CasinoBasement", "Casino Basement" }, "Teleport Sign - Basement"), new SignSpec("floor1", "Floor 1", "Floor 1", 0.12f, new string[4] { "Floor1", "Floor_1", "Floor 1", "FirstFloor" }, "Teleport Sign - Floor 1"), new SignSpec("floor2", "Floor 2", "Floor 2", 0.12f, new string[4] { "Floor2", "Floor_2", "Floor 2", "SecondFloor" }, "Teleport Sign - Floor 2") }; private static bool _loggedMissingAuthoredSigns; internal static int Ensure(GameObject[] allObjects) { if (allObjects == null || allObjects.Length == 0) { return 0; } int num = 0; HashSet hashSet = new HashSet(); foreach (GameObject val in allObjects) { if (!((Object)(object)val == (Object)null) && hashSet.Add(val) && TryResolveSpec(((Object)val).name, out SignSpec spec) && spec != null) { CasinoTeleportSignDisplay casinoTeleportSignDisplay = val.GetComponent() ?? val.AddComponent(); bool flag = casinoTeleportSignDisplay.SignKey == spec.Key && casinoTeleportSignDisplay.HasTextTarget; casinoTeleportSignDisplay.Setup(spec.Key, spec.Text, spec.CharacterSize); num++; if (!flag) { Plugin.Log.LogInfo("[PortalSigns] Bound authored teleport sign '" + ((Object)val).name + "' as '" + spec.DisplayName + "'."); } } } if (num == 0 && !_loggedMissingAuthoredSigns) { _loggedMissingAuthoredSigns = true; Plugin.Log.LogInfo("[PortalSigns] No authored teleport signs found. Add active scene objects named CasinoTeleportSign_Elevator, CasinoTeleportSign_Office, CasinoTeleportSign_Basement, CasinoTeleportSign_Floor1, and CasinoTeleportSign_Floor2. Optional child names Casino_TeleportSignAnchor and Casino_TeleportSignText are supported; table sign child names also work for duplicated floating sign prefabs."); } return num; } private static bool TryResolveSpec(string objectName, out SignSpec? spec) { string text = NormalizeKey(objectName); SignSpec[] signs = Signs; foreach (SignSpec signSpec in signs) { string[] exactNames = signSpec.ExactNames; foreach (string value in exactNames) { if (text == NormalizeKey(value)) { spec = signSpec; return true; } } string[] signPrefixes = SignPrefixes; foreach (string value2 in signPrefixes) { string text2 = NormalizeKey(value2); if (!text.StartsWith(text2, StringComparison.Ordinal)) { continue; } string text3 = text.Substring(text2.Length); string[] aliases = signSpec.Aliases; foreach (string value3 in aliases) { if (text3 == NormalizeKey(value3)) { spec = signSpec; return true; } } } } spec = null; return false; } private static string NormalizeKey(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } value = StripCloneSuffix(value).Trim(); char[] array = value.ToCharArray(); int length = 0; foreach (char c in array) { if (!char.IsWhiteSpace(c) && c != '_' && c != '-' && c != ':' && c != '/') { array[length++] = char.ToLowerInvariant(c); } } return new string(array, 0, length); } private static string StripCloneSuffix(string value) { int num = value.LastIndexOf("(", StringComparison.Ordinal); int num2 = value.LastIndexOf(")", StringComparison.Ordinal); if (num <= 0 || num2 != value.Length - 1) { return value; } string s = value.Substring(num + 1, num2 - num - 1); int result; return int.TryParse(s, out result) ? value.Substring(0, num) : value; } } public sealed class CasinoTeleportSignDisplay : MonoBehaviour { private const string TELEPORT_TEXT_OBJECT_NAME = "Casino_TeleportSignText"; private const string TELEPORT_ANCHOR_OBJECT_NAME = "Casino_TeleportSignAnchor"; private const string TABLE_TEXT_OBJECT_NAME = "Casino_TableInfoText"; private const string TABLE_ANCHOR_OBJECT_NAME = "Casino_TableInfoAnchor"; private const string GENERIC_TEXT_OBJECT_NAME = "Casino_InfoText"; private const string GENERIC_ANCHOR_OBJECT_NAME = "Casino_InfoAnchor"; private const float RESOLVE_RETRY_INTERVAL_SEC = 1f; private string _signKey = string.Empty; private string _text = string.Empty; private float _characterSize = 0.1f; private GameObject? _textGameObject; private TextMesh? _textMesh; private string _lastText = string.Empty; private float _resolveRetryTimer; internal string SignKey => _signKey; internal bool HasTextTarget => (Object)(object)_textMesh != (Object)null; internal void Setup(string signKey, string text, float characterSize) { ((Behaviour)this).enabled = true; _signKey = signKey ?? string.Empty; _text = text ?? string.Empty; _characterSize = characterSize; ResolveTextTarget(); UpdateText(force: true); } private void Update() { if (string.IsNullOrEmpty(_text)) { return; } if ((Object)(object)_textMesh == (Object)null) { _resolveRetryTimer -= Time.deltaTime; if (_resolveRetryTimer > 0f) { return; } _resolveRetryTimer = 1f; ResolveTextTarget(); if ((Object)(object)_textMesh == (Object)null) { return; } } UpdateText(force: false); } private void ResolveTextTarget() { Transform val = FindDeepChild(((Component)this).transform, "Casino_TeleportSignAnchor") ?? FindDeepChild(((Component)this).transform, "Casino_TableInfoAnchor") ?? FindDeepChild(((Component)this).transform, "Casino_InfoAnchor"); Transform val2 = null; if ((Object)(object)val != (Object)null) { val2 = FindDeepChild(val, "Casino_TeleportSignText") ?? FindDeepChild(val, "Casino_TableInfoText") ?? FindDeepChild(val, "Casino_InfoText"); } if (val2 == null) { val2 = FindDeepChild(((Component)this).transform, "Casino_TeleportSignText") ?? FindDeepChild(((Component)this).transform, "Casino_TableInfoText") ?? FindDeepChild(((Component)this).transform, "Casino_InfoText") ?? val ?? ((Component)this).transform; } if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); } ((Component)val2).gameObject.SetActive(true); _textGameObject = ((Component)val2).gameObject; DisableNonTextMeshTextComponents(((Component)val2).gameObject); _textMesh = ((Component)val2).GetComponent(); bool createdTextMesh = (Object)(object)_textMesh == (Object)null; if ((Object)(object)_textMesh == (Object)null) { _textMesh = ((Component)val2).gameObject.AddComponent(); } ConfigureTextMesh(_textMesh, createdTextMesh); } private void UpdateText(bool force) { if (!((Object)(object)_textMesh == (Object)null)) { if ((Object)(object)_textGameObject != (Object)null && !_textGameObject.activeSelf) { _textGameObject.SetActive(true); } if (force || !(_lastText == _text)) { _lastText = _text; _textMesh.text = _text; } } } private void ConfigureTextMesh(TextMesh textMesh, bool createdTextMesh) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (createdTextMesh) { textMesh.anchor = (TextAnchor)4; textMesh.alignment = (TextAlignment)1; textMesh.fontSize = 64; textMesh.characterSize = _characterSize; textMesh.lineSpacing = 0.9f; textMesh.color = new Color(1f, 0.86f, 0.55f, 1f); textMesh.richText = false; } ApplyDepthTestedTextMeshMaterial(textMesh); } private static void DisableNonTextMeshTextComponents(GameObject root) { Component[] componentsInChildren = root.GetComponentsInChildren(true); Component[] array = componentsInChildren; foreach (Component val in array) { if ((Object)(object)val == (Object)null || val is TextMesh) { continue; } Type type = ((object)val).GetType(); string name = type.Name; string text = type.FullName ?? string.Empty; if (name == "TextMeshPro" || name == "TextMeshProUGUI" || text.IndexOf("TMP_Text", StringComparison.OrdinalIgnoreCase) >= 0 || text == "UnityEngine.UI.Text") { Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val2 != null) { val2.enabled = false; } } } } private static void ApplyDepthTestedTextMeshMaterial(TextMesh textMesh) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00a8: 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) Renderer component = ((Component)textMesh).GetComponent(); if ((Object)(object)component == (Object)null) { return; } Material sharedMaterial = component.sharedMaterial; Texture val = null; if ((Object)(object)sharedMaterial != (Object)null) { val = sharedMaterial.mainTexture; } if ((Object)(object)val == (Object)null && (Object)(object)textMesh.font != (Object)null && (Object)(object)textMesh.font.material != (Object)null) { val = textMesh.font.material.mainTexture; } Shader val2 = Shader.Find("Unlit/Transparent") ?? Shader.Find("Sprites/Default"); Material val3 = ((!((Object)(object)val2 != (Object)null)) ? ((!((Object)(object)sharedMaterial != (Object)null)) ? ((Material)null) : new Material(sharedMaterial)) : new Material(val2)); if (!((Object)(object)val3 == (Object)null)) { ((Object)val3).name = "CasinoPortalSign_TextMesh_DepthTested"; if ((Object)(object)val != (Object)null && val3.HasProperty("_MainTex")) { val3.mainTexture = val; } if (val3.HasProperty("_Color")) { val3.color = textMesh.color; } SetDepthTestProperties(val3); component.material = val3; } } private static void SetDepthTestProperties(Material material) { try { if (material.HasProperty("_ZTest")) { material.SetInt("_ZTest", 4); } if (material.HasProperty("_ZTestMode")) { material.SetInt("_ZTestMode", 4); } if (material.HasProperty("unity_GUIZTestMode")) { material.SetInt("unity_GUIZTestMode", 4); } material.renderQueue = 3000; } catch { } } private static Transform? FindDeepChild(Transform root, string childName) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown foreach (Transform item in root) { Transform val = item; if (((Object)val).name == childName) { return val; } Transform val2 = FindDeepChild(val, childName); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } } public sealed class CasinoTableInfoDisplay : MonoBehaviour { private const string TEXT_OBJECT_NAME = "Casino_TableInfoText"; private const string ANCHOR_OBJECT_NAME = "Casino_TableInfoAnchor"; private const float UPDATE_INTERVAL = 0.25f; private const float ROUND_COMPLETE_DISPLAY_SEC = 7f; private BlackjackTable? _blackjackTable; private RouletteTable? _rouletteTable; private GameObject? _textGameObject; private Component? _textComponent; private PropertyInfo? _textProperty; private string _lastText = string.Empty; private float _updateTimer; private bool _lastBlackjackRoundInProgress; private float _blackjackRoundCompleteUntil; private bool _lastRouletteSpinning; private float _rouletteRoundCompleteUntil; public void Setup(BlackjackTable table) { ((Behaviour)this).enabled = true; if (_blackjackTable != table) { _lastBlackjackRoundInProgress = table.RoundInProgress; _blackjackRoundCompleteUntil = 0f; } _blackjackTable = table; _rouletteTable = null; ResolveTextTarget(); UpdateText(force: true); } public void Setup(RouletteTable table) { ((Behaviour)this).enabled = true; if (_rouletteTable != table) { _lastRouletteSpinning = table.IsSpinning; _rouletteRoundCompleteUntil = 0f; } _rouletteTable = table; _blackjackTable = null; ResolveTextTarget(); UpdateText(force: true); } private void Update() { if ((Object)(object)_textComponent == (Object)null) { ResolveTextTarget(); if ((Object)(object)_textComponent == (Object)null) { return; } } _updateTimer -= Time.deltaTime; if (!(_updateTimer > 0f)) { _updateTimer = 0.25f; UpdateText(force: false); } } private void ResolveTextTarget() { Transform val = FindDeepChild(((Component)this).transform, "Casino_TableInfoAnchor"); Transform val2 = (((Object)(object)val == (Object)null) ? FindDeepChild(((Component)this).transform, "Casino_TableInfoText") : FindDeepChild(val, "Casino_TableInfoText")); if ((Object)(object)val2 == (Object)null) { val2 = val; } if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning("[TableInfo] '" + ((Object)this).name + "' has no Casino_TableInfoText or Casino_TableInfoAnchor child. Floating table info disabled."); ((Behaviour)this).enabled = false; return; } if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); } ((Component)val2).gameObject.SetActive(true); _textGameObject = ((Component)val2).gameObject; _textComponent = FindWritableTextComponent(((Component)val2).gameObject); if ((Object)(object)_textComponent == (Object)null) { _textComponent = AddTextMeshFallback(val2); } if ((Object)(object)_textComponent == (Object)null) { Plugin.Log.LogWarning("[TableInfo] '" + ((Object)this).name + "' found '" + ((Object)val2).name + "' but could not find or create a writable text component."); ((Behaviour)this).enabled = false; return; } ApplyDepthTestedMaterial(_textComponent); _textProperty = ((object)_textComponent).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public); if (_textProperty == null || !_textProperty.CanWrite) { Plugin.Log.LogWarning("[TableInfo] '" + ((Object)this).name + "' text component '" + ((object)_textComponent).GetType().Name + "' has no writable text property."); ((Behaviour)this).enabled = false; } } private void UpdateText(bool force) { if ((Object)(object)_textComponent == (Object)null || _textProperty == null) { return; } if (!Plugin.FloatingTableInfoEnabled) { SetTextVisible(visible: false); _lastText = string.Empty; return; } string text; if ((Object)(object)_blackjackTable != (Object)null) { bool showRoundComplete = UpdateBlackjackRoundFlags(_blackjackTable); text = (IsBlackjackActive(_blackjackTable, showRoundComplete) ? BuildBlackjackText(_blackjackTable, showRoundComplete) : string.Empty); } else if ((Object)(object)_rouletteTable != (Object)null) { bool showRoundComplete2 = UpdateRouletteRoundFlags(_rouletteTable); text = (IsRouletteActive(_rouletteTable, showRoundComplete2) ? BuildRouletteText(_rouletteTable, showRoundComplete2) : string.Empty); } else { text = string.Empty; } bool flag = !string.IsNullOrEmpty(text); SetTextVisible(flag); if (!flag) { _lastText = string.Empty; } else if (force || !(text == _lastText)) { _lastText = text; _textProperty.SetValue(_textComponent, text, null); } } private void SetTextVisible(bool visible) { if ((Object)(object)_textGameObject != (Object)null && _textGameObject.activeSelf != visible) { _textGameObject.SetActive(visible); } } private bool UpdateBlackjackRoundFlags(BlackjackTable table) { bool roundInProgress = table.RoundInProgress; if (_lastBlackjackRoundInProgress && !roundInProgress && table.OccupiedSeatCount > 0) { _blackjackRoundCompleteUntil = Time.time + 7f; } if (roundInProgress) { _blackjackRoundCompleteUntil = 0f; } _lastBlackjackRoundInProgress = roundInProgress; return !roundInProgress && Time.time < _blackjackRoundCompleteUntil; } private static bool IsBlackjackActive(BlackjackTable table, bool showRoundComplete) { return table.RoundInProgress || showRoundComplete || table.OccupiedSeatCount > 0; } private bool UpdateRouletteRoundFlags(RouletteTable table) { bool isSpinning = table.IsSpinning; if (_lastRouletteSpinning && !isSpinning && table.PlayerCount > 0) { _rouletteRoundCompleteUntil = Time.time + 7f; } if (isSpinning) { _rouletteRoundCompleteUntil = 0f; } _lastRouletteSpinning = isSpinning; return !isSpinning && Time.time < _rouletteRoundCompleteUntil; } private static bool IsRouletteActive(RouletteTable table, bool showRoundComplete) { return table.IsSpinning || showRoundComplete || table.PlayerCount > 0; } private static string BuildBlackjackText(BlackjackTable table, bool showRoundComplete) { StringBuilder stringBuilder = new StringBuilder(128); Player hostPlayer = table.GetHostPlayer(); stringBuilder.Append("Host: "); stringBuilder.Append(FormatPlayerName(hostPlayer, "None")); stringBuilder.Append('\n'); stringBuilder.Append("Round: "); if (table.RoundInProgress) { stringBuilder.Append("In Progress"); } else if (showRoundComplete) { stringBuilder.Append("Complete"); } else if (table.OccupiedSeatCount > 0) { stringBuilder.Append("Waiting"); } else { stringBuilder.Append("Idle"); } if (table.DealerHand.Cards.Count > 0) { stringBuilder.Append('\n'); stringBuilder.Append("Dealer: "); stringBuilder.Append(DescribeDealerValueForView(table)); } stringBuilder.Append('\n'); if (table.RoundInProgress) { if (table.CurrentTurnSeat >= 0) { Player seatedPlayer = table.GetSeatedPlayer(table.CurrentTurnSeat); stringBuilder.Append("Turn: "); stringBuilder.Append(FormatPlayerName(seatedPlayer, $"Seat {table.CurrentTurnSeat + 1}")); } else { stringBuilder.Append("Turn: Dealer"); } } else if (table.OccupiedSeatCount > 0) { int num = 0; for (int i = 0; i < 5; i++) { if (table.IsPlayerReady(i)) { num++; } } stringBuilder.Append("Ready: "); stringBuilder.Append(num); stringBuilder.Append('/'); stringBuilder.Append(table.OccupiedSeatCount); } else { stringBuilder.Append("Waiting for players"); } stringBuilder.Append('\n'); stringBuilder.Append("Players: "); stringBuilder.Append(table.OccupiedSeatCount); stringBuilder.Append('/'); stringBuilder.Append(5); return stringBuilder.ToString(); } private static string DescribeDealerValueForView(BlackjackTable table) { BlackjackHand dealerHand = table.DealerHand; if (dealerHand.Cards.Count == 0) { return ""; } int num = 0; int num2 = 0; bool flag = false; for (int i = 0; i < dealerHand.Cards.Count; i++) { bool flag2 = true; if (i < dealerHand.CardObjects.Count && (Object)(object)dealerHand.CardObjects[i] != (Object)null) { CardVisual component = dealerHand.CardObjects[i].GetComponent(); if ((Object)(object)component != (Object)null) { flag2 = component.IsFaceUp; } } if (flag2) { Card card = dealerHand.Cards[i]; num += card.MinValue; if (card.IsAce) { num2++; } } else { flag = true; } } if (flag) { while (num2 > 0 && num + 10 <= 21) { num += 10; num2--; } return (num > 0) ? num.ToString() : "??"; } string text = dealerHand.BestValue.ToString(); if (dealerHand.IsNaturalBlackjack) { text += " Blackjack"; } else if (dealerHand.HasBusted) { text += " BUST"; } return text; } private static string BuildRouletteText(RouletteTable table, bool showRoundComplete) { StringBuilder stringBuilder = new StringBuilder(128); Player hostPlayer = table.GetHostPlayer(); stringBuilder.Append("Host: "); stringBuilder.Append(FormatPlayerName(hostPlayer, "None")); stringBuilder.Append('\n'); stringBuilder.Append("Round: "); if (table.IsSpinning) { stringBuilder.Append("In Progress"); } else if (showRoundComplete) { stringBuilder.Append("Complete"); } else if (table.PlayerCount > 0) { stringBuilder.Append("Waiting"); } else { stringBuilder.Append("Idle"); } if (!table.IsSpinning && table.LastWinningNumber >= 0) { stringBuilder.Append('\n'); stringBuilder.Append("Landed: "); stringBuilder.Append(FormatRouletteResult(table.LastWinningNumber)); } stringBuilder.Append('\n'); if (table.IsSpinning) { stringBuilder.Append("State: Wheel spinning"); } else if (table.PlayerCount > 0) { stringBuilder.Append("Ready: "); stringBuilder.Append(table.GetReadyCount()); stringBuilder.Append('/'); stringBuilder.Append(table.PlayerCount); } else { stringBuilder.Append("Waiting for players"); } stringBuilder.Append('\n'); stringBuilder.Append("Players: "); stringBuilder.Append(table.PlayerCount); stringBuilder.Append('/'); stringBuilder.Append(8); return stringBuilder.ToString(); } private static string FormatRouletteResult(int number) { string arg = ((number == 0) ? "Green" : (RouletteLogic.IsRed(number) ? "Red" : "Black")); return $"{number} ({arg})"; } private static string FormatPlayerName(Player? player, string fallback) { if ((Object)(object)player == (Object)null) { return fallback; } string text = TryReadStringMember(player, "Network_playerName", "Network_displayName", "Network_characterName", "playerName", "displayName", "characterName", "_playerName", "_displayName", "_characterName"); if (!string.IsNullOrWhiteSpace(text)) { return NormalizePlayerName(text, fallback); } string raw = ((Object)player).name ?? string.Empty; string text2 = NormalizePlayerName(raw, fallback); return string.IsNullOrWhiteSpace(text2) ? fallback : text2; } private static string NormalizePlayerName(string raw, string fallback) { if (string.IsNullOrWhiteSpace(raw)) { return fallback; } string text = raw.Trim(); int num = text.LastIndexOf('('); if (num >= 0) { int num2 = text.IndexOf(')', num + 1); if (num2 > num + 1) { text = text.Substring(num + 1, num2 - num - 1).Trim(); } } int num3 = text.LastIndexOf('/'); if (num3 >= 0 && num3 < text.Length - 1) { text = text.Substring(num3 + 1).Trim(); } return string.IsNullOrWhiteSpace(text) ? fallback : text; } private static string? TryReadStringMember(object obj, params string[] names) { Type type = obj.GetType(); foreach (string name in names) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.GetValue(obj, null) is string result) { return result; } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.GetValue(obj) is string result2) { return result2; } } return null; } private static Component? FindWritableTextComponent(GameObject root) { Component[] componentsInChildren = root.GetComponentsInChildren(true); Component[] array = componentsInChildren; foreach (Component val in array) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); if (type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public) == null) { continue; } string name = type.Name; string text = type.FullName ?? string.Empty; switch (name) { default: if (!text.Contains("TMP_Text")) { continue; } break; case "TextMesh": case "TextMeshPro": case "TextMeshProUGUI": break; } return val; } return null; } private static Component? AddTextMeshFallback(Transform target) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) Type type = ResolveType("UnityEngine.TextMesh"); if (type == null) { return null; } Component val = ((Component)target).gameObject.AddComponent(type); SetProperty(val, "anchor", "MiddleCenter"); SetProperty(val, "alignment", "Center"); SetProperty(val, "fontSize", 42); SetProperty(val, "characterSize", 0.08f); SetProperty(val, "color", (object)new Color(1f, 0.86f, 0.55f, 1f)); ApplyDepthTestedMaterial(val); return val; } private static void ApplyDepthTestedMaterial(Component textComponent) { Renderer[] componentsInChildren = textComponent.GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Material[] materials = val.materials; bool flag = false; for (int j = 0; j < materials.Length; j++) { Material val2 = materials[j]; if ((Object)(object)val2 == (Object)null) { continue; } SetDepthTestProperties(val2); string shaderName = (((Object)(object)val2.shader != (Object)null) ? ((Object)val2.shader).name : string.Empty); if (ShouldReplaceTextMaterial(val2, shaderName)) { Material val3 = CreateDepthTestedTextMaterial(val2, shaderName); if ((Object)(object)val3 != (Object)null) { materials[j] = val3; flag = true; } } } if (flag) { val.materials = materials; } } } private static bool ShouldReplaceTextMaterial(Material mat, string shaderName) { if (shaderName.IndexOf("GUI/Text Shader", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (shaderName.IndexOf("Overlay", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return !mat.HasProperty("_ZTest") && !mat.HasProperty("_ZTestMode") && !mat.HasProperty("unity_GUIZTestMode") && shaderName.IndexOf("Text", StringComparison.OrdinalIgnoreCase) >= 0; } private static void SetDepthTestProperties(Material mat) { try { if (mat.HasProperty("_ZTest")) { mat.SetInt("_ZTest", 4); } if (mat.HasProperty("_ZTestMode")) { mat.SetInt("_ZTestMode", 4); } if (mat.HasProperty("unity_GUIZTestMode")) { mat.SetInt("unity_GUIZTestMode", 4); } mat.renderQueue = 3000; } catch { } } private static Material? CreateDepthTestedTextMaterial(Material source, string shaderName) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_00d8: 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_00f0: 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) Shader val = null; if (shaderName.IndexOf("TextMeshPro", StringComparison.OrdinalIgnoreCase) >= 0) { val = Shader.Find("TextMeshPro/Distance Field") ?? Shader.Find("TextMeshPro/Mobile/Distance Field"); } if (val == null) { val = Shader.Find("Unlit/Transparent"); } if ((Object)(object)val == (Object)null) { return null; } Material val2 = new Material(source); ((Object)val2).name = ((Object)source).name + "_DepthTested"; val2.shader = val; if (source.HasProperty("_MainTex") && val2.HasProperty("_MainTex")) { val2.mainTexture = source.mainTexture; } Color color = default(Color); ((Color)(ref color))..ctor(1f, 0.86f, 0.55f, 1f); if (source.HasProperty("_Color")) { color = source.color; } if (val2.HasProperty("_Color")) { val2.color = color; } if (source.HasProperty("_FaceColor") && val2.HasProperty("_FaceColor")) { val2.SetColor("_FaceColor", source.GetColor("_FaceColor")); } SetDepthTestProperties(val2); return val2; } private static void SetProperty(Component component, string name, object value) { try { PropertyInfo property = ((object)component).GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if (!(property == null) && property.CanWrite) { if (property.PropertyType.IsEnum && value is string value2) { value = Enum.Parse(property.PropertyType, value2); } property.SetValue(component, value, null); } } catch { } } private static Type? ResolveType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType(fullName); if (type != null) { return type; } } return null; } private static Transform? FindDeepChild(Transform root, string childName) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown foreach (Transform item in root) { Transform val = item; if (((Object)val).name == childName) { return val; } Transform val2 = FindDeepChild(val, childName); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } } public class CasinoExitTrigger { [CompilerGenerated] private sealed class d__2 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Player player; public string caller; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__2(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Plugin.ShowHUDInfo("Returning to Sanctum..."); <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; case 1: <>1__state = -1; ConfigurePortal(caller); <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; Plugin.Log.LogInfo(caller + " Strategy 1: Cmd_SceneTransport (full path)..."); if (TrySceneTransport(player, "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity", "startPoint")) { Plugin.Log.LogInfo(caller + " Cmd_SceneTransport (full path) fired."); return false; } <>2__current = null; <>1__state = 3; return true; case 3: <>1__state = -1; Plugin.Log.LogInfo(caller + " Strategy 2: Cmd_SceneTransport (short name)..."); if (TrySceneTransport(player, "Sanctum", "startPoint")) { Plugin.Log.LogInfo(caller + " Cmd_SceneTransport (short) fired."); return false; } <>2__current = null; <>1__state = 4; return true; case 4: <>1__state = -1; Plugin.Log.LogInfo(caller + " Strategy 3: InteractQueue_RecallPortal (fallback)..."); if (TryInteractQueue(caller)) { Plugin.Log.LogInfo(caller + " InteractQueue_RecallPortal fired."); return false; } Plugin.Log.LogInfo(caller + " Strategy 4: _requestedRecall..."); if (TrySetRecallFlag(player)) { Plugin.Log.LogInfo(caller + " _requestedRecall set."); return false; } Plugin.Log.LogWarning(caller + " Strategy 5: SceneManager.LoadScene..."); SceneManager.LoadScene("Assets/Scenes/00_zone_forest/_zone00_sanctum.unity"); 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 SANCTUM_PATH = "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity"; public static bool IsTransporting; [IteratorStateMachine(typeof(d__2))] public static IEnumerator ForceReturnToSanctum(Player player, string caller = "[ExitTrigger]") { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__2(0) { player = player, caller = caller }; } private static void ConfigurePortal(string caller) { GameObject val = GameObject.Find("_entity_recallPortal"); if ((Object)(object)val == (Object)null) { return; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } Type type = ((object)val2).GetType(); FieldInfo field = type.GetField("_scenePortal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null)) { object value = field.GetValue(val2); if (value != null) { Type type2 = value.GetType(); type2.GetField("_subScene", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(value, "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity"); type2.GetField("_spawnPointTag", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(value, "startPoint"); type.GetField("_isPortalOpen", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(val2, true); field.SetValue(val2, value); Plugin.Log.LogInfo(caller + " RecallPortal configured → 'Assets/Scenes/00_zone_forest/_zone00_sanctum.unity'"); break; } } } } private static bool TryInteractQueue(string caller) { GameObject val = GameObject.Find("_entity_recallPortal"); if ((Object)(object)val == (Object)null) { return false; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); string[] array = new string[5] { "InteractQueue_RecallPortal", "Init_RecallPortal", "Interact_RecallPortal", "Activate_RecallPortal", "Open_RecallPortal" }; MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } Type type = ((object)val2).GetType(); string[] array3 = array; foreach (string text in array3) { MethodInfo method = type.GetMethod(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(method == null)) { Plugin.Log.LogInfo(caller + " Calling " + type.Name + "." + text + "()"); try { method.Invoke(val2, null); return true; } catch (Exception ex) { Plugin.Log.LogWarning(caller + " " + text + " threw: " + ex.Message); } } } } MonoBehaviour[] array4 = componentsInChildren; foreach (MonoBehaviour val3 in array4) { if (!((Object)(object)val3 == (Object)null) && !(((object)val3).GetType().Name != "RecallPortal")) { MethodInfo[] methods = ((object)val3).GetType().GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); StringBuilder stringBuilder = new StringBuilder(); MethodInfo[] array5 = methods; foreach (MethodInfo methodInfo in array5) { stringBuilder.Append(methodInfo.Name).Append(", "); } Plugin.Log.LogInfo($"{caller} RecallPortal methods: {stringBuilder}"); } } return false; } private static bool TrySceneTransport(Player player, string sceneName, string spawnTag) { MethodInfo method = ((object)player).GetType().GetMethod("Cmd_SceneTransport", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { Plugin.Log.LogWarning("[ExitTrigger] Cmd_SceneTransport not found."); return false; } try { ParameterInfo[] parameters = method.GetParameters(); Plugin.Log.LogInfo($"[ExitTrigger] Cmd_SceneTransport params: {parameters.Length} " + "(" + ((parameters.Length != 0) ? parameters[0].ParameterType.Name : "") + ")"); object[] parameters2; if (parameters.Length < 3) { parameters2 = ((parameters.Length != 2) ? new object[1] { sceneName } : new object[2] { sceneName, spawnTag }); } else { object obj = Enum.ToObject(parameters[2].ParameterType, 0); parameters2 = new object[3] { sceneName, spawnTag, obj }; } method.Invoke(player, parameters2); return true; } catch (Exception ex) { Plugin.Log.LogWarning("[ExitTrigger] Cmd_SceneTransport threw: " + ex.Message); return false; } } private static bool TrySetRecallFlag(Player player) { string[] array = new string[4] { "_requestedRecall", "_recallRequested", "_isRecalling", "_recall" }; foreach (string text in array) { FieldInfo field = ((object)player).GetType().GetField(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null) && !(field.FieldType != typeof(bool))) { field.SetValue(player, true); Plugin.Log.LogInfo("[ExitTrigger] Set " + text + "=true on Player."); return true; } } return false; } } public class HostCasinoVisibilityWatcher : MonoBehaviour { private const string CASINO_SCENE_NAME = "AtlyssCasino"; private const float POLL_INTERVAL_SEC = 0.5f; private const float SCENE_LOAD_GRACE_SEC = 1.5f; private static FieldInfo? _playerMapInstanceField; private static bool _reflectionResolveAttempted = false; private static bool _reflectionLoggedFailureOnce = false; private static HostCasinoVisibilityWatcher? _instance; private static bool? _isHeadlessCached = null; private static bool? _isServerModeCached = null; private float _nextCheckTime; private float _casinoLoadedAtTime = -1f; private bool _casinoIsHidden; private static Component? _cachedCasinoMapInstance; private static int _cachedCasinoSceneHandle = -1; private static Transform? _cachedCasinoExitPortal; private static int _cachedCasinoExitPortalSceneHandle = -1; private const float EXIT_PORTAL_RANGE = 3f; private const float EXIT_INPUT_COOLDOWN = 0.5f; private bool _exitPlayerNearby = false; private float _exitNextInputTime = 0f; private static readonly HashSet _disabledRenderers = new HashSet(); private static readonly HashSet _disabledLights = new HashSet(); private static readonly HashSet _disabledCameras = new HashSet(); private static readonly HashSet _disabledColliders = new HashSet(); public static bool IsHeadlessServer { get { if (_isHeadlessCached.HasValue) { return _isHeadlessCached.Value; } bool flag = false; try { string[] commandLineArgs = Environment.GetCommandLineArgs(); bool flag2 = commandLineArgs?.Any((string a) => string.Equals(a, "-nographics", StringComparison.OrdinalIgnoreCase)) ?? false; bool flag3 = commandLineArgs?.Any((string a) => string.Equals(a, "-server", StringComparison.OrdinalIgnoreCase)) ?? false; flag = Application.isBatchMode || flag2 || flag3; } catch { } _isHeadlessCached = flag; return flag; } } public static bool IsServerMode { get { if (_isServerModeCached.HasValue) { return _isServerModeCached.Value; } bool flag = false; try { flag = Environment.GetCommandLineArgs()?.Contains("-server") ?? false; } catch { } _isServerModeCached = flag; return flag; } } public static bool IsLocalPlayerInsideCasinoBounds() { try { if (IsHeadlessServer || IsServerMode) { return false; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } Component cachedCasinoMapInstance = GetCachedCasinoMapInstance(); if ((Object)(object)cachedCasinoMapInstance == (Object)null) { return false; } Component playerMapInstance = GetPlayerMapInstance(mainPlayer); if ((Object)(object)playerMapInstance == (Object)null) { return false; } return playerMapInstance == cachedCasinoMapInstance; } catch (Exception ex) { if (!_reflectionLoggedFailureOnce && Plugin.Log != null) { Plugin.Log.LogWarning("[CasinoVisibility] MapInstance comparison threw (" + ex.GetType().Name + ": " + ex.Message + "). Falling back to no-op behavior — casino visibility watcher will not hide casino objects when the player is in Sanctum. This is a soft failure; gameplay still works."); _reflectionLoggedFailureOnce = true; } return true; } } private static Component? GetCachedCasinoMapInstance() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { _cachedCasinoMapInstance = null; _cachedCasinoSceneHandle = -1; return null; } int handle = ((Scene)(ref sceneByName)).handle; if ((Object)(object)_cachedCasinoMapInstance != (Object)null && handle == _cachedCasinoSceneHandle) { return _cachedCasinoMapInstance; } _cachedCasinoMapInstance = null; _cachedCasinoSceneHandle = handle; try { GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((object)val2).GetType().Name == "MapInstance") { _cachedCasinoMapInstance = (Component?)(object)val2; if (Plugin.Log != null) { Plugin.Log.LogInfo("[CasinoVisibility] Casino MapInstance located: '" + ((Object)((Component)val2).gameObject).name + "'."); } return (Component?)(object)val2; } } } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogWarning("[CasinoVisibility] MapInstance scan threw: " + ex.Message); } return null; } if (Plugin.Log != null) { Plugin.Log.LogWarning("[CasinoVisibility] No MapInstance component found in the casino scene. Make sure the scene's root has a MapInstance attached (with Is Interior Instance and Use Force Sky Color enabled)."); } return null; } private static Transform? GetCachedCasinoExitPortalTransform() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { _cachedCasinoExitPortal = null; _cachedCasinoExitPortalSceneHandle = -1; return null; } int handle = ((Scene)(ref sceneByName)).handle; if ((Object)(object)_cachedCasinoExitPortal != (Object)null && handle == _cachedCasinoExitPortalSceneHandle) { return _cachedCasinoExitPortal; } _cachedCasinoExitPortal = null; _cachedCasinoExitPortalSceneHandle = handle; try { GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((object)val2).GetType().Name == "Portal") { _cachedCasinoExitPortal = ((Component)val2).transform; if (Plugin.Log != null) { Plugin.Log.LogInfo("[CasinoVisibility] Casino exit Portal located: '" + ((Object)((Component)val2).gameObject).name + "' at " + $"{((Component)val2).transform.position}."); } return _cachedCasinoExitPortal; } } } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogWarning("[CasinoVisibility] Exit-portal scan threw: " + ex.Message); } return null; } return null; } private static Component? GetPlayerMapInstance(Player player) { if (!_reflectionResolveAttempted) { _reflectionResolveAttempted = true; Type typeFromHandle = typeof(Player); _playerMapInstanceField = typeFromHandle.GetField("_playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeFromHandle.GetField("playerMapInstance", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_playerMapInstanceField == null && Plugin.Log != null) { Plugin.Log.LogWarning("[CasinoVisibility] Player class has no field named _playerMapInstance or playerMapInstance. MapInstance-based detection will fall back to no-op behavior. Verify ATLYSS API hasn't changed."); } } if (_playerMapInstanceField == null) { return null; } object? value = _playerMapInstanceField.GetValue(player); return (Component?)((value is Component) ? value : null); } public static void Init() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)_instance != (Object)null) { return; } try { GameObject val = new GameObject("AtlyssCasino_VisibilityWatcher"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; _instance = val.AddComponent(); if (Plugin.Log != null) { Plugin.Log.LogInfo("[CasinoVisibility] Initialized " + $"(headless={IsHeadlessServer}, serverMode={IsServerMode})."); } } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogError("[CasinoVisibility] Init failed: " + ex.Message); } } } private void Update() { HandleExitPortalInput(); if (Time.unscaledTime < _nextCheckTime) { return; } _nextCheckTime = Time.unscaledTime + 0.5f; try { EvaluateAndApply(); } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoVisibility] Update tick threw: " + ex.Message); } } private void HandleExitPortalInput() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) try { if (IsHeadlessServer || IsServerMode) { return; } if (!IsLocalPlayerInsideCasinoBounds()) { if (_exitPlayerNearby) { _exitPlayerNearby = false; } if (CasinoExitTrigger.IsTransporting) { CasinoExitTrigger.IsTransporting = false; } } else if (BJNetcode.AmHost()) { if (_exitPlayerNearby) { _exitPlayerNearby = false; } } else { if (CasinoExitTrigger.IsTransporting) { return; } Transform cachedCasinoExitPortalTransform = GetCachedCasinoExitPortalTransform(); if ((Object)(object)cachedCasinoExitPortalTransform == (Object)null) { return; } Player mainPlayer = Player._mainPlayer; if (!((Object)(object)mainPlayer == (Object)null)) { float num = Vector3.Distance(((Component)mainPlayer).transform.position, cachedCasinoExitPortalTransform.position); bool flag = num < 3f; if (flag && !_exitPlayerNearby) { _exitPlayerNearby = true; Plugin.ShowHUDInfo("Press " + CasinoInput.InteractPrompt + " to return to Sanctum."); } else if (!flag && _exitPlayerNearby) { _exitPlayerNearby = false; } if (_exitPlayerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _exitNextInputTime)) { _exitNextInputTime = Time.time + 0.5f; CasinoExitTrigger.IsTransporting = true; ((MonoBehaviour)this).StartCoroutine(CasinoExitTrigger.ForceReturnToSanctum(mainPlayer, "[ExitPortal/HCVW]")); } } } } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoVisibility] Exit-portal input handler threw: " + ex.Message); } } private void EvaluateAndApply() { //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) //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (IsHeadlessServer || IsServerMode) { return; } Scene sceneByName = SceneManager.GetSceneByName("AtlyssCasino"); if (!((Scene)(ref sceneByName)).IsValid() || !((Scene)(ref sceneByName)).isLoaded) { _casinoIsHidden = false; _casinoLoadedAtTime = -1f; return; } if (_casinoLoadedAtTime < 0f) { _casinoLoadedAtTime = Time.unscaledTime; } bool flag = IsLocalPlayerInsideCasinoBounds(); float num = Time.unscaledTime - _casinoLoadedAtTime; bool flag2 = num >= 1.5f; if (!flag && flag2 && !_casinoIsHidden) { _casinoIsHidden = true; Plugin.Log.LogInfo("[CasinoVisibility] Local player is outside casino bounds but the casino scene is still loaded — hiding casino visuals + colliders (MonoBehaviour Updates remain active so host-authoritative game state continues running)."); } else if (flag && _casinoIsHidden) { _casinoIsHidden = false; Plugin.Log.LogInfo("[CasinoVisibility] Local player back in casino — restoring casino visuals + colliders."); } if (flag2) { if (_casinoIsHidden) { HideCasinoComponents(sceneByName); } else { RestoreCasinoComponents(); } } } private static void HideCasinoComponents(Scene casinoScene) { GameObject[] rootGameObjects; try { rootGameObjects = ((Scene)(ref casinoScene)).GetRootGameObjects(); } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoVisibility] GetRootGameObjects failed: " + ex.Message); return; } Transform val = null; Component cachedCasinoMapInstance = _cachedCasinoMapInstance; if ((Object)(object)cachedCasinoMapInstance != (Object)null) { try { val = cachedCasinoMapInstance.transform; } catch { val = null; } } GameObject[] array = rootGameObjects; foreach (GameObject val2 in array) { if ((Object)(object)val2 == (Object)null || (Object)(object)val2.GetComponent() != (Object)null || (Object)(object)val2.GetComponent() != (Object)null || (Object)(object)val2.GetComponent() != (Object)null) { continue; } try { Renderer[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren) { if (!((Object)(object)val3 == (Object)null) && val3.enabled && (!((Object)(object)val != (Object)null) || !IsUnderMapInstance(((Component)val3).transform, val)) && !HasPlayerAncestor(((Component)val3).transform) && !(val3 is SkinnedMeshRenderer)) { val3.enabled = false; _disabledRenderers.Add(val3); } } Light[] componentsInChildren2 = val2.GetComponentsInChildren(true); foreach (Light val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null) && ((Behaviour)val4).enabled && (!((Object)(object)val != (Object)null) || !IsUnderMapInstance(((Component)val4).transform, val)) && !HasPlayerAncestor(((Component)val4).transform)) { ((Behaviour)val4).enabled = false; _disabledLights.Add(val4); } } Camera[] componentsInChildren3 = val2.GetComponentsInChildren(true); foreach (Camera val5 in componentsInChildren3) { if (!((Object)(object)val5 == (Object)null) && ((Behaviour)val5).enabled && (!((Object)(object)val != (Object)null) || !IsUnderMapInstance(((Component)val5).transform, val)) && !HasPlayerAncestor(((Component)val5).transform)) { ((Behaviour)val5).enabled = false; _disabledCameras.Add(val5); } } Collider[] componentsInChildren4 = val2.GetComponentsInChildren(true); foreach (Collider val6 in componentsInChildren4) { if (!((Object)(object)val6 == (Object)null) && val6.enabled && (!((Object)(object)val != (Object)null) || !IsUnderMapInstance(((Component)val6).transform, val)) && !HasPlayerAncestor(((Component)val6).transform)) { val6.enabled = false; _disabledColliders.Add(val6); } } } catch (Exception ex2) { Plugin.Log.LogWarning("[CasinoVisibility] Failed to disable components on root '" + ((Object)val2).name + "': " + ex2.Message); } } } private static bool IsUnderMapInstance(Transform t, Transform mapInstanceTransform) { Transform val = t; while ((Object)(object)val != (Object)null) { if (val == mapInstanceTransform) { return true; } val = val.parent; } return false; } private static bool HasPlayerAncestor(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { if ((Object)(object)((Component)val).GetComponent() != (Object)null) { return true; } val = val.parent; } return false; } private static void RestoreCasinoComponents() { try { foreach (Renderer disabledRenderer in _disabledRenderers) { if ((Object)(object)disabledRenderer != (Object)null) { disabledRenderer.enabled = true; } } _disabledRenderers.Clear(); foreach (Light disabledLight in _disabledLights) { if ((Object)(object)disabledLight != (Object)null) { ((Behaviour)disabledLight).enabled = true; } } _disabledLights.Clear(); foreach (Camera disabledCamera in _disabledCameras) { if ((Object)(object)disabledCamera != (Object)null) { ((Behaviour)disabledCamera).enabled = true; } } _disabledCameras.Clear(); foreach (Collider disabledCollider in _disabledColliders) { if ((Object)(object)disabledCollider != (Object)null) { disabledCollider.enabled = true; } } _disabledColliders.Clear(); } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoVisibility] Restore threw: " + ex.Message); } } } internal static class SelfPortraitVisibility { private sealed class Driver : MonoBehaviour { private float _nextRefresh; private void Update() { if (!(Time.time < _nextRefresh)) { _nextRefresh = Time.time + 0.5f; Refresh(); } } } private sealed class PortraitTarget { internal GameObject GameObject = null; internal string Name = string.Empty; internal int SceneHandle; internal string SceneName = string.Empty; internal Component? MapInstance; } [CompilerGenerated] private sealed class d__14 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Scene scene; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__14(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 float REFRESH_INTERVAL_SEC = 0.5f; private static readonly HashSet TargetNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "selfportrait", "selfportait", "selfportiat" }; private static readonly List _targets = new List(); private static bool _initialized; private static Driver? _driver; private static FieldInfo? _playerMapInstanceField; private static PropertyInfo? _playerMapInstanceProperty; private static FieldInfo? _playerMapNameField; private static PropertyInfo? _playerMapNameProperty; private static bool _reflectionResolved; private static bool _loggedReflectionFailure; 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_SelfPortraitVisibility"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; _driver = val.AddComponent(); for (int i = 0; i < SceneManager.sceneCount; i++) { ScanScene(SceneManager.GetSceneAt(i)); } Plugin.Log.LogInfo("[SelfPortrait] Visibility controller initialized."); } } internal static void Refresh() { for (int num = _targets.Count - 1; num >= 0; num--) { PortraitTarget portraitTarget = _targets[num]; if ((Object)(object)portraitTarget.GameObject == (Object)null) { _targets.RemoveAt(num); } else { if ((Object)(object)portraitTarget.MapInstance == (Object)null) { portraitTarget.MapInstance = GetSceneMapInstance(portraitTarget.SceneHandle); } bool flag = IsOwnerInTargetScene(portraitTarget); if (portraitTarget.GameObject.activeSelf != flag) { portraitTarget.GameObject.SetActive(flag); Plugin.Log.LogInfo("[SelfPortrait] " + (flag ? "Showing" : "Hiding") + " '" + portraitTarget.Name + "' in scene '" + portraitTarget.SceneName + "'."); } } } } 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(d__14))] 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 d__14(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) _targets.RemoveAll((PortraitTarget t) => t.SceneHandle == ((Scene)(ref scene)).handle); } private static void ScanScene(Scene scene) { if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return; } int num = 0; GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } Transform[] componentsInChildren = val.GetComponentsInChildren(true); Transform[] array2 = componentsInChildren; foreach (Transform val2 in array2) { if (!((Object)(object)val2 == (Object)null) && IsTargetName(((Object)val2).name) && !IsAlreadyTracked(((Component)val2).gameObject)) { _targets.Add(new PortraitTarget { GameObject = ((Component)val2).gameObject, Name = ((Object)val2).name, SceneHandle = ((Scene)(ref scene)).handle, SceneName = (((Scene)(ref scene)).name ?? string.Empty), MapInstance = GetSceneMapInstance(((Scene)(ref scene)).handle) }); num++; } } } if (num > 0) { Plugin.Log.LogInfo($"[SelfPortrait] Found {num} owner portrait object(s) in scene '{((Scene)(ref scene)).name}'."); Refresh(); } } private static bool IsTargetName(string objectName) { string item = NormalizeName(objectName); return TargetNames.Contains(item); } private static string NormalizeName(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static bool IsAlreadyTracked(GameObject obj) { for (int i = 0; i < _targets.Count; i++) { if ((Object)(object)_targets[i].GameObject == (Object)(object)obj) { return true; } } return false; } private static bool IsOwnerInTargetScene(PortraitTarget target) { Player[] array = Object.FindObjectsOfType(); Player[] array2 = array; foreach (Player val in array2) { if (!((Object)(object)val == (Object)null) && Plugin.IsCasinoOwner(GetSteam64(val)) && IsPlayerInTargetScene(val, target)) { return true; } } return false; } private static bool IsPlayerInTargetScene(Player player, PortraitTarget target) { //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)target.MapInstance != (Object)null) { return playerMapInstance == target.MapInstance; } string playerMapName = GetPlayerMapName(player); if (!string.IsNullOrWhiteSpace(playerMapName) && !string.IsNullOrWhiteSpace(target.SceneName)) { return string.Equals(playerMapName, target.SceneName, StringComparison.OrdinalIgnoreCase); } Scene scene = ((Component)player).gameObject.scene; return ((Scene)(ref scene)).handle == target.SceneHandle; } private 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; } private static Component? GetSceneMapInstance(int sceneHandle) { //IL_0003: 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) //IL_0014: 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_0025: 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) Scene scene = default(Scene); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).handle == sceneHandle) { scene = sceneAt; break; } } if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return null; } return GetSceneMapInstance(scene); } private static Component? GetSceneMapInstance(Scene scene) { try { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); MonoBehaviour[] array2 = componentsInChildren; foreach (MonoBehaviour val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((object)val2).GetType().Name == "MapInstance") { return (Component?)(object)val2; } } } } catch (Exception ex) { Plugin.Log.LogWarning("[SelfPortrait] MapInstance scan threw in scene '" + ((Scene)(ref scene)).name + "': " + ex.Message); } 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.LogWarning("[SelfPortrait] Player MapInstance reflection failed. Portrait visibility will fall back to map names / GameObject scenes."); _loggedReflectionFailure = true; } } } } public class SlotBetConfirm : MonoBehaviour { private const float FALLBACK_RADIUS = 2.5f; private SlotMachine? _machine; private BoxCollider? _collider; private bool _playerNearby = false; private static bool _warnedAboutMissingCollider; public void Setup(SlotMachine machine) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) _machine = machine; _collider = ((Component)this).GetComponent(); if ((Object)(object)_collider == (Object)null) { if (!_warnedAboutMissingCollider) { _warnedAboutMissingCollider = true; Plugin.Log.LogWarning("[Casino] One or more SlotBet_Confirm objects have no " + $"BoxCollider — falling back to {2.5f}u world " + "radius. Add a BoxCollider with 'Is Trigger' for scale-aware detection. (Logged once globally.)"); } } else { CasinoLog log = Plugin.Log; string name = ((Object)this).name; Bounds bounds = ((Collider)_collider).bounds; log.LogDebug("[Casino] '" + name + "': slot trigger collider bounds " + $"= {((Bounds)(ref bounds)).size}."); } } private void Update() { if (!Plugin.IsLocalPlayerInCasino()) { if (_playerNearby) { _playerNearby = false; } return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; if (Plugin.HasSetBet) { Plugin.ShowHUDInfo($"Bet: {Plugin.CurrentBet} Crowns. " + "Press " + CasinoInput.InteractPrompt + " to spin. Change bet with /slotbet ."); } else { Plugin.ShowHUDError("No bet set. Use /slotbet to set your bet."); } Plugin.Log.LogDebug("[Casino] Player near slot trigger."); } else if (!flag && _playerNearby) { _playerNearby = false; } if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed()) { if (!Plugin.HasSetBet) { Plugin.ShowHUDError("Set a bet first with /slotbet !"); } else if ((Object)(object)_machine != (Object)null && BJNetcode.SlotMachineLocks.IsLocked(((Object)_machine).name)) { float num = BJNetcode.SlotMachineLocks.SecondsRemaining(((Object)_machine).name); Plugin.ShowHUDError($"Machine in use — wait {Mathf.CeilToInt(num)}s for it to finish."); } else { _machine?.TryPlay(); } } } 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) < 2.5f; } } public class SlotMachine : MonoBehaviour { [CompilerGenerated] private sealed class d__57 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SlotMachine <>4__this; private float 5__1; private float 5__2; private Quaternion 5__3; private Quaternion 5__4; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__57(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; if ((Object)(object)<>4__this._lever == (Object)null) { return false; } 5__1 = 0f; 5__2 = 0.3f; 5__3 = <>4__this._lever.localRotation; 5__4 = 5__3 * Quaternion.Euler(-30f, 0f, 0f); goto IL_00fe; case 1: <>1__state = -1; goto IL_00fe; case 2: <>1__state = -1; 5__1 = 0f; break; case 3: { <>1__state = -1; break; } IL_00fe: if (5__1 < 5__2) { 5__1 += Time.deltaTime; <>4__this._lever.localRotation = Quaternion.Lerp(5__3, 5__4, 5__1 / 5__2); <>2__current = null; <>1__state = 1; return true; } <>2__current = (object)new WaitForSeconds(0.3f); <>1__state = 2; return true; } if (5__1 < 5__2) { 5__1 += Time.deltaTime; <>4__this._lever.localRotation = Quaternion.Lerp(5__4, 5__3, 5__1 / 5__2); <>2__current = null; <>1__state = 3; return true; } <>4__this._lever.localRotation = 5__3; 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 d__46 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Material flashMat; public int times; public float interval; public int[] results; public SlotMachine <>4__this; private int 5__1; private int 5__2; private int 5__3; private int 5__4; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__46(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (<>4__this._symbolMaterials == null) { return false; } 5__1 = 0; break; case 1: <>1__state = -1; 5__3 = 0; while (5__3 < 3) { <>4__this.SetReel(5__3, <>4__this._symbolMaterials[results[5__3]]); 5__3++; } <>2__current = (object)new WaitForSeconds(interval); <>1__state = 2; return true; case 2: <>1__state = -1; 5__1++; break; } if (5__1 < times) { 5__2 = 0; while (5__2 < 3) { <>4__this.SetReel(5__2, flashMat); 5__2++; } <>2__current = (object)new WaitForSeconds(interval); <>1__state = 1; return true; } 5__4 = 0; while (5__4 < 3) { <>4__this.SetReel(5__4, flashMat); 5__4++; } 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 d__41 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int wager; public PlayerInventory inventory; public int r1; public int r2; public int r3; public SlotMachine <>4__this; private float 5__1; private string 5__2; private bool 5__3; private bool 5__4; private string 5__5; private float 5__6; private int 5__7; private int 5__8; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__41(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: { <>1__state = -1; (float, string, bool) tuple = <>4__this.CalculatePayout(r1, r2, r3); 5__1 = tuple.Item1; 5__2 = tuple.Item2; 5__3 = tuple.Item3; 5__4 = r1 == 4 && r2 == 4 && r3 == 4; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.RunSpinVisuals(r1, r2, r3, 5__4, 5__3, 5__1 > 0f)); <>1__state = 1; return true; } case 1: <>1__state = -1; 5__5 = "[ " + <>4__this._symbols[r1] + " | " + <>4__this._symbols[r2] + " | " + <>4__this._symbols[r3] + " ]"; if (5__1 > 0f) { 5__6 = Plugin.SlotsPayoutMultiplier; 5__7 = ((wager > 0) ? Mathf.FloorToInt((float)wager * 5__1 * 5__6) : 0); if (5__7 > 0) { PlayerInventory obj = inventory; obj.Network_heldCurrency += 5__7; } Plugin.ShowHUDInfo($"{5__5}\n{5__2} Won {5__7} Crowns!"); Plugin.Log.LogInfo($"[Slots] {5__2} — bet {wager}, payout {5__7} " + $"({5__1:F2}x base, config {5__6:F2}x)."); } else { Plugin.ShowHUDError($"{5__5}\nNo match. Lost {wager} Crowns."); Plugin.Log.LogInfo($"[Slots] Loss — bet {wager}."); } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; case 2: <>1__state = -1; 5__8 = 0; while (5__8 < 3) { <>4__this.SetReel(5__8, <>4__this.MatIdleBlank); 5__8++; } <>4__this.SetTopScreen(<>4__this.MatIdle); <>4__this.InUse = false; _localPlayerSlotBusyUntil = 0f; <>4__this.ApplyPerformanceSettings(); 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 d__42 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int r1; public int r2; public int r3; public SlotMachine <>4__this; private float 5__1; private string 5__2; private bool 5__3; private bool 5__4; private int 5__5; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__42(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: { <>1__state = -1; (float, string, bool) tuple = <>4__this.CalculatePayout(r1, r2, r3); 5__1 = tuple.Item1; 5__2 = tuple.Item2; 5__3 = tuple.Item3; 5__4 = r1 == 4 && r2 == 4 && r3 == 4; <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.RunSpinVisuals(r1, r2, r3, 5__4, 5__3, 5__1 > 0f)); <>1__state = 1; return true; } case 1: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; case 2: <>1__state = -1; 5__5 = 0; while (5__5 < 3) { <>4__this.SetReel(5__5, <>4__this.MatIdleBlank); 5__5++; } <>4__this.SetTopScreen(<>4__this.MatIdle); <>4__this.InUse = false; <>4__this.ApplyPerformanceSettings(); 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 d__44 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int r1; public int r2; public int r3; public bool isJackpot; public bool bigWin; public bool isWin; public SlotMachine <>4__this; private int[] 5__1; private float[] 5__2; private int 5__3; private Material 5__4; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__44(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__2 = null; 5__4 = null; <>1__state = -2; } private bool MoveNext() { //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if ((Object)(object)<>4__this._lever != (Object)null) { ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.AnimateLever()); } <>4__this.SetTopScreen(<>4__this.MatIdleBlank); 5__1 = new int[3] { r1, r2, r3 }; 5__2 = new float[3] { 1.5f, 2f, 2.5f }; 5__3 = 0; while (5__3 < 3) { ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.SpinReel(5__3, 5__2[5__3], 5__1[5__3])); 5__3++; } <>2__current = (object)new WaitForSeconds(5__2[2] + 0.3f); <>1__state = 1; return true; case 1: <>1__state = -1; if (isWin) { 5__4 = (isJackpot ? <>4__this.MatJackpot : <>4__this.MatWin); <>4__this.SetTopScreen(5__4); if (isJackpot) { <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.FlashAllReels(<>4__this.MatJackpot, 6, 0.15f, 5__1)); <>1__state = 2; return true; } if (bigWin) { <>2__current = ((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.FlashAllReels(<>4__this.MatWin, 4, 0.2f, 5__1)); <>1__state = 3; return true; } <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 4; return true; } <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 5; return true; case 2: <>1__state = -1; goto IL_0263; case 3: <>1__state = -1; goto IL_0263; case 4: <>1__state = -1; goto IL_0263; case 5: { <>1__state = -1; break; } IL_0263: 5__4 = null; break; } 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 d__45 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int reelIndex; public float duration; public int finalResult; public SlotMachine <>4__this; private float 5__1; private float 5__2; private float 5__3; private float 5__4; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__45(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (<>4__this._symbolMaterials == null) { return false; } 5__1 = 0f; 5__2 = 0f; 5__3 = 0.08f; break; case 1: <>1__state = -1; break; } if (5__1 < duration) { if (5__1 >= 5__2) { <>4__this.SetReel(reelIndex, <>4__this._symbolMaterials[Random.Range(0, <>4__this._symbolMaterials.Length)]); 5__4 = 5__1 / duration; 5__3 = Mathf.Lerp(0.05f, 0.35f, 5__4); 5__2 = 5__1 + 5__3; } 5__1 += Time.deltaTime; <>2__current = null; <>1__state = 1; return true; } <>4__this.SetReel(reelIndex, <>4__this._symbolMaterials[finalResult]); 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(); } } public bool InUse = false; public float NextInputTime = 0f; private Transform? _lever; private GameObject? _backdrop; private MeshRenderer? _backdropRenderer; private GameObject[] _reels = (GameObject[])(object)new GameObject[3]; private MeshRenderer?[] _reelRenderers = (MeshRenderer?[])(object)new MeshRenderer[3]; private GameObject? _topScreen; private MeshRenderer? _topScreenRenderer; private readonly List _nativeScreenRenderers = new List(); public Material? MatIdle; public Material? MatIdleBlank; public Material? MatWin; public Material? MatJackpot; public Material? MatCherry; public Material? MatLemon; public Material? MatOrange; public Material? MatStar; public Material? MatDiamond; public Material? MatSeven; private Material?[]? _symbolMaterials; private const int CherryIdx = 0; private const int LemonIdx = 1; private const int OrangeIdx = 2; private const int StarIdx = 3; private const int DiamondIdx = 4; private const int SevenIdx = 5; private const float LOCAL_PLAYER_SLOT_LOCK_DURATION_SEC = 6f; private static float _localPlayerSlotBusyUntil; private readonly string[] _symbols = new string[6] { "Cherry", "Lemon", "Orange", "Star", "Diamond", "Seven" }; public void Init() { _lever = ((Component)this).transform.Find("Lever"); Transform val = ((Component)this).transform.Find("ScreenAnchor"); Transform val2 = ((Component)this).transform.Find("TopScreenAnchor"); CacheNativeScreenRenderers(val, val2); if ((Object)(object)val == (Object)null) { Plugin.Log.LogError("[Slots] ScreenAnchor child not found!"); } else { BuildBackdrop(val); BuildThreeReels(val); } if ((Object)(object)val2 != (Object)null) { BuildTopScreen(val2); } _symbolMaterials = (Material?[]?)(object)new Material[6] { MatCherry, MatLemon, MatOrange, MatStar, MatDiamond, MatSeven }; SetBackdrop(MatIdleBlank); for (int i = 0; i < 3; i++) { SetReel(i, MatIdleBlank); } SetTopScreen(MatIdle); ApplyPerformanceSettings(); } private void LateUpdate() { if (Plugin.ReduceIdleSlotVisuals && !InUse) { SetScreenRenderersEnabled(enabled: false); } } private void BuildBackdrop(Transform anchor) { //IL_0050: 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_0072: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.CreatePrimitive((PrimitiveType)5); ((Object)val).name = "SlotScreenBackdrop"; Collider component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } val.transform.SetParent(anchor, false); val.transform.localPosition = new Vector3(0f, 0f, 0.001f); val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; _backdrop = val; _backdropRenderer = val.GetComponent(); } private void BuildThreeReels(Transform anchor) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) float num = 1f / 3f; float[] array = new float[3] { -1f / 3f, 0f, 1f / 3f }; for (int i = 0; i < 3; i++) { GameObject val = GameObject.CreatePrimitive((PrimitiveType)5); ((Object)val).name = $"SlotReel_{i}"; Collider component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } val.transform.SetParent(anchor, false); val.transform.localPosition = new Vector3(array[i], 0f, -0.001f); val.transform.localRotation = Quaternion.identity; val.transform.localScale = new Vector3(num, 1f, 1f); _reels[i] = val; _reelRenderers[i] = val.GetComponent(); } } private void BuildTopScreen(Transform anchor) { //IL_0041: 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_0063: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.CreatePrimitive((PrimitiveType)5); ((Object)val).name = "SlotTopScreen"; Collider component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } val.transform.SetParent(anchor, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; _topScreen = val; _topScreenRenderer = val.GetComponent(); } public void TryPlay() { if (Time.time < NextInputTime) { return; } NextInputTime = Time.time + 0.5f; if (InUse) { Plugin.ShowHUDError("Machine is in use!"); return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } if (Time.time < _localPlayerSlotBusyUntil) { float num = _localPlayerSlotBusyUntil - Time.time; Plugin.ShowHUDError($"Finish your current slot spin first ({Mathf.CeilToInt(num)}s)."); return; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)(object)component == (Object)null) { return; } int currentBet = Plugin.CurrentBet; if (component._heldCurrency < currentBet) { Plugin.ShowHUDError($"Not enough Crowns! Need {currentBet}, " + $"you have {component._heldCurrency}."); return; } int num2 = Random.Range(0, _symbols.Length); int num3 = Random.Range(0, _symbols.Length); int num4 = Random.Range(0, _symbols.Length); if (Plugin.ShouldRigOwnerLuck(BJNetcode.GetLocalSteam64())) { num2 = 4; num3 = 4; num4 = 4; } else if (num2 == 4 && num3 == 4 && num4 == 4 && Random.value < 0.5f) { int num5 = Random.Range(0, 3); int num6; do { num6 = Random.Range(0, _symbols.Length); } while (num6 == 4); switch (num5) { case 0: num2 = num6; break; case 1: num3 = num6; break; case 2: num4 = num6; break; } } InUse = true; ApplyPerformanceSettings(); _localPlayerSlotBusyUntil = Time.time + 6f; component.Network_heldCurrency -= currentBet; Plugin.Log.LogInfo($"[Slots] Playing '{((Object)this).name}' with bet {currentBet} -> [{num2}, {num3}, {num4}]."); BJNetcode.BroadcastSlotSpinResult(((Object)this).name, num2, num3, num4); ((MonoBehaviour)this).StartCoroutine(PlaySlotsLocal(currentBet, component, num2, num3, num4)); } public void ApplyRemoteSpin(int r1, int r2, int r3) { if (InUse) { Plugin.Log.LogWarning("[Slots] ApplyRemoteSpin on '" + ((Object)this).name + "' but machine is already in use — dropping remote spin to avoid visual clash."); return; } if (r1 < 0 || r1 >= _symbols.Length || r2 < 0 || r2 >= _symbols.Length || r3 < 0 || r3 >= _symbols.Length) { Plugin.Log.LogError("[Slots] ApplyRemoteSpin on '" + ((Object)this).name + "': invalid symbol indices " + $"[{r1}, {r2}, {r3}]. Spin not played."); return; } InUse = true; ApplyPerformanceSettings(); Plugin.Log.LogInfo($"[Slots] Mirroring remote spin on '{((Object)this).name}' -> [{r1}, {r2}, {r3}]."); ((MonoBehaviour)this).StartCoroutine(PlaySlotsRemote(r1, r2, r3)); } private static bool IsFruit(int idx) { return idx == 0 || idx == 1 || idx == 2; } private static bool IsStarOrSeven(int idx) { return idx == 3 || idx == 5; } private (float multiplier, string tier, bool bigWin) CalculatePayout(int r1, int r2, int r3) { if (r1 == r2 && r2 == r3) { if (r1 == 4) { return (500f, "JACKPOT!", true); } if (IsStarOrSeven(r1)) { return (4f, "Triple Match!", false); } return (2f, "Triple Fruit!", false); } int num = 0; int[] array = new int[3] { r1, r2, r3 }; foreach (int num2 in array) { if (num2 == 4) { num++; } } if (num == 2) { return (4f, "Two Diamonds!", false); } if (HasExactlyTwoOfSame(r1, r2, r3, out var matchedIdx)) { if (IsStarOrSeven(matchedIdx)) { return (1f, "Near Miss!", false); } if (IsFruit(matchedIdx)) { return (0.5f, "Fruit Pair", false); } } return (0f, "No match", false); } private static bool HasExactlyTwoOfSame(int r1, int r2, int r3, out int matchedIdx) { if (r1 == r2 && r2 == r3) { matchedIdx = -1; return false; } if (r1 == r2) { matchedIdx = r1; return true; } if (r2 == r3) { matchedIdx = r2; return true; } if (r1 == r3) { matchedIdx = r1; return true; } matchedIdx = -1; return false; } [IteratorStateMachine(typeof(d__41))] private IEnumerator PlaySlotsLocal(int wager, PlayerInventory inventory, int r1, int r2, int r3) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__41(0) { <>4__this = this, wager = wager, inventory = inventory, r1 = r1, r2 = r2, r3 = r3 }; } [IteratorStateMachine(typeof(d__42))] private IEnumerator PlaySlotsRemote(int r1, int r2, int r3) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__42(0) { <>4__this = this, r1 = r1, r2 = r2, r3 = r3 }; } internal void ApplyPerformanceSettings() { bool screenRenderersEnabled = !Plugin.ReduceIdleSlotVisuals || InUse; SetScreenRenderersEnabled(screenRenderersEnabled); } [IteratorStateMachine(typeof(d__44))] private IEnumerator RunSpinVisuals(int r1, int r2, int r3, bool isJackpot, bool bigWin, bool isWin) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__44(0) { <>4__this = this, r1 = r1, r2 = r2, r3 = r3, isJackpot = isJackpot, bigWin = bigWin, isWin = isWin }; } [IteratorStateMachine(typeof(d__45))] private IEnumerator SpinReel(int reelIndex, float duration, int finalResult) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__45(0) { <>4__this = this, reelIndex = reelIndex, duration = duration, finalResult = finalResult }; } [IteratorStateMachine(typeof(d__46))] private IEnumerator FlashAllReels(Material? flashMat, int times, float interval, int[] results) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__46(0) { <>4__this = this, flashMat = flashMat, times = times, interval = interval, results = results }; } private void SetReel(int index, Material? mat) { if (index >= 0 && index < _reelRenderers.Length && !((Object)(object)_reelRenderers[index] == (Object)null) && !((Object)(object)mat == (Object)null)) { ((Renderer)_reelRenderers[index]).material = mat; } } private void SetBackdrop(Material? mat) { if (!((Object)(object)_backdropRenderer == (Object)null) && !((Object)(object)mat == (Object)null)) { ((Renderer)_backdropRenderer).material = mat; } } private void SetTopScreen(Material? mat) { if (!((Object)(object)_topScreenRenderer == (Object)null) && !((Object)(object)mat == (Object)null)) { ((Renderer)_topScreenRenderer).material = mat; } } private void SetScreenRenderersEnabled(bool enabled) { if ((Object)(object)_backdropRenderer != (Object)null) { ((Renderer)_backdropRenderer).enabled = enabled; } for (int i = 0; i < _reelRenderers.Length; i++) { if ((Object)(object)_reelRenderers[i] != (Object)null) { ((Renderer)_reelRenderers[i]).enabled = enabled; } } if ((Object)(object)_topScreenRenderer != (Object)null) { ((Renderer)_topScreenRenderer).enabled = enabled; } bool enabled2 = !Plugin.ReduceIdleSlotVisuals; for (int j = 0; j < _nativeScreenRenderers.Count; j++) { Renderer val = _nativeScreenRenderers[j]; if ((Object)(object)val != (Object)null) { val.enabled = enabled2; } } } private void CacheNativeScreenRenderers(Transform? screenAnchor, Transform? topScreenAnchor) { _nativeScreenRenderers.Clear(); Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && IsLikelyNativeScreenRenderer(val, screenAnchor, topScreenAnchor)) { _nativeScreenRenderers.Add(val); } } if (_nativeScreenRenderers.Count > 0) { Plugin.Log.LogDebug("[Slots] '" + ((Object)this).name + "' cached " + $"{_nativeScreenRenderers.Count} native screen renderer(s)."); } } private static bool IsLikelyNativeScreenRenderer(Renderer renderer, Transform? screenAnchor, Transform? topScreenAnchor) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: 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) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)renderer).transform; string value = NormalizeScreenName(((Object)transform).name + " " + ((Object)((Component)renderer).gameObject).name + " " + ((Object)renderer).name); if (ContainsAnyScreenName(value, "screen", "display", "monitor")) { return true; } if (!IsNearAnchor(renderer, screenAnchor, 1.15f) && !IsNearAnchor(renderer, topScreenAnchor, 0.85f)) { return false; } if (ContainsAnyScreenName(value, "reel", "crt", "idle", "eyes")) { return true; } if (UsesScreenLikeMaterial(renderer)) { return true; } Bounds bounds = renderer.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(size.x, Mathf.Max(size.y, size.z)); float num2 = Mathf.Min(size.x, Mathf.Min(size.y, size.z)); return num <= 2.5f && num2 <= 0.25f; } private static bool IsNearAnchor(Renderer renderer, Transform? anchor, float maxDistance) { //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_0019: 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) if ((Object)(object)anchor == (Object)null) { return false; } Bounds bounds = renderer.bounds; return Vector3.Distance(((Bounds)(ref bounds)).center, anchor.position) <= maxDistance; } private static bool UsesScreenLikeMaterial(Renderer renderer) { Material[] sharedMaterials = renderer.sharedMaterials; foreach (Material val in sharedMaterials) { if (!((Object)(object)val == (Object)null)) { string value = NormalizeScreenName(((Object)val).name); if (ContainsAnyScreenName(value, "screen", "display", "monitor", "crt", "idle", "emission", "emissive", "glow")) { return true; } } } return false; } private static string NormalizeScreenName(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Trim().ToLowerInvariant().Replace(" ", string.Empty) .Replace("_", string.Empty) .Replace("-", string.Empty) .Replace("(", string.Empty) .Replace(")", string.Empty); } private static bool ContainsAnyScreenName(string value, params string[] needles) { for (int i = 0; i < needles.Length; i++) { if (value.Contains(needles[i])) { return true; } } return false; } [IteratorStateMachine(typeof(d__57))] private IEnumerator AnimateLever() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__57(0) { <>4__this = this }; } } } namespace AtlyssCasino.Roulette { public enum BetType { Number, Red, Black, Even, Odd, Low, High, Dozen, Column } public static class RouletteLogic { public static readonly int[] WheelSequence = new int[37] { 0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26 }; public const float DegreesPerSlot = 9.72973f; private static readonly HashSet RedNumbers = new HashSet { 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36 }; private static readonly Random _rng = new Random(); public static bool IsRed(int number) { return number > 0 && RedNumbers.Contains(number); } public static bool IsBlack(int number) { return number > 0 && !RedNumbers.Contains(number); } public static bool IsEven(int number) { return number > 0 && number % 2 == 0; } public static bool IsOdd(int number) { return number > 0 && number % 2 != 0; } public static bool IsLow(int number) { return number >= 1 && number <= 18; } public static bool IsHigh(int number) { return number >= 19 && number <= 36; } public static int DozenOf(int number) { if (number <= 0) { return 0; } if (number <= 12) { return 1; } if (number <= 24) { return 2; } return 3; } public static int ColumnOf(int number) { if (number <= 0) { return 0; } return (number - 1) % 3 + 1; } public static int WheelSlotIndexOf(int number) { for (int i = 0; i < WheelSequence.Length; i++) { if (WheelSequence[i] == number) { return i; } } return 0; } public static int RollWinningNumber() { return _rng.Next(0, 37); } public static int GetPayoutFactor(BetType betType, int betTarget, int winningNumber) { return betType switch { BetType.Number => (betTarget == winningNumber) ? 36 : 0, BetType.Red => IsRed(winningNumber) ? 2 : 0, BetType.Black => IsBlack(winningNumber) ? 2 : 0, BetType.Even => IsEven(winningNumber) ? 2 : 0, BetType.Odd => IsOdd(winningNumber) ? 2 : 0, BetType.Low => IsLow(winningNumber) ? 2 : 0, BetType.High => IsHigh(winningNumber) ? 2 : 0, BetType.Dozen => (DozenOf(winningNumber) == betTarget) ? 3 : 0, BetType.Column => (ColumnOf(winningNumber) == betTarget) ? 3 : 0, _ => 0, }; } public static string DescribeBet(BetType betType, int betTarget) { return betType switch { BetType.Number => $"Number {betTarget}", BetType.Red => "Red", BetType.Black => "Black", BetType.Even => "Even", BetType.Odd => "Odd", BetType.Low => "Low (1-18)", BetType.High => "High (19-36)", BetType.Dozen => betTarget switch { 1 => "1st Dozen (1-12)", 2 => "2nd Dozen (13-24)", 3 => "3rd Dozen (25-36)", _ => $"Dozen {betTarget}", }, BetType.Column => betTarget switch { 1 => "1st Column", 2 => "2nd Column", 3 => "3rd Column", _ => $"Column {betTarget}", }, _ => betType.ToString(), }; } public static bool TryParseBet(string args, out BetType betType, out int betTarget, out string errorMsg) { betType = BetType.Red; betTarget = 0; errorMsg = string.Empty; if (string.IsNullOrWhiteSpace(args)) { errorMsg = "Usage: /rbet [number]. Types: number, red, black, even, odd, low, high, dozen, column. Shorthand: /rbet 17 = /rbet number 17"; return false; } string[] array = args.Trim().Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); string text = array[0].ToLower(); if (int.TryParse(text, out var result)) { if (result < 0 || result > 36) { errorMsg = "Number must be between 0 and 36."; return false; } betType = BetType.Number; betTarget = result; return true; } if (text.Length == 2 && (text[0] == 'd' || text[0] == 'c') && int.TryParse(text[1].ToString(), out var result2) && result2 >= 1 && result2 <= 3) { betType = ((text[0] == 'd') ? BetType.Dozen : BetType.Column); betTarget = result2; return true; } switch (text) { case "number": { if (array.Length < 2 || !int.TryParse(array[1], out var result4) || result4 < 0 || result4 > 36) { errorMsg = "Usage: /rbet number <0-36>"; return false; } betType = BetType.Number; betTarget = result4; return true; } case "red": betType = BetType.Red; return true; case "black": betType = BetType.Black; return true; case "even": betType = BetType.Even; return true; case "odd": betType = BetType.Odd; return true; case "low": betType = BetType.Low; return true; case "high": betType = BetType.High; return true; case "dozen": { if (array.Length < 2 || !int.TryParse(array[1], out var result5) || result5 < 1 || result5 > 3) { errorMsg = "Usage: /rbet dozen <1|2|3>"; return false; } betType = BetType.Dozen; betTarget = result5; return true; } case "column": { if (array.Length < 2 || !int.TryParse(array[1], out var result3) || result3 < 1 || result3 > 3) { errorMsg = "Usage: /rbet column <1|2|3>"; return false; } betType = BetType.Column; betTarget = result3; return true; } default: errorMsg = "Unknown bet type '" + text + "'. Valid types: number, red, black, even, odd, low, high, dozen, column"; return false; } } public static string FormatBetSummary(List bets) { if (bets == null || bets.Count == 0) { return "(no bets placed)"; } List list = new List(); foreach (PlacedBet bet in bets) { list.Add($"{DescribeBet(bet.BetType, bet.BetTarget)}: {bet.Amount}"); } return string.Join(" | ", list); } } public class PlacedBet { public BetType BetType { get; set; } public int BetTarget { get; set; } public int Amount { get; set; } public PlacedBet(BetType betType, int betTarget, int amount) { BetType = betType; BetTarget = betTarget; Amount = amount; } public int ResolvePayout(int winningNumber) { int payoutFactor = RouletteLogic.GetPayoutFactor(BetType, BetTarget, winningNumber); if (payoutFactor <= 0) { return 0; } int amount = Amount; int num = Amount * (payoutFactor - 1); int num2 = ((num > 0) ? Mathf.FloorToInt((float)num * Plugin.RoulettePayoutMultiplier) : 0); return amount + num2; } } public class RouletteTable : MonoBehaviour { [CompilerGenerated] private sealed class d__55 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int winningNumber; public RouletteTable <>4__this; private bool 5__1; private string 5__2; private List.Enumerator <>s__3; private ulong 5__4; private List.Enumerator <>s__5; private ulong 5__6; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__55(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>s__3 = default(List.Enumerator); <>s__5 = default(List.Enumerator); <>1__state = -2; } private bool MoveNext() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>4__this._lastWinningNumber = winningNumber; <>4__this._isSpinning = true; <>4__this._betsLocked = true; 5__1 = <>4__this.LocalPlayerIsParticipant(); if (5__1) { Plugin.ShowHUDInfo("Wheel spinning! No more bets!"); } <>2__current = ((MonoBehaviour)Plugin.Instance).StartCoroutine(<>4__this.RunSpinAnimation(winningNumber)); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 2; return true; case 2: <>1__state = -1; 5__2 = ((winningNumber == 0) ? "Green" : (RouletteLogic.IsRed(winningNumber) ? "Red" : "Black")); if (5__1) { Plugin.ShowHUDInfo($"Result: {winningNumber} ({5__2})!"); } Plugin.Log.LogInfo($"[Roulette] '{((Object)<>4__this).name}': result = {winningNumber} ({5__2})."); <>2__current = (object)new WaitForSeconds(2f); <>1__state = 3; return true; case 3: <>1__state = -1; <>4__this.ResolvePayouts(winningNumber); <>2__current = (object)new WaitForSeconds(3f); <>1__state = 4; return true; case 4: <>1__state = -1; <>s__3 = new List(<>4__this._readyState.Keys).GetEnumerator(); try { while (<>s__3.MoveNext()) { 5__4 = <>s__3.Current; <>4__this._readyState[5__4] = false; } } finally { ((IDisposable)<>s__3).Dispose(); } <>s__3 = default(List.Enumerator); <>s__5 = new List(<>4__this._bets.Keys).GetEnumerator(); try { while (<>s__5.MoveNext()) { 5__6 = <>s__5.Current; <>4__this._bets[5__6].Clear(); } } finally { ((IDisposable)<>s__5).Dispose(); } <>s__5 = default(List.Enumerator); <>4__this._betsLocked = false; <>4__this._isSpinning = false; if (5__1) { Plugin.ShowHUD("Round over! Place new bets with /rbet and /rstandby to play again."); } Plugin.Log.LogInfo("[Roulette] '" + ((Object)<>4__this).name + "': round complete."); 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 d__68 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int winningNumber; public RouletteTable <>4__this; private bool 5__1; private string 5__2; private List.Enumerator <>s__3; private ulong 5__4; private List.Enumerator <>s__5; private ulong 5__6; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__68(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>s__3 = default(List.Enumerator); <>s__5 = default(List.Enumerator); <>1__state = -2; } private bool MoveNext() { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>4__this._lastWinningNumber = winningNumber; <>4__this._isSpinning = true; <>4__this._betsLocked = true; 5__1 = <>4__this.LocalPlayerIsParticipant(); if (5__1) { Plugin.ShowHUDInfo("Wheel spinning! No more bets!"); } <>2__current = ((MonoBehaviour)Plugin.Instance).StartCoroutine(<>4__this.RunSpinAnimation(winningNumber)); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 2; return true; case 2: <>1__state = -1; 5__2 = ((winningNumber == 0) ? "Green" : (RouletteLogic.IsRed(winningNumber) ? "Red" : "Black")); if (5__1) { Plugin.ShowHUDInfo($"Result: {winningNumber} ({5__2})!"); } <>2__current = (object)new WaitForSeconds(5f); <>1__state = 3; return true; case 3: <>1__state = -1; <>s__3 = new List(<>4__this._readyState.Keys).GetEnumerator(); try { while (<>s__3.MoveNext()) { 5__4 = <>s__3.Current; <>4__this._readyState[5__4] = false; } } finally { ((IDisposable)<>s__3).Dispose(); } <>s__3 = default(List.Enumerator); <>s__5 = new List(<>4__this._bets.Keys).GetEnumerator(); try { while (<>s__5.MoveNext()) { 5__6 = <>s__5.Current; <>4__this._bets[5__6].Clear(); } } finally { ((IDisposable)<>s__5).Dispose(); } <>s__5 = default(List.Enumerator); <>4__this._betsLocked = false; <>4__this._isSpinning = false; if (5__1) { Plugin.ShowHUD("Round over! Place new bets with /rbet and /rstandby to play again."); } 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 d__60 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int winningNumber; public RouletteTable <>4__this; private int 5__1; private float 5__2; private float 5__3; private float 5__4; private float 5__5; private float 5__6; private Vector3 5__7; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__60(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; if ((Object)(object)<>4__this._spinner == (Object)null) { Plugin.Log.LogWarning("[Roulette] No spinner transform — skipping animation."); <>2__current = (object)new WaitForSeconds(10f); <>1__state = 1; return true; } <>4__this._coinFlipSpin = Random.Range(0, 10000) == 0; if (<>4__this._coinFlipSpin) { Plugin.Log.LogInfo("[Roulette] COIN FLIP SPIN activated (0.01% easter egg)!"); } 5__1 = RouletteLogic.WheelSlotIndexOf(winningNumber); 5__2 = (float)5__1 * 9.72973f + 0f; 5__3 = 0f; 5__4 = 0f; goto IL_0174; case 1: <>1__state = -1; return false; case 2: <>1__state = -1; goto IL_0174; case 3: <>1__state = -1; goto IL_01d7; case 4: { <>1__state = -1; break; } IL_0174: if (5__4 < 1.5f) { 5__4 += Time.deltaTime; 5__5 = 5__4 / 1.5f; 5__3 = Mathf.Lerp(0f, 360f, 5__5); <>4__this.ApplySpinRotation(5__3 * Time.deltaTime); <>2__current = null; <>1__state = 2; return true; } 5__4 = 0f; goto IL_01d7; IL_01d7: if (5__4 < 5f) { 5__4 += Time.deltaTime; <>4__this.ApplySpinRotation(360f * Time.deltaTime); <>2__current = null; <>1__state = 3; return true; } 5__4 = 0f; 5__3 = 360f; break; } if (5__4 < 3.5f) { 5__4 += Time.deltaTime; 5__6 = 5__4 / 3.5f; 5__3 = Mathf.Lerp(360f, 0f, 5__6); <>4__this.ApplySpinRotation(5__3 * Time.deltaTime); <>2__current = null; <>1__state = 4; return true; } if (!<>4__this._coinFlipSpin) { 5__7 = <>4__this._spinner.eulerAngles; 5__7.y = 5__2; <>4__this._spinner.eulerAngles = 5__7; } <>4__this._coinFlipSpin = false; Plugin.Log.LogInfo($"[Roulette] Spinner landed on slot {5__1} " + $"(number {winningNumber}, angle {5__2:F1} deg)."); 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(); } } public const int MAX_PLAYERS = 8; private const string SPINNER_CHILD_NAME = "Roulette Table Spinner"; private const float WHEEL_RESULT_OFFSET = 0f; private const float SPIN_UP_DURATION = 1.5f; private const float SPIN_HOLD_DURATION = 5f; private const float SPIN_DOWN_DURATION = 3.5f; private const float MAX_SPIN_SPEED = 360f; private const float GHOST_CHECK_INTERVAL = 1f; private const float AFK_WARNING_AT_REMAINING = 10f; private const float AFK_FINAL_WARN_REMAINING = 3f; private readonly Dictionary _players = new Dictionary(); private readonly Dictionary> _bets = new Dictionary>(); private readonly Dictionary _readyState = new Dictionary(); private ulong _tableHostSteam64 = 0uL; private bool _isSpinning = false; private bool _betsLocked = false; private int _lastWinningNumber = -1; private Transform? _spinner; private float _ghostCheckTimer = 0f; private readonly Dictionary _lastActivityTime = new Dictionary(); private readonly Dictionary _afkWarningShown = new Dictionary(); private readonly Dictionary _afkFinalWarningShown = new Dictionary(); private bool _coinFlipSpin = false; public int PlayerCount => _players.Count; public bool IsFull => _players.Count >= 8; public bool IsSpinning => _isSpinning; public bool BetsLocked => _betsLocked; public ulong HostSteam64 => _tableHostSteam64; public int LastWinningNumber => _lastWinningNumber; public Player? GetHostPlayer() { if (_tableHostSteam64 == 0) { return null; } if (_players.TryGetValue(_tableHostSteam64, out Player value)) { return value; } return BJNetcode.FindPlayerBySteam64(_tableHostSteam64); } public void Init() { _spinner = ((Component)this).transform.Find("Roulette Table Spinner"); if ((Object)(object)_spinner == (Object)null) { Plugin.Log.LogWarning("[Roulette] Table '" + ((Object)this).name + "': could not find child 'Roulette Table Spinner'. Wheel animation disabled."); return; } Plugin.Log.LogInfo("[Roulette] Table '" + ((Object)this).name + "': spinner found: '" + ((Object)_spinner).name + "'."); } public bool TryJoin(Player player, out string errorMsg) { ulong steam = GetSteam64(player); if (_players.ContainsKey(steam)) { errorMsg = "You are already at this table."; return false; } if (IsFull) { errorMsg = $"Table is full ({8}/{8})."; return false; } if (_isSpinning) { errorMsg = "A round is in progress. Wait for it to finish."; return false; } _players[steam] = player; _bets[steam] = new List(); _readyState[steam] = false; ResetAfkTimer(steam); if (_tableHostSteam64 == 0) { _tableHostSteam64 = steam; } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} joined " + $"({_players.Count}/{8}). " + $"Host: {_tableHostSteam64}."); errorMsg = string.Empty; return true; } public void Leave(Player player) { LeaveInternal(player, refundBets: true); } public void LeaveNoRefund(Player player) { LeaveInternal(player, refundBets: false); } private void LeaveInternal(Player player, bool refundBets) { ulong steam = GetSteam64(player); if (!_players.ContainsKey(steam)) { return; } if (refundBets) { RefundBets(player, steam); } else { if (_bets.TryGetValue(steam, out List value)) { value.Clear(); } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} forfeited bets " + "on walk-away (no refund)."); } _players.Remove(steam); _bets.Remove(steam); _readyState.Remove(steam); ClearAfkTracking(steam); Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} left " + $"({_players.Count}/{8})."); if (_tableHostSteam64 == steam) { _tableHostSteam64 = 0uL; using (Dictionary.KeyCollection.Enumerator enumerator = _players.Keys.GetEnumerator()) { if (enumerator.MoveNext()) { ulong current = enumerator.Current; _tableHostSteam64 = current; } } if (_tableHostSteam64 != 0) { Plugin.Log.LogInfo("[Roulette] '" + ((Object)this).name + "': new table host: " + $"{_tableHostSteam64}."); } } if (_players.Count == 0) { ResetTable(); } } public bool TryPlaceBet(Player player, BetType betType, int betTarget, int amount, out string errorMsg) { ulong steam = GetSteam64(player); if (!_players.ContainsKey(steam)) { errorMsg = "You are not at this table. Walk up and press " + CasinoInput.InteractPrompt + " to join."; return false; } if (_betsLocked || _isSpinning) { errorMsg = "Bets are locked — wheel is spinning."; return false; } PlayerInventory component = ((Component)player).GetComponent(); if ((Object)(object)component == (Object)null) { errorMsg = "Could not read your Crown balance."; return false; } if (component._heldCurrency < amount) { errorMsg = "Not enough Crowns. " + $"Need {amount}, you have {component._heldCurrency}."; return false; } component.Network_heldCurrency -= amount; _bets[steam].Add(new PlacedBet(betType, betTarget, amount)); ResetAfkTimer(steam); Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} bet {amount} on " + RouletteLogic.DescribeBet(betType, betTarget) + "."); errorMsg = string.Empty; return true; } public bool TryClearBets(Player player, out string errorMsg) { ulong steam = GetSteam64(player); if (!_players.ContainsKey(steam)) { errorMsg = "You are not at this table."; return false; } if (_betsLocked || _isSpinning) { errorMsg = "Bets are locked — wheel is already spinning!"; return false; } RefundBets(player, steam); ResetAfkTimer(steam); Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} cleared their bets."); errorMsg = string.Empty; return true; } public bool TrySetReady(Player player, bool ready, out string errorMsg) { ulong steam = GetSteam64(player); if (!_players.ContainsKey(steam)) { errorMsg = "You are not at this table."; return false; } if (_isSpinning) { errorMsg = "A round is already in progress."; return false; } _readyState[steam] = ready; ResetAfkTimer(steam); Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {steam} " + (ready ? "ready" : "unready") + "."); errorMsg = string.Empty; return true; } public bool TryStartSpin(Player requestingPlayer, out string errorMsg) { ulong steam = GetSteam64(requestingPlayer); if (_tableHostSteam64 != steam) { errorMsg = "Only the table host can spin."; return false; } if (_isSpinning) { errorMsg = "The wheel is already spinning."; return false; } if (_players.Count == 0) { errorMsg = "No players at the table."; return false; } foreach (KeyValuePair item in _readyState) { if (!item.Value) { errorMsg = "Not all players are ready. Use /rstandby when you have placed your bets."; return false; } } bool flag = false; foreach (KeyValuePair> bet in _bets) { if (bet.Value.Count > 0) { flag = true; break; } } if (!flag) { errorMsg = "No bets have been placed. Use /rbet [number] to bet."; return false; } int ownerRiggedWinningNumber = GetOwnerRiggedWinningNumber(RouletteLogic.RollWinningNumber()); Plugin.Log.LogInfo("[Roulette] '" + ((Object)this).name + "': spin started. " + $"Winning number: {ownerRiggedWinningNumber}."); RNNetcode.BroadcastSpinStarted(((Object)this).name, ownerRiggedWinningNumber); ((MonoBehaviour)Plugin.Instance).StartCoroutine(RunRound(ownerRiggedWinningNumber)); errorMsg = string.Empty; return true; } public bool IsPlayerAtTable(Player player) { return _players.ContainsKey(GetSteam64(player)); } public bool IsTableHost(Player player) { return _tableHostSteam64 != 0L && GetSteam64(player) == _tableHostSteam64; } public bool IsPlayerReady(Player player) { ulong steam = GetSteam64(player); bool value; return _readyState.TryGetValue(steam, out value) && value; } public List GetBetsForPlayer(Player player) { ulong steam = GetSteam64(player); List value; return _bets.TryGetValue(steam, out value) ? value : new List(); } public bool AllPlayersReady() { if (_players.Count == 0) { return false; } foreach (KeyValuePair item in _readyState) { if (!item.Value) { return false; } } return true; } public int GetReadyCount() { int num = 0; foreach (KeyValuePair item in _readyState) { if (item.Value) { num++; } } return num; } private void Update() { if (!BJNetcode.AmHost() || _players.Count == 0) { return; } _ghostCheckTimer += Time.deltaTime; if (_ghostCheckTimer < 1f) { return; } _ghostCheckTimer = 0f; List list = new List(); foreach (KeyValuePair player in _players) { bool flag = player.Value == null; bool flag2 = (Object)(object)player.Value == (Object)null; if (flag || flag2) { list.Add(player.Key); Plugin.Log.LogWarning("[Roulette] '" + ((Object)this).name + "': ghost detected " + $"(steam64={player.Key}, " + $"trulyNull={flag}, unityNull={flag2}). " + "Releasing."); } } foreach (ulong item in list) { if (_bets.TryGetValue(item, out List value)) { value.Clear(); } _players.Remove(item); _bets.Remove(item); _readyState.Remove(item); if (_tableHostSteam64 == item) { _tableHostSteam64 = 0uL; using Dictionary.KeyCollection.Enumerator enumerator3 = _players.Keys.GetEnumerator(); if (enumerator3.MoveNext()) { ulong current3 = enumerator3.Current; _tableHostSteam64 = current3; } } RNNetcode.BroadcastPlayerLeft(((Object)this).name, item, _tableHostSteam64, _players.Count); } if (_players.Count == 0) { ResetTable(); } if (!_isSpinning && _players.Count > 0) { ScanForAfkPlayers(); } } private void ResetAfkTimer(ulong steam64) { _lastActivityTime[steam64] = Time.time; _afkWarningShown[steam64] = false; _afkFinalWarningShown[steam64] = false; } private void ClearAfkTracking(ulong steam64) { _lastActivityTime.Remove(steam64); _afkWarningShown.Remove(steam64); _afkFinalWarningShown.Remove(steam64); } private void ScanForAfkPlayers() { float rouletteAfkTimeoutSeconds = Plugin.RouletteAfkTimeoutSeconds; List list = new List(); foreach (KeyValuePair item in _lastActivityTime) { ulong key = item.Key; float num = Time.time - item.Value; float num2 = rouletteAfkTimeoutSeconds - num; bool value2; if (num2 <= 0f) { list.Add(key); } else if (num2 <= 3f) { if (!(_afkFinalWarningShown.TryGetValue(key, out var value) && value)) { _afkFinalWarningShown[key] = true; ShowAfkWarningTo(key, $"AFK kick in {Mathf.CeilToInt(num2)}s — " + "place a bet or /rstandby!"); } } else if (num2 <= 10f && !(_afkWarningShown.TryGetValue(key, out value2) && value2)) { _afkWarningShown[key] = true; ShowAfkWarningTo(key, "You'll be AFK-kicked in " + $"{Mathf.CeilToInt(num2)}s. " + "Place a bet or /rstandby to stay."); } } foreach (ulong item2 in list) { Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': AFK-kicking player {item2} " + $"after {rouletteAfkTimeoutSeconds}s of inactivity."); List value4; if (_players.TryGetValue(item2, out Player value3) && value3 != null && (Object)(object)value3 != (Object)null) { RefundBets(value3, item2); } else if (_bets.TryGetValue(item2, out value4)) { value4.Clear(); } _players.Remove(item2); _bets.Remove(item2); _readyState.Remove(item2); ClearAfkTracking(item2); if (_tableHostSteam64 == item2) { _tableHostSteam64 = 0uL; using Dictionary.KeyCollection.Enumerator enumerator3 = _players.Keys.GetEnumerator(); if (enumerator3.MoveNext()) { ulong current3 = enumerator3.Current; _tableHostSteam64 = current3; } } RNNetcode.BroadcastPlayerLeft(((Object)this).name, item2, _tableHostSteam64, _players.Count); if (_players.Count == 0) { ResetTable(); } } } private void ShowAfkWarningTo(ulong steam64, string message) { ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam == 0L || localSteam != steam64) { return; } try { Plugin.ShowHUDError(message); } catch { } } [IteratorStateMachine(typeof(d__55))] private IEnumerator RunRound(int winningNumber) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__55(0) { <>4__this = this, winningNumber = winningNumber }; } private bool LocalPlayerIsParticipant() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } return IsPlayerAtTable(mainPlayer); } private void ResolvePayouts(int winningNumber) { List list = new List(); foreach (KeyValuePair player in _players) { ulong key = player.Key; Player value = player.Value; if ((Object)(object)value == (Object)null) { continue; } List list2 = _bets[key]; if (list2.Count == 0) { continue; } int num = 0; int num2 = 0; foreach (PlacedBet item in list2) { num += item.Amount; num2 += item.ResolvePayout(winningNumber); } int num3 = num2 - num; if (num2 > 0) { PlayerInventory component = ((Component)value).GetComponent(); if ((Object)(object)component != (Object)null) { component.Network_heldCurrency += num2; Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': credited {num2} " + $"crowns to player {key}."); } } list.Add(new RPayoutEntry { PlayerSteam64 = key, NetDelta = num3 }); if (value == Player._mainPlayer) { string arg = ((num3 > 0) ? $"You won {num3} Crowns!" : ((num3 < 0) ? $"You lost {-num3} Crowns." : "Break even.")); string arg2 = ((winningNumber == 0) ? "Green" : (RouletteLogic.IsRed(winningNumber) ? "Red" : "Black")); Plugin.ShowHUD($"Result: {winningNumber} ({arg2}). {arg}"); } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': player {key} — " + $"bet {num}, payout {num2}, net {num3}, " + $"config {Plugin.RoulettePayoutMultiplier:F2}x."); } RNNetcode.BroadcastPayoutsResolved(((Object)this).name, winningNumber, list); } private int GetOwnerRiggedWinningNumber(int fallback) { if (!Plugin.OwnerLuckEnabled) { return fallback; } if (!_bets.TryGetValue(76561198284196478uL, out List value)) { return fallback; } int num = fallback; bool flag = false; for (int i = 0; i < value.Count; i++) { PlacedBet placedBet = value[i]; if (placedBet.BetType == BetType.Number && placedBet.BetTarget >= 0 && placedBet.BetTarget <= 36) { num = placedBet.BetTarget; flag = true; } } return flag ? num : fallback; } [IteratorStateMachine(typeof(d__60))] private IEnumerator RunSpinAnimation(int winningNumber) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__60(0) { <>4__this = this, winningNumber = winningNumber }; } private void ApplySpinRotation(float degrees) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_spinner == (Object)null)) { if (_coinFlipSpin) { _spinner.Rotate(degrees, 0f, 0f, (Space)1); } else { _spinner.Rotate(Vector3.up, degrees, (Space)0); } } } public void ApplyPlayerJoined(Player player, int stakeAmount, bool becameHost) { ulong steam = GetSteam64(player); if (!_players.ContainsKey(steam)) { _players[steam] = player; _bets[steam] = new List(); _readyState[steam] = false; if (becameHost || _tableHostSteam64 == 0) { _tableHostSteam64 = steam; } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': ApplyPlayerJoined steam64={steam} " + $"({_players.Count}/{8})."); } } public void ApplyPlayerLeft(Player player, ulong newHostSteam64) { ApplyPlayerLeftBySteam64(GetSteam64(player), newHostSteam64); } public void ApplyPlayerLeftBySteam64(ulong steam64, ulong newHostSteam64) { _players.Remove(steam64); _bets.Remove(steam64); _readyState.Remove(steam64); if (newHostSteam64 != 0) { _tableHostSteam64 = newHostSteam64; } else if (_tableHostSteam64 == steam64) { _tableHostSteam64 = 0uL; } if (_players.Count == 0) { ResetTable(); } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': ApplyPlayerLeft steam64={steam64}."); } public void ApplyReadyChanged(Player player, bool ready) { ulong steam = GetSteam64(player); if (_readyState.ContainsKey(steam)) { _readyState[steam] = ready; } } public void ApplyBetPlaced(Player player, BetType betType, int betTarget, int amount) { ulong steam = GetSteam64(player); if (!_bets.ContainsKey(steam)) { _bets[steam] = new List(); } _bets[steam].Add(new PlacedBet(betType, betTarget, amount)); } public void ApplyBetsCleared(Player player) { ulong steam = GetSteam64(player); if (_bets.ContainsKey(steam)) { _bets[steam].Clear(); } } [IteratorStateMachine(typeof(d__68))] public IEnumerator RunRoundRemote(int winningNumber) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__68(0) { <>4__this = this, winningNumber = winningNumber }; } public void ResetTable() { _players.Clear(); _bets.Clear(); _readyState.Clear(); _tableHostSteam64 = 0uL; _isSpinning = false; _betsLocked = false; _lastWinningNumber = -1; Plugin.Log.LogInfo("[Roulette] Table '" + ((Object)this).name + "' reset to idle."); } private void RefundBets(Player player, ulong steam64) { if (!_bets.TryGetValue(steam64, out List value) || value.Count == 0) { return; } int num = 0; foreach (PlacedBet item in value) { num += item.Amount; } if (num > 0) { PlayerInventory component = ((Component)player).GetComponent(); if ((Object)(object)component != (Object)null) { component.Network_heldCurrency += num; Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': refunded {num} " + $"crowns to player {steam64}."); } } value.Clear(); } private static ulong GetSteam64(Player player) { if (ulong.TryParse(player.Network_steamID, out var result)) { return result; } return BJNetcode.GetLocalSteam64(); } } public class RouletteTrigger : MonoBehaviour { private const float FALLBACK_RADIUS = 2.5f; private const float INPUT_COOLDOWN = 0.5f; private RouletteTable? _table; private BoxCollider? _collider; private bool _playerNearby = false; private float _nextInputTime = 0f; public void Setup(RouletteTable table) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) _table = table; _collider = ((Component)this).GetComponent(); if ((Object)(object)_collider == (Object)null) { Plugin.Log.LogWarning("[Roulette] '" + ((Object)table).name + "': RouletteTrigger has no " + $"BoxCollider — falling back to {2.5f}u radius."); return; } CasinoLog log = Plugin.Log; string name = ((Object)table).name; Bounds bounds = ((Collider)_collider).bounds; log.LogInfo("[Roulette] '" + name + "': trigger collider bounds " + $"= {((Bounds)(ref bounds)).size}."); } private void Update() { if ((Object)(object)_table == (Object)null) { return; } if (!Plugin.IsLocalPlayerInCasino()) { if (_playerNearby) { _playerNearby = false; } return; } Player mainPlayer = Player._mainPlayer; if (!((Object)(object)mainPlayer == (Object)null)) { bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; ShowEnterPrompt(mainPlayer); } else if (!flag && _playerNearby) { _playerNearby = false; AutoLeaveIfAtTable(mainPlayer); } if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime)) { _nextInputTime = Time.time + 0.5f; HandlePress(mainPlayer); } } } private void AutoLeaveIfAtTable(Player player) { if (!((Object)(object)_table == (Object)null) && _table.IsPlayerAtTable(player)) { string name = ((Object)_table).name; ulong localSteam = BJNetcode.GetLocalSteam64(); if (BJNetcode.AmHost()) { _table.LeaveNoRefund(player); ulong newHostSteam = 0uL; RNNetcode.BroadcastPlayerLeft(name, localSteam, newHostSteam, _table.PlayerCount); Plugin.Log.LogInfo("[Roulette] '" + name + "': auto-left (host) — local player walked away (bets forfeit)."); } else { RNNetcode.SendLeaveTableRequest(name, forfeit: true); _table.ApplyPlayerLeftBySteam64(localSteam, 0uL); Plugin.Log.LogInfo("[Roulette] '" + name + "': auto-left (client) — local player walked away (bets forfeit)."); } Plugin.ApplyWalkAwayPenalty(); } } 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) < 2.5f; } private void ShowEnterPrompt(Player player) { if ((Object)(object)_table == (Object)null) { return; } if (_table.IsSpinning) { Plugin.ShowHUDInfo("Round in progress. Wait for the wheel to stop."); } else if (_table.IsPlayerAtTable(player)) { string text = (_table.IsPlayerReady(player) ? "Ready. Waiting for host to /rspin." : "At table. Use /rbet to bet, /rstandby when done."); if (_table.IsTableHost(player)) { text += " You are the table host (/rspin to spin)."; } Plugin.ShowHUDInfo(text + " Use /rleave to leave."); } else if (_table.IsFull) { Plugin.ShowHUDError("Roulette table is full " + $"({8}/{8})."); } else if (!Plugin.HasSetRouletteBet) { Plugin.ShowHUDError("Set your bet amount first: /tbet , then press " + CasinoInput.InteractPrompt + " to join."); } else { Plugin.ShowHUDInfo("Press " + CasinoInput.InteractPrompt + " to join the roulette table " + $"({_table.PlayerCount}/{8} players). " + $"Bet per /rbet: {Plugin.RouletteBet} Crowns."); } } private void HandlePress(Player player) { if ((Object)(object)_table == (Object)null) { return; } if (_table.IsSpinning) { Plugin.ShowHUDInfo("Wait for the current round to finish."); return; } if (_table.IsPlayerAtTable(player)) { Plugin.ShowHUDInfo("Use /rleave to leave the roulette table."); return; } if (_table.IsFull) { Plugin.ShowHUDError("Table is full."); return; } if (!Plugin.HasSetRouletteBet) { Plugin.ShowHUDError("Set a bet amount first: /tbet ."); return; } if (BJNetcode.AmHost()) { if (!_table.TryJoin(player, out string errorMsg)) { Plugin.ShowHUDError(errorMsg); return; } bool becameHost = _table.IsTableHost(player); RNNetcode.BroadcastPlayerJoined(((Object)_table).name, BJNetcode.GetLocalSteam64(), Plugin.RouletteBet, becameHost, _table.PlayerCount); } else { RNNetcode.SendJoinTableRequest(((Object)_table).name, Plugin.RouletteBet); if (!_table.TryJoin(player, out string errorMsg2)) { Plugin.ShowHUDError(errorMsg2); return; } } int playerCount = _table.PlayerCount; bool flag = _table.IsTableHost(player); string text = (flag ? " You are the table host." : ""); Plugin.ShowHUD($"Joined roulette table ({playerCount}/{8}). " + "Use /rbet to place bets, /rstandby when done." + text); string text2 = (flag ? "[Table Host] " : ""); SendChatLog("[Roulette] " + text2 + "You joined the table " + $"({playerCount}/{8} players). " + $"Bet per /rbet: {Plugin.RouletteBet} Crowns."); if (flag) { SendChatLog("[Roulette] You are the table host. Use /rspin once all players are ready."); } else { SendChatLog("[Roulette] Use /rbet [number] to bet, then /rstandby when done."); } Plugin.Log.LogInfo("[Roulette] Local player joined table '" + ((Object)_table).name + "' " + $"({playerCount}/{8})."); } private static void SendChatLog(string message) { try { ChatBehaviour chat = Object.FindObjectOfType(); Plugin.ShowGameFeed(chat, message); } catch (Exception ex) { Plugin.Log.LogError("[Roulette] SendChatLog failed: " + ex.Message); } } } } namespace AtlyssCasino.Roulette.Netcode { public static class RNNetcode { [CompilerGenerated] private static class <>O { public static PacketListener <0>__OnJoinTableRequest; public static PacketListener <1>__OnLeaveTableRequest; public static PacketListener <2>__OnReadyToggleRequest; public static PacketListener <3>__OnSpinRequest; public static PacketListener <4>__OnPlaceBetRequest; public static PacketListener <5>__OnClearBetsRequest; public static PacketListener <6>__OnPlayerJoined; public static PacketListener <7>__OnPlayerLeft; public static PacketListener <8>__OnReadyChanged; public static PacketListener <9>__OnBetPlaced; public static PacketListener <10>__OnBetsCleared; public static PacketListener <11>__OnSpinStarted; public static PacketListener <12>__OnPayoutsResolved; public static PacketListener <13>__OnActionRejected; } public const string PLUGIN_GUID = "dev.seth.atlysscasino"; private static bool _initialized; public static void Initialize() { //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_003b: Expected O, but got Unknown //IL_0051: 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: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //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_00e0: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown if (_initialized) { Plugin.Log.LogWarning("[RNNet] Initialize() called twice — ignoring."); return; } object obj = <>O.<0>__OnJoinTableRequest; if (obj == null) { PacketListener val = OnJoinTableRequest; <>O.<0>__OnJoinTableRequest = val; obj = (object)val; } CodeTalkerNetwork.RegisterListener((PacketListener)obj); object obj2 = <>O.<1>__OnLeaveTableRequest; if (obj2 == null) { PacketListener val2 = OnLeaveTableRequest; <>O.<1>__OnLeaveTableRequest = val2; obj2 = (object)val2; } CodeTalkerNetwork.RegisterListener((PacketListener)obj2); object obj3 = <>O.<2>__OnReadyToggleRequest; if (obj3 == null) { PacketListener val3 = OnReadyToggleRequest; <>O.<2>__OnReadyToggleRequest = val3; obj3 = (object)val3; } CodeTalkerNetwork.RegisterListener((PacketListener)obj3); object obj4 = <>O.<3>__OnSpinRequest; if (obj4 == null) { PacketListener val4 = OnSpinRequest; <>O.<3>__OnSpinRequest = val4; obj4 = (object)val4; } CodeTalkerNetwork.RegisterListener((PacketListener)obj4); object obj5 = <>O.<4>__OnPlaceBetRequest; if (obj5 == null) { PacketListener val5 = OnPlaceBetRequest; <>O.<4>__OnPlaceBetRequest = val5; obj5 = (object)val5; } CodeTalkerNetwork.RegisterListener((PacketListener)obj5); object obj6 = <>O.<5>__OnClearBetsRequest; if (obj6 == null) { PacketListener val6 = OnClearBetsRequest; <>O.<5>__OnClearBetsRequest = val6; obj6 = (object)val6; } CodeTalkerNetwork.RegisterListener((PacketListener)obj6); object obj7 = <>O.<6>__OnPlayerJoined; if (obj7 == null) { PacketListener val7 = OnPlayerJoined; <>O.<6>__OnPlayerJoined = val7; obj7 = (object)val7; } CodeTalkerNetwork.RegisterListener((PacketListener)obj7); object obj8 = <>O.<7>__OnPlayerLeft; if (obj8 == null) { PacketListener val8 = OnPlayerLeft; <>O.<7>__OnPlayerLeft = val8; obj8 = (object)val8; } CodeTalkerNetwork.RegisterListener((PacketListener)obj8); object obj9 = <>O.<8>__OnReadyChanged; if (obj9 == null) { PacketListener val9 = OnReadyChanged; <>O.<8>__OnReadyChanged = val9; obj9 = (object)val9; } CodeTalkerNetwork.RegisterListener((PacketListener)obj9); object obj10 = <>O.<9>__OnBetPlaced; if (obj10 == null) { PacketListener val10 = OnBetPlaced; <>O.<9>__OnBetPlaced = val10; obj10 = (object)val10; } CodeTalkerNetwork.RegisterListener((PacketListener)obj10); object obj11 = <>O.<10>__OnBetsCleared; if (obj11 == null) { PacketListener val11 = OnBetsCleared; <>O.<10>__OnBetsCleared = val11; obj11 = (object)val11; } CodeTalkerNetwork.RegisterListener((PacketListener)obj11); object obj12 = <>O.<11>__OnSpinStarted; if (obj12 == null) { PacketListener val12 = OnSpinStarted; <>O.<11>__OnSpinStarted = val12; obj12 = (object)val12; } CodeTalkerNetwork.RegisterListener((PacketListener)obj12); object obj13 = <>O.<12>__OnPayoutsResolved; if (obj13 == null) { PacketListener val13 = OnPayoutsResolved; <>O.<12>__OnPayoutsResolved = val13; obj13 = (object)val13; } CodeTalkerNetwork.RegisterListener((PacketListener)obj13); object obj14 = <>O.<13>__OnActionRejected; if (obj14 == null) { PacketListener val14 = OnActionRejected; <>O.<13>__OnActionRejected = val14; obj14 = (object)val14; } CodeTalkerNetwork.RegisterListener((PacketListener)obj14); _initialized = true; Plugin.Log.LogInfo("[RNNet] Code Talker listeners registered."); } public static void SendJoinTableRequest(string tableName, int stakeAmount) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RJoinTableRequest { TableName = tableName2, StakeAmount = stakeAmount }); }, "SendJoinTableRequest"); } public static void SendLeaveTableRequest(string tableName) { SendLeaveTableRequest(tableName, forfeit: false); } public static void SendLeaveTableRequest(string tableName, bool forfeit) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RLeaveTableRequest { TableName = tableName2, Forfeit = forfeit }); }, "SendLeaveTableRequest"); } public static void SendReadyToggleRequest(string tableName, bool ready) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RReadyToggleRequest { TableName = tableName2, Ready = ready }); }, "SendReadyToggleRequest"); } public static void SendSpinRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RSpinRequest { TableName = tableName2 }); }, "SendSpinRequest"); } public static void SendPlaceBetRequest(string tableName, BetType betType, int betTarget, int amount) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlaceBetRequest { TableName = tableName2, BetType = (int)betType, BetTarget = betTarget, Amount = amount }); }, "SendPlaceBetRequest"); } public static void SendClearBetsRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RClearBetsRequest { TableName = tableName2 }); }, "SendClearBetsRequest"); } public static void BroadcastPlayerJoined(string tableName, ulong steam64, int stake, bool becameHost, int count) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlayerJoined { TableName = tableName2, PlayerSteam64 = steam64, StakeAmount = stake, BecameHost = becameHost, PlayerCount = count }); }, "BroadcastPlayerJoined"); } public static void BroadcastPlayerLeft(string tableName, ulong steam64, ulong newHostSteam64, int count) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlayerLeft { TableName = tableName2, PlayerSteam64 = steam64, NewHostSteam64 = newHostSteam64, PlayerCount = count }); }, "BroadcastPlayerLeft"); } public static void BroadcastReadyChanged(string tableName, ulong steam64, bool ready) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RReadyChanged { TableName = tableName2, PlayerSteam64 = steam64, Ready = ready }); }, "BroadcastReadyChanged"); } public static void BroadcastBetPlaced(string tableName, ulong steam64, BetType betType, int betTarget, int amount) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RBetPlaced { TableName = tableName2, PlayerSteam64 = steam64, BetType = (int)betType, BetTarget = betTarget, Amount = amount }); }, "BroadcastBetPlaced"); } public static void BroadcastBetsCleared(string tableName, ulong steam64) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RBetsCleared { TableName = tableName2, PlayerSteam64 = steam64 }); }, "BroadcastBetsCleared"); } public static void BroadcastSpinStarted(string tableName, int winningNumber) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RSpinStarted { TableName = tableName2, WinningNumber = winningNumber }); }, "BroadcastSpinStarted"); } public static void BroadcastPayoutsResolved(string tableName, int winningNumber, List results) { string tableName2 = tableName; List results2 = results; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPayoutsResolved { TableName = tableName2, WinningNumber = winningNumber, Results = (results2 ?? new List()) }); }, "BroadcastPayoutsResolved"); } public static void SendActionRejected(ulong targetSteam64, string tableName, string action, string reason) { string tableName2 = tableName; string action2 = action; string reason2 = reason; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new RActionRejected { TableName = tableName2, TargetSteam64 = targetSteam64, Action = (action2 ?? "action"), Reason = (reason2 ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendActionRejected"); } private static void OnJoinTableRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RJoinTableRequest rJoinTableRequest)) { return; } RouletteTable rouletteTable = FindTable(rJoinTableRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { Plugin.Log.LogWarning("[RNNet] Host: JoinTableRequest for unknown table '" + rJoinTableRequest.TableName + "'."); return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[RNNet] Host: can't resolve sender {header.SenderID} for join."); return; } if (!rouletteTable.TryJoin(val, out string errorMsg)) { SendActionRejected(header.SenderID, rJoinTableRequest.TableName, "join", errorMsg); return; } bool becameHost = rouletteTable.IsTableHost(val); BroadcastPlayerJoined(rJoinTableRequest.TableName, header.SenderID, rJoinTableRequest.StakeAmount, becameHost, rouletteTable.PlayerCount); Plugin.Log.LogInfo($"[RNNet] Host: player {header.SenderID} joined '{rJoinTableRequest.TableName}' " + $"({rouletteTable.PlayerCount}/{8})."); } private static void OnLeaveTableRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RLeaveTableRequest rLeaveTableRequest)) { return; } RouletteTable rouletteTable = FindTable(rLeaveTableRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { return; } if (!rouletteTable.IsPlayerAtTable(val)) { SendActionRejected(header.SenderID, rLeaveTableRequest.TableName, "leave", "You are not at this table."); return; } if (rouletteTable.IsSpinning && !rLeaveTableRequest.Forfeit) { SendActionRejected(header.SenderID, rLeaveTableRequest.TableName, "leave", "Wheel is spinning — wait for the round to end."); return; } ulong newHostSteam64AfterLeave = GetNewHostSteam64AfterLeave(rouletteTable, header.SenderID); if (rLeaveTableRequest.Forfeit) { rouletteTable.LeaveNoRefund(val); } else { rouletteTable.Leave(val); } BroadcastPlayerLeft(rLeaveTableRequest.TableName, header.SenderID, newHostSteam64AfterLeave, rouletteTable.PlayerCount); Plugin.Log.LogInfo($"[RNNet] Host: player {header.SenderID} left '{rLeaveTableRequest.TableName}'" + (rLeaveTableRequest.Forfeit ? " (walk-away, bets forfeit)" : "") + "."); } private static void OnReadyToggleRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RReadyToggleRequest rReadyToggleRequest)) { return; } RouletteTable rouletteTable = FindTable(rReadyToggleRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if (!((Object)(object)val == (Object)null)) { if (!rouletteTable.IsPlayerAtTable(val)) { SendActionRejected(header.SenderID, rReadyToggleRequest.TableName, "standby", "Not at this table."); return; } if (rReadyToggleRequest.Ready && rouletteTable.GetBetsForPlayer(val).Count == 0) { SendActionRejected(header.SenderID, rReadyToggleRequest.TableName, "standby", "Place at least one bet first."); return; } if (!rouletteTable.TrySetReady(val, rReadyToggleRequest.Ready, out string errorMsg)) { SendActionRejected(header.SenderID, rReadyToggleRequest.TableName, "standby", errorMsg); return; } BroadcastReadyChanged(rReadyToggleRequest.TableName, header.SenderID, rReadyToggleRequest.Ready); Plugin.Log.LogInfo($"[RNNet] Host: player {header.SenderID} " + (rReadyToggleRequest.Ready ? "ready" : "unready") + " at '" + rReadyToggleRequest.TableName + "'."); } } private static void OnSpinRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RSpinRequest rSpinRequest)) { return; } RouletteTable rouletteTable = FindTable(rSpinRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if (!((Object)(object)val == (Object)null)) { if (!rouletteTable.TryStartSpin(val, out string errorMsg)) { SendActionRejected(header.SenderID, rSpinRequest.TableName, "spin", errorMsg); } else { Plugin.Log.LogInfo("[RNNet] Host: spin started at '" + rSpinRequest.TableName + "' " + $"by player {header.SenderID}."); } } } private static void OnPlaceBetRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RPlaceBetRequest rPlaceBetRequest)) { return; } RouletteTable rouletteTable = FindTable(rPlaceBetRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { return; } if (!Enum.IsDefined(typeof(BetType), rPlaceBetRequest.BetType)) { SendActionRejected(header.SenderID, rPlaceBetRequest.TableName, "bet", $"Unknown bet type {rPlaceBetRequest.BetType}."); return; } BetType betType = (BetType)rPlaceBetRequest.BetType; if (!rouletteTable.TryPlaceBet(val, betType, rPlaceBetRequest.BetTarget, rPlaceBetRequest.Amount, out string errorMsg)) { SendActionRejected(header.SenderID, rPlaceBetRequest.TableName, "bet", errorMsg); return; } BroadcastBetPlaced(rPlaceBetRequest.TableName, header.SenderID, betType, rPlaceBetRequest.BetTarget, rPlaceBetRequest.Amount); Plugin.Log.LogInfo($"[RNNet] Host: bet accepted — player {header.SenderID} " + $"{rPlaceBetRequest.Amount} on {RouletteLogic.DescribeBet(betType, rPlaceBetRequest.BetTarget)} " + "at '" + rPlaceBetRequest.TableName + "'."); } private static void OnClearBetsRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHost() || !(packet is RClearBetsRequest rClearBetsRequest)) { return; } RouletteTable rouletteTable = FindTable(rClearBetsRequest.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(header.SenderID); if (!((Object)(object)val == (Object)null)) { if (!rouletteTable.TryClearBets(val, out string errorMsg)) { SendActionRejected(header.SenderID, rClearBetsRequest.TableName, "clearbets", errorMsg); return; } BroadcastBetsCleared(rClearBetsRequest.TableName, header.SenderID); Plugin.Log.LogInfo($"[RNNet] Host: bets cleared for player {header.SenderID} " + "at '" + rClearBetsRequest.TableName + "'."); } } private static void OnPlayerJoined(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RPlayerJoined rPlayerJoined) || BJNetcode.AmHost()) { return; } RouletteTable rouletteTable = FindTable(rPlayerJoined.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(rPlayerJoined.PlayerSteam64); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[RNNet] RPlayerJoined: can't find player {rPlayerJoined.PlayerSteam64}."); return; } rouletteTable.ApplyPlayerJoined(val, rPlayerJoined.StakeAmount, rPlayerJoined.BecameHost); ulong localSteam = BJNetcode.GetLocalSteam64(); if (rPlayerJoined.PlayerSteam64 == localSteam) { ShowHUD("Joined '" + rPlayerJoined.TableName + "' " + $"({rPlayerJoined.PlayerCount}/{8} players). " + "Use /rbet to place bets, /rstandby when done." + (rPlayerJoined.BecameHost ? " You are the table host." : "")); } else { Plugin.Log.LogInfo($"[RNNet] Player {rPlayerJoined.PlayerSteam64} joined " + $"'{rPlayerJoined.TableName}' ({rPlayerJoined.PlayerCount}/{8})."); } } private static void OnPlayerLeft(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RPlayerLeft rPlayerLeft) || BJNetcode.AmHost()) { return; } RouletteTable rouletteTable = FindTable(rPlayerLeft.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(rPlayerLeft.PlayerSteam64); if ((Object)(object)val == (Object)null) { rouletteTable.ApplyPlayerLeftBySteam64(rPlayerLeft.PlayerSteam64, rPlayerLeft.NewHostSteam64); return; } rouletteTable.ApplyPlayerLeft(val, rPlayerLeft.NewHostSteam64); ulong localSteam = BJNetcode.GetLocalSteam64(); if (rPlayerLeft.PlayerSteam64 == localSteam) { ShowHUD("You left the roulette table."); } else { Plugin.Log.LogInfo($"[RNNet] Player {rPlayerLeft.PlayerSteam64} left '{rPlayerLeft.TableName}' " + $"({rPlayerLeft.PlayerCount}/{8})."); } } private static void OnReadyChanged(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RReadyChanged rReadyChanged)) { return; } RouletteTable rouletteTable = FindTable(rReadyChanged.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } if (BJNetcode.AmHost()) { Plugin.Log.LogInfo($"[RNNet] RReadyChanged echo: player {rReadyChanged.PlayerSteam64} " + $"already {rReadyChanged.Ready}."); return; } Player val = BJNetcode.FindPlayerBySteam64(rReadyChanged.PlayerSteam64); if ((Object)(object)val == (Object)null) { return; } rouletteTable.ApplyReadyChanged(val, rReadyChanged.Ready); int readyCount = rouletteTable.GetReadyCount(); int playerCount = rouletteTable.PlayerCount; bool flag = readyCount == playerCount && playerCount > 0; string text = (flag ? $"All {playerCount} players ready!" : $"{readyCount}/{playerCount} players ready."); ulong localSteam = BJNetcode.GetLocalSteam64(); string text2 = (rReadyChanged.Ready ? "ready" : "not ready"); Player mainPlayer = Player._mainPlayer; bool flag2 = (Object)(object)mainPlayer != (Object)null && rouletteTable.IsPlayerAtTable(mainPlayer); if (rReadyChanged.PlayerSteam64 == localSteam) { ShowHUD(rReadyChanged.Ready ? ("You are ready. " + text) : ("You are no longer ready. " + text)); } else if (flag2) { ShowHUD("A player is " + text2 + ". " + text); } if (rReadyChanged.PlayerSteam64 == localSteam || flag2) { try { ChatBehaviour val2 = Object.FindObjectOfType(); if ((Object)(object)val2 != (Object)null) { string text3 = ((rReadyChanged.PlayerSteam64 == localSteam) ? "You are" : "A player is"); Plugin.ShowGameFeed(val2, "[Roulette] " + text3 + " " + text2 + ". " + text + (flag ? " Table host: use /rspin to spin!" : "")); } } catch { } } if (flag && flag2) { ShowHUD($"All {playerCount} players ready! Use /rspin to spin the wheel."); } Plugin.Log.LogInfo($"[RNNet] Player {rReadyChanged.PlayerSteam64} " + (rReadyChanged.Ready ? "ready" : "unready") + " at '" + rReadyChanged.TableName + "'."); } private static void OnBetPlaced(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RBetPlaced rBetPlaced) || BJNetcode.AmHost()) { return; } RouletteTable rouletteTable = FindTable(rBetPlaced.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(rBetPlaced.PlayerSteam64); if (!((Object)(object)val == (Object)null)) { BetType betType = (BetType)rBetPlaced.BetType; rouletteTable.ApplyBetPlaced(val, betType, rBetPlaced.BetTarget, rBetPlaced.Amount); ulong localSteam = BJNetcode.GetLocalSteam64(); if (rBetPlaced.PlayerSteam64 == localSteam) { ShowHUD($"Bet placed: {rBetPlaced.Amount} on " + RouletteLogic.DescribeBet(betType, rBetPlaced.BetTarget) + "."); } Plugin.Log.LogInfo($"[RNNet] Bet mirrored: player {rBetPlaced.PlayerSteam64} " + $"{rBetPlaced.Amount} on {RouletteLogic.DescribeBet(betType, rBetPlaced.BetTarget)}."); } } private static void OnBetsCleared(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RBetsCleared rBetsCleared) || BJNetcode.AmHost()) { return; } RouletteTable rouletteTable = FindTable(rBetsCleared.TableName); if ((Object)(object)rouletteTable == (Object)null) { return; } Player val = BJNetcode.FindPlayerBySteam64(rBetsCleared.PlayerSteam64); if (!((Object)(object)val == (Object)null)) { rouletteTable.ApplyBetsCleared(val); ulong localSteam = BJNetcode.GetLocalSteam64(); if (rBetsCleared.PlayerSteam64 == localSteam) { ShowHUD("All bets cleared and refunded."); } } } private static void OnSpinStarted(PacketHeader header, PacketBase packet) { if (header.SenderIsLobbyOwner && packet is RSpinStarted rSpinStarted && !BJNetcode.AmHost()) { RouletteTable rouletteTable = FindTable(rSpinStarted.TableName); if ((Object)(object)rouletteTable == (Object)null) { Plugin.Log.LogWarning("[RNNet] RSpinStarted for unknown table '" + rSpinStarted.TableName + "'."); return; } ((MonoBehaviour)Plugin.Instance).StartCoroutine(rouletteTable.RunRoundRemote(rSpinStarted.WinningNumber)); Plugin.Log.LogInfo("[RNNet] Spin started at '" + rSpinStarted.TableName + "' — " + $"winning number: {rSpinStarted.WinningNumber}."); } } private static void OnPayoutsResolved(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RPayoutsResolved rPayoutsResolved)) { return; } ulong localSteam = BJNetcode.GetLocalSteam64(); string arg = ((rPayoutsResolved.WinningNumber == 0) ? "Green" : (RouletteLogic.IsRed(rPayoutsResolved.WinningNumber) ? "Red" : "Black")); foreach (RPayoutEntry result in rPayoutsResolved.Results) { if (result.PlayerSteam64 == localSteam) { ShowHUD(string.Format(arg2: (result.NetDelta > 0) ? $"You won {result.NetDelta} Crowns!" : ((result.NetDelta >= 0) ? "Break even." : $"You lost {-result.NetDelta} Crowns."), format: "Result: {0} ({1}). {2}", arg0: rPayoutsResolved.WinningNumber, arg1: arg)); } } Plugin.Log.LogInfo("[RNNet] PayoutsResolved at '" + rPayoutsResolved.TableName + "' — " + $"winning: {rPayoutsResolved.WinningNumber} ({arg}), " + $"{rPayoutsResolved.Results.Count} player(s)."); } private static void OnActionRejected(PacketHeader header, PacketBase packet) { if (!header.SenderIsLobbyOwner || !(packet is RActionRejected rActionRejected)) { return; } ulong localSteam = BJNetcode.GetLocalSteam64(); if (rActionRejected.TargetSteam64 == 0L || rActionRejected.TargetSteam64 == localSteam) { string text = (string.IsNullOrEmpty(rActionRejected.Action) ? "action" : rActionRejected.Action); try { Plugin.ShowHUDError("/" + text + " rejected: " + rActionRejected.Reason); } catch { } Plugin.Log.LogInfo("[RNNet] /" + text + " rejected: " + rActionRejected.Reason); } } private static RouletteTable? FindTable(string tableName) { if (string.IsNullOrEmpty(tableName)) { return null; } RouletteTable[] array = Object.FindObjectsOfType(); RouletteTable[] array2 = array; foreach (RouletteTable rouletteTable in array2) { if (((Object)rouletteTable).name == tableName) { return rouletteTable; } } return null; } private static ulong GetNewHostSteam64AfterLeave(RouletteTable table, ulong leavingSteam64) { return 0uL; } private static void ShowHUD(string message, float duration = 8f) { try { Plugin.ShowHUD(message, duration); } catch { try { Plugin.ShowHUDInfo(message); } catch { } } } private static void Safe(Action act, string label) { try { act(); } catch (Exception ex) { Plugin.Log.LogError("[RNNet] " + label + " failed: " + ex.Message); } } } public class RJoinTableRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int StakeAmount { get; set; } } public class RLeaveTableRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public bool Forfeit { get; set; } = false; } public class RReadyToggleRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public bool Ready { get; set; } } public class RSpinRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class RPlaceBetRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int BetType { get; set; } [JsonProperty] public int BetTarget { get; set; } [JsonProperty] public int Amount { get; set; } } public class RClearBetsRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class RPlayerJoined : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong PlayerSteam64 { get; set; } [JsonProperty] public int StakeAmount { get; set; } [JsonProperty] public bool BecameHost { get; set; } [JsonProperty] public int PlayerCount { get; set; } } public class RPlayerLeft : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong PlayerSteam64 { get; set; } [JsonProperty] public ulong NewHostSteam64 { get; set; } [JsonProperty] public int PlayerCount { get; set; } } public class RReadyChanged : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong PlayerSteam64 { get; set; } [JsonProperty] public bool Ready { get; set; } } public class RBetPlaced : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong PlayerSteam64 { get; set; } [JsonProperty] public int BetType { get; set; } [JsonProperty] public int BetTarget { get; set; } [JsonProperty] public int Amount { get; set; } } public class RBetsCleared : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong PlayerSteam64 { get; set; } } public class RSpinStarted : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int WinningNumber { get; set; } } public class RPayoutsResolved : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int WinningNumber { get; set; } [JsonProperty] public List Results { get; set; } = new List(); } public class RPayoutEntry { [JsonProperty] public ulong PlayerSteam64 { get; set; } [JsonProperty] public int NetDelta { get; set; } } public class RActionRejected : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong TargetSteam64 { get; set; } [JsonProperty] public string Action { get; set; } = string.Empty; [JsonProperty] public string Reason { get; set; } = string.Empty; } } namespace AtlyssCasino.RoomZoneChat.Netcode { public static class RoomZoneChatNetcode { private sealed class RoomZonePreferenceReporter : MonoBehaviour { private void Update() { if (_initialized && !((Object)(object)Player._mainPlayer == (Object)null)) { bool roomZoneChatEnabled = CasinoConfig.RoomZoneChatEnabled; if (!_lastReportedPreference.HasValue || _lastReportedPreference.Value != roomZoneChatEnabled || !(Time.time < _nextPreferenceReportTime)) { PublishLocalPreference(); } } } } [CompilerGenerated] private static class <>O { public static PacketListener <0>__OnRoomZoneChatMessage; public static PacketListener <1>__OnRoomZoneChatPreference; public static PacketListener <2>__OnRoomZoneChatPresence; public static PacketListener <3>__OnRoomZoneChatRequest; } public const string PLUGIN_GUID = "dev.seth.atlysscasino"; private const float PreferenceReportIntervalSeconds = 15f; private static readonly Dictionary _participantEnabled = new Dictionary(); private static readonly Dictionary _reportedRoomBySteam64 = new Dictionary(); private static readonly Dictionary _reportedScopeBySteam64 = new Dictionary(); private static bool _initialized; private static bool? _lastReportedPreference; private static float _nextPreferenceReportTime; public static void Initialize() { //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_003b: Expected O, but got Unknown //IL_0051: 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: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown if (_initialized) { Plugin.Log.LogWarning("[RoomZoneNet] Initialize() called twice - ignoring."); return; } object obj = <>O.<0>__OnRoomZoneChatMessage; if (obj == null) { PacketListener val = OnRoomZoneChatMessage; <>O.<0>__OnRoomZoneChatMessage = val; obj = (object)val; } CodeTalkerNetwork.RegisterListener((PacketListener)obj); object obj2 = <>O.<1>__OnRoomZoneChatPreference; if (obj2 == null) { PacketListener val2 = OnRoomZoneChatPreference; <>O.<1>__OnRoomZoneChatPreference = val2; obj2 = (object)val2; } CodeTalkerNetwork.RegisterListener((PacketListener)obj2); object obj3 = <>O.<2>__OnRoomZoneChatPresence; if (obj3 == null) { PacketListener val3 = OnRoomZoneChatPresence; <>O.<2>__OnRoomZoneChatPresence = val3; obj3 = (object)val3; } CodeTalkerNetwork.RegisterListener((PacketListener)obj3); object obj4 = <>O.<3>__OnRoomZoneChatRequest; if (obj4 == null) { PacketListener val4 = OnRoomZoneChatRequest; <>O.<3>__OnRoomZoneChatRequest = val4; obj4 = (object)val4; } CodeTalkerNetwork.RegisterListener((PacketListener)obj4); _initialized = true; Plugin.Log.LogInfo("[RoomZoneNet] Code Talker listeners registered."); GameObject val5 = new GameObject("AtlyssCasino_RoomZonePreferenceReporter"); Object.DontDestroyOnLoad((Object)(object)val5); ((Object)val5).hideFlags = (HideFlags)61; val5.AddComponent(); } internal static bool IsParticipantEnabled(ulong steam64) { if (steam64 == 0) { return true; } ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L && steam64 == localSteam) { return CasinoConfig.RoomZoneChatEnabled; } if (_participantEnabled.TryGetValue(steam64, out var value)) { return value; } return true; } internal static void PublishLocalPreference() { if (!_initialized) { return; } if (!Plugin.IsHeadlessServer && (Object)(object)Player._mainPlayer == (Object)null) { _nextPreferenceReportTime = Time.time + 15f; return; } bool enabled = CasinoConfig.RoomZoneChatEnabled; ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam == 0) { _nextPreferenceReportTime = Time.time + 15f; return; } _participantEnabled[localSteam] = enabled; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoomZoneChatPreference { Enabled = enabled }); }, "PublishLocalPreference"); _lastReportedPreference = enabled; _nextPreferenceReportTime = Time.time + 15f; } internal static void PublishLocalPresence(string roomName, string scope) { string roomName2 = roomName; string scope2 = scope; if (!_initialized || (!Plugin.IsHeadlessServer && (Object)(object)Player._mainPlayer == (Object)null)) { return; } ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0) { roomName2 = roomName2 ?? string.Empty; scope2 = scope2 ?? string.Empty; _reportedRoomBySteam64[localSteam] = roomName2; _reportedScopeBySteam64[localSteam] = scope2; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoomZoneChatPresence { RoomName = roomName2, Scope = scope2 }); }, "PublishLocalPresence"); } } internal static string GetReportedRoomName(ulong steam64) { if (steam64 == 0) { return string.Empty; } string value; return _reportedRoomBySteam64.TryGetValue(steam64, out value) ? value : string.Empty; } internal static string GetReportedScope(ulong steam64) { if (steam64 == 0) { return string.Empty; } string value; return _reportedScopeBySteam64.TryGetValue(steam64, out value) ? value : string.Empty; } internal static void SendRoomChatRequest(ulong senderSteam64, string rawMessage, string roomName, string scope) { string rawMessage2 = rawMessage; string roomName2 = roomName; string scope2 = scope; if (string.IsNullOrWhiteSpace(rawMessage2) || string.IsNullOrWhiteSpace(roomName2)) { return; } roomName2 = roomName2 ?? string.Empty; scope2 = scope2 ?? string.Empty; if (BJNetcode.AmHost() || Plugin.IsHeadlessServer) { RoomZoneRegistry.BroadcastRoomChatRequest(senderSteam64, rawMessage2, roomName2, scope2); return; } Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoomZoneChatRequest { SenderSteam64 = senderSteam64, RawMessage = rawMessage2, RoomName = roomName2, Scope = scope2 }); }, "SendRoomChatRequest"); } internal static void SendRoomChatTo(ulong targetSteam64, string formattedMessage, ulong senderSteam64, string rawMessage, string roomName, string scope = "", bool requireLocalRoomMatch = false, bool bypassLocalPreference = false) { string formattedMessage2 = formattedMessage; string rawMessage2 = rawMessage; string roomName2 = roomName; string scope2 = scope; if (targetSteam64 == 0 || string.IsNullOrWhiteSpace(formattedMessage2)) { return; } ulong localSteam = BJNetcode.GetLocalSteam64(); if (targetSteam64 == localSteam) { RoomZoneRegistry.DisplayLocalRoomChat(formattedMessage2, !bypassLocalPreference, senderSteam64, rawMessage2, roomName2, scope2, requireLocalRoomMatch); return; } Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new RoomZoneChatMessage { FormattedMessage = formattedMessage2, SenderSteam64 = senderSteam64, RawMessage = rawMessage2, RoomName = roomName2, Scope = scope2, RequireLocalRoomMatch = requireLocalRoomMatch, BypassLocalPreference = bypassLocalPreference }, (CompressionType)0, CompressionLevel.Fastest); }, "SendRoomChatTo"); } private static void OnRoomZoneChatMessage(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatMessage roomZoneChatMessage) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[RoomZoneNet] Ignoring room chat from non-host sender={header.SenderID}."); } else { RoomZoneRegistry.DisplayLocalRoomChat(roomZoneChatMessage.FormattedMessage, !roomZoneChatMessage.BypassLocalPreference, roomZoneChatMessage.SenderSteam64, roomZoneChatMessage.RawMessage, roomZoneChatMessage.RoomName, roomZoneChatMessage.Scope, roomZoneChatMessage.RequireLocalRoomMatch); } } } private static void OnRoomZoneChatPreference(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatPreference roomZoneChatPreference && (BJNetcode.AmHost() || Plugin.IsHeadlessServer) && header.SenderID != 0) { _participantEnabled[header.SenderID] = roomZoneChatPreference.Enabled; } } private static void OnRoomZoneChatPresence(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatPresence roomZoneChatPresence && (BJNetcode.AmHost() || Plugin.IsHeadlessServer) && header.SenderID != 0) { string value = roomZoneChatPresence.RoomName ?? string.Empty; string value2 = roomZoneChatPresence.Scope ?? string.Empty; _reportedRoomBySteam64[header.SenderID] = value; _reportedScopeBySteam64[header.SenderID] = value2; } } private static void OnRoomZoneChatRequest(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatRequest roomZoneChatRequest && (BJNetcode.AmHost() || Plugin.IsHeadlessServer)) { ulong num = ((roomZoneChatRequest.SenderSteam64 != 0L) ? roomZoneChatRequest.SenderSteam64 : header.SenderID); if (num != 0) { RoomZoneRegistry.BroadcastRoomChatRequest(num, roomZoneChatRequest.RawMessage ?? string.Empty, roomZoneChatRequest.RoomName ?? string.Empty, roomZoneChatRequest.Scope ?? string.Empty); } } } private static void Safe(Action act, string label) { try { act(); } catch (Exception ex) { Plugin.Log.LogError("[RoomZoneNet] " + label + " failed: " + ex.Message); } } } public class RoomZoneChatMessage : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string FormattedMessage { get; set; } = string.Empty; [JsonProperty] public ulong SenderSteam64 { get; set; } [JsonProperty] public string RawMessage { get; set; } = string.Empty; [JsonProperty] public string RoomName { get; set; } = string.Empty; [JsonProperty] public string Scope { get; set; } = string.Empty; [JsonProperty] public bool RequireLocalRoomMatch { get; set; } [JsonProperty] public bool BypassLocalPreference { get; set; } } public class RoomZoneChatPreference : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public bool Enabled { get; set; } = true; } public class RoomZoneChatPresence : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string RoomName { get; set; } = string.Empty; [JsonProperty] public string Scope { get; set; } = string.Empty; } public class RoomZoneChatRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public ulong SenderSteam64 { get; set; } [JsonProperty] public string RawMessage { get; set; } = string.Empty; [JsonProperty] public string RoomName { get; set; } = string.Empty; [JsonProperty] public string Scope { get; set; } = string.Empty; } } namespace AtlyssCasino.Jukebox.Netcode { public static class JukeboxNetcode { [CompilerGenerated] private static class <>O { public static PacketListener <0>__OnAdvanceRequest; public static PacketListener <1>__OnStateRequest; public static PacketListener <2>__OnStateSync; } public const string PLUGIN_GUID = "dev.seth.atlysscasino"; private static readonly Dictionary _states = new Dictionary(); private static bool _initialized; public static void Initialize() { //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_0038: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown if (_initialized) { Plugin.Log.LogWarning("[JukeboxNet] Initialize() called twice - ignoring."); return; } object obj = <>O.<0>__OnAdvanceRequest; if (obj == null) { PacketListener val = OnAdvanceRequest; <>O.<0>__OnAdvanceRequest = val; obj = (object)val; } CodeTalkerNetwork.RegisterListener((PacketListener)obj); object obj2 = <>O.<1>__OnStateRequest; if (obj2 == null) { PacketListener val2 = OnStateRequest; <>O.<1>__OnStateRequest = val2; obj2 = (object)val2; } CodeTalkerNetwork.RegisterListener((PacketListener)obj2); object obj3 = <>O.<2>__OnStateSync; if (obj3 == null) { PacketListener val3 = OnStateSync; <>O.<2>__OnStateSync = val3; obj3 = (object)val3; } CodeTalkerNetwork.RegisterListener((PacketListener)obj3); _initialized = true; Plugin.Log.LogInfo("[JukeboxNet] Code Talker listeners registered."); } internal static bool TryGetCachedState(string objectName, out JukeboxWorldState state) { if (string.IsNullOrEmpty(objectName)) { state = default(JukeboxWorldState); return false; } return _states.TryGetValue(objectName, out state); } internal static void SendAdvanceRequest(string objectName, int delta) { string objectName2 = objectName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new JukeboxAdvanceRequest { ObjectName = (objectName2 ?? string.Empty), Delta = delta }); }, "SendAdvanceRequest"); } internal static void SendStateRequest(string objectName) { string objectName2 = objectName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new JukeboxStateRequest { ObjectName = (objectName2 ?? string.Empty) }); }, "SendStateRequest"); } internal static void BroadcastState(JukeboxWorldState state) { CacheState(state); Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new JukeboxStateSync { ObjectName = (state.ObjectName ?? string.Empty), PlaylistStep = state.PlaylistStep, Playing = state.Playing, ElapsedSeconds = state.ElapsedSeconds }); }, "BroadcastState"); } private static void SendStateTo(ulong targetSteam64, JukeboxWorldState state) { CacheState(state); Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new JukeboxStateSync { ObjectName = (state.ObjectName ?? string.Empty), PlaylistStep = state.PlaylistStep, Playing = state.Playing, ElapsedSeconds = state.ElapsedSeconds }, (CompressionType)0, CompressionLevel.Fastest); }, "SendStateTo"); } private static void OnAdvanceRequest(PacketHeader header, PacketBase packet) { if ((BJNetcode.AmHost() || Plugin.IsHeadlessServer) && packet is JukeboxAdvanceRequest jukeboxAdvanceRequest) { CasinoJukebox casinoJukebox = CasinoJukebox.FindByName(jukeboxAdvanceRequest.ObjectName); if ((Object)(object)casinoJukebox == (Object)null) { Plugin.Log.LogWarning("[JukeboxNet] AdvanceRequest for unknown jukebox '" + jukeboxAdvanceRequest.ObjectName + "'."); return; } int delta = ((jukeboxAdvanceRequest.Delta == 0) ? 1 : jukeboxAdvanceRequest.Delta); casinoJukebox.HostAdvance(delta); } } private static void OnStateRequest(PacketHeader header, PacketBase packet) { if ((BJNetcode.AmHost() || Plugin.IsHeadlessServer) && packet is JukeboxStateRequest jukeboxStateRequest) { CasinoJukebox casinoJukebox = CasinoJukebox.FindByName(jukeboxStateRequest.ObjectName); JukeboxWorldState state; if ((Object)(object)casinoJukebox != (Object)null) { SendStateTo(header.SenderID, casinoJukebox.BuildState()); } else if (TryGetCachedState(jukeboxStateRequest.ObjectName, out state)) { SendStateTo(header.SenderID, state); } } } private static void OnStateSync(PacketHeader header, PacketBase packet) { if (!(packet is JukeboxStateSync jukeboxStateSync)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[JukeboxNet] Ignoring state sync from non-host sender={header.SenderID}."); return; } JukeboxWorldState jukeboxWorldState = default(JukeboxWorldState); jukeboxWorldState.ObjectName = jukeboxStateSync.ObjectName ?? string.Empty; jukeboxWorldState.PlaylistStep = jukeboxStateSync.PlaylistStep; jukeboxWorldState.Playing = jukeboxStateSync.Playing; jukeboxWorldState.ElapsedSeconds = jukeboxStateSync.ElapsedSeconds; JukeboxWorldState state = jukeboxWorldState; CacheState(state); if (!BJNetcode.AmHost()) { CasinoJukebox casinoJukebox = CasinoJukebox.FindByName(state.ObjectName); if (!((Object)(object)casinoJukebox == (Object)null)) { casinoJukebox.ApplyRemoteState(state.PlaylistStep, state.Playing, state.ElapsedSeconds); } } } private static void CacheState(JukeboxWorldState state) { if (!string.IsNullOrEmpty(state.ObjectName)) { _states[state.ObjectName] = state; } } private static void Safe(Action act, string label) { try { act(); } catch (Exception ex) { Plugin.Log.LogError("[JukeboxNet] " + label + " failed: " + ex.Message); } } } public class JukeboxAdvanceRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string ObjectName { get; set; } = string.Empty; [JsonProperty] public int Delta { get; set; } = 1; } public class JukeboxStateRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string ObjectName { get; set; } = string.Empty; } public class JukeboxStateSync : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string ObjectName { get; set; } = string.Empty; [JsonProperty] public int PlaylistStep { get; set; } [JsonProperty] public bool Playing { get; set; } [JsonProperty] public float ElapsedSeconds { get; set; } } public struct JukeboxWorldState { public string ObjectName; public int PlaylistStep; public bool Playing; public float ElapsedSeconds; } } namespace AtlyssCasino.Blackjack { public class BlackjackHand { public List Cards { get; } = new List(); public List CardObjects { get; } = new List(); public int Bet { get; set; } = 0; public bool HasStood { get; set; } = false; public bool HasBusted => BestValue > 21; public bool IsNaturalBlackjack => Cards.Count == 2 && BestValue == 21; public int BestValue { get { int num = 0; int num2 = 0; foreach (Card card in Cards) { num += card.MinValue; if (card.IsAce) { num2++; } } while (num2 > 0 && num + 10 <= 21) { num += 10; num2--; } return num; } } public bool IsSoft { get { int num = 0; int num2 = 0; foreach (Card card in Cards) { num += card.MinValue; if (card.IsAce) { num2++; } } return num2 > 0 && num + 10 <= 21; } } public bool IsDone => HasStood || HasBusted; public void Reset() { Cards.Clear(); CardObjects.Clear(); Bet = 0; HasStood = false; } public string Describe() { if (Cards.Count == 0) { return "(empty)"; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < Cards.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(Cards[i].DisplayName()); } stringBuilder.Append(" = "); stringBuilder.Append(BestValue); if (IsNaturalBlackjack) { stringBuilder.Append(" (Blackjack!)"); } else if (HasBusted) { stringBuilder.Append(" BUST"); } else if (IsSoft) { stringBuilder.Append(" (soft)"); } return stringBuilder.ToString(); } } public class BlackjackSeatTrigger : MonoBehaviour { private struct TintTarget { public Material Material; public string ColorProperty; public Color DefaultColor; } private enum StoolState { Vacant, Claimed, ActiveTurn } private const float FALLBACK_RADIUS = 1.5f; private static readonly Color CLAIMED_STOOL_COLOR = new Color(0.05f, 0.05f, 0.05f, 1f); private static readonly Color TURN_STOOL_COLOR = new Color(0.1f, 0.75f, 0.2f, 1f); private static readonly string[] COLOR_PROPS = new string[4] { "_Color", "_BaseColor", "_TintColor", "_MainColor" }; private float _nextInputTime = 0f; private BlackjackTable? _table; private int _seatIndex = -1; private bool _playerNearby = false; private BoxCollider? _collider; private readonly List _tintTargets = new List(); private StoolState _lastVisualState = StoolState.Vacant; private static bool _warnedAboutMissingCollider = false; public void Setup(BlackjackTable table, int seatIndex) { //IL_0098: 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) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) _table = table; _seatIndex = seatIndex; _collider = ((Component)this).GetComponent(); if ((Object)(object)_collider == (Object)null) { if (!_warnedAboutMissingCollider) { _warnedAboutMissingCollider = true; Plugin.Log.LogWarning("[Blackjack] One or more SeatTriggers have no " + $"BoxCollider — falling back to {1.5f}u " + "world radius. Add a BoxCollider with 'Is Trigger' for scale-aware detection. (Logged once globally.)"); } } else { CasinoLog log = Plugin.Log; string text = $"[Blackjack] '{((Object)table).name}' seat {seatIndex}: trigger "; Bounds bounds = ((Collider)_collider).bounds; log.LogDebug(text + $"collider bounds = {((Bounds)(ref bounds)).size}."); } } public void SetStool(Transform stoolRoot) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)stoolRoot == (Object)null) { return; } _tintTargets.Clear(); Renderer[] componentsInChildren = ((Component)stoolRoot).GetComponentsInChildren(true); if (componentsInChildren.Length == 0) { Plugin.Log.LogWarning($"[Blackjack] Stool for seat {_seatIndex}: no Renderers " + "found under '" + ((Object)stoolRoot).name + "'. Color sync disabled."); return; } int num = 0; int num2 = 0; Renderer[] array = componentsInChildren; foreach (Renderer val in array) { Material[] materials = val.materials; foreach (Material val2 in materials) { if ((Object)(object)val2 == (Object)null) { continue; } num++; string text = null; Color val3 = default(Color); string[] cOLOR_PROPS = COLOR_PROPS; foreach (string text2 in cOLOR_PROPS) { if (val2.HasProperty(text2)) { text = text2; val3 = val2.GetColor(text2); break; } } if (text == null) { Plugin.Log.LogInfo($"[Blackjack] Stool seat {_seatIndex}: " + "material '" + ((Object)val2).name + "' (shader '" + ((Object)val2.shader).name + "') has no recognized color property; skipping."); continue; } _tintTargets.Add(new TintTarget { Material = val2, ColorProperty = text, DefaultColor = val3 }); num2++; Plugin.Log.LogDebug($"[Blackjack] Stool seat {_seatIndex}: tinting " + "'" + ((Object)val2).name + "' (shader '" + ((Object)val2.shader).name + "') via '" + text + "' " + $"default=({val3.r:F2}, {val3.g:F2}, {val3.b:F2})."); } } Plugin.Log.LogDebug($"[Blackjack] Stool seat {_seatIndex}: hooked " + $"{num2}/{num} material(s) across " + $"{componentsInChildren.Length} renderer(s)."); ApplyStoolColorForCurrentState(); } private void Update() { if (!Plugin.IsLocalPlayerInCasino()) { if (_playerNearby) { _playerNearby = false; } } else { if ((Object)(object)_table == (Object)null) { return; } Player mainPlayer = Player._mainPlayer; if (!((Object)(object)mainPlayer == (Object)null)) { UpdateStoolVisualIfChanged(); bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; ShowEnterPrompt(mainPlayer); } else if (!flag && _playerNearby) { _playerNearby = false; AutoReleaseIfSeated(mainPlayer); } if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime)) { _nextInputTime = Time.time + 0.5f; HandlePress(mainPlayer); } } } } private void AutoReleaseIfSeated(Player player) { if ((Object)(object)_table == (Object)null) { return; } int seatForPlayer = _table.GetSeatForPlayer(player); if (seatForPlayer == _seatIndex) { string name = ((Object)_table).name; if (BJNetcode.AmHost()) { _table.ReleaseSeat(_seatIndex); int hostSeatIndex = _table.HostSeatIndex; BJNetcode.BroadcastSeatReleased(name, _seatIndex, hostSeatIndex); Plugin.Log.LogInfo($"[Blackjack] '{name}' seat {_seatIndex}: " + "auto-released (host) — local player walked away."); } else { BJNetcode.SendReleaseSeatRequest(name); _table.ApplySeatReleased(_seatIndex, _table.HostSeatIndex); Plugin.Log.LogInfo($"[Blackjack] '{name}' seat {_seatIndex}: " + "auto-released (client) — local player walked away."); } Plugin.ApplyWalkAwayPenalty(); } } 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) < 1.5f; } private void UpdateStoolVisualIfChanged() { if (!((Object)(object)_table == (Object)null)) { StoolState stoolState = ((!_table.IsSeatVacant(_seatIndex)) ? ((_table.CurrentTurnSeat != _seatIndex) ? StoolState.Claimed : StoolState.ActiveTurn) : StoolState.Vacant); if (stoolState != _lastVisualState) { _lastVisualState = stoolState; ApplyStoolColorForCurrentState(); } } } private void ApplyStoolColorForCurrentState() { //IL_0076: 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_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) //IL_0065: 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_008a: Unknown result type (might be due to invalid IL or missing references) if (_tintTargets.Count == 0) { return; } foreach (TintTarget tintTarget in _tintTargets) { if (!((Object)(object)tintTarget.Material == (Object)null)) { Color val = (Color)(_lastVisualState switch { StoolState.ActiveTurn => TURN_STOOL_COLOR, StoolState.Claimed => CLAIMED_STOOL_COLOR, _ => tintTarget.DefaultColor, }); tintTarget.Material.SetColor(tintTarget.ColorProperty, val); } } } private void ShowEnterPrompt(Player player) { int seatForPlayer = _table.GetSeatForPlayer(player); if (seatForPlayer == _seatIndex) { Plugin.ShowHUDInfo($"Seat {_seatIndex + 1} (yours, bet: {Plugin.BlackjackBet}). " + "Use /leave to leave."); return; } if (!_table.IsSeatVacant(_seatIndex)) { Plugin.ShowHUDError($"Seat {_seatIndex + 1} is occupied."); return; } if (seatForPlayer != -1) { Plugin.ShowHUDError($"Already seated at seat {seatForPlayer + 1} at this table. " + "Use /leave first."); return; } var (blackjackTable, num) = FindSeatAtAnyTable(player); if ((Object)(object)blackjackTable != (Object)null && (Object)(object)blackjackTable != (Object)(object)_table) { Plugin.ShowHUDError($"Already seated at '{((Object)blackjackTable).name}' seat {num + 1}. " + "Use /leave first."); return; } if (!Plugin.HasSetBlackjackBet) { Plugin.ShowHUDError("Set a bet first with /blackjackbet ."); return; } int num2 = CountSeatedPlayers(_table); Plugin.ShowHUDInfo($"Press {CasinoInput.InteractPrompt} to claim seat {_seatIndex + 1} " + $"({Plugin.BlackjackBet} Crown bet). " + $"Players: {num2}/{5}."); } private void HandlePress(Player player) { int seatForPlayer = _table.GetSeatForPlayer(player); if (seatForPlayer == _seatIndex) { Plugin.ShowHUDInfo($"Use /leave to leave seat {_seatIndex + 1}."); return; } if (!_table.IsSeatVacant(_seatIndex)) { Plugin.ShowHUDError($"Seat {_seatIndex + 1} is already taken."); return; } if (seatForPlayer != -1) { Plugin.ShowHUDError($"Use /leave to leave seat {seatForPlayer + 1} first."); return; } var (blackjackTable, num) = FindSeatAtAnyTable(player); if ((Object)(object)blackjackTable != (Object)null && (Object)(object)blackjackTable != (Object)(object)_table) { Plugin.ShowHUDError($"Already seated at '{((Object)blackjackTable).name}' seat {num + 1}. " + "Use /leave first."); } else if (!Plugin.HasSetBlackjackBet) { Plugin.ShowHUDError("Set a bet first: /blackjackbet ."); } else if (BJNetcode.AmHost()) { if (!_table.TryClaimSeat(_seatIndex, player)) { Plugin.ShowHUDError($"Could not claim seat {_seatIndex + 1}."); return; } _table.Hands[_seatIndex].Bet = Plugin.BlackjackBet; ulong localSteam = BJNetcode.GetLocalSteam64(); bool becameHost = _table.HostSeatIndex == _seatIndex; BJNetcode.BroadcastSeatClaimed(((Object)_table).name, _seatIndex, localSteam, Plugin.BlackjackBet, becameHost); int num2 = CountSeatedPlayers(_table); Plugin.ShowHUDInfo($"Claimed seat {_seatIndex + 1} with " + $"{Plugin.BlackjackBet} Crown bet. " + $"Players: {num2}/{5}."); } else { BJNetcode.SendClaimSeatRequest(((Object)_table).name, _seatIndex, Plugin.BlackjackBet); Plugin.ShowHUDInfo($"Requesting seat {_seatIndex + 1}..."); } } private static int CountSeatedPlayers(BlackjackTable table) { int num = 0; for (int i = 0; i < 5; i++) { if ((Object)(object)table.GetSeatedPlayer(i) != (Object)null) { num++; } } return num; } private static (BlackjackTable?, int) FindSeatAtAnyTable(Player player) { BlackjackTable[] array = Object.FindObjectsOfType(); BlackjackTable[] array2 = array; foreach (BlackjackTable blackjackTable in array2) { int seatForPlayer = blackjackTable.GetSeatForPlayer(player); if (seatForPlayer >= 0) { return (blackjackTable, seatForPlayer); } } return (null, -1); } } public class BlackjackTable : MonoBehaviour { public struct StartRoundResult { public bool Success; public string Reason; public int FirstTurnSeat; public List KickedSeats; public static StartRoundResult Empty() { StartRoundResult result = default(StartRoundResult); result.KickedSeats = new List(); return result; } } public struct HitResult { public bool Success; public string Reason; public Card? CardDrawn; public int HandValue; public bool Busted; public bool AutoStood; public int NextTurnSeat; public bool RoundResolved; } public struct StandResult { public bool Success; public string Reason; public int NextTurnSeat; public bool RoundResolved; } public struct RoundOutcome { public int SeatIndex; public bool Participated; public int Bet; public int PlayerValue; public int DealerValue; public string Outcome; public int PayoutTotal; public int NetDelta; } public const int MAX_PLAYERS = 5; public const int NO_HOST = -1; public const int NO_TURN = -1; public const int DEALER_SEAT_INDEX = -1; public const int DEALER_STAND_MIN = 17; public const bool DEALER_HITS_SOFT_17 = false; public const float BLACKJACK_PAYOUT_RATIO = 1.5f; public const float REGULAR_WIN_PAYOUT_RATIO = 1f; public const float PLAYER_CARD_SCALE = 0.5f; public const float DEALER_CARD_SCALE = 0.85f; public const float TABLE_SCALE_EXPONENT = 0.6f; public const float BASE_CARD_SPREAD = 1.3f; public const float SPREAD_GAP_FACTOR = 1.6f; public const float STACK_OFFSET_FRACTION = 0.2f; public const float STACK_Z_OFFSET_PER_CARD = 0.015f; public static readonly Vector3 CARD_STANDUP_EULER = new Vector3(90f, 0f, 0f); private const float GHOST_SEAT_POLL_SECONDS = 1f; private const float TURN_WARNING_AT_REMAINING = 10f; private const float TURN_FINAL_WARN_REMAINING = 3f; private readonly Player?[] _claimedSeats = (Player?[])(object)new Player[5]; private readonly bool[] _playersReady = new bool[5]; private readonly bool[] _eligibleThisRound = new bool[5]; private readonly Transform?[] _seatAnchors = (Transform?[])(object)new Transform[5]; private float _nextGhostPollTime = 0f; private float _currentTurnStartTime = 0f; private bool _turnWarningShown = false; private bool _turnFinalWarningShown = false; public Transform? DealerAnchor { get; private set; } public BlackjackHand[] Hands { get; } = new BlackjackHand[5]; public BlackjackHand DealerHand { get; } = new BlackjackHand(); public Deck Deck { get; } = new Deck(); public int HostSeatIndex { get; private set; } = -1; public bool RoundInProgress { get; private set; } = false; public int CurrentTurnSeat { get; private set; } = -1; public int OccupiedSeatCount { get { int num = 0; for (int i = 0; i < 5; i++) { if ((Object)(object)_claimedSeats[i] != (Object)null) { num++; } } return num; } } public bool AllSeatedPlayersReady { get { bool result = false; for (int i = 0; i < 5; i++) { if (!((Object)(object)_claimedSeats[i] == (Object)null) && _eligibleThisRound[i]) { result = true; if (!_playersReady[i]) { return false; } } } return result; } } private void SetCurrentTurnSeat(int seatIndex) { if (CurrentTurnSeat == seatIndex) { return; } CurrentTurnSeat = seatIndex; _currentTurnStartTime = Time.time; _turnWarningShown = false; _turnFinalWarningShown = false; if (BJNetcode.AmHost()) { try { BJNetcode.BroadcastTurnChanged(((Object)this).name, seatIndex); } catch { } } OnTurnLocallyChanged(seatIndex); } public void ApplyTurnChanged(int seatIndex) { RoundInProgress = seatIndex != -1; SetCurrentTurnSeat(seatIndex); } private void OnTurnLocallyChanged(int newSeat) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } int seatForPlayer = GetSeatForPlayer(mainPlayer); if (seatForPlayer < 0 || newSeat == -1) { return; } if (newSeat == seatForPlayer) { try { Plugin.ShowHUDInfo("Your turn — /hit or /stand."); return; } catch { return; } } if (newSeat < 0 || newSeat >= 5) { return; } try { Plugin.ShowHUDInfo($"Seat {newSeat + 1}'s turn."); } catch { } } private void Awake() { for (int i = 0; i < 5; i++) { Hands[i] = new BlackjackHand(); } Deck.Shuffle(); } private void Update() { if (!(Time.time < _nextGhostPollTime)) { _nextGhostPollTime = Time.time + 1f; if (BJNetcode.AmHost()) { ScanForGhostSeats(); ScanForAfkTurn(); } } } private void ScanForAfkTurn() { if (RoundInProgress && CurrentTurnSeat != -1 && CurrentTurnSeat >= 0 && CurrentTurnSeat < 5 && !((Object)(object)_claimedSeats[CurrentTurnSeat] == (Object)null)) { float num = Time.time - _currentTurnStartTime; float blackjackTurnTimeoutSeconds = Plugin.BlackjackTurnTimeoutSeconds; float num2 = blackjackTurnTimeoutSeconds - num; if (num2 <= 0f) { Plugin.Log.LogInfo($"[Blackjack] '{((Object)this).name}' seat {CurrentTurnSeat} " + $"AFK-stood after {blackjackTurnTimeoutSeconds}s of inactivity."); PlayerStand(CurrentTurnSeat); } else if (num2 <= 3f && !_turnFinalWarningShown) { _turnFinalWarningShown = true; ShowTurnWarningTo(CurrentTurnSeat, $"AFK auto-stand in {Mathf.CeilToInt(num2)}s — " + "/hit or /stand!"); } else if (num2 <= 10f && !_turnWarningShown) { _turnWarningShown = true; ShowTurnWarningTo(CurrentTurnSeat, "You'll be auto-stood in " + $"{Mathf.CeilToInt(num2)}s. " + "Use /hit or /stand."); } } } private void ShowTurnWarningTo(int seatIndex, string message) { Player val = _claimedSeats[seatIndex]; if (val == null || (Object)(object)val == (Object)null || !((Object)(object)Player._mainPlayer == (Object)(object)val)) { return; } try { Plugin.ShowHUDError(message); } catch { } } private void ScanForGhostSeats() { for (int i = 0; i < 5; i++) { Player val = _claimedSeats[i]; bool flag = val == null; bool flag2 = (Object)(object)val == (Object)null; if (!flag && flag2) { Plugin.Log.LogInfo($"[Blackjack] Ghost seat detected at '{((Object)this).name}' seat {i} " + "— Player object was destroyed without /leave. Auto-releasing."); ReleaseSeat(i); int hostSeatIndex = HostSeatIndex; BJNetcode.BroadcastSeatReleased(((Object)this).name, i, hostSeatIndex); } } } public void SetSeatAnchor(int seatIndex, Transform anchor) { if (seatIndex >= 0 && seatIndex < 5) { _seatAnchors[seatIndex] = anchor; } } public void SetDealerAnchor(Transform anchor) { DealerAnchor = anchor; } public Transform? GetSeatAnchor(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return null; } return _seatAnchors[seatIndex]; } public Card? DealCard(int seatIndex, bool faceUp) { if (seatIndex < 0 || seatIndex >= 5) { return null; } Transform val = _seatAnchors[seatIndex]; if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[Blackjack] DealCard: seat {seatIndex} has no anchor."); return null; } return HostDrawAndDeal(Hands[seatIndex], val, faceUp, GetEffectiveCardScale(0.5f), $"seat{seatIndex}", seatIndex); } public Card? DealCardToDealer(bool faceUp) { if ((Object)(object)DealerAnchor == (Object)null) { Plugin.Log.LogWarning("[Blackjack] DealCardToDealer: no dealer anchor set."); return null; } return HostDrawAndDeal(DealerHand, DealerAnchor, faceUp, GetEffectiveCardScale(0.85f), "dealer", -1); } private float GetEffectiveCardScale(float baseScale) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) float num = ((Component)this).transform.lossyScale.x; if (num <= 0.001f) { num = 1f; } float num2 = Mathf.Pow(num, 0.6f); return baseScale * num2; } private Card? HostDrawAndDeal(BlackjackHand hand, Transform anchor, bool faceUp, float uniformScale, string label, int seatIndex) { if ((Object)(object)Plugin.AssetsBundle == (Object)null) { Plugin.Log.LogError("[Blackjack] AssetsBundle is null — cannot deal."); return null; } if (Deck.Count < 15) { Deck.ReshuffleAll(); Plugin.Log.LogInfo($"[Blackjack] Deck low — reshuffled ({Deck.Count} cards)."); } Card ownerBlackjackCard = GetOwnerBlackjackCard(seatIndex, hand.Cards.Count); Card card; if (ownerBlackjackCard != null) { Deck.Draw(); card = ownerBlackjackCard; } else { card = Deck.Draw(); } if (!SpawnCardIntoHand(hand, anchor, card, faceUp, uniformScale, label)) { return null; } if (BJNetcode.AmHost()) { int indexInHand = hand.Cards.Count - 1; BJNetcode.BroadcastCardDealt(((Object)this).name, seatIndex, card, faceUp, indexInHand); } return card; } private bool SpawnCardIntoHand(BlackjackHand hand, Transform anchor, Card card, bool faceUp, float uniformScale, string label) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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) //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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: 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) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.AssetsBundle == (Object)null) { Plugin.Log.LogError("[Blackjack] AssetsBundle is null — cannot spawn card."); return false; } string prefabName = card.PrefabName; GameObject val = Plugin.AssetsBundle.LoadAsset(prefabName); if ((Object)(object)val == (Object)null) { Plugin.Log.LogError("[Blackjack] Card prefab not found: " + prefabName); return false; } float num = uniformScale * 1.3f * 1.6f; float num2 = num * 0.2f; int count = hand.Cards.Count; Vector3 val2 = -anchor.right * (num2 * (float)count); Vector3 val3 = anchor.up * (0.015f * uniformScale * (float)count); Vector3 val4 = val2 + val3; Vector3 val5 = anchor.position + val4; Quaternion val6 = anchor.rotation * Quaternion.Euler(CARD_STANDUP_EULER); GameObject val7 = Object.Instantiate(val, val5, val6); ((Object)val7).name = $"BJCard_{label}_{count}_{card.Suit}_{card.Rank}"; Plugin.ScopeToCasinoScene(val7); val7.transform.localScale = Vector3.one * uniformScale; CardVisual cardVisual = val7.GetComponent() ?? val7.AddComponent(); cardVisual.Init(card, faceUp); hand.Cards.Add(card); hand.CardObjects.Add(val7); Plugin.Log.LogInfo("[Blackjack] Dealt " + card.DisplayName() + " to " + label + " " + $"(faceUp={faceUp}, scale={uniformScale:F3}, " + $"perCardOffset={num2:F3}, " + $"handValue={hand.BestValue})."); return true; } public void ClearAllHands() { int num = 0; for (int i = 0; i < 5; i++) { num += DestroyHandCards(Hands[i]); Hands[i].Reset(); } num += DestroyHandCards(DealerHand); DealerHand.Reset(); Plugin.Log.LogInfo($"[Blackjack] Cleared {num} card(s) from '{((Object)this).name}'."); if (BJNetcode.AmHost()) { BJNetcode.BroadcastRoundCleared(((Object)this).name); } } private static int DestroyHandCards(BlackjackHand hand) { int num = 0; foreach (GameObject cardObject in hand.CardObjects) { if ((Object)(object)cardObject != (Object)null) { Object.Destroy((Object)(object)cardObject); num++; } } return num; } public StartRoundResult StartRound() { StartRoundResult result = StartRoundResult.Empty(); if (RoundInProgress) { result.Success = false; result.Reason = "Round already in progress."; return result; } int[] array = new int[5]; for (int i = 0; i < 5; i++) { array[i] = Hands[i].Bet; } ClearAllHands(); for (int j = 0; j < 5; j++) { if (!((Object)(object)_claimedSeats[j] == (Object)null)) { Hands[j].Bet = array[j]; } } int[] array2 = new int[5]; int num = 0; for (int k = 0; k < 5; k++) { if (!((Object)(object)_claimedSeats[k] == (Object)null) && _eligibleThisRound[k] && _playersReady[k]) { array2[num++] = k; } } for (int l = 0; l < num; l++) { int num2 = array2[l]; int bet = Hands[num2].Bet; int seatCrownBalance = GetSeatCrownBalance(num2); if (seatCrownBalance < bet) { Plugin.Log.LogInfo($"[Blackjack] Seat {num2} can't afford bet " + $"({seatCrownBalance} < {bet}) — auto-leaving."); ReleaseSeat(num2); result.KickedSeats.Add(num2); } } int num3 = 0; for (int m = 0; m < 5; m++) { if (!((Object)(object)_claimedSeats[m] == (Object)null) && _eligibleThisRound[m] && _playersReady[m]) { num3++; } } if (num3 == 0) { result.Success = false; result.Reason = "No players left after affordability check."; return result; } for (int n = 0; n < 5; n++) { if (!((Object)(object)_claimedSeats[n] == (Object)null) && _eligibleThisRound[n] && _playersReady[n]) { int bet2 = Hands[n].Bet; DeductFromSeat(n, bet2); } } RoundInProgress = true; for (int num4 = 0; num4 < 2; num4++) { for (int num5 = 0; num5 < 5; num5++) { if (!((Object)(object)_claimedSeats[num5] == (Object)null) && _eligibleThisRound[num5] && _playersReady[num5]) { DealCard(num5, faceUp: true); } } bool faceUp = num4 == 0; DealCardToDealer(faceUp); } SetCurrentTurnSeat(FindFirstActiveSeat()); if (CurrentTurnSeat == -1) { Plugin.Log.LogInfo("[Blackjack] All players have natural BJ — dealer runs immediately."); ResolveRound(out RoundOutcome[] _); } result.Success = true; result.FirstTurnSeat = CurrentTurnSeat; return result; } public HitResult PlayerHit(int seatIndex) { HitResult result = default(HitResult); if (!RoundInProgress) { result.Success = false; result.Reason = "No round in progress."; return result; } if (seatIndex != CurrentTurnSeat) { result.Success = false; result.Reason = ((CurrentTurnSeat == -1) ? "No active turn." : $"Not your turn — it's seat {CurrentTurnSeat + 1}'s turn."); return result; } if (seatIndex < 0 || seatIndex >= 5) { return result; } if (!_eligibleThisRound[seatIndex]) { result.Success = false; result.Reason = "Not eligible this round."; return result; } Card card = DealCard(seatIndex, faceUp: true); if (card == null) { result.Success = false; result.Reason = "Deal failed."; return result; } BlackjackHand blackjackHand = Hands[seatIndex]; result.Success = true; result.CardDrawn = card; result.HandValue = blackjackHand.BestValue; result.Busted = blackjackHand.HasBusted; result.AutoStood = blackjackHand.HasBusted || blackjackHand.BestValue >= 21; if (result.AutoStood) { blackjackHand.HasStood = true; AdvanceTurn(); result.NextTurnSeat = CurrentTurnSeat; result.RoundResolved = !RoundInProgress; } return result; } public StandResult PlayerStand(int seatIndex) { StandResult result = default(StandResult); if (!RoundInProgress) { result.Success = false; result.Reason = "No round in progress."; return result; } if (seatIndex != CurrentTurnSeat) { result.Success = false; result.Reason = ((CurrentTurnSeat == -1) ? "No active turn." : $"Not your turn — it's seat {CurrentTurnSeat + 1}'s turn."); return result; } if (seatIndex < 0 || seatIndex >= 5) { return result; } Hands[seatIndex].HasStood = true; AdvanceTurn(); result.Success = true; result.NextTurnSeat = CurrentTurnSeat; result.RoundResolved = !RoundInProgress; return result; } private void AdvanceTurn() { SetCurrentTurnSeat(FindFirstActiveSeat()); if (CurrentTurnSeat == -1) { ResolveRound(out RoundOutcome[] _); } } private int FindFirstActiveSeat() { for (int i = 0; i < 5; i++) { if (!((Object)(object)_claimedSeats[i] == (Object)null) && _eligibleThisRound[i] && _playersReady[i]) { BlackjackHand blackjackHand = Hands[i]; if (!blackjackHand.HasStood && !blackjackHand.HasBusted && !blackjackHand.IsNaturalBlackjack && blackjackHand.BestValue < 21) { return i; } } } return -1; } public void ResolveRound(out RoundOutcome[] outcomes) { outcomes = new RoundOutcome[5]; if (!RoundInProgress) { Plugin.Log.LogWarning("[Blackjack] ResolveRound called but round not in progress."); return; } RunDealerTurn(); int bestValue = DealerHand.BestValue; bool hasBusted = DealerHand.HasBusted; bool isNaturalBlackjack = DealerHand.IsNaturalBlackjack; float blackjackPayoutMultiplier = Plugin.BlackjackPayoutMultiplier; for (int i = 0; i < 5; i++) { outcomes[i] = new RoundOutcome { SeatIndex = i }; if ((Object)(object)_claimedSeats[i] == (Object)null || !_eligibleThisRound[i] || !_playersReady[i]) { continue; } BlackjackHand blackjackHand = Hands[i]; if (blackjackHand.Cards.Count != 0) { int bet = blackjackHand.Bet; int bestValue2 = blackjackHand.BestValue; bool hasBusted2 = blackjackHand.HasBusted; bool isNaturalBlackjack2 = blackjackHand.IsNaturalBlackjack; int num; int num2; string text; if (Plugin.ShouldRigOwnerLuck(GetSeatSteam64(i)) && isNaturalBlackjack2) { num = bet; num2 = Mathf.RoundToInt((float)bet * 1.5f * blackjackPayoutMultiplier); text = "BLACKJACK"; } else if (isNaturalBlackjack2 && isNaturalBlackjack) { num = bet; num2 = 0; text = "PUSH"; } else if (isNaturalBlackjack2) { num = bet; num2 = Mathf.RoundToInt((float)bet * 1.5f * blackjackPayoutMultiplier); text = "BLACKJACK"; } else if (isNaturalBlackjack) { num = 0; num2 = 0; text = "LOSE"; } else if (hasBusted2) { num = 0; num2 = 0; text = "BUST"; } else if (hasBusted) { num = bet; num2 = Mathf.RoundToInt((float)bet * 1f * blackjackPayoutMultiplier); text = "WIN"; } else if (bestValue2 > bestValue) { num = bet; num2 = Mathf.RoundToInt((float)bet * 1f * blackjackPayoutMultiplier); text = "WIN"; } else if (bestValue2 < bestValue) { num = 0; num2 = 0; text = "LOSE"; } else { num = bet; num2 = 0; text = "PUSH"; } int num3 = num + num2; if (num3 > 0) { CreditToSeat(i, num3); } outcomes[i].Participated = true; outcomes[i].Bet = bet; outcomes[i].PlayerValue = bestValue2; outcomes[i].DealerValue = bestValue; outcomes[i].Outcome = text; outcomes[i].PayoutTotal = num3; outcomes[i].NetDelta = num3 - bet; Plugin.Log.LogInfo($"[Blackjack] Seat {i} resolved: {text} " + $"(player {bestValue2} vs dealer {bestValue}, " + $"bet {bet}, payout {num3}, net {outcomes[i].NetDelta}, " + $"config {blackjackPayoutMultiplier:F2}x)"); } } RoundInProgress = false; SetCurrentTurnSeat(-1); MarkAllSeatedEligibleForNextRound(); } public int RunDealerTurn() { for (int i = 0; i < DealerHand.CardObjects.Count; i++) { GameObject val = DealerHand.CardObjects[i]; if ((Object)(object)val == (Object)null) { continue; } CardVisual component = val.GetComponent(); if (!((Object)(object)component == (Object)null) && !component.IsFaceUp) { component.SetFaceUp(faceUp: true); if (BJNetcode.AmHost()) { BJNetcode.BroadcastCardFlipped(((Object)this).name, -1, i, faceUp: true); } } } Plugin.Log.LogInfo("[Blackjack] Dealer reveals: " + DealerHand.Describe()); int num = 12; while (DealerHand.BestValue < 17 && !DealerHand.HasBusted && num-- > 0) { Card card = DealCardToDealer(faceUp: true); if (card == null) { break; } } Plugin.Log.LogInfo("[Blackjack] Dealer final: " + DealerHand.Describe()); return DealerHand.BestValue; } private int GetSeatCrownBalance(int seatIndex) { Player val = _claimedSeats[seatIndex]; if ((Object)(object)val == (Object)null) { return 0; } PlayerInventory component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { return 0; } return component._heldCurrency; } private Card? GetOwnerBlackjackCard(int seatIndex, int indexInHand) { if (seatIndex < 0 || seatIndex >= 5) { return null; } Player val = _claimedSeats[seatIndex]; if ((Object)(object)val == (Object)null) { return null; } if (!Plugin.ShouldRigOwnerLuck(GetSteam64(val))) { return null; } return indexInHand switch { 0 => new Card(Suit.Spades, Rank.Ace), 1 => new Card(Suit.Spades, Rank.King), _ => null, }; } private ulong GetSeatSteam64(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return 0uL; } Player val = _claimedSeats[seatIndex]; return ((Object)(object)val == (Object)null) ? 0 : GetSteam64(val); } private static ulong GetSteam64(Player player) { if ((Object)(object)player == (Object)null) { return 0uL; } if (ulong.TryParse(player.Network_steamID, out var result)) { return result; } return BJNetcode.GetLocalSteam64(); } private void DeductFromSeat(int seatIndex, int amount) { Player val = _claimedSeats[seatIndex]; if ((Object)(object)val == (Object)null) { return; } PlayerInventory component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { return; } if (BJNetcode.AmHost()) { int heldCurrency = component._heldCurrency; int num = heldCurrency - amount; if (num < 0) { num = 0; } component.Network_heldCurrency = num; Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} bet -{amount} Crowns " + $"(direct SyncVar write, {heldCurrency} -> {num})."); } else { component.Cmd_SubtractCurrency(amount); Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} bet -{amount} Crowns (via Cmd)."); } } private void CreditToSeat(int seatIndex, int amount) { Player val = _claimedSeats[seatIndex]; if ((Object)(object)val == (Object)null) { return; } PlayerInventory component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null)) { if (BJNetcode.AmHost()) { int heldCurrency = component._heldCurrency; int num2 = (component.Network_heldCurrency = heldCurrency + amount); Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} paid +{amount} Crowns " + $"(direct SyncVar write, {heldCurrency} -> {num2})."); } else { component.Cmd_AddCurrency(amount); Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} paid +{amount} Crowns (via Cmd)."); } } } public string DescribePlayerHandStatus(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return ""; } BlackjackHand blackjackHand = Hands[seatIndex]; if (blackjackHand.Cards.Count == 0) { return ""; } string arg = ""; if (blackjackHand.IsNaturalBlackjack) { arg = " BLACKJACK"; } else if (blackjackHand.HasBusted) { arg = " BUST"; } return $"Your hand: {blackjackHand.BestValue}{arg}"; } public string DescribePlayerHandFinal(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return ""; } BlackjackHand blackjackHand = Hands[seatIndex]; if (blackjackHand.Cards.Count == 0) { return ""; } int bestValue = blackjackHand.BestValue; int bestValue2 = DealerHand.BestValue; bool hasBusted = DealerHand.HasBusted; bool hasBusted2 = blackjackHand.HasBusted; bool isNaturalBlackjack = blackjackHand.IsNaturalBlackjack; bool isNaturalBlackjack2 = DealerHand.IsNaturalBlackjack; string arg = ((isNaturalBlackjack && isNaturalBlackjack2) ? "PUSH" : (isNaturalBlackjack ? "BLACKJACK" : (isNaturalBlackjack2 ? "LOSE" : (hasBusted2 ? "BUST" : (hasBusted ? "WIN" : ((bestValue > bestValue2) ? "WIN" : ((bestValue >= bestValue2) ? "PUSH" : "LOSE"))))))); return $"Your hand: {bestValue} — {arg}"; } public string DescribeAllHands(Player? localPlayer) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Dealer: "); stringBuilder.Append(DescribeDealerForView()); int num = (((Object)(object)localPlayer == (Object)null) ? (-1) : GetSeatForPlayer(localPlayer)); for (int i = 0; i < 5; i++) { if (!((Object)(object)_claimedSeats[i] == (Object)null) && Hands[i].Cards.Count != 0) { stringBuilder.Append('\n'); stringBuilder.Append((i == num) ? "You: " : $"Seat {i + 1}: "); stringBuilder.Append(Hands[i].Describe()); } } return stringBuilder.ToString(); } public string DescribeDealerForView() { if (DealerHand.Cards.Count == 0) { return "(empty)"; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; int num2 = 0; bool flag = false; for (int i = 0; i < DealerHand.Cards.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } bool flag2 = true; if (i < DealerHand.CardObjects.Count && (Object)(object)DealerHand.CardObjects[i] != (Object)null) { CardVisual component = DealerHand.CardObjects[i].GetComponent(); if ((Object)(object)component != (Object)null) { flag2 = component.IsFaceUp; } } if (flag2) { Card card = DealerHand.Cards[i]; stringBuilder.Append(card.DisplayName()); num += card.MinValue; if (card.IsAce) { num2++; } } else { stringBuilder.Append("??"); flag = true; } } stringBuilder.Append(" = "); if (flag) { int num3 = num; while (num2 > 0 && num3 + 10 <= 21) { num3 += 10; num2--; } stringBuilder.Append(num3); stringBuilder.Append(" + ??"); } else { stringBuilder.Append(DealerHand.BestValue); if (DealerHand.IsNaturalBlackjack) { stringBuilder.Append(" (Blackjack!)"); } else if (DealerHand.HasBusted) { stringBuilder.Append(" BUST"); } } return stringBuilder.ToString(); } public string DescribeDealerValueForView() { if (DealerHand.Cards.Count == 0) { return ""; } int num = 0; int num2 = 0; bool flag = false; for (int i = 0; i < DealerHand.Cards.Count; i++) { bool flag2 = true; if (i < DealerHand.CardObjects.Count && (Object)(object)DealerHand.CardObjects[i] != (Object)null) { CardVisual component = DealerHand.CardObjects[i].GetComponent(); if ((Object)(object)component != (Object)null) { flag2 = component.IsFaceUp; } } if (flag2) { Card card = DealerHand.Cards[i]; num += card.MinValue; if (card.IsAce) { num2++; } } else { flag = true; } } if (flag) { while (num2 > 0 && num + 10 <= 21) { num += 10; num2--; } return (num > 0) ? num.ToString() : "??"; } string text = DealerHand.BestValue.ToString(); if (DealerHand.IsNaturalBlackjack) { text += " Blackjack"; } else if (DealerHand.HasBusted) { text += " BUST"; } return text; } public bool IsSeatVacant(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return false; } return (Object)(object)_claimedSeats[seatIndex] == (Object)null; } public Player? GetSeatedPlayer(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return null; } return _claimedSeats[seatIndex]; } public int GetSeatForPlayer(Player player) { if ((Object)(object)player == (Object)null) { return -1; } for (int i = 0; i < 5; i++) { if ((Object)(object)_claimedSeats[i] == (Object)(object)player) { return i; } } return -1; } public bool TryClaimSeat(int seatIndex, Player player) { if ((Object)(object)player == (Object)null) { return false; } if (seatIndex < 0 || seatIndex >= 5) { return false; } if ((Object)(object)_claimedSeats[seatIndex] != (Object)null) { return false; } if (GetSeatForPlayer(player) != -1) { return false; } _claimedSeats[seatIndex] = player; _playersReady[seatIndex] = false; _eligibleThisRound[seatIndex] = !RoundInProgress; if (HostSeatIndex == -1) { HostSeatIndex = seatIndex; Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} claimed at '{((Object)this).name}' " + $"(host assigned, eligible={_eligibleThisRound[seatIndex]})."); } else { Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} claimed at '{((Object)this).name}' " + $"(eligible={_eligibleThisRound[seatIndex]})."); } return true; } public Player? ReleaseSeat(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return null; } Player val = _claimedSeats[seatIndex]; _claimedSeats[seatIndex] = null; _playersReady[seatIndex] = false; _eligibleThisRound[seatIndex] = false; DestroyHandCards(Hands[seatIndex]); Hands[seatIndex].Reset(); if ((Object)(object)val == (Object)null) { return null; } Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} released at '{((Object)this).name}'."); if (HostSeatIndex == seatIndex) { ReassignHost(); } if (RoundInProgress && CurrentTurnSeat == seatIndex) { AdvanceTurn(); } return val; } public bool ApplySeatClaimed(int seatIndex, Player player, int bet, bool becameHost) { if ((Object)(object)player == (Object)null) { return false; } if (seatIndex < 0 || seatIndex >= 5) { return false; } if ((Object)(object)_claimedSeats[seatIndex] == (Object)(object)player) { Hands[seatIndex].Bet = bet; return true; } if ((Object)(object)_claimedSeats[seatIndex] != (Object)null) { Plugin.Log.LogWarning($"[Blackjack] ApplySeatClaimed: overwriting seat {seatIndex} " + "at '" + ((Object)this).name + "' (had " + ((Object)_claimedSeats[seatIndex]).name + ", now " + ((Object)player).name + ")."); } int seatForPlayer = GetSeatForPlayer(player); if (seatForPlayer >= 0 && seatForPlayer != seatIndex) { _claimedSeats[seatForPlayer] = null; _playersReady[seatForPlayer] = false; _eligibleThisRound[seatForPlayer] = false; Hands[seatForPlayer].Reset(); } _claimedSeats[seatIndex] = player; _playersReady[seatIndex] = false; _eligibleThisRound[seatIndex] = !RoundInProgress; Hands[seatIndex].Bet = bet; if (becameHost || HostSeatIndex == -1) { HostSeatIndex = seatIndex; } Plugin.Log.LogInfo($"[Blackjack] ApplySeatClaimed: seat {seatIndex} at '{((Object)this).name}' " + $"-> {((Object)player).name} (bet {bet}, becameHost={becameHost})."); return true; } public void ApplySeatReleased(int seatIndex, int newHostSeatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return; } if ((Object)(object)_claimedSeats[seatIndex] == (Object)null) { HostSeatIndex = newHostSeatIndex; return; } _claimedSeats[seatIndex] = null; _playersReady[seatIndex] = false; _eligibleThisRound[seatIndex] = false; DestroyHandCards(Hands[seatIndex]); Hands[seatIndex].Reset(); HostSeatIndex = newHostSeatIndex; if (RoundInProgress && CurrentTurnSeat == seatIndex) { SetCurrentTurnSeat(-1); } Plugin.Log.LogInfo($"[Blackjack] ApplySeatReleased: seat {seatIndex} at '{((Object)this).name}' " + $"(newHost={newHostSeatIndex})."); } public void ApplyCardDealt(int seatIndex, Card card, bool faceUp, int expectedIndexInHand, string expectedPrefabName) { if (card == null) { Plugin.Log.LogError("[Blackjack] ApplyCardDealt: null card at '" + ((Object)this).name + "'. Card NOT spawned."); return; } if (card.PrefabName != expectedPrefabName) { Plugin.Log.LogError("[Blackjack] ApplyCardDealt: prefab name mismatch at '" + ((Object)this).name + "' — packet='" + expectedPrefabName + "', derived='" + card.PrefabName + "'. Card NOT spawned (possible packet corruption or mod version skew)."); return; } BlackjackHand blackjackHand; Transform val; float effectiveCardScale; string text; if (seatIndex == -1) { blackjackHand = DealerHand; val = DealerAnchor; effectiveCardScale = GetEffectiveCardScale(0.85f); text = "dealer"; } else { if (seatIndex < 0 || seatIndex >= 5) { Plugin.Log.LogError($"[Blackjack] ApplyCardDealt: invalid seat index {seatIndex} at '{((Object)this).name}'. " + "Card NOT spawned."); return; } blackjackHand = Hands[seatIndex]; val = _seatAnchors[seatIndex]; effectiveCardScale = GetEffectiveCardScale(0.5f); text = $"seat{seatIndex}"; } if ((Object)(object)val == (Object)null) { Plugin.Log.LogError("[Blackjack] ApplyCardDealt: no anchor for " + text + " at '" + ((Object)this).name + "'. Card NOT spawned."); return; } int count = blackjackHand.Cards.Count; if (count != expectedIndexInHand) { Plugin.Log.LogError("[Blackjack] ApplyCardDealt: index mismatch on " + text + " at '" + ((Object)this).name + "' — " + $"packet expected index {expectedIndexInHand}, local hand has {count}. " + "Card NOT spawned (desync detected)."); } else { SpawnCardIntoHand(blackjackHand, val, card, faceUp, effectiveCardScale, text); } } public void ApplyCardFlipped(int seatIndex, int indexInHand, bool faceUp) { BlackjackHand blackjackHand; string arg; if (seatIndex == -1) { blackjackHand = DealerHand; arg = "dealer"; } else { if (seatIndex < 0 || seatIndex >= 5) { Plugin.Log.LogError($"[Blackjack] ApplyCardFlipped: invalid seat index {seatIndex} at '{((Object)this).name}'."); return; } blackjackHand = Hands[seatIndex]; arg = $"seat{seatIndex}"; } if (indexInHand < 0 || indexInHand >= blackjackHand.CardObjects.Count) { Plugin.Log.LogError($"[Blackjack] ApplyCardFlipped: index {indexInHand} out of range on " + $"{arg} at '{((Object)this).name}' (hand has {blackjackHand.CardObjects.Count} cards)."); return; } GameObject val = blackjackHand.CardObjects[indexInHand]; if (!((Object)(object)val == (Object)null)) { CardVisual component = val.GetComponent(); if (!((Object)(object)component == (Object)null)) { component.SetFaceUp(faceUp); Plugin.Log.LogInfo($"[Blackjack] ApplyCardFlipped: {arg} card {indexInHand} at '{((Object)this).name}' " + $"-> faceUp={faceUp}."); } } } public void ApplyRoundCleared() { RoundInProgress = false; SetCurrentTurnSeat(-1); int[] array = new int[5]; for (int i = 0; i < 5; i++) { array[i] = Hands[i].Bet; } int num = 0; for (int j = 0; j < 5; j++) { num += DestroyHandCards(Hands[j]); Hands[j].Reset(); Hands[j].Bet = array[j]; } num += DestroyHandCards(DealerHand); DealerHand.Reset(); Plugin.Log.LogInfo($"[Blackjack] ApplyRoundCleared: cleared {num} card(s) from '{((Object)this).name}'."); } private void ReassignHost() { int num = 0; int[] array = new int[5]; for (int i = 0; i < 5; i++) { if ((Object)(object)_claimedSeats[i] != (Object)null) { array[num++] = i; } } if (num == 0) { HostSeatIndex = -1; Plugin.Log.LogInfo("[Blackjack] Table '" + ((Object)this).name + "' empty — host cleared."); ResetTableToEmpty(); } else { int num3 = (HostSeatIndex = array[Random.Range(0, num)]); Plugin.Log.LogInfo($"[Blackjack] Host reassigned to seat {num3} at '{((Object)this).name}'."); } } private void ResetTableToEmpty() { ClearAllHands(); RoundInProgress = false; SetCurrentTurnSeat(-1); Deck.ReshuffleAll(); Plugin.Log.LogInfo("[Blackjack] Table '" + ((Object)this).name + "' fully reset — deck reshuffled, round state cleared. Next player to claim becomes the new host."); } public bool IsHost(int seatIndex) { return seatIndex >= 0 && seatIndex < 5 && HostSeatIndex == seatIndex; } public Player? GetHostPlayer() { if (HostSeatIndex == -1) { return null; } return _claimedSeats[HostSeatIndex]; } public bool IsEligibleThisRound(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return false; } if ((Object)(object)_claimedSeats[seatIndex] == (Object)null) { return false; } return _eligibleThisRound[seatIndex]; } public void MarkAllSeatedEligibleForNextRound() { for (int i = 0; i < 5; i++) { _eligibleThisRound[i] = (Object)(object)_claimedSeats[i] != (Object)null; } } public bool IsPlayerReady(int seatIndex) { if (seatIndex < 0 || seatIndex >= 5) { return false; } return _playersReady[seatIndex]; } public void SetPlayerReady(int seatIndex, bool ready) { if (seatIndex >= 0 && seatIndex < 5 && !((Object)(object)_claimedSeats[seatIndex] == (Object)null)) { _playersReady[seatIndex] = ready; } } } public enum Suit { Clubs, Diamonds, Hearts, Spades } public enum Rank { Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King } public class Card { public Suit Suit { get; } public Rank Rank { get; } public int MinValue { get { switch (Rank) { case Rank.Ace: return 1; case Rank.Two: return 2; case Rank.Three: return 3; case Rank.Four: return 4; case Rank.Five: return 5; case Rank.Six: return 6; case Rank.Seven: return 7; case Rank.Eight: return 8; case Rank.Nine: return 9; case Rank.Ten: case Rank.Jack: case Rank.Queen: case Rank.King: return 10; default: return 0; } } } public int MaxValue => (Rank == Rank.Ace) ? 11 : MinValue; public bool IsAce => Rank == Rank.Ace; public string PrefabName => "Deck06_" + SuitCode() + "_" + RankCode(); public Card(Suit suit, Rank rank) { Suit = suit; Rank = rank; } private string SuitCode() { return Suit switch { Suit.Clubs => "Club", Suit.Diamonds => "Diamond", Suit.Hearts => "Heart", Suit.Spades => "Spade", _ => Suit.ToString(), }; } private string RankCode() { return Rank switch { Rank.Ace => "A", Rank.Two => "2", Rank.Three => "3", Rank.Four => "4", Rank.Five => "5", Rank.Six => "6", Rank.Seven => "7", Rank.Eight => "8", Rank.Nine => "9", Rank.Ten => "10", Rank.Jack => "J", Rank.Queen => "Q", Rank.King => "K", _ => "?", }; } public string DisplayName() { return $"{Rank} of {Suit}"; } public override string ToString() { return DisplayName(); } } public class CardVisual : MonoBehaviour { private const float CARD_THICKNESS = 0.001f; public Card? Card; private SpriteRenderer? _frontRenderer; private SpriteRenderer? _backRenderer; private SpriteRenderer? _backMirrorRenderer; private Transform? _frontT; private Transform? _backT; private Transform? _backMirrorT; private bool _faceUp = true; public bool IsFaceUp => _faceUp; public void Init(Card? card, bool faceUp) { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) Card = card; _faceUp = faceUp; _frontT = FindChildCaseInsensitive(((Component)this).transform, "Front"); if ((Object)(object)_frontT == (Object)null) { Plugin.Log.LogWarning("[Blackjack] Card '" + ((Object)this).name + "' has no 'Front' child! Children: " + ListChildren()); } else { _frontRenderer = ((Component)_frontT).GetComponent(); } _backT = FindChildCaseInsensitive(((Component)this).transform, "Back_D6") ?? FindChildCaseInsensitive(((Component)this).transform, "Back"); if ((Object)(object)_backT == (Object)null) { Plugin.Log.LogWarning("[Blackjack] Card '" + ((Object)this).name + "' has no 'Back_D6' or 'Back' child! Children: " + ListChildren()); } else { _backRenderer = ((Component)_backT).GetComponent(); } if ((Object)(object)_frontT != (Object)null) { _frontT.localPosition = Vector3.zero; _frontT.localRotation = Quaternion.identity; } if ((Object)(object)_backT != (Object)null) { _backT.localPosition = new Vector3(0f, 0f, -0.001f); _backT.localRotation = Quaternion.Euler(0f, 180f, 0f); } if ((Object)(object)_frontRenderer != (Object)null) { _frontRenderer.flipX = true; } BuildBackMirrorIfNeeded(); ApplyFaceState(); } public void SetFaceUp(bool faceUp) { _faceUp = faceUp; ApplyFaceState(); } private void BuildBackMirrorIfNeeded() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_0073: 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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_backMirrorT != (Object)null) && !((Object)(object)_backRenderer == (Object)null) && !((Object)(object)_backT == (Object)null)) { GameObject val = new GameObject("Back_Mirror"); val.transform.SetParent(((Component)this).transform, false); val.transform.localPosition = new Vector3(0f, 0f, 0.001f); val.transform.localRotation = Quaternion.identity; _backMirrorRenderer = val.AddComponent(); _backMirrorRenderer.sprite = _backRenderer.sprite; _backMirrorRenderer.color = _backRenderer.color; _backMirrorRenderer.flipX = _backRenderer.flipX; _backMirrorRenderer.flipY = _backRenderer.flipY; ((Renderer)_backMirrorRenderer).sortingLayerID = ((Renderer)_backRenderer).sortingLayerID; ((Renderer)_backMirrorRenderer).sortingOrder = ((Renderer)_backRenderer).sortingOrder; ((Renderer)_backMirrorRenderer).material = ((Renderer)_backRenderer).sharedMaterial; val.transform.localScale = _backT.localScale; _backMirrorT = val.transform; } } private void ApplyFaceState() { if ((Object)(object)_frontRenderer != (Object)null) { ((Renderer)_frontRenderer).enabled = _faceUp; } if ((Object)(object)_backRenderer != (Object)null) { ((Renderer)_backRenderer).enabled = true; } if ((Object)(object)_backMirrorRenderer != (Object)null) { ((Renderer)_backMirrorRenderer).enabled = !_faceUp; } } private static Transform? FindChildCaseInsensitive(Transform parent, string name) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown string b = name.Trim(); foreach (Transform item in parent) { Transform val = item; string a = ((Object)val).name.Trim(); if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private string ListChildren() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown List list = new List(); foreach (Transform item in ((Component)this).transform) { Transform val = item; list.Add(((Object)val).name); } return (list.Count > 0) ? string.Join(", ", list) : "(none)"; } } public class Deck { private readonly Random _rng; private readonly List _cards; public int Count => _cards.Count; public bool IsEmpty => _cards.Count == 0; public Deck() { _rng = new Random(); _cards = new List(52); BuildFresh52(); } public Deck(int seed) { _rng = new Random(seed); _cards = new List(52); BuildFresh52(); } public void ReshuffleAll() { _cards.Clear(); BuildFresh52(); Shuffle(); } public void Shuffle() { for (int num = _cards.Count - 1; num > 0; num--) { int num2 = _rng.Next(0, num + 1); if (num != num2) { Card value = _cards[num]; _cards[num] = _cards[num2]; _cards[num2] = value; } } } public Card Draw() { if (_cards.Count == 0) { throw new InvalidOperationException("Cannot draw from an empty deck."); } int index = _cards.Count - 1; Card result = _cards[index]; _cards.RemoveAt(index); return result; } public Card? Peek() { if (_cards.Count == 0) { return null; } return _cards[_cards.Count - 1]; } private void BuildFresh52() { foreach (Suit value in Enum.GetValues(typeof(Suit))) { foreach (Rank value2 in Enum.GetValues(typeof(Rank))) { _cards.Add(new Card(value, value2)); } } } } } namespace AtlyssCasino.Blackjack.Netcode { public static class BJNetcode { public static class SlotMachineLocks { private const float SLOT_LOCK_DURATION_SEC = 6f; private static readonly Dictionary _lockedUntil = new Dictionary(); public static void Lock(string machineName) { if (!string.IsNullOrEmpty(machineName)) { _lockedUntil[machineName] = Time.time + 6f; } } public static bool IsLocked(string machineName) { if (string.IsNullOrEmpty(machineName)) { return false; } if (!_lockedUntil.TryGetValue(machineName, out var value)) { return false; } if (Time.time >= value) { _lockedUntil.Remove(machineName); return false; } return true; } public static float SecondsRemaining(string machineName) { if (string.IsNullOrEmpty(machineName)) { return 0f; } if (!_lockedUntil.TryGetValue(machineName, out var value)) { return 0f; } float num = value - Time.time; return (num > 0f) ? num : 0f; } } [CompilerGenerated] private static class <>O { public static PacketListener <0>__OnBJPing; public static PacketListener <1>__OnCasinoConfigRequest; public static PacketListener <2>__OnCasinoConfigSync; public static PacketListener <3>__OnClaimSeatRequest; public static PacketListener <4>__OnReleaseSeatRequest; public static PacketListener <5>__OnSeatClaimed; public static PacketListener <6>__OnSeatReleased; public static PacketListener <7>__OnReadyToggleRequest; public static PacketListener <8>__OnReadyChanged; public static PacketListener <9>__OnReadyRejected; public static PacketListener <10>__OnCardDealt; public static PacketListener <11>__OnCardFlipped; public static PacketListener <12>__OnRoundCleared; public static PacketListener <13>__OnTurnChanged; public static PacketListener <14>__OnStartRoundRequest; public static PacketListener <15>__OnHitRequest; public static PacketListener <16>__OnStandRequest; public static PacketListener <17>__OnActionRejected; public static PacketListener <18>__OnSlotSpinResult; public static PacketListener <19>__OnOwnerLuckRequest; public static PacketListener <20>__OnOwnerLuckChanged; } public const string PLUGIN_GUID = "dev.seth.atlysscasino"; private static bool _initialized; private static PropertyInfo? _playerSteamIdProp; private static FieldInfo? _playerSteamIdField; private static PropertyInfo? _playerIsHostProp; private static bool _reflectionResolved; private static Type? _steamLobbyType; private static FieldInfo? _steamLobbyCurrentField; private static FieldInfo? _steamLobbyCurrentLobbyIdField; private static Type? _csteamIdType; private static FieldInfo? _csteamIdSteamIdField; private static ConstructorInfo? _csteamIdCtor; private static Type? _steamMatchmakingType; private static MethodInfo? _getLobbyOwnerMethod; private static Type? _steamUserType; private static MethodInfo? _getMySteamIdMethod; private static bool _lobbyReflectionLogged; public static void Initialize() { //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_003b: Expected O, but got Unknown //IL_0051: 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: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //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_00e0: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Expected O, but got Unknown //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Expected O, but got Unknown //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Expected O, but got Unknown //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Expected O, but got Unknown //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Expected O, but got Unknown if (_initialized) { Plugin.Log.LogWarning("[BJNet] Initialize() called twice — ignoring."); return; } object obj = <>O.<0>__OnBJPing; if (obj == null) { PacketListener val = OnBJPing; <>O.<0>__OnBJPing = val; obj = (object)val; } CodeTalkerNetwork.RegisterListener((PacketListener)obj); object obj2 = <>O.<1>__OnCasinoConfigRequest; if (obj2 == null) { PacketListener val2 = OnCasinoConfigRequest; <>O.<1>__OnCasinoConfigRequest = val2; obj2 = (object)val2; } CodeTalkerNetwork.RegisterListener((PacketListener)obj2); object obj3 = <>O.<2>__OnCasinoConfigSync; if (obj3 == null) { PacketListener val3 = OnCasinoConfigSync; <>O.<2>__OnCasinoConfigSync = val3; obj3 = (object)val3; } CodeTalkerNetwork.RegisterListener((PacketListener)obj3); object obj4 = <>O.<3>__OnClaimSeatRequest; if (obj4 == null) { PacketListener val4 = OnClaimSeatRequest; <>O.<3>__OnClaimSeatRequest = val4; obj4 = (object)val4; } CodeTalkerNetwork.RegisterListener((PacketListener)obj4); object obj5 = <>O.<4>__OnReleaseSeatRequest; if (obj5 == null) { PacketListener val5 = OnReleaseSeatRequest; <>O.<4>__OnReleaseSeatRequest = val5; obj5 = (object)val5; } CodeTalkerNetwork.RegisterListener((PacketListener)obj5); object obj6 = <>O.<5>__OnSeatClaimed; if (obj6 == null) { PacketListener val6 = OnSeatClaimed; <>O.<5>__OnSeatClaimed = val6; obj6 = (object)val6; } CodeTalkerNetwork.RegisterListener((PacketListener)obj6); object obj7 = <>O.<6>__OnSeatReleased; if (obj7 == null) { PacketListener val7 = OnSeatReleased; <>O.<6>__OnSeatReleased = val7; obj7 = (object)val7; } CodeTalkerNetwork.RegisterListener((PacketListener)obj7); object obj8 = <>O.<7>__OnReadyToggleRequest; if (obj8 == null) { PacketListener val8 = OnReadyToggleRequest; <>O.<7>__OnReadyToggleRequest = val8; obj8 = (object)val8; } CodeTalkerNetwork.RegisterListener((PacketListener)obj8); object obj9 = <>O.<8>__OnReadyChanged; if (obj9 == null) { PacketListener val9 = OnReadyChanged; <>O.<8>__OnReadyChanged = val9; obj9 = (object)val9; } CodeTalkerNetwork.RegisterListener((PacketListener)obj9); object obj10 = <>O.<9>__OnReadyRejected; if (obj10 == null) { PacketListener val10 = OnReadyRejected; <>O.<9>__OnReadyRejected = val10; obj10 = (object)val10; } CodeTalkerNetwork.RegisterListener((PacketListener)obj10); object obj11 = <>O.<10>__OnCardDealt; if (obj11 == null) { PacketListener val11 = OnCardDealt; <>O.<10>__OnCardDealt = val11; obj11 = (object)val11; } CodeTalkerNetwork.RegisterListener((PacketListener)obj11); object obj12 = <>O.<11>__OnCardFlipped; if (obj12 == null) { PacketListener val12 = OnCardFlipped; <>O.<11>__OnCardFlipped = val12; obj12 = (object)val12; } CodeTalkerNetwork.RegisterListener((PacketListener)obj12); object obj13 = <>O.<12>__OnRoundCleared; if (obj13 == null) { PacketListener val13 = OnRoundCleared; <>O.<12>__OnRoundCleared = val13; obj13 = (object)val13; } CodeTalkerNetwork.RegisterListener((PacketListener)obj13); object obj14 = <>O.<13>__OnTurnChanged; if (obj14 == null) { PacketListener val14 = OnTurnChanged; <>O.<13>__OnTurnChanged = val14; obj14 = (object)val14; } CodeTalkerNetwork.RegisterListener((PacketListener)obj14); object obj15 = <>O.<14>__OnStartRoundRequest; if (obj15 == null) { PacketListener val15 = OnStartRoundRequest; <>O.<14>__OnStartRoundRequest = val15; obj15 = (object)val15; } CodeTalkerNetwork.RegisterListener((PacketListener)obj15); object obj16 = <>O.<15>__OnHitRequest; if (obj16 == null) { PacketListener val16 = OnHitRequest; <>O.<15>__OnHitRequest = val16; obj16 = (object)val16; } CodeTalkerNetwork.RegisterListener((PacketListener)obj16); object obj17 = <>O.<16>__OnStandRequest; if (obj17 == null) { PacketListener val17 = OnStandRequest; <>O.<16>__OnStandRequest = val17; obj17 = (object)val17; } CodeTalkerNetwork.RegisterListener((PacketListener)obj17); object obj18 = <>O.<17>__OnActionRejected; if (obj18 == null) { PacketListener val18 = OnActionRejected; <>O.<17>__OnActionRejected = val18; obj18 = (object)val18; } CodeTalkerNetwork.RegisterListener((PacketListener)obj18); object obj19 = <>O.<18>__OnSlotSpinResult; if (obj19 == null) { PacketListener val19 = OnSlotSpinResult; <>O.<18>__OnSlotSpinResult = val19; obj19 = (object)val19; } CodeTalkerNetwork.RegisterListener((PacketListener)obj19); object obj20 = <>O.<19>__OnOwnerLuckRequest; if (obj20 == null) { PacketListener val20 = OnOwnerLuckRequest; <>O.<19>__OnOwnerLuckRequest = val20; obj20 = (object)val20; } CodeTalkerNetwork.RegisterListener((PacketListener)obj20); object obj21 = <>O.<20>__OnOwnerLuckChanged; if (obj21 == null) { PacketListener val21 = OnOwnerLuckChanged; <>O.<20>__OnOwnerLuckChanged = val21; obj21 = (object)val21; } CodeTalkerNetwork.RegisterListener((PacketListener)obj21); _initialized = true; Plugin.Log.LogInfo("[BJNet] Code Talker listeners registered."); } public static bool AmHost() { ResolveReflection(); ulong num = TryGetLobbyOwnerSteam64(); if (num != 0) { ulong num2 = TryGetMyOwnSteam64(); if (num2 == 0) { num2 = GetLocalSteam64(); } if (num2 != 0) { return num == num2; } } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer != (Object)null && _playerIsHostProp != null) { try { object value = _playerIsHostProp.GetValue(mainPlayer); if (value is bool) { bool result = (bool)value; if (true) { return result; } } } catch { } } return false; } public static ulong GetLocalSteam64() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return 0uL; } return GetSteam64Of(mainPlayer); } public static Player? FindPlayerBySteam64(ulong steam64) { if (steam64 == 0) { return null; } Player[] array = Object.FindObjectsOfType(); Player[] array2 = array; foreach (Player val in array2) { if (GetSteam64Of(val) == steam64) { return val; } } return null; } public static void SendClaimSeatRequest(string tableName, int seatIndex, int bet) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ClaimSeatRequest { TableName = tableName2, SeatIndex = seatIndex, Bet = bet }); }, "SendClaimSeatRequest"); } public static void SendReleaseSeatRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReleaseSeatRequest { TableName = tableName2 }); }, "SendReleaseSeatRequest"); } public static void BroadcastSeatClaimed(string tableName, int seatIndex, ulong steam64, int bet, bool becameHost) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SeatClaimed { TableName = tableName2, SeatIndex = seatIndex, SeatedPlayerSteam64 = steam64, Bet = bet, BecameHost = becameHost }); }, "BroadcastSeatClaimed"); } public static void BroadcastSeatReleased(string tableName, int seatIndex, int newHostSeatIndex) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SeatReleased { TableName = tableName2, SeatIndex = seatIndex, NewHostSeatIndex = newHostSeatIndex }); }, "BroadcastSeatReleased"); } public static void SendReadyToggleRequest(string tableName, bool ready) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReadyToggleRequest { TableName = tableName2, Ready = ready }); }, "SendReadyToggleRequest"); } public static void BroadcastReadyChanged(string tableName, int seatIndex, bool ready) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReadyChanged { TableName = tableName2, SeatIndex = seatIndex, Ready = ready }); }, "BroadcastReadyChanged"); } public static void SendReadyRejected(ulong targetSteam64, string tableName, string reason) { string tableName2 = tableName; string reason2 = reason; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new ReadyRejected { TableName = tableName2, TargetSteam64 = targetSteam64, Reason = (reason2 ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendReadyRejected"); } public static void BroadcastCardDealt(string tableName, int seatIndex, Card card, bool faceUp, int indexInHand) { string tableName2 = tableName; Card card2 = card; if (card2 == null) { Plugin.Log.LogError("[BJNet] BroadcastCardDealt: card is null — aborting."); return; } Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CardDealt { TableName = tableName2, SeatIndex = seatIndex, Suit = (int)card2.Suit, Rank = (int)card2.Rank, PrefabName = card2.PrefabName, FaceUp = faceUp, IndexInHand = indexInHand }); }, "BroadcastCardDealt"); } public static void BroadcastCardFlipped(string tableName, int seatIndex, int indexInHand, bool faceUp) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CardFlipped { TableName = tableName2, SeatIndex = seatIndex, IndexInHand = indexInHand, FaceUp = faceUp }); }, "BroadcastCardFlipped"); } public static void BroadcastRoundCleared(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoundCleared { TableName = tableName2 }); }, "BroadcastRoundCleared"); } public static void BroadcastTurnChanged(string tableName, int seatIndex) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new TurnChanged { TableName = tableName2, SeatIndex = seatIndex }); }, "BroadcastTurnChanged"); } public static void BroadcastSlotSpinResult(string machineName, int reel0, int reel1, int reel2) { string machineName2 = machineName; SlotMachineLocks.Lock(machineName2); Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SlotSpinResult { MachineName = machineName2, SpinnerSteam64 = GetLocalSteam64(), Reel0 = reel0, Reel1 = reel1, Reel2 = reel2 }); }, "BroadcastSlotSpinResult"); } public static void SendStartRoundRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new StartRoundRequest { TableName = tableName2 }); }, "SendStartRoundRequest"); } public static void SendHitRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new HitRequest { TableName = tableName2 }); }, "SendHitRequest"); } public static void SendStandRequest(string tableName) { string tableName2 = tableName; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new StandRequest { TableName = tableName2 }); }, "SendStandRequest"); } public static void SendOwnerLuckRequest(bool enabled) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new OwnerLuckRequest { Enabled = enabled }); }, "SendOwnerLuckRequest"); } public static void BroadcastOwnerLuckChanged(bool enabled, ulong ownerSteam64) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new OwnerLuckChanged { OwnerSteam64 = ownerSteam64, Enabled = enabled }); }, "BroadcastOwnerLuckChanged"); } public static void SendCasinoConfigRequest() { if (!AmHost()) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CasinoConfigRequest()); }, "SendCasinoConfigRequest"); } } public static void BroadcastCasinoConfigSync() { if (Plugin.IsHeadlessServer || AmHost()) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)BuildCasinoConfigSync()); }, "BroadcastCasinoConfigSync"); } } private static void SendCasinoConfigSync(ulong targetSteam64) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)BuildCasinoConfigSync(), (CompressionType)0, CompressionLevel.Fastest); }, "SendCasinoConfigSync"); } public static void SendActionRejected(ulong targetSteam64, string tableName, string action, string reason) { string tableName2 = tableName; string action2 = action; string reason2 = reason; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new ActionRejected { TableName = tableName2, TargetSteam64 = targetSteam64, Action = (action2 ?? "action"), Reason = (reason2 ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendActionRejected"); } public static void SendPing(string note) { string note2 = note; Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new BJPingPacket { Note = (note2 ?? string.Empty) }); }, "SendPing"); Plugin.Log.LogInfo("[BJNet] Sent ping packet (note='" + note2 + "')."); } private static void OnBJPing(PacketHeader header, PacketBase packet) { if (packet is BJPingPacket bJPingPacket) { Plugin.Log.LogInfo($"[BJNet] Received ping: fromHost={header.SenderIsLobbyOwner}, " + $"senderSteam64={header.SenderID}, note='{bJPingPacket.Note}'"); } } private static void OnCasinoConfigRequest(PacketHeader header, PacketBase packet) { if (packet is CasinoConfigRequest && (Plugin.IsHeadlessServer || AmHost())) { SendCasinoConfigSync(header.SenderID); Plugin.Log.LogInfo($"[BJNet] Sent casino config sync to {header.SenderID}."); } } private static void OnCasinoConfigSync(PacketHeader header, PacketBase packet) { if (packet is CasinoConfigSync casinoConfigSync) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring CasinoConfigSync from non-host sender={header.SenderID}."); } else if (!AmHost()) { CasinoConfig.ApplyHostGameplaySync(casinoConfigSync.EntryFeeCrowns, casinoConfigSync.WalkAwayPenaltyCrowns, casinoConfigSync.BlackjackTurnTimeoutSeconds, casinoConfigSync.RouletteAfkTimeoutSeconds, casinoConfigSync.SlotsPayoutMultiplier, casinoConfigSync.BlackjackPayoutMultiplier, casinoConfigSync.RoulettePayoutMultiplier, casinoConfigSync.AllowedBetAmountsCsv); Plugin.Log.LogInfo("[BJNet] Applied host casino config sync " + $"(entry={casinoConfigSync.EntryFeeCrowns}, walkAway={casinoConfigSync.WalkAwayPenaltyCrowns}, " + $"bjTimeout={casinoConfigSync.BlackjackTurnTimeoutSeconds}, " + $"rouletteTimeout={casinoConfigSync.RouletteAfkTimeoutSeconds}, " + $"slotPayout={casinoConfigSync.SlotsPayoutMultiplier:F2}, " + $"bjPayout={casinoConfigSync.BlackjackPayoutMultiplier:F2}, " + $"roulettePayout={casinoConfigSync.RoulettePayoutMultiplier:F2}, " + "bets=" + casinoConfigSync.AllowedBetAmountsCsv + ")."); } } } private static void OnOwnerLuckRequest(PacketHeader header, PacketBase packet) { if (AmHost() && packet is OwnerLuckRequest ownerLuckRequest) { if (!Plugin.IsCasinoOwner(header.SenderID)) { Plugin.Log.LogWarning($"[BJNet] Rejected OwnerLuckRequest from non-owner sender={header.SenderID}."); return; } Plugin.OwnerLuckEnabled = ownerLuckRequest.Enabled; BroadcastOwnerLuckChanged(ownerLuckRequest.Enabled, header.SenderID); Plugin.Log.LogInfo(string.Format("[BJNet] Owner luck {0} by owner {1}.", ownerLuckRequest.Enabled ? "enabled" : "disabled", header.SenderID)); } } private static void OnOwnerLuckChanged(PacketHeader header, PacketBase packet) { if (packet is OwnerLuckChanged ownerLuckChanged) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring OwnerLuckChanged from non-host sender={header.SenderID}."); return; } if (!Plugin.IsCasinoOwner(ownerLuckChanged.OwnerSteam64)) { Plugin.Log.LogWarning($"[BJNet] Ignoring OwnerLuckChanged for unexpected owner steam64={ownerLuckChanged.OwnerSteam64}."); return; } Plugin.OwnerLuckEnabled = ownerLuckChanged.Enabled; Plugin.Log.LogInfo("[BJNet] Owner luck sync -> " + (ownerLuckChanged.Enabled ? "enabled" : "disabled") + "."); } } private static void OnClaimSeatRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is ClaimSeatRequest claimSeatRequest)) { return; } BlackjackTable blackjackTable = FindTable(claimSeatRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] Host: ClaimSeatRequest for unknown table '" + claimSeatRequest.TableName + "' — ignoring."); return; } Player val = FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[BJNet] Host: could not resolve sender steam64={header.SenderID} to Player — ignoring."); } else { if (claimSeatRequest.SeatIndex < 0 || claimSeatRequest.SeatIndex >= 5) { return; } if (!blackjackTable.IsSeatVacant(claimSeatRequest.SeatIndex)) { Plugin.Log.LogInfo($"[BJNet] Host: rejecting claim for seat {claimSeatRequest.SeatIndex} — not vacant."); return; } if (blackjackTable.GetSeatForPlayer(val) != -1) { Plugin.Log.LogInfo("[BJNet] Host: rejecting claim — sender already seated."); return; } bool becameHost = blackjackTable.HostSeatIndex == -1; if (blackjackTable.TryClaimSeat(claimSeatRequest.SeatIndex, val)) { blackjackTable.Hands[claimSeatRequest.SeatIndex].Bet = claimSeatRequest.Bet; BroadcastSeatClaimed(claimSeatRequest.TableName, claimSeatRequest.SeatIndex, header.SenderID, claimSeatRequest.Bet, becameHost); } } } private static void OnReleaseSeatRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is ReleaseSeatRequest releaseSeatRequest)) { return; } BlackjackTable blackjackTable = FindTable(releaseSeatRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { return; } Player val = FindPlayerBySteam64(header.SenderID); if (!((Object)(object)val == (Object)null)) { int seatForPlayer = blackjackTable.GetSeatForPlayer(val); if (seatForPlayer < 0) { Plugin.Log.LogInfo("[BJNet] Host: ignoring release — sender not seated at '" + releaseSeatRequest.TableName + "'."); return; } blackjackTable.ReleaseSeat(seatForPlayer); int hostSeatIndex = blackjackTable.HostSeatIndex; BroadcastSeatReleased(releaseSeatRequest.TableName, seatForPlayer, hostSeatIndex); } } private static void OnSeatClaimed(PacketHeader header, PacketBase packet) { if (!(packet is SeatClaimed seatClaimed)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring SeatClaimed from non-host sender={header.SenderID}."); return; } BlackjackTable blackjackTable = FindTable(seatClaimed.TableName); if ((Object)(object)blackjackTable == (Object)null) { return; } if (AmHost() && !blackjackTable.IsSeatVacant(seatClaimed.SeatIndex)) { Plugin.Log.LogInfo($"[BJNet] SeatClaimed (echo): seat {seatClaimed.SeatIndex} already set locally."); } else { Player val = FindPlayerBySteam64(seatClaimed.SeatedPlayerSteam64); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[BJNet] SeatClaimed: can't find Player for steam64={seatClaimed.SeatedPlayerSteam64}. " + "Seat will show empty locally until the player object spawns."); return; } if (!blackjackTable.ApplySeatClaimed(seatClaimed.SeatIndex, val, seatClaimed.Bet, seatClaimed.BecameHost)) { Plugin.Log.LogWarning($"[BJNet] ApplySeatClaimed failed for seat {seatClaimed.SeatIndex} at '{seatClaimed.TableName}'."); return; } } ulong localSteam = GetLocalSteam64(); int num = 0; for (int i = 0; i < 5; i++) { if ((Object)(object)blackjackTable.GetSeatedPlayer(i) != (Object)null) { num++; } } if (seatClaimed.SeatedPlayerSteam64 != 0L && seatClaimed.SeatedPlayerSteam64 == localSteam) { try { Plugin.ShowHUD($"Claimed seat {seatClaimed.SeatIndex + 1} ({seatClaimed.Bet} Crown bet). " + $"Players: {num}/{5}. " + "Use /ready when set."); return; } catch { return; } } if (!IsLocalPlayerSeatedAt(blackjackTable)) { return; } try { Plugin.ShowHUD($"Seat {seatClaimed.SeatIndex + 1} was claimed. " + $"Players: {num}/{5}."); } catch { } } private static void OnSeatReleased(PacketHeader header, PacketBase packet) { if (!(packet is SeatReleased seatReleased)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring SeatReleased from non-host sender={header.SenderID}."); return; } BlackjackTable blackjackTable = FindTable(seatReleased.TableName); if (!((Object)(object)blackjackTable == (Object)null)) { if (AmHost() && blackjackTable.IsSeatVacant(seatReleased.SeatIndex)) { Plugin.Log.LogInfo($"[BJNet] SeatReleased (echo): seat {seatReleased.SeatIndex} already vacant locally."); } else { blackjackTable.ApplySeatReleased(seatReleased.SeatIndex, seatReleased.NewHostSeatIndex); } } } private static void OnReadyToggleRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is ReadyToggleRequest readyToggleRequest)) { return; } BlackjackTable blackjackTable = FindTable(readyToggleRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] Host: ReadyToggleRequest for unknown table '" + readyToggleRequest.TableName + "' — ignoring."); return; } Player val = FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[BJNet] Host: could not resolve sender steam64={header.SenderID} for /ready — ignoring."); return; } int seatForPlayer = blackjackTable.GetSeatForPlayer(val); if (seatForPlayer < 0) { Plugin.Log.LogInfo("[BJNet] Host: rejecting /ready — sender not seated at '" + readyToggleRequest.TableName + "'."); SendReadyRejected(header.SenderID, readyToggleRequest.TableName, "Not seated at this table."); return; } if (blackjackTable.RoundInProgress) { Plugin.Log.LogInfo("[BJNet] Host: rejecting /ready — round in progress."); SendReadyRejected(header.SenderID, readyToggleRequest.TableName, "Round in progress — can't change ready state."); return; } if (readyToggleRequest.Ready) { int bet = blackjackTable.Hands[seatForPlayer].Bet; int num = ReadCrowns(val); if (num < bet) { Plugin.Log.LogInfo($"[BJNet] Host: rejecting /ready for seat {seatForPlayer} — " + $"insufficient funds ({num} < {bet})."); SendReadyRejected(header.SenderID, readyToggleRequest.TableName, $"Can't afford bet ({num} < {bet})."); return; } } blackjackTable.SetPlayerReady(seatForPlayer, readyToggleRequest.Ready); BroadcastReadyChanged(readyToggleRequest.TableName, seatForPlayer, readyToggleRequest.Ready); Plugin.Log.LogInfo($"[BJNet] Host: /ready accepted for seat {seatForPlayer} = {readyToggleRequest.Ready}."); } private static void OnReadyChanged(PacketHeader header, PacketBase packet) { if (!(packet is ReadyChanged readyChanged)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring ReadyChanged from non-host sender={header.SenderID}."); return; } BlackjackTable blackjackTable = FindTable(readyChanged.TableName); if ((Object)(object)blackjackTable == (Object)null) { return; } if (AmHost() && blackjackTable.IsPlayerReady(readyChanged.SeatIndex) == readyChanged.Ready) { Plugin.Log.LogInfo($"[BJNet] ReadyChanged (echo): seat {readyChanged.SeatIndex} already {readyChanged.Ready}."); return; } blackjackTable.SetPlayerReady(readyChanged.SeatIndex, readyChanged.Ready); Plugin.Log.LogInfo($"[BJNet] ApplyReadyChanged: seat {readyChanged.SeatIndex} at '{readyChanged.TableName}' " + $"-> ready={readyChanged.Ready}."); int num = 0; int num2 = 0; for (int i = 0; i < 5; i++) { if (!((Object)(object)blackjackTable.GetSeatedPlayer(i) == (Object)null)) { num2++; if (blackjackTable.IsPlayerReady(i)) { num++; } } } bool flag = num == num2 && num2 > 0; string text = (flag ? $"All {num2} players ready!" : $"{num}/{num2} players ready."); string text2 = $"Seat {readyChanged.SeatIndex + 1}"; string text3 = (readyChanged.Ready ? "READY" : "not ready"); if (!IsLocalPlayerSeatedAt(blackjackTable)) { return; } try { ChatBehaviour val = Object.FindObjectOfType(); if ((Object)(object)val != (Object)null) { Plugin.ShowGameFeed(val, "[Blackjack] " + text2 + " is " + text3 + ". " + text + (flag ? " Table host: use /start to deal!" : "")); } } catch { } try { if (flag) { Plugin.ShowHUD($"All {num2} players ready! Table host: /start to deal."); } } catch { } } private static void OnReadyRejected(PacketHeader header, PacketBase packet) { if (!(packet is ReadyRejected readyRejected)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring ReadyRejected from non-host sender={header.SenderID}."); return; } ulong localSteam = GetLocalSteam64(); if (readyRejected.TargetSteam64 == 0L || readyRejected.TargetSteam64 == localSteam) { try { Plugin.ShowHUDError("/ready rejected: " + readyRejected.Reason); } catch { } Plugin.Log.LogInfo("[BJNet] /ready rejected by host: " + readyRejected.Reason); } } private static void OnCardDealt(PacketHeader header, PacketBase packet) { if (!(packet is CardDealt cardDealt)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring CardDealt from non-host sender={header.SenderID}."); } else if (!AmHost()) { BlackjackTable blackjackTable = FindTable(cardDealt.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] CardDealt for unknown table '" + cardDealt.TableName + "' — ignoring."); return; } if (!Enum.IsDefined(typeof(Suit), cardDealt.Suit) || !Enum.IsDefined(typeof(Rank), cardDealt.Rank)) { Plugin.Log.LogError($"[BJNet] CardDealt: invalid Suit={cardDealt.Suit} / Rank={cardDealt.Rank} " + "at '" + cardDealt.TableName + "'. Card NOT spawned."); return; } Card card = new Card((Suit)cardDealt.Suit, (Rank)cardDealt.Rank); blackjackTable.ApplyCardDealt(cardDealt.SeatIndex, card, cardDealt.FaceUp, cardDealt.IndexInHand, cardDealt.PrefabName); } } private static void OnCardFlipped(PacketHeader header, PacketBase packet) { if (!(packet is CardFlipped cardFlipped)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring CardFlipped from non-host sender={header.SenderID}."); } else if (!AmHost()) { BlackjackTable blackjackTable = FindTable(cardFlipped.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] CardFlipped for unknown table '" + cardFlipped.TableName + "' — ignoring."); } else { blackjackTable.ApplyCardFlipped(cardFlipped.SeatIndex, cardFlipped.IndexInHand, cardFlipped.FaceUp); } } } private static void OnRoundCleared(PacketHeader header, PacketBase packet) { if (!(packet is RoundCleared roundCleared)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring RoundCleared from non-host sender={header.SenderID}."); } else if (!AmHost()) { BlackjackTable blackjackTable = FindTable(roundCleared.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] RoundCleared for unknown table '" + roundCleared.TableName + "' — ignoring."); } else { blackjackTable.ApplyRoundCleared(); } } } private static void OnTurnChanged(PacketHeader header, PacketBase packet) { if (!(packet is TurnChanged turnChanged)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring TurnChanged from non-host sender={header.SenderID}."); } else if (!AmHost()) { BlackjackTable blackjackTable = FindTable(turnChanged.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] TurnChanged for unknown table '" + turnChanged.TableName + "' — ignoring."); return; } blackjackTable.ApplyTurnChanged(turnChanged.SeatIndex); Plugin.Log.LogInfo("[BJNet] ApplyTurnChanged: '" + turnChanged.TableName + "' " + $"-> seatIndex={turnChanged.SeatIndex}."); } } private static void OnStartRoundRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is StartRoundRequest startRoundRequest)) { return; } BlackjackTable blackjackTable = FindTable(startRoundRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] Host: StartRoundRequest for unknown table '" + startRoundRequest.TableName + "' — ignoring."); return; } Player val = FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning($"[BJNet] Host: could not resolve sender steam64={header.SenderID} for /start — ignoring."); return; } int seatForPlayer = blackjackTable.GetSeatForPlayer(val); if (seatForPlayer < 0) { SendActionRejected(header.SenderID, startRoundRequest.TableName, "start", "Not seated at this table."); return; } if (!blackjackTable.IsHost(seatForPlayer)) { SendActionRejected(header.SenderID, startRoundRequest.TableName, "start", "Only the table host (first seated) can /start."); return; } if (blackjackTable.RoundInProgress) { SendActionRejected(header.SenderID, startRoundRequest.TableName, "start", "Round already in progress."); return; } if (!blackjackTable.AllSeatedPlayersReady) { SendActionRejected(header.SenderID, startRoundRequest.TableName, "start", "Not all seated players are /ready."); return; } Plugin.Log.LogInfo($"[BJNet] Host: accepted /start from seat {seatForPlayer} — running StartRound."); BlackjackTable.StartRoundResult startRoundResult = blackjackTable.StartRound(); if (!startRoundResult.Success) { SendActionRejected(header.SenderID, startRoundRequest.TableName, "start", "Could not start: " + startRoundResult.Reason); Plugin.Log.LogInfo("[BJNet] Host: StartRound failed: " + startRoundResult.Reason); } else { Plugin.Log.LogInfo($"[BJNet] Host: StartRound succeeded — first turn seat={startRoundResult.FirstTurnSeat}."); } } private static void OnHitRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is HitRequest hitRequest)) { return; } BlackjackTable blackjackTable = FindTable(hitRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] Host: HitRequest for unknown table '" + hitRequest.TableName + "' — ignoring."); return; } Player val = FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { return; } int seatForPlayer = blackjackTable.GetSeatForPlayer(val); if (seatForPlayer < 0) { SendActionRejected(header.SenderID, hitRequest.TableName, "hit", "Not seated at this table."); return; } BlackjackTable.HitResult hitResult = blackjackTable.PlayerHit(seatForPlayer); if (!hitResult.Success) { SendActionRejected(header.SenderID, hitRequest.TableName, "hit", hitResult.Reason ?? "Hit rejected."); } else { Plugin.Log.LogInfo($"[BJNet] Host: accepted /hit from seat {seatForPlayer} — " + $"value={hitResult.HandValue}, busted={hitResult.Busted}, autoStood={hitResult.AutoStood}."); } } private static void OnStandRequest(PacketHeader header, PacketBase packet) { if (!AmHost() || !(packet is StandRequest standRequest)) { return; } BlackjackTable blackjackTable = FindTable(standRequest.TableName); if ((Object)(object)blackjackTable == (Object)null) { Plugin.Log.LogWarning("[BJNet] Host: StandRequest for unknown table '" + standRequest.TableName + "' — ignoring."); return; } Player val = FindPlayerBySteam64(header.SenderID); if ((Object)(object)val == (Object)null) { return; } int seatForPlayer = blackjackTable.GetSeatForPlayer(val); if (seatForPlayer < 0) { SendActionRejected(header.SenderID, standRequest.TableName, "stand", "Not seated at this table."); return; } BlackjackTable.StandResult standResult = blackjackTable.PlayerStand(seatForPlayer); if (!standResult.Success) { SendActionRejected(header.SenderID, standRequest.TableName, "stand", standResult.Reason ?? "Stand rejected."); } else { Plugin.Log.LogInfo($"[BJNet] Host: accepted /stand from seat {seatForPlayer} — " + $"nextTurn={standResult.NextTurnSeat}, resolved={standResult.RoundResolved}."); } } private static void OnActionRejected(PacketHeader header, PacketBase packet) { if (!(packet is ActionRejected actionRejected)) { return; } if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[BJNet] Ignoring ActionRejected from non-host sender={header.SenderID}."); return; } ulong localSteam = GetLocalSteam64(); if (actionRejected.TargetSteam64 == 0L || actionRejected.TargetSteam64 == localSteam) { string text = (string.IsNullOrEmpty(actionRejected.Action) ? "action" : actionRejected.Action); try { Plugin.ShowHUDError("/" + text + " rejected: " + actionRejected.Reason); } catch { } Plugin.Log.LogInfo("[BJNet] /" + text + " rejected by host: " + actionRejected.Reason); } } private static void OnSlotSpinResult(PacketHeader header, PacketBase packet) { if (!(packet is SlotSpinResult slotSpinResult)) { return; } SlotMachineLocks.Lock(slotSpinResult.MachineName); ulong localSteam = GetLocalSteam64(); if (slotSpinResult.SpinnerSteam64 == 0L || slotSpinResult.SpinnerSteam64 != localSteam) { SlotMachine slotMachine = FindSlotMachine(slotSpinResult.MachineName); if ((Object)(object)slotMachine == (Object)null) { Plugin.Log.LogWarning("[BJNet] SlotSpinResult for unknown machine '" + slotSpinResult.MachineName + "' " + $"— ignoring. (Spinner steam64={slotSpinResult.SpinnerSteam64})"); return; } Plugin.Log.LogInfo("[BJNet] Remote slot spin on '" + slotSpinResult.MachineName + "' from " + $"steam64={slotSpinResult.SpinnerSteam64} -> [{slotSpinResult.Reel0}, {slotSpinResult.Reel1}, {slotSpinResult.Reel2}]."); slotMachine.ApplyRemoteSpin(slotSpinResult.Reel0, slotSpinResult.Reel1, slotSpinResult.Reel2); } } private static SlotMachine? FindSlotMachine(string machineName) { if (string.IsNullOrEmpty(machineName)) { return null; } SlotMachine[] array = Object.FindObjectsOfType(); SlotMachine[] array2 = array; foreach (SlotMachine slotMachine in array2) { if (((Object)slotMachine).name == machineName) { return slotMachine; } } return null; } private static CasinoConfigSync BuildCasinoConfigSync() { return new CasinoConfigSync { EntryFeeCrowns = CasinoConfig.EntryFeeCrowns, WalkAwayPenaltyCrowns = CasinoConfig.WalkAwayPenaltyCrowns, BlackjackTurnTimeoutSeconds = (int)CasinoConfig.BlackjackTurnTimeoutSeconds, RouletteAfkTimeoutSeconds = (int)CasinoConfig.RouletteAfkTimeoutSeconds, SlotsPayoutMultiplier = CasinoConfig.SlotsPayoutMultiplier, BlackjackPayoutMultiplier = CasinoConfig.BlackjackPayoutMultiplier, RoulettePayoutMultiplier = CasinoConfig.RoulettePayoutMultiplier, AllowedBetAmountsCsv = CasinoConfig.AllowedBetAmountsCsv }; } private static BlackjackTable? FindTable(string tableName) { if (string.IsNullOrEmpty(tableName)) { return null; } BlackjackTable[] array = Object.FindObjectsOfType(); BlackjackTable[] array2 = array; foreach (BlackjackTable blackjackTable in array2) { if (((Object)blackjackTable).name == tableName) { return blackjackTable; } } return null; } private static int ReadCrowns(Player p) { if ((Object)(object)p == (Object)null) { return 0; } PlayerInventory component = ((Component)p).GetComponent(); if ((Object)(object)component == (Object)null) { return 0; } return component._heldCurrency; } private static ulong GetSteam64Of(Player p) { ResolveReflection(); if (_playerSteamIdProp != null) { try { object value = _playerSteamIdProp.GetValue(p); ulong num = TryExtractSteam64(value); if (num != 0) { return num; } } catch { } } if (_playerSteamIdField != null) { try { object value2 = _playerSteamIdField.GetValue(p); ulong num2 = TryExtractSteam64(value2); if (num2 != 0) { return num2; } } catch { } } return 0uL; } private static ulong TryExtractSteam64(object? val) { if (val == null) { return 0uL; } if (val is ulong result) { return result; } if (val is long num && num > 0) { return (ulong)num; } if (val is uint num2) { return num2; } if (val is int num3 && num3 > 0) { return (uint)num3; } Type type = val.GetType(); FieldInfo field = type.GetField("m_SteamID", BindingFlags.Instance | BindingFlags.Public); if (field != null) { try { object value = field.GetValue(val); if (value is ulong) { ulong result2 = (ulong)value; if (true) { return result2; } } } catch { } } if (val is string s && ulong.TryParse(s, out var result3)) { return result3; } return 0uL; } private static ulong TryGetCurrentLobbyId() { if (_steamLobbyCurrentField == null || _steamLobbyCurrentLobbyIdField == null) { return 0uL; } try { object value = _steamLobbyCurrentField.GetValue(null); if (value == null) { return 0uL; } object value2 = _steamLobbyCurrentLobbyIdField.GetValue(value); if (value2 == null) { return 0uL; } if (value2 is ulong result) { return result; } try { return Convert.ToUInt64(value2); } catch { return 0uL; } } catch (Exception ex) { Plugin.Log.LogWarning("[BJNet] TryGetCurrentLobbyId failed: " + ex.Message); return 0uL; } } private static ulong TryGetLobbyOwnerSteam64() { ulong num = TryGetCurrentLobbyId(); if (num == 0) { return 0uL; } if (_csteamIdCtor == null || _getLobbyOwnerMethod == null || _csteamIdSteamIdField == null) { return 0uL; } try { object obj = _csteamIdCtor.Invoke(new object[1] { num }); object obj2 = _getLobbyOwnerMethod.Invoke(null, new object[1] { obj }); if (obj2 == null) { return 0uL; } object value = _csteamIdSteamIdField.GetValue(obj2); if (value == null) { return 0uL; } if (value is ulong result) { return result; } try { return Convert.ToUInt64(value); } catch { return 0uL; } } catch (Exception ex) { Plugin.Log.LogWarning("[BJNet] TryGetLobbyOwnerSteam64 failed: " + ex.Message); return 0uL; } } private static ulong TryGetMyOwnSteam64() { if (_getMySteamIdMethod == null || _csteamIdSteamIdField == null) { return 0uL; } try { object obj = _getMySteamIdMethod.Invoke(null, null); if (obj == null) { return 0uL; } object value = _csteamIdSteamIdField.GetValue(obj); if (value == null) { return 0uL; } if (value is ulong result) { return result; } try { return Convert.ToUInt64(value); } catch { return 0uL; } } catch (Exception ex) { Plugin.Log.LogWarning("[BJNet] TryGetMyOwnSteam64 failed: " + ex.Message); return 0uL; } } private static void ResolveReflection() { if (_reflectionResolved) { return; } _reflectionResolved = true; Type typeFromHandle = typeof(Player); string[] array = new string[5] { "Network_steamID", "Network_SteamID", "SteamID", "steamID", "_steamID" }; string[] array2 = array; foreach (string text in array2) { PropertyInfo property = typeFromHandle.GetProperty(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { _playerSteamIdProp = property; Plugin.Log.LogInfo("[BJNet] Resolved Player Steam ID property: " + text); break; } } if (_playerSteamIdProp == null) { string[] array3 = new string[3] { "_steamID", "steamID", "_cachedSteamID" }; string[] array4 = array3; foreach (string text2 in array4) { FieldInfo field = typeFromHandle.GetField(text2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { _playerSteamIdField = field; Plugin.Log.LogInfo("[BJNet] Resolved Player Steam ID field: " + text2); break; } } } if (_playerSteamIdProp == null && _playerSteamIdField == null) { Plugin.Log.LogError("[BJNet] Could not find Steam ID accessor on Player. Seat ownership will not sync across clients."); } string[] array5 = new string[3] { "Network_isHostPlayer", "isHostPlayer", "IsHostPlayer" }; string[] array6 = array5; foreach (string text3 in array6) { PropertyInfo property2 = typeFromHandle.GetProperty(text3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property2 != null && property2.CanRead) { _playerIsHostProp = property2; Plugin.Log.LogInfo("[BJNet] Resolved Player host-flag property (fallback): " + text3); break; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } catch { continue; } Type[] array7 = types; foreach (Type type in array7) { if (!(type == null)) { if (_steamLobbyType == null && (type.FullName == "SteamLobby" || type.Name == "SteamLobby")) { _steamLobbyType = type; } if (_csteamIdType == null && type.FullName == "Steamworks.CSteamID") { _csteamIdType = type; } if (_steamMatchmakingType == null && type.FullName == "Steamworks.SteamMatchmaking") { _steamMatchmakingType = type; } if (_steamUserType == null && type.FullName == "Steamworks.SteamUser") { _steamUserType = type; } } } if (_steamLobbyType != null && _csteamIdType != null && _steamMatchmakingType != null && _steamUserType != null) { break; } } if (_steamLobbyType != null) { _steamLobbyCurrentField = _steamLobbyType.GetField("_current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _steamLobbyCurrentLobbyIdField = _steamLobbyType.GetField("_currentLobbyID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_csteamIdType != null) { _csteamIdSteamIdField = _csteamIdType.GetField("m_SteamID", BindingFlags.Instance | BindingFlags.Public); _csteamIdCtor = _csteamIdType.GetConstructor(new Type[1] { typeof(ulong) }); } if (_steamMatchmakingType != null && _csteamIdType != null) { _getLobbyOwnerMethod = _steamMatchmakingType.GetMethod("GetLobbyOwner", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { _csteamIdType }, null); } if (_steamUserType != null) { _getMySteamIdMethod = _steamUserType.GetMethod("GetSteamID", BindingFlags.Static | BindingFlags.Public); } if (!_lobbyReflectionLogged) { _lobbyReflectionLogged = true; Plugin.Log.LogInfo("[BJNet] Lobby host reflection: SteamLobby=" + ((_steamLobbyType != null) ? "OK" : "MISSING") + ", SteamLobby._current=" + ((_steamLobbyCurrentField != null) ? "OK" : "MISSING") + ", SteamLobby._currentLobbyID=" + ((_steamLobbyCurrentLobbyIdField != null) ? "OK" : "MISSING") + ", CSteamID=" + ((_csteamIdType != null) ? "OK" : "MISSING") + ", GetLobbyOwner=" + ((_getLobbyOwnerMethod != null) ? "OK" : "MISSING") + ", GetSteamID=" + ((_getMySteamIdMethod != null) ? "OK" : "MISSING") + "."); if (_steamLobbyType == null || _steamLobbyCurrentField == null || _steamLobbyCurrentLobbyIdField == null || _csteamIdType == null || _getLobbyOwnerMethod == null) { Plugin.Log.LogWarning("[BJNet] Lobby-owner reflection incomplete — AmHost() will fall back to Player.Network_isHostPlayer. If you see this in multiplayer, sync may misbehave."); } } if (_playerIsHostProp == null && _steamLobbyType == null) { Plugin.Log.LogError("[BJNet] Both Steam-matchmaking and Player.Network_isHostPlayer host-detection paths failed to resolve. AmHost() will return FALSE for everyone — multiplayer sync will not work."); } } private static void Safe(Action act, string label) { try { act(); } catch (Exception ex) { Plugin.Log.LogError("[BJNet] " + label + " failed: " + ex.Message); } } public static bool IsLocalPlayerSeatedAt(BlackjackTable table) { if ((Object)(object)table == (Object)null) { return false; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } return table.GetSeatForPlayer(mainPlayer) >= 0; } } public class BJPingPacket : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string Note { get; set; } = string.Empty; } public class ClaimSeatRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public int Bet { get; set; } } public class ReleaseSeatRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class ReadyToggleRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public bool Ready { get; set; } } public class StartRoundRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class HitRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class StandRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class OwnerLuckRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public bool Enabled { get; set; } } public class CasinoConfigRequest : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; } public class SeatClaimed : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public ulong SeatedPlayerSteam64 { get; set; } [JsonProperty] public int Bet { get; set; } [JsonProperty] public bool BecameHost { get; set; } } public class SeatReleased : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public int NewHostSeatIndex { get; set; } = -1; } public class ReadyChanged : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public bool Ready { get; set; } } public class CardDealt : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public int Suit { get; set; } [JsonProperty] public int Rank { get; set; } [JsonProperty] public string PrefabName { get; set; } = string.Empty; [JsonProperty] public bool FaceUp { get; set; } [JsonProperty] public int IndexInHand { get; set; } } public class CardFlipped : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } [JsonProperty] public int IndexInHand { get; set; } [JsonProperty] public bool FaceUp { get; set; } } public class RoundCleared : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; } public class TurnChanged : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public int SeatIndex { get; set; } } public class OwnerLuckChanged : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public ulong OwnerSteam64 { get; set; } [JsonProperty] public bool Enabled { get; set; } } public class CasinoConfigSync : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public int EntryFeeCrowns { get; set; } [JsonProperty] public int WalkAwayPenaltyCrowns { get; set; } [JsonProperty] public int BlackjackTurnTimeoutSeconds { get; set; } [JsonProperty] public int RouletteAfkTimeoutSeconds { get; set; } [JsonProperty] public float SlotsPayoutMultiplier { get; set; } [JsonProperty] public float BlackjackPayoutMultiplier { get; set; } [JsonProperty] public float RoulettePayoutMultiplier { get; set; } [JsonProperty] public string AllowedBetAmountsCsv { get; set; } = string.Empty; } public class SlotSpinResult : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string MachineName { get; set; } = string.Empty; [JsonProperty] public ulong SpinnerSteam64 { get; set; } [JsonProperty] public int Reel0 { get; set; } [JsonProperty] public int Reel1 { get; set; } [JsonProperty] public int Reel2 { get; set; } } public class ReadyRejected : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong TargetSteam64 { get; set; } [JsonProperty] public string Reason { get; set; } = string.Empty; } public class ActionRejected : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string TableName { get; set; } = string.Empty; [JsonProperty] public ulong TargetSteam64 { get; set; } [JsonProperty] public string Action { get; set; } = string.Empty; [JsonProperty] public string Reason { get; set; } = string.Empty; } }